Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remote_allow_ranges: allow inside CIDR specific remote_allow_lists #540

Merged
merged 3 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion allow_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ import (
type AllowList struct {
// The values of this cidrTree are `bool`, signifying allow/deny
cidrTree *CIDR6Tree
}

type RemoteAllowList struct {
AllowList *AllowList

// Inside Range Specific, keys of this tree are inside CIDRs and values
// are *AllowList
insideAllowLists *CIDR6Tree
}

type LocalAllowList struct {
AllowList *AllowList

// To avoid ambiguity, all rules must be true, or all rules must be false.
nameRules []AllowListNameRule
Expand Down Expand Up @@ -61,7 +73,14 @@ func (al *AllowList) AllowIpV6(hi, lo uint64) bool {
}
}

func (al *AllowList) AllowName(name string) bool {
func (al *LocalAllowList) Allow(ip net.IP) bool {
if al == nil {
return true
}
return al.AllowList.Allow(ip)
}

func (al *LocalAllowList) AllowName(name string) bool {
if al == nil || len(al.nameRules) == 0 {
return true
}
Expand All @@ -75,3 +94,47 @@ func (al *AllowList) AllowName(name string) bool {
// If no rules match, return the default, which is the inverse of the rules
return !al.nameRules[0].Allow
}

func (al *RemoteAllowList) AllowUnknownVpnIp(ip net.IP) bool {
if al == nil {
return true
}
return al.AllowList.Allow(ip)
}

func (al *RemoteAllowList) Allow(vpnIp uint32, ip net.IP) bool {
if !al.getInsideAllowList(vpnIp).Allow(ip) {
return false
}
return al.AllowList.Allow(ip)
}

func (al *RemoteAllowList) AllowIpV4(vpnIp uint32, ip uint32) bool {
if al == nil {
return true
}
if !al.getInsideAllowList(vpnIp).AllowIpV4(ip) {
return false
}
return al.AllowList.AllowIpV4(ip)
}

func (al *RemoteAllowList) AllowIpV6(vpnIp uint32, hi, lo uint64) bool {
if al == nil {
return true
}
if !al.getInsideAllowList(vpnIp).AllowIpV6(hi, lo) {
return false
}
return al.AllowList.AllowIpV6(hi, lo)
}

func (al *RemoteAllowList) getInsideAllowList(vpnIp uint32) *AllowList {
if al.insideAllowLists != nil {
inside := al.insideAllowLists.MostSpecificContainsIpV4(vpnIp)
if inside != nil {
return inside.(*AllowList)
}
}
return nil
}
8 changes: 4 additions & 4 deletions allow_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ func TestAllowList_Allow(t *testing.T) {
assert.Equal(t, false, al.Allow(net.ParseIP("::2")))
}

func TestAllowList_AllowName(t *testing.T) {
assert.Equal(t, true, ((*AllowList)(nil)).AllowName("docker0"))
func TestLocalAllowList_AllowName(t *testing.T) {
assert.Equal(t, true, ((*LocalAllowList)(nil)).AllowName("docker0"))

rules := []AllowListNameRule{
{Name: regexp.MustCompile("^docker.*$"), Allow: false},
{Name: regexp.MustCompile("^tun.*$"), Allow: false},
}
al := &AllowList{nameRules: rules}
al := &LocalAllowList{nameRules: rules}

assert.Equal(t, false, al.AllowName("docker0"))
assert.Equal(t, false, al.AllowName("tun0"))
Expand All @@ -48,7 +48,7 @@ func TestAllowList_AllowName(t *testing.T) {
{Name: regexp.MustCompile("^eth.*$"), Allow: true},
{Name: regexp.MustCompile("^ens.*$"), Allow: true},
}
al = &AllowList{nameRules: rules}
al = &LocalAllowList{nameRules: rules}

assert.Equal(t, false, al.AllowName("docker0"))
assert.Equal(t, true, al.AllowName("eth0"))
Expand Down
99 changes: 85 additions & 14 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,19 +226,94 @@ func (c *Config) GetDuration(k string, d time.Duration) time.Duration {
return v
}

func (c *Config) GetAllowList(k string, allowInterfaces bool) (*AllowList, error) {
func (c *Config) GetLocalAllowList(k string) (*LocalAllowList, error) {
var nameRules []AllowListNameRule
handleKey := func(key string, value interface{}) (bool, error) {
if key == "interfaces" {
var err error
nameRules, err = c.getAllowListInterfaces(k, value)
if err != nil {
return false, err
}

return true, nil
}
return false, nil
}

al, err := c.GetAllowList(k, handleKey)
if err != nil {
return nil, err
}
return &LocalAllowList{AllowList: al, nameRules: nameRules}, nil
}

func (c *Config) GetRemoteAllowList(k, rangesKey string) (*RemoteAllowList, error) {
al, err := c.GetAllowList(k, nil)
if err != nil {
return nil, err
}
remoteAllowRanges, err := c.getRemoteAllowRanges(rangesKey)
if err != nil {
return nil, err
}
return &RemoteAllowList{AllowList: al, insideAllowLists: remoteAllowRanges}, nil
}

func (c *Config) getRemoteAllowRanges(k string) (*CIDR6Tree, error) {
value := c.Get(k)
if value == nil {
return nil, nil
}

remoteAllowRanges := NewCIDR6Tree()

rawMap, ok := value.(map[interface{}]interface{})
if !ok {
return nil, fmt.Errorf("config `%s` has invalid type: %T", k, value)
}
for rawKey, rawValue := range rawMap {
rawCIDR, ok := rawKey.(string)
if !ok {
return nil, fmt.Errorf("config `%s` has invalid key (type %T): %v", k, rawKey, rawKey)
}

allowList, err := c.getAllowList(fmt.Sprintf("%s.%s", k, rawCIDR), rawValue, nil)
if err != nil {
return nil, err
}

_, cidr, err := net.ParseCIDR(rawCIDR)
if err != nil {
return nil, fmt.Errorf("config `%s` has invalid CIDR: %s", k, rawCIDR)
}

remoteAllowRanges.AddCIDR(cidr, allowList)
}

return remoteAllowRanges, nil
}

// If the handleKey func returns true, the rest of the parsing is skipped
// for this key. This allows parsing of special values like `interfaces`.
func (c *Config) GetAllowList(k string, handleKey func(key string, value interface{}) (bool, error)) (*AllowList, error) {
r := c.Get(k)
if r == nil {
return nil, nil
}

rawMap, ok := r.(map[interface{}]interface{})
return c.getAllowList(k, r, handleKey)
}

// If the handleKey func returns true, the rest of the parsing is skipped
// for this key. This allows parsing of special values like `interfaces`.
func (c *Config) getAllowList(k string, raw interface{}, handleKey func(key string, value interface{}) (bool, error)) (*AllowList, error) {
rawMap, ok := raw.(map[interface{}]interface{})
if !ok {
return nil, fmt.Errorf("config `%s` has invalid type: %T", k, r)
return nil, fmt.Errorf("config `%s` has invalid type: %T", k, raw)
}

tree := NewCIDR6Tree()
var nameRules []AllowListNameRule

// Keep track of the rules we have added for both ipv4 and ipv6
type allowListRules struct {
Expand All @@ -256,18 +331,14 @@ func (c *Config) GetAllowList(k string, allowInterfaces bool) (*AllowList, error
return nil, fmt.Errorf("config `%s` has invalid key (type %T): %v", k, rawKey, rawKey)
}

// Special rule for interface names
if rawCIDR == "interfaces" {
if !allowInterfaces {
return nil, fmt.Errorf("config `%s` does not support `interfaces`", k)
}
var err error
nameRules, err = c.getAllowListInterfaces(k, rawValue)
if handleKey != nil {
handled, err := handleKey(rawCIDR, rawValue)
if err != nil {
return nil, err
}

continue
if handled {
continue
}
}

value, ok := rawValue.(bool)
Expand Down Expand Up @@ -325,7 +396,7 @@ func (c *Config) GetAllowList(k string, allowInterfaces bool) (*AllowList, error
}
}

return &AllowList{cidrTree: tree, nameRules: nameRules}, nil
return &AllowList{cidrTree: tree}, nil
}

func (c *Config) getAllowListInterfaces(k string, v interface{}) ([]AllowListNameRule, error) {
Expand Down
28 changes: 10 additions & 18 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,21 +97,21 @@ func TestConfig_GetAllowList(t *testing.T) {
c.Settings["allowlist"] = map[interface{}]interface{}{
"192.168.0.0": true,
}
r, err := c.GetAllowList("allowlist", false)
r, err := c.GetAllowList("allowlist", nil)
assert.EqualError(t, err, "config `allowlist` has invalid CIDR: 192.168.0.0")
assert.Nil(t, r)

c.Settings["allowlist"] = map[interface{}]interface{}{
"192.168.0.0/16": "abc",
}
r, err = c.GetAllowList("allowlist", false)
r, err = c.GetAllowList("allowlist", nil)
assert.EqualError(t, err, "config `allowlist` has invalid value (type string): abc")

c.Settings["allowlist"] = map[interface{}]interface{}{
"192.168.0.0/16": true,
"10.0.0.0/8": false,
}
r, err = c.GetAllowList("allowlist", false)
r, err = c.GetAllowList("allowlist", nil)
assert.EqualError(t, err, "config `allowlist` contains both true and false rules, but no default set for 0.0.0.0/0")

c.Settings["allowlist"] = map[interface{}]interface{}{
Expand All @@ -121,15 +121,15 @@ func TestConfig_GetAllowList(t *testing.T) {
"fd00::/8": true,
"fd00:fd00::/16": false,
}
r, err = c.GetAllowList("allowlist", false)
r, err = c.GetAllowList("allowlist", nil)
assert.EqualError(t, err, "config `allowlist` contains both true and false rules, but no default set for ::/0")

c.Settings["allowlist"] = map[interface{}]interface{}{
"0.0.0.0/0": true,
"10.0.0.0/8": false,
"10.42.42.0/24": true,
}
r, err = c.GetAllowList("allowlist", false)
r, err = c.GetAllowList("allowlist", nil)
if assert.NoError(t, err) {
assert.NotNil(t, r)
}
Expand All @@ -142,27 +142,19 @@ func TestConfig_GetAllowList(t *testing.T) {
"fd00::/8": true,
"fd00:fd00::/16": false,
}
r, err = c.GetAllowList("allowlist", false)
r, err = c.GetAllowList("allowlist", nil)
if assert.NoError(t, err) {
assert.NotNil(t, r)
}

// Test interface names

c.Settings["allowlist"] = map[interface{}]interface{}{
"interfaces": map[interface{}]interface{}{
`docker.*`: false,
},
}
r, err = c.GetAllowList("allowlist", false)
assert.EqualError(t, err, "config `allowlist` does not support `interfaces`")

c.Settings["allowlist"] = map[interface{}]interface{}{
"interfaces": map[interface{}]interface{}{
`docker.*`: "foo",
},
}
r, err = c.GetAllowList("allowlist", true)
lr, err := c.GetLocalAllowList("allowlist")
assert.EqualError(t, err, "config `allowlist.interfaces` has invalid value (type string): foo")

c.Settings["allowlist"] = map[interface{}]interface{}{
Expand All @@ -171,17 +163,17 @@ func TestConfig_GetAllowList(t *testing.T) {
`eth.*`: true,
},
}
r, err = c.GetAllowList("allowlist", true)
lr, err = c.GetLocalAllowList("allowlist")
assert.EqualError(t, err, "config `allowlist.interfaces` values must all be the same true/false value")

c.Settings["allowlist"] = map[interface{}]interface{}{
"interfaces": map[interface{}]interface{}{
`docker.*`: false,
},
}
r, err = c.GetAllowList("allowlist", true)
lr, err = c.GetLocalAllowList("allowlist")
if assert.NoError(t, err) {
assert.NotNil(t, r)
assert.NotNil(t, lr)
}
}

Expand Down
8 changes: 8 additions & 0 deletions examples/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ lighthouse:
#"10.0.0.0/8": false
#"10.42.42.0/24": true

# EXPERIMENTAL: This option my change or disappear in the future.
# Optionally allows the definition of remote_allow_list blocks
# specific to an inside VPN IP CIDR.
#remote_allow_ranges:
# This rule would only allow only private IPs for this VPN range
#"10.42.42.0/24":
#"192.168.0.0/16": true

# local_allow_list allows you to filter which local IP addresses we advertise
# to the lighthouses. This uses the same logic as `remote_allow_list`, but
# additionally, you can specify an `interfaces` map of regular expressions
Expand Down
3 changes: 2 additions & 1 deletion handshake.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const (
)

func HandleIncomingHandshake(f *Interface, addr *udpAddr, packet []byte, h *Header, hostinfo *HostInfo) {
if !f.lightHouse.remoteAllowList.Allow(addr.IP) {
// First remote allow list check before we know the vpnIp
if !f.lightHouse.remoteAllowList.AllowUnknownVpnIp(addr.IP) {
f.l.WithField("udpAddr", addr).Debug("lighthouse.remote_allow_list denied incoming handshake")
return
}
Expand Down
10 changes: 10 additions & 0 deletions handshake_ix.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ func ixHandshakeStage1(f *Interface, addr *udpAddr, packet []byte, h *Header) {
return
}

if !f.lightHouse.remoteAllowList.Allow(vpnIP, addr.IP) {
f.l.WithField("vpnIp", IntIp(vpnIP)).WithField("udpAddr", addr).Debug("lighthouse.remote_allow_list denied incoming handshake")
return
}

myIndex, err := generateIndex(f.l)
if err != nil {
f.l.WithError(err).WithField("vpnIp", IntIp(vpnIP)).WithField("udpAddr", addr).
Expand Down Expand Up @@ -299,6 +304,11 @@ func ixHandshakeStage2(f *Interface, addr *udpAddr, hostinfo *HostInfo, packet [
hostinfo.Lock()
defer hostinfo.Unlock()

if !f.lightHouse.remoteAllowList.Allow(hostinfo.hostId, addr.IP) {
f.l.WithField("vpnIp", IntIp(hostinfo.hostId)).WithField("udpAddr", addr).Debug("lighthouse.remote_allow_list denied incoming handshake")
return false
}

ci := hostinfo.ConnectionState
if ci.ready {
f.l.WithField("vpnIp", IntIp(hostinfo.hostId)).WithField("udpAddr", addr).
Expand Down
2 changes: 1 addition & 1 deletion hostmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ func (i *HostInfo) Probes() []*Probe {

// Utility functions

func localIps(l *logrus.Logger, allowList *AllowList) *[]net.IP {
func localIps(l *logrus.Logger, allowList *LocalAllowList) *[]net.IP {
//FIXME: This function is pretty garbage
var ips []net.IP
ifaces, _ := net.Interfaces()
Expand Down
Loading