diff --git a/caddytls/config.go b/caddytls/config.go index 4654c064..d3468e34 100644 --- a/caddytls/config.go +++ b/caddytls/config.go @@ -148,6 +148,11 @@ type OnDemandState struct { // Set from max_certs in tls config, it specifies the // maximum number of certificates that can be issued. MaxObtain int32 + + // The url to call to check if an on-demand tls certificate should + // be issued. If a request to the URL fails or returns a non 2xx + // status on-demand issuances must fail. + AskURL *url.URL } // ObtainCert obtains a certificate for name using c, as long diff --git a/caddytls/handshake.go b/caddytls/handshake.go index 31fc67e0..c50e8ab6 100644 --- a/caddytls/handshake.go +++ b/caddytls/handshake.go @@ -19,6 +19,8 @@ import ( "errors" "fmt" "log" + "net/http" + "net/url" "strings" "sync" "sync/atomic" @@ -135,8 +137,8 @@ func (cfg *Config) getCertDuringHandshake(name string, loadIfNecessary, obtainIf name = strings.ToLower(name) - // Make sure aren't over any applicable limits - err := cfg.checkLimitsForObtainingNewCerts(name) + // Make sure the certificate should be obtained based on config + err := cfg.checkIfCertShouldBeObtained(name) if err != nil { return Certificate{}, err } @@ -159,10 +161,52 @@ func (cfg *Config) getCertDuringHandshake(name string, loadIfNecessary, obtainIf return Certificate{}, fmt.Errorf("no certificate available for %s", name) } +// checkIfCertShouldBeObtained checks to see if an on-demand tls certificate +// should be obtained for a given domain based upon the config settings. If +// a non-nil error is returned, do not issue a new certificate for name. +func (cfg *Config) checkIfCertShouldBeObtained(name string) error { + // If the "ask" URL is defined in the config, use to determine if a + // cert should obtained + if cfg.OnDemandState.AskURL != nil { + return cfg.checkURLForObtainingNewCerts(name) + } + + // Otherwise use the limit defined by the "max_certs" setting + return cfg.checkLimitsForObtainingNewCerts(name) +} + +func (cfg *Config) checkURLForObtainingNewCerts(name string) error { + client := http.Client{ + Timeout: 10 * time.Second, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return errors.New("following http redirects is not allowed") + }, + } + + // Copy the URL from the config in order to modify it for this request + askURL := new(url.URL) + *askURL = *cfg.OnDemandState.AskURL + + query := askURL.Query() + query.Set("domain", name) + askURL.RawQuery = query.Encode() + + resp, err := client.Get(askURL.String()) + if err != nil { + return fmt.Errorf("error checking %v to deterine if certificate for hostname '%s' should be allowed: %v", cfg.OnDemandState.AskURL, name, err) + } + defer 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, cfg.OnDemandState.AskURL) + } + + return nil +} + // checkLimitsForObtainingNewCerts checks to see if name can be issued right -// now according to mitigating factors we keep track of and preferences the -// user has set. If a non-nil error is returned, do not issue a new certificate -// for name. +// now according the maximum count defined in the configuration. If a non-nil +// error is returned, do not issue a new certificate for name. func (cfg *Config) checkLimitsForObtainingNewCerts(name string) error { // User can set hard limit for number of certs for the process to issue if cfg.OnDemandState.MaxObtain > 0 && diff --git a/caddytls/setup.go b/caddytls/setup.go index 6a16cbf3..cbc2baca 100644 --- a/caddytls/setup.go +++ b/caddytls/setup.go @@ -21,6 +21,7 @@ import ( "fmt" "io/ioutil" "log" + "net/url" "os" "path/filepath" "strconv" @@ -49,7 +50,7 @@ func setupTLS(c *caddy.Controller) error { config.Enabled = true for c.Next() { - var certificateFile, keyFile, loadDir, maxCerts string + var certificateFile, keyFile, loadDir, maxCerts, askURL string args := c.RemainingArgs() switch len(args) { @@ -164,6 +165,9 @@ func setupTLS(c *caddy.Controller) error { case "max_certs": c.Args(&maxCerts) config.OnDemand = true + case "ask": + c.Args(&askURL) + config.OnDemand = true case "dns": args := c.RemainingArgs() if len(args) != 1 { @@ -213,6 +217,19 @@ func setupTLS(c *caddy.Controller) error { config.OnDemandState.MaxObtain = int32(maxCertsNum) } + if askURL != "" { + parsedURL, err := url.Parse(askURL) + if err != nil { + return c.Err("ask must be a valid url") + } + + if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { + return c.Err("ask URL must use http or https") + } + + config.OnDemandState.AskURL = parsedURL + } + // don't try to load certificates unless we're supposed to if !config.Enabled || !config.Manual { continue