Lots of progress
This commit is contained in:
11
.idea/go.imports.xml
generated
Normal file
11
.idea/go.imports.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GoImports">
|
||||
<option name="excludedPackages">
|
||||
<array>
|
||||
<option value="github.com/pkg/errors" />
|
||||
<option value="golang.org/x/net/context" />
|
||||
</array>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,5 +0,0 @@
|
||||
@echo off
|
||||
set GOARCH=amd64
|
||||
set GOOS=linux
|
||||
|
||||
go build -o ./certman .
|
||||
5
certs.go
5
certs.go
@@ -6,10 +6,11 @@ import (
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
@@ -25,7 +26,7 @@ type User struct {
|
||||
func (u *User) GetEmail() string {
|
||||
return u.Email
|
||||
}
|
||||
func (u User) GetRegistration() *registration.Resource {
|
||||
func (u *User) GetRegistration() *registration.Resource {
|
||||
return u.Registration
|
||||
}
|
||||
func (u *User) GetPrivateKey() crypto.PrivateKey {
|
||||
|
||||
44
config.go
44
config.go
@@ -4,10 +4,14 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.nevets.tech/Steven/ezconf"
|
||||
)
|
||||
|
||||
var domainConfCache map[string]*ezconf.Configuration
|
||||
|
||||
func makeDirs() {
|
||||
err := os.MkdirAll("/etc/certman", 0775)
|
||||
err := os.MkdirAll("/etc/certman", 0644)
|
||||
if err != nil {
|
||||
if !os.IsExist(err) {
|
||||
fmt.Println("Unable to create config directory")
|
||||
@@ -15,7 +19,7 @@ func makeDirs() {
|
||||
}
|
||||
}
|
||||
|
||||
err = os.Mkdir("/etc/certman/conf", 0775)
|
||||
err = os.Mkdir("/etc/certman/domains", 0644)
|
||||
if err != nil {
|
||||
if !os.IsExist(err) {
|
||||
fmt.Println("Unable to create config directory")
|
||||
@@ -23,7 +27,7 @@ func makeDirs() {
|
||||
}
|
||||
}
|
||||
|
||||
err = os.Mkdir("/var/local/certman", 0660)
|
||||
err = os.Mkdir("/var/local/certman", 0640)
|
||||
if err != nil {
|
||||
if !os.IsExist(err) {
|
||||
fmt.Printf("Unable to create certman directory: %v\n", err)
|
||||
@@ -34,11 +38,20 @@ func makeDirs() {
|
||||
|
||||
func createNewDomainConfig(domain string) {
|
||||
data := []byte(strings.ReplaceAll(defaultDomainConfig, "{domain}", domain))
|
||||
createFile("/etc/certman/conf/"+domain+".conf", 0755, data)
|
||||
createFile("/etc/certman/domains/"+domain+".conf", 0755, data)
|
||||
}
|
||||
|
||||
func createNewDomainCertsDir(domain string) {
|
||||
err := os.Mkdir("/var/local/certman/"+domain, 0660)
|
||||
func createNewDomainCertsDir(domain string, dir string) {
|
||||
var err error
|
||||
if dir == "/opt/certs/example.com" {
|
||||
err = os.Mkdir("/var/local/certman/"+domain, 0640)
|
||||
} else {
|
||||
if strings.HasSuffix(dir, "/") {
|
||||
err = os.MkdirAll(dir+domain, 0640)
|
||||
} else {
|
||||
err = os.Mkdir(dir+"/"+domain, 0640)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if os.IsExist(err) {
|
||||
fmt.Println("Directory already exists...")
|
||||
@@ -48,3 +61,22 @@ func createNewDomainCertsDir(domain string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getDomainConfig(domain string) *ezconf.Configuration {
|
||||
if domainConfCache == nil {
|
||||
domainConfCache = make(map[string]*ezconf.Configuration)
|
||||
domainConf := ezconf.LoadConfiguration("/etc/certman/domains/" + domain + ".conf")
|
||||
domainConfCache[domain] = domainConf
|
||||
return domainConf
|
||||
}
|
||||
if domainConfCache[domain] == nil {
|
||||
domainConf := ezconf.LoadConfiguration("/etc/certman/domains/" + domain + ".conf")
|
||||
domainConfCache[domain] = domainConf
|
||||
return domainConf
|
||||
}
|
||||
return domainConfCache[domain]
|
||||
}
|
||||
|
||||
func clearDomainConfCache() {
|
||||
domainConfCache = nil
|
||||
}
|
||||
|
||||
124
crypto.go
124
crypto.go
@@ -1,17 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
_ "filippo.io/age"
|
||||
"filippo.io/age/armor"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
//var cert *x509.Certificate
|
||||
@@ -75,86 +73,80 @@ func GenerateKey() (string, error) {
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
// LoadKeyFromFile reads a key file that contains either a raw base64 string or
|
||||
// "AGE_SYM_KEY=<base64>" (handy for dotenv). Whitespace is trimmed.
|
||||
func LoadKeyFromFile(path string) (string, error) {
|
||||
b, err := os.ReadFile(path)
|
||||
func decodeKey(b64 string) ([]byte, error) {
|
||||
key, err := base64.StdEncoding.DecodeString(b64) // standard padded
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
s := strings.TrimSpace(string(b))
|
||||
if i := strings.Index(s, "AGE_SYM_KEY="); i >= 0 {
|
||||
s = strings.TrimSpace(strings.TrimPrefix(s, "AGE_SYM_KEY="))
|
||||
if len(key) != chacha20poly1305.KeySize {
|
||||
return nil, fmt.Errorf("bad key length: got %d, want %d", len(key), chacha20poly1305.KeySize)
|
||||
}
|
||||
if s == "" {
|
||||
return "", errors.New("empty symmetric key")
|
||||
}
|
||||
// Quick sanity check that it’s base64 and ~32 bytes after decode.
|
||||
if _, err := base64.StdEncoding.DecodeString(s); err != nil {
|
||||
return "", fmt.Errorf("invalid base64 key: %w", err)
|
||||
}
|
||||
return s, nil
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// Encrypt streams plaintext from r to w using a symmetric passphrase.
|
||||
// If armorOut is true, output is ASCII-armored (BEGIN AGE ENCRYPTED FILE).
|
||||
func Encrypt(r io.Reader, w io.Writer, passphrase string, armorOut bool) error {
|
||||
passphrase = strings.TrimSpace(passphrase)
|
||||
if passphrase == "" {
|
||||
return errors.New("missing passphrase")
|
||||
}
|
||||
|
||||
var out io.WriteCloser
|
||||
var err error
|
||||
|
||||
if armorOut {
|
||||
aw := armor.NewWriter(w)
|
||||
defer aw.Close()
|
||||
//out, err = age.Encrypt(aw, age.NewScryptRecipient(passphrase))
|
||||
} else {
|
||||
//out, err = age.Encrypt(w, age.NewScryptRecipient(passphrase))
|
||||
}
|
||||
func EncryptFileXChaCha(keyB64, inPath, outPath string, aad []byte) error {
|
||||
key, err := decodeKey(keyB64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, copyErr := io.Copy(out, bufio.NewReader(r))
|
||||
closeErr := out.Close()
|
||||
if copyErr != nil {
|
||||
return copyErr
|
||||
}
|
||||
return closeErr
|
||||
aead, err := chacha20poly1305.NewX(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new aead: %w", err)
|
||||
}
|
||||
|
||||
// Decrypt streams ciphertext from r to w using the same symmetric passphrase.
|
||||
// It auto-detects armored vs binary ciphertext.
|
||||
func Decrypt(r io.Reader, w io.Writer, passphrase string) error {
|
||||
passphrase = strings.TrimSpace(passphrase)
|
||||
if passphrase == "" {
|
||||
return errors.New("missing passphrase")
|
||||
plaintext, err := os.ReadFile(inPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read input: %w", err)
|
||||
}
|
||||
|
||||
br := bufio.NewReader(r)
|
||||
peek, _ := br.Peek(32)
|
||||
//var in io.Reader = br
|
||||
if strings.HasPrefix(string(peek), "-----BEGIN AGE ENCRYPTED FILE-----") {
|
||||
// in = armor.NewReader(br)
|
||||
nonce := make([]byte, chacha20poly1305.NonceSizeX) // 24 bytes
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return fmt.Errorf("nonce: %w", err)
|
||||
}
|
||||
|
||||
//dr, err := age.Decrypt(in, age.NewScryptIdentity(passphrase))
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//_, err = io.Copy(w, bufio.NewWriter(wrap0600(w)))
|
||||
//return err
|
||||
ciphertext := aead.Seal(nil, nonce, plaintext, aad)
|
||||
|
||||
// Write: nonce || ciphertext
|
||||
out := make([]byte, 0, len(nonce)+len(ciphertext))
|
||||
out = append(out, nonce...)
|
||||
out = append(out, ciphertext...)
|
||||
|
||||
if err := os.WriteFile(outPath, out, 0600); err != nil {
|
||||
return fmt.Errorf("write output: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wrap0600 ensures that when w is an *os.File newly created by caller,
|
||||
// its perms are 0600. If it’s not an *os.File, it’s returned unchanged.
|
||||
func wrap0600(w io.Writer) io.Writer {
|
||||
if f, ok := w.(*os.File); ok {
|
||||
_ = f.Chmod(0600)
|
||||
func DecryptFileXChaCha(keyB64, inPath, outPath string, aad []byte) error {
|
||||
key, err := decodeKey(keyB64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w
|
||||
|
||||
aead, err := chacha20poly1305.NewX(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new aead: %w", err)
|
||||
}
|
||||
|
||||
in, err := os.ReadFile(inPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read input: %w", err)
|
||||
}
|
||||
if len(in) < chacha20poly1305.NonceSizeX {
|
||||
return errors.New("ciphertext too short")
|
||||
}
|
||||
|
||||
nonce := in[:chacha20poly1305.NonceSizeX]
|
||||
ciphertext := in[chacha20poly1305.NonceSizeX:]
|
||||
|
||||
plaintext, err := aead.Open(nil, nonce, ciphertext, aad)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt/auth failed: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(outPath, plaintext, 0640); err != nil {
|
||||
return fmt.Errorf("write output: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
[App]
|
||||
mode = {mode}
|
||||
|
||||
[Git]
|
||||
host = gitea
|
||||
server = https://gitea.instance.com
|
||||
username = user
|
||||
api_token = xxxxxxxxxxxxxxxx
|
||||
org_name = org
|
||||
template_name = template
|
||||
|
||||
[Certificates]
|
||||
email = user@example.com
|
||||
data_root = /var/local/certman
|
||||
request_method = dns
|
||||
|
||||
[Cloudflare]
|
||||
cf_email = email@example.com
|
||||
cf_api_token = xxxxxxxxxxxxxxxx
|
||||
|
||||
[Certificates]
|
||||
data_root = /var/local/certman
|
||||
19
example.domainconfig.conf
Normal file
19
example.domainconfig.conf
Normal file
@@ -0,0 +1,19 @@
|
||||
[Domain]
|
||||
domain_name = {domain}
|
||||
; default (use system dns) or IPv4 Address (1.1.1.1)
|
||||
dns_server = default
|
||||
; optionally use /path/to/directory
|
||||
file_location = default
|
||||
|
||||
[Certificates]
|
||||
subdomains =
|
||||
expiry = 90
|
||||
cert_symlink =
|
||||
key_symlink =
|
||||
|
||||
[Repo]
|
||||
repo_suffix = -certificates
|
||||
|
||||
; Don't change setting below here unless you know what you're doing!
|
||||
[Internal]
|
||||
last_issued = never
|
||||
@@ -1,6 +0,0 @@
|
||||
[Domain]
|
||||
domain_name = example.com
|
||||
|
||||
[Certificates]
|
||||
subdomains =
|
||||
expiry = 90
|
||||
@@ -1,6 +0,0 @@
|
||||
@echo off
|
||||
set GOARCH=amd64
|
||||
set GOOS=linux
|
||||
go install -v -a std
|
||||
|
||||
go build -o ./certman .
|
||||
28
git.go
28
git.go
@@ -1,15 +1,16 @@
|
||||
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"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/google/go-github/v55/github"
|
||||
)
|
||||
|
||||
func createGithubClient() *github.Client {
|
||||
@@ -49,13 +50,14 @@ func createGithubRepo(domain *Domain, client *github.Client) string {
|
||||
}
|
||||
|
||||
func createGiteaRepo() string {
|
||||
domainConfig := getDomainConfig(domain)
|
||||
options := gitea.CreateRepoFromTemplateOption{
|
||||
Avatar: true,
|
||||
Description: "Certificates storage for " + domain,
|
||||
GitContent: true,
|
||||
GitHooks: true,
|
||||
Labels: true,
|
||||
Name: domain + "-certificates",
|
||||
Name: domain + domainConfig.GetAsString("Repo.repo_suffix"),
|
||||
Owner: config.GetAsString("Git.org_name"),
|
||||
Private: true,
|
||||
Topics: true,
|
||||
@@ -85,19 +87,19 @@ func cloneRepo(url string) (*git.Repository, *git.Worktree) {
|
||||
}
|
||||
|
||||
func addAndPushCerts() {
|
||||
certs, err := os.ReadDir(config.GetAsString("Certificates.certs_path") + "/certificates")
|
||||
certFiles, 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())
|
||||
for _, file := range certFiles {
|
||||
if strings.HasPrefix(file.Name(), domain) {
|
||||
file, err := fs.Create(file.Name())
|
||||
if err != nil {
|
||||
fmt.Printf("Error copying cert to memfs: %v\n", err)
|
||||
fmt.Printf("Error copying file to memfs: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
certFile, err := os.ReadFile(config.GetAsString("Certificates.certs_path") + "/certificates/" + cert.Name())
|
||||
certFile, err := os.ReadFile(config.GetAsString("Certificates.certs_path") + "/certificates/" + file.Name())
|
||||
//certFile = encryptBytes(certFile)
|
||||
_, err = file.Write(certFile)
|
||||
err = file.Close()
|
||||
@@ -105,9 +107,9 @@ func addAndPushCerts() {
|
||||
fmt.Printf("Error writing to memfs: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
_, err = workTree.Add(cert.Name())
|
||||
_, err = workTree.Add(file.Name())
|
||||
if err != nil {
|
||||
fmt.Printf("Error adding certificate %v: %v", cert.Name(), err)
|
||||
fmt.Printf("Error adding file %v: %v", file.Name(), err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
3
go.mod
3
go.mod
@@ -12,6 +12,8 @@ require (
|
||||
github.com/go-git/go-billy/v5 v5.4.1
|
||||
github.com/go-git/go-git/v5 v5.7.0
|
||||
github.com/google/go-github/v55 v55.0.0
|
||||
github.com/makifdb/pidfile v0.0.0-20231129022650-50ec86392313
|
||||
golang.org/x/crypto v0.42.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -34,7 +36,6 @@ require (
|
||||
github.com/sergi/go-diff v1.3.1 // indirect
|
||||
github.com/skeema/knownhosts v1.1.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
|
||||
13
go.sum
13
go.sum
@@ -1,4 +1,5 @@
|
||||
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
|
||||
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
|
||||
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
||||
code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M=
|
||||
code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=
|
||||
@@ -13,7 +14,9 @@ github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI
|
||||
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
|
||||
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
@@ -24,10 +27,13 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
|
||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||
github.com/go-acme/lego/v4 v4.26.0 h1:521aEQxNstXvPQcFDDPrJiFfixcCQuvAvm35R4GbyYA=
|
||||
github.com/go-acme/lego/v4 v4.26.0/go.mod h1:BQVAWgcyzW4IT9eIKHY/RxYlVhoyKyOMXOkq7jK1eEQ=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
@@ -35,6 +41,7 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmS
|
||||
github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4=
|
||||
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
|
||||
github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE=
|
||||
github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8=
|
||||
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
|
||||
@@ -43,6 +50,7 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
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=
|
||||
@@ -63,6 +71,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/makifdb/pidfile v0.0.0-20231129022650-50ec86392313 h1:5/CjuZQWnRALu4hkEDRg4fA5lWDSfjlKg+koRDRuotQ=
|
||||
github.com/makifdb/pidfile v0.0.0-20231129022650-50ec86392313/go.mod h1:nm72+BE0Z1PcotR9soX+NnGyEs1iQ0b1Ot0IhL2Nwwk=
|
||||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
|
||||
@@ -74,6 +84,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
@@ -83,6 +94,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -136,6 +148,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
|
||||
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.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
||||
186
main.go
186
main.go
@@ -2,9 +2,19 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"git.nevets.tech/Steven/ezconf"
|
||||
"github.com/go-git/go-billy/v5"
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
@@ -12,12 +22,7 @@ import (
|
||||
"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"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"github.com/makifdb/pidfile"
|
||||
)
|
||||
|
||||
var config *ezconf.Configuration
|
||||
@@ -33,57 +38,175 @@ var creds *http.BasicAuth
|
||||
|
||||
var repo *git.Repository
|
||||
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
var wg sync.WaitGroup
|
||||
|
||||
//TODO create logic for gh vs gt repos
|
||||
|
||||
func main() {
|
||||
|
||||
devFlag := flag.Bool("dev", false, "Developer Mode")
|
||||
|
||||
configFile := flag.String("config", "/etc/certman/certman.conf", "Configuration file")
|
||||
|
||||
newDomainFlag := flag.String("new-domain", "example.com", "Domain to create new configs and directories for")
|
||||
newDomainDirFlag := flag.String("new-domain-dir", "/opt/certs/example.com", "Directory that certs will be stored in")
|
||||
|
||||
installFlag := flag.Bool("install", false, "Install Certman")
|
||||
modeFlag := flag.String("mode", "client", "CertManager Mode [server, client]")
|
||||
thinInstallFlag := flag.Bool("t", false, "Thin Install (skip creating dirs)")
|
||||
|
||||
newKeyFlag := flag.Bool("newkey", false, "Generate new encryption key")
|
||||
|
||||
reloadFlag := flag.Bool("reload", false, "Reload configs")
|
||||
|
||||
daemonFlag := flag.Bool("d", false, "Daemon Mode")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *devFlag {
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
if *newDomainFlag != "example.com" {
|
||||
fmt.Printf("Creating new domain %s\n", *newDomainFlag)
|
||||
createNewDomainConfig(*newDomainFlag)
|
||||
createNewDomainCertsDir(*newDomainFlag)
|
||||
createNewDomainCertsDir(*newDomainFlag, *newDomainDirFlag)
|
||||
fmt.Println("Successfully created domain entry for " + *newDomainFlag + "\nUpdate config file as needed in /etc/certman/domains/" + *newDomainFlag + ".conf")
|
||||
os.Exit(0)
|
||||
}
|
||||
if *installFlag {
|
||||
if !*thinInstallFlag {
|
||||
makeDirs()
|
||||
config = ezconf.NewConfiguration("/etc/certman/certman.conf", defaultConfig)
|
||||
}
|
||||
config = ezconf.NewConfiguration(*configFile, strings.ReplaceAll(defaultConfig, "{mode}", *modeFlag))
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *newKeyFlag {
|
||||
key, err := GenerateKey()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf(key)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *reloadFlag {
|
||||
pidBytes, err := os.ReadFile("/var/run/certman.pid")
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting PID from /var/run/certman.pid: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pidStr := strings.TrimSpace(string(pidBytes))
|
||||
daemonPid, err := strconv.Atoi(pidStr)
|
||||
if err != nil {
|
||||
fmt.Printf("Error converting PID string to int (%s): %v\n", pidStr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
proc, err := os.FindProcess(daemonPid)
|
||||
if err != nil {
|
||||
fmt.Printf("Error finding process with PID %d: %v\n", daemonPid, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = proc.Signal(syscall.SIGHUP)
|
||||
if err != nil {
|
||||
fmt.Printf("Error sending SIGHUP to PID %d: %v\n", daemonPid, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *daemonFlag {
|
||||
err := pidfile.CreateOrUpdatePIDFile("/var/run/certman.pid")
|
||||
if err != nil {
|
||||
fmt.Println("Error creating pidfile")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
|
||||
// Check if main config exists
|
||||
if _, err := os.Stat("/etc/certman/certman.conf"); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(*configFile); os.IsNotExist(err) {
|
||||
fmt.Println("Main config file not found, please run 'certman --install', then properly configure /etc/certman/certman.conf.")
|
||||
os.Exit(1)
|
||||
} else if err != nil {
|
||||
fmt.Printf("Error opening /etc/certman/certman.conf: %v\n", err)
|
||||
fmt.Printf("Error opening %s: %v\n", *configFile, err)
|
||||
}
|
||||
config = ezconf.LoadConfiguration(*configFile)
|
||||
|
||||
// Setup SIGINT and SIGTERM listeners
|
||||
sigChannel := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM)
|
||||
defer signal.Stop(sigChannel)
|
||||
|
||||
// Task loop
|
||||
reloadSigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(reloadSigChan, syscall.SIGHUP)
|
||||
defer signal.Stop(reloadSigChan)
|
||||
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
wg.Add(1)
|
||||
if config.GetAsString("App.mode") == "server" {
|
||||
fmt.Println("Starting CertManager in server mode...")
|
||||
// Server Task loop
|
||||
go func() {
|
||||
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
fmt.Println("Shutting down server")
|
||||
return
|
||||
case <-reloadSigChan:
|
||||
{
|
||||
fmt.Println("Reloading configs...")
|
||||
}
|
||||
case <-ticker.C:
|
||||
{
|
||||
fmt.Println("Tick!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
} else if config.GetAsString("App.mode") == "client" {
|
||||
fmt.Println("Starting CertManager in client mode...")
|
||||
// Client Task loop
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
fmt.Println("Shutting down client")
|
||||
return
|
||||
case <-reloadSigChan:
|
||||
{
|
||||
fmt.Println("Reloading configs...")
|
||||
}
|
||||
case <-ticker.C:
|
||||
{
|
||||
fmt.Println("Tick!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
fmt.Println("Invalid operating mode \"" + config.GetAsString("App.mode") + "\"")
|
||||
}
|
||||
|
||||
// Cleanup on stop
|
||||
sig := <-sigChannel
|
||||
fmt.Printf("Program terminated with %v\n", sig)
|
||||
|
||||
close()
|
||||
stop()
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func close() {
|
||||
|
||||
func stop() {
|
||||
cancel()
|
||||
}
|
||||
|
||||
func maindis() {
|
||||
@@ -141,7 +264,6 @@ func maindis() {
|
||||
{
|
||||
url := createGiteaRepo()
|
||||
repo, workTree = cloneRepo(url)
|
||||
fixUpdateSh()
|
||||
cmd = exec.Command("lego", legoNewSiteArgs...)
|
||||
}
|
||||
case "renew":
|
||||
@@ -161,7 +283,6 @@ func maindis() {
|
||||
{
|
||||
url := createGiteaRepo()
|
||||
repo, workTree = cloneRepo(url)
|
||||
fixUpdateSh()
|
||||
addAndPushCerts()
|
||||
os.Exit(0)
|
||||
}
|
||||
@@ -202,30 +323,3 @@ func maindis() {
|
||||
}
|
||||
addAndPushCerts()
|
||||
}
|
||||
|
||||
func fixUpdateSh() {
|
||||
oldUpdateSh, err := fs.Open("update.sh")
|
||||
if err != nil {
|
||||
fmt.Printf("Error opening update.sh: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
contentBytes, err := io.ReadAll(oldUpdateSh)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading update.sh: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
content := string(contentBytes)
|
||||
strings.ReplaceAll(content, "<>", domain)
|
||||
updateSh, err := fs.Create("update.sh")
|
||||
_, err = updateSh.Write([]byte(content))
|
||||
err = updateSh.Close()
|
||||
if err != nil {
|
||||
fmt.Printf("Error writing update.sh: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
_, err = workTree.Add("update.sh")
|
||||
if err != nil {
|
||||
fmt.Printf("Error adding update.sh: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
18
util.go
18
util.go
@@ -1,12 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"git.nevets.tech/Steven/ezconf"
|
||||
"github.com/google/go-github/v55/github"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Domain struct {
|
||||
@@ -99,7 +100,10 @@ func insert(a []string, index int, value string) []string {
|
||||
return a
|
||||
}
|
||||
|
||||
const defaultConfig = `[Git]
|
||||
const defaultConfig = `[App]
|
||||
mode = {mode}
|
||||
|
||||
[Git]
|
||||
host = gitea
|
||||
server = https://gitea.instance.com
|
||||
username = user
|
||||
@@ -120,12 +124,20 @@ const defaultDomainConfig = `[Domain]
|
||||
domain_name = {domain}
|
||||
; default (use system dns) or IPv4 Address (1.1.1.1)
|
||||
dns_server = default
|
||||
; optionally use /path/to/directory
|
||||
file_location = default
|
||||
|
||||
[Certificates]
|
||||
subdomains =
|
||||
expiry = 90
|
||||
cert_symlink =
|
||||
key_symlink =
|
||||
|
||||
[Repo]
|
||||
repo_suffix = -certificates
|
||||
|
||||
; Don't change setting below here unless you know what you're doing!
|
||||
[Internal]
|
||||
last_issued = never
|
||||
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user