mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-20 12:42:34 +02:00
autocert update:
- save ACME private key to reuse previous registered ACME account - properly renew certificate with `Certificate.RenewWithOptions` instead of re-obtaining with `Certificate.Obtain`
This commit is contained in:
parent
d3842ec3c3
commit
acdb324f7d
4 changed files with 79 additions and 25 deletions
|
@ -4,10 +4,13 @@ import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/x509"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/utils"
|
"github.com/yusing/go-proxy/internal/utils"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
|
|
||||||
|
@ -33,6 +36,9 @@ func NewConfig(cfg *types.AutoCertConfig) *Config {
|
||||||
if cfg.Provider == "" {
|
if cfg.Provider == "" {
|
||||||
cfg.Provider = ProviderLocal
|
cfg.Provider = ProviderLocal
|
||||||
}
|
}
|
||||||
|
if cfg.ACMEKeyPath == "" {
|
||||||
|
cfg.ACMEKeyPath = ACMEKeyFileDefault
|
||||||
|
}
|
||||||
return (*Config)(cfg)
|
return (*Config)(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,10 +68,18 @@ func (cfg *Config) GetProvider() (*Provider, E.Error) {
|
||||||
return nil, b.Error()
|
return nil, b.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
var privKey *ecdsa.PrivateKey
|
||||||
if err != nil {
|
var err error
|
||||||
b.Addf("generate private key: %w", err)
|
|
||||||
return nil, b.Error()
|
if privKey, err = cfg.loadACMEKey(); err != nil {
|
||||||
|
logging.Err(err).Msg("load ACME private key failed, generating one...")
|
||||||
|
privKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.New("generate ACME private key").With(err)
|
||||||
|
}
|
||||||
|
if err = cfg.saveACMEKey(privKey); err != nil {
|
||||||
|
return nil, E.New("save ACME private key").With(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user := &User{
|
user := &User{
|
||||||
|
@ -82,3 +96,19 @@ func (cfg *Config) GetProvider() (*Provider, E.Error) {
|
||||||
legoCfg: legoCfg,
|
legoCfg: legoCfg,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) loadACMEKey() (*ecdsa.PrivateKey, error) {
|
||||||
|
data, err := os.ReadFile(cfg.ACMEKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x509.ParseECPrivateKey(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) saveACMEKey(key *ecdsa.PrivateKey) error {
|
||||||
|
data, err := x509.MarshalECPrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(cfg.ACMEKeyPath, data, 0o600)
|
||||||
|
}
|
||||||
|
|
|
@ -8,10 +8,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
certBasePath = "certs/"
|
certBasePath = "certs/"
|
||||||
CertFileDefault = certBasePath + "cert.crt"
|
CertFileDefault = certBasePath + "cert.crt"
|
||||||
KeyFileDefault = certBasePath + "priv.key"
|
KeyFileDefault = certBasePath + "priv.key"
|
||||||
RegistrationFile = certBasePath + "registration.json"
|
ACMEKeyFileDefault = certBasePath + "acme.key"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -28,6 +28,7 @@ type (
|
||||||
legoCfg *lego.Config
|
legoCfg *lego.Config
|
||||||
client *lego.Client
|
client *lego.Client
|
||||||
|
|
||||||
|
legoCert *certificate.Resource
|
||||||
tlsCert *tls.Certificate
|
tlsCert *tls.Certificate
|
||||||
certExpiries CertExpiries
|
certExpiries CertExpiries
|
||||||
}
|
}
|
||||||
|
@ -78,14 +79,29 @@ func (p *Provider) ObtainCert() E.Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client := p.client
|
var cert *certificate.Resource
|
||||||
req := certificate.ObtainRequest{
|
var err error
|
||||||
Domains: p.cfg.Domains,
|
|
||||||
Bundle: true,
|
if p.legoCert != nil {
|
||||||
|
cert, err = p.client.Certificate.RenewWithOptions(*p.legoCert, &certificate.RenewOptions{
|
||||||
|
Bundle: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
p.legoCert = nil
|
||||||
|
logger.Err(err).Msg("cert renew failed, fallback to obtain")
|
||||||
|
} else {
|
||||||
|
p.legoCert = cert
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cert, err := client.Certificate.Obtain(req)
|
|
||||||
if err != nil {
|
if cert == nil {
|
||||||
return E.From(err)
|
cert, err = p.client.Certificate.Obtain(certificate.ObtainRequest{
|
||||||
|
Domains: p.cfg.Domains,
|
||||||
|
Bundle: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return E.From(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = p.saveCert(cert); err != nil {
|
if err = p.saveCert(cert); err != nil {
|
||||||
|
@ -179,11 +195,18 @@ func (p *Provider) registerACME() error {
|
||||||
if p.user.Registration != nil {
|
if p.user.Registration != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
reg, err := p.client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
if reg, err := p.client.Registration.ResolveAccountByKey(); err == nil {
|
||||||
if err != nil {
|
p.user.Registration = reg
|
||||||
return err
|
logger.Info().Msg("reused acme registration from private key")
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
reg, err := p.client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.user.Registration = reg
|
||||||
|
logger.Info().Interface("reg", reg).Msg("acme registered")
|
||||||
}
|
}
|
||||||
p.user.Registration = reg
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,13 @@ package types
|
||||||
|
|
||||||
type (
|
type (
|
||||||
AutoCertConfig struct {
|
AutoCertConfig struct {
|
||||||
Email string `json:"email,omitempty" yaml:"email"`
|
Email string `json:"email,omitempty" yaml:"email"`
|
||||||
Domains []string `json:"domains,omitempty" yaml:",flow"`
|
Domains []string `json:"domains,omitempty" yaml:",flow"`
|
||||||
CertPath string `json:"cert_path,omitempty" yaml:"cert_path"`
|
CertPath string `json:"cert_path,omitempty" yaml:"cert_path"`
|
||||||
KeyPath string `json:"key_path,omitempty" yaml:"key_path"`
|
KeyPath string `json:"key_path,omitempty" yaml:"key_path"`
|
||||||
Provider string `json:"provider,omitempty" yaml:"provider"`
|
ACMEKeyPath string `json:"acme_key_path,omitempty" yaml:"acme_key_path"`
|
||||||
Options AutocertProviderOpt `json:"options,omitempty" yaml:",flow"`
|
Provider string `json:"provider,omitempty" yaml:"provider"`
|
||||||
|
Options AutocertProviderOpt `json:"options,omitempty" yaml:",flow"`
|
||||||
}
|
}
|
||||||
AutocertProviderOpt map[string]any
|
AutocertProviderOpt map[string]any
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue