-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
dc25fc4
commit 4147afd
Showing
6 changed files
with
92 additions
and
112 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,57 +12,37 @@ import ( | |
"os" | ||
|
||
"github.com/caddyserver/certmagic" | ||
"github.com/coredns/coredns/core/dnsserver" | ||
) | ||
|
||
type ACMEManager struct { | ||
type CertManager struct { | ||
Config *certmagic.Config | ||
Issuer *certmagic.ACMEIssuer | ||
Zone string | ||
} | ||
|
||
// NewACMEManager create a new ACMEManager | ||
func NewACMEManager(config *dnsserver.Config, zone string, ca string, path string, caCert string, port int, email string) *ACMEManager { | ||
if ca == "" { | ||
ca = "localhost:14001/dir" //pebble default | ||
} | ||
|
||
// default email if none is provided | ||
// providing a reail email is recommended to receiv notifications for expiring certificates | ||
// in case that something goes wrong | ||
if email == "" { | ||
email = "[email protected]" | ||
} | ||
|
||
pool, err := x509.SystemCertPool() | ||
if err != nil { | ||
log.Errorf("Failed to get system pool of trusted certificates: %v \n", err) | ||
} | ||
|
||
if caCert != "" { | ||
certbytes, err := os.ReadFile(caCert) | ||
if err != nil { | ||
log.Errorf("Failed to read certificate provided by cacert option: %v \n", err) | ||
} | ||
pemcert, _ := pem.Decode(certbytes) | ||
if pemcert == nil { | ||
log.Errorf("Failed to decode CaCert: %v \n", err) | ||
} | ||
cert, err := x509.ParseCertificate(pemcert.Bytes) | ||
if err != nil { | ||
log.Errorf("Failed to parse certificate provided by cacert option: %v \n", err) | ||
} | ||
pool.AddCert(cert) | ||
func NewConfig(path string) *certmagic.Config { | ||
acmeConfigTemplate := NewCertmagicConfig() | ||
acmeConfigTemplate.RenewalWindowRatio = 0.7 | ||
acmeConfigTemplate.Storage = &certmagic.FileStorage{ | ||
Path: path, | ||
} | ||
cache := certmagic.NewCache(certmagic.CacheOptions{ | ||
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) { | ||
return acmeConfigTemplate, nil | ||
}, | ||
}) | ||
acmeConfig := certmagic.New(cache, *acmeConfigTemplate) | ||
return acmeConfig | ||
} | ||
|
||
func NewIssuer(config *certmagic.Config, ca string, email string, pool *x509.CertPool, port int) *certmagic.ACMEIssuer { | ||
readyChan := make(chan string) | ||
solver := &DNSSolver{ | ||
Port: port, | ||
readyChan: readyChan, | ||
} | ||
|
||
certmagic.DefaultACME.Email = "[email protected]" | ||
|
||
acmeIssuerTemplate := certmagic.ACMEIssuer{ | ||
Agreed: true, | ||
DisableHTTPChallenge: true, | ||
|
@@ -74,67 +54,83 @@ func NewACMEManager(config *dnsserver.Config, zone string, ca string, path strin | |
TrustedRoots: pool, | ||
} | ||
|
||
acmeConfigTemplate := NewCertmagicConfig() | ||
acmeConfigTemplate.RenewalWindowRatio = 0.7 | ||
acmeIssuer := certmagic.NewACMEIssuer(config, acmeIssuerTemplate) | ||
config.Issuers = append(config.Issuers, acmeIssuer) | ||
|
||
acmeConfigTemplate.Storage = &certmagic.FileStorage{ | ||
Path: path, | ||
return acmeIssuer | ||
} | ||
|
||
func setupCertPool(caCert string) (*x509.CertPool, error) { | ||
pool, err := x509.SystemCertPool() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
cache := certmagic.NewCache(certmagic.CacheOptions{ | ||
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) { | ||
return acmeConfigTemplate, nil | ||
}, | ||
}) | ||
acmeConfig := certmagic.New(cache, *acmeConfigTemplate) | ||
acmeIssuer := certmagic.NewACMEIssuer(acmeConfig, acmeIssuerTemplate) | ||
acmeConfig.Issuers = append(acmeConfig.Issuers, acmeIssuer) | ||
if caCert != "" { | ||
certbytes, err := os.ReadFile(caCert) | ||
if err != nil { | ||
return nil, err | ||
} | ||
pemcert, _ := pem.Decode(certbytes) | ||
if pemcert == nil { | ||
return nil, err | ||
} | ||
cert, err := x509.ParseCertificate(pemcert.Bytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
pool.AddCert(cert) | ||
} | ||
return pool, nil | ||
} | ||
|
||
return &ACMEManager{ | ||
Config: acmeConfig, | ||
Issuer: acmeIssuer, | ||
// NewACMEManager create a new ACMEManager | ||
func NewCertManager(zone string, config *certmagic.Config, issuer *certmagic.ACMEIssuer) *CertManager { | ||
return &CertManager{ | ||
Config: config, | ||
Issuer: issuer, | ||
Zone: zone, | ||
} | ||
} | ||
|
||
func (am *ACMEManager) configureTLSwithACME(ctx context.Context) (*tls.Config, *certmagic.Certificate, error) { | ||
func (c *CertManager) configureTLSwithACME(ctx context.Context) (*tls.Config, *certmagic.Certificate, error) { | ||
var cert certmagic.Certificate | ||
var err error | ||
|
||
// try loading existing certificate | ||
cert, err = am.Config.CacheManagedCertificate(ctx, am.Zone) | ||
cert, err = c.Config.CacheManagedCertificate(ctx, c.Zone) | ||
if err != nil { | ||
log.Info("Obtaining TLS Certificate, may take a moment") | ||
if !errors.Is(err, fs.ErrNotExist) { | ||
return nil, nil, err | ||
} | ||
err = am.GetCert(am.Zone) | ||
err = c.GetCert(c.Zone) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
cert, err = am.CacheCertificate(ctx, am.Zone) | ||
cert, err = c.CacheCertificate(ctx, c.Zone) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
} | ||
|
||
// check if renewal is required | ||
if cert.NeedsRenewal(am.Config) { | ||
if cert.NeedsRenewal(c.Config) { | ||
log.Info("Renewing TLS Certificate") | ||
var err error | ||
err = am.RenewCert(ctx, am.Zone) | ||
err = c.RenewCert(ctx, c.Zone) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("%s: renewing certificate: %w", am.Zone, err) | ||
return nil, nil, fmt.Errorf("%s: renewing certificate: %w", c.Zone, err) | ||
} | ||
// successful renewal, so update in-memory cache | ||
cert, err = am.CacheCertificate(ctx, am.Zone) | ||
cert, err = c.CacheCertificate(ctx, c.Zone) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("%s: reloading renewed certificate into memory: %v", am.Zone, err) | ||
return nil, nil, fmt.Errorf("%s: reloading renewed certificate into memory: %v", c.Zone, err) | ||
} | ||
} | ||
|
||
// check again, if it still needs renewal something went wrong | ||
if cert.NeedsRenewal(am.Config) { | ||
if cert.NeedsRenewal(c.Config) { | ||
log.Error("Failed to renew certificate") | ||
} | ||
|
||
|
@@ -146,17 +142,17 @@ func (am *ACMEManager) configureTLSwithACME(ctx context.Context) (*tls.Config, * | |
return tlsConfig, &cert, nil | ||
} | ||
|
||
func (a *ACMEManager) GetCert(zone string) error { | ||
err := a.Config.ObtainCertSync(context.Background(), zone) | ||
func (c *CertManager) GetCert(zone string) error { | ||
err := c.Config.ObtainCertSync(context.Background(), zone) | ||
return err | ||
} | ||
|
||
func (a *ACMEManager) RenewCert(ctx context.Context, zone string) error { | ||
err := a.Config.RenewCertSync(ctx, zone, false) | ||
func (c *CertManager) RenewCert(ctx context.Context, zone string) error { | ||
err := c.Config.RenewCertSync(ctx, zone, false) | ||
return err | ||
} | ||
|
||
func (a *ACMEManager) CacheCertificate(ctx context.Context, zone string) (certmagic.Certificate, error) { | ||
cert, err := a.Config.CacheManagedCertificate(ctx, zone) | ||
func (c *CertManager) CacheCertificate(ctx context.Context, zone string) (certmagic.Certificate, error) { | ||
cert, err := c.Config.CacheManagedCertificate(ctx, zone) | ||
return cert, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,13 +36,11 @@ var ( | |
) | ||
|
||
const ( | ||
argDomain = "domain" | ||
argCheckInternal = "checkinterval" | ||
argCa = "ca" | ||
argCaCert = "cacert" | ||
argEmail = "email" | ||
argCertPath = "certpath" | ||
argPort = "port" | ||
defaultCA = "https://acme-v02.api.letsencrypt.org/directory" | ||
defaultEmail = "[email protected]" | ||
defaultCheckInterval = 15 | ||
defaultPort = 53 | ||
defaultCertPath = "./local/share/certmagic" | ||
) | ||
|
||
func parseTLS(c *caddy.Controller) error { | ||
|
@@ -67,10 +65,10 @@ func parseTLS(c *caddy.Controller) error { | |
ctx := context.Background() | ||
|
||
var domainNameACME string | ||
var ca string | ||
var caCert string | ||
var port string | ||
var email string | ||
ca := "https://acme-v02.api.letsencrypt.org/directory" | ||
checkInterval := 15 | ||
userHome, homeExists := os.LookupEnv("HOME") | ||
if !homeExists { | ||
|
@@ -81,46 +79,46 @@ func parseTLS(c *caddy.Controller) error { | |
for c.NextBlock() { | ||
token := c.Val() | ||
switch token { | ||
case argDomain: | ||
case "domain": | ||
domainArgs := c.RemainingArgs() | ||
if len(domainArgs) > 1 { | ||
return plugin.Error("tls", c.Errf("Too many arguments to domain")) | ||
} | ||
domainNameACME = domainArgs[0] | ||
case argCa: | ||
case "ca": | ||
caArgs := c.RemainingArgs() | ||
if len(caArgs) > 1 { | ||
return plugin.Error("tls", c.Errf("Too many arguments to ca")) | ||
} | ||
ca = caArgs[0] | ||
case argCaCert: | ||
case "cacert": | ||
caCertArgs := c.RemainingArgs() | ||
if len(caCertArgs) > 1 { | ||
return plugin.Error("tls", c.Errf("Too many arguments to cacert")) | ||
} | ||
caCert = caCertArgs[0] | ||
case argEmail: | ||
case "email": | ||
emailArgs := c.RemainingArgs() | ||
if len(emailArgs) > 1 { | ||
return plugin.Error("tls", c.Errf("Too many arguments to email")) | ||
} | ||
email = emailArgs[0] | ||
case argPort: | ||
case "port": | ||
portArgs := c.RemainingArgs() | ||
if len(portArgs) > 1 { | ||
return plugin.Error("tls", c.Errf("Too many arguments to port")) | ||
} | ||
port = portArgs[0] | ||
case argCertPath: | ||
case "certpath": | ||
certPathArgs := c.RemainingArgs() | ||
if len(certPathArgs) > 1 { | ||
return plugin.Error("tls", c.Errf("Too many arguments to CertPath")) | ||
return plugin.Error("tls", c.Errf("Too many arguments to certpath")) | ||
} | ||
certPath = certPathArgs[0] | ||
case argCheckInternal: | ||
case "checkinterval": | ||
checkIntervalArgs := c.RemainingArgs() | ||
if len(checkIntervalArgs) > 1 { | ||
return plugin.Error("tls", c.Errf("Too many arguments to checkInterval")) | ||
return plugin.Error("tls", c.Errf("Too many arguments to checkinterval")) | ||
} | ||
interval, err := strconv.Atoi(checkIntervalArgs[0]) | ||
if err != nil { | ||
|
@@ -142,12 +140,18 @@ func parseTLS(c *caddy.Controller) error { | |
} | ||
} | ||
|
||
manager := NewACMEManager(config, domainNameACME, ca, certPath, caCert, portNumber, email) | ||
pool, err := setupCertPool(caCert) | ||
if err != nil { | ||
log.Errorf("Failed to add the custom CA certfiicate to the pool of trusted certificates: %v, \n", err) | ||
} | ||
certmagicConfig := NewConfig(certPath) | ||
certmagicIssuer := NewIssuer(certmagicConfig, ca, email, pool, portNumber) | ||
certManager := NewCertManager(domainNameACME, certmagicConfig, certmagicIssuer) | ||
|
||
var names []string | ||
names = append(names, manager.Zone) | ||
names = append(names, certManager.Zone) | ||
|
||
tlsconf, cert, err = manager.configureTLSwithACME(ctx) | ||
tlsconf, cert, err = certManager.configureTLSwithACME(ctx) | ||
if err != nil { | ||
log.Errorf("Failed to setup TLS automatically: %v \n", err) | ||
} | ||
|
@@ -159,7 +163,7 @@ func parseTLS(c *caddy.Controller) error { | |
log.Debug("Starting certificate renewal loop in the background") | ||
for { | ||
time.Sleep(time.Duration(checkInterval) * time.Minute) | ||
if cert.NeedsRenewal(manager.Config) { | ||
if cert.NeedsRenewal(certManager.Config) { | ||
log.Info("Certificate expiring soon, initializing reload") | ||
r.renew <- true | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters