summaryrefslogtreecommitdiff
path: root/modules/caddytls/matchers.go
diff options
context:
space:
mode:
Diffstat (limited to 'modules/caddytls/matchers.go')
-rw-r--r--modules/caddytls/matchers.go104
1 files changed, 102 insertions, 2 deletions
diff --git a/modules/caddytls/matchers.go b/modules/caddytls/matchers.go
index 50da609..aee0e72 100644
--- a/modules/caddytls/matchers.go
+++ b/modules/caddytls/matchers.go
@@ -16,13 +16,18 @@ package caddytls
import (
"crypto/tls"
+ "fmt"
+ "net"
+ "strings"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/certmagic"
+ "go.uber.org/zap"
)
func init() {
caddy.RegisterModule(MatchServerName{})
+ caddy.RegisterModule(MatchRemoteIP{})
}
// MatchServerName matches based on SNI. Names in
@@ -48,5 +53,100 @@ func (m MatchServerName) Match(hello *tls.ClientHelloInfo) bool {
return false
}
-// Interface guard
-var _ ConnectionMatcher = (*MatchServerName)(nil)
+// MatchRemoteIP matches based on the remote IP of the
+// connection. Specific IPs or CIDR ranges can be specified.
+//
+// Note that IPs can sometimes be spoofed, so do not rely
+// on this as a replacement for actual authentication.
+type MatchRemoteIP struct {
+ // The IPs or CIDR ranges to match.
+ Ranges []string `json:"ranges,omitempty"`
+
+ // The IPs or CIDR ranges to *NOT* match.
+ NotRanges []string `json:"not_ranges,omitempty"`
+
+ cidrs []*net.IPNet
+ notCidrs []*net.IPNet
+ logger *zap.Logger
+}
+
+// CaddyModule returns the Caddy module information.
+func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
+ return caddy.ModuleInfo{
+ ID: "tls.handshake_match.remote_ip",
+ New: func() caddy.Module { return new(MatchRemoteIP) },
+ }
+}
+
+// Provision parses m's IP ranges, either from IP or CIDR expressions.
+func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
+ m.logger = ctx.Logger(m)
+ for _, str := range m.Ranges {
+ cidrs, err := m.parseIPRange(str)
+ if err != nil {
+ return err
+ }
+ m.cidrs = cidrs
+ }
+ for _, str := range m.NotRanges {
+ cidrs, err := m.parseIPRange(str)
+ if err != nil {
+ return err
+ }
+ m.notCidrs = cidrs
+ }
+ return nil
+}
+
+// Match matches hello based on the connection's remote IP.
+func (m MatchRemoteIP) Match(hello *tls.ClientHelloInfo) bool {
+ remoteAddr := hello.Conn.RemoteAddr().String()
+ ipStr, _, err := net.SplitHostPort(remoteAddr)
+ if err != nil {
+ ipStr = remoteAddr // weird; maybe no port?
+ }
+ ip := net.ParseIP(ipStr)
+ if ip == nil {
+ m.logger.Error("invalid client IP addresss", zap.String("ip", ipStr))
+ return false
+ }
+ return (len(m.cidrs) == 0 || m.matches(ip, m.cidrs)) &&
+ (len(m.notCidrs) == 0 || !m.matches(ip, m.notCidrs))
+}
+
+func (MatchRemoteIP) parseIPRange(str string) ([]*net.IPNet, error) {
+ var cidrs []*net.IPNet
+ if strings.Contains(str, "/") {
+ _, ipNet, err := net.ParseCIDR(str)
+ if err != nil {
+ return nil, fmt.Errorf("parsing CIDR expression: %v", err)
+ }
+ cidrs = append(cidrs, ipNet)
+ } else {
+ ip := net.ParseIP(str)
+ if ip == nil {
+ return nil, fmt.Errorf("invalid IP address: %s", str)
+ }
+ mask := len(ip) * 8
+ cidrs = append(cidrs, &net.IPNet{
+ IP: ip,
+ Mask: net.CIDRMask(mask, mask),
+ })
+ }
+ return cidrs, nil
+}
+
+func (MatchRemoteIP) matches(ip net.IP, ranges []*net.IPNet) bool {
+ for _, ipRange := range ranges {
+ if ipRange.Contains(ip) {
+ return true
+ }
+ }
+ return false
+}
+
+// Interface guards
+var (
+ _ ConnectionMatcher = (*MatchServerName)(nil)
+ _ ConnectionMatcher = (*MatchRemoteIP)(nil)
+)