Fix bugs related to auto HTTPS and alternate port configurations

This commit is contained in:
Matthew Holt 2019-06-04 22:43:21 -06:00
parent 613aecb898
commit b79f86f256
3 changed files with 70 additions and 31 deletions

View File

@ -126,7 +126,8 @@ func (app *App) Start() error {
return fmt.Errorf("%s: listening on %s: %v", network, addr, err) return fmt.Errorf("%s: listening on %s: %v", network, addr, err)
} }
// enable HTTP/2 by default // enable HTTP/2 (and support for solving the
// TLS-ALPN ACME challenge) by default
for _, pol := range srv.TLSConnPolicies { for _, pol := range srv.TLSConnPolicies {
if len(pol.ALPN) == 0 { if len(pol.ALPN) == 0 {
pol.ALPN = append(pol.ALPN, defaultALPN...) pol.ALPN = append(pol.ALPN, defaultALPN...)
@ -219,13 +220,38 @@ func (app *App) automaticHTTPS() error {
domains = append(domains, d) domains = append(domains, 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,
},
TLSALPN: caddytls.TLSALPNChallengeConfig{
AlternatePort: app.HTTPSPort,
},
},
}
acmeManager.SetDefaults()
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies,
caddytls.AutomationPolicy{
Hosts: domains,
Management: acmeManager,
})
// manage their certificates // manage their certificates
err := tlsApp.Manage(domains) err := tlsApp.Manage(domains)
if err != nil { if err != nil {
return fmt.Errorf("%s: managing certificate for %s: %s", srvName, domains, err) return fmt.Errorf("%s: managing certificate for %s: %s", srvName, domains, err)
} }
// tell the server to use TLS // tell the server to use TLS by specifying a TLS
// connection policy (which supports HTTP/2 and the
// TLS-ALPN ACME challenge as well)
srv.TLSConnPolicies = caddytls.ConnectionPolicies{ srv.TLSConnPolicies = caddytls.ConnectionPolicies{
{ALPN: defaultALPN}, {ALPN: defaultALPN},
} }
@ -296,6 +322,7 @@ func (app *App) automaticHTTPS() error {
Listen: lnAddrs, Listen: lnAddrs,
Routes: redirRoutes, Routes: redirRoutes,
DisableAutoHTTPS: true, DisableAutoHTTPS: true,
tlsApp: tlsApp, // required to solve HTTP challenge
} }
} }

View File

