mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-25 09:40:13 +08:00
Merge branch 'certmagic-refactor' into v2
This commit is contained in:
commit
b216d285df
|
@ -44,7 +44,7 @@ This is the development branch for Caddy 2.
|
|||
<p align="center">
|
||||
<b>Powered by</b>
|
||||
<br>
|
||||
<a href="https://github.com/mholt/certmagic"><img src="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png" alt="CertMagic" width="250"></a>
|
||||
<a href="https://github.com/caddyserver/certmagic"><img src="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png" alt="CertMagic" width="250"></a>
|
||||
</p>
|
||||
|
||||
## Build from source
|
||||
|
|
14
caddy.go
14
caddy.go
|
@ -32,7 +32,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/certmagic"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
@ -382,14 +382,12 @@ func run(newCfg *Config, start bool) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Load, Provision, Validate each app and their submodules
|
||||
// Load and Provision each app and their submodules
|
||||
err = func() error {
|
||||
appsIface, err := ctx.LoadModule(newCfg, "AppsRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading app modules: %v", err)
|
||||
}
|
||||
for appName, appIface := range appsIface.(map[string]interface{}) {
|
||||
newCfg.apps[appName] = appIface.(App)
|
||||
for appName := range newCfg.AppsRaw {
|
||||
if _, err := ctx.App(appName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/mholt/certmagic"
|
||||
"github.com/caddyserver/certmagic"
|
||||
)
|
||||
|
||||
// mapAddressToServerBlocks returns a map of listener address to list of server
|
||||
|
|
|
@ -111,7 +111,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||
var cp *caddytls.ConnectionPolicy
|
||||
var fileLoader caddytls.FileLoader
|
||||
var folderLoader caddytls.FolderLoader
|
||||
var mgr caddytls.ACMEManagerMaker
|
||||
var mgr caddytls.ACMEIssuer
|
||||
|
||||
// fill in global defaults, if configured
|
||||
if email := h.Option("email"); email != nil {
|
||||
|
@ -322,9 +322,9 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||
}
|
||||
|
||||
// automation policy
|
||||
if !reflect.DeepEqual(mgr, caddytls.ACMEManagerMaker{}) {
|
||||
if !reflect.DeepEqual(mgr, caddytls.ACMEIssuer{}) {
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.automation_manager",
|
||||
Class: "tls.cert_issuer",
|
||||
Value: mgr,
|
||||
})
|
||||
}
|
||||
|
@ -533,12 +533,10 @@ func parseLog(h Helper) ([]ConfigValue, error) {
|
|||
|
||||
var val namedCustomLog
|
||||
if !reflect.DeepEqual(cl, new(caddy.CustomLog)) {
|
||||
|
||||
logCounter, ok := h.State["logCounter"].(int)
|
||||
if !ok {
|
||||
logCounter = 0
|
||||
}
|
||||
|
||||
cl.Include = []string{"http.log.access"}
|
||||
val.name = fmt.Sprintf("log%d", logCounter)
|
||||
val.log = cl
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/mholt/certmagic"
|
||||
"github.com/caddyserver/certmagic"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -185,9 +185,9 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
|||
for _, p := range pairings {
|
||||
for i, sblock := range p.serverBlocks {
|
||||
// tls automation policies
|
||||
if mmVals, ok := sblock.pile["tls.automation_manager"]; ok {
|
||||
if mmVals, ok := sblock.pile["tls.cert_issuer"]; ok {
|
||||
for _, mmVal := range mmVals {
|
||||
mm := mmVal.Value.(caddytls.ManagerMaker)
|
||||
mm := mmVal.Value.(certmagic.Issuer)
|
||||
sblockHosts, err := st.autoHTTPSHosts(sblock)
|
||||
if err != nil {
|
||||
return nil, warnings, err
|
||||
|
@ -197,8 +197,8 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
|||
tlsApp.Automation = new(caddytls.AutomationConfig)
|
||||
}
|
||||
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, &caddytls.AutomationPolicy{
|
||||
Hosts: sblockHosts,
|
||||
ManagementRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID.Name(), &warnings),
|
||||
Hosts: sblockHosts,
|
||||
IssuerRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID.Name(), &warnings),
|
||||
})
|
||||
} else {
|
||||
warnings = append(warnings, caddyconfig.Warning{
|
||||
|
@ -257,7 +257,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
|||
if !hasEmail {
|
||||
email = ""
|
||||
}
|
||||
mgr := caddytls.ACMEManagerMaker{
|
||||
mgr := caddytls.ACMEIssuer{
|
||||
CA: acmeCA.(string),
|
||||
Email: email.(string),
|
||||
}
|
||||
|
@ -272,7 +272,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
|||
}
|
||||
}
|
||||
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, &caddytls.AutomationPolicy{
|
||||
ManagementRaw: caddyconfig.JSONModuleObject(mgr, "module", "acme", &warnings),
|
||||
IssuerRaw: caddyconfig.JSONModuleObject(mgr, "module", "acme", &warnings),
|
||||
})
|
||||
}
|
||||
if tlsApp.Automation != nil {
|
||||
|
@ -349,6 +349,18 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
|||
}
|
||||
}
|
||||
}
|
||||
if len(customLogs) > 0 {
|
||||
if cfg.Logging == nil {
|
||||
cfg.Logging = &caddy.Logging{
|
||||
Logs: make(map[string]*caddy.CustomLog),
|
||||
}
|
||||
}
|
||||
for _, ncl := range customLogs {
|
||||
if ncl.name != "" {
|
||||
cfg.Logging.Logs[ncl.name] = ncl.log
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, warnings, nil
|
||||
}
|
||||
|
@ -487,6 +499,7 @@ func (st *ServerType) serversFromPairings(
|
|||
}
|
||||
|
||||
// tls: connection policies and toggle auto HTTPS
|
||||
defaultSNI := tryString(options["default_sni"], warnings)
|
||||
autoHTTPSQualifiedHosts, err := st.autoHTTPSHosts(sblock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -499,6 +512,7 @@ func (st *ServerType) serversFromPairings(
|
|||
srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, autoHTTPSQualifiedHosts...)
|
||||
} else if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
|
||||
// tls connection policies
|
||||
|
||||
for _, cpVal := range cpVals {
|
||||
cp := cpVal.Value.(*caddytls.ConnectionPolicy)
|
||||
|
||||
|
@ -507,6 +521,13 @@ func (st *ServerType) serversFromPairings(
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, h := range hosts {
|
||||
if h == defaultSNI {
|
||||
hosts = append(hosts, "")
|
||||
cp.DefaultSNI = defaultSNI
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: are matchers needed if every hostname of the resulting config is matched?
|
||||
if len(hosts) > 0 {
|
||||
|
@ -520,6 +541,11 @@ func (st *ServerType) serversFromPairings(
|
|||
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
|
||||
}
|
||||
// TODO: consolidate equal conn policies
|
||||
} else if defaultSNI != "" {
|
||||
hasCatchAllTLSConnPolicy = true
|
||||
srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{
|
||||
DefaultSNI: defaultSNI,
|
||||
})
|
||||
}
|
||||
|
||||
// exclude any hosts that were defined explicitly with
|
||||
|
@ -770,7 +796,7 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
|
|||
// otherwise the one without any hosts (a catch-all) would be
|
||||
// eaten up by the one with hosts; and if both have hosts, we
|
||||
// need to combine their lists
|
||||
if reflect.DeepEqual(aps[i].ManagementRaw, aps[j].ManagementRaw) &&
|
||||
if reflect.DeepEqual(aps[i].IssuerRaw, aps[j].IssuerRaw) &&
|
||||
aps[i].ManageSync == aps[j].ManageSync {
|
||||
if len(aps[i].Hosts) == 0 && len(aps[j].Hosts) > 0 {
|
||||
aps = append(aps[:j], aps[j+1:]...)
|
||||
|
|
|
@ -35,7 +35,7 @@ import (
|
|||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/mholt/certmagic"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
|
|
@ -274,6 +274,8 @@ directly instead of printing it.`,
|
|||
// This function panics if the name is already registered,
|
||||
// if the name does not meet the described format, or if
|
||||
// any of the fields are missing from cmd.
|
||||
//
|
||||
// This function should be used in init().
|
||||
func RegisterCommand(cmd Command) {
|
||||
if cmd.Name == "" {
|
||||
panic("command name is required")
|
||||
|
|
10
context.go
10
context.go
|
@ -21,7 +21,7 @@ import (
|
|||
"log"
|
||||
"reflect"
|
||||
|
||||
"github.com/mholt/certmagic"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
@ -384,9 +384,13 @@ func (ctx Context) App(name string) (interface{}, error) {
|
|||
if app, ok := ctx.cfg.apps[name]; ok {
|
||||
return app, nil
|
||||
}
|
||||
modVal, err := ctx.LoadModuleByID(name, nil)
|
||||
appRaw := ctx.cfg.AppsRaw[name]
|
||||
modVal, err := ctx.LoadModuleByID(name, appRaw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("instantiating new module %s: %v", name, err)
|
||||
return nil, fmt.Errorf("loading %s app module: %v", name, err)
|
||||
}
|
||||
if appRaw != nil {
|
||||
ctx.cfg.AppsRaw[name] = nil // allow GC to deallocate
|
||||
}
|
||||
ctx.cfg.apps[name] = modVal.(App)
|
||||
return modVal, nil
|
||||
|
|
42
go.mod
42
go.mod
|
@ -3,33 +3,33 @@ module github.com/caddyserver/caddy/v2
|
|||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/Masterminds/sprig/v3 v3.0.0
|
||||
github.com/alecthomas/chroma v0.7.0
|
||||
github.com/andybalholm/brotli v0.0.0-20190821151343-b60f0d972eeb
|
||||
github.com/cenkalti/backoff/v3 v3.1.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.0.2
|
||||
github.com/alecthomas/chroma v0.7.1
|
||||
github.com/andybalholm/brotli v1.0.0
|
||||
github.com/caddyserver/certmagic v0.10.0
|
||||
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
|
||||
github.com/go-acme/lego/v3 v3.3.0
|
||||
github.com/golang/groupcache v0.0.0-20191002201903-404acd9df4cc
|
||||
github.com/go-acme/lego/v3 v3.4.0
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
|
||||
github.com/ilibs/json5 v1.0.1
|
||||
github.com/imdario/mergo v0.3.8 // indirect
|
||||
github.com/jsternberg/zap-logfmt v1.2.0
|
||||
github.com/klauspost/compress v1.8.6
|
||||
github.com/klauspost/cpuid v1.2.2
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/lucas-clemente/quic-go v0.15.1
|
||||
github.com/mholt/certmagic v0.9.3
|
||||
github.com/miekg/dns v1.1.25 // indirect
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20190906142622-1265e9b150c6
|
||||
github.com/klauspost/compress v1.10.2
|
||||
github.com/klauspost/cpuid v1.2.3
|
||||
github.com/lucas-clemente/quic-go v0.15.2
|
||||
github.com/manifoldco/promptui v0.7.0 // indirect
|
||||
github.com/miekg/dns v1.1.27 // indirect
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20200303171503-1e787b591db7
|
||||
github.com/naoina/go-stringutil v0.1.0 // indirect
|
||||
github.com/naoina/toml v0.1.1
|
||||
github.com/smallstep/certificates v0.14.0-rc.5
|
||||
github.com/smallstep/cli v0.14.0-rc.3
|
||||
github.com/smallstep/truststore v0.9.4
|
||||
github.com/vulcand/oxy v1.0.0
|
||||
github.com/yuin/goldmark v1.1.17
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20191202084645-78f32c8dd6d5
|
||||
go.uber.org/multierr v1.2.0 // indirect
|
||||
go.uber.org/zap v1.10.0
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
|
||||
github.com/yuin/goldmark v1.1.24
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20200218065240-d1af22c1126f
|
||||
go.uber.org/zap v1.14.0
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
)
|
||||
|
|
|
@ -294,8 +294,8 @@ type Provisioner interface {
|
|||
// Validator is implemented by modules which can verify that their
|
||||
// configurations are valid. This method will be called after
|
||||
// Provision() (if implemented). Validation should always be fast
|
||||
// (imperceptible running time) and an error should be returned only
|
||||
// if the value's configuration is invalid.
|
||||
// (imperceptible running time) and an error must be returned if
|
||||
// the module's configuration is invalid.
|
||||
type Validator interface {
|
||||
Validate() error
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/mholt/certmagic"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
@ -42,12 +42,10 @@ type AutoHTTPSConfig struct {
|
|||
// enabled. To force automated certificate management
|
||||
// regardless of loaded certificates, set this to true.
|
||||
IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"`
|
||||
|
||||
domainSet map[string]struct{}
|
||||
}
|
||||
|
||||
// Skipped returns true if name is in skipSlice, which
|
||||
// should be one of the Skip* fields on ahc.
|
||||
// should be either the Skip or SkipCerts field on ahc.
|
||||
func (ahc AutoHTTPSConfig) Skipped(name string, skipSlice []string) bool {
|
||||
for _, n := range skipSlice {
|
||||
if name == n {
|
||||
|
@ -68,6 +66,8 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||
// addresses to the routes that do HTTP->HTTPS redirects
|
||||
lnAddrRedirRoutes := make(map[string]Route)
|
||||
|
||||
uniqueDomainsForCerts := make(map[string]struct{})
|
||||
|
||||
for srvName, srv := range app.Servers {
|
||||
// as a prerequisite, provision route matchers; this is
|
||||
// required for all routes on all servers, and must be
|
||||
|
@ -116,8 +116,8 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||
srv.TLSConnPolicies = defaultConnPolicies
|
||||
}
|
||||
|
||||
// find all qualifying domain names in this server
|
||||
srv.AutoHTTPS.domainSet = make(map[string]struct{})
|
||||
// find all qualifying domain names (deduplicated) in this server
|
||||
serverDomainSet := make(map[string]struct{})
|
||||
for routeIdx, route := range srv.Routes {
|
||||
for matcherSetIdx, matcherSet := range route.MatcherSets {
|
||||
for matcherIdx, m := range matcherSet {
|
||||
|
@ -131,7 +131,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||
}
|
||||
if certmagic.HostQualifies(d) &&
|
||||
!srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.Skip) {
|
||||
srv.AutoHTTPS.domainSet[d] = struct{}{}
|
||||
serverDomainSet[d] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -141,10 +141,29 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||
|
||||
// nothing more to do here if there are no
|
||||
// domains that qualify for automatic HTTPS
|
||||
if len(srv.AutoHTTPS.domainSet) == 0 {
|
||||
if len(serverDomainSet) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// for all the hostnames we found, filter them so we have
|
||||
// a deduplicated list of names for which to obtain certs
|
||||
for d := range serverDomainSet {
|
||||
if !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.SkipCerts) {
|
||||
// if a certificate for this name is already loaded,
|
||||
// don't obtain another one for it, unless we are
|
||||
// supposed to ignore loaded certificates
|
||||
if !srv.AutoHTTPS.IgnoreLoadedCerts &&
|
||||
len(app.tlsApp.AllMatchingCertificates(d)) > 0 {
|
||||
app.logger.Info("skipping automatic certificate management because one or more matching certificates are already loaded",
|
||||
zap.String("domain", d),
|
||||
zap.String("server_name", srvName),
|
||||
)
|
||||
continue
|
||||
}
|
||||
uniqueDomainsForCerts[d] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// tell the server to use TLS if it is not already doing so
|
||||
if srv.TLSConnPolicies == nil {
|
||||
srv.TLSConnPolicies = defaultConnPolicies
|
||||
|
@ -209,6 +228,19 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||
}
|
||||
}
|
||||
|
||||
// we now have a list of all the unique names for which we need certs;
|
||||
// turn the set into a slice so that phase 2 can use it
|
||||
app.allCertDomains = make([]string, 0, len(uniqueDomainsForCerts))
|
||||
for d := range uniqueDomainsForCerts {
|
||||
app.allCertDomains = append(app.allCertDomains, d)
|
||||
}
|
||||
|
||||
// ensure there is an automation policy to handle these certs
|
||||
err := app.createAutomationPolicy(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if there are HTTP->HTTPS redirects to add, do so now
|
||||
if len(lnAddrRedirRoutes) == 0 {
|
||||
return nil
|
||||
|
@ -258,28 +290,78 @@ redirRoutesLoop:
|
|||
return nil
|
||||
}
|
||||
|
||||
// automaticHTTPSPhase2 attaches a TLS app pointer to each
|
||||
// server. This phase must occur after provisioning, and
|
||||
// at the beginning of the app start, before starting each
|
||||
// of the servers.
|
||||
func (app *App) automaticHTTPSPhase2() error {
|
||||
tlsAppIface, err := app.ctx.App("tls")
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting tls app: %v", err)
|
||||
// createAutomationPolicy ensures that certificates for this app are
|
||||
// managed properly; for example, it's implied that the HTTPPort
|
||||
// should also be the port the HTTP challenge is solved on; the same
|
||||
// for HTTPS port and TLS-ALPN challenge also. We need to tell the
|
||||
// TLS app to manage these certs by honoring those port configurations,
|
||||
// so we either find an existing matching automation policy with an
|
||||
// ACME issuer, or make a new one and append it.
|
||||
func (app *App) createAutomationPolicy(ctx caddy.Context) error {
|
||||
var matchingPolicy *caddytls.AutomationPolicy
|
||||
var acmeIssuer *caddytls.ACMEIssuer
|
||||
if app.tlsApp.Automation != nil {
|
||||
// maybe we can find an exisitng one that matches; this is
|
||||
// useful if the user made a single automation policy to
|
||||
// set the CA endpoint to a test/staging endpoint (very
|
||||
// common), but forgot to customize the ports here, while
|
||||
// setting them in the HTTP app instead (I did this too
|
||||
// many times)
|
||||
for _, ap := range app.tlsApp.Automation.Policies {
|
||||
if len(ap.Hosts) == 0 {
|
||||
matchingPolicy = ap
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if matchingPolicy != nil {
|
||||
// if it has an ACME issuer, maybe we can just use that
|
||||
acmeIssuer, _ = matchingPolicy.Issuer.(*caddytls.ACMEIssuer)
|
||||
}
|
||||
if acmeIssuer.Challenges == nil {
|
||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||
}
|
||||
if acmeIssuer.Challenges.HTTP == nil {
|
||||
acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig)
|
||||
}
|
||||
if acmeIssuer.Challenges.HTTP.AlternatePort == 0 {
|
||||
// don't overwrite existing explicit config
|
||||
acmeIssuer.Challenges.HTTP.AlternatePort = app.HTTPPort
|
||||
}
|
||||
if acmeIssuer.Challenges.TLSALPN == nil {
|
||||
acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
|
||||
}
|
||||
if acmeIssuer.Challenges.TLSALPN.AlternatePort == 0 {
|
||||
// don't overwrite existing explicit config
|
||||
acmeIssuer.Challenges.TLSALPN.AlternatePort = app.HTTPSPort
|
||||
}
|
||||
tlsApp := tlsAppIface.(*caddytls.TLS)
|
||||
|
||||
// set the tlsApp pointer before starting any
|
||||
// challenges, since it is required to solve
|
||||
// the ACME HTTP challenge
|
||||
for _, srv := range app.Servers {
|
||||
srv.tlsApp = tlsApp
|
||||
if matchingPolicy == nil {
|
||||
// if there was no matching policy, we'll have to append our own
|
||||
err := app.tlsApp.AddAutomationPolicy(&caddytls.AutomationPolicy{
|
||||
Hosts: app.allCertDomains,
|
||||
Issuer: acmeIssuer,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// if there was an existing matching policy, we need to reprovision
|
||||
// its issuer (because we just changed its port settings and it has
|
||||
// to re-build its stored certmagic config template with the new
|
||||
// values), then re-assign the Issuer pointer on the policy struct
|
||||
// because our type assertion changed the address
|
||||
err := acmeIssuer.Provision(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
matchingPolicy.Issuer = acmeIssuer
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// automaticHTTPSPhase3 begins certificate management for
|
||||
// automaticHTTPSPhase2 begins certificate management for
|
||||
// all names in the qualifying domain set for each server.
|
||||
// This phase must occur after provisioning and at the end
|
||||
// of app start, after all the servers have been started.
|
||||
|
@ -289,72 +371,17 @@ func (app *App) automaticHTTPSPhase2() error {
|
|||
// first, then our servers would fail to bind to them,
|
||||
// which would be bad, since CertMagic's bindings are
|
||||
// temporary and don't serve the user's sites!).
|
||||
func (app *App) automaticHTTPSPhase3() error {
|
||||
// begin managing certificates for enabled servers
|
||||
for srvName, srv := range app.Servers {
|
||||
if srv.AutoHTTPS == nil ||
|
||||
srv.AutoHTTPS.Disabled ||
|
||||
len(srv.AutoHTTPS.domainSet) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// marshal the domains into a slice
|
||||
var domains, domainsForCerts []string
|
||||
for d := range srv.AutoHTTPS.domainSet {
|
||||
domains = append(domains, d)
|
||||
if !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.SkipCerts) {
|
||||
// if a certificate for this name is already loaded,
|
||||
// don't obtain another one for it, unless we are
|
||||
// supposed to ignore loaded certificates
|
||||
if !srv.AutoHTTPS.IgnoreLoadedCerts &&
|
||||
len(srv.tlsApp.AllMatchingCertificates(d)) > 0 {
|
||||
app.logger.Info("skipping automatic certificate management because one or more matching certificates are already loaded",
|
||||
zap.String("domain", d),
|
||||
zap.String("server_name", srvName),
|
||||
)
|
||||
continue
|
||||
}
|
||||
domainsForCerts = append(domainsForCerts, d)
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that these certificates are managed properly;
|
||||
// for example, it's implied that the HTTPPort should also
|
||||
// be the port the HTTP challenge is solved on, and so
|
||||
// for HTTPS port and TLS-ALPN challenge also - we need
|
||||
// to tell the TLS app to manage these certs by honoring
|
||||
// those port configurations
|
||||
acmeManager := &caddytls.ACMEManagerMaker{
|
||||
Challenges: &caddytls.ChallengesConfig{
|
||||
HTTP: &caddytls.HTTPChallengeConfig{
|
||||
AlternatePort: app.HTTPPort, // we specifically want the user-configured port, if any
|
||||
},
|
||||
TLSALPN: &caddytls.TLSALPNChallengeConfig{
|
||||
AlternatePort: app.HTTPSPort, // we specifically want the user-configured port, if any
|
||||
},
|
||||
},
|
||||
}
|
||||
if srv.tlsApp.Automation == nil {
|
||||
srv.tlsApp.Automation = new(caddytls.AutomationConfig)
|
||||
}
|
||||
srv.tlsApp.Automation.Policies = append(srv.tlsApp.Automation.Policies,
|
||||
&caddytls.AutomationPolicy{
|
||||
Hosts: domainsForCerts,
|
||||
Management: acmeManager,
|
||||
})
|
||||
|
||||
// manage their certificates
|
||||
app.logger.Info("enabling automatic TLS certificate management",
|
||||
zap.Strings("domains", domainsForCerts),
|
||||
)
|
||||
err := srv.tlsApp.Manage(domainsForCerts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: managing certificate for %s: %s", srvName, domains, err)
|
||||
}
|
||||
|
||||
// no longer needed; allow GC to deallocate
|
||||
srv.AutoHTTPS.domainSet = nil
|
||||
func (app *App) automaticHTTPSPhase2() error {
|
||||
if len(app.allCertDomains) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
app.logger.Info("enabling automatic TLS certificate management",
|
||||
zap.Strings("domains", app.allCertDomains),
|
||||
)
|
||||
err := app.tlsApp.Manage(app.allCertDomains)
|
||||
if err != nil {
|
||||
return fmt.Errorf("managing certificates for %v: %s", app.allCertDomains, err)
|
||||
}
|
||||
app.allCertDomains = nil // no longer needed; allow GC to deallocate
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -28,8 +28,9 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/lucas-clemente/quic-go/http3"
|
||||
"github.com/mholt/certmagic"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
@ -122,6 +123,10 @@ type App struct {
|
|||
|
||||
ctx caddy.Context
|
||||
logger *zap.Logger
|
||||
tlsApp *caddytls.TLS
|
||||
|
||||
// used temporarily between phases 1 and 2 of auto HTTPS
|
||||
allCertDomains []string
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
|
@ -134,6 +139,12 @@ func (App) CaddyModule() caddy.ModuleInfo {
|
|||
|
||||
// Provision sets up the app.
|
||||
func (app *App) Provision(ctx caddy.Context) error {
|
||||
// store some references
|
||||
tlsAppIface, err := ctx.App("tls")
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting tls app: %v", err)
|
||||
}
|
||||
app.tlsApp = tlsAppIface.(*caddytls.TLS)
|
||||
app.ctx = ctx
|
||||
app.logger = ctx.Logger(app)
|
||||
|
||||
|
@ -144,12 +155,14 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||
// this provisions the matchers for each route,
|
||||
// and prepares auto HTTP->HTTPS redirects, and
|
||||
// is required before we provision each server
|
||||
err := app.automaticHTTPSPhase1(ctx, repl)
|
||||
err = app.automaticHTTPSPhase1(ctx, repl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// prepare each server
|
||||
for srvName, srv := range app.Servers {
|
||||
srv.tlsApp = app.tlsApp
|
||||
srv.logger = app.logger.Named("log")
|
||||
srv.errorLogger = app.logger.Named("log.error")
|
||||
|
||||
|
@ -202,9 +215,14 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("server %s: setting up server error handling routes: %v", srvName, err)
|
||||
}
|
||||
|
||||
srv.errorHandlerChain = srv.Errors.Routes.Compile(errorEmptyHandler)
|
||||
}
|
||||
|
||||
// prepare the TLS connection policies
|
||||
err = srv.TLSConnPolicies.Provision(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server %s: setting up TLS connection policies: %v", srvName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -238,14 +256,6 @@ func (app *App) Validate() error {
|
|||
// Start runs the app. It finishes automatic HTTPS if enabled,
|
||||
// including management of certificates.
|
||||
func (app *App) Start() error {
|
||||
// give each server a pointer to the TLS app;
|
||||
// this is required before they are started so
|
||||
// they can solve ACME challenges
|
||||
err := app.automaticHTTPSPhase2()
|
||||
if err != nil {
|
||||
return fmt.Errorf("enabling automatic HTTPS, phase 2: %v", err)
|
||||
}
|
||||
|
||||
for srvName, srv := range app.Servers {
|
||||
s := &http.Server{
|
||||
ReadTimeout: time.Duration(srv.ReadTimeout),
|
||||
|
@ -279,10 +289,7 @@ func (app *App) Start() error {
|
|||
if len(srv.TLSConnPolicies) > 0 &&
|
||||
int(listenAddr.StartPort+portOffset) != app.httpPort() {
|
||||
// create TLS listener
|
||||
tlsCfg, err := srv.TLSConnPolicies.TLSConfig(app.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s/%s: making TLS configuration: %v", listenAddr.Network, hostport, err)
|
||||
}
|
||||
tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx)
|
||||
ln = tls.NewListener(ln, tlsCfg)
|
||||
|
||||
/////////
|
||||
|
@ -318,7 +325,7 @@ func (app *App) Start() error {
|
|||
|
||||
// finish automatic HTTPS by finally beginning
|
||||
// certificate management
|
||||
err = app.automaticHTTPSPhase3()
|
||||
err := app.automaticHTTPSPhase2()
|
||||
if err != nil {
|
||||
return fmt.Errorf("finalizing automatic HTTPS: %v", err)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/mholt/certmagic"
|
||||
"github.com/caddyserver/certmagic"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -16,6 +16,7 @@ package httpcache
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -108,7 +109,8 @@ func (c *Cache) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp
|
|||
return next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
ctx := getterContext{w, r, next}
|
||||
getterCtx := getterContext{w, r, next}
|
||||
ctx := context.WithValue(r.Context(), getterContextCtxKey, getterCtx)
|
||||
|
||||
// TODO: rigorous performance testing
|
||||
|
||||
|
@ -152,8 +154,8 @@ func (c *Cache) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) getter(ctx groupcache.Context, key string, dest groupcache.Sink) error {
|
||||
combo := ctx.(getterContext)
|
||||
func (c *Cache) getter(ctx context.Context, key string, dest groupcache.Sink) error {
|
||||
combo := ctx.Value(getterContextCtxKey).(getterContext)
|
||||
|
||||
// the buffer will store the gob-encoded header, then the body
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
|
@ -228,6 +230,10 @@ var errUncacheable = fmt.Errorf("uncacheable")
|
|||
|
||||
const groupName = "http_requests"
|
||||
|
||||
type ctxKey string
|
||||
|
||||
const getterContextCtxKey ctxKey = "getter_context"
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.Provisioner = (*Cache)(nil)
|
||||
|
|
|
@ -29,7 +29,7 @@ import (
|
|||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
||||
"github.com/mholt/certmagic"
|
||||
"github.com/caddyserver/certmagic"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
207
modules/caddytls/acmeissuer.go
Normal file
207
modules/caddytls/acmeissuer.go
Normal file
|
@ -0,0 +1,207 @@
|
|||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package caddytls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(ACMEIssuer{})
|
||||
}
|
||||
|
||||
// ACMEIssuer makes an ACME manager
|
||||
// for managing certificates using ACME.
|
||||
//
|
||||
// TODO: support multiple ACME endpoints (probably
|
||||
// requires an array of these structs) - caddy would
|
||||
// also have to load certs from the backup CAs if the
|
||||
// first one is expired...
|
||||
type ACMEIssuer struct {
|
||||
// The URL to the CA's ACME directory endpoint.
|
||||
CA string `json:"ca,omitempty"`
|
||||
|
||||
// The URL to the test CA's ACME directory endpoint.
|
||||
// This endpoint is only used during retries if there
|
||||
// is a failure using the primary CA.
|
||||
TestCA string `json:"test_ca,omitempty"`
|
||||
|
||||
// Your email address, so the CA can contact you if necessary.
|
||||
// Not required, but strongly recommended to provide one so
|
||||
// you can be reached if there is a problem. Your email is
|
||||
// not sent to any Caddy mothership or used for any purpose
|
||||
// other than ACME transactions.
|
||||
Email string `json:"email,omitempty"`
|
||||
|
||||
// Time to wait before timing out an ACME operation.
|
||||
ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"`
|
||||
|
||||
// Configures the various ACME challenge types.
|
||||
Challenges *ChallengesConfig `json:"challenges,omitempty"`
|
||||
|
||||
// An array of files of CA certificates to accept when connecting to the
|
||||
// ACME CA. Generally, you should only use this if the ACME CA endpoint
|
||||
// is internal or for development/testing purposes.
|
||||
TrustedRootsPEMFiles []string `json:"trusted_roots_pem_files,omitempty"`
|
||||
|
||||
rootPool *x509.CertPool
|
||||
template certmagic.ACMEManager
|
||||
magic *certmagic.Config
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (ACMEIssuer) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "tls.issuance.acme",
|
||||
New: func() caddy.Module { return new(ACMEIssuer) },
|
||||
}
|
||||
}
|
||||
|
||||
// Provision sets up m.
|
||||
func (m *ACMEIssuer) Provision(ctx caddy.Context) error {
|
||||
// DNS providers
|
||||
if m.Challenges != nil && m.Challenges.DNSRaw != nil {
|
||||
val, err := ctx.LoadModule(m.Challenges, "DNSRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading DNS provider module: %v", err)
|
||||
}
|
||||
prov, err := val.(DNSProviderMaker).NewDNSProvider()
|
||||
if err != nil {
|
||||
return fmt.Errorf("making DNS provider: %v", err)
|
||||
}
|
||||
m.Challenges.DNS = prov
|
||||
}
|
||||
|
||||
// add any custom CAs to trust store
|
||||
if len(m.TrustedRootsPEMFiles) > 0 {
|
||||
m.rootPool = x509.NewCertPool()
|
||||
for _, pemFile := range m.TrustedRootsPEMFiles {
|
||||
pemData, err := ioutil.ReadFile(pemFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading trusted root CA's PEM file: %s: %v", pemFile, err)
|
||||
}
|
||||
if !m.rootPool.AppendCertsFromPEM(pemData) {
|
||||
return fmt.Errorf("unable to add %s to trust pool: %v", pemFile, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m.template = m.makeIssuerTemplate()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ACMEIssuer) makeIssuerTemplate() certmagic.ACMEManager {
|
||||
template := certmagic.ACMEManager{
|
||||
CA: m.CA,
|
||||
Email: m.Email,
|
||||
Agreed: true,
|
||||
CertObtainTimeout: time.Duration(m.ACMETimeout),
|
||||
TrustedRoots: m.rootPool,
|
||||
}
|
||||
|
||||
if m.Challenges != nil {
|
||||
if m.Challenges.HTTP != nil {
|
||||
template.DisableHTTPChallenge = m.Challenges.HTTP.Disabled
|
||||
template.AltHTTPPort = m.Challenges.HTTP.AlternatePort
|
||||
}
|
||||
if m.Challenges.TLSALPN != nil {
|
||||
template.DisableTLSALPNChallenge = m.Challenges.TLSALPN.Disabled
|
||||
template.AltTLSALPNPort = m.Challenges.TLSALPN.AlternatePort
|
||||
}
|
||||
template.DNSProvider = m.Challenges.DNS
|
||||
}
|
||||
|
||||
return template
|
||||
}
|
||||
|
||||
// SetConfig sets the associated certmagic config for this issuer.
|
||||
// This is required because ACME needs values from the config in
|
||||
// order to solve the challenges during issuance. This implements
|
||||
// the ConfigSetter interface.
|
||||
func (m *ACMEIssuer) SetConfig(cfg *certmagic.Config) {
|
||||
m.magic = cfg
|
||||
}
|
||||
|
||||
// PreCheck implements the certmagic.PreChecker interface.
|
||||
func (m *ACMEIssuer) PreCheck(names []string, interactive bool) (skip bool, err error) {
|
||||
return certmagic.NewACMEManager(m.magic, m.template).PreCheck(names, interactive)
|
||||
}
|
||||
|
||||
// Issue obtains a certificate for the given csr.
|
||||
func (m *ACMEIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
|
||||
return certmagic.NewACMEManager(m.magic, m.template).Issue(ctx, csr)
|
||||
}
|
||||
|
||||
// IssuerKey returns the unique issuer key for the configured CA endpoint.
|
||||
func (m *ACMEIssuer) IssuerKey() string {
|
||||
return m.template.IssuerKey() // does not need storage and cache
|
||||
}
|
||||
|
||||
// Revoke revokes the given certificate.
|
||||
func (m *ACMEIssuer) Revoke(ctx context.Context, cert certmagic.CertificateResource) error {
|
||||
return certmagic.NewACMEManager(m.magic, m.template).Revoke(ctx, cert)
|
||||
}
|
||||
|
||||
// onDemandAskRequest makes a request to the ask URL
|
||||
// to see if a certificate can be obtained for name.
|
||||
// The certificate request should be denied if this
|
||||
// returns an error.
|
||||
func onDemandAskRequest(ask string, name string) error {
|
||||
askURL, err := url.Parse(ask)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing ask URL: %v", err)
|
||||
}
|
||||
qs := askURL.Query()
|
||||
qs.Set("domain", name)
|
||||
askURL.RawQuery = qs.Encode()
|
||||
|
||||
resp, err := onDemandAskClient.Get(askURL.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error checking %v to deterine if certificate for hostname '%s' should be allowed: %v",
|
||||
ask, name, err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
return fmt.Errorf("certificate for hostname '%s' not allowed; non-2xx status code %d returned from %v",
|
||||
name, resp.StatusCode, ask)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DNSProviderMaker is a type that can create a new DNS provider.
|
||||
// Modules in the tls.dns namespace should implement this interface.
|
||||
type DNSProviderMaker interface {
|
||||
NewDNSProvider() (challenge.Provider, error)
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ certmagic.Issuer = (*ACMEIssuer)(nil)
|
||||
_ certmagic.Revoker = (*ACMEIssuer)(nil)
|
||||
_ certmagic.PreChecker = (*ACMEIssuer)(nil)
|
||||
_ ConfigSetter = (*ACMEIssuer)(nil)
|
||||
)
|
|
@ -1,252 +0,0 @@
|
|||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package caddytls
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/mholt/certmagic"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(ACMEManagerMaker{})
|
||||
}
|
||||
|
||||
// ACMEManagerMaker makes an ACME manager
|
||||
// for managing certificates using ACME.
|
||||
// If crafting one manually rather than
|
||||
// through the config-unmarshal process
|
||||
// (provisioning), be sure to call
|
||||
// SetDefaults to ensure sane defaults
|
||||
// after you have configured this struct
|
||||
// to your liking.
|
||||
type ACMEManagerMaker struct {
|
||||
// The URL to the CA's ACME directory endpoint.
|
||||
CA string `json:"ca,omitempty"`
|
||||
|
||||
// Your email address, so the CA can contact you if necessary.
|
||||
// Not required, but strongly recommended to provide one so
|
||||
// you can be reached if there is a problem. Your email is
|
||||
// not sent to any Caddy mothership or used for any purpose
|
||||
// other than ACME transactions.
|
||||
Email string `json:"email,omitempty"`
|
||||
|
||||
// How long before a certificate's expiration to try renewing it.
|
||||
// Should usually be about 1/3 of certificate lifetime, but long
|
||||
// enough to give yourself time to troubleshoot problems before
|
||||
// expiration. Default: 30d
|
||||
RenewAhead caddy.Duration `json:"renew_ahead,omitempty"`
|
||||
|
||||
// The type of key to generate for the certificate.
|
||||
// Supported values: `rsa2048`, `rsa4096`, `p256`, `p384`.
|
||||
KeyType string `json:"key_type,omitempty"`
|
||||
|
||||
// Time to wait before timing out an ACME operation.
|
||||
ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"`
|
||||
|
||||
// If true, certificates will be requested with MustStaple. Not all
|
||||
// CAs support this, and there are potentially serious consequences
|
||||
// of enabling this feature without proper threat modeling.
|
||||
MustStaple bool `json:"must_staple,omitempty"`
|
||||
|
||||
// Configures the various ACME challenge types.
|
||||
Challenges *ChallengesConfig `json:"challenges,omitempty"`
|
||||
|
||||
// If true, certificates will be managed "on demand", that is, during
|
||||
// TLS handshakes or when needed, as opposed to at startup or config
|
||||
// load.
|
||||
OnDemand bool `json:"on_demand,omitempty"`
|
||||
|
||||
// Optionally configure a separate storage module associated with this
|
||||
// manager, instead of using Caddy's global/default-configured storage.
|
||||
Storage json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
|
||||
|
||||
// An array of files of CA certificates to accept when connecting to the
|
||||
// ACME CA. Generally, you should only use this if the ACME CA endpoint
|
||||
// is internal or for development/testing purposes.
|
||||
TrustedRootsPEMFiles []string `json:"trusted_roots_pem_files,omitempty"`
|
||||
|
||||
storage certmagic.Storage
|
||||
rootPool *x509.CertPool
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (ACMEManagerMaker) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "tls.management.acme",
|
||||
New: func() caddy.Module { return new(ACMEManagerMaker) },
|
||||
}
|
||||
}
|
||||
|
||||
// NewManager is a no-op to satisfy the ManagerMaker interface,
|
||||
// because this manager type is a special case.
|
||||
func (m ACMEManagerMaker) NewManager(interactive bool) (certmagic.Manager, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Provision sets up m.
|
||||
func (m *ACMEManagerMaker) Provision(ctx caddy.Context) error {
|
||||
// DNS providers
|
||||
if m.Challenges != nil && m.Challenges.DNSRaw != nil {
|
||||
val, err := ctx.LoadModule(m.Challenges, "DNSRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading DNS provider module: %v", err)
|
||||
}
|
||||
prov, err := val.(DNSProviderMaker).NewDNSProvider()
|
||||
if err != nil {
|
||||
return fmt.Errorf("making DNS provider: %v", err)
|
||||
}
|
||||
m.Challenges.DNS = prov
|
||||
}
|
||||
|
||||
// policy-specific storage implementation
|
||||
if m.Storage != nil {
|
||||
val, err := ctx.LoadModule(m, "Storage")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading TLS storage module: %v", err)
|
||||
}
|
||||
cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage()
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating TLS storage configuration: %v", err)
|
||||
}
|
||||
m.storage = cmStorage
|
||||
}
|
||||
|
||||
// add any custom CAs to trust store
|
||||
if len(m.TrustedRootsPEMFiles) > 0 {
|
||||
m.rootPool = x509.NewCertPool()
|
||||
for _, pemFile := range m.TrustedRootsPEMFiles {
|
||||
pemData, err := ioutil.ReadFile(pemFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading trusted root CA's PEM file: %s: %v", pemFile, err)
|
||||
}
|
||||
if !m.rootPool.AppendCertsFromPEM(pemData) {
|
||||
return fmt.Errorf("unable to add %s to trust pool: %v", pemFile, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeCertMagicConfig converts m into a certmagic.Config, because
|
||||
// this is a special case where the default manager is the certmagic
|
||||
// Config and not a separate manager.
|
||||
func (m *ACMEManagerMaker) makeCertMagicConfig(ctx caddy.Context) certmagic.Config {
|
||||
storage := m.storage
|
||||
if storage == nil {
|
||||
storage = ctx.Storage()
|
||||
}
|
||||
|
||||
var ond *certmagic.OnDemandConfig
|
||||
if m.OnDemand {
|
||||
var onDemand *OnDemandConfig
|
||||
appVal, err := ctx.App("tls")
|
||||
if err == nil && appVal.(*TLS).Automation != nil {
|
||||
onDemand = appVal.(*TLS).Automation.OnDemand
|
||||
}
|
||||
|
||||
ond = &certmagic.OnDemandConfig{
|
||||
DecisionFunc: func(name string) error {
|
||||
if onDemand != nil {
|
||||
if onDemand.Ask != "" {
|
||||
err := onDemandAskRequest(onDemand.Ask, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// check the rate limiter last because
|
||||
// doing so makes a reservation
|
||||
if !onDemandRateLimiter.Allow() {
|
||||
return fmt.Errorf("on-demand rate limit exceeded")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
cfg := certmagic.Config{
|
||||
CA: m.CA,
|
||||
Email: m.Email,
|
||||
Agreed: true,
|
||||
RenewDurationBefore: time.Duration(m.RenewAhead),
|
||||
KeyType: supportedCertKeyTypes[m.KeyType],
|
||||
CertObtainTimeout: time.Duration(m.ACMETimeout),
|
||||
OnDemand: ond,
|
||||
MustStaple: m.MustStaple,
|
||||
Storage: storage,
|
||||
TrustedRoots: m.rootPool,
|
||||
// TODO: listenHost
|
||||
}
|
||||
|
||||
if m.Challenges != nil {
|
||||
if m.Challenges.HTTP != nil {
|
||||
cfg.DisableHTTPChallenge = m.Challenges.HTTP.Disabled
|
||||
cfg.AltHTTPPort = m.Challenges.HTTP.AlternatePort
|
||||
}
|
||||
if m.Challenges.TLSALPN != nil {
|
||||
cfg.DisableTLSALPNChallenge = m.Challenges.TLSALPN.Disabled
|
||||
cfg.AltTLSALPNPort = m.Challenges.TLSALPN.AlternatePort
|
||||
}
|
||||
cfg.DNSProvider = m.Challenges.DNS
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
// onDemandAskRequest makes a request to the ask URL
|
||||
// to see if a certificate can be obtained for name.
|
||||
// The certificate request should be denied if this
|
||||
// returns an error.
|
||||
func onDemandAskRequest(ask string, name string) error {
|
||||
askURL, err := url.Parse(ask)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing ask URL: %v", err)
|
||||
}
|
||||
qs := askURL.Query()
|
||||
qs.Set("domain", name)
|
||||
askURL.RawQuery = qs.Encode()
|
||||
|
||||
resp, err := onDemandAskClient.Get(askURL.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error checking %v to determine if certificate for hostname '%s' should be allowed: %v",
|
||||
ask, name, err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
return fmt.Errorf("certificate for hostname '%s' not allowed; non-2xx status code %d returned from %v",
|
||||
name, resp.StatusCode, ask)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DNSProviderMaker is a type that can create a new DNS provider.
|
||||
// Modules in the tls.dns namespace should implement this interface.
|
||||
type DNSProviderMaker interface {
|
||||
NewDNSProvider() (challenge.Provider, error)
|
||||
}
|
||||
|
||||
// Interface guard
|
||||
var _ ManagerMaker = (*ACMEManagerMaker)(nil)
|
|
@ -7,7 +7,7 @@ import (
|
|||
"math/big"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/mholt/certmagic"
|
||||
"github.com/caddyserver/certmagic"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -23,8 +23,8 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
|
||||
"github.com/mholt/certmagic"
|
||||
)
|
||||
|
||||
// ConnectionPolicies is an ordered group of connection policies;
|
||||
|
@ -32,16 +32,15 @@ import (
|
|||
// connections at handshake-time.
|
||||
type ConnectionPolicies []*ConnectionPolicy
|
||||
|
||||
// TLSConfig converts the group of policies to a standard-lib-compatible
|
||||
// TLS configuration which selects the first matching policy based on
|
||||
// the ClientHello.
|
||||
func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) (*tls.Config, error) {
|
||||
// set up each of the connection policies
|
||||
// Provision sets up each connection policy. It should be called
|
||||
// during the Validate() phase, after the TLS app (if any) is
|
||||
// already set up.
|
||||
func (cp ConnectionPolicies) Provision(ctx caddy.Context) error {
|
||||
for i, pol := range cp {
|
||||
// matchers
|
||||
mods, err := ctx.LoadModule(pol, "MatchersRaw")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading handshake matchers: %v", err)
|
||||
return fmt.Errorf("loading handshake matchers: %v", err)
|
||||
}
|
||||
for _, modIface := range mods.(map[string]interface{}) {
|
||||
cp[i].matchers = append(cp[i].matchers, modIface.(ConnectionMatcher))
|
||||
|
@ -51,20 +50,24 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) (*tls.Config, error) {
|
|||
if pol.CertSelection != nil {
|
||||
val, err := ctx.LoadModule(pol, "CertSelection")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading certificate selection module: %s", err)
|
||||
return fmt.Errorf("loading certificate selection module: %s", err)
|
||||
}
|
||||
cp[i].certSelector = val.(certmagic.CertificateSelector)
|
||||
}
|
||||
}
|
||||
|
||||
// pre-build standard TLS configs so we don't have to at handshake-time
|
||||
for i := range cp {
|
||||
err := cp[i].buildStandardTLSConfig(ctx)
|
||||
// pre-build standard TLS config so we don't have to at handshake-time
|
||||
err = pol.buildStandardTLSConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connection policy %d: building standard TLS config: %s", i, err)
|
||||
return fmt.Errorf("connection policy %d: building standard TLS config: %s", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TLSConfig returns a standard-lib-compatible TLS configuration which
|
||||
// selects the first matching policy based on the ClientHello.
|
||||
func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) *tls.Config {
|
||||
// using ServerName to match policies is extremely common, especially in configs
|
||||
// with lots and lots of different policies; we can fast-track those by indexing
|
||||
// them by SNI, so we don't have to iterate potentially thousands of policies
|
||||
|
@ -102,7 +105,7 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) (*tls.Config, error) {
|
|||
|
||||
return nil, fmt.Errorf("no server TLS configuration available for ClientHello: %+v", hello)
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionPolicy specifies the logic for handling a TLS handshake.
|
||||
|
@ -137,6 +140,10 @@ type ConnectionPolicy struct {
|
|||
// Enables and configures TLS client authentication.
|
||||
ClientAuthentication *ClientAuthentication `json:"client_authentication,omitempty"`
|
||||
|
||||
// DefaultSNI becomes the ServerName in a ClientHello if there
|
||||
// is no policy configured for the empty SNI value.
|
||||
DefaultSNI string `json:"default_sni,omitempty"`
|
||||
|
||||
matchers []ConnectionMatcher
|
||||
certSelector certmagic.CertificateSelector
|
||||
|
||||
|
@ -158,15 +165,24 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error {
|
|||
NextProtos: p.ALPN,
|
||||
PreferServerCipherSuites: true,
|
||||
GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
cfgTpl, err := tlsApp.getConfigForName(hello.ServerName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting config for name %s: %v", hello.ServerName, err)
|
||||
}
|
||||
newCfg := certmagic.New(tlsApp.certCache, cfgTpl)
|
||||
// TODO: I don't love how this works: we pre-build certmagic configs
|
||||
// so that handshakes are faster. Unfortunately, certmagic configs are
|
||||
// comprised of settings from both a TLS connection policy and a TLS
|
||||
// automation policy. The only two fields (as of March 2020; v2 beta 16)
|
||||
// of a certmagic config that come from the TLS connection policy are
|
||||
// CertSelection and DefaultServerName, so an automation policy is what
|
||||
// builds the base certmagic config. Since the pre-built config is
|
||||
// shared, I don't think we can change any of its fields per-handshake,
|
||||
// hence the awkward shallow copy (dereference) here and the subsequent
|
||||
// changing of some of its fields. I'm worried this dereference allocates
|
||||
// more at handshake-time, but I don't know how to practically pre-build
|
||||
// a certmagic config for each combination of conn policy + automation policy...
|
||||
cfg := *tlsApp.getConfigForName(hello.ServerName)
|
||||
if p.certSelector != nil {
|
||||
newCfg.CertSelection = p.certSelector
|
||||
cfg.CertSelection = p.certSelector
|
||||
}
|
||||
return newCfg.GetCertificate(hello)
|
||||
cfg.DefaultServerName = p.DefaultSNI
|
||||
return cfg.GetCertificate(hello)
|
||||
},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
|
@ -240,8 +256,6 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: other fields
|
||||
|
||||
setDefaultTLSParams(cfg)
|
||||
|
||||
p.stdTLSConfig = cfg
|
||||
|
|
|
@ -32,7 +32,7 @@ import (
|
|||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/mholt/certmagic"
|
||||
"github.com/caddyserver/certmagic"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -23,8 +23,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/mholt/certmagic"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
@ -71,13 +71,15 @@ func (TLS) CaddyModule() caddy.ModuleInfo {
|
|||
|
||||
// Provision sets up the configuration for the TLS app.
|
||||
func (t *TLS) Provision(ctx caddy.Context) error {
|
||||
// TODO: Move assets to the new folder structure!!
|
||||
|
||||
t.ctx = ctx
|
||||
t.logger = ctx.Logger(t)
|
||||
|
||||
// set up a new certificate cache; this (re)loads all certificates
|
||||
cacheOpts := certmagic.CacheOptions{
|
||||
GetConfigForCert: func(cert certmagic.Certificate) (certmagic.Config, error) {
|
||||
return t.getConfigForName(cert.Names[0])
|
||||
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
|
||||
return t.getConfigForName(cert.Names[0]), nil
|
||||
},
|
||||
}
|
||||
if t.Automation != nil {
|
||||
|
@ -87,20 +89,25 @@ func (t *TLS) Provision(ctx caddy.Context) error {
|
|||
t.certCache = certmagic.NewCache(cacheOpts)
|
||||
|
||||
// automation/management policies
|
||||
if t.Automation != nil {
|
||||
for i, ap := range t.Automation.Policies {
|
||||
val, err := ctx.LoadModule(ap, "ManagementRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading TLS automation management module: %s", err)
|
||||
}
|
||||
t.Automation.Policies[i].Management = val.(ManagerMaker)
|
||||
if t.Automation == nil {
|
||||
t.Automation = new(AutomationConfig)
|
||||
}
|
||||
t.Automation.defaultAutomationPolicy = new(AutomationPolicy)
|
||||
err := t.Automation.defaultAutomationPolicy.provision(t)
|
||||
if err != nil {
|
||||
return fmt.Errorf("provisioning default automation policy: %v", err)
|
||||
}
|
||||
for i, ap := range t.Automation.Policies {
|
||||
err := ap.provision(t)
|
||||
if err != nil {
|
||||
return fmt.Errorf("provisioning automation policy %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// certificate loaders
|
||||
val, err := ctx.LoadModule(t, "CertificatesRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading TLS automation management module: %s", err)
|
||||
return fmt.Errorf("loading certificate loader modules: %s", err)
|
||||
}
|
||||
for modName, modIface := range val.(map[string]interface{}) {
|
||||
if modName == "automate" {
|
||||
|
@ -216,12 +223,11 @@ func (t *TLS) Manage(names []string) error {
|
|||
// certmagic.Config for each (potentially large) group of names
|
||||
// and call ManageSync/ManageAsync just once for the whole batch
|
||||
for ap, names := range policyToNames {
|
||||
magic := certmagic.New(t.certCache, ap.makeCertMagicConfig(t.ctx))
|
||||
var err error
|
||||
if ap.ManageSync {
|
||||
err = magic.ManageSync(names)
|
||||
err = ap.magic.ManageSync(names)
|
||||
} else {
|
||||
err = magic.ManageAsync(t.ctx.Context, names)
|
||||
err = ap.magic.ManageAsync(t.ctx.Context, names)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("automate: manage %v: %v", names, err)
|
||||
|
@ -232,36 +238,54 @@ func (t *TLS) Manage(names []string) error {
|
|||
}
|
||||
|
||||
// HandleHTTPChallenge ensures that the HTTP challenge is handled for the
|
||||
// certificate named by r.Host, if it is an HTTP challenge request.
|
||||
// certificate named by r.Host, if it is an HTTP challenge request. It
|
||||
// requires that the automation policy for r.Host has an issue of type
|
||||
// *certmagic.ACMEManager.
|
||||
func (t *TLS) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool {
|
||||
if !certmagic.LooksLikeHTTPChallenge(r) {
|
||||
return false
|
||||
}
|
||||
ap := t.getAutomationPolicyForName(r.Host)
|
||||
magic := certmagic.New(t.certCache, ap.makeCertMagicConfig(t.ctx))
|
||||
return magic.HandleHTTPChallenge(w, r)
|
||||
if ap.magic.Issuer == nil {
|
||||
return false
|
||||
}
|
||||
if am, ok := ap.magic.Issuer.(*certmagic.ACMEManager); ok {
|
||||
return am.HandleHTTPChallenge(w, r)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *TLS) getConfigForName(name string) (certmagic.Config, error) {
|
||||
// AddAutomationPolicy provisions and adds ap to the list of the app's
|
||||
// automation policies.
|
||||
func (t *TLS) AddAutomationPolicy(ap *AutomationPolicy) error {
|
||||
if t.Automation == nil {
|
||||
t.Automation = new(AutomationConfig)
|
||||
}
|
||||
err := ap.provision(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Automation.Policies = append(t.Automation.Policies, ap)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TLS) getConfigForName(name string) *certmagic.Config {
|
||||
ap := t.getAutomationPolicyForName(name)
|
||||
return ap.makeCertMagicConfig(t.ctx), nil
|
||||
return ap.magic
|
||||
}
|
||||
|
||||
func (t *TLS) getAutomationPolicyForName(name string) *AutomationPolicy {
|
||||
if t.Automation != nil {
|
||||
for _, ap := range t.Automation.Policies {
|
||||
if len(ap.Hosts) == 0 {
|
||||
// no host filter is an automatic match
|
||||
for _, ap := range t.Automation.Policies {
|
||||
if len(ap.Hosts) == 0 {
|
||||
return ap // no host filter is an automatic match
|
||||
}
|
||||
for _, h := range ap.Hosts {
|
||||
if h == name {
|
||||
return ap
|
||||
}
|
||||
for _, h := range ap.Hosts {
|
||||
if h == name {
|
||||
return ap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultAutomationPolicy
|
||||
return t.Automation.defaultAutomationPolicy
|
||||
}
|
||||
|
||||
// AllMatchingCertificates returns the list of all certificates in
|
||||
|
@ -309,10 +333,8 @@ func (t *TLS) cleanStorageUnits() {
|
|||
// then clean each storage defined in ACME automation policies
|
||||
if t.Automation != nil {
|
||||
for _, ap := range t.Automation.Policies {
|
||||
if acmeMgmt, ok := ap.Management.(ACMEManagerMaker); ok {
|
||||
if acmeMgmt.storage != nil {
|
||||
certmagic.CleanStorage(acmeMgmt.storage, options)
|
||||
}
|
||||
if ap.storage != nil {
|
||||
certmagic.CleanStorage(ap.storage, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -355,23 +377,56 @@ type AutomationConfig struct {
|
|||
OCSPCheckInterval caddy.Duration `json:"ocsp_interval,omitempty"`
|
||||
|
||||
// Every so often, Caddy will scan all loaded, managed
|
||||
// certificates for expiration. Certificates which are
|
||||
// about 2/3 into their valid lifetime are due for
|
||||
// renewal. This setting changes how frequently the scan
|
||||
// is performed. If your certificate lifetimes are very
|
||||
// short (less than ~1 week), you should customize this.
|
||||
// certificates for expiration. This setting changes how
|
||||
// frequently the scan for expiring certificates is
|
||||
// performed. If your certificate lifetimes are very
|
||||
// short (less than ~24 hours), you should set this to
|
||||
// a low value.
|
||||
RenewCheckInterval caddy.Duration `json:"renew_interval,omitempty"`
|
||||
|
||||
defaultAutomationPolicy *AutomationPolicy
|
||||
}
|
||||
|
||||
// AutomationPolicy designates the policy for automating the
|
||||
// management (obtaining, renewal, and revocation) of managed
|
||||
// TLS certificates.
|
||||
//
|
||||
// An AutomationPolicy value is not valid until it has been
|
||||
// provisioned; use the `AddAutomationPolicy()` method on the
|
||||
// TLS app to properly provision a new policy.
|
||||
type AutomationPolicy struct {
|
||||
// Which hostnames this policy applies to.
|
||||
Hosts []string `json:"hosts,omitempty"`
|
||||
|
||||
// How to manage certificates.
|
||||
ManagementRaw json.RawMessage `json:"management,omitempty" caddy:"namespace=tls.management inline_key=module"`
|
||||
// The module that will issue certificates. Default: acme
|
||||
IssuerRaw json.RawMessage `json:"issuer,omitempty" caddy:"namespace=tls.issuance inline_key=module"`
|
||||
|
||||
// If true, certificates will be requested with MustStaple. Not all
|
||||
// CAs support this, and there are potentially serious consequences
|
||||
// of enabling this feature without proper threat modeling.
|
||||
MustStaple bool `json:"must_staple,omitempty"`
|
||||
|
||||
// How long before a certificate's expiration to try renewing it,
|
||||
// as a function of its total lifetime. As a general and conservative
|
||||
// rule, it is a good idea to renew a certificate when it has about
|
||||
// 1/3 of its total lifetime remaining. This utilizes the majority
|
||||
// of the certificate's lifetime while still saving time to
|
||||
// troubleshoot problems. However, for extremely short-lived certs,
|
||||
// you may wish to increase the ratio to ~1/2.
|
||||
RenewalWindowRatio float64 `json:"renewal_window_ratio,omitempty"`
|
||||
|
||||
// The type of key to generate for certificates.
|
||||
// Supported values: `ed25519`, `p256`, `p384`, `rsa2048`, `rsa4096`.
|
||||
KeyType string `json:"key_type,omitempty"`
|
||||
|
||||
// Optionally configure a separate storage module associated with this
|
||||
// manager, instead of using Caddy's global/default-configured storage.
|
||||
StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
|
||||
|
||||
// If true, certificates will be managed "on demand", that is, during
|
||||
// TLS handshakes or when needed, as opposed to at startup or config
|
||||
// load.
|
||||
OnDemand bool `json:"on_demand,omitempty"`
|
||||
|
||||
// If true, certificate management will be conducted
|
||||
// in the foreground; this will block config reloads
|
||||
|
@ -381,23 +436,96 @@ type AutomationPolicy struct {
|
|||
// of your control. Default: false
|
||||
ManageSync bool `json:"manage_sync,omitempty"`
|
||||
|
||||
Management ManagerMaker `json:"-"`
|
||||
Issuer certmagic.Issuer `json:"-"`
|
||||
|
||||
magic *certmagic.Config
|
||||
storage certmagic.Storage
|
||||
}
|
||||
|
||||
// makeCertMagicConfig converts ap into a CertMagic config. Passing onDemand
|
||||
// is necessary because the automation policy does not have convenient access
|
||||
// to the TLS app's global on-demand policies;
|
||||
func (ap AutomationPolicy) makeCertMagicConfig(ctx caddy.Context) certmagic.Config {
|
||||
// default manager (ACME) is a special case because of how CertMagic is designed
|
||||
// TODO: refactor certmagic so that ACME manager is not a special case by extracting
|
||||
// its config fields out of the certmagic.Config struct, or something...
|
||||
if acmeMgmt, ok := ap.Management.(*ACMEManagerMaker); ok {
|
||||
return acmeMgmt.makeCertMagicConfig(ctx)
|
||||
// provision converts ap into a CertMagic config.
|
||||
func (ap *AutomationPolicy) provision(tlsApp *TLS) error {
|
||||
// policy-specific storage implementation
|
||||
if ap.StorageRaw != nil {
|
||||
val, err := tlsApp.ctx.LoadModule(ap, "StorageRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading TLS storage module: %v", err)
|
||||
}
|
||||
cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage()
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating TLS storage configuration: %v", err)
|
||||
}
|
||||
ap.storage = cmStorage
|
||||
}
|
||||
|
||||
return certmagic.Config{
|
||||
NewManager: ap.Management.NewManager,
|
||||
var ond *certmagic.OnDemandConfig
|
||||
if ap.OnDemand {
|
||||
var onDemand *OnDemandConfig
|
||||
if tlsApp.Automation != nil {
|
||||
onDemand = tlsApp.Automation.OnDemand
|
||||
}
|
||||
|
||||
ond = &certmagic.OnDemandConfig{
|
||||
DecisionFunc: func(name string) error {
|
||||
if onDemand != nil {
|
||||
if onDemand.Ask != "" {
|
||||
err := onDemandAskRequest(onDemand.Ask, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// check the rate limiter last because
|
||||
// doing so makes a reservation
|
||||
if !onDemandRateLimiter.Allow() {
|
||||
return fmt.Errorf("on-demand rate limit exceeded")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
keySource := certmagic.StandardKeyGenerator{
|
||||
KeyType: supportedCertKeyTypes[ap.KeyType],
|
||||
}
|
||||
|
||||
storage := ap.storage
|
||||
if storage == nil {
|
||||
storage = tlsApp.ctx.Storage()
|
||||
}
|
||||
|
||||
template := certmagic.Config{
|
||||
MustStaple: ap.MustStaple,
|
||||
RenewalWindowRatio: ap.RenewalWindowRatio,
|
||||
KeySource: keySource,
|
||||
OnDemand: ond,
|
||||
Storage: storage,
|
||||
}
|
||||
cfg := certmagic.New(tlsApp.certCache, template)
|
||||
ap.magic = cfg
|
||||
|
||||
if ap.IssuerRaw != nil {
|
||||
val, err := tlsApp.ctx.LoadModule(ap, "IssuerRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading TLS automation management module: %s", err)
|
||||
}
|
||||
ap.Issuer = val.(certmagic.Issuer)
|
||||
}
|
||||
|
||||
// sometimes issuers may need the parent certmagic.Config in
|
||||
// order to function properly (for example, ACMEIssuer needs
|
||||
// access to the correct storage and cache so it can solve
|
||||
// ACME challenges -- it's an annoying, inelegant circular
|
||||
// dependency that I don't know how to resolve nicely!)
|
||||
if configger, ok := ap.Issuer.(ConfigSetter); ok {
|
||||
configger.SetConfig(cfg)
|
||||
}
|
||||
|
||||
cfg.Issuer = ap.Issuer
|
||||
if rev, ok := ap.Issuer.(certmagic.Revoker); ok {
|
||||
cfg.Revoker = rev
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChallengesConfig configures the ACME challenges.
|
||||
|
@ -482,11 +610,6 @@ type RateLimit struct {
|
|||
Burst int `json:"burst,omitempty"`
|
||||
}
|
||||
|
||||
// ManagerMaker makes a certificate manager.
|
||||
type ManagerMaker interface {
|
||||
NewManager(interactive bool) (certmagic.Manager, error)
|
||||
}
|
||||
|
||||
// AutomateLoader is a no-op certificate loader module
|
||||
// that is treated as a special case: it uses this app's
|
||||
// automation features to load certificates for the
|
||||
|
@ -502,6 +625,15 @@ func (AutomateLoader) CaddyModule() caddy.ModuleInfo {
|
|||
}
|
||||
}
|
||||
|
||||
// ConfigSetter is implemented by certmagic.Issuers that
|
||||
// need access to a parent certmagic.Config as part of
|
||||
// their provisioning phase. For example, the ACMEIssuer
|
||||
// requires a config so it can access storage and the
|
||||
// cache to solve ACME challenges.
|
||||
type ConfigSetter interface {
|
||||
SetConfig(cfg *certmagic.Config)
|
||||
}
|
||||
|
||||
// These perpetual values are used for on-demand TLS.
|
||||
var (
|
||||
onDemandRateLimiter = certmagic.NewRateLimiter(0, 0)
|
||||
|
@ -521,8 +653,6 @@ var (
|
|||
storageCleanMu sync.Mutex
|
||||
)
|
||||
|
||||
var defaultAutomationPolicy = &AutomationPolicy{Management: new(ACMEManagerMaker)}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.App = (*TLS)(nil)
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"crypto/x509"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/v3/certcrypto"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/klauspost/cpuid"
|
||||
)
|
||||
|
||||
|
@ -102,11 +102,12 @@ var SupportedCurves = map[string]tls.CurveID{
|
|||
|
||||
// supportedCertKeyTypes is all the key types that are supported
|
||||
// for certificates that are obtained through ACME.
|
||||
var supportedCertKeyTypes = map[string]certcrypto.KeyType{
|
||||
"rsa_2048": certcrypto.RSA2048,
|
||||
"rsa_4096": certcrypto.RSA4096,
|
||||
"ec_p256": certcrypto.EC256,
|
||||
"ec_p384": certcrypto.EC384,
|
||||
var supportedCertKeyTypes = map[string]certmagic.KeyType{
|
||||
"rsa2048": certmagic.RSA2048,
|
||||
"rsa4096": certmagic.RSA4096,
|
||||
"p256": certmagic.P256,
|
||||
"p384": certmagic.P384,
|
||||
"ed25519": certmagic.ED25519,
|
||||
}
|
||||
|
||||
// defaultCurves is the list of only the curves we want to use
|
||||
|
|
|
@ -17,7 +17,7 @@ package filestorage
|
|||
import (
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/mholt/certmagic"
|
||||
"github.com/caddyserver/certmagic"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/mholt/certmagic"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/mholt/certmagic"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user