mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 06:03:48 +03:00
Optionally enforce strict TLS SNI + HTTP Host matching, & misc. cleanup
We should look into a way to enable this by default when TLS client auth is configured for a server
This commit is contained in:
parent
a524bcfe78
commit
2b22d2e6ea
4 changed files with 49 additions and 14 deletions
|
@ -13,8 +13,8 @@ import (
|
||||||
// Context is a type which defines the lifetime of modules that
|
// Context is a type which defines the lifetime of modules that
|
||||||
// are loaded and provides access to the parent configuration
|
// are loaded and provides access to the parent configuration
|
||||||
// that spawned the modules which are loaded. It should be used
|
// that spawned the modules which are loaded. It should be used
|
||||||
// with care and only wrapped with derivation functions from
|
// with care and wrapped with derivation functions from the
|
||||||
// the standard context package if you don't need the Caddy
|
// standard context package only if you don't need the Caddy
|
||||||
// specific features. These contexts are cancelled when the
|
// specific features. These contexts are cancelled when the
|
||||||
// lifetime of the modules loaded from it are over.
|
// lifetime of the modules loaded from it are over.
|
||||||
//
|
//
|
||||||
|
|
|
@ -162,11 +162,11 @@ func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Re
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapMiddleware wraps m such that it can be correctly
|
// wrapMiddleware wraps m such that it can be correctly
|
||||||
// appended to a list of middleware. This is necessary
|
// appended to a list of middleware. This separate closure
|
||||||
// so that only the last middleware in a loop does not
|
// is necessary so that only the last middleware in a loop
|
||||||
// become the only middleware of the stack, repeatedly
|
// does not become the only middleware of the stack,
|
||||||
// executed (i.e. it is necessary to keep a reference
|
// repeatedly executed (i.e. it is necessary to keep a
|
||||||
// to this m outside of the scope of a loop)!
|
// reference to this m outside of the scope of a loop)!
|
||||||
func wrapMiddleware(m MiddlewareHandler) Middleware {
|
func wrapMiddleware(m MiddlewareHandler) Middleware {
|
||||||
return func(next HandlerFunc) HandlerFunc {
|
return func(next HandlerFunc) HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) error {
|
return func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy"
|
"github.com/caddyserver/caddy"
|
||||||
"github.com/caddyserver/caddy/modules/caddytls"
|
"github.com/caddyserver/caddy/modules/caddytls"
|
||||||
|
@ -25,6 +26,7 @@ type Server struct {
|
||||||
TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies,omitempty"`
|
TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies,omitempty"`
|
||||||
AutoHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"`
|
AutoHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"`
|
||||||
MaxRehandles int `json:"max_rehandles,omitempty"`
|
MaxRehandles int `json:"max_rehandles,omitempty"`
|
||||||
|
StrictSNIHost bool `json:"strict_sni_host,omitempty"` // TODO: see if we can turn this on by default when clientauth is configured
|
||||||
|
|
||||||
tlsApp *caddytls.TLS
|
tlsApp *caddytls.TLS
|
||||||
}
|
}
|
||||||
|
@ -47,8 +49,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
addHTTPVarsToReplacer(repl, r, w)
|
addHTTPVarsToReplacer(repl, r, w)
|
||||||
|
|
||||||
// build and execute the main handler chain
|
// build and execute the main handler chain
|
||||||
stack, w := s.Routes.BuildCompositeRoute(w, r)
|
stack, wrappedWriter := s.Routes.BuildCompositeRoute(w, r)
|
||||||
err := s.executeCompositeRoute(w, r, stack)
|
stack = s.wrapPrimaryRoute(stack)
|
||||||
|
err := s.executeCompositeRoute(wrappedWriter, r, stack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// add the raw error value to the request context
|
// add the raw error value to the request context
|
||||||
// so it can be accessed by error handlers
|
// so it can be accessed by error handlers
|
||||||
|
@ -66,8 +69,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Errors != nil && len(s.Errors.Routes) > 0 {
|
if s.Errors != nil && len(s.Errors.Routes) > 0 {
|
||||||
errStack, w := s.Errors.Routes.BuildCompositeRoute(w, r)
|
errStack, wrappedWriter := s.Errors.Routes.BuildCompositeRoute(w, r)
|
||||||
err := s.executeCompositeRoute(w, r, errStack)
|
err := s.executeCompositeRoute(wrappedWriter, r, errStack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: what should we do if the error handler has an error?
|
// TODO: what should we do if the error handler has an error?
|
||||||
log.Printf("[ERROR] [%s %s] handling error: %v", r.Method, r.RequestURI, err)
|
log.Printf("[ERROR] [%s %s] handling error: %v", r.Method, r.RequestURI, err)
|
||||||
|
@ -104,6 +107,37 @@ func (s *Server) executeCompositeRoute(w http.ResponseWriter, r *http.Request, s
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wrapPrimaryRoute wraps stack (a compiled middleware handler chain)
|
||||||
|
// in s.enforcementHandler which performs crucial security checks, etc.
|
||||||
|
func (s *Server) wrapPrimaryRoute(stack Handler) Handler {
|
||||||
|
return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return s.enforcementHandler(w, r, stack)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// enforcementHandler is an implicit middleware which performs
|
||||||
|
// standard checks before executing the HTTP middleware chain.
|
||||||
|
func (s *Server) enforcementHandler(w http.ResponseWriter, r *http.Request, next Handler) error {
|
||||||
|
// enforce strict host matching, which ensures that the SNI
|
||||||
|
// value (if any), matches the Host header; essential for
|
||||||
|
// servers that rely on TLS ClientAuth sharing a listener
|
||||||
|
// with servers that do not; if not enforced, client could
|
||||||
|
// bypass by sending benign SNI then restricted Host header
|
||||||
|
if s.StrictSNIHost && r.TLS != nil {
|
||||||
|
hostname, _, err := net.SplitHostPort(r.Host)
|
||||||
|
if err != nil {
|
||||||
|
hostname = r.Host // OK; probably lacked port
|
||||||
|
}
|
||||||
|
if strings.ToLower(r.TLS.ServerName) != strings.ToLower(hostname) {
|
||||||
|
err := fmt.Errorf("strict host matching: TLS ServerName (%s) and HTTP Host (%s) values differ",
|
||||||
|
r.TLS.ServerName, hostname)
|
||||||
|
r.Close = true
|
||||||
|
return Error(http.StatusForbidden, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) listenersUseAnyPortOtherThan(otherPort int) bool {
|
func (s *Server) listenersUseAnyPortOtherThan(otherPort int) bool {
|
||||||
for _, lnAddr := range s.Listen {
|
for _, lnAddr := range s.Listen {
|
||||||
_, addrs, err := parseListenAddr(lnAddr)
|
_, addrs, err := parseListenAddr(lnAddr)
|
||||||
|
|
|
@ -88,15 +88,16 @@ 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 {
|
||||||
|
magic := certmagic.New(t.certCache, certmagic.Config{
|
||||||
|
Storage: t.ctx.Storage(),
|
||||||
|
})
|
||||||
|
|
||||||
// load manual/static (unmanaged) certificates
|
// load manual/static (unmanaged) certificates
|
||||||
for _, loader := range t.certificateLoaders {
|
for _, loader := range t.certificateLoaders {
|
||||||
certs, err := loader.LoadCertificates()
|
certs, err := loader.LoadCertificates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading certificates: %v", err)
|
return fmt.Errorf("loading certificates: %v", err)
|
||||||
}
|
}
|
||||||
magic := certmagic.New(t.certCache, certmagic.Config{
|
|
||||||
Storage: t.ctx.Storage(),
|
|
||||||
})
|
|
||||||
for _, cert := range certs {
|
for _, cert := range certs {
|
||||||
err := magic.CacheUnmanagedTLSCertificate(cert.Certificate, cert.Tags)
|
err := magic.CacheUnmanagedTLSCertificate(cert.Certificate, cert.Tags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in a new issue