Add lighthouse.{remoteAllowList,localAllowList} (#217)
These settings make it possible to blacklist / whitelist IP addresses
that are used for remote connections.
`lighthouse.remoteAllowList` filters which remote IPs are allow when
fetching from the lighthouse (or, if you are the lighthouse, which IPs
you store and forward to querying hosts). By default, any remote IPs are
allowed. You can provide CIDRs here with `true` to allow and `false` to
deny. The most specific CIDR rule applies to each remote. If all rules
are "allow", the default will be "deny", and vice-versa. If both "allow"
and "deny" rules are present, then you MUST set a rule for "0.0.0.0/0"
as the default.
lighthouse:
remoteAllowList:
# Example to block IPs from this subnet from being used for remote IPs.
"172.16.0.0/12": false
# A more complicated example, allow public IPs but only private IPs from a specific subnet
"0.0.0.0/0": true
"10.0.0.0/8": false
"10.42.42.0/24": true
`lighthouse.localAllowList` has the same logic as above, but it applies
to the local addresses we advertise to the lighthouse. Additionally, you
can specify an `interfaces` map of regular expressions to match against
interface names. The regexp must match the entire name. All interface
rules must be either true or false (and the default rule will be the
inverse). CIDR rules are matched after interface name rules.
Default is all local IP addresses.
lighthouse:
localAllowList:
# Example to blacklist docker interfaces.
interfaces:
'docker.*': false
# Example to only advertise IPs in this subnet to the lighthouse.
"10.0.0.0/8": true
2020-04-08 13:36:43 -06:00
|
|
|
package nebula
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2024-07-31 09:18:56 -06:00
|
|
|
"net/netip"
|
Add lighthouse.{remoteAllowList,localAllowList} (#217)
These settings make it possible to blacklist / whitelist IP addresses
that are used for remote connections.
`lighthouse.remoteAllowList` filters which remote IPs are allow when
fetching from the lighthouse (or, if you are the lighthouse, which IPs
you store and forward to querying hosts). By default, any remote IPs are
allowed. You can provide CIDRs here with `true` to allow and `false` to
deny. The most specific CIDR rule applies to each remote. If all rules
are "allow", the default will be "deny", and vice-versa. If both "allow"
and "deny" rules are present, then you MUST set a rule for "0.0.0.0/0"
as the default.
lighthouse:
remoteAllowList:
# Example to block IPs from this subnet from being used for remote IPs.
"172.16.0.0/12": false
# A more complicated example, allow public IPs but only private IPs from a specific subnet
"0.0.0.0/0": true
"10.0.0.0/8": false
"10.42.42.0/24": true
`lighthouse.localAllowList` has the same logic as above, but it applies
to the local addresses we advertise to the lighthouse. Additionally, you
can specify an `interfaces` map of regular expressions to match against
interface names. The regexp must match the entire name. All interface
rules must be either true or false (and the default rule will be the
inverse). CIDR rules are matched after interface name rules.
Default is all local IP addresses.
lighthouse:
localAllowList:
# Example to blacklist docker interfaces.
interfaces:
'docker.*': false
# Example to only advertise IPs in this subnet to the lighthouse.
"10.0.0.0/8": true
2020-04-08 13:36:43 -06:00
|
|
|
"regexp"
|
2021-11-03 19:54:04 -06:00
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
"github.com/gaissmai/bart"
|
2021-11-03 19:54:04 -06:00
|
|
|
"github.com/slackhq/nebula/config"
|
Add lighthouse.{remoteAllowList,localAllowList} (#217)
These settings make it possible to blacklist / whitelist IP addresses
that are used for remote connections.
`lighthouse.remoteAllowList` filters which remote IPs are allow when
fetching from the lighthouse (or, if you are the lighthouse, which IPs
you store and forward to querying hosts). By default, any remote IPs are
allowed. You can provide CIDRs here with `true` to allow and `false` to
deny. The most specific CIDR rule applies to each remote. If all rules
are "allow", the default will be "deny", and vice-versa. If both "allow"
and "deny" rules are present, then you MUST set a rule for "0.0.0.0/0"
as the default.
lighthouse:
remoteAllowList:
# Example to block IPs from this subnet from being used for remote IPs.
"172.16.0.0/12": false
# A more complicated example, allow public IPs but only private IPs from a specific subnet
"0.0.0.0/0": true
"10.0.0.0/8": false
"10.42.42.0/24": true
`lighthouse.localAllowList` has the same logic as above, but it applies
to the local addresses we advertise to the lighthouse. Additionally, you
can specify an `interfaces` map of regular expressions to match against
interface names. The regexp must match the entire name. All interface
rules must be either true or false (and the default rule will be the
inverse). CIDR rules are matched after interface name rules.
Default is all local IP addresses.
lighthouse:
localAllowList:
# Example to blacklist docker interfaces.
interfaces:
'docker.*': false
# Example to only advertise IPs in this subnet to the lighthouse.
"10.0.0.0/8": true
2020-04-08 13:36:43 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
type AllowList struct {
|
|
|
|
// The values of this cidrTree are `bool`, signifying allow/deny
|
2024-07-31 09:18:56 -06:00
|
|
|
cidrTree *bart.Table[bool]
|
2021-10-19 08:54:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type RemoteAllowList struct {
|
|
|
|
AllowList *AllowList
|
|
|
|
|
|
|
|
// Inside Range Specific, keys of this tree are inside CIDRs and values
|
|
|
|
// are *AllowList
|
2024-07-31 09:18:56 -06:00
|
|
|
insideAllowLists *bart.Table[*AllowList]
|
2021-10-19 08:54:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type LocalAllowList struct {
|
|
|
|
AllowList *AllowList
|
Add lighthouse.{remoteAllowList,localAllowList} (#217)
These settings make it possible to blacklist / whitelist IP addresses
that are used for remote connections.
`lighthouse.remoteAllowList` filters which remote IPs are allow when
fetching from the lighthouse (or, if you are the lighthouse, which IPs
you store and forward to querying hosts). By default, any remote IPs are
allowed. You can provide CIDRs here with `true` to allow and `false` to
deny. The most specific CIDR rule applies to each remote. If all rules
are "allow", the default will be "deny", and vice-versa. If both "allow"
and "deny" rules are present, then you MUST set a rule for "0.0.0.0/0"
as the default.
lighthouse:
remoteAllowList:
# Example to block IPs from this subnet from being used for remote IPs.
"172.16.0.0/12": false
# A more complicated example, allow public IPs but only private IPs from a specific subnet
"0.0.0.0/0": true
"10.0.0.0/8": false
"10.42.42.0/24": true
`lighthouse.localAllowList` has the same logic as above, but it applies
to the local addresses we advertise to the lighthouse. Additionally, you
can specify an `interfaces` map of regular expressions to match against
interface names. The regexp must match the entire name. All interface
rules must be either true or false (and the default rule will be the
inverse). CIDR rules are matched after interface name rules.
Default is all local IP addresses.
lighthouse:
localAllowList:
# Example to blacklist docker interfaces.
interfaces:
'docker.*': false
# Example to only advertise IPs in this subnet to the lighthouse.
"10.0.0.0/8": true
2020-04-08 13:36:43 -06:00
|
|
|
|
|
|
|
// To avoid ambiguity, all rules must be true, or all rules must be false.
|
|
|
|
nameRules []AllowListNameRule
|
|
|
|
}
|
|
|
|
|
|
|
|
type AllowListNameRule struct {
|
|
|
|
Name *regexp.Regexp
|
|
|
|
Allow bool
|
|
|
|
}
|
|
|
|
|
2021-11-03 19:54:04 -06:00
|
|
|
func NewLocalAllowListFromConfig(c *config.C, k string) (*LocalAllowList, error) {
|
|
|
|
var nameRules []AllowListNameRule
|
|
|
|
handleKey := func(key string, value interface{}) (bool, error) {
|
|
|
|
if key == "interfaces" {
|
|
|
|
var err error
|
|
|
|
nameRules, err = getAllowListInterfaces(k, value)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
al, err := newAllowListFromConfig(c, k, handleKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &LocalAllowList{AllowList: al, nameRules: nameRules}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewRemoteAllowListFromConfig(c *config.C, k, rangesKey string) (*RemoteAllowList, error) {
|
|
|
|
al, err := newAllowListFromConfig(c, k, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
remoteAllowRanges, err := getRemoteAllowRanges(c, rangesKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &RemoteAllowList{AllowList: al, insideAllowLists: 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 newAllowListFromConfig(c *config.C, k string, handleKey func(key string, value interface{}) (bool, error)) (*AllowList, error) {
|
|
|
|
r := c.Get(k)
|
|
|
|
if r == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return newAllowList(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 newAllowList(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, raw)
|
|
|
|
}
|
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
tree := new(bart.Table[bool])
|
2021-11-03 19:54:04 -06:00
|
|
|
|
|
|
|
// Keep track of the rules we have added for both ipv4 and ipv6
|
|
|
|
type allowListRules struct {
|
|
|
|
firstValue bool
|
|
|
|
allValuesMatch bool
|
|
|
|
defaultSet bool
|
|
|
|
allValues bool
|
|
|
|
}
|
|
|
|
|
|
|
|
rules4 := allowListRules{firstValue: true, allValuesMatch: true, defaultSet: false}
|
|
|
|
rules6 := allowListRules{firstValue: true, allValuesMatch: true, defaultSet: false}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
if handleKey != nil {
|
|
|
|
handled, err := handleKey(rawCIDR, rawValue)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if handled {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
value, ok := rawValue.(bool)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("config `%s` has invalid value (type %T): %v", k, rawValue, rawValue)
|
|
|
|
}
|
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
ipNet, err := netip.ParsePrefix(rawCIDR)
|
2021-11-03 19:54:04 -06:00
|
|
|
if err != nil {
|
2024-07-31 09:18:56 -06:00
|
|
|
return nil, fmt.Errorf("config `%s` has invalid CIDR: %s. %w", k, rawCIDR, err)
|
2021-11-03 19:54:04 -06:00
|
|
|
}
|
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
ipNet = netip.PrefixFrom(ipNet.Addr().Unmap(), ipNet.Bits())
|
|
|
|
|
2021-11-03 19:54:04 -06:00
|
|
|
// TODO: should we error on duplicate CIDRs in the config?
|
2024-07-31 09:18:56 -06:00
|
|
|
tree.Insert(ipNet, value)
|
2021-11-03 19:54:04 -06:00
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
maskBits := ipNet.Bits()
|
2021-11-03 19:54:04 -06:00
|
|
|
|
|
|
|
var rules *allowListRules
|
2024-07-31 09:18:56 -06:00
|
|
|
if ipNet.Addr().Is4() {
|
2021-11-03 19:54:04 -06:00
|
|
|
rules = &rules4
|
|
|
|
} else {
|
|
|
|
rules = &rules6
|
|
|
|
}
|
|
|
|
|
|
|
|
if rules.firstValue {
|
|
|
|
rules.allValues = value
|
|
|
|
rules.firstValue = false
|
|
|
|
} else {
|
|
|
|
if value != rules.allValues {
|
|
|
|
rules.allValuesMatch = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if this is 0.0.0.0/0 or ::/0
|
|
|
|
if maskBits == 0 {
|
|
|
|
rules.defaultSet = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !rules4.defaultSet {
|
|
|
|
if rules4.allValuesMatch {
|
2024-07-31 09:18:56 -06:00
|
|
|
tree.Insert(netip.PrefixFrom(netip.IPv4Unspecified(), 0), !rules4.allValues)
|
2021-11-03 19:54:04 -06:00
|
|
|
} else {
|
|
|
|
return nil, fmt.Errorf("config `%s` contains both true and false rules, but no default set for 0.0.0.0/0", k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !rules6.defaultSet {
|
|
|
|
if rules6.allValuesMatch {
|
2024-07-31 09:18:56 -06:00
|
|
|
tree.Insert(netip.PrefixFrom(netip.IPv6Unspecified(), 0), !rules6.allValues)
|
2021-11-03 19:54:04 -06:00
|
|
|
} else {
|
|
|
|
return nil, fmt.Errorf("config `%s` contains both true and false rules, but no default set for ::/0", k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &AllowList{cidrTree: tree}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getAllowListInterfaces(k string, v interface{}) ([]AllowListNameRule, error) {
|
|
|
|
var nameRules []AllowListNameRule
|
|
|
|
|
|
|
|
rawRules, ok := v.(map[interface{}]interface{})
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("config `%s.interfaces` is invalid (type %T): %v", k, v, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
firstEntry := true
|
|
|
|
var allValues bool
|
|
|
|
for rawName, rawAllow := range rawRules {
|
|
|
|
name, ok := rawName.(string)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("config `%s.interfaces` has invalid key (type %T): %v", k, rawName, rawName)
|
|
|
|
}
|
|
|
|
allow, ok := rawAllow.(bool)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("config `%s.interfaces` has invalid value (type %T): %v", k, rawAllow, rawAllow)
|
|
|
|
}
|
|
|
|
|
|
|
|
nameRE, err := regexp.Compile("^" + name + "$")
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("config `%s.interfaces` has invalid key: %s: %v", k, name, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
nameRules = append(nameRules, AllowListNameRule{
|
|
|
|
Name: nameRE,
|
|
|
|
Allow: allow,
|
|
|
|
})
|
|
|
|
|
|
|
|
if firstEntry {
|
|
|
|
allValues = allow
|
|
|
|
firstEntry = false
|
|
|
|
} else {
|
|
|
|
if allow != allValues {
|
|
|
|
return nil, fmt.Errorf("config `%s.interfaces` values must all be the same true/false value", k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nameRules, nil
|
|
|
|
}
|
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
func getRemoteAllowRanges(c *config.C, k string) (*bart.Table[*AllowList], error) {
|
2021-11-03 19:54:04 -06:00
|
|
|
value := c.Get(k)
|
|
|
|
if value == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
remoteAllowRanges := new(bart.Table[*AllowList])
|
2021-11-03 19:54:04 -06:00
|
|
|
|
|
|
|
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 := newAllowList(fmt.Sprintf("%s.%s", k, rawCIDR), rawValue, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
ipNet, err := netip.ParsePrefix(rawCIDR)
|
2021-11-03 19:54:04 -06:00
|
|
|
if err != nil {
|
2024-07-31 09:18:56 -06:00
|
|
|
return nil, fmt.Errorf("config `%s` has invalid CIDR: %s. %w", k, rawCIDR, err)
|
2021-11-03 19:54:04 -06:00
|
|
|
}
|
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
remoteAllowRanges.Insert(netip.PrefixFrom(ipNet.Addr().Unmap(), ipNet.Bits()), allowList)
|
2021-11-03 19:54:04 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return remoteAllowRanges, nil
|
|
|
|
}
|
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
func (al *AllowList) Allow(ip netip.Addr) bool {
|
2021-04-01 09:23:31 -06:00
|
|
|
if al == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
result, _ := al.cidrTree.Lookup(ip)
|
2023-11-02 16:05:08 -06:00
|
|
|
return result
|
2021-04-01 09:23:31 -06:00
|
|
|
}
|
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
func (al *LocalAllowList) Allow(ip netip.Addr) bool {
|
2021-10-19 08:54:30 -06:00
|
|
|
if al == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return al.AllowList.Allow(ip)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (al *LocalAllowList) AllowName(name string) bool {
|
Add lighthouse.{remoteAllowList,localAllowList} (#217)
These settings make it possible to blacklist / whitelist IP addresses
that are used for remote connections.
`lighthouse.remoteAllowList` filters which remote IPs are allow when
fetching from the lighthouse (or, if you are the lighthouse, which IPs
you store and forward to querying hosts). By default, any remote IPs are
allowed. You can provide CIDRs here with `true` to allow and `false` to
deny. The most specific CIDR rule applies to each remote. If all rules
are "allow", the default will be "deny", and vice-versa. If both "allow"
and "deny" rules are present, then you MUST set a rule for "0.0.0.0/0"
as the default.
lighthouse:
remoteAllowList:
# Example to block IPs from this subnet from being used for remote IPs.
"172.16.0.0/12": false
# A more complicated example, allow public IPs but only private IPs from a specific subnet
"0.0.0.0/0": true
"10.0.0.0/8": false
"10.42.42.0/24": true
`lighthouse.localAllowList` has the same logic as above, but it applies
to the local addresses we advertise to the lighthouse. Additionally, you
can specify an `interfaces` map of regular expressions to match against
interface names. The regexp must match the entire name. All interface
rules must be either true or false (and the default rule will be the
inverse). CIDR rules are matched after interface name rules.
Default is all local IP addresses.
lighthouse:
localAllowList:
# Example to blacklist docker interfaces.
interfaces:
'docker.*': false
# Example to only advertise IPs in this subnet to the lighthouse.
"10.0.0.0/8": true
2020-04-08 13:36:43 -06:00
|
|
|
if al == nil || len(al.nameRules) == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, rule := range al.nameRules {
|
|
|
|
if rule.Name.MatchString(name) {
|
|
|
|
return rule.Allow
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no rules match, return the default, which is the inverse of the rules
|
|
|
|
return !al.nameRules[0].Allow
|
|
|
|
}
|
2021-10-19 08:54:30 -06:00
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
func (al *RemoteAllowList) AllowUnknownVpnIp(ip netip.Addr) bool {
|
2021-10-19 08:54:30 -06:00
|
|
|
if al == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return al.AllowList.Allow(ip)
|
|
|
|
}
|
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
func (al *RemoteAllowList) Allow(vpnIp netip.Addr, ip netip.Addr) bool {
|
2021-10-19 08:54:30 -06:00
|
|
|
if !al.getInsideAllowList(vpnIp).Allow(ip) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return al.AllowList.Allow(ip)
|
|
|
|
}
|
|
|
|
|
2024-07-31 09:18:56 -06:00
|
|
|
func (al *RemoteAllowList) getInsideAllowList(vpnIp netip.Addr) *AllowList {
|
2021-10-19 08:54:30 -06:00
|
|
|
if al.insideAllowLists != nil {
|
2024-07-31 09:18:56 -06:00
|
|
|
inside, ok := al.insideAllowLists.Lookup(vpnIp)
|
2023-11-02 16:05:08 -06:00
|
|
|
if ok {
|
|
|
|
return inside
|
2021-10-19 08:54:30 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|