Skip to content

Commit

Permalink
Allow multiple CIDR ranges per acl_zone (#19)
Browse files Browse the repository at this point in the history
* Allow multiple CIDR ranges per `acl_zone`

* simplify branching
  • Loading branch information
unRob authored Nov 5, 2023
1 parent 4c1a062 commit 52ab41d
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 41 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ consul_catalog [TAGS...] {
# ACL configuration
acl_metadata_tag META_TAG
acl_zone ZONE_NAME ZONE_CIDR
acl_zone ZONE_NAME ZONE_CIDR [ZONE_CIDR...]
# Service proxy allows static services to target a Catalog service
service_proxy PROXY_TAG PROXY_SERVICE
Expand All @@ -55,7 +55,7 @@ consul_catalog [TAGS...] {
* `endpoint` (default `consul.service.consul:8500`) specifies the host and port where to find consul catalog.
* `token` specifies the token to authenticate with the consul service, having at least .
* `acl_metadata_tag` (default: `coredns-acl`) specifies the Consul Metadata tag to read ACL rules from. An ACL rule looks like: `allow network1; deny network2`. Rules are interpreted in order of appearance. If specified, requests will only receive answers when their IP address corresponds to any of the allowed `acl_zone`s' CIDR ranges for a service.
* `acl_zone` adds an ACL zone named **ZONE_NAME** with corresponding **ZONE_CIDR** range.
* `acl_zone` adds an ACL zone named **ZONE_NAME** with corresponding **ZONE_CIDR** range(s).
* `service_proxy` If specified, services tagged with **PROXY_TAG** will respond with the address for **PROXY_SERVICE** instead.
* `alias_metadata_tag` (default: `coredns-alias`) specifies the Consul Metadata tag to read aliases to setup for service. Aliases are semicolon separated dns prefixes that reply with the same target as the original service. For example: `coredns-alias = "*.myservice; client.myservice"`. Aliases that begin with `*.`, are treated as a wildcard prefix that will match any sub-domains of the `zone` (and/or dots after the `*.` prefix).
* `static_entries_path` If specified, consul's kv store will be queried at **CONSUL_KV_PATH** and specified entries will be served before querying for catalog records. The value at **CONSUL_KV_PATH** must contain json following this schema:
Expand All @@ -67,7 +67,7 @@ consul_catalog [TAGS...] {
"aliases": ["*.static"] // a list of other names that should also reply with this service's info
},
"myServiceProxyService": {
"target": "@service_proxy", // a run-time alias for acl_zone's PROXY_SERVICE
"target": "@service_proxy", // a run-time alias for service_proxy's PROXY_SERVICE
"acl": ["allow network1"]
},
"my-a-record": {
Expand Down Expand Up @@ -110,8 +110,8 @@ example.com {
// Enable ACL
acl_metada_tag coredns-consul
// A service with `coredns-acl = "trusted" will only reply to clients in 10.0.0.0/24
acl_zone trusted 10.0.0.0/24
// A service with `coredns-acl = "trusted" will only reply to clients in the listed cidr ranges
acl_zone trusted 10.0.0.0/24 10.0.10.0/24 176.16.0.0/24
acl_zone guests 192.168.10.0/24
acl_zone iot 192.168.20.0/24
acl_zone public 0.0.0.0/0
Expand Down
8 changes: 4 additions & 4 deletions catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type Catalog struct {
Token string
ProxyService string
ProxyTag string
Networks map[string]*net.IPNet
Networks map[string][]*net.IPNet
ACLTag string
AliasTag string
Next plugin.Handler
Expand Down Expand Up @@ -146,10 +146,10 @@ func (c *Catalog) parseACL(svc *Service, rules []string) error {
}
action := ruleParts[0]
for _, networkName := range regexp.MustCompile(`,\s*`).Split(ruleParts[1], -1) {
if cidr, ok := c.Networks[networkName]; ok {
if ranges, ok := c.Networks[networkName]; ok {
svc.ACL = append(svc.ACL, &ServiceACL{
Action: action,
Network: cidr,
Action: action,
Networks: ranges,
})
} else {
return fmt.Errorf("unknown network %s", networkName)
Expand Down
28 changes: 15 additions & 13 deletions service.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (

// ServiceACL holds an action and corresponding network range.
type ServiceACL struct {
Action string
Network *net.IPNet
Action string
Networks []*net.IPNet
}

// Service has a target and ACL rules.
Expand All @@ -36,17 +36,19 @@ func NewService(name, target string) *Service {
func (s Service) RespondsTo(ip net.IP) bool {
Log.Debugf("Evaluating %d rules", len(s.ACL))
for _, acl := range s.ACL {
Log.Debugf("Evaluating %s", acl.Network)
if acl.Network.Contains(ip) {
switch acl.Action {
case "allow":
Log.Debugf("Allowed %s from %s", ip, acl.Network)
return true
case "deny":
Log.Debugf("Denied %s from %s", ip, acl.Network)
return false
default:
Log.Errorf("unknown acl action: %s", acl.Action)
Log.Debugf("Evaluating %s", acl.Networks)
for _, net := range acl.Networks {
if net.Contains(ip) {
switch acl.Action {
case "allow":
Log.Debugf("Allowed %s from %s", ip, acl.Networks)
return true
case "deny":
Log.Debugf("Denied %s from %s", ip, acl.Networks)
return false
default:
Log.Errorf("unknown acl action: %s", acl.Action)
}
}
}
}
Expand Down
20 changes: 13 additions & 7 deletions setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func parse(c *caddy.Controller) (cc *Catalog, err error) { // nolint: gocyclo
cc = New()

token := ""
networks := map[string]*net.IPNet{}
networks := map[string][]*net.IPNet{}
tag := defaultTag
for c.Next() {
tags := c.RemainingArgs()
Expand Down Expand Up @@ -122,13 +122,19 @@ func parse(c *caddy.Controller) (cc *Catalog, err error) { // nolint: gocyclo
if len(remaining) < 1 {
return nil, c.Errf("must supply a name and cidr range for acl_zone")
}
name := remaining[0]
_, network, err := net.ParseCIDR(remaining[1])
if err != nil {
return nil, c.Errf("unable to parse network range <%s>", remaining[1])
}

networks[name] = network
zoneName := remaining[0]
networks[zoneName] = []*net.IPNet{}
for idx, netRange := range remaining {
if idx == 0 {
continue
}
_, network, err := net.ParseCIDR(netRange)
if err != nil {
return nil, c.Errf("unable to parse network range <%s> of acl zone <%s>", netRange, zoneName)
}
networks[zoneName] = append(networks[zoneName], network)
}
case "service_proxy":
remaining := c.RemainingArgs()
if len(remaining) < 1 {
Expand Down
33 changes: 25 additions & 8 deletions setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestSetup(t *testing.T) {
endpoint string
ttl uint32
metaTag string
networks map[string]*net.IPNet
networks map[string][]*net.IPNet
}{
{
input: `consul_catalog`,
Expand Down Expand Up @@ -82,16 +82,25 @@ func TestSetup(t *testing.T) {
{
input: `consul_catalog {
acl_zone private 10.0.0.1/24
acl_zone multiple 172.16.0.0/12 192.168.0.0/16
acl_zone public 0.0.0.0/0
}`,
shouldError: false,
tags: defaultTags,
endpoint: defaultEndpoint,
ttl: defaultTTL,
metaTag: defaultACLTag,
networks: map[string]*net.IPNet{
"private": {IP: net.ParseIP("10.0.0.0"), Mask: net.IPv4Mask(255, 255, 255, 0)},
"public": {IP: net.ParseIP("0.0.0.0"), Mask: net.IPv4Mask(0, 0, 0, 0)},
networks: map[string][]*net.IPNet{
"private": {
{IP: net.ParseIP("10.0.0.0"), Mask: net.IPv4Mask(255, 255, 255, 0)},
},
"multiple": {
{IP: net.ParseIP("172.16.0.0"), Mask: net.IPv4Mask(255, 240, 0, 0)},
{IP: net.ParseIP("192.168.0.0"), Mask: net.IPv4Mask(255, 255, 0, 0)},
},
"public": {
{IP: net.ParseIP("0.0.0.0"), Mask: net.IPv4Mask(0, 0, 0, 0)},
},
},
},
{
Expand Down Expand Up @@ -132,11 +141,19 @@ func TestSetup(t *testing.T) {
t.Fatalf("TTL doesn't match: %v != %v", catalog.TTL, tst.ttl)
}

for name, cidr := range catalog.Networks {
if expectedCIDR, ok := tst.networks[name]; !ok {
for name, cidrRanges := range catalog.Networks {
expectedCIDR, ok := tst.networks[name]
if !ok {
t.Fatalf("Networks missing %s", name)
} else if expectedCIDR.String() != cidr.String() {
t.Fatalf("Wrong CIDR found: %s, expected %s", cidr, expectedCIDR)
}
if len(expectedCIDR) != len(cidrRanges) {
t.Fatalf("expected %d ranges, got %d", len(expectedCIDR), len(cidrRanges))
}
for idx, parsed := range cidrRanges {
expected := expectedCIDR[idx]
if parsed.String() != expected.String() {
t.Fatalf("Wrong CIDR found: %s, expected %s", parsed, expected)
}
}
}
})
Expand Down
8 changes: 4 additions & 4 deletions util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ func NewTestCatalog(fetch bool, extraSources ...*Watch) (*Catalog, Client, KVCli
c.FQDN = []string{"example.com."}
c.ProxyTag = "traefik.enable=true"
c.ProxyService = "traefik"
c.Networks = map[string]*net.IPNet{}
c.Networks = map[string][]*net.IPNet{}
_, private, _ := net.ParseCIDR("192.168.100.0/24")
c.Networks["private"] = private
c.Networks["private"] = []*net.IPNet{private}
_, guest, _ := net.ParseCIDR("192.168.1.0/24")
c.Networks["guest"] = guest
c.Networks["guest"] = []*net.IPNet{guest}
_, public, _ := net.ParseCIDR("0.0.0.0/0")
c.Networks["public"] = public
c.Networks["public"] = []*net.IPNet{public}
client := NewTestCatalogClient()
kvClient := NewTestKVClient()
c.SetClients(client, kvClient)
Expand Down

0 comments on commit 52ab41d

Please sign in to comment.