summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorMatthew Holt <mholt@users.noreply.github.com>2019-06-20 20:36:29 -0600
committerMatthew Holt <mholt@users.noreply.github.com>2019-06-20 20:36:29 -0600
commit269b1e9aa34b2b02911f8746e7b6a162cd8222cf (patch)
tree325e2a14ef30c0512674dda800ba6d5f17cc7ac7 /modules
parent6d0350d04ecd7074a65d57f03bd721e75537c13d (diff)
tls: Improve (and fix) on-demand configuration
Diffstat (limited to 'modules')
-rw-r--r--modules/caddytls/acmemanager.go66
-rw-r--r--modules/caddytls/connpolicy.go3
-rw-r--r--modules/caddytls/tls.go39
3 files changed, 98 insertions, 10 deletions
diff --git a/modules/caddytls/acmemanager.go b/modules/caddytls/acmemanager.go
index 50f8648..871b2bf 100644
--- a/modules/caddytls/acmemanager.go
+++ b/modules/caddytls/acmemanager.go
@@ -3,6 +3,7 @@ package caddytls
import (
"encoding/json"
"fmt"
+ "net/url"
"time"
"github.com/go-acme/lego/certcrypto"
@@ -30,12 +31,12 @@ func init() {
type ACMEManagerMaker struct {
CA string `json:"ca,omitempty"`
Email string `json:"email,omitempty"`
- RenewAhead caddy.Duration `json:"renew_ahead,omitempty"`
+ RenewAhead caddy.Duration `json:"renew_ahead,omitempty"`
KeyType string `json:"key_type,omitempty"`
- ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"`
+ ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"`
MustStaple bool `json:"must_staple,omitempty"`
- Challenges ChallengesConfig `json:"challenges"`
- OnDemand *OnDemandConfig `json:"on_demand,omitempty"`
+ Challenges ChallengesConfig `json:"challenges,omitempty"`
+ OnDemand bool `json:"on_demand,omitempty"`
Storage json.RawMessage `json:"storage,omitempty"`
storage certmagic.Storage
@@ -109,9 +110,34 @@ func (m *ACMEManagerMaker) makeCertMagicConfig(ctx caddy.Context) certmagic.Conf
}
var ond *certmagic.OnDemandConfig
- if m.OnDemand != nil {
+ if m.OnDemand {
+ var onDemand *OnDemandConfig
+ appVal, err := ctx.App("tls")
+ if err == nil {
+ onDemand = appVal.(*TLS).Automation.OnDemand
+ }
+
ond = &certmagic.OnDemandConfig{
- // TODO: fill this out
+ DecisionFunc: func(name string) error {
+ if onDemand != nil {
+ if onDemand.Ask != "" {
+ err := onDemandAskRequest(onDemand.Ask, name)
+ if err != nil {
+ return err
+ }
+ }
+ // check the rate limiter last, since
+ // even checking consumes a token; so
+ // don't even bother checking if the
+ // other regulations fail anyway
+ if onDemand.RateLimit != nil {
+ if !onDemandRateLimiter.Allow() {
+ return fmt.Errorf("on-demand rate limit exceeded")
+ }
+ }
+ }
+ return nil
+ },
}
}
@@ -134,6 +160,34 @@ func (m *ACMEManagerMaker) makeCertMagicConfig(ctx caddy.Context) certmagic.Conf
}
}
+// onDemandAskRequest makes a request to the ask URL
+// to see if a certificate can be obtained for name.
+// The certificate request should be denied if this
+// returns an error.
+func onDemandAskRequest(ask string, name string) error {
+ askURL, err := url.Parse(ask)
+ if err != nil {
+ return fmt.Errorf("parsing ask URL: %v", err)
+ }
+ qs := askURL.Query()
+ qs.Set("domain", name)
+ askURL.RawQuery = qs.Encode()
+
+ resp, err := onDemandAskClient.Get(askURL.String())
+ if err != nil {
+ return fmt.Errorf("error checking %v to deterine if certificate for hostname '%s' should be allowed: %v",
+ ask, name, err)
+ }
+ 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, ask)
+ }
+
+ return nil
+}
+
// supportedCertKeyTypes is all the key types that are supported
// for certificates that are obtained through ACME.
var supportedCertKeyTypes = map[string]certcrypto.KeyType{
diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go
index 54cad7c..a278326 100644
--- a/modules/caddytls/connpolicy.go
+++ b/modules/caddytls/connpolicy.go
@@ -86,6 +86,7 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) (*tls.Config, error) {
}
return pol.stdTLSConfig, nil
}
+
return nil, fmt.Errorf("no server TLS configuration available for ClientHello: %+v", hello)
},
}, nil
@@ -148,6 +149,8 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error {
tlsApp.SessionTickets.unregister(cfg)
})
+ // TODO: Clean up active locks if app (or process) is being closed!
+
// add all the cipher suites in order, without duplicates
cipherSuitesAdded := make(map[uint16]struct{})
for _, csName := range p.CipherSuites {
diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go
index 0614d24..c84118a 100644
--- a/modules/caddytls/tls.go
+++ b/modules/caddytls/tls.go
@@ -5,10 +5,12 @@ import (
"encoding/json"
"fmt"
"net/http"
+ "time"
"github.com/caddyserver/caddy"
"github.com/go-acme/lego/challenge"
"github.com/mholt/certmagic"
+ "golang.org/x/time/rate"
)
func init() {
@@ -71,6 +73,16 @@ func (t *TLS) Provision(ctx caddy.Context) error {
return fmt.Errorf("provisioning session tickets configuration: %v", err)
}
+ // on-demand rate limiting
+ if t.Automation.OnDemand != nil && t.Automation.OnDemand.RateLimit != nil {
+ limit := rate.Every(time.Duration(t.Automation.OnDemand.RateLimit.Interval))
+ // TODO: Burst size is not updated, see https://github.com/golang/go/issues/23575
+ onDemandRateLimiter.SetLimit(limit)
+ } else {
+ // if no rate limit is specified, be sure to remove any existing limit
+ onDemandRateLimiter.SetLimit(0)
+ }
+
return nil
}
@@ -178,6 +190,7 @@ type CertificateLoader interface {
// construction and use of ACME clients.
type AutomationConfig struct {
Policies []AutomationPolicy `json:"policies,omitempty"`
+ OnDemand *OnDemandConfig `json:"on_demand,omitempty"`
}
// AutomationPolicy designates the policy for automating the
@@ -189,6 +202,9 @@ type AutomationPolicy struct {
Management managerMaker `json:"-"`
}
+// makeCertMagicConfig converts ap into a CertMagic config. Passing onDemand
+// is necessary because the automation policy does not have convenient access
+// to the TLS app's global on-demand policies;
func (ap AutomationPolicy) makeCertMagicConfig(ctx caddy.Context) certmagic.Config {
// 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
@@ -226,10 +242,14 @@ type TLSALPNChallengeConfig struct {
// OnDemandConfig configures on-demand TLS, for obtaining
// needed certificates at handshake-time.
type OnDemandConfig struct {
- // TODO: MaxCertificates state might not endure reloads...
- // MaxCertificates int `json:"max_certificates,omitempty"`
- AskURL string `json:"ask_url,omitempty"`
- AskStarlark string `json:"ask_starlark,omitempty"`
+ RateLimit *RateLimit `json:"rate_limit,omitempty"`
+ Ask string `json:"ask,omitempty"`
+}
+
+// RateLimit specifies an interval with optional burst size.
+type RateLimit struct {
+ Interval caddy.Duration `json:"interval,omitempty"`
+ Burst int `json:"burst,omitempty"`
}
// managerMaker makes a certificate manager.
@@ -237,4 +257,15 @@ type managerMaker interface {
newManager(interactive bool) (certmagic.Manager, error)
}
+// These perpetual values are used for on-demand TLS.
+var (
+ onDemandRateLimiter = rate.NewLimiter(0, 1)
+ onDemandAskClient = &http.Client{
+ Timeout: 10 * time.Second,
+ CheckRedirect: func(req *http.Request, via []*http.Request) error {
+ return fmt.Errorf("following http redirects is not allowed")
+ },
+ }
+)
+
const automateKey = "automate"