lots o progress

This commit is contained in:
2023-09-11 05:08:12 -04:00
parent 323b70eb9f
commit ac98a90222
8 changed files with 322 additions and 115 deletions

37
config.go Normal file
View File

@@ -0,0 +1,37 @@
package main
import (
"fmt"
"os"
)
func makeDirs() {
err := os.MkdirAll("/etc/certman", 0775)
if err != nil {
if !os.IsExist(err) {
fmt.Println("Unable to create config directory")
os.Exit(1)
}
}
err = os.Mkdir("/etc/certman/conf", 0775)
if err != nil {
if !os.IsExist(err) {
fmt.Println("Unable to create config directory")
os.Exit(1)
}
}
}
// TODO make domain level configs override global config
func createConfig() {
confPath := "/etc/certman/certman.conf"
configBytes := []byte("[Git]\nhost = github\nserver = https://gitea.instance.com\nusername = user\napi_token = xxxxxxxxxxxxxxxx\norg_name = org\ntemplate_name = template\n\n[Crypto]\ncert_path = /etc/certman/crypto/cert.pem\nkey_path = /etc/certman/crypto/key.pem")
createFile(confPath, configBytes)
}
func createNewDomainConfig(domain string) {
data := []byte("[Cloudflare]\ncf_email = email@example.com\ncf_api_token = xxxxxxxxxxxxxxxx\n\n[Certificates]\ncerts_path = ./certs/" + domain + "\nsubdomains =")
createFile("/etc/certman/"+domain+".conf", data)
}

58
crypto.go Normal file
View File

@@ -0,0 +1,58 @@
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"fmt"
"os"
)
var cert *x509.Certificate
var key *rsa.PrivateKey
func encryptBytes(data []byte) []byte {
if cert == nil || key == nil {
loadCerts()
}
encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, cert.PublicKey.(*rsa.PublicKey), data)
if err != nil {
fmt.Println("Error encrypting data,", err)
os.Exit(1)
}
return encrypted
}
func decryptBytes(data []byte) []byte {
if cert == nil || key == nil {
loadCerts()
}
decrypted, err := rsa.DecryptPKCS1v15(rand.Reader, key, data)
if err != nil {
fmt.Println("Error decrypting data,", err)
os.Exit(1)
}
return decrypted
}
func loadCerts() {
var err error
certBytes, err := os.ReadFile(config.GetAsString("Crypto.cert_path"))
keyBytes, err := os.ReadFile(config.GetAsString("Crypto.key_path"))
if err != nil {
fmt.Println("Error reading cert or key,", err)
os.Exit(1)
}
cert, err = x509.ParseCertificate(certBytes)
if err != nil {
fmt.Println("Error parsing certificate,", err)
os.Exit(1)
}
key, err = x509.ParsePKCS1PrivateKey(keyBytes)
if err != nil {
fmt.Println("Error parsing private key,", err)
}
}

View File

@@ -1,4 +1,5 @@
[Git]
host = github
server = https://gitea.instance.com
username = user
api_token = xxxxxxxxxxxxxxxx

139
git.go Normal file
View File

