From 3c90e370a49cafe7f58c7195187822ddc86ced4a Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Tue, 10 Dec 2019 13:36:46 -0700 Subject: v2: Module documentation; refactor LoadModule(); new caddy struct tags (#2924) This commit goes a long way toward making automated documentation of Caddy config and Caddy modules possible. It's a broad, sweeping change, but mostly internal. It allows us to automatically generate docs for all Caddy modules (including future third-party ones) and make them viewable on a web page; it also doubles as godoc comments. As such, this commit makes significant progress in migrating the docs from our temporary wiki page toward our new website which is still under construction. With this change, all host modules will use ctx.LoadModule() and pass in both the struct pointer and the field name as a string. This allows the reflect package to read the struct tag from that field so that it can get the necessary information like the module namespace and the inline key. This has the nice side-effect of unifying the code and documentation. It also simplifies module loading, and handles several variations on field types for raw module fields (i.e. variations on json.RawMessage, such as arrays and maps). I also renamed ModuleInfo.Name -> ModuleInfo.ID, to make it clear that the ID is the "full name" which includes both the module namespace and the name. This clarity is helpful when describing module hierarchy. As of this change, Caddy modules are no longer an experimental design. I think the architecture is good enough to go forward. --- modules/caddytls/acmemanager.go | 68 +++++-- modules/caddytls/certselection.go | 4 +- modules/caddytls/connpolicy.go | 51 ++++-- .../caddytls/distributedstek/distributedstek.go | 17 +- modules/caddytls/fileloader.go | 21 ++- modules/caddytls/folderloader.go | 4 +- modules/caddytls/matchers.go | 4 +- modules/caddytls/pemloader.go | 17 +- modules/caddytls/sessiontickets.go | 24 ++- modules/caddytls/standardstek/stek.go | 4 +- modules/caddytls/tls.go | 201 ++++++++++++++++----- 11 files changed, 302 insertions(+), 113 deletions(-) (limited to 'modules/caddytls') diff --git a/modules/caddytls/acmemanager.go b/modules/caddytls/acmemanager.go index 9f31215..31c954f 100644 --- a/modules/caddytls/acmemanager.go +++ b/modules/caddytls/acmemanager.go @@ -40,16 +40,50 @@ func init() { // after you have configured this struct // to your liking. type ACMEManagerMaker struct { - CA string `json:"ca,omitempty"` - Email string `json:"email,omitempty"` - RenewAhead caddy.Duration `json:"renew_ahead,omitempty"` - KeyType string `json:"key_type,omitempty"` - ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"` - MustStaple bool `json:"must_staple,omitempty"` - Challenges *ChallengesConfig `json:"challenges,omitempty"` - OnDemand bool `json:"on_demand,omitempty"` - Storage json.RawMessage `json:"storage,omitempty"` - TrustedRootsPEMFiles []string `json:"trusted_roots_pem_files,omitempty"` + // 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"` + + // 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 @@ -58,8 +92,8 @@ type ACMEManagerMaker struct { // CaddyModule returns the Caddy module information. func (ACMEManagerMaker) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.management.acme", - New: func() caddy.Module { return new(ACMEManagerMaker) }, + ID: "tls.management.acme", + New: func() caddy.Module { return new(ACMEManagerMaker) }, } } @@ -73,26 +107,24 @@ func (m ACMEManagerMaker) NewManager(interactive bool) (certmagic.Manager, error func (m *ACMEManagerMaker) Provision(ctx caddy.Context) error { // DNS providers if m.Challenges != nil && m.Challenges.DNSRaw != nil { - val, err := ctx.LoadModuleInline("provider", "tls.dns", m.Challenges.DNSRaw) + val, err := ctx.LoadModule(m.Challenges, "DNSRaw") if err != nil { - return fmt.Errorf("loading DNS provider module: %s", err) + return fmt.Errorf("loading DNS provider module: %v", err) } m.Challenges.DNS = val.(challenge.Provider) - m.Challenges.DNSRaw = nil // allow GC to deallocate } // policy-specific storage implementation if m.Storage != nil { - val, err := ctx.LoadModuleInline("module", "caddy.storage", m.Storage) + val, err := ctx.LoadModule(m, "Storage") if err != nil { - return fmt.Errorf("loading TLS storage module: %s", err) + 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 - m.Storage = nil // allow GC to deallocate } // add any custom CAs to trust store diff --git a/modules/caddytls/certselection.go b/modules/caddytls/certselection.go index b56185a..eb01605 100644 --- a/modules/caddytls/certselection.go +++ b/modules/caddytls/certselection.go @@ -28,8 +28,8 @@ type Policy struct { // CaddyModule returns the Caddy module information. func (Policy) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.certificate_selection.custom", - New: func() caddy.Module { return new(Policy) }, + ID: "tls.certificate_selection.custom", + New: func() caddy.Module { return new(Policy) }, } } diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go index c82337d..6ce6b9e 100644 --- a/modules/caddytls/connpolicy.go +++ b/modules/caddytls/connpolicy.go @@ -39,23 +39,21 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) (*tls.Config, error) { // set up each of the connection policies for i, pol := range cp { // matchers - for modName, rawMsg := range pol.Matchers { - val, err := ctx.LoadModule("tls.handshake_match."+modName, rawMsg) - if err != nil { - return nil, fmt.Errorf("loading handshake matcher module '%s': %s", modName, err) - } - cp[i].matchers = append(cp[i].matchers, val.(ConnectionMatcher)) + mods, err := ctx.LoadModule(pol, "MatchersRaw") + if err != nil { + return nil, fmt.Errorf("loading handshake matchers: %v", err) + } + for _, modIface := range mods.(map[string]interface{}) { + cp[i].matchers = append(cp[i].matchers, modIface.(ConnectionMatcher)) } - cp[i].Matchers = nil // allow GC to deallocate // certificate selector if pol.CertSelection != nil { - val, err := ctx.LoadModuleInline("policy", "tls.certificate_selection", pol.CertSelection) + val, err := ctx.LoadModule(pol, "CertSelection") if err != nil { return nil, fmt.Errorf("loading certificate selection module: %s", err) } cp[i].certSelector = val.(certmagic.CertificateSelector) - cp[i].CertSelection = nil // allow GC to deallocate } } @@ -109,14 +107,33 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) (*tls.Config, error) { // ConnectionPolicy specifies the logic for handling a TLS handshake. type ConnectionPolicy struct { - Matchers map[string]json.RawMessage `json:"match,omitempty"` - CertSelection json.RawMessage `json:"certificate_selection,omitempty"` - - CipherSuites []string `json:"cipher_suites,omitempty"` - Curves []string `json:"curves,omitempty"` - ALPN []string `json:"alpn,omitempty"` - ProtocolMin string `json:"protocol_min,omitempty"` - ProtocolMax string `json:"protocol_max,omitempty"` + // How to match this policy with a TLS ClientHello. If + // this policy is the first to match, it will be used. + MatchersRaw caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=tls.handshake_match"` + + // How to choose a certificate if more than one matched + // the given ServerName (SNI) value. + CertSelection json.RawMessage `json:"certificate_selection,omitempty" caddy:"namespace=tls.certificate_selection inline_key=policy"` + + // The list of cipher suites to support. Caddy's + // defaults are modern and secure. + CipherSuites []string `json:"cipher_suites,omitempty"` + + // The list of elliptic curves to support. Caddy's + // defaults are modern and secure. + Curves []string `json:"curves,omitempty"` + + // Protocols to use for Application-Layer Protocol + // Negotiation (ALPN) during the handshake. + ALPN []string `json:"alpn,omitempty"` + + // Minimum TLS protocol version to allow. Default: `tls1.2` + ProtocolMin string `json:"protocol_min,omitempty"` + + // Maximum TLS protocol version to allow. Default: `tls1.3` + ProtocolMax string `json:"protocol_max,omitempty"` + + // Enables and configures TLS client authentication. ClientAuthentication *ClientAuthentication `json:"client_authentication,omitempty"` matchers []ConnectionMatcher diff --git a/modules/caddytls/distributedstek/distributedstek.go b/modules/caddytls/distributedstek/distributedstek.go index a0c4cd2..cef3733 100644 --- a/modules/caddytls/distributedstek/distributedstek.go +++ b/modules/caddytls/distributedstek/distributedstek.go @@ -39,9 +39,15 @@ func init() { caddy.RegisterModule(Provider{}) } -// Provider implements a distributed STEK provider. +// Provider implements a distributed STEK provider. This +// module will obtain STEKs from a storage module instead +// of generating STEKs internally. This allows STEKs to be +// coordinated, improving TLS session resumption in a cluster. type Provider struct { - Storage json.RawMessage `json:"storage,omitempty"` + // The storage module wherein to store and obtain session + // ticket keys. If unset, Caddy's default/global-configured + // storage module will be used. + Storage json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"` storage certmagic.Storage stekConfig *caddytls.SessionTicketService @@ -51,8 +57,8 @@ type Provider struct { // CaddyModule returns the Caddy module information. func (Provider) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.stek.distributed", - New: func() caddy.Module { return new(Provider) }, + ID: "tls.stek.distributed", + New: func() caddy.Module { return new(Provider) }, } } @@ -60,7 +66,7 @@ func (Provider) CaddyModule() caddy.ModuleInfo { func (s *Provider) Provision(ctx caddy.Context) error { // unpack the storage module to use, if different from the default if s.Storage != nil { - val, err := ctx.LoadModuleInline("module", "caddy.storage", s.Storage) + val, err := ctx.LoadModule(s, "Storage") if err != nil { return fmt.Errorf("loading TLS storage module: %s", err) } @@ -69,7 +75,6 @@ func (s *Provider) Provision(ctx caddy.Context) error { return fmt.Errorf("creating TLS storage configuration: %v", err) } s.storage = cmStorage - s.Storage = nil // allow GC to deallocate } // otherwise, use default storage diff --git a/modules/caddytls/fileloader.go b/modules/caddytls/fileloader.go index b2cc132..6d6ff99 100644 --- a/modules/caddytls/fileloader.go +++ b/modules/caddytls/fileloader.go @@ -32,18 +32,27 @@ type FileLoader []CertKeyFilePair // CaddyModule returns the Caddy module information. func (FileLoader) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.certificates.load_files", - New: func() caddy.Module { return new(FileLoader) }, + ID: "tls.certificates.load_files", + New: func() caddy.Module { return new(FileLoader) }, } } // CertKeyFilePair pairs certificate and key file names along with their // encoding format so that they can be loaded from disk. type CertKeyFilePair struct { - Certificate string `json:"certificate"` - Key string `json:"key"` - Format string `json:"format,omitempty"` // "pem" is default - Tags []string `json:"tags,omitempty"` + // Path to the certificate (public key) file. + Certificate string `json:"certificate"` + + // Path to the private key file. + Key string `json:"key"` + + // The format of the cert and key. Can be "pem". Default: "pem" + Format string `json:"format,omitempty"` + + // Arbitrary values to associate with this certificate. + // Can be useful when you want to select a particular + // certificate when there may be multiple valid candidates. + Tags []string `json:"tags,omitempty"` } // LoadCertificates returns the certificates to be loaded by fl. diff --git a/modules/caddytls/folderloader.go b/modules/caddytls/folderloader.go index da1dff0..f1a742d 100644 --- a/modules/caddytls/folderloader.go +++ b/modules/caddytls/folderloader.go @@ -39,8 +39,8 @@ type FolderLoader []string // CaddyModule returns the Caddy module information. func (FolderLoader) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.certificates.load_folders", - New: func() caddy.Module { return new(FolderLoader) }, + ID: "tls.certificates.load_folders", + New: func() caddy.Module { return new(FolderLoader) }, } } diff --git a/modules/caddytls/matchers.go b/modules/caddytls/matchers.go index 47fb296..9e2dfc5 100644 --- a/modules/caddytls/matchers.go +++ b/modules/caddytls/matchers.go @@ -30,8 +30,8 @@ type MatchServerName []string // CaddyModule returns the Caddy module information. func (MatchServerName) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.handshake_match.sni", - New: func() caddy.Module { return new(MatchServerName) }, + ID: "tls.handshake_match.sni", + New: func() caddy.Module { return new(MatchServerName) }, } } diff --git a/modules/caddytls/pemloader.go b/modules/caddytls/pemloader.go index 30a491c..46d06a8 100644 --- a/modules/caddytls/pemloader.go +++ b/modules/caddytls/pemloader.go @@ -33,16 +33,23 @@ type PEMLoader []CertKeyPEMPair // CaddyModule returns the Caddy module information. func (PEMLoader) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.certificates.load_pem", - New: func() caddy.Module { return PEMLoader{} }, + ID: "tls.certificates.load_pem", + New: func() caddy.Module { return PEMLoader{} }, } } // CertKeyPEMPair pairs certificate and key PEM blocks. type CertKeyPEMPair struct { - CertificatePEM string `json:"certificate"` - KeyPEM string `json:"key"` - Tags []string `json:"tags,omitempty"` + // The certificate (public key) in PEM format. + CertificatePEM string `json:"certificate"` + + // The private key in PEM format. + KeyPEM string `json:"key"` + + // Arbitrary values to associate with this certificate. + // Can be useful when you want to select a particular + // certificate when there may be multiple valid candidates. + Tags []string `json:"tags,omitempty"` } // LoadCertificates returns the certificates contained in pl. diff --git a/modules/caddytls/sessiontickets.go b/modules/caddytls/sessiontickets.go index 6ca921d..258c135 100644 --- a/modules/caddytls/sessiontickets.go +++ b/modules/caddytls/sessiontickets.go @@ -28,11 +28,22 @@ import ( // SessionTicketService configures and manages TLS session tickets. type SessionTicketService struct { - KeySource json.RawMessage `json:"key_source,omitempty"` - RotationInterval caddy.Duration `json:"rotation_interval,omitempty"` - MaxKeys int `json:"max_keys,omitempty"` - DisableRotation bool `json:"disable_rotation,omitempty"` - Disabled bool `json:"disabled,omitempty"` + // KeySource is the method by which Caddy produces or obtains + // TLS session ticket keys (STEKs). By default, Caddy generates + // them internally using a secure pseudorandom source. + KeySource json.RawMessage `json:"key_source,omitempty" caddy:"namespace=tls.stek inline_key=provider"` + + // How often Caddy rotates STEKs. Default: 12h. + RotationInterval caddy.Duration `json:"rotation_interval,omitempty"` + + // The maximum number of keys to keep in rotation. Default: 4. + MaxKeys int `json:"max_keys,omitempty"` + + // Disables STEK rotation. + DisableRotation bool `json:"disable_rotation,omitempty"` + + // Disables TLS session resumption by tickets. + Disabled bool `json:"disabled,omitempty"` keySource STEKProvider configs map[*tls.Config]struct{} @@ -57,12 +68,11 @@ func (s *SessionTicketService) provision(ctx caddy.Context) error { } // load the STEK module, which will provide keys - val, err := ctx.LoadModuleInline("provider", "tls.stek", s.KeySource) + val, err := ctx.LoadModule(s, "KeySource") if err != nil { return fmt.Errorf("loading TLS session ticket ephemeral keys provider module: %s", err) } s.keySource = val.(STEKProvider) - s.KeySource = nil // allow GC to deallocate // if session tickets or just rotation are // disabled, no need to start service diff --git a/modules/caddytls/standardstek/stek.go b/modules/caddytls/standardstek/stek.go index 6d10c76..eb609ca 100644 --- a/modules/caddytls/standardstek/stek.go +++ b/modules/caddytls/standardstek/stek.go @@ -35,8 +35,8 @@ type standardSTEKProvider struct { // CaddyModule returns the Caddy module information. func (standardSTEKProvider) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.stek.standard", - New: func() caddy.Module { return new(standardSTEKProvider) }, + ID: "tls.stek.standard", + New: func() caddy.Module { return new(standardSTEKProvider) }, } } diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go index 5dfe063..1b155b0 100644 --- a/modules/caddytls/tls.go +++ b/modules/caddytls/tls.go @@ -30,15 +30,30 @@ import ( func init() { caddy.RegisterModule(TLS{}) + caddy.RegisterModule(AutomateLoader{}) } -// TLS represents a process-wide TLS configuration. +// TLS provides TLS facilities including certificate +// loading and management, client auth, and more. type TLS struct { - Certificates map[string]json.RawMessage `json:"certificates,omitempty"` - Automation *AutomationConfig `json:"automation,omitempty"` - SessionTickets *SessionTicketService `json:"session_tickets,omitempty"` + // Caches certificates in memory for quick use during + // TLS handshakes. Each key is the name of a certificate + // loader module. All loaded certificates get pooled + // into the same cache and may be used to complete TLS + // handshakes for the relevant server names (SNI). + // Certificates loaded manually (anything other than + // "automate") are not automatically managed and will + // have to be refreshed manually before they expire. + CertificatesRaw caddy.ModuleMap `json:"certificates,omitempty" caddy:"namespace=tls.certificates"` + + // Configures the automation of certificate management. + Automation *AutomationConfig `json:"automation,omitempty"` + + // Configures session ticket ephemeral keys (STEKs). + SessionTickets *SessionTicketService `json:"session_tickets,omitempty"` certificateLoaders []CertificateLoader + automateNames []string certCache *certmagic.Cache ctx caddy.Context storageCleanTicker *time.Ticker @@ -49,8 +64,8 @@ type TLS struct { // CaddyModule returns the Caddy module information. func (TLS) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls", - New: func() caddy.Module { return new(TLS) }, + ID: "tls", + New: func() caddy.Module { return new(TLS) }, } } @@ -74,25 +89,32 @@ func (t *TLS) Provision(ctx caddy.Context) error { // automation/management policies if t.Automation != nil { for i, ap := range t.Automation.Policies { - val, err := ctx.LoadModuleInline("module", "tls.management", ap.ManagementRaw) + 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) - t.Automation.Policies[i].ManagementRaw = nil // allow GC to deallocate } } // certificate loaders - for modName, rawMsg := range t.Certificates { - if modName == automateKey { - continue // special case; these will be loaded in later - } - val, err := ctx.LoadModule("tls.certificates."+modName, rawMsg) - if err != nil { - return fmt.Errorf("loading certificate module '%s': %s", modName, err) + val, err := ctx.LoadModule(t, "CertificatesRaw") + if err != nil { + return fmt.Errorf("loading TLS automation management module: %s", err) + } + for modName, modIface := range val.(map[string]interface{}) { + if modName == "automate" { + // special case; these will be loaded in later + // using our automation facilities, which we + // want to avoid during provisioning + var ok bool + t.automateNames, ok = modIface.([]string) + if !ok { + return fmt.Errorf("loading certificates with 'automate' requires []string, got: %#v", modIface) + } + continue } - t.certificateLoaders = append(t.certificateLoaders, val.(CertificateLoader)) + t.certificateLoaders = append(t.certificateLoaders, modIface.(CertificateLoader)) } // session ticket ephemeral keys (STEK) service and provider @@ -115,7 +137,8 @@ func (t *TLS) Provision(ctx caddy.Context) error { // load manual/static (unmanaged) certificates - we do this in // provision so that other apps (such as http) can know which - // certificates have been manually loaded + // certificates have been manually loaded, and also so that + // commands like validate can be a better test magic := certmagic.New(t.certCache, certmagic.Config{ Storage: ctx.Storage(), }) @@ -137,19 +160,12 @@ func (t *TLS) Provision(ctx caddy.Context) error { // Start activates the TLS module. func (t *TLS) Start() error { - // load automated (managed) certificates - if automatedRawMsg, ok := t.Certificates[automateKey]; ok { - var names []string - err := json.Unmarshal(automatedRawMsg, &names) - if err != nil { - return fmt.Errorf("automate: decoding names: %v", err) - } - err = t.Manage(names) - if err != nil { - return fmt.Errorf("automate: managing %v: %v", names, err) - } + // now that we are running, and all manual certificates have + // been loaded, time to load the automated/managed certificates + err := t.Manage(t.automateNames) + if err != nil { + return fmt.Errorf("automate: managing %v: %v", t.automateNames, err) } - t.Certificates = nil // allow GC to deallocate t.keepStorageClean() @@ -311,18 +327,48 @@ type Certificate struct { // AutomationConfig designates configuration for the // construction and use of ACME clients. type AutomationConfig struct { - Policies []AutomationPolicy `json:"policies,omitempty"` - OnDemand *OnDemandConfig `json:"on_demand,omitempty"` - OCSPCheckInterval caddy.Duration `json:"ocsp_interval,omitempty"` - RenewCheckInterval caddy.Duration `json:"renew_interval,omitempty"` + // The list of automation policies. The first matching + // policy will be applied for a given certificate/name. + Policies []AutomationPolicy `json:"policies,omitempty"` + + // On-Demand TLS defers certificate operations to the + // moment they are needed, e.g. during a TLS handshake. + // Useful when you don't know all the hostnames up front. + // Caddy was the first web server to deploy this technology. + OnDemand *OnDemandConfig `json:"on_demand,omitempty"` + + // Caddy staples OCSP (and caches the response) for all + // qualifying certificates by default. This setting + // changes how often it scans responses for freshness, + // and updates them if they are getting stale. + 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. + RenewCheckInterval caddy.Duration `json:"renew_interval,omitempty"` } // AutomationPolicy designates the policy for automating the -// management of managed TLS certificates. +// management (obtaining, renewal, and revocation) of managed +// TLS certificates. type AutomationPolicy struct { - Hosts []string `json:"hosts,omitempty"` - ManagementRaw json.RawMessage `json:"management,omitempty"` - ManageSync bool `json:"manage_sync,omitempty"` + // 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"` + + // If true, certificate management will be conducted + // in the foreground; this will block config reloads + // and return errors if there were problems with + // obtaining or renewing certificates. This is often + // not desirable, especially when serving sites out + // of your control. Default: false + ManageSync bool `json:"manage_sync,omitempty"` Management ManagerMaker `json:"-"` } @@ -345,36 +391,84 @@ func (ap AutomationPolicy) makeCertMagicConfig(ctx caddy.Context) certmagic.Conf // ChallengesConfig configures the ACME challenges. type ChallengesConfig struct { - HTTP *HTTPChallengeConfig `json:"http,omitempty"` + // HTTP configures the ACME HTTP challenge. This + // challenge is enabled and used automatically + // and by default. + HTTP *HTTPChallengeConfig `json:"http,omitempty"` + + // TLSALPN configures the ACME TLS-ALPN challenge. + // This challenge is enabled and used automatically + // and by default. TLSALPN *TLSALPNChallengeConfig `json:"tls-alpn,omitempty"` - DNSRaw json.RawMessage `json:"dns,omitempty"` + + // Configures the ACME DNS challenge. Because this + // challenge typically requires credentials for + // interfacing with a DNS provider, this challenge is + // not enabled by default. This is the only challenge + // type which does not require a direct connection + // to Caddy from an external server. + DNSRaw json.RawMessage `json:"dns,omitempty" caddy:"namespace=tls.dns inline_key=provider"` DNS challenge.Provider `json:"-"` } // HTTPChallengeConfig configures the ACME HTTP challenge. type HTTPChallengeConfig struct { - Disabled bool `json:"disabled,omitempty"` - AlternatePort int `json:"alternate_port,omitempty"` + // If true, the HTTP challenge will be disabled. + Disabled bool `json:"disabled,omitempty"` + + // An alternate port on which to service this + // challenge. Note that the HTTP challenge port is + // hard-coded into the spec and cannot be changed, + // so you would have to forward packets from the + // standard HTTP challenge port to this one. + AlternatePort int `json:"alternate_port,omitempty"` } // TLSALPNChallengeConfig configures the ACME TLS-ALPN challenge. type TLSALPNChallengeConfig struct { - Disabled bool `json:"disabled,omitempty"` - AlternatePort int `json:"alternate_port,omitempty"` + // If true, the TLS-ALPN challenge will be disabled. + Disabled bool `json:"disabled,omitempty"` + + // An alternate port on which to service this + // challenge. Note that the TLS-ALPN challenge port + // is hard-coded into the spec and cannot be changed, + // so you would have to forward packets from the + // standard TLS-ALPN challenge port to this one. + AlternatePort int `json:"alternate_port,omitempty"` } // OnDemandConfig configures on-demand TLS, for obtaining -// needed certificates at handshake-time. +// needed certificates at handshake-time. Because this +// feature can easily be abused, you should set up rate +// limits and/or an internal endpoint that Caddy can +// "ask" if it should be allowed to manage certificates +// for a given hostname. type OnDemandConfig struct { + // An optional rate limit to throttle the + // issuance of certificates from handshakes. RateLimit *RateLimit `json:"rate_limit,omitempty"` - Ask string `json:"ask,omitempty"` + + // If Caddy needs to obtain or renew a certificate + // during a TLS handshake, it will perform a quick + // HTTP request to this URL to check if it should be + // allowed to try to get a certificate for the name + // in the "domain" query string parameter, like so: + // `?domain=example.com`. The endpoint must return a + // 200 OK status if a certificate is allowed; + // anything else will cause it to be denied. + // Redirects are not followed. + Ask string `json:"ask,omitempty"` } // RateLimit specifies an interval with optional burst size. type RateLimit struct { + // A duration value. A certificate may be obtained 'burst' + // times during this interval. Interval caddy.Duration `json:"interval,omitempty"` - Burst int `json:"burst,omitempty"` + + // How many times during an interval a certificate can be obtained. + Burst int `json:"burst,omitempty"` } // ManagerMaker makes a certificate manager. @@ -382,6 +476,21 @@ 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 +// list of hostnames, rather than loading certificates +// manually. +type AutomateLoader []string + +// CaddyModule returns the Caddy module information. +func (AutomateLoader) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.certificates.automate", + New: func() caddy.Module { return new(AutomateLoader) }, + } +} + // These perpetual values are used for on-demand TLS. var ( onDemandRateLimiter = certmagic.NewRateLimiter(0, 0) -- cgit v1.2.3