@ -30,15 +30,30 @@ import (
func init ( ) {
func init ( ) {
caddy . RegisterModule ( TLS { } )
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 {
type TLS struct {
Certificates map [ string ] json . RawMessage ` json:"certificates,omitempty" `
// Caches certificates in memory for quick use during
Automation * AutomationConfig ` json:"automation,omitempty" `
// TLS handshakes. Each key is the name of a certificate
SessionTickets * SessionTicketService ` json:"session_tickets,omitempty" `
// 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
certificateLoaders [ ] CertificateLoader
automateNames [ ] string
certCache * certmagic . Cache
certCache * certmagic . Cache
ctx caddy . Context
ctx caddy . Context
storageCleanTicker * time . Ticker
storageCleanTicker * time . Ticker
@ -49,8 +64,8 @@ type TLS struct {
// CaddyModule returns the Caddy module information.
// CaddyModule returns the Caddy module information.
func ( TLS ) CaddyModule ( ) caddy . ModuleInfo {
func ( TLS ) CaddyModule ( ) caddy . ModuleInfo {
return caddy . ModuleInfo {
return caddy . ModuleInfo {
Name : "tls" ,
ID : "tls" ,
New : func ( ) caddy . Module { return new ( TLS ) } ,
New : func ( ) caddy . Module { return new ( TLS ) } ,
}
}
}
}
@ -74,25 +89,32 @@ func (t *TLS) Provision(ctx caddy.Context) error {
// automation/management policies
// automation/management policies
if t . Automation != nil {
if t . Automation != nil {
for i , ap := range t . Automation . Policies {
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 {
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 ] . ManagementRaw = nil // allow GC to deallocate
}
}
}
}
// certificate loaders
// certificate loaders
for modName , rawMsg := range t . Certificates {
val , err := ctx . LoadModule ( t , "CertificatesRaw" )
if modName == automateKey {
if err != nil {
continue // special case; these will be loaded in later
return fmt . Errorf ( "loading TLS automation management module: %s" , err )
}
}
val , err := ctx . LoadModule ( "tls.certificates." + modName , rawMsg )
for modName , modIface := range val . ( map [ string ] interface { } ) {
if err != nil {
if modName == "automate" {
return fmt . Errorf ( "loading certificate module '%s': %s" , modName , err )
// 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
// 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
// load manual/static (unmanaged) certificates - we do this in
// provision so that other apps (such as http) can know which
// 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 {
magic := certmagic . New ( t . certCache , certmagic . Config {
Storage : ctx . Storage ( ) ,
Storage : ctx . Storage ( ) ,
} )
} )
@ -137,19 +160,12 @@ func (t *TLS) Provision(ctx caddy.Context) error {
// Start activates the TLS module.
// Start activates the TLS module.
func ( t * TLS ) Start ( ) error {
func ( t * TLS ) Start ( ) error {
// load automated (managed) certificates
// now that we are running, and all manual certificates have
if automatedRawMsg , ok := t . Certificates [ automateKey ] ; ok {
// been loaded, time to load the automated/managed certificates
var names [ ] string
err := t . Manage ( t . automateNames )
err := json . Unmarshal ( automatedRawMsg , & names )
if err != nil {
if err != nil {
return fmt . Errorf ( "automate: managing %v: %v" , t . automateNames , err )
return fmt . Errorf ( "automate: decoding names: %v" , err )
}
err = t . Manage ( names )
if err != nil {
return fmt . Errorf ( "automate: managing %v: %v" , names , err )
}
}
}
t . Certificates = nil // allow GC to deallocate
t . keepStorageClean ( )
t . keepStorageClean ( )
@ -311,18 +327,48 @@ type Certificate struct {
// AutomationConfig designates configuration for the
// AutomationConfig designates configuration for the
// construction and use of ACME clients.
// construction and use of ACME clients.
type AutomationConfig struct {
type AutomationConfig struct {
Policies [ ] AutomationPolicy ` json:"policies,omitempty" `
// The list of automation policies. The first matching
OnDemand * OnDemandConfig ` json:"on_demand,omitempty" `
// policy will be applied for a given certificate/name.
OCSPCheckInterval caddy . Duration ` json:"ocsp_interval,omitempty" `
Policies [ ] AutomationPolicy ` json:"policies,omitempty" `
RenewCheckInterval caddy . Duration ` json:"renew_interval,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
// AutomationPolicy designates the policy for automating the
// management of managed TLS certificates.
// management (obtaining, renewal, and revocation) of managed
// TLS certificates.
type AutomationPolicy struct {
type AutomationPolicy struct {
Hosts [ ] string ` json:"hosts,omitempty" `
// Which hostnames this policy applies to.
ManagementRaw json . RawMessage ` json:"management,omitempty" `
Hosts [ ] string ` json:"hosts,omitempty" `
ManageSync bool ` json:"manage_sync,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:"-" `
Management ManagerMaker ` json:"-" `
}
}
@ -345,36 +391,84 @@ func (ap AutomationPolicy) makeCertMagicConfig(ctx caddy.Context) certmagic.Conf
// ChallengesConfig configures the ACME challenges.
// ChallengesConfig configures the ACME challenges.
type ChallengesConfig struct {
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" `
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:"-" `
DNS challenge . Provider ` json:"-" `
}
}
// HTTPChallengeConfig configures the ACME HTTP challenge.
// HTTPChallengeConfig configures the ACME HTTP challenge.
type HTTPChallengeConfig struct {
type HTTPChallengeConfig struct {
Disabled bool ` json:"disabled,omitempty" `
// If true, the HTTP challenge will be disabled.
AlternatePort int ` json:"alternate_port,omitempty" `
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.
// TLSALPNChallengeConfig configures the ACME TLS-ALPN challenge.
type TLSALPNChallengeConfig struct {
type TLSALPNChallengeConfig struct {
Disabled bool ` json:"disabled,omitempty" `
// If true, the TLS-ALPN challenge will be disabled.
AlternatePort int ` json:"alternate_port,omitempty" `
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
// 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 {
type OnDemandConfig struct {
// An optional rate limit to throttle the
// issuance of certificates from handshakes.
RateLimit * RateLimit ` json:"rate_limit,omitempty" `
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.
// RateLimit specifies an interval with optional burst size.
type RateLimit struct {
type RateLimit struct {
// A duration value. A certificate may be obtained 'burst'
// times during this interval.
Interval caddy . Duration ` json:"interval,omitempty" `
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.
// ManagerMaker makes a certificate manager.
@ -382,6 +476,21 @@ type ManagerMaker interface {
NewManager ( interactive bool ) ( certmagic . Manager , error )
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.
// These perpetual values are used for on-demand TLS.
var (
var (
onDemandRateLimiter = certmagic . NewRateLimiter ( 0 , 0 )
onDemandRateLimiter = certmagic . NewRateLimiter ( 0 , 0 )