summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/caddyhttp/caddyhttp.go9
-rw-r--r--modules/caddyhttp/encode/encode.go2
-rw-r--r--modules/caddyhttp/fileserver/matcher.go2
-rw-r--r--modules/caddyhttp/matchers.go2
-rw-r--r--modules/caddyhttp/reverseproxy/caddyfile.go146
-rw-r--r--modules/caddyhttp/reverseproxy/command.go21
-rw-r--r--modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go12
-rw-r--r--modules/caddyhttp/reverseproxy/hosts.go2
-rw-r--r--modules/caddyhttp/server.go4
-rw-r--r--modules/caddyhttp/templates/templates.go2
10 files changed, 177 insertions, 25 deletions
diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go
index 94b2eee..6ad70f5 100644
--- a/modules/caddyhttp/caddyhttp.go
+++ b/modules/caddyhttp/caddyhttp.go
@@ -29,6 +29,7 @@ import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddytls"
+ "github.com/caddyserver/certmagic"
"github.com/lucas-clemente/quic-go/http3"
"go.uber.org/zap"
)
@@ -112,6 +113,10 @@ type App struct {
// affect functionality.
Servers map[string]*Server `json:"servers,omitempty"`
+ // DefaultSNI if set configures all certificate lookups to fallback to use
+ // this SNI name if a more specific certificate could not be found
+ DefaultSNI string `json:"default_sni,omitempty"`
+
servers []*http.Server
h3servers []*http3.Server
h3listeners []net.PacketConn
@@ -145,8 +150,10 @@ func (app *App) Provision(ctx caddy.Context) error {
repl := caddy.NewReplacer()
+ certmagic.Default.DefaultServerName = app.DefaultSNI
+
// this provisions the matchers for each route,
- // and prepares auto HTTP->HTTP redirects, and
+ // and prepares auto HTTP->HTTPS redirects, and
// is required before we provision each server
err = app.automaticHTTPSPhase1(ctx, repl)
if err != nil {
diff --git a/modules/caddyhttp/encode/encode.go b/modules/caddyhttp/encode/encode.go
index c3a1c23..52205aa 100644
--- a/modules/caddyhttp/encode/encode.go
+++ b/modules/caddyhttp/encode/encode.go
@@ -282,7 +282,7 @@ func acceptedEncodings(r *http.Request) []string {
}
// encodings with q-factor of 0 are not accepted;
- // use a small theshold to account for float precision
+ // use a small threshold to account for float precision
if qFactor < 0.00001 {
continue
}
diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go
index ed5c102..1915fb7 100644
--- a/modules/caddyhttp/fileserver/matcher.go
+++ b/modules/caddyhttp/fileserver/matcher.go
@@ -51,7 +51,7 @@ type MatchFile struct {
Root string `json:"root,omitempty"`
// The list of files to try. Each path here is
- // considered relatice to Root. If nil, the request
+ // considered related to Root. If nil, the request
// URL's path will be assumed. Files and
// directories are treated distinctly, so to match
// a directory, the filepath MUST end in a forward
diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go
index 3c357c6..6b57ead 100644
--- a/modules/caddyhttp/matchers.go
+++ b/modules/caddyhttp/matchers.go
@@ -680,7 +680,7 @@ func (m MatchRemoteIP) Match(r *http.Request) bool {
return false
}
-// MatchRegexp is an embeddable type for matching
+// MatchRegexp is an embedable type for matching
// using regular expressions. It adds placeholders
// to the request's replacer.
type MatchRegexp struct {
diff --git a/modules/caddyhttp/reverseproxy/caddyfile.go b/modules/caddyhttp/reverseproxy/caddyfile.go
index d08e7f1..9ff9dce 100644
--- a/modules/caddyhttp/reverseproxy/caddyfile.go
+++ b/modules/caddyhttp/reverseproxy/caddyfile.go
@@ -15,7 +15,10 @@
package reverseproxy
import (
+ "net"
"net/http"
+ "net/url"
+ "reflect"
"strconv"
"strings"
"time"
@@ -81,10 +84,106 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
// }
// }
//
+// Proxy upstream addresses should be network dial addresses such
+// as `host:port`, or a URL such as `scheme://host:port`. Scheme
+// and port may be inferred from other parts of the address/URL; if
+// either are missing, defaults to HTTP.
func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ // currently, all backends must use the same scheme/protocol (the
+ // underlying JSON does not yet support per-backend transports)
+ var commonScheme string
+
+ // we'll wait until the very end of parsing before
+ // validating and encoding the transport
+ var transport http.RoundTripper
+ var transportModuleName string
+
+ // TODO: the logic in this function is kind of sensitive, we need
+ // to write tests before making any more changes to it
+ upstreamDialAddress := func(upstreamAddr string) (string, error) {
+ var network, scheme, host, port string
+
+ if strings.Contains(upstreamAddr, "://") {
+ toURL, err := url.Parse(upstreamAddr)
+ if err != nil {
+ return "", d.Errf("parsing upstream URL: %v", err)
+ }
+
+ // there is currently no way to perform a URL rewrite between choosing
+ // a backend and proxying to it, so we cannot allow extra components
+ // in backend URLs
+ if toURL.Path != "" || toURL.RawQuery != "" || toURL.Fragment != "" {
+ return "", d.Err("for now, URLs for proxy upstreams only support scheme, host, and port components")
+ }
+
+ // ensure the port and scheme aren't in conflict
+ urlPort := toURL.Port()
+ if toURL.Scheme == "http" && urlPort == "443" {
+ return "", d.Err("upstream address has conflicting scheme (http://) and port (:443, the HTTPS port)")
+ }
+ if toURL.Scheme == "https" && urlPort == "80" {
+ return "", d.Err("upstream address has conflicting scheme (https://) and port (:80, the HTTP port)")
+ }
+
+ // if port is missing, attempt to infer from scheme
+ if toURL.Port() == "" {
+ var toPort string
+ switch toURL.Scheme {
+ case "", "http":
+ toPort = "80"
+ case "https":
+ toPort = "443"
+ }
+ toURL.Host = net.JoinHostPort(toURL.Hostname(), toPort)
+ }
+
+ scheme, host, port = toURL.Scheme, toURL.Hostname(), toURL.Port()
+ } else {
+ // extract network manually, since caddy.ParseNetworkAddress() will always add one
+ if idx := strings.Index(upstreamAddr, "/"); idx >= 0 {
+ network = strings.ToLower(strings.TrimSpace(upstreamAddr[:idx]))
+ upstreamAddr = upstreamAddr[idx+1:]
+ }
+ var err error
+ host, port, err = net.SplitHostPort(upstreamAddr)
+ if err != nil {
+ host = upstreamAddr
+ }
+ }
+
+ // if scheme is not set, we may be able to infer it from a known port
+ if scheme == "" {
+ if port == "80" {
+ scheme = "http"
+ } else if port == "443" {
+ scheme = "https"
+ }
+ }
+
+ // the underlying JSON does not yet support different
+ // transports (protocols or schemes) to each backend,
+ // so we remember the last one we see and compare them
+ if commonScheme != "" && scheme != commonScheme {
+ return "", d.Errf("for now, all proxy upstreams must use the same scheme (transport protocol); expecting '%s://' but got '%s://'",
+ commonScheme, scheme)
+ }
+ commonScheme = scheme
+
+ // for simplest possible config, we only need to include
+ // the network portion if the user specified one
+ if network != "" {
+ return caddy.JoinNetworkAddress(network, host, port), nil
+ }
+ return net.JoinHostPort(host, port), nil
+ }
+
for d.Next() {
for _, up := range d.RemainingArgs() {
- h.Upstreams = append(h.Upstreams, &Upstream{Dial: up})
+ dialAddr, err := upstreamDialAddress(up)
+ if err != nil {
+ return err
+ }
+ h.Upstreams = append(h.Upstreams, &Upstream{Dial: dialAddr})
}
for d.NextBlock(0) {
@@ -95,7 +194,11 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return d.ArgErr()
}
for _, up := range args {
- h.Upstreams = append(h.Upstreams, &Upstream{Dial: up})
+ dialAddr, err := upstreamDialAddress(up)
+ if err != nil {
+ return err
+ }
+ h.Upstreams = append(h.Upstreams, &Upstream{Dial: dialAddr})
}
case "lb_policy":
@@ -392,8 +495,8 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if h.TransportRaw != nil {
return d.Err("transport already specified")
}
- name := d.Val()
- mod, err := caddy.GetModule("http.reverse_proxy.transport." + name)
+ transportModuleName = d.Val()
+ mod, err := caddy.GetModule("http.reverse_proxy.transport." + transportModuleName)
if err != nil {
return d.Errf("getting transport module '%s': %v", mod, err)
}
@@ -409,7 +512,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if !ok {
return d.Errf("module %s is not a RoundTripper", mod)
}
- h.TransportRaw = caddyconfig.JSONModuleObject(rt, "protocol", name, nil)
+ transport = rt
default:
return d.Errf("unrecognized subdirective %s", d.Val())
@@ -417,6 +520,39 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
}
+ // if the scheme inferred from the backends' addresses is
+ // HTTPS, we will need a non-nil transport to enable TLS
+ if commonScheme == "https" && transport == nil {
+ transport = new(HTTPTransport)
+ transportModuleName = "http"
+ }
+
+ // verify transport configuration, and finally encode it
+ if transport != nil {
+ // TODO: these two cases are identical, but I don't know how to reuse the code
+ switch ht := transport.(type) {
+ case *HTTPTransport:
+ if commonScheme == "https" && ht.TLS == nil {
+ ht.TLS = new(TLSConfig)
+ }
+ if ht.TLS != nil && commonScheme == "http" {
+ return d.Errf("upstream address scheme is HTTP but transport is configured for HTTP+TLS (HTTPS)")
+ }
+
+ case *NTLMTransport:
+ if commonScheme == "https" && ht.TLS == nil {
+ ht.TLS = new(TLSConfig)
+ }
+ if ht.TLS != nil && commonScheme == "http" {
+ return d.Errf("upstream address scheme is HTTP but transport is configured for HTTP+TLS (HTTPS)")
+ }
+ }
+
+ if !reflect.DeepEqual(transport, new(HTTPTransport)) {
+ h.TransportRaw = caddyconfig.JSONModuleObject(transport, "protocol", transportModuleName, nil)
+ }
+ }
+
return nil
}
diff --git a/modules/caddyhttp/reverseproxy/command.go b/modules/caddyhttp/reverseproxy/command.go
index 462be1b..6f70d14 100644
--- a/modules/caddyhttp/reverseproxy/command.go
+++ b/modules/caddyhttp/reverseproxy/command.go
@@ -36,7 +36,7 @@ func init() {
caddycmd.RegisterCommand(caddycmd.Command{
Name: "reverse-proxy",
Func: cmdReverseProxy,
- Usage: "[--from <addr>] [--to <addr>]",
+ Usage: "[--from <addr>] [--to <addr>] [--change-host-header]",
Short: "A quick and production-ready reverse proxy",
Long: `
A simple but production-ready reverse proxy. Useful for quick deployments,
@@ -46,11 +46,16 @@ Simply shuttles HTTP traffic from the --from address to the --to address.
If the --from address has a domain name, Caddy will attempt to serve the
proxy over HTTPS with a certificate.
+
+If --change-host-header is set, the Host header on the request will be modified
+from its original incoming value to the address of the upstream. (Otherwise, by
+default, all incoming headers are passed through unmodified.)
`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("file-server", flag.ExitOnError)
- fs.String("from", "", "Address to receive traffic on")
- fs.String("to", "", "Upstream address to proxy traffic to")
+ fs.String("from", "", "Address on which to receive traffic")
+ fs.String("to", "", "Upstream address to which to to proxy traffic")
+ fs.Bool("change-host-header", false, "Set upstream Host header to address of upstream")
return fs
}(),
})
@@ -59,6 +64,7 @@ proxy over HTTPS with a certificate.
func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
from := fs.String("from")
to := fs.String("to")
+ changeHost := fs.Bool("change-host-header")
if from == "" {
from = "localhost:" + httpcaddyfile.DefaultPort
@@ -97,13 +103,16 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
handler := Handler{
TransportRaw: caddyconfig.JSONModuleObject(ht, "protocol", "http", nil),
Upstreams: UpstreamPool{{Dial: toURL.Host}},
- Headers: &headers.Handler{
+ }
+
+ if changeHost {
+ handler.Headers = &headers.Handler{
Request: &headers.HeaderOps{
Set: http.Header{
- "Host": []string{"{http.reverse_proxy.upstream.host}"},
+ "Host": []string{"{http.reverse_proxy.upstream.hostport}"},
},
},
- },
+ }
}
route := caddyhttp.Route{
diff --git a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
index 8c9fd38..81fd48e 100644
--- a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
+++ b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
@@ -105,7 +105,7 @@ func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// }
//
// Thus, this directive produces multiple handlers, each with a different
-// matcher because multiple consecutive hgandlers are necessary to support
+// matcher because multiple consecutive handlers are necessary to support
// the common PHP use case. If this "common" config is not compatible
// with a user's PHP requirements, they can use a manual approach based
// on the example above to configure it precisely as they need.
@@ -167,14 +167,10 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
// either way, strip the matcher token and pass
// the remaining tokens to the unmarshaler so that
// we can gain the rest of the reverse_proxy syntax
- userMatcherSet, hasUserMatcher, err := h.MatcherToken()
+ userMatcherSet, err := h.ExtractMatcherSet()
if err != nil {
return nil, err
}
- if hasUserMatcher {
- h.Dispenser.Delete() // strip matcher token
- }
- h.Dispenser.Reset() // pretend this lookahead never happened
// set up the transport for FastCGI, and specifically PHP
fcgiTransport := Transport{SplitPath: ".php"}
@@ -186,6 +182,8 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
// the rest of the config is specified by the user
// using the reverse_proxy directive syntax
+ // TODO: this can overwrite our fcgiTransport that we encoded and
+ // set on the rpHandler... even with a non-fastcgi transport!
err = rpHandler.UnmarshalCaddyfile(h.Dispenser)
if err != nil {
return nil, err
@@ -204,7 +202,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
// the user's matcher is a prerequisite for ours, so
// wrap ours in a subroute and return that
- if hasUserMatcher {
+ if userMatcherSet != nil {
return []httpcaddyfile.ConfigValue{
{
Class: "route",
diff --git a/modules/caddyhttp/reverseproxy/hosts.go b/modules/caddyhttp/reverseproxy/hosts.go
index 54de5a8..602aab2 100644
--- a/modules/caddyhttp/reverseproxy/hosts.go
+++ b/modules/caddyhttp/reverseproxy/hosts.go
@@ -27,7 +27,7 @@ import (
// Host represents a remote host which can be proxied to.
// Its methods must be safe for concurrent use.
type Host interface {
- // NumRequests returns the numnber of requests
+ // NumRequests returns the number of requests
// currently in process with the host.
NumRequests() int
diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go
index 580449b..1e22790 100644
--- a/modules/caddyhttp/server.go
+++ b/modules/caddyhttp/server.go
@@ -94,7 +94,9 @@ type Server struct {
// client authentication.
StrictSNIHost *bool `json:"strict_sni_host,omitempty"`
- // Logs customizes how access logs are handled in this server.
+ // Customizes how access logs are handled in this server. To
+ // minimally enable access logs, simply set this to a non-null,
+ // empty struct.
Logs *ServerLogConfig `json:"logs,omitempty"`
// Enable experimental HTTP/3 support. Note that HTTP/3 is not a
diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go
index 94764bf..cf0908d 100644
--- a/modules/caddyhttp/templates/templates.go
+++ b/modules/caddyhttp/templates/templates.go
@@ -254,7 +254,7 @@ func (t *Templates) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy
rec.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-created content
rec.Header().Del("Last-Modified") // useless for dynamic content since it's always changing
- // we don't know a way to guickly generate etag for dynamic content,
+ // we don't know a way to quickly generate etag for dynamic content,
// and weak etags still cause browsers to rely on it even after a
// refresh, so disable them until we find a better way to do this
rec.Header().Del("Etag")