mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 14:13:48 +03:00
connection policy: add local_ip
matcher (#6074)
* connection policy: add `local_ip` Co-authored-by: Matt Holt <mholt@users.noreply.github.com> --------- Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
This commit is contained in:
parent
b40cacf5ce
commit
26748d06b4
2 changed files with 150 additions and 0 deletions
|
@ -30,6 +30,7 @@ import (
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(MatchServerName{})
|
caddy.RegisterModule(MatchServerName{})
|
||||||
caddy.RegisterModule(MatchRemoteIP{})
|
caddy.RegisterModule(MatchRemoteIP{})
|
||||||
|
caddy.RegisterModule(MatchLocalIP{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchServerName matches based on SNI. Names in
|
// MatchServerName matches based on SNI. Names in
|
||||||
|
@ -144,8 +145,85 @@ func (MatchRemoteIP) matches(ip netip.Addr, ranges []netip.Prefix) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MatchLocalIP matches based on the IP address of the interface
|
||||||
|
// receiving the connection. Specific IPs or CIDR ranges can be specified.
|
||||||
|
type MatchLocalIP struct {
|
||||||
|
// The IPs or CIDR ranges to match.
|
||||||
|
Ranges []string `json:"ranges,omitempty"`
|
||||||
|
|
||||||
|
cidrs []netip.Prefix
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MatchLocalIP) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
ID: "tls.handshake_match.local_ip",
|
||||||
|
New: func() caddy.Module { return new(MatchLocalIP) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision parses m's IP ranges, either from IP or CIDR expressions.
|
||||||
|
func (m *MatchLocalIP) Provision(ctx caddy.Context) error {
|
||||||
|
m.logger = ctx.Logger()
|
||||||
|
for _, str := range m.Ranges {
|
||||||
|
cidrs, err := m.parseIPRange(str)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.cidrs = append(m.cidrs, cidrs...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match matches hello based on the connection's remote IP.
|
||||||
|
func (m MatchLocalIP) Match(hello *tls.ClientHelloInfo) bool {
|
||||||
|
localAddr := hello.Conn.LocalAddr().String()
|
||||||
|
ipStr, _, err := net.SplitHostPort(localAddr)
|
||||||
|
if err != nil {
|
||||||
|
ipStr = localAddr // weird; maybe no port?
|
||||||
|
}
|
||||||
|
ipAddr, err := netip.ParseAddr(ipStr)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("invalid local IP addresss", zap.String("ip", ipStr))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (len(m.cidrs) == 0 || m.matches(ipAddr, m.cidrs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MatchLocalIP) parseIPRange(str string) ([]netip.Prefix, error) {
|
||||||
|
var cidrs []netip.Prefix
|
||||||
|
if strings.Contains(str, "/") {
|
||||||
|
ipNet, err := netip.ParsePrefix(str)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing CIDR expression: %v", err)
|
||||||
|
}
|
||||||
|
cidrs = append(cidrs, ipNet)
|
||||||
|
} else {
|
||||||
|
ipAddr, err := netip.ParseAddr(str)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid IP address: '%s': %v", str, err)
|
||||||
|
}
|
||||||
|
ip := netip.PrefixFrom(ipAddr, ipAddr.BitLen())
|
||||||
|
cidrs = append(cidrs, ip)
|
||||||
|
}
|
||||||
|
return cidrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MatchLocalIP) matches(ip netip.Addr, ranges []netip.Prefix) bool {
|
||||||
|
for _, ipRange := range ranges {
|
||||||
|
if ipRange.Contains(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ ConnectionMatcher = (*MatchServerName)(nil)
|
_ ConnectionMatcher = (*MatchServerName)(nil)
|
||||||
_ ConnectionMatcher = (*MatchRemoteIP)(nil)
|
_ ConnectionMatcher = (*MatchRemoteIP)(nil)
|
||||||
|
|
||||||
|
_ caddy.Provisioner = (*MatchLocalIP)(nil)
|
||||||
|
_ ConnectionMatcher = (*MatchLocalIP)(nil)
|
||||||
)
|
)
|
||||||
|
|
|
@ -165,12 +165,84 @@ func TestRemoteIPMatcher(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLocalIPMatcher(t *testing.T) {
|
||||||
|
ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
for i, tc := range []struct {
|
||||||
|
ranges []string
|
||||||
|
input string
|
||||||
|
expect bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
ranges: []string{"127.0.0.1"},
|
||||||
|
input: "127.0.0.1:12345",
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ranges: []string{"127.0.0.1"},
|
||||||
|
input: "127.0.0.2:12345",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ranges: []string{"127.0.0.1/16"},
|
||||||
|
input: "127.0.1.23:12345",
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ranges: []string{"127.0.0.1", "192.168.1.105"},
|
||||||
|
input: "192.168.1.105:12345",
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "127.0.0.1:12345",
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ranges: []string{"127.0.0.1"},
|
||||||
|
input: "127.0.0.1:12345",
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ranges: []string{"127.0.0.2"},
|
||||||
|
input: "127.0.0.3:12345",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ranges: []string{"127.0.0.2"},
|
||||||
|
input: "127.0.0.2",
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ranges: []string{"127.0.0.2"},
|
||||||
|
input: "127.0.0.300",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
matcher := MatchLocalIP{Ranges: tc.ranges}
|
||||||
|
err := matcher.Provision(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test %d: Provision failed: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := testAddr(tc.input)
|
||||||
|
chi := &tls.ClientHelloInfo{Conn: testConn{addr: addr}}
|
||||||
|
|
||||||
|
actual := matcher.Match(chi)
|
||||||
|
if actual != tc.expect {
|
||||||
|
t.Errorf("Test %d: Expected %t but got %t (input=%s ranges=%v)",
|
||||||
|
i, tc.expect, actual, tc.input, tc.ranges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type testConn struct {
|
type testConn struct {
|
||||||
*net.TCPConn
|
*net.TCPConn
|
||||||
addr testAddr
|
addr testAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc testConn) RemoteAddr() net.Addr { return tc.addr }
|
func (tc testConn) RemoteAddr() net.Addr { return tc.addr }
|
||||||
|
func (tc testConn) LocalAddr() net.Addr { return tc.addr }
|
||||||
|
|
||||||
type testAddr string
|
type testAddr string
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue