maddy/internal/tls/acme/acme.go
fox.cpp 93cf4f231a Integrate CertMagic ACME client with dns-01 challenge
Support for a subset of libdns providers is added.
Some are enabled by default (assuming they are popular ones). AWS and Google Cloud SDKs take up extra 10 MiB of executable size.

Only filesystem storage is supported as of now.

Closes #3.
2021-07-13 12:38:42 +03:00

159 lines
3.7 KiB
Go

package acme
import (
"context"
"crypto/tls"
"fmt"
"path/filepath"
"github.com/caddyserver/certmagic"
"github.com/foxcpp/maddy/framework/config"
modconfig "github.com/foxcpp/maddy/framework/config/module"
"github.com/foxcpp/maddy/framework/hooks"
"github.com/foxcpp/maddy/framework/log"
"github.com/foxcpp/maddy/framework/module"
)
const modName = "tls.loader.acme"
type Loader struct {
instName string
store certmagic.Storage
cache *certmagic.Cache
cfg *certmagic.Config
cancelManage context.CancelFunc
log log.Logger
}
func New(_, instName string, _, inlineArgs []string) (module.Module, error) {
if len(inlineArgs) != 0 {
return nil, fmt.Errorf("%s: no inline args expected", modName)
}
return &Loader{
instName: instName,
log: log.Logger{Name: modName},
}, nil
}
func (l *Loader) Init(cfg *config.Map) error {
var (
hostname string
extraNames []string
storePath string
caPath string
testCAPath string
email string
agreed bool
challenge string
provider certmagic.ACMEDNSProvider
)
cfg.Bool("debug", true, false, &l.log.Debug)
cfg.String("hostname", true, true, "", &hostname)
cfg.StringList("extra_names", false, false, nil, &extraNames)
cfg.String("store_path", false, false,
filepath.Join(config.StateDirectory, "acme"), &storePath)
cfg.String("ca", false, false,
certmagic.LetsEncryptStagingCA, &caPath)
cfg.String("test_ca", false, false,
certmagic.LetsEncryptStagingCA, &testCAPath)
cfg.String("email", false, false,
"", &email)
cfg.Bool("agreed", false, false, &agreed)
cfg.Enum("challenge", false, true,
[]string{"dns-01"}, "dns-01", &challenge)
cfg.Custom("dns", false, false, func() (interface{}, error) {
return nil, nil
}, func(m *config.Map, node config.Node) (interface{}, error) {
var p certmagic.ACMEDNSProvider
err := modconfig.ModuleFromNode("libdns", node.Args, node, m.Globals, &p)
return p, err
}, &provider)
if _, err := cfg.Process(); err != nil {
return err
}
cmLog := l.log.Zap()
l.store = &certmagic.FileStorage{Path: storePath}
l.cache = certmagic.NewCache(certmagic.CacheOptions{
Logger: cmLog,
GetConfigForCert: func(c certmagic.Certificate) (*certmagic.Config, error) {
return &certmagic.Config{
Storage: l.store,
Logger: cmLog,
}, nil
},
})
l.cfg = certmagic.New(l.cache, certmagic.Config{
Storage: l.store, // not sure if it is necessary to set these twice
Logger: cmLog,
})
mngr := certmagic.NewACMEManager(l.cfg, certmagic.ACMEManager{
Logger: cmLog,
CA: caPath,
Email: email,
Agreed: agreed,
})
switch challenge {
case "dns-01":
mngr.DisableTLSALPNChallenge = true
mngr.DisableHTTPChallenge = true
if provider == nil {
return fmt.Errorf("tls.loader.acme: dns-01 challenge requires a configured DNS provider")
}
mngr.DNS01Solver = &certmagic.DNS01Solver{
DNSProvider: provider,
}
default:
return fmt.Errorf("tls.loader.acme: challenge not supported")
}
l.cfg.Issuers = []certmagic.Issuer{mngr}
if module.NoRun {
return nil
}
manageCtx, cancelManage := context.WithCancel(context.Background())
err := l.cfg.ManageAsync(manageCtx, append([]string{hostname}, extraNames...))
if err != nil {
cancelManage()
return err
}
l.cancelManage = cancelManage
return nil
}
func (l *Loader) ConfigureTLS(c *tls.Config) error {
c.GetCertificate = l.cfg.GetCertificate
return nil
}
func (l *Loader) Close() error {
l.cancelManage()
l.cache.Stop()
return nil
}
func (l *Loader) Name() string {
return modName
}
func (l *Loader) InstanceName() string {
return l.instName
}
func init() {
hooks.AddHook(hooks.EventShutdown, func() {
certmagic.CleanUpOwnLocks(nil)
})
}
func init() {
var _ module.TLSLoader = &Loader{}
module.Register(modName, New)
}