summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorMatthew Holt <mholt@users.noreply.github.com>2022-09-30 13:29:33 -0600
committerMatthew Holt <mholt@users.noreply.github.com>2022-09-30 13:29:33 -0600
commit9873ff9918224f56604048ea0ed3d3ae3953939e (patch)
treecde9d321ddd60339e8cf1e9872072f75d537e3f6 /modules
parent5e52bbb1369b7444700427539e4dd532ed13ac6d (diff)
caddyhttp: Remote IP prefix placeholders
See https://github.com/mholt/caddy-ratelimit/issues/12
Diffstat (limited to 'modules')
-rw-r--r--modules/caddyhttp/replacer.go32
-rw-r--r--modules/caddyhttp/replacer_test.go20
2 files changed, 48 insertions, 4 deletions
diff --git a/modules/caddyhttp/replacer.go b/modules/caddyhttp/replacer.go
index e154649..9ea5f77 100644
--- a/modules/caddyhttp/replacer.go
+++ b/modules/caddyhttp/replacer.go
@@ -31,6 +31,7 @@ import (
"io"
"net"
"net/http"
+ "net/netip"
"net/textproto"
"net/url"
"path"
@@ -196,6 +197,37 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
return or.URL.RawQuery, true
}
+ // remote IP range/prefix (e.g. keep top 24 bits of 1.2.3.4 => "1.2.3.0/24")
+ // syntax: "/V4,V6" where V4 = IPv4 bits, and V6 = IPv6 bits; if no comma, then same bit length used for both
+ // (EXPERIMENTAL)
+ if strings.HasPrefix(key, "http.request.remote.host/") {
+ host, _, err := net.SplitHostPort(req.RemoteAddr)
+ if err != nil {
+ host = req.RemoteAddr // assume no port, I guess?
+ }
+ addr, err := netip.ParseAddr(host)
+ if err != nil {
+ return host, true // not an IP address
+ }
+ // extract the bits from the end of the placeholder (start after "/") then split on ","
+ bitsBoth := key[strings.Index(key, "/")+1:]
+ ipv4BitsStr, ipv6BitsStr, cutOK := strings.Cut(bitsBoth, ",")
+ bitsStr := ipv4BitsStr
+ if addr.Is6() && cutOK {
+ bitsStr = ipv6BitsStr
+ }
+ // convert to integer then compute prefix
+ bits, err := strconv.Atoi(bitsStr)
+ if err != nil {
+ return "", true
+ }
+ prefix, err := addr.Prefix(bits)
+ if err != nil {
+ return "", true
+ }
+ return prefix.String(), true
+ }
+
// hostname labels
if strings.HasPrefix(key, reqHostLabelsReplPrefix) {
idxStr := key[len(reqHostLabelsReplPrefix):]
diff --git a/modules/caddyhttp/replacer_test.go b/modules/caddyhttp/replacer_test.go
index 18253d3..44c6f2a 100644
--- a/modules/caddyhttp/replacer_test.go
+++ b/modules/caddyhttp/replacer_test.go
@@ -32,7 +32,7 @@ func TestHTTPVarReplacement(t *testing.T) {
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
req = req.WithContext(ctx)
req.Host = "example.com:80"
- req.RemoteAddr = "localhost:1234"
+ req.RemoteAddr = "192.168.159.32:1234"
clientCert := []byte(`-----BEGIN CERTIFICATE-----
MIIB9jCCAV+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1DYWRk
@@ -61,7 +61,7 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
req.TLS = &tls.ConnectionState{
Version: tls.VersionTLS13,
HandshakeComplete: true,
- ServerName: "foo.com",
+ ServerName: "example.com",
CipherSuite: tls.TLS_AES_256_GCM_SHA384,
PeerCertificates: []*x509.Certificate{cert},
NegotiatedProtocol: "h2",
@@ -97,7 +97,19 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
},
{
get: "http.request.remote.host",
- expect: "localhost",
+ expect: "192.168.159.32",
+ },
+ {
+ get: "http.request.remote.host/24",
+ expect: "192.168.159.0/24",
+ },
+ {
+ get: "http.request.remote.host/24,32",
+ expect: "192.168.159.0/24",
+ },
+ {
+ get: "http.request.remote.host/999",
+ expect: "",
},
{
get: "http.request.remote.port",
@@ -146,7 +158,7 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
},
{
get: "http.request.tls.server_name",
- expect: "foo.com",
+ expect: "example.com",
},
{
get: "http.request.tls.version",