aboutsummaryrefslogtreecommitdiffhomepage
path: root/modules/caddyhttp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/caddyhttp')
-rw-r--r--modules/caddyhttp/ip_matchers.go6
-rw-r--r--modules/caddyhttp/matchers.go64
-rw-r--r--modules/caddyhttp/replacer.go8
-rw-r--r--modules/caddyhttp/reverseproxy/reverseproxy.go12
4 files changed, 90 insertions, 0 deletions
diff --git a/modules/caddyhttp/ip_matchers.go b/modules/caddyhttp/ip_matchers.go
index baa7c51ce..9101a0357 100644
--- a/modules/caddyhttp/ip_matchers.go
+++ b/modules/caddyhttp/ip_matchers.go
@@ -143,6 +143,9 @@ func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
// Match returns true if r matches m.
func (m MatchRemoteIP) Match(r *http.Request) bool {
+ if r.TLS != nil && !r.TLS.HandshakeComplete {
+ return false // if handshake is not finished, we infer 0-RTT that has not verified remote IP; could be spoofed
+ }
address := r.RemoteAddr
clientIP, zoneID, err := parseIPZoneFromString(address)
if err != nil {
@@ -228,6 +231,9 @@ func (m *MatchClientIP) Provision(ctx caddy.Context) error {
// Match returns true if r matches m.
func (m MatchClientIP) Match(r *http.Request) bool {
+ if r.TLS != nil && !r.TLS.HandshakeComplete {
+ return false // if handshake is not finished, we infer 0-RTT that has not verified remote IP; could be spoofed
+ }
address := GetVar(r.Context(), ClientIPVarKey).(string)
clientIP, zoneID, err := parseIPZoneFromString(address)
if err != nil {
diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go
index 392312b6c..b7952ab69 100644
--- a/modules/caddyhttp/matchers.go
+++ b/modules/caddyhttp/matchers.go
@@ -178,6 +178,22 @@ type (
// "http/2", "http/3", or minimum versions: "http/2+", etc.
MatchProtocol string
+ // MatchTLS matches HTTP requests based on the underlying
+ // TLS connection state. If this matcher is specified but
+ // the request did not come over TLS, it will never match.
+ // If this matcher is specified but is empty and the request
+ // did come in over TLS, it will always match.
+ MatchTLS struct {
+ // Matches if the TLS handshake has completed. QUIC 0-RTT early
+ // data may arrive before the handshake completes. Generally, it
+ // is unsafe to replay these requests if they are not idempotent;
+ // additionally, the remote IP of early data packets can more
+ // easily be spoofed. It is conventional to respond with HTTP 425
+ // Too Early if the request cannot risk being processed in this
+ // state.
+ HandshakeComplete *bool `json:"handshake_complete,omitempty"`
+ }
+
// MatchNot matches requests by negating the results of its matcher
// sets. A single "not" matcher takes one or more matcher sets. Each
// matcher set is OR'ed; in other words, if any matcher set returns
@@ -213,6 +229,7 @@ func init() {
caddy.RegisterModule(MatchHeader{})
caddy.RegisterModule(MatchHeaderRE{})
caddy.RegisterModule(new(MatchProtocol))
+ caddy.RegisterModule(MatchTLS{})
caddy.RegisterModule(MatchNot{})
}
@@ -1237,6 +1254,53 @@ func (MatchProtocol) CELLibrary(_ caddy.Context) (cel.Library, error) {
}
// CaddyModule returns the Caddy module information.
+func (MatchTLS) CaddyModule() caddy.ModuleInfo {
+ return caddy.ModuleInfo{
+ ID: "http.matchers.tls",
+ New: func() caddy.Module { return new(MatchTLS) },
+ }
+}
+
+// Match returns true if r matches m.
+func (m MatchTLS) Match(r *http.Request) bool {
+ if r.TLS == nil {
+ return false
+ }
+ if m.HandshakeComplete != nil {
+ if (!*m.HandshakeComplete && r.TLS.HandshakeComplete) ||
+ (*m.HandshakeComplete && !r.TLS.HandshakeComplete) {
+ return false
+ }
+ }
+ return true
+}
+
+// UnmarshalCaddyfile parses Caddyfile tokens for this matcher. Syntax:
+//
+// ... tls [early_data]
+//
+// EXPERIMENTAL SYNTAX: Subject to change.
+func (m *MatchTLS) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ // iterate to merge multiple matchers into one
+ for d.Next() {
+ if d.NextArg() {
+ switch d.Val() {
+ case "early_data":
+ var false bool
+ m.HandshakeComplete = &false
+ }
+ }
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+ if d.NextBlock(0) {
+ return d.Err("malformed tls matcher: blocks are not supported yet")
+ }
+ }
+ return nil
+}
+
+// CaddyModule returns the Caddy module information.
func (MatchNot) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.matchers.not",
diff --git a/modules/caddyhttp/replacer.go b/modules/caddyhttp/replacer.go
index 1cf3ec474..2c0f32357 100644
--- a/modules/caddyhttp/replacer.go
+++ b/modules/caddyhttp/replacer.go
@@ -142,8 +142,16 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
}
return port, true
case "http.request.remote":
+ if req.TLS != nil && !req.TLS.HandshakeComplete {
+ // without a complete handshake (QUIC "early data") we can't trust the remote IP address to not be spoofed
+ return nil, true
+ }
return req.RemoteAddr, true
case "http.request.remote.host":
+ if req.TLS != nil && !req.TLS.HandshakeComplete {
+ // without a complete handshake (QUIC "early data") we can't trust the remote IP address to not be spoofed
+ return nil, true
+ }
host, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
// req.RemoteAddr is host:port for tcp and udp sockets and /unix/socket.path
diff --git a/modules/caddyhttp/reverseproxy/reverseproxy.go b/modules/caddyhttp/reverseproxy/reverseproxy.go
index 1a559e5dd..4f97edead 100644
--- a/modules/caddyhttp/reverseproxy/reverseproxy.go
+++ b/modules/caddyhttp/reverseproxy/reverseproxy.go
@@ -605,6 +605,18 @@ func (h Handler) prepareRequest(req *http.Request, repl *caddy.Replacer) (*http.
req.Header.Set("User-Agent", "")
}
+ // Indicate if request has been conveyed in early data.
+ // RFC 8470: "An intermediary that forwards a request prior to the
+ // completion of the TLS handshake with its client MUST send it with
+ // the Early-Data header field set to “1” (i.e., it adds it if not
+ // present in the request). An intermediary MUST use the Early-Data
+ // header field if the request might have been subject to a replay and
+ // might already have been forwarded by it or another instance
+ // (see Section 6.2)."
+ if req.TLS != nil && !req.TLS.HandshakeComplete {
+ req.Header.Set("Early-Data", "1")
+ }
+
reqUpType := upgradeType(req.Header)
removeConnectionHeaders(req.Header)