@ -15,13 +15,19 @@ import (
func init() { func init() {
caddy2.RegisterModule(caddy2.Module{ caddy2.RegisterModule(caddy2.Module{
Name: "tls.management.acme", Name: "tls.management.acme",
New: func() interface{} { return new(acmeManagerMaker) }, New: func() interface{} { return new(ACMEManagerMaker) },
}) })
} }
// acmeManagerMaker makes an ACME manager // ACMEManagerMaker makes an ACME manager
// for managinig certificates using ACME. // for managing certificates using ACME.
type acmeManagerMaker struct { // 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 {
CA string `json:"ca,omitempty"` CA string `json:"ca,omitempty"`
Email string `json:"email,omitempty"` Email string `json:"email,omitempty"`
RenewAhead caddy2.Duration `json:"renew_ahead,omitempty"` RenewAhead caddy2.Duration `json:"renew_ahead,omitempty"`
@ -36,19 +42,22 @@ type acmeManagerMaker struct {
keyType certcrypto.KeyType keyType certcrypto.KeyType
} }
func (m *acmeManagerMaker) newManager(interactive bool) (certmagic.Manager, error) { // 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 return nil, nil
} }
func (m *acmeManagerMaker) Provision(ctx caddy2.Context) error { // Provision sets up m.
func (m *ACMEManagerMaker) Provision(ctx caddy2.Context) error {
// DNS providers // DNS providers
if m.Challenges.DNS != nil { if m.Challenges.DNS != nil {
val, err := ctx.LoadModuleInline("provider", "tls.dns", m.Challenges.DNS) val, err := ctx.LoadModuleInline("provider", "tls.dns", m.Challenges.DNSRaw)
if err != nil { if err != nil {
return fmt.Errorf("loading TLS storage module: %s", err) return fmt.Errorf("loading TLS storage module: %s", err)
} }
m.Challenges.dns = val.(challenge.Provider) m.Challenges.DNS = val.(challenge.Provider)
m.Challenges.DNS = nil // allow GC to deallocate - TODO: Does this help? m.Challenges.DNSRaw = nil // allow GC to deallocate - TODO: Does this help?
} }
// policy-specific storage implementation // policy-specific storage implementation
@ -65,14 +74,14 @@ func (m *acmeManagerMaker) Provision(ctx caddy2.Context) error {
m.Storage = nil // allow GC to deallocate - TODO: Does this help? m.Storage = nil // allow GC to deallocate - TODO: Does this help?
} }
m.setDefaults() m.SetDefaults()
return nil return nil
} }
// setDefaults sets necessary values that are // SetDefaults sets necessary values that are
// currently empty to their default values. // currently empty to their default values.
func (m *acmeManagerMaker) setDefaults() { func (m *ACMEManagerMaker) SetDefaults() {
if m.CA == "" { if m.CA == "" {
m.CA = certmagic.LetsEncryptStagingCA // certmagic.Default.CA // TODO: When not testing, switch to production CA m.CA = certmagic.LetsEncryptStagingCA // certmagic.Default.CA // TODO: When not testing, switch to production CA
} }
@ -93,7 +102,7 @@ func (m *acmeManagerMaker) setDefaults() {
// makeCertMagicConfig converts m into a certmagic.Config, because // makeCertMagicConfig converts m into a certmagic.Config, because
// this is a special case where the default manager is the certmagic // this is a special case where the default manager is the certmagic
// Config and not a separate manager. // Config and not a separate manager.
func (m *acmeManagerMaker) makeCertMagicConfig(ctx caddy2.Context) certmagic.Config { func (m *ACMEManagerMaker) makeCertMagicConfig(ctx caddy2.Context) certmagic.Config {
storage := m.storage storage := m.storage
if storage == nil { if storage == nil {
storage = ctx.Storage() storage = ctx.Storage()
@ -115,7 +124,7 @@ func (m *acmeManagerMaker) makeCertMagicConfig(ctx caddy2.Context) certmagic.Con
RenewDurationBefore: time.Duration(m.RenewAhead), RenewDurationBefore: time.Duration(m.RenewAhead),
AltHTTPPort: m.Challenges.HTTP.AlternatePort, AltHTTPPort: m.Challenges.HTTP.AlternatePort,
AltTLSALPNPort: m.Challenges.TLSALPN.AlternatePort, AltTLSALPNPort: m.Challenges.TLSALPN.AlternatePort,
DNSProvider: m.Challenges.dns, DNSProvider: m.Challenges.DNS,
KeyType: supportedCertKeyTypes[m.KeyType], KeyType: supportedCertKeyTypes[m.KeyType],
CertObtainTimeout: time.Duration(m.ACMETimeout), CertObtainTimeout: time.Duration(m.ACMETimeout),
OnDemand: ond, OnDemand: ond,
@ -133,3 +142,6 @@ var supportedCertKeyTypes = map[string]certcrypto.KeyType{
"P256": certcrypto.EC256, "P256": certcrypto.EC256,
"P384": certcrypto.EC384, "P384": certcrypto.EC384,
} }
// Interface guard
var _ managerMaker = (*ACMEManagerMaker)(nil)

View File

@ -45,12 +45,12 @@ func (t *TLS) Provision(ctx caddy2.Context) error {
// automation/management policies // automation/management policies
for i, ap := range t.Automation.Policies { for i, ap := range t.Automation.Policies {
val, err := ctx.LoadModuleInline("module", "tls.management", ap.Management) val, err := ctx.LoadModuleInline("module", "tls.management", ap.ManagementRaw)
if err != nil { if err != nil {
return fmt.Errorf("loading TLS automation management module: %s", err) return fmt.Errorf("loading TLS automation management module: %s", err)
} }
t.Automation.Policies[i].management = val.(ManagerMaker) t.Automation.Policies[i].Management = val.(managerMaker)
t.Automation.Policies[i].Management = nil // allow GC to deallocate - TODO: Does this help? t.Automation.Policies[i].ManagementRaw = nil // allow GC to deallocate - TODO: Does this help?
} }
// certificate loaders // certificate loaders
@ -164,9 +164,9 @@ func (t *TLS) getAutomationPolicyForName(name string) AutomationPolicy {
} }
// default automation policy // default automation policy
mgmt := new(acmeManagerMaker) mgmt := new(ACMEManagerMaker)
mgmt.setDefaults() mgmt.SetDefaults()
return AutomationPolicy{management: mgmt} return AutomationPolicy{Management: mgmt}
} }
// CertificateLoader is a type that can load certificates. // CertificateLoader is a type that can load certificates.
@ -184,21 +184,21 @@ type AutomationConfig struct {
// management of managed TLS certificates. // management of managed TLS certificates.
type AutomationPolicy struct { type AutomationPolicy struct {
Hosts []string `json:"hosts,omitempty"` Hosts []string `json:"hosts,omitempty"`
Management json.RawMessage `json:"management"` ManagementRaw json.RawMessage `json:"management"`
management ManagerMaker Management managerMaker `json:"-"`
} }
func (ap AutomationPolicy) makeCertMagicConfig(ctx caddy2.Context) certmagic.Config { func (ap AutomationPolicy) makeCertMagicConfig(ctx caddy2.Context) certmagic.Config {
// default manager (ACME) is a special case because of how CertMagic is designed // 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 // 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... // its config fields out of the certmagic.Config struct, or something...
if acmeMgmt, ok := ap.management.(*acmeManagerMaker); ok { if acmeMgmt, ok := ap.Management.(*ACMEManagerMaker); ok {
return acmeMgmt.makeCertMagicConfig(ctx) return acmeMgmt.makeCertMagicConfig(ctx)
} }
return certmagic.Config{ return certmagic.Config{
NewManager: ap.management.newManager, NewManager: ap.Management.newManager,
} }
} }
@ -206,9 +206,9 @@ func (ap AutomationPolicy) makeCertMagicConfig(ctx caddy2.Context) certmagic.Con
type ChallengesConfig struct { type ChallengesConfig struct {
HTTP HTTPChallengeConfig `json:"http"` HTTP HTTPChallengeConfig `json:"http"`
TLSALPN TLSALPNChallengeConfig `json:"tls-alpn"` TLSALPN TLSALPNChallengeConfig `json:"tls-alpn"`
DNS json.RawMessage `json:"dns,omitempty"` DNSRaw json.RawMessage `json:"dns,omitempty"`
dns challenge.Provider DNS challenge.Provider `json:"-"`
} }
// HTTPChallengeConfig configures the ACME HTTP challenge. // HTTPChallengeConfig configures the ACME HTTP challenge.
@ -232,8 +232,8 @@ type OnDemandConfig struct {
AskStarlark string `json:"ask_starlark,omitempty"` AskStarlark string `json:"ask_starlark,omitempty"`
} }
// ManagerMaker makes a certificate manager. // managerMaker makes a certificate manager.
type ManagerMaker interface { type managerMaker interface {
newManager(interactive bool) (certmagic.Manager, error) newManager(interactive bool) (certmagic.Manager, error)
} }