Merge pull request #618 from juanfont/acl-syntax-fixes

This commit is contained in:
Kristoffer Dalby 2022-06-11 15:07:29 +01:00 committed by GitHub
commit 883bb92991
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 332 additions and 159 deletions

View File

@ -2,6 +2,10 @@
## 0.16.0 (2022-xx-xx) ## 0.16.0 (2022-xx-xx)
### BREAKING
- Old ACL syntax is no longer supported ("users" & "ports" -> "src" & "dst"). Please check [the new syntax](https://tailscale.com/kb/1018/acls/).
### Changes ### Changes
- **Drop** armhf (32-bit ARM) support. [#609](https://github.com/juanfont/headscale/pull/609) - **Drop** armhf (32-bit ARM) support. [#609](https://github.com/juanfont/headscale/pull/609)
@ -22,6 +26,7 @@
- This change disables the logs by default - This change disables the logs by default
- Use [Prometheus]'s duration parser, supporting days (`d`), weeks (`w`) and years (`y`) [#598](https://github.com/juanfont/headscale/pull/598) - Use [Prometheus]'s duration parser, supporting days (`d`), weeks (`w`) and years (`y`) [#598](https://github.com/juanfont/headscale/pull/598)
- Add support for reloading ACLs with SIGHUP [#601](https://github.com/juanfont/headscale/pull/601) - Add support for reloading ACLs with SIGHUP [#601](https://github.com/juanfont/headscale/pull/601)
- Use new ACL syntax [#618](https://github.com/juanfont/headscale/pull/618)
- Add -c option to specify config file from command line [#285](https://github.com/juanfont/headscale/issues/285) [#612](https://github.com/juanfont/headscale/pull/601) - Add -c option to specify config file from command line [#285](https://github.com/juanfont/headscale/issues/285) [#612](https://github.com/juanfont/headscale/pull/601)
## 0.15.0 (2022-03-20) ## 0.15.0 (2022-03-20)

109
acls.go
View File

@ -23,6 +23,7 @@ const (
errInvalidGroup = Error("invalid group") errInvalidGroup = Error("invalid group")
errInvalidTag = Error("invalid tag") errInvalidTag = Error("invalid tag")
errInvalidPortFormat = Error("invalid port format") errInvalidPortFormat = Error("invalid port format")
errWildcardIsNeeded = Error("wildcard as port is required for the protocol")
) )
const ( const (
@ -36,6 +37,23 @@ const (
expectedTokenItems = 2 expectedTokenItems = 2
) )
// For some reason golang.org/x/net/internal/iana is an internal package
const (
protocolICMP = 1 // Internet Control Message
protocolIGMP = 2 // Internet Group Management
protocolIPv4 = 4 // IPv4 encapsulation
protocolTCP = 6 // Transmission Control
protocolEGP = 8 // Exterior Gateway Protocol
protocolIGP = 9 // any private interior gateway (used by Cisco for their IGRP)
protocolUDP = 17 // User Datagram
protocolGRE = 47 // Generic Routing Encapsulation
protocolESP = 50 // Encap Security Payload
protocolAH = 51 // Authentication Header
protocolIPv6ICMP = 58 // ICMP for IPv6
protocolSCTP = 132 // Stream Control Transmission Protocol
ProtocolFC = 133 // Fibre Channel
)
// LoadACLPolicy loads the ACL policy from the specify path, and generates the ACL rules. // LoadACLPolicy loads the ACL policy from the specify path, and generates the ACL rules.
func (h *Headscale) LoadACLPolicy(path string) error { func (h *Headscale) LoadACLPolicy(path string) error {
log.Debug(). log.Debug().
@ -123,23 +141,31 @@ func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) {
} }
srcIPs := []string{} srcIPs := []string{}
for innerIndex, user := range acl.Users { for innerIndex, src := range acl.Sources {
srcs, err := h.generateACLPolicySrcIP(machines, *h.aclPolicy, user) srcs, err := h.generateACLPolicySrcIP(machines, *h.aclPolicy, src)
if err != nil { if err != nil {
log.Error(). log.Error().
Msgf("Error parsing ACL %d, User %d", index, innerIndex) Msgf("Error parsing ACL %d, Source %d", index, innerIndex)
return nil, err return nil, err
} }
srcIPs = append(srcIPs, srcs...) srcIPs = append(srcIPs, srcs...)
} }
destPorts := []tailcfg.NetPortRange{} protocols, needsWildcard, err := parseProtocol(acl.Protocol)
for innerIndex, ports := range acl.Ports {
dests, err := h.generateACLPolicyDestPorts(machines, *h.aclPolicy, ports)
if err != nil { if err != nil {
log.Error(). log.Error().
Msgf("Error parsing ACL %d, Port %d", index, innerIndex) Msgf("Error parsing ACL %d. protocol unknown %s", index, acl.Protocol)
return nil, err
}
destPorts := []tailcfg.NetPortRange{}
for innerIndex, dest := range acl.Destinations {
dests, err := h.generateACLPolicyDest(machines, *h.aclPolicy, dest, needsWildcard)
if err != nil {
log.Error().
Msgf("Error parsing ACL %d, Destination %d", index, innerIndex)
return nil, err return nil, err
} }
@ -149,6 +175,7 @@ func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) {
rules = append(rules, tailcfg.FilterRule{ rules = append(rules, tailcfg.FilterRule{
SrcIPs: srcIPs, SrcIPs: srcIPs,
DstPorts: destPorts, DstPorts: destPorts,
IPProto: protocols,
}) })
} }
@ -158,17 +185,18 @@ func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) {
func (h *Headscale) generateACLPolicySrcIP( func (h *Headscale) generateACLPolicySrcIP(
machines []Machine, machines []Machine,
aclPolicy ACLPolicy, aclPolicy ACLPolicy,
u string, src string,
) ([]string, error) { ) ([]string, error) {
return expandAlias(machines, aclPolicy, u, h.cfg.OIDC.StripEmaildomain) return expandAlias(machines, aclPolicy, src, h.cfg.OIDC.StripEmaildomain)
} }
func (h *Headscale) generateACLPolicyDestPorts( func (h *Headscale) generateACLPolicyDest(
machines []Machine, machines []Machine,
aclPolicy ACLPolicy, aclPolicy ACLPolicy,
d string, dest string,
needsWildcard bool,
) ([]tailcfg.NetPortRange, error) { ) ([]tailcfg.NetPortRange, error) {
tokens := strings.Split(d, ":") tokens := strings.Split(dest, ":")
if len(tokens) < expectedTokenItems || len(tokens) > 3 { if len(tokens) < expectedTokenItems || len(tokens) > 3 {
return nil, errInvalidPortFormat return nil, errInvalidPortFormat
} }
@ -195,7 +223,7 @@ func (h *Headscale) generateACLPolicyDestPorts(
if err != nil { if err != nil {
return nil, err return nil, err
} }
ports, err := expandPorts(tokens[len(tokens)-1]) ports, err := expandPorts(tokens[len(tokens)-1], needsWildcard)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -214,6 +242,54 @@ func (h *Headscale) generateACLPolicyDestPorts(
return dests, nil return dests, nil
} }
// parseProtocol reads the proto field of the ACL and generates a list of
// protocols that will be allowed, following the IANA IP protocol number
// https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
//
// If the ACL proto field is empty, it allows ICMPv4, ICMPv6, TCP, and UDP,
// as per Tailscale behaviour (see tailcfg.FilterRule).
//
// Also returns a boolean indicating if the protocol
// requires all the destinations to use wildcard as port number (only TCP,
// UDP and SCTP support specifying ports).
func parseProtocol(protocol string) ([]int, bool, error) {
switch protocol {
case "":
return []int{protocolICMP, protocolIPv6ICMP, protocolTCP, protocolUDP}, false, nil
case "igmp":
return []int{protocolIGMP}, true, nil
case "ipv4", "ip-in-ip":
return []int{protocolIPv4}, true, nil
case "tcp":
return []int{protocolTCP}, false, nil
case "egp":
return []int{protocolEGP}, true, nil
case "igp":
return []int{protocolIGP}, true, nil
case "udp":
return []int{protocolUDP}, false, nil
case "gre":
return []int{protocolGRE}, true, nil
case "esp":
return []int{protocolESP}, true, nil
case "ah":
return []int{protocolAH}, true, nil
case "sctp":
return []int{protocolSCTP}, false, nil
case "icmp":
return []int{protocolICMP, protocolIPv6ICMP}, true, nil
default:
protocolNumber, err := strconv.Atoi(protocol)
if err != nil {
return nil, false, err
}
needsWildcard := protocolNumber != protocolTCP && protocolNumber != protocolUDP && protocolNumber != protocolSCTP
return []int{protocolNumber}, needsWildcard, nil
}
}
// expandalias has an input of either // expandalias has an input of either
// - a namespace // - a namespace
// - a group // - a group
@ -268,6 +344,7 @@ func expandAlias(
alias, alias,
) )
} }
return ips, nil return ips, nil
} else { } else {
return ips, err return ips, err
@ -359,13 +436,17 @@ func excludeCorrectlyTaggedNodes(
return out return out
} }
func expandPorts(portsStr string) (*[]tailcfg.PortRange, error) { func expandPorts(portsStr string, needsWildcard bool) (*[]tailcfg.PortRange, error) {
if portsStr == "*" { if portsStr == "*" {
return &[]tailcfg.PortRange{ return &[]tailcfg.PortRange{
{First: portRangeBegin, Last: portRangeEnd}, {First: portRangeBegin, Last: portRangeEnd},
}, nil }, nil
} }
if needsWildcard {
return nil, errWildcardIsNeeded
}
ports := []tailcfg.PortRange{} ports := []tailcfg.PortRange{}
for _, portStr := range strings.Split(portsStr, ",") { for _, portStr := range strings.Split(portsStr, ",") {
rang := strings.Split(portStr, "-") rang := strings.Split(portStr, "-")

View File

@ -62,7 +62,7 @@ func (s *Suite) TestBasicRule(c *check.C) {
func (s *Suite) TestInvalidAction(c *check.C) { func (s *Suite) TestInvalidAction(c *check.C) {
app.aclPolicy = &ACLPolicy{ app.aclPolicy = &ACLPolicy{
ACLs: []ACL{ ACLs: []ACL{
{Action: "invalidAction", Users: []string{"*"}, Ports: []string{"*:*"}}, {Action: "invalidAction", Sources: []string{"*"}, Destinations: []string{"*:*"}},
}, },
} }
err := app.UpdateACLRules() err := app.UpdateACLRules()
@ -70,14 +70,14 @@ func (s *Suite) TestInvalidAction(c *check.C) {
} }
func (s *Suite) TestInvalidGroupInGroup(c *check.C) { func (s *Suite) TestInvalidGroupInGroup(c *check.C) {
// this ACL is wrong because the group in users sections doesn't exist // this ACL is wrong because the group in Sources sections doesn't exist
app.aclPolicy = &ACLPolicy{ app.aclPolicy = &ACLPolicy{
Groups: Groups{ Groups: Groups{
"group:test": []string{"foo"}, "group:test": []string{"foo"},
"group:error": []string{"foo", "group:test"}, "group:error": []string{"foo", "group:test"},
}, },
ACLs: []ACL{ ACLs: []ACL{
{Action: "accept", Users: []string{"group:error"}, Ports: []string{"*:*"}}, {Action: "accept", Sources: []string{"group:error"}, Destinations: []string{"*:*"}},
}, },
} }
err := app.UpdateACLRules() err := app.UpdateACLRules()
@ -88,7 +88,7 @@ func (s *Suite) TestInvalidTagOwners(c *check.C) {
// this ACL is wrong because no tagOwners own the requested tag for the server // this ACL is wrong because no tagOwners own the requested tag for the server
app.aclPolicy = &ACLPolicy{ app.aclPolicy = &ACLPolicy{
ACLs: []ACL{ ACLs: []ACL{
{Action: "accept", Users: []string{"tag:foo"}, Ports: []string{"*:*"}}, {Action: "accept", Sources: []string{"tag:foo"}, Destinations: []string{"*:*"}},
}, },
} }
err := app.UpdateACLRules() err := app.UpdateACLRules()
@ -97,8 +97,8 @@ func (s *Suite) TestInvalidTagOwners(c *check.C) {
// this test should validate that we can expand a group in a TagOWner section and // this test should validate that we can expand a group in a TagOWner section and
// match properly the IP's of the related hosts. The owner is valid and the tag is also valid. // match properly the IP's of the related hosts. The owner is valid and the tag is also valid.
// the tag is matched in the Users section. // the tag is matched in the Sources section.
func (s *Suite) TestValidExpandTagOwnersInUsers(c *check.C) { func (s *Suite) TestValidExpandTagOwnersInSources(c *check.C) {
namespace, err := app.CreateNamespace("user1") namespace, err := app.CreateNamespace("user1")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
@ -131,7 +131,7 @@ func (s *Suite) TestValidExpandTagOwnersInUsers(c *check.C) {
Groups: Groups{"group:test": []string{"user1", "user2"}}, Groups: Groups{"group:test": []string{"user1", "user2"}},
TagOwners: TagOwners{"tag:test": []string{"user3", "group:test"}}, TagOwners: TagOwners{"tag:test": []string{"user3", "group:test"}},
ACLs: []ACL{ ACLs: []ACL{
{Action: "accept", Users: []string{"tag:test"}, Ports: []string{"*:*"}}, {Action: "accept", Sources: []string{"tag:test"}, Destinations: []string{"*:*"}},
}, },
} }
err = app.UpdateACLRules() err = app.UpdateACLRules()
@ -143,8 +143,8 @@ func (s *Suite) TestValidExpandTagOwnersInUsers(c *check.C) {
// this test should validate that we can expand a group in a TagOWner section and // this test should validate that we can expand a group in a TagOWner section and
// match properly the IP's of the related hosts. The owner is valid and the tag is also valid. // match properly the IP's of the related hosts. The owner is valid and the tag is also valid.
// the tag is matched in the Ports section. // the tag is matched in the Destinations section.
func (s *Suite) TestValidExpandTagOwnersInPorts(c *check.C) { func (s *Suite) TestValidExpandTagOwnersInDestinations(c *check.C) {
namespace, err := app.CreateNamespace("user1") namespace, err := app.CreateNamespace("user1")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
@ -177,7 +177,7 @@ func (s *Suite) TestValidExpandTagOwnersInPorts(c *check.C) {
Groups: Groups{"group:test": []string{"user1", "user2"}}, Groups: Groups{"group:test": []string{"user1", "user2"}},
TagOwners: TagOwners{"tag:test": []string{"user3", "group:test"}}, TagOwners: TagOwners{"tag:test": []string{"user3", "group:test"}},
ACLs: []ACL{ ACLs: []ACL{
{Action: "accept", Users: []string{"*"}, Ports: []string{"tag:test:*"}}, {Action: "accept", Sources: []string{"*"}, Destinations: []string{"tag:test:*"}},
}, },
} }
err = app.UpdateACLRules() err = app.UpdateACLRules()
@ -222,7 +222,7 @@ func (s *Suite) TestInvalidTagValidNamespace(c *check.C) {
app.aclPolicy = &ACLPolicy{ app.aclPolicy = &ACLPolicy{
TagOwners: TagOwners{"tag:test": []string{"user1"}}, TagOwners: TagOwners{"tag:test": []string{"user1"}},
ACLs: []ACL{ ACLs: []ACL{
{Action: "accept", Users: []string{"user1"}, Ports: []string{"*:*"}}, {Action: "accept", Sources: []string{"user1"}, Destinations: []string{"*:*"}},
}, },
} }
err = app.UpdateACLRules() err = app.UpdateACLRules()
@ -288,8 +288,8 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) {
ACLs: []ACL{ ACLs: []ACL{
{ {
Action: "accept", Action: "accept",
Users: []string{"user1"}, Sources: []string{"user1"},
Ports: []string{"tag:webapp:80,443"}, Destinations: []string{"tag:webapp:80,443"},
}, },
}, },
} }
@ -321,6 +321,20 @@ func (s *Suite) TestPortRange(c *check.C) {
c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(5500)) c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(5500))
} }
func (s *Suite) TestProtocolParsing(c *check.C) {
err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_protocols.hujson")
c.Assert(err, check.IsNil)
rules, err := app.generateACLRules()
c.Assert(err, check.IsNil)
c.Assert(rules, check.NotNil)
c.Assert(rules, check.HasLen, 3)
c.Assert(rules[0].IPProto[0], check.Equals, protocolTCP)
c.Assert(rules[1].IPProto[0], check.Equals, protocolUDP)
c.Assert(rules[2].IPProto[1], check.Equals, protocolIPv6ICMP)
}
func (s *Suite) TestPortWildcard(c *check.C) { func (s *Suite) TestPortWildcard(c *check.C) {
err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_wildcards.hujson") err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_wildcards.hujson")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
@ -629,6 +643,7 @@ func Test_expandTagOwners(t *testing.T) {
func Test_expandPorts(t *testing.T) { func Test_expandPorts(t *testing.T) {
type args struct { type args struct {
portsStr string portsStr string
needsWildcard bool
} }
tests := []struct { tests := []struct {
name string name string
@ -638,15 +653,29 @@ func Test_expandPorts(t *testing.T) {
}{ }{
{ {
name: "wildcard", name: "wildcard",
args: args{portsStr: "*"}, args: args{portsStr: "*", needsWildcard: true},
want: &[]tailcfg.PortRange{ want: &[]tailcfg.PortRange{
{First: portRangeBegin, Last: portRangeEnd}, {First: portRangeBegin, Last: portRangeEnd},
}, },
wantErr: false, wantErr: false,
}, },
{ {
name: "two ports", name: "needs wildcard but does not require it",
args: args{portsStr: "80,443"}, args: args{portsStr: "*", needsWildcard: false},
want: &[]tailcfg.PortRange{
{First: portRangeBegin, Last: portRangeEnd},
},
wantErr: false,
},
{
name: "needs wildcard but gets port",
args: args{portsStr: "80,443", needsWildcard: true},
want: nil,
wantErr: true,
},
{
name: "two Destinations",
args: args{portsStr: "80,443", needsWildcard: false},
want: &[]tailcfg.PortRange{ want: &[]tailcfg.PortRange{
{First: 80, Last: 80}, {First: 80, Last: 80},
{First: 443, Last: 443}, {First: 443, Last: 443},
@ -655,7 +684,7 @@ func Test_expandPorts(t *testing.T) {
}, },
{ {
name: "a range and a port", name: "a range and a port",
args: args{portsStr: "80-1024,443"}, args: args{portsStr: "80-1024,443", needsWildcard: false},
want: &[]tailcfg.PortRange{ want: &[]tailcfg.PortRange{
{First: 80, Last: 1024}, {First: 80, Last: 1024},
{First: 443, Last: 443}, {First: 443, Last: 443},
@ -664,38 +693,38 @@ func Test_expandPorts(t *testing.T) {
}, },
{ {
name: "out of bounds", name: "out of bounds",
args: args{portsStr: "854038"}, args: args{portsStr: "854038", needsWildcard: false},
want: nil, want: nil,
wantErr: true, wantErr: true,
}, },
{ {
name: "wrong port", name: "wrong port",
args: args{portsStr: "85a38"}, args: args{portsStr: "85a38", needsWildcard: false},
want: nil, want: nil,
wantErr: true, wantErr: true,
}, },
{ {
name: "wrong port in first", name: "wrong port in first",
args: args{portsStr: "a-80"}, args: args{portsStr: "a-80", needsWildcard: false},
want: nil, want: nil,
wantErr: true, wantErr: true,
}, },
{ {
name: "wrong port in last", name: "wrong port in last",
args: args{portsStr: "80-85a38"}, args: args{portsStr: "80-85a38", needsWildcard: false},
want: nil, want: nil,
wantErr: true, wantErr: true,
}, },
{ {
name: "wrong port format", name: "wrong port format",
args: args{portsStr: "80-85a38-3"}, args: args{portsStr: "80-85a38-3", needsWildcard: false},
want: nil, want: nil,
wantErr: true, wantErr: true,
}, },
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
got, err := expandPorts(test.args.portsStr) got, err := expandPorts(test.args.portsStr, test.args.needsWildcard)
if (err != nil) != test.wantErr { if (err != nil) != test.wantErr {
t.Errorf("expandPorts() error = %v, wantErr %v", err, test.wantErr) t.Errorf("expandPorts() error = %v, wantErr %v", err, test.wantErr)

View File

@ -11,18 +11,19 @@ import (
// ACLPolicy represents a Tailscale ACL Policy. // ACLPolicy represents a Tailscale ACL Policy.
type ACLPolicy struct { type ACLPolicy struct {
Groups Groups `json:"Groups" yaml:"Groups"` Groups Groups `json:"groups" yaml:"groups"`
Hosts Hosts `json:"Hosts" yaml:"Hosts"` Hosts Hosts `json:"hosts" yaml:"hosts"`
TagOwners TagOwners `json:"TagOwners" yaml:"TagOwners"` TagOwners TagOwners `json:"tagOwners" yaml:"tagOwners"`
ACLs []ACL `json:"ACLs" yaml:"ACLs"` ACLs []ACL `json:"acls" yaml:"acls"`
Tests []ACLTest `json:"Tests" yaml:"Tests"` Tests []ACLTest `json:"tests" yaml:"tests"`
} }
// ACL is a basic rule for the ACL Policy. // ACL is a basic rule for the ACL Policy.
type ACL struct { type ACL struct {
Action string `json:"Action" yaml:"Action"` Action string `json:"action" yaml:"action"`
Users []string `json:"Users" yaml:"Users"` Protocol string `json:"proto" yaml:"proto"`
Ports []string `json:"Ports" yaml:"Ports"` Sources []string `json:"src" yaml:"src"`
Destinations []string `json:"dst" yaml:"dst"`
} }
// Groups references a series of alias in the ACL rules. // Groups references a series of alias in the ACL rules.
@ -36,9 +37,9 @@ type TagOwners map[string][]string
// ACLTest is not implemented, but should be use to check if a certain rule is allowed. // ACLTest is not implemented, but should be use to check if a certain rule is allowed.
type ACLTest struct { type ACLTest struct {
User string `json:"User" yaml:"User"` Source string `json:"src" yaml:"src"`
Allow []string `json:"Allow" yaml:"Allow"` Accept []string `json:"accept" yaml:"accept"`
Deny []string `json:"Deny,omitempty" yaml:"Deny,omitempty"` Deny []string `json:"deny,omitempty" yaml:"deny,omitempty"`
} }
// UnmarshalJSON allows to parse the Hosts directly into netaddr objects. // UnmarshalJSON allows to parse the Hosts directly into netaddr objects.

View File

@ -33,7 +33,7 @@ Note: Namespaces will be created automatically when users authenticate with the
Headscale server. Headscale server.
ACLs could be written either on [huJSON](https://github.com/tailscale/hujson) ACLs could be written either on [huJSON](https://github.com/tailscale/hujson)
or Yaml. Check the [test ACLs](../tests/acls) for further information. or YAML. Check the [test ACLs](../tests/acls) for further information.
When registering the servers we will need to add the flag When registering the servers we will need to add the flag
`--advertised-tags=tag:<tag1>,tag:<tag2>`, and the user (namespace) that is `--advertised-tags=tag:<tag1>,tag:<tag2>`, and the user (namespace) that is
@ -83,8 +83,8 @@ Here are the ACL's to implement the same permissions as above:
// boss have access to all servers // boss have access to all servers
{ {
"action": "accept", "action": "accept",
"users": ["group:boss"], "src": ["group:boss"],
"ports": [ "dst": [
"tag:prod-databases:*", "tag:prod-databases:*",
"tag:prod-app-servers:*", "tag:prod-app-servers:*",
"tag:internal:*", "tag:internal:*",
@ -93,11 +93,12 @@ Here are the ACL's to implement the same permissions as above:
] ]
}, },
// admin have only access to administrative ports of the servers // admin have only access to administrative ports of the servers, in tcp/22
{ {
"action": "accept", "action": "accept",
"users": ["group:admin"], "src": ["group:admin"],
"ports": [ "proto": "tcp",
"dst": [
"tag:prod-databases:22", "tag:prod-databases:22",
"tag:prod-app-servers:22", "tag:prod-app-servers:22",
"tag:internal:22", "tag:internal:22",
@ -106,12 +107,26 @@ Here are the ACL's to implement the same permissions as above:
] ]
}, },
// we also allow admin to ping the servers
{
"action": "accept",
"src": ["group:admin"],
"proto": "icmp",
"dst": [
"tag:prod-databases:*",
"tag:prod-app-servers:*",
"tag:internal:*",
"tag:dev-databases:*",
"tag:dev-app-servers:*"
]
},
// developers have access to databases servers and application servers on all ports // developers have access to databases servers and application servers on all ports
// they can only view the applications servers in prod and have no access to databases servers in production // they can only view the applications servers in prod and have no access to databases servers in production
{ {
"action": "accept", "action": "accept",
"users": ["group:dev"], "src": ["group:dev"],
"ports": [ "dst": [
"tag:dev-databases:*", "tag:dev-databases:*",
"tag:dev-app-servers:*", "tag:dev-app-servers:*",
"tag:prod-app-servers:80,443" "tag:prod-app-servers:80,443"
@ -124,37 +139,38 @@ Here are the ACL's to implement the same permissions as above:
// https://github.com/juanfont/headscale/issues/502 // https://github.com/juanfont/headscale/issues/502
{ {
"action": "accept", "action": "accept",
"users": ["group:dev"], "src": ["group:dev"],
"ports": ["10.20.0.0/16:443,5432", "router.internal:0"] "dst": ["10.20.0.0/16:443,5432", "router.internal:0"]
}, },
// servers should be able to talk to database. Database should not be able to initiate connections to // servers should be able to talk to database in tcp/5432. Database should not be able to initiate connections to
// applications servers // applications servers
{ {
"action": "accept", "action": "accept",
"users": ["tag:dev-app-servers"], "src": ["tag:dev-app-servers"],
"ports": ["tag:dev-databases:5432"] "proto": "tcp",
"dst": ["tag:dev-databases:5432"]
}, },
{ {
"action": "accept", "action": "accept",
"users": ["tag:prod-app-servers"], "src": ["tag:prod-app-servers"],
"ports": ["tag:prod-databases:5432"] "dst": ["tag:prod-databases:5432"]
}, },
// interns have access to dev-app-servers only in reading mode // interns have access to dev-app-servers only in reading mode
{ {
"action": "accept", "action": "accept",
"users": ["group:intern"], "src": ["group:intern"],
"ports": ["tag:dev-app-servers:80,443"] "dst": ["tag:dev-app-servers:80,443"]
}, },
// We still have to allow internal namespaces communications since nothing guarantees that each user have // We still have to allow internal namespaces communications since nothing guarantees that each user have
// their own namespaces. // their own namespaces.
{ "action": "accept", "users": ["boss"], "ports": ["boss:*"] }, { "action": "accept", "src": ["boss"], "dst": ["boss:*"] },
{ "action": "accept", "users": ["dev1"], "ports": ["dev1:*"] }, { "action": "accept", "src": ["dev1"], "dst": ["dev1:*"] },
{ "action": "accept", "users": ["dev2"], "ports": ["dev2:*"] }, { "action": "accept", "src": ["dev2"], "dst": ["dev2:*"] },
{ "action": "accept", "users": ["admin1"], "ports": ["admin1:*"] }, { "action": "accept", "src": ["admin1"], "dst": ["admin1:*"] },
{ "action": "accept", "users": ["intern1"], "ports": ["intern1:*"] } { "action": "accept", "src": ["intern1"], "dst": ["intern1:*"] }
] ]
} }
``` ```

View File

@ -188,8 +188,8 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
Hosts: map[string]netaddr.IPPrefix{}, Hosts: map[string]netaddr.IPPrefix{},
TagOwners: map[string][]string{}, TagOwners: map[string][]string{},
ACLs: []ACL{ ACLs: []ACL{
{Action: "accept", Users: []string{"admin"}, Ports: []string{"*:*"}}, {Action: "accept", Sources: []string{"admin"}, Destinations: []string{"*:*"}},
{Action: "accept", Users: []string{"test"}, Ports: []string{"test:*"}}, {Action: "accept", Sources: []string{"test"}, Destinations: []string{"test:*"}},
}, },
Tests: []ACLTest{}, Tests: []ACLTest{},
} }

View File

@ -1,6 +1,6 @@
{ {
// Declare static groups of users beyond those in the identity service. // Declare static groups of users beyond those in the identity service.
"Groups": { "groups": {
"group:example": [ "group:example": [
"user1@example.com", "user1@example.com",
"user2@example.com", "user2@example.com",
@ -11,12 +11,12 @@
], ],
}, },
// Declare hostname aliases to use in place of IP addresses or subnets. // Declare hostname aliases to use in place of IP addresses or subnets.
"Hosts": { "hosts": {
"example-host-1": "100.100.100.100", "example-host-1": "100.100.100.100",
"example-host-2": "100.100.101.100/24", "example-host-2": "100.100.101.100/24",
}, },
// Define who is allowed to use which tags. // Define who is allowed to use which tags.
"TagOwners": { "tagOwners": {
// Everyone in the montreal-admins or global-admins group are // Everyone in the montreal-admins or global-admins group are
// allowed to tag servers as montreal-webserver. // allowed to tag servers as montreal-webserver.
"tag:montreal-webserver": [ "tag:montreal-webserver": [
@ -29,17 +29,17 @@
], ],
}, },
// Access control lists. // Access control lists.
"ACLs": [ "acls": [
// Engineering users, plus the president, can access port 22 (ssh) // Engineering users, plus the president, can access port 22 (ssh)
// and port 3389 (remote desktop protocol) on all servers, and all // and port 3389 (remote desktop protocol) on all servers, and all
// ports on git-server or ci-server. // ports on git-server or ci-server.
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"group:example2", "group:example2",
"192.168.1.0/24" "192.168.1.0/24"
], ],
"Ports": [ "dst": [
"*:22,3389", "*:22,3389",
"git-server:*", "git-server:*",
"ci-server:*" "ci-server:*"
@ -48,22 +48,22 @@
// Allow engineer users to access any port on a device tagged with // Allow engineer users to access any port on a device tagged with
// tag:production. // tag:production.
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"group:example" "group:example"
], ],
"Ports": [ "dst": [
"tag:production:*" "tag:production:*"
], ],
}, },
// Allow servers in the my-subnet host and 192.168.1.0/24 to access hosts // Allow servers in the my-subnet host and 192.168.1.0/24 to access hosts
// on both networks. // on both networks.
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"example-host-2", "example-host-2",
], ],
"Ports": [ "dst": [
"example-host-1:*", "example-host-1:*",
"192.168.1.0/24:*" "192.168.1.0/24:*"
], ],
@ -72,22 +72,22 @@
// Comment out this section if you want to define specific ACL // Comment out this section if you want to define specific ACL
// restrictions above. // restrictions above.
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"*" "*"
], ],
"Ports": [ "dst": [
"*:*" "*:*"
], ],
}, },
// All users in Montreal are allowed to access the Montreal web // All users in Montreal are allowed to access the Montreal web
// servers. // servers.
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"example-host-1" "example-host-1"
], ],
"Ports": [ "dst": [
"tag:montreal-webserver:80,443" "tag:montreal-webserver:80,443"
], ],
}, },
@ -96,30 +96,30 @@
// In contrast, this doesn't grant API servers the right to initiate // In contrast, this doesn't grant API servers the right to initiate
// any connections. // any connections.
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"tag:montreal-webserver" "tag:montreal-webserver"
], ],
"Ports": [ "dst": [
"tag:api-server:443" "tag:api-server:443"
], ],
}, },
], ],
// Declare tests to check functionality of ACL rules // Declare tests to check functionality of ACL rules
"Tests": [ "tests": [
{ {
"User": "user1@example.com", "src": "user1@example.com",
"Allow": [ "accept": [
"example-host-1:22", "example-host-1:22",
"example-host-2:80" "example-host-2:80"
], ],
"Deny": [ "deny": [
"exapmle-host-2:100" "exapmle-host-2:100"
], ],
}, },
{ {
"User": "user2@example.com", "src": "user2@example.com",
"Allow": [ "accept": [
"100.60.3.4:22" "100.60.3.4:22"
], ],
}, },

View File

@ -3,19 +3,19 @@
{ {
"Hosts": { "hosts": {
"host-1": "100.100.100.100", "host-1": "100.100.100.100",
"subnet-1": "100.100.101.100/24", "subnet-1": "100.100.101.100/24",
}, },
"ACLs": [ "acls": [
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"subnet-1", "subnet-1",
"192.168.1.0/24" "192.168.1.0/24"
], ],
"Ports": [ "dst": [
"*:22,3389", "*:22,3389",
"host-1:*", "host-1:*",
], ],

View File

@ -1,24 +1,24 @@
// This ACL is used to test group expansion // This ACL is used to test group expansion
{ {
"Groups": { "groups": {
"group:example": [ "group:example": [
"testnamespace", "testnamespace",
], ],
}, },
"Hosts": { "hosts": {
"host-1": "100.100.100.100", "host-1": "100.100.100.100",
"subnet-1": "100.100.101.100/24", "subnet-1": "100.100.101.100/24",
}, },
"ACLs": [ "acls": [
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"group:example", "group:example",
], ],
"Ports": [ "dst": [
"host-1:*", "host-1:*",
], ],
}, },

View File

@ -1,18 +1,18 @@
// This ACL is used to test namespace expansion // This ACL is used to test namespace expansion
{ {
"Hosts": { "hosts": {
"host-1": "100.100.100.100", "host-1": "100.100.100.100",
"subnet-1": "100.100.101.100/24", "subnet-1": "100.100.101.100/24",
}, },
"ACLs": [ "acls": [
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"testnamespace", "testnamespace",
], ],
"Ports": [ "dst": [
"host-1:*", "host-1:*",
], ],
}, },

View File

@ -0,0 +1,41 @@
// This ACL is used to test wildcards
{
"hosts": {
"host-1": "100.100.100.100",
"subnet-1": "100.100.101.100/24",
},
"acls": [
{
"Action": "accept",
"src": [
"*",
],
"proto": "tcp",
"dst": [
"host-1:*",
],
},
{
"Action": "accept",
"src": [
"*",
],
"proto": "udp",
"dst": [
"host-1:53",
],
},
{
"Action": "accept",
"src": [
"*",
],
"proto": "icmp",
"dst": [
"host-1:*",
],
},
],
}

View File

@ -1,18 +1,18 @@
// This ACL is used to test the port range expansion // This ACL is used to test the port range expansion
{ {
"Hosts": { "hosts": {
"host-1": "100.100.100.100", "host-1": "100.100.100.100",
"subnet-1": "100.100.101.100/24", "subnet-1": "100.100.101.100/24",
}, },
"ACLs": [ "acls": [
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"subnet-1", "subnet-1",
], ],
"Ports": [ "dst": [
"host-1:5400-5500", "host-1:5400-5500",
], ],
}, },

View File

@ -1,18 +1,18 @@
// This ACL is used to test wildcards // This ACL is used to test wildcards
{ {
"Hosts": { "hosts": {
"host-1": "100.100.100.100", "host-1": "100.100.100.100",
"subnet-1": "100.100.101.100/24", "subnet-1": "100.100.101.100/24",
}, },
"ACLs": [ "acls": [
{ {
"Action": "accept", "Action": "accept",
"Users": [ "src": [
"*", "*",
], ],
"Ports": [ "dst": [
"host-1:*", "host-1:*",
], ],
}, },

View File

@ -1,10 +1,10 @@
--- ---
Hosts: hosts:
host-1: 100.100.100.100/32 host-1: 100.100.100.100/32
subnet-1: 100.100.101.100/24 subnet-1: 100.100.101.100/24
ACLs: acls:
- Action: accept - action: accept
Users: src:
- "*" - "*"
Ports: dst:
- host-1:* - host-1:*

View File

@ -1,18 +1,18 @@
{ {
// Declare static groups of users beyond those in the identity service. // Declare static groups of users beyond those in the identity service.
"Groups": { "groups": {
"group:example": [ "group:example": [
"user1@example.com", "user1@example.com",
"user2@example.com", "user2@example.com",
], ],
}, },
// Declare hostname aliases to use in place of IP addresses or subnets. // Declare hostname aliases to use in place of IP addresses or subnets.
"Hosts": { "hosts": {
"example-host-1": "100.100.100.100", "example-host-1": "100.100.100.100",
"example-host-2": "100.100.101.100/24", "example-host-2": "100.100.101.100/24",
}, },
// Define who is allowed to use which tags. // Define who is allowed to use which tags.
"TagOwners": { "tagOwners": {
// Everyone in the montreal-admins or global-admins group are // Everyone in the montreal-admins or global-admins group are
// allowed to tag servers as montreal-webserver. // allowed to tag servers as montreal-webserver.
"tag:montreal-webserver": [ "tag:montreal-webserver": [
@ -26,17 +26,17 @@
], ],
}, },
// Access control lists. // Access control lists.
"ACLs": [ "acls": [
// Engineering users, plus the president, can access port 22 (ssh) // Engineering users, plus the president, can access port 22 (ssh)
// and port 3389 (remote desktop protocol) on all servers, and all // and port 3389 (remote desktop protocol) on all servers, and all
// ports on git-server or ci-server. // ports on git-server or ci-server.
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"group:engineering", "group:engineering",
"president@example.com" "president@example.com"
], ],
"Ports": [ "dst": [
"*:22,3389", "*:22,3389",
"git-server:*", "git-server:*",
"ci-server:*" "ci-server:*"
@ -45,23 +45,23 @@
// Allow engineer users to access any port on a device tagged with // Allow engineer users to access any port on a device tagged with
// tag:production. // tag:production.
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"group:engineers" "group:engineers"
], ],
"Ports": [ "dst": [
"tag:production:*" "tag:production:*"
], ],
}, },
// Allow servers in the my-subnet host and 192.168.1.0/24 to access hosts // Allow servers in the my-subnet host and 192.168.1.0/24 to access hosts
// on both networks. // on both networks.
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"my-subnet", "my-subnet",
"192.168.1.0/24" "192.168.1.0/24"
], ],
"Ports": [ "dst": [
"my-subnet:*", "my-subnet:*",
"192.168.1.0/24:*" "192.168.1.0/24:*"
], ],
@ -70,22 +70,22 @@
// Comment out this section if you want to define specific ACL // Comment out this section if you want to define specific ACL
// restrictions above. // restrictions above.
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"*" "*"
], ],
"Ports": [ "dst": [
"*:*" "*:*"
], ],
}, },
// All users in Montreal are allowed to access the Montreal web // All users in Montreal are allowed to access the Montreal web
// servers. // servers.
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"group:montreal-users" "group:montreal-users"
], ],
"Ports": [ "dst": [
"tag:montreal-webserver:80,443" "tag:montreal-webserver:80,443"
], ],
}, },
@ -94,30 +94,30 @@
// In contrast, this doesn't grant API servers the right to initiate // In contrast, this doesn't grant API servers the right to initiate
// any connections. // any connections.
{ {
"Action": "accept", "action": "accept",
"Users": [ "src": [
"tag:montreal-webserver" "tag:montreal-webserver"
], ],
"Ports": [ "dst": [
"tag:api-server:443" "tag:api-server:443"
], ],
}, },
], ],
// Declare tests to check functionality of ACL rules // Declare tests to check functionality of ACL rules
"Tests": [ "tests": [
{ {
"User": "user1@example.com", "src": "user1@example.com",
"Allow": [ "accept": [
"example-host-1:22", "example-host-1:22",
"example-host-2:80" "example-host-2:80"
], ],
"Deny": [ "deny": [
"exapmle-host-2:100" "exapmle-host-2:100"
], ],
}, },
{ {
"User": "user2@example.com", "src": "user2@example.com",
"Allow": [ "accept": [
"100.60.3.4:22" "100.60.3.4:22"
], ],
}, },