Merge branch 'certmagic-refactor' into v2

This commit is contained in:
Matthew Holt 2020-03-06 23:24:09 -07:00
commit b216d285df
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5
26 changed files with 1161 additions and 540 deletions

View file

@ -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

View file

@ -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 := range newCfg.AppsRaw {
if _, err := ctx.App(appName); err != nil {
return err
}
for appName, appIface := range appsIface.(map[string]interface{}) {
newCfg.apps[appName] = appIface.(App)
}
return nil
}()

View file

@ -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

View file

@ -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

View file

@ -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
@ -198,7 +198,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
}
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, &caddytls.AutomationPolicy{
Hosts: sblockHosts,
ManagementRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID.Name(), &warnings),
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:]...)

View file

@ -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"
)

View file

@ -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")

View file

@ -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
View file

@ -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
)

523
go.sum

File diff suppressed because it is too large Load diff

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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() {

View file

@ -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)

View file

@ -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() {

View 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)
)

View file

@ -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)

View file

@ -7,7 +7,7 @@ import (
"math/big"
"github.com/caddyserver/caddy/v2"
"github.com/mholt/certmagic"
"github.com/caddyserver/certmagic"
)
func init() {

View file

@ -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

View file

@ -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() {

View file

@ -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)
if t.Automation == nil {
t.Automation = new(AutomationConfig)
}
t.Automation.Policies[i].Management = val.(ManagerMaker)
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,27 +238,46 @@ 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
return ap
return ap // no host filter is an automatic match
}
for _, h := range ap.Hosts {
if h == name {
@ -260,8 +285,7 @@ func (t *TLS) getAutomationPolicyForName(name string) *AutomationPolicy {
}
}
}
}
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)

View file

@ -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

View file

@ -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() {

View file

@ -21,7 +21,7 @@ import (
"os/signal"
"syscall"
"github.com/mholt/certmagic"
"github.com/caddyserver/certmagic"
"go.uber.org/zap"
)

View file

@ -19,7 +19,7 @@ import (
"path/filepath"
"runtime"
"github.com/mholt/certmagic"
"github.com/caddyserver/certmagic"
"go.uber.org/zap"
)