@@ -0,0 +1,139 @@
package main
import (
"code.gitea.io/sdk/gitea"
"context"
"fmt"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/google/go-github/v55/github"
"os"
"strings"
"time"
)
func createGithubClient() *github.Client {
return github.NewClient(nil).WithAuthToken(config.GetAsString("Git.api_token"))
}
func createGiteaClient() *gitea.Client {
client, err := gitea.NewClient(config.GetAsString("Git.server"), gitea.SetToken(config.GetAsString("Git.api_token")))
if err != nil {
fmt.Printf("Error connecting to gitea instance: %v\n", err)
os.Exit(1)
}
return client
}
func createGithubRepo(domain *Domain, client *github.Client) string {
name := domain.name
owner := domain.config.GetAsString("Repo.owner")
description := domain.description
private := true
includeAllBranches := false
ctx := context.Background()
template := &github.TemplateRepoRequest{
Name: name,
Owner: &owner,
Description: description,
Private: &private,
IncludeAllBranches: &includeAllBranches,
}
repo, _, err := client.Repositories.CreateFromTemplate(ctx, config.GetAsString("Git.org_name"), config.GetAsString("Git.template_name"), template)
if err != nil {
fmt.Println("Error creating repository from template,", err)
os.Exit(1)
}
return *repo.CloneURL
}
func createGiteaRepo() string {
options := gitea.CreateRepoFromTemplateOption{
Avatar: true,
Description: "Certificates storage for " + domain,
GitContent: true,
GitHooks: true,
Labels: true,
Name: domain + "-certificates",
Owner: config.GetAsString("Git.org_name"),
Private: true,
Topics: true,
Webhooks: true,
}
giteaRepo, _, err := giteaClient.CreateRepoFromTemplate(config.GetAsString("Git.org_name"), config.GetAsString("Git.template_name"), options)
if err != nil {
fmt.Printf("Error creating repo: %v\n", err)
os.Exit(1)
}
return giteaRepo.CloneURL
}
func cloneRepo(url string) (*git.Repository, *git.Worktree) {
repository, err := git.Clone(storage, fs, &git.CloneOptions{URL: url, Auth: creds})
if err != nil {
fmt.Printf("Error clone git repo: %v\n", err)
os.Exit(1)
}
workingTree, err := repo.Worktree()
if err != nil {
fmt.Printf("Error getting worktree from repo: %v\n", err)
os.Exit(1)
}
return repository, workingTree
}
func addAndPushCerts() {
certs, err := os.ReadDir(config.GetAsString("Certificates.certs_path") + "/certificates")
if err != nil {
fmt.Printf("Error reading from directory: %v\n", err)
os.Exit(1)
}
for _, cert := range certs {
if strings.HasPrefix(cert.Name(), domain) {
file, err := fs.Create(cert.Name())
if err != nil {
fmt.Printf("Error copying cert to memfs: %v\n", err)
os.Exit(1)
}
certFile, err := os.ReadFile(config.GetAsString("Certificates.certs_path") + "/certificates/" + cert.Name())
certFile = encryptBytes(certFile)
_, err = file.Write(certFile)
err = file.Close()
if err != nil {
fmt.Printf("Error writing to memfs: %v\n", err)
os.Exit(1)
}
_, err = workTree.Add(cert.Name())
if err != nil {
fmt.Printf("Error adding certificate %v: %v", cert.Name(), err)
os.Exit(1)
}
}
}
status, err := workTree.Status()
if err != nil {
fmt.Printf("Error getting repo status: %v\n", err)
os.Exit(1)
}
fmt.Println("Work Tree Status:\n" + status.String())
signature := &object.Signature{
Name: "Cert Manager",
Email: config.GetAsString("Git.email"),
When: time.Now(),
}
_, err = workTree.Commit("Update "+domain+" @ "+time.Now().Format("Mon Jan _2 2006 15:04:05 MST"), &git.CommitOptions{Author: signature, Committer: signature})
if err != nil {
fmt.Printf("Error committing certs: %v\n", err)
os.Exit(1)
}
err = repo.Push(&git.PushOptions{Auth: creds, Force: true, RemoteName: "origin"})
if err != nil {
fmt.Printf("Error pushing to origin: %v\n", err)
os.Exit(1)
}
fmt.Println("Successfully uploaded to " + config.GetAsString("Git.server") + "/" + config.GetAsString("Git.org_name") + "/" + domain + "-certificates.git")
}

6
go.mod
View File

