Browse Source
To use cloudflared as a socks proxy, add an ingress on the server side with your desired rules. Rules are matched in the order they are added. If there are no rules, it is an implicit allow. If there are rules, but no rule matches match, the connection is denied. ingress: - hostname: socks.example.com service: socks-proxy originRequest: ipRules: - prefix: 1.1.1.1/24 ports: [80, 443] allow: true - prefix: 0.0.0.0/0 allow: false On the client, run using tcp mode: cloudflared access tcp --hostname socks.example.com --url 127.0.0.1:8080 Set your socks proxy as 127.0.0.1:8080 and you will now be proxying all connections to the remote machine.pull/171/merge

12 changed files with 419 additions and 7 deletions
@ -0,0 +1,101 @@
|
||||
package ipaccess |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net" |
||||
"sort" |
||||
) |
||||
|
||||
type Policy struct { |
||||
defaultAllow bool |
||||
rules []Rule |
||||
} |
||||
|
||||
type Rule struct { |
||||
ipNet *net.IPNet |
||||
ports []int |
||||
allow bool |
||||
} |
||||
|
||||
func NewPolicy(defaultAllow bool, rules []Rule) (*Policy, error) { |
||||
for _, rule := range rules { |
||||
if err := rule.Validate(); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
policy := Policy{ |
||||
defaultAllow: defaultAllow, |
||||
rules: rules, |
||||
} |
||||
|
||||
return &policy, nil |
||||
} |
||||
|
||||
func NewRuleByCIDR(prefix *string, ports []int, allow bool) (Rule, error) { |
||||
if prefix == nil || len(*prefix) == 0 { |
||||
return Rule{}, fmt.Errorf("no prefix provided") |
||||
} |
||||
|
||||
_, ipnet, err := net.ParseCIDR(*prefix) |
||||
if err != nil { |
||||
return Rule{}, fmt.Errorf("unable to parse cidr: %s", *prefix) |
||||
} |
||||
|
||||
return NewRule(ipnet, ports, allow) |
||||
} |
||||
|
||||
func NewRule(ipnet *net.IPNet, ports []int, allow bool) (Rule, error) { |
||||
rule := Rule{ |
||||
ipNet: ipnet, |
||||
ports: ports, |
||||
allow: allow, |
||||
} |
||||
return rule, rule.Validate() |
||||
} |
||||
|
||||
func (r *Rule) Validate() error { |
||||
if r.ipNet == nil { |
||||
return fmt.Errorf("no ipnet set on the rule") |
||||
} |
||||
|
||||
if len(r.ports) > 0 { |
||||
sort.Ints(r.ports) |
||||
for _, port := range r.ports { |
||||
if port < 1 || port > 65535 { |
||||
return fmt.Errorf("invalid port %d, needs to be between 1 and 65535", port) |
||||
} |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (h *Policy) Allowed(ip net.IP, port int) (bool, *Rule) { |
||||
if len(h.rules) == 0 { |
||||
return h.defaultAllow, nil |
||||
} |
||||
|
||||
for _, rule := range h.rules { |
||||
if rule.ipNet.Contains(ip) { |
||||
if len(rule.ports) == 0 { |
||||
return rule.allow, &rule |
||||
} else if pos := sort.SearchInts(rule.ports, port); pos < len(rule.ports) && rule.ports[pos] == port { |
||||
return rule.allow, &rule |
||||
} |
||||
} |
||||
} |
||||
|
||||
return h.defaultAllow, nil |
||||
} |
||||
|
||||
func (ipr *Rule) String() string { |
||||
return fmt.Sprintf("prefix:%s/port:%s/allow:%t", ipr.ipNet, ipr.PortsString(), ipr.allow) |
||||
} |
||||
|
||||
func (ipr *Rule) PortsString() string { |
||||
if len(ipr.ports) > 0 { |
||||
return fmt.Sprint(ipr.ports) |
||||
} |
||||
return "all" |
||||
} |
@ -0,0 +1,107 @@
|
||||
package ipaccess |
||||
|
||||
import ( |
||||
"bytes" |
||||
"net" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestRuleCreation(t *testing.T) { |
||||
_, ipnet, _ := net.ParseCIDR("1.1.1.1/24") |
||||
|
||||
_, err := NewRule(nil, []int{80}, false) |
||||
assert.Error(t, err, "expected error as no ipnet provided") |
||||
|
||||
_, err = NewRule(ipnet, []int{65536, 80}, false) |
||||
assert.Error(t, err, "expected error as port higher than 65535") |
||||
|
||||
_, err = NewRule(ipnet, []int{80, -1}, false) |
||||
assert.Error(t, err, "expected error as port less than 0") |
||||
|
||||
rule, err := NewRule(ipnet, []int{443, 80}, false) |
||||
assert.NoError(t, err) |
||||
assert.True(t, ipnet.IP.Equal(rule.ipNet.IP) && bytes.Compare(ipnet.Mask, rule.ipNet.Mask) == 0, "ipnet expected to be %+v, got: %+v", ipnet, rule.ipNet) |
||||
assert.True(t, len(rule.ports) == 2 && rule.ports[0] == 80 && rule.ports[1] == 443, "expected ports to be sorted") |
||||
} |
||||
|
||||
func TestRuleCreationByCIDR(t *testing.T) { |
||||
var cidr *string |
||||
_, err := NewRuleByCIDR(cidr, []int{80}, false) |
||||
assert.Error(t, err, "expected error as cidr is nil") |
||||
|
||||
badCidr := "1.1.1.1" |
||||
cidr = &badCidr |
||||
_, err = NewRuleByCIDR(cidr, []int{80}, false) |
||||
assert.Error(t, err, "expected error as the cidr is bad") |
||||
|
||||
goodCidr := "1.1.1.1/24" |
||||
_, ipnet, _ := net.ParseCIDR("1.1.1.0/24") |
||||
cidr = &goodCidr |
||||
rule, err := NewRuleByCIDR(cidr, []int{80}, false) |
||||
assert.NoError(t, err) |
||||
assert.True(t, ipnet.IP.Equal(rule.ipNet.IP) && bytes.Compare(ipnet.Mask, rule.ipNet.Mask) == 0, "ipnet expected to be %+v, got: %+v", ipnet, rule.ipNet) |
||||
} |
||||
|
||||
func TestRulesNoRules(t *testing.T) { |
||||
ip, _, _ := net.ParseCIDR("1.2.3.4/24") |
||||
|
||||
policy, _ := NewPolicy(true, []Rule{}) |
||||
|
||||
allowed, rule := policy.Allowed(ip, 80) |
||||
assert.True(t, allowed, "expected to be allowed as no rules and default allow") |
||||
assert.Nil(t, rule, "expected to be nil as no rules") |
||||
|
||||
policy, _ = NewPolicy(false, []Rule{}) |
||||
|
||||
allowed, rule = policy.Allowed(ip, 80) |
||||
assert.False(t, allowed, "expected to be denied as no rules and default deny") |
||||
assert.Nil(t, rule, "expected to be nil as no rules") |
||||
} |
||||
|
||||
func TestRulesMatchIPAndPort(t *testing.T) { |
||||
ip1, ipnet1, _ := net.ParseCIDR("1.2.3.4/24") |
||||
ip2, _, _ := net.ParseCIDR("2.3.4.5/24") |
||||
|
||||
rule1, _ := NewRule(ipnet1, []int{80, 443}, true) |
||||
rules := []Rule{ |
||||
rule1, |
||||
} |
||||
|
||||
policy, _ := NewPolicy(false, rules) |
||||
|
||||
allowed, rule := policy.Allowed(ip1, 80) |
||||
assert.True(t, allowed, "expected to be allowed as matching rule") |
||||
assert.True(t, rule.ipNet == ipnet1, "expected to match ipnet1") |
||||
|
||||
allowed, rule = policy.Allowed(ip2, 80) |
||||
assert.False(t, allowed, "expected to be denied as no matching rule") |
||||
assert.Nil(t, rule, "expected to be nil") |
||||
} |
||||
|
||||
func TestRulesMatchIPAndPort2(t *testing.T) { |
||||
ip1, ipnet1, _ := net.ParseCIDR("1.2.3.4/24") |
||||
ip2, ipnet2, _ := net.ParseCIDR("2.3.4.5/24") |
||||
|
||||
rule1, _ := NewRule(ipnet1, []int{53, 80}, false) |
||||
rule2, _ := NewRule(ipnet2, []int{53, 80}, true) |
||||
rules := []Rule{ |
||||
rule1, |
||||
rule2, |
||||
} |
||||
|
||||
policy, _ := NewPolicy(false, rules) |
||||
|
||||
allowed, rule := policy.Allowed(ip1, 80) |
||||
assert.False(t, allowed, "expected to be denied as matching rule") |
||||
assert.True(t, rule.ipNet == ipnet1, "expected to match ipnet1") |
||||
|
||||
allowed, rule = policy.Allowed(ip2, 80) |
||||
assert.True(t, allowed, "expected to be allowed as matching rule") |
||||
assert.True(t, rule.ipNet == ipnet2, "expected to match ipnet1") |
||||
|
||||
allowed, rule = policy.Allowed(ip2, 81) |
||||
assert.False(t, allowed, "expected to be denied as no matching rule") |
||||
assert.Nil(t, rule, "expected to be nil") |
||||
} |
Loading…
Reference in new issue