Files
core/autocert/autocert.go
2023-06-26 20:38:16 +02:00

127 lines
2.9 KiB
Go

package autocert
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"strings"
"sync"
"time"
"github.com/datarhei/core/v16/log"
"github.com/caddyserver/certmagic"
"go.uber.org/zap"
)
type Manager interface {
AcquireCertificates(ctx context.Context, listenAddress string, hostnames []string) error
GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error)
HTTPChallengeHandler(h http.Handler) http.Handler
}
type Config struct {
Storage certmagic.Storage
DefaultHostname string
EmailAddress string
IsProduction bool
Logger log.Logger
}
type manager struct {
config *certmagic.Config
logger log.Logger
}
func New(config Config) (Manager, error) {
m := &manager{
logger: config.Logger,
}
if m.logger == nil {
m.logger = log.New("")
}
certmagic.Default.Storage = config.Storage
certmagic.Default.DefaultServerName = config.DefaultHostname
certmagic.Default.Logger = zap.NewNop()
ca := certmagic.LetsEncryptStagingCA
if config.IsProduction {
ca = certmagic.LetsEncryptProductionCA
}
certmagic.DefaultACME.Agreed = true
certmagic.DefaultACME.Email = config.EmailAddress
certmagic.DefaultACME.CA = ca
certmagic.DefaultACME.DisableHTTPChallenge = false
certmagic.DefaultACME.DisableTLSALPNChallenge = true
certmagic.DefaultACME.Logger = zap.NewNop()
magic := certmagic.NewDefault()
acme := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
acme.Logger = zap.NewNop()
magic.Issuers = []certmagic.Issuer{acme}
magic.Logger = zap.NewNop()
m.config = magic
return m, nil
}
func (m *manager) HTTPChallengeHandler(h http.Handler) http.Handler {
acme := m.config.Issuers[0].(*certmagic.ACMEIssuer)
return acme.HTTPChallengeHandler(h)
}
func (m *manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
return m.config.GetCertificate(hello)
}
func (m *manager) AcquireCertificates(ctx context.Context, listenAddress string, hostnames []string) error {
acme := m.config.Issuers[0].(*certmagic.ACMEIssuer)
// Start temporary http server on configured port
tempserver := &http.Server{
Addr: listenAddress,
Handler: acme.HTTPChallengeHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
})),
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
tempserver.ListenAndServe()
wg.Done()
}()
var certerr error
// Get the certificates
logger := m.logger.WithField("hostnames", hostnames)
logger.Info().Log("Acquiring certificate ...")
err := m.config.ManageSync(ctx, hostnames)
if err != nil {
certerr = fmt.Errorf("failed to acquire certificate for %s: %w", strings.Join(hostnames, ","), err)
} else {
logger.Info().Log("Successfully acquired certificate")
}
// Shut down the temporary http server
tempserver.Close()
wg.Wait()
return certerr
}