package headscale import ( "errors" "reflect" "testing" "gopkg.in/check.v1" "gorm.io/datatypes" "inet.af/netaddr" "tailscale.com/tailcfg" ) func (s *Suite) TestWrongPath(c *check.C) { err := app.LoadACLPolicy("asdfg") c.Assert(err, check.NotNil) } func (s *Suite) TestBrokenHuJson(c *check.C) { err := app.LoadACLPolicy("./tests/acls/broken.hujson") c.Assert(err, check.NotNil) } func (s *Suite) TestInvalidPolicyHuson(c *check.C) { err := app.LoadACLPolicy("./tests/acls/invalid.hujson") c.Assert(err, check.NotNil) c.Assert(err, check.Equals, errEmptyPolicy) } func (s *Suite) TestParseHosts(c *check.C) { var hosts Hosts err := hosts.UnmarshalJSON( []byte( `{"example-host-1": "100.100.100.100","example-host-2": "100.100.101.100/24"}`, ), ) c.Assert(hosts, check.NotNil) c.Assert(err, check.IsNil) } func (s *Suite) TestParseInvalidCIDR(c *check.C) { var hosts Hosts err := hosts.UnmarshalJSON([]byte(`{"example-host-1": "100.100.100.100/42"}`)) c.Assert(hosts, check.IsNil) c.Assert(err, check.NotNil) } func (s *Suite) TestRuleInvalidGeneration(c *check.C) { err := app.LoadACLPolicy("./tests/acls/acl_policy_invalid.hujson") c.Assert(err, check.NotNil) } func (s *Suite) TestBasicRule(c *check.C) { err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_1.hujson") c.Assert(err, check.IsNil) rules, err := app.generateACLRules() c.Assert(err, check.IsNil) c.Assert(rules, check.NotNil) } # TODO(kradalby): Make tests values safe, independent and descriptive. func (s *Suite) TestInvalidAction(c *check.C) { app.aclPolicy = &ACLPolicy{ ACLs: []ACL{ {Action: "invalidAction", Users: []string{"*"}, Ports: []string{"*:*"}}, }, } err := app.UpdateACLRules() c.Assert(errors.Is(err, errInvalidAction), check.Equals, true) } func (s *Suite) TestInvalidGroupInGroup(c *check.C) { // this ACL is wrong because the group in users sections doesn't exist app.aclPolicy = &ACLPolicy{ Groups: Groups{ "group:test": []string{"foo"}, "group:error": []string{"foo", "group:test"}, }, ACLs: []ACL{ {Action: "accept", Users: []string{"group:error"}, Ports: []string{"*:*"}}, }, } err := app.UpdateACLRules() c.Assert(errors.Is(err, errInvalidGroup), check.Equals, true) } func (s *Suite) TestInvalidTagOwners(c *check.C) { // this ACL is wrong because no tagOwners own the requested tag for the server app.aclPolicy = &ACLPolicy{ ACLs: []ACL{ {Action: "accept", Users: []string{"tag:foo"}, Ports: []string{"*:*"}}, }, } err := app.UpdateACLRules() c.Assert(errors.Is(err, errInvalidTag), check.Equals, true) } // 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. // the tag is matched in the Users section. func (s *Suite) TestValidExpandTagOwnersInUsers(c *check.C) { namespace, err := app.CreateNamespace("user1") c.Assert(err, check.IsNil) pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil) c.Assert(err, check.IsNil) _, err = app.GetMachine("user1", "testmachine") c.Assert(err, check.NotNil) hostInfo := []byte( "{\"OS\":\"centos\",\"Hostname\":\"testmachine\",\"RequestTags\":[\"tag:test\"]}", ) machine := Machine{ ID: 0, MachineKey: "foo", NodeKey: "bar", DiscoKey: "faa", Name: "testmachine", IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")}, NamespaceID: namespace.ID, Registered: true, RegisterMethod: RegisterMethodAuthKey, AuthKeyID: uint(pak.ID), HostInfo: datatypes.JSON(hostInfo), } app.db.Save(&machine) app.aclPolicy = &ACLPolicy{ Groups: Groups{"group:test": []string{"user1", "user2"}}, TagOwners: TagOwners{"tag:test": []string{"user3", "group:test"}}, ACLs: []ACL{ {Action: "accept", Users: []string{"tag:test"}, Ports: []string{"*:*"}}, }, } err = app.UpdateACLRules() c.Assert(err, check.IsNil) c.Assert(app.aclRules, check.HasLen, 1) c.Assert(app.aclRules[0].SrcIPs, check.HasLen, 1) c.Assert(app.aclRules[0].SrcIPs[0], check.Equals, "100.64.0.1") } // 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. // the tag is matched in the Ports section. func (s *Suite) TestValidExpandTagOwnersInPorts(c *check.C) { namespace, err := app.CreateNamespace("user1") c.Assert(err, check.IsNil) pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil) c.Assert(err, check.IsNil) _, err = app.GetMachine("user1", "testmachine") c.Assert(err, check.NotNil) hostInfo := []byte( "{\"OS\":\"centos\",\"Hostname\":\"testmachine\",\"RequestTags\":[\"tag:test\"]}", ) machine := Machine{ ID: 1, MachineKey: "12345", NodeKey: "bar", DiscoKey: "faa", Name: "testmachine", IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")}, NamespaceID: namespace.ID, Registered: true, RegisterMethod: RegisterMethodAuthKey, AuthKeyID: uint(pak.ID), HostInfo: datatypes.JSON(hostInfo), } app.db.Save(&machine) app.aclPolicy = &ACLPolicy{ Groups: Groups{"group:test": []string{"user1", "user2"}}, TagOwners: TagOwners{"tag:test": []string{"user3", "group:test"}}, ACLs: []ACL{ {Action: "accept", Users: []string{"*"}, Ports: []string{"tag:test:*"}}, }, } err = app.UpdateACLRules() c.Assert(err, check.IsNil) c.Assert(app.aclRules, check.HasLen, 1) c.Assert(app.aclRules[0].DstPorts, check.HasLen, 1) c.Assert(app.aclRules[0].DstPorts[0].IP, check.Equals, "100.64.0.1") } // need a test with: // tag on a host that isn't owned by a tag owners. So the namespace // of the host should be valid. func (s *Suite) TestInvalidTagValidNamespace(c *check.C) { namespace, err := app.CreateNamespace("user1") c.Assert(err, check.IsNil) pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil) c.Assert(err, check.IsNil) _, err = app.GetMachine("user1", "testmachine") c.Assert(err, check.NotNil) hostInfo := []byte( "{\"OS\":\"centos\",\"Hostname\":\"testmachine\",\"RequestTags\":[\"tag:foo\"]}", ) machine := Machine{ ID: 1, MachineKey: "12345", NodeKey: "bar", DiscoKey: "faa", Name: "testmachine", IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")}, NamespaceID: namespace.ID, Registered: true, RegisterMethod: RegisterMethodAuthKey, AuthKeyID: uint(pak.ID), HostInfo: datatypes.JSON(hostInfo), } app.db.Save(&machine) app.aclPolicy = &ACLPolicy{ TagOwners: TagOwners{"tag:test": []string{"user1"}}, ACLs: []ACL{ {Action: "accept", Users: []string{"user1"}, Ports: []string{"*:*"}}, }, } err = app.UpdateACLRules() c.Assert(err, check.IsNil) c.Assert(app.aclRules, check.HasLen, 1) c.Assert(app.aclRules[0].SrcIPs, check.HasLen, 1) c.Assert(app.aclRules[0].SrcIPs[0], check.Equals, "100.64.0.1") } // tag on a host is owned by a tag owner, the tag is valid. // an ACL rule is matching the tag to a namespace. It should not be valid since the // host should be tied to the tag now. func (s *Suite) TestValidTagInvalidNamespace(c *check.C) { namespace, err := app.CreateNamespace("user1") c.Assert(err, check.IsNil) pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil) c.Assert(err, check.IsNil) _, err = app.GetMachine("user1", "webserver") c.Assert(err, check.NotNil) hostInfo := []byte( "{\"OS\":\"centos\",\"Hostname\":\"webserver\",\"RequestTags\":[\"tag:webapp\"]}", ) machine := Machine{ ID: 1, MachineKey: "12345", NodeKey: "bar", DiscoKey: "faa", Name: "webserver", IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")}, NamespaceID: namespace.ID, Registered: true, RegisterMethod: RegisterMethodAuthKey, AuthKeyID: uint(pak.ID), HostInfo: datatypes.JSON(hostInfo), } app.db.Save(&machine) _, err = app.GetMachine("user1", "user") hostInfo = []byte("{\"OS\":\"debian\",\"Hostname\":\"user\"}") c.Assert(err, check.NotNil) machine = Machine{ ID: 2, MachineKey: "56789", NodeKey: "bar2", DiscoKey: "faab", Name: "user", IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")}, NamespaceID: namespace.ID, Registered: true, RegisterMethod: RegisterMethodAuthKey, AuthKeyID: uint(pak.ID), HostInfo: datatypes.JSON(hostInfo), } app.db.Save(&machine) app.aclPolicy = &ACLPolicy{ TagOwners: TagOwners{"tag:webapp": []string{"user1"}}, ACLs: []ACL{ { Action: "accept", Users: []string{"user1"}, Ports: []string{"tag:webapp:80,443"}, }, }, } err = app.UpdateACLRules() c.Assert(err, check.IsNil) c.Assert(app.aclRules, check.HasLen, 1) c.Assert(app.aclRules[0].SrcIPs, check.HasLen, 1) c.Assert(app.aclRules[0].SrcIPs[0], check.Equals, "100.64.0.2") c.Assert(app.aclRules[0].DstPorts, check.HasLen, 2) c.Assert(app.aclRules[0].DstPorts[0].Ports.First, check.Equals, uint16(80)) c.Assert(app.aclRules[0].DstPorts[0].Ports.Last, check.Equals, uint16(80)) c.Assert(app.aclRules[0].DstPorts[0].IP, check.Equals, "100.64.0.1") c.Assert(app.aclRules[0].DstPorts[1].Ports.First, check.Equals, uint16(443)) c.Assert(app.aclRules[0].DstPorts[1].Ports.Last, check.Equals, uint16(443)) c.Assert(app.aclRules[0].DstPorts[1].IP, check.Equals, "100.64.0.1") } func (s *Suite) TestPortRange(c *check.C) { err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_range.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, 1) c.Assert(rules[0].DstPorts, check.HasLen, 1) c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(5400)) c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(5500)) } func (s *Suite) TestPortWildcard(c *check.C) { err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_wildcards.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, 1) c.Assert(rules[0].DstPorts, check.HasLen, 1) c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(0)) c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535)) c.Assert(rules[0].SrcIPs, check.HasLen, 1) c.Assert(rules[0].SrcIPs[0], check.Equals, "*") } func (s *Suite) TestPortNamespace(c *check.C) { namespace, err := app.CreateNamespace("testnamespace") c.Assert(err, check.IsNil) pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil) c.Assert(err, check.IsNil) _, err = app.GetMachine("testnamespace", "testmachine") c.Assert(err, check.NotNil) ips, _ := app.getAvailableIPs() machine := Machine{ ID: 0, MachineKey: "12345", NodeKey: "bar", DiscoKey: "faa", Name: "testmachine", NamespaceID: namespace.ID, Registered: true, RegisterMethod: RegisterMethodAuthKey, IPAddresses: ips, AuthKeyID: uint(pak.ID), } app.db.Save(&machine) err = app.LoadACLPolicy( "./tests/acls/acl_policy_basic_namespace_as_user.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, 1) c.Assert(rules[0].DstPorts, check.HasLen, 1) c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(0)) c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535)) c.Assert(rules[0].SrcIPs, check.HasLen, 1) c.Assert(rules[0].SrcIPs[0], check.Not(check.Equals), "not an ip") c.Assert(len(ips), check.Equals, 1) c.Assert(rules[0].SrcIPs[0], check.Equals, ips[0].String()) } func (s *Suite) TestPortGroup(c *check.C) { namespace, err := app.CreateNamespace("testnamespace") c.Assert(err, check.IsNil) pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil) c.Assert(err, check.IsNil) _, err = app.GetMachine("testnamespace", "testmachine") c.Assert(err, check.NotNil) ips, _ := app.getAvailableIPs() machine := Machine{ ID: 0, MachineKey: "foo", NodeKey: "bar", DiscoKey: "faa", Name: "testmachine", NamespaceID: namespace.ID, Registered: true, RegisterMethod: RegisterMethodAuthKey, IPAddresses: ips, AuthKeyID: uint(pak.ID), } app.db.Save(&machine) err = app.LoadACLPolicy("./tests/acls/acl_policy_basic_groups.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, 1) c.Assert(rules[0].DstPorts, check.HasLen, 1) c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(0)) c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535)) c.Assert(rules[0].SrcIPs, check.HasLen, 1) c.Assert(rules[0].SrcIPs[0], check.Not(check.Equals), "not an ip") c.Assert(len(ips), check.Equals, 1) c.Assert(rules[0].SrcIPs[0], check.Equals, ips[0].String()) } func Test_expandGroup(t *testing.T) { type args struct { aclPolicy ACLPolicy group string } tests := []struct { name string args args want []string wantErr bool }{ { name: "simple test", args: args{ aclPolicy: ACLPolicy{ Groups: Groups{ "group:test": []string{"user1", "user2", "user3"}, "group:foo": []string{"user2", "user3"}, }, }, group: "group:test", }, want: []string{"user1", "user2", "user3"}, wantErr: false, }, { name: "InexistantGroup", args: args{ aclPolicy: ACLPolicy{ Groups: Groups{ "group:test": []string{"user1", "user2", "user3"}, "group:foo": []string{"user2", "user3"}, }, }, group: "group:undefined", }, want: []string{}, wantErr: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, err := expandGroup(test.args.aclPolicy, test.args.group) if (err != nil) != test.wantErr { t.Errorf("expandGroup() error = %v, wantErr %v", err, test.wantErr) return } if !reflect.DeepEqual(got, test.want) { t.Errorf("expandGroup() = %v, want %v", got, test.want) } }) } } func Test_expandTagOwners(t *testing.T) { type args struct { aclPolicy ACLPolicy tag string } tests := []struct { name string args args want []string wantErr bool }{ { name: "simple tag expansion", args: args{ aclPolicy: ACLPolicy{ TagOwners: TagOwners{"tag:test": []string{"user1"}}, }, tag: "tag:test", }, want: []string{"user1"}, wantErr: false, }, { name: "expand with tag and group", args: args{ aclPolicy: ACLPolicy{ Groups: Groups{"group:foo": []string{"user1", "user2"}}, TagOwners: TagOwners{"tag:test": []string{"group:foo"}}, }, tag: "tag:test", }, want: []string{"user1", "user2"}, wantErr: false, }, { name: "expand with namespace and group", args: args{ aclPolicy: ACLPolicy{ Groups: Groups{"group:foo": []string{"user1", "user2"}}, TagOwners: TagOwners{"tag:test": []string{"group:foo", "user3"}}, }, tag: "tag:test", }, want: []string{"user1", "user2", "user3"}, wantErr: false, }, { name: "invalid tag", args: args{ aclPolicy: ACLPolicy{ TagOwners: TagOwners{"tag:foo": []string{"group:foo", "user1"}}, }, tag: "tag:test", }, want: []string{}, wantErr: true, }, { name: "invalid group", args: args{ aclPolicy: ACLPolicy{ Groups: Groups{"group:bar": []string{"user1", "user2"}}, TagOwners: TagOwners{"tag:test": []string{"group:foo", "user2"}}, }, tag: "tag:test", }, want: []string{}, wantErr: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, err := expandTagOwners(test.args.aclPolicy, test.args.tag) if (err != nil) != test.wantErr { t.Errorf("expandTagOwners() error = %v, wantErr %v", err, test.wantErr) return } if !reflect.DeepEqual(got, test.want) { t.Errorf("expandTagOwners() = %v, want %v", got, test.want) } }) } } func Test_expandPorts(t *testing.T) { type args struct { portsStr string } tests := []struct { name string args args want *[]tailcfg.PortRange wantErr bool }{ { name: "wildcard", args: args{portsStr: "*"}, want: &[]tailcfg.PortRange{ {First: portRangeBegin, Last: portRangeEnd}, }, wantErr: false, }, { name: "two ports", args: args{portsStr: "80,443"}, want: &[]tailcfg.PortRange{ {First: 80, Last: 80}, {First: 443, Last: 443}, }, wantErr: false, }, { name: "a range and a port", args: args{portsStr: "80-1024,443"}, want: &[]tailcfg.PortRange{ {First: 80, Last: 1024}, {First: 443, Last: 443}, }, wantErr: false, }, { name: "out of bounds", args: args{portsStr: "854038"}, want: nil, wantErr: true, }, { name: "wrong port", args: args{portsStr: "85a38"}, want: nil, wantErr: true, }, { name: "wrong port in first", args: args{portsStr: "a-80"}, want: nil, wantErr: true, }, { name: "wrong port in last", args: args{portsStr: "80-85a38"}, want: nil, wantErr: true, }, { name: "wrong port format", args: args{portsStr: "80-85a38-3"}, want: nil, wantErr: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, err := expandPorts(test.args.portsStr) if (err != nil) != test.wantErr { t.Errorf("expandPorts() error = %v, wantErr %v", err, test.wantErr) return } if !reflect.DeepEqual(got, test.want) { t.Errorf("expandPorts() = %v, want %v", got, test.want) } }) } } func Test_listMachinesInNamespace(t *testing.T) { type args struct { machines []Machine namespace string } tests := []struct { name string args args want []Machine }{ { name: "1 machine in namespace", args: args{ machines: []Machine{ {Namespace: Namespace{Name: "joe"}}, }, namespace: "joe", }, want: []Machine{ {Namespace: Namespace{Name: "joe"}}, }, }, { name: "3 machines, 2 in namespace", args: args{ machines: []Machine{ {ID: 1, Namespace: Namespace{Name: "joe"}}, {ID: 2, Namespace: Namespace{Name: "marc"}}, {ID: 3, Namespace: Namespace{Name: "marc"}}, }, namespace: "marc", }, want: []Machine{ {ID: 2, Namespace: Namespace{Name: "marc"}}, {ID: 3, Namespace: Namespace{Name: "marc"}}, }, }, { name: "5 machines, 0 in namespace", args: args{ machines: []Machine{ {ID: 1, Namespace: Namespace{Name: "joe"}}, {ID: 2, Namespace: Namespace{Name: "marc"}}, {ID: 3, Namespace: Namespace{Name: "marc"}}, {ID: 4, Namespace: Namespace{Name: "marc"}}, {ID: 5, Namespace: Namespace{Name: "marc"}}, }, namespace: "mickael", }, want: []Machine{}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if got := filterMachinesByNamespace(test.args.machines, test.args.namespace); !reflect.DeepEqual( got, test.want, ) { t.Errorf("listMachinesInNamespace() = %v, want %v", got, test.want) } }) } } // nolint func Test_expandAlias(t *testing.T) { type args struct { machines []Machine aclPolicy ACLPolicy alias string } tests := []struct { name string args args want []string wantErr bool }{ { name: "wildcard", args: args{ alias: "*", machines: []Machine{ {IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")}}, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.78.84.227"), }, }, }, aclPolicy: ACLPolicy{}, }, want: []string{"*"}, wantErr: false, }, { name: "simple group", args: args{ alias: "group:accountant", machines: []Machine{ { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.1"), }, Namespace: Namespace{Name: "joe"}, }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.2"), }, Namespace: Namespace{Name: "joe"}, }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.3"), }, Namespace: Namespace{Name: "marc"}, }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.4"), }, Namespace: Namespace{Name: "mickael"}, }, }, aclPolicy: ACLPolicy{ Groups: Groups{"group:accountant": []string{"joe", "marc"}}, }, }, want: []string{"100.64.0.1", "100.64.0.2", "100.64.0.3"}, wantErr: false, }, { name: "wrong group", args: args{ alias: "group:hr", machines: []Machine{ { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.1"), }, Namespace: Namespace{Name: "joe"}, }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.2"), }, Namespace: Namespace{Name: "joe"}, }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.3"), }, Namespace: Namespace{Name: "marc"}, }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.4"), }, Namespace: Namespace{Name: "mickael"}, }, }, aclPolicy: ACLPolicy{ Groups: Groups{"group:accountant": []string{"joe", "marc"}}, }, }, want: []string{}, wantErr: true, }, { name: "simple ipaddress", args: args{ alias: "10.0.0.3", machines: []Machine{}, aclPolicy: ACLPolicy{}, }, want: []string{"10.0.0.3"}, wantErr: false, }, { name: "private network", args: args{ alias: "homeNetwork", machines: []Machine{}, aclPolicy: ACLPolicy{ Hosts: Hosts{ "homeNetwork": netaddr.MustParseIPPrefix("192.168.1.0/24"), }, }, }, want: []string{"192.168.1.0/24"}, wantErr: false, }, { name: "simple host", args: args{ alias: "10.0.0.1", machines: []Machine{}, aclPolicy: ACLPolicy{}, }, want: []string{"10.0.0.1"}, wantErr: false, }, { name: "simple CIDR", args: args{ alias: "10.0.0.0/16", machines: []Machine{}, aclPolicy: ACLPolicy{}, }, want: []string{"10.0.0.0/16"}, wantErr: false, }, { name: "simple tag", args: args{ alias: "tag:hr-webserver", machines: []Machine{ { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.1"), }, Namespace: Namespace{Name: "joe"}, HostInfo: []byte( "{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:hr-webserver\"]}", ), }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.2"), }, Namespace: Namespace{Name: "joe"}, HostInfo: []byte( "{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:hr-webserver\"]}", ), }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.3"), }, Namespace: Namespace{Name: "marc"}, }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.4"), }, Namespace: Namespace{Name: "joe"}, }, }, aclPolicy: ACLPolicy{ TagOwners: TagOwners{"tag:hr-webserver": []string{"joe"}}, }, }, want: []string{"100.64.0.1", "100.64.0.2"}, wantErr: false, }, { name: "No tag defined", args: args{ alias: "tag:hr-webserver", machines: []Machine{ { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.1"), }, Namespace: Namespace{Name: "joe"}, }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.2"), }, Namespace: Namespace{Name: "joe"}, }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.3"), }, Namespace: Namespace{Name: "marc"}, }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.4"), }, Namespace: Namespace{Name: "mickael"}, }, }, aclPolicy: ACLPolicy{ Groups: Groups{"group:accountant": []string{"joe", "marc"}}, TagOwners: TagOwners{ "tag:accountant-webserver": []string{"group:accountant"}, }, }, }, want: []string{}, wantErr: true, }, { name: "list host in namespace without correctly tagged servers", args: args{ alias: "joe", machines: []Machine{ { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.1"), }, Namespace: Namespace{Name: "joe"}, HostInfo: []byte( "{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}", ), }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.2"), }, Namespace: Namespace{Name: "joe"}, HostInfo: []byte( "{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}", ), }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.3"), }, Namespace: Namespace{Name: "marc"}, }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.4"), }, Namespace: Namespace{Name: "joe"}, }, }, aclPolicy: ACLPolicy{ TagOwners: TagOwners{"tag:accountant-webserver": []string{"joe"}}, }, }, want: []string{"100.64.0.4"}, wantErr: false, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, err := expandAlias( test.args.machines, test.args.aclPolicy, test.args.alias, ) if (err != nil) != test.wantErr { t.Errorf("expandAlias() error = %v, wantErr %v", err, test.wantErr) return } if !reflect.DeepEqual(got, test.want) { t.Errorf("expandAlias() = %v, want %v", got, test.want) } }) } } func Test_excludeCorrectlyTaggedNodes(t *testing.T) { type args struct { aclPolicy ACLPolicy nodes []Machine namespace string } tests := []struct { name string args args want []Machine wantErr bool }{ { name: "exclude nodes with valid tags", args: args{ aclPolicy: ACLPolicy{ TagOwners: TagOwners{"tag:accountant-webserver": []string{"joe"}}, }, nodes: []Machine{ { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.1"), }, Namespace: Namespace{Name: "joe"}, HostInfo: []byte( "{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}", ), }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.2"), }, Namespace: Namespace{Name: "joe"}, HostInfo: []byte( "{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}", ), }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.4"), }, Namespace: Namespace{Name: "joe"}, }, }, namespace: "joe", }, want: []Machine{ { IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.4")}, Namespace: Namespace{Name: "joe"}, }, }, wantErr: false, }, { name: "all nodes have invalid tags, don't exclude them", args: args{ aclPolicy: ACLPolicy{ TagOwners: TagOwners{"tag:accountant-webserver": []string{"joe"}}, }, nodes: []Machine{ { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.1"), }, Namespace: Namespace{Name: "joe"}, HostInfo: []byte( "{\"OS\":\"centos\",\"Hostname\":\"hr-web1\",\"RequestTags\":[\"tag:hr-webserver\"]}", ), }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.2"), }, Namespace: Namespace{Name: "joe"}, HostInfo: []byte( "{\"OS\":\"centos\",\"Hostname\":\"hr-web2\",\"RequestTags\":[\"tag:hr-webserver\"]}", ), }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.4"), }, Namespace: Namespace{Name: "joe"}, }, }, namespace: "joe", }, want: []Machine{ { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.1"), }, Namespace: Namespace{Name: "joe"}, HostInfo: []byte( "{\"OS\":\"centos\",\"Hostname\":\"hr-web1\",\"RequestTags\":[\"tag:hr-webserver\"]}", ), }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.2"), }, Namespace: Namespace{Name: "joe"}, HostInfo: []byte( "{\"OS\":\"centos\",\"Hostname\":\"hr-web2\",\"RequestTags\":[\"tag:hr-webserver\"]}", ), }, { IPAddresses: MachineAddresses{ netaddr.MustParseIP("100.64.0.4"), }, Namespace: Namespace{Name: "joe"}, }, }, wantErr: false, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, err := excludeCorrectlyTaggedNodes( test.args.aclPolicy, test.args.nodes, test.args.namespace, ) if (err != nil) != test.wantErr { t.Errorf( "excludeCorrectlyTaggedNodes() error = %v, wantErr %v", err, test.wantErr, ) return } if !reflect.DeepEqual(got, test.want) { t.Errorf("excludeCorrectlyTaggedNodes() = %v, want %v", got, test.want) } }) } }