feat: per-site SSL email and error display

This commit is contained in:
orohi
2026-06-17 06:05:34 +03:00
parent 02201fb4e3
commit f2c05d15ec
9 changed files with 151 additions and 59 deletions
+30 -25
View File
@@ -6,7 +6,9 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
@@ -31,7 +33,7 @@ type ACMEUser struct {
func (u *ACMEUser) GetEmail() string { return u.Email }
func (u *ACMEUser) GetRegistration() *registration.Resource { return u.Registration }
func (u *ACMEUser) GetPrivateKey() crypto.PrivateKey { return u.key }
func (u *ACMEUser) GetPrivateKey() crypto.PrivateKey { return u.key }
type webrootProvider struct {
webroot string
@@ -54,29 +56,29 @@ func (p *webrootProvider) CleanUp(domain, token, keyAuth string) error {
}
type Issuer struct {
email string
staging bool
storageDir string
store *ChallengeStore
mu sync.Mutex
client *lego.Client
staging bool
storageDir string
store *ChallengeStore
mu sync.Mutex
clients map[string]*lego.Client
}
func NewIssuer(email string, staging bool, storageDir string, store *ChallengeStore) *Issuer {
func NewIssuer(staging bool, storageDir string, store *ChallengeStore) *Issuer {
return &Issuer{
email: email,
staging: staging,
storageDir: storageDir,
store: store,
clients: make(map[string]*lego.Client),
}
}
func (i *Issuer) Obtain(ctx context.Context, domain, webroot string) (*certificate.Resource, error) {
if i.email == "" {
return nil, errors.New("ACME_EMAIL is not configured")
func (i *Issuer) Obtain(ctx context.Context, email, domain, webroot string) (*certificate.Resource, error) {
email = strings.TrimSpace(strings.ToLower(email))
if email == "" {
return nil, errors.New("ssl email is not configured")
}
client, err := i.getClient()
client, err := i.getClient(email)
if err != nil {
return nil, err
}
@@ -103,15 +105,15 @@ func (i *Issuer) Obtain(ctx context.Context, domain, webroot string) (*certifica
return res, nil
}
func (i *Issuer) getClient() (*lego.Client, error) {
func (i *Issuer) getClient(email string) (*lego.Client, error) {
i.mu.Lock()
defer i.mu.Unlock()
if i.client != nil {
return i.client, nil
if client, ok := i.clients[email]; ok {
return client, nil
}
user, err := i.loadOrCreateUser()
user, err := i.loadOrCreateUser(email)
if err != nil {
return nil, err
}
@@ -140,18 +142,21 @@ func (i *Issuer) getClient() (*lego.Client, error) {
}
}
i.client = client
i.clients[email] = client
return client, nil
}
func (i *Issuer) accountPath() string {
return filepath.Join(i.storageDir, "acme", "account.pem")
func (i *Issuer) accountPath(email string) string {
sum := sha256.Sum256([]byte(strings.ToLower(email)))
id := hex.EncodeToString(sum[:8])
return filepath.Join(i.storageDir, "acme", id, "account.pem")
}
func (i *Issuer) loadOrCreateUser() (*ACMEUser, error) {
user := &ACMEUser{Email: i.email}
func (i *Issuer) loadOrCreateUser(email string) (*ACMEUser, error) {
user := &ACMEUser{Email: email}
path := i.accountPath(email)
if data, err := os.ReadFile(i.accountPath()); err == nil {
if data, err := os.ReadFile(path); err == nil {
block, _ := pem.Decode(data)
if block != nil {
key, err := x509.ParseECPrivateKey(block.Bytes)
@@ -168,7 +173,7 @@ func (i *Issuer) loadOrCreateUser() (*ACMEUser, error) {
}
user.key = key
if err := os.MkdirAll(filepath.Dir(i.accountPath()), 0o700); err != nil {
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
return nil, err
}
if err := i.saveAccount(user); err != nil {
@@ -183,7 +188,7 @@ func (i *Issuer) saveAccount(user *ACMEUser) error {
return err
}
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: der})
return os.WriteFile(i.accountPath(), pemBytes, 0o600)
return os.WriteFile(i.accountPath(user.Email), pemBytes, 0o600)
}
func (i *Issuer) saveToDisk(domain string, res *certificate.Resource) error {