@@ -19,6 +19,8 @@ require (
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.4.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-github/v55 v55.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/hashicorp/go-version v1.2.1 // indirect
github.com/imdario/mergo v0.3.15 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
@@ -28,9 +30,9 @@ require (
github.com/skeema/knownhosts v1.1.1 // indirect
github.com/src-d/gcfg v1.4.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/sys v0.11.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/src-d/go-git.v4 v4.13.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect

11
go.sum
View File

@@ -43,7 +43,12 @@ github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhc
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg=
github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
@@ -102,6 +107,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -138,11 +145,14 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -151,6 +161,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=

132
main.go
View File

@@ -5,21 +5,20 @@ import (
"code.gitea.io/sdk/gitea"
"fmt"
"git.nevets.tech/Steven/ezconf"
"github.com/go-git/go-git/v5/plumbing/object"
"io"
"os/exec"
"strings"
"time"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/google/go-github/v55/github"
"io"
"os"
"os/exec"
"strings"
)
var config *ezconf.Configuration
var githubClient *github.Client
var giteaClient *gitea.Client
var domain string
var legoBaseArgs []string
@@ -31,19 +30,20 @@ var creds *http.BasicAuth
var repo *git.Repository
//TODO create logic for domain based configs
//TODO create logic for gh vs gt repos
func main() {
makeDirs()
createConfig()
}
func maindis() {
config = ezconf.NewConfiguration("/etc/certman/certman.conf")
var err error
args := os.Args
// -c
hasConfig, configIndex := contains(args, "-c")
if hasConfig {
config = ezconf.NewConfiguration(args[configIndex+1])
} else {
fmt.Printf("Error, no config passed. Please add '-c /path/to/config.ini' to the command\n")
os.Exit(1)
}
// -d
hasDomain, domainIndex := contains(args, "-d")
if hasDomain {
@@ -82,11 +82,7 @@ func main() {
Username: config.GetAsString("Git.username"),
Password: config.GetAsString("Git.api_token"),
}
giteaClient, err = gitea.NewClient(config.GetAsString("Git.server"), gitea.SetToken(config.GetAsString("Git.api_token")))
if err != nil {
fmt.Printf("Error connecting to gitea instance: %v\n", err)
os.Exit(1)
}
giteaClient = createGiteaClient()
storage = memory.NewStorage()
fs = memfs.New()
@@ -96,13 +92,13 @@ func main() {
case "gen":
{
url := createGiteaRepo()
cloneRepo(url)
repo, workTree = cloneRepo(url)
fixUpdateSh()
cmd = exec.Command("lego", legoNewSiteArgs...)
}
case "renew":
{
cloneRepo(config.GetAsString("Git.server") + "/" + config.GetAsString("Git.org_name") + "/" + domain + "-certificates.git")
repo, workTree = cloneRepo(config.GetAsString("Git.server") + "/" + config.GetAsString("Git.org_name") + "/" + domain + "-certificates.git")
cmd = exec.Command("lego", legoRenewSiteArgs...)
}
case "gen-cert-only":
@@ -116,7 +112,7 @@ func main() {
case "git":
{
url := createGiteaRepo()
cloneRepo(url)
repo, workTree = cloneRepo(url)
fixUpdateSh()
addAndPushCerts()
os.Exit(0)
@@ -159,42 +155,6 @@ func main() {
addAndPushCerts()
}
func createGiteaRepo() string {
options := gitea.CreateRepoFromTemplateOption{
Avatar: true,
Description: "Certificates storage for " + domain,
GitContent: true,
GitHooks: true,
Labels: true,
Name: domain + "-certificates",
Owner: config.GetAsString("Git.org_name"),
Private: true,
Topics: true,
Webhooks: true,
}
giteaRepo, _, err := giteaClient.CreateRepoFromTemplate(config.GetAsString("Git.org_name"), config.GetAsString("Git.template_name"), options)
if err != nil {
fmt.Printf("Error creating repo: %v\n", err)
os.Exit(1)
}
return giteaRepo.CloneURL
}
func cloneRepo(url string) {
var err error
repo, err = git.Clone(storage, fs, &git.CloneOptions{URL: url, Auth: creds})
if err != nil {
fmt.Printf("Error clone git repo: %v\n", err)
os.Exit(1)
}
workTree, err = repo.Worktree()
if err != nil {
fmt.Printf("Error getting worktree from repo: %v\n", err)
os.Exit(1)
}
}
func fixUpdateSh() {
oldUpdateSh, err := fs.Open("update.sh")
if err != nil {
@@ -222,60 +182,6 @@ func fixUpdateSh() {
}
}
func addAndPushCerts() {
//TODO integrate SOPS api when stable release
certs, err := os.ReadDir(config.GetAsString("Certificates.certs_path") + "/certificates")
if err != nil {
fmt.Printf("Error reading from directory: %v\n", err)
os.Exit(1)
}
for _, cert := range certs {
if strings.HasPrefix(cert.Name(), domain) {
file, err := fs.Create(cert.Name())
if err != nil {
fmt.Printf("Error copying cert to memfs: %v\n", err)
os.Exit(1)
}
certFile, err := os.ReadFile(config.GetAsString("Certificates.certs_path") + "/certificates/" + cert.Name())
_, err = file.Write(certFile)
err = file.Close()
if err != nil {
fmt.Printf("Error writing to memfs: %v\n", err)
os.Exit(1)
}
_, err = workTree.Add(cert.Name())
if err != nil {
fmt.Printf("Error adding certificate %v: %v", cert.Name(), err)
os.Exit(1)
}
}
}
status, err := workTree.Status()
if err != nil {
fmt.Printf("Error getting repo status: %v\n", err)
os.Exit(1)
}
fmt.Println("Work Tree Status:\n" + status.String())
signature := &object.Signature{
Name: "Cert Manager",
Email: "certs@nevets.tech",
When: time.Now(),
}
_, err = workTree.Commit("Update "+domain+" @ "+time.Now().Format("Mon Jan _2 2006 15:04:05 MST"), &git.CommitOptions{Author: signature, Committer: signature})
if err != nil {
fmt.Printf("Error committing certs: %v\n", err)
os.Exit(1)
}
err = repo.Push(&git.PushOptions{Auth: creds, Force: true, RemoteName: "origin"})
if err != nil {
fmt.Printf("Error pushing to origin: %v\n", err)
os.Exit(1)
}
fmt.Println("Successfully uploaded to " + config.GetAsString("Git.server") + "/" + config.GetAsString("Git.org_name") + "/" + domain + "-certificates.git")
}
func contains(slice []string, value string) (sliceHas bool, index int) {
for i, entry := range slice {
if entry == value {

53
util.go Normal file
View File

@@ -0,0 +1,53 @@
package main
import (
"code.gitea.io/sdk/gitea"
"fmt"
"git.nevets.tech/Steven/ezconf"
"github.com/google/go-github/v55/github"
"os"
)
type Domain struct {
name *string
config *ezconf.Configuration
description *string
ghClient *github.Client
gtClient *gitea.Client
}
func createFile(fileName string, data []byte) {
fileInfo, err := os.Stat(fileName)
if err != nil {
if os.IsNotExist(err) {
file, err := os.Create(fileName)
if err != nil {
fmt.Println("Error creating configuration file:", err)
os.Exit(1)
}
_, err = file.Write(data)
if err != nil {
fmt.Println("Error writing to file;", err)
os.Exit(1)
}
} else {
fmt.Println("Error opening configuration file:", err)
os.Exit(1)
}
} else {
if fileInfo.Size() == 0 {
file, err := os.Create(fileName)
if err != nil {
fmt.Println("Error creating configuration file:", err)
os.Exit(1)
}
_, err = file.Write(data)
if err != nil {
fmt.Println("Error writing to file:", err)
os.Exit(1)
}
}
}
}