mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 10:37:24 -04:00 
			
		
		
		
	tls: Add option for backend to approve on-demand cert (#1939)
This adds the ask sub-directive to tls that defines the URL of a backend HTTP service to be queried during the TLS handshake to determine if an on-demand TLS certificate should be acquired for incoming hostnames. When the ask sub-directive is defined, Caddy will query the URL for permission to acquire a cert by making a HTTP GET request to the URL including the requested domain in the query string. If the backend service returns a 2xx response Caddy will acquire a cert. Any other response code (including 3xx redirects) are be considered a rejection and the certificate will not be acquired.
This commit is contained in:
		
							parent
							
								
									2782553231
								
							
						
					
					
						commit
						689591ef01
					
				| @ -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 | ||||
|  | ||||
| @ -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 && | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user