Merge branch 'main' into registration-simplification

This commit is contained in:
Kristoffer Dalby 2022-03-02 07:31:02 +00:00 committed by GitHub
commit e4d81bbb16
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 349 additions and 39 deletions

View file

@ -12,12 +12,14 @@
### Features ### Features
- Add support for writing ACL files with YAML [#359](https://github.com/juanfont/headscale/pull/359) - Add support for writing ACL files with YAML [#359](https://github.com/juanfont/headscale/pull/359)
- Users can now use emails in ACL's groups [#372](https://github.com/juanfont/headscale/issues/372)
### Changes ### Changes
- Fix a bug were the same IP could be assigned to multiple hosts if joined in quick succession [#346](https://github.com/juanfont/headscale/pull/346) - Fix a bug were the same IP could be assigned to multiple hosts if joined in quick succession [#346](https://github.com/juanfont/headscale/pull/346)
- Simplify the code behind registration of machines [#366](https://github.com/juanfont/headscale/pull/366) - Simplify the code behind registration of machines [#366](https://github.com/juanfont/headscale/pull/366)
- Nodes are now only written to database if they are registrated successfully - Nodes are now only written to database if they are registrated successfully
- Fix a limitation in the ACLs that prevented users to write rules with `*` as source [#374](https://github.com/juanfont/headscale/issues/374)
## 0.14.0 (2022-02-24) ## 0.14.0 (2022-02-24)

46
acls.go
View file

@ -160,7 +160,7 @@ func (h *Headscale) generateACLPolicySrcIP(
aclPolicy ACLPolicy, aclPolicy ACLPolicy,
u string, u string,
) ([]string, error) { ) ([]string, error) {
return expandAlias(machines, aclPolicy, u) return expandAlias(machines, aclPolicy, u, h.cfg.OIDC.StripEmaildomain)
} }
func (h *Headscale) generateACLPolicyDestPorts( func (h *Headscale) generateACLPolicyDestPorts(
@ -186,7 +186,12 @@ func (h *Headscale) generateACLPolicyDestPorts(
alias = fmt.Sprintf("%s:%s", tokens[0], tokens[1]) alias = fmt.Sprintf("%s:%s", tokens[0], tokens[1])
} }
expanded, err := expandAlias(machines, aclPolicy, alias) expanded, err := expandAlias(
machines,
aclPolicy,
alias,
h.cfg.OIDC.StripEmaildomain,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -218,6 +223,7 @@ func expandAlias(
machines []Machine, machines []Machine,
aclPolicy ACLPolicy, aclPolicy ACLPolicy,
alias string, alias string,
stripEmailDomain bool,
) ([]string, error) { ) ([]string, error) {
ips := []string{} ips := []string{}
if alias == "*" { if alias == "*" {
@ -225,7 +231,7 @@ func expandAlias(
} }
if strings.HasPrefix(alias, "group:") { if strings.HasPrefix(alias, "group:") {
namespaces, err := expandGroup(aclPolicy, alias) namespaces, err := expandGroup(aclPolicy, alias, stripEmailDomain)
if err != nil { if err != nil {
return ips, err return ips, err
} }
@ -240,7 +246,7 @@ func expandAlias(
} }
if strings.HasPrefix(alias, "tag:") { if strings.HasPrefix(alias, "tag:") {
owners, err := expandTagOwners(aclPolicy, alias) owners, err := expandTagOwners(aclPolicy, alias, stripEmailDomain)
if err != nil { if err != nil {
return ips, err return ips, err
} }
@ -396,7 +402,11 @@ func filterMachinesByNamespace(machines []Machine, namespace string) []Machine {
// expandTagOwners will return a list of namespace. An owner can be either a namespace or a group // expandTagOwners will return a list of namespace. An owner can be either a namespace or a group
// a group cannot be composed of groups. // a group cannot be composed of groups.
func expandTagOwners(aclPolicy ACLPolicy, tag string) ([]string, error) { func expandTagOwners(
aclPolicy ACLPolicy,
tag string,
stripEmailDomain bool,
) ([]string, error) {
var owners []string var owners []string
ows, ok := aclPolicy.TagOwners[tag] ows, ok := aclPolicy.TagOwners[tag]
if !ok { if !ok {
@ -408,7 +418,7 @@ func expandTagOwners(aclPolicy ACLPolicy, tag string) ([]string, error) {
} }
for _, owner := range ows { for _, owner := range ows {
if strings.HasPrefix(owner, "group:") { if strings.HasPrefix(owner, "group:") {
gs, err := expandGroup(aclPolicy, owner) gs, err := expandGroup(aclPolicy, owner, stripEmailDomain)
if err != nil { if err != nil {
return []string{}, err return []string{}, err
} }
@ -423,8 +433,13 @@ func expandTagOwners(aclPolicy ACLPolicy, tag string) ([]string, error) {
// expandGroup will return the list of namespace inside the group // expandGroup will return the list of namespace inside the group
// after some validation. // after some validation.
func expandGroup(aclPolicy ACLPolicy, group string) ([]string, error) { func expandGroup(
groups, ok := aclPolicy.Groups[group] aclPolicy ACLPolicy,
group string,
stripEmailDomain bool,
) ([]string, error) {
outGroups := []string{}
aclGroups, ok := aclPolicy.Groups[group]
if !ok { if !ok {
return []string{}, fmt.Errorf( return []string{}, fmt.Errorf(
"group %v isn't registered. %w", "group %v isn't registered. %w",
@ -432,14 +447,23 @@ func expandGroup(aclPolicy ACLPolicy, group string) ([]string, error) {
errInvalidGroup, errInvalidGroup,
) )
} }
for _, g := range groups { for _, group := range aclGroups {
if strings.HasPrefix(g, "group:") { if strings.HasPrefix(group, "group:") {
return []string{}, fmt.Errorf( return []string{}, fmt.Errorf(
"%w. A group cannot be composed of groups. https://tailscale.com/kb/1018/acls/#groups", "%w. A group cannot be composed of groups. https://tailscale.com/kb/1018/acls/#groups",
errInvalidGroup, errInvalidGroup,
) )
} }
grp, err := NormalizeNamespaceName(group, stripEmailDomain)
if err != nil {
return []string{}, fmt.Errorf(
"failed to normalize group %q, err: %w",
group,
errInvalidGroup,
)
}
outGroups = append(outGroups, grp)
} }
return groups, nil return outGroups, nil
} }

View file

@ -425,6 +425,7 @@ func Test_expandGroup(t *testing.T) {
type args struct { type args struct {
aclPolicy ACLPolicy aclPolicy ACLPolicy
group string group string
stripEmailDomain bool
} }
tests := []struct { tests := []struct {
name string name string
@ -442,6 +443,7 @@ func Test_expandGroup(t *testing.T) {
}, },
}, },
group: "group:test", group: "group:test",
stripEmailDomain: true,
}, },
want: []string{"user1", "user2", "user3"}, want: []string{"user1", "user2", "user3"},
wantErr: false, wantErr: false,
@ -456,14 +458,53 @@ func Test_expandGroup(t *testing.T) {
}, },
}, },
group: "group:undefined", group: "group:undefined",
stripEmailDomain: true,
}, },
want: []string{}, want: []string{},
wantErr: true, wantErr: true,
}, },
{
name: "Expand emails in group",
args: args{
aclPolicy: ACLPolicy{
Groups: Groups{
"group:admin": []string{
"joe.bar@gmail.com",
"john.doe@yahoo.fr",
},
},
},
group: "group:admin",
stripEmailDomain: true,
},
want: []string{"joe.bar", "john.doe"},
wantErr: false,
},
{
name: "Expand emails in group",
args: args{
aclPolicy: ACLPolicy{
Groups: Groups{
"group:admin": []string{
"joe.bar@gmail.com",
"john.doe@yahoo.fr",
},
},
},
group: "group:admin",
stripEmailDomain: false,
},
want: []string{"joe.bar.gmail.com", "john.doe.yahoo.fr"},
wantErr: false,
},
} }
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 := expandGroup(test.args.aclPolicy, test.args.group) got, err := expandGroup(
test.args.aclPolicy,
test.args.group,
test.args.stripEmailDomain,
)
if (err != nil) != test.wantErr { if (err != nil) != test.wantErr {
t.Errorf("expandGroup() error = %v, wantErr %v", err, test.wantErr) t.Errorf("expandGroup() error = %v, wantErr %v", err, test.wantErr)
@ -480,6 +521,7 @@ func Test_expandTagOwners(t *testing.T) {
type args struct { type args struct {
aclPolicy ACLPolicy aclPolicy ACLPolicy
tag string tag string
stripEmailDomain bool
} }
tests := []struct { tests := []struct {
name string name string
@ -494,6 +536,7 @@ func Test_expandTagOwners(t *testing.T) {
TagOwners: TagOwners{"tag:test": []string{"user1"}}, TagOwners: TagOwners{"tag:test": []string{"user1"}},
}, },
tag: "tag:test", tag: "tag:test",
stripEmailDomain: true,
}, },
want: []string{"user1"}, want: []string{"user1"},
wantErr: false, wantErr: false,
@ -506,6 +549,7 @@ func Test_expandTagOwners(t *testing.T) {
TagOwners: TagOwners{"tag:test": []string{"group:foo"}}, TagOwners: TagOwners{"tag:test": []string{"group:foo"}},
}, },
tag: "tag:test", tag: "tag:test",
stripEmailDomain: true,
}, },
want: []string{"user1", "user2"}, want: []string{"user1", "user2"},
wantErr: false, wantErr: false,
@ -518,6 +562,7 @@ func Test_expandTagOwners(t *testing.T) {
TagOwners: TagOwners{"tag:test": []string{"group:foo", "user3"}}, TagOwners: TagOwners{"tag:test": []string{"group:foo", "user3"}},
}, },
tag: "tag:test", tag: "tag:test",
stripEmailDomain: true,
}, },
want: []string{"user1", "user2", "user3"}, want: []string{"user1", "user2", "user3"},
wantErr: false, wantErr: false,
@ -529,6 +574,7 @@ func Test_expandTagOwners(t *testing.T) {
TagOwners: TagOwners{"tag:foo": []string{"group:foo", "user1"}}, TagOwners: TagOwners{"tag:foo": []string{"group:foo", "user1"}},
}, },
tag: "tag:test", tag: "tag:test",
stripEmailDomain: true,
}, },
want: []string{}, want: []string{},
wantErr: true, wantErr: true,
@ -541,6 +587,7 @@ func Test_expandTagOwners(t *testing.T) {
TagOwners: TagOwners{"tag:test": []string{"group:foo", "user2"}}, TagOwners: TagOwners{"tag:test": []string{"group:foo", "user2"}},
}, },
tag: "tag:test", tag: "tag:test",
stripEmailDomain: true,
}, },
want: []string{}, want: []string{},
wantErr: true, wantErr: true,
@ -548,7 +595,11 @@ func Test_expandTagOwners(t *testing.T) {
} }
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 := expandTagOwners(test.args.aclPolicy, test.args.tag) got, err := expandTagOwners(
test.args.aclPolicy,
test.args.tag,
test.args.stripEmailDomain,
)
if (err != nil) != test.wantErr { if (err != nil) != test.wantErr {
t.Errorf("expandTagOwners() error = %v, wantErr %v", err, test.wantErr) t.Errorf("expandTagOwners() error = %v, wantErr %v", err, test.wantErr)
@ -713,6 +764,7 @@ func Test_expandAlias(t *testing.T) {
machines []Machine machines []Machine
aclPolicy ACLPolicy aclPolicy ACLPolicy
alias string alias string
stripEmailDomain bool
} }
tests := []struct { tests := []struct {
name string name string
@ -733,6 +785,7 @@ func Test_expandAlias(t *testing.T) {
}, },
}, },
aclPolicy: ACLPolicy{}, aclPolicy: ACLPolicy{},
stripEmailDomain: true,
}, },
want: []string{"*"}, want: []string{"*"},
wantErr: false, wantErr: false,
@ -770,6 +823,7 @@ func Test_expandAlias(t *testing.T) {
aclPolicy: ACLPolicy{ aclPolicy: ACLPolicy{
Groups: Groups{"group:accountant": []string{"joe", "marc"}}, Groups: Groups{"group:accountant": []string{"joe", "marc"}},
}, },
stripEmailDomain: true,
}, },
want: []string{"100.64.0.1", "100.64.0.2", "100.64.0.3"}, want: []string{"100.64.0.1", "100.64.0.2", "100.64.0.3"},
wantErr: false, wantErr: false,
@ -807,6 +861,7 @@ func Test_expandAlias(t *testing.T) {
aclPolicy: ACLPolicy{ aclPolicy: ACLPolicy{
Groups: Groups{"group:accountant": []string{"joe", "marc"}}, Groups: Groups{"group:accountant": []string{"joe", "marc"}},
}, },
stripEmailDomain: true,
}, },
want: []string{}, want: []string{},
wantErr: true, wantErr: true,
@ -817,6 +872,7 @@ func Test_expandAlias(t *testing.T) {
alias: "10.0.0.3", alias: "10.0.0.3",
machines: []Machine{}, machines: []Machine{},
aclPolicy: ACLPolicy{}, aclPolicy: ACLPolicy{},
stripEmailDomain: true,
}, },
want: []string{"10.0.0.3"}, want: []string{"10.0.0.3"},
wantErr: false, wantErr: false,
@ -831,6 +887,7 @@ func Test_expandAlias(t *testing.T) {
"homeNetwork": netaddr.MustParseIPPrefix("192.168.1.0/24"), "homeNetwork": netaddr.MustParseIPPrefix("192.168.1.0/24"),
}, },
}, },
stripEmailDomain: true,
}, },
want: []string{"192.168.1.0/24"}, want: []string{"192.168.1.0/24"},
wantErr: false, wantErr: false,
@ -841,6 +898,7 @@ func Test_expandAlias(t *testing.T) {
alias: "10.0.0.1", alias: "10.0.0.1",
machines: []Machine{}, machines: []Machine{},
aclPolicy: ACLPolicy{}, aclPolicy: ACLPolicy{},
stripEmailDomain: true,
}, },
want: []string{"10.0.0.1"}, want: []string{"10.0.0.1"},
wantErr: false, wantErr: false,
@ -851,6 +909,7 @@ func Test_expandAlias(t *testing.T) {
alias: "10.0.0.0/16", alias: "10.0.0.0/16",
machines: []Machine{}, machines: []Machine{},
aclPolicy: ACLPolicy{}, aclPolicy: ACLPolicy{},
stripEmailDomain: true,
}, },
want: []string{"10.0.0.0/16"}, want: []string{"10.0.0.0/16"},
wantErr: false, wantErr: false,
@ -894,6 +953,7 @@ func Test_expandAlias(t *testing.T) {
aclPolicy: ACLPolicy{ aclPolicy: ACLPolicy{
TagOwners: TagOwners{"tag:hr-webserver": []string{"joe"}}, TagOwners: TagOwners{"tag:hr-webserver": []string{"joe"}},
}, },
stripEmailDomain: true,
}, },
want: []string{"100.64.0.1", "100.64.0.2"}, want: []string{"100.64.0.1", "100.64.0.2"},
wantErr: false, wantErr: false,
@ -934,6 +994,7 @@ func Test_expandAlias(t *testing.T) {
"tag:accountant-webserver": []string{"group:accountant"}, "tag:accountant-webserver": []string{"group:accountant"},
}, },
}, },
stripEmailDomain: true,
}, },
want: []string{}, want: []string{},
wantErr: true, wantErr: true,
@ -977,6 +1038,7 @@ func Test_expandAlias(t *testing.T) {
aclPolicy: ACLPolicy{ aclPolicy: ACLPolicy{
TagOwners: TagOwners{"tag:accountant-webserver": []string{"joe"}}, TagOwners: TagOwners{"tag:accountant-webserver": []string{"joe"}},
}, },
stripEmailDomain: true,
}, },
want: []string{"100.64.0.4"}, want: []string{"100.64.0.4"},
wantErr: false, wantErr: false,
@ -988,6 +1050,7 @@ func Test_expandAlias(t *testing.T) {
test.args.machines, test.args.machines,
test.args.aclPolicy, test.args.aclPolicy,
test.args.alias, test.args.alias,
test.args.stripEmailDomain,
) )
if (err != nil) != test.wantErr { if (err != nil) != test.wantErr {
t.Errorf("expandAlias() error = %v, wantErr %v", err, test.wantErr) t.Errorf("expandAlias() error = %v, wantErr %v", err, test.wantErr)

View file

@ -174,6 +174,12 @@ func getFilteredByACLPeers(
machine.IPAddresses.ToStringSlice(), machine.IPAddresses.ToStringSlice(),
peer.IPAddresses.ToStringSlice(), peer.IPAddresses.ToStringSlice(),
) || // match source and destination ) || // match source and destination
matchSourceAndDestinationWithRule(
rule.SrcIPs,
dst,
peer.IPAddresses.ToStringSlice(),
machine.IPAddresses.ToStringSlice(),
) || // match return path
matchSourceAndDestinationWithRule( matchSourceAndDestinationWithRule(
rule.SrcIPs, rule.SrcIPs,
dst, dst,
@ -183,9 +189,21 @@ func getFilteredByACLPeers(
matchSourceAndDestinationWithRule( matchSourceAndDestinationWithRule(
rule.SrcIPs, rule.SrcIPs,
dst, dst,
[]string{"*"},
[]string{"*"},
) || // match source and all destination
matchSourceAndDestinationWithRule(
rule.SrcIPs,
dst,
[]string{"*"},
peer.IPAddresses.ToStringSlice(), peer.IPAddresses.ToStringSlice(),
) || // match source and all destination
matchSourceAndDestinationWithRule(
rule.SrcIPs,
dst,
[]string{"*"},
machine.IPAddresses.ToStringSlice(), machine.IPAddresses.ToStringSlice(),
) { // match return path ) { // match all sources and source
peers[peer.ID] = peer peers[peer.ID] = peer
} }
} }

View file

@ -289,6 +289,7 @@ func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) {
} }
} }
// nolint
func Test_getFilteredByACLPeers(t *testing.T) { func Test_getFilteredByACLPeers(t *testing.T) {
type args struct { type args struct {
machines []Machine machines []Machine
@ -436,7 +437,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
}, },
}, },
machine: &Machine{ // current machine machine: &Machine{ // current machine
ID: 1, ID: 2,
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")}, IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
Namespace: Namespace{Name: "marc"}, Namespace: Namespace{Name: "marc"},
}, },
@ -449,6 +450,208 @@ func Test_getFilteredByACLPeers(t *testing.T) {
}, },
}, },
}, },
{
name: "rules allows all hosts to reach one destination",
args: args{
machines: []Machine{ // list of all machines in the database
{
ID: 1,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.1"),
},
Namespace: Namespace{Name: "joe"},
},
{
ID: 2,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.2"),
},
Namespace: Namespace{Name: "marc"},
},
{
ID: 3,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.3"),
},
Namespace: Namespace{Name: "mickael"},
},
},
rules: []tailcfg.FilterRule{ // list of all ACLRules registered
{
SrcIPs: []string{"*"},
DstPorts: []tailcfg.NetPortRange{
{IP: "100.64.0.2"},
},
},
},
machine: &Machine{ // current machine
ID: 1,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.1"),
},
Namespace: Namespace{Name: "joe"},
},
},
want: Machines{
{
ID: 2,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.2"),
},
Namespace: Namespace{Name: "marc"},
},
},
},
{
name: "rules allows all hosts to reach one destination, destination can reach all hosts",
args: args{
machines: []Machine{ // list of all machines in the database
{
ID: 1,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.1"),
},
Namespace: Namespace{Name: "joe"},
},
{
ID: 2,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.2"),
},
Namespace: Namespace{Name: "marc"},
},
{
ID: 3,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.3"),
},
Namespace: Namespace{Name: "mickael"},
},
},
rules: []tailcfg.FilterRule{ // list of all ACLRules registered
{
SrcIPs: []string{"*"},
DstPorts: []tailcfg.NetPortRange{
{IP: "100.64.0.2"},
},
},
},
machine: &Machine{ // current machine
ID: 2,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.2"),
},
Namespace: Namespace{Name: "marc"},
},
},
want: Machines{
{
ID: 1,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.1"),
},
Namespace: Namespace{Name: "joe"},
},
{
ID: 3,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.3"),
},
Namespace: Namespace{Name: "mickael"},
},
},
},
{
name: "rule allows all hosts to reach all destinations",
args: args{
machines: []Machine{ // list of all machines in the database
{
ID: 1,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.1"),
},
Namespace: Namespace{Name: "joe"},
},
{
ID: 2,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.2"),
},
Namespace: Namespace{Name: "marc"},
},
{
ID: 3,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.3"),
},
Namespace: Namespace{Name: "mickael"},
},
},
rules: []tailcfg.FilterRule{ // list of all ACLRules registered
{
SrcIPs: []string{"*"},
DstPorts: []tailcfg.NetPortRange{
{IP: "*"},
},
},
},
machine: &Machine{ // current machine
ID: 2,
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
Namespace: Namespace{Name: "marc"},
},
},
want: Machines{
{
ID: 1,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.1"),
},
Namespace: Namespace{Name: "joe"},
},
{
ID: 3,
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.3")},
Namespace: Namespace{Name: "mickael"},
},
},
},
{
name: "without rule all communications are forbidden",
args: args{
machines: []Machine{ // list of all machines in the database
{
ID: 1,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.1"),
},
Namespace: Namespace{Name: "joe"},
},
{
ID: 2,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.2"),
},
Namespace: Namespace{Name: "marc"},
},
{
ID: 3,
IPAddresses: MachineAddresses{
netaddr.MustParseIP("100.64.0.3"),
},
Namespace: Namespace{Name: "mickael"},
},
},
rules: []tailcfg.FilterRule{ // list of all ACLRules registered
},
machine: &Machine{ // current machine
ID: 2,
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
Namespace: Namespace{Name: "marc"},
},
},
want: Machines{},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {