From a455a874add8b559eaa287c4d380ecda0196b357 Mon Sep 17 00:00:00 2001
From: Adrien Raffin-Caboisse <adrien.raffin@sekoia.fr>
Date: Tue, 1 Mar 2022 21:01:46 +0100
Subject: [PATCH 1/4] feat(acls): normalize the group name

---
 acls.go      |  46 ++++++++++++++++-----
 acls_test.go | 115 +++++++++++++++++++++++++++++++++++++++------------
 2 files changed, 124 insertions(+), 37 deletions(-)

diff --git a/acls.go b/acls.go
index 1a597c2..7b93ad7 100644
--- a/acls.go
+++ b/acls.go
@@ -160,7 +160,7 @@ func (h *Headscale) generateACLPolicySrcIP(
 	aclPolicy ACLPolicy,
 	u string,
 ) ([]string, error) {
-	return expandAlias(machines, aclPolicy, u)
+	return expandAlias(machines, aclPolicy, u, h.cfg.OIDC.StripEmaildomain)
 }
 
 func (h *Headscale) generateACLPolicyDestPorts(
@@ -186,7 +186,12 @@ func (h *Headscale) generateACLPolicyDestPorts(
 		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 {
 		return nil, err
 	}
@@ -218,6 +223,7 @@ func expandAlias(
 	machines []Machine,
 	aclPolicy ACLPolicy,
 	alias string,
+	stripEmailDomain bool,
 ) ([]string, error) {
 	ips := []string{}
 	if alias == "*" {
@@ -225,7 +231,7 @@ func expandAlias(
 	}
 
 	if strings.HasPrefix(alias, "group:") {
-		namespaces, err := expandGroup(aclPolicy, alias)
+		namespaces, err := expandGroup(aclPolicy, alias, stripEmailDomain)
 		if err != nil {
 			return ips, err
 		}
@@ -240,7 +246,7 @@ func expandAlias(
 	}
 
 	if strings.HasPrefix(alias, "tag:") {
-		owners, err := expandTagOwners(aclPolicy, alias)
+		owners, err := expandTagOwners(aclPolicy, alias, stripEmailDomain)
 		if err != nil {
 			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
 // 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
 	ows, ok := aclPolicy.TagOwners[tag]
 	if !ok {
@@ -408,7 +418,7 @@ func expandTagOwners(aclPolicy ACLPolicy, tag string) ([]string, error) {
 	}
 	for _, owner := range ows {
 		if strings.HasPrefix(owner, "group:") {
-			gs, err := expandGroup(aclPolicy, owner)
+			gs, err := expandGroup(aclPolicy, owner, stripEmailDomain)
 			if err != nil {
 				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
 // after some validation.
-func expandGroup(aclPolicy ACLPolicy, group string) ([]string, error) {
-	groups, ok := aclPolicy.Groups[group]
+func expandGroup(
+	aclPolicy ACLPolicy,
+	group string,
+	stripEmailDomain bool,
+) ([]string, error) {
+	outGroups := []string{}
+	aclGroups, ok := aclPolicy.Groups[group]
 	if !ok {
 		return []string{}, fmt.Errorf(
 			"group %v isn't registered. %w",
@@ -432,14 +447,23 @@ func expandGroup(aclPolicy ACLPolicy, group string) ([]string, error) {
 			errInvalidGroup,
 		)
 	}
-	for _, g := range groups {
-		if strings.HasPrefix(g, "group:") {
+	for _, group := range aclGroups {
+		if strings.HasPrefix(group, "group:") {
 			return []string{}, fmt.Errorf(
 				"%w. A group cannot be composed of groups. https://tailscale.com/kb/1018/acls/#groups",
 				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
 }
diff --git a/acls_test.go b/acls_test.go
index 9f0432a..6cee02c 100644
--- a/acls_test.go
+++ b/acls_test.go
@@ -430,8 +430,9 @@ func (s *Suite) TestPortGroup(c *check.C) {
 
 func Test_expandGroup(t *testing.T) {
 	type args struct {
-		aclPolicy ACLPolicy
-		group     string
+		aclPolicy        ACLPolicy
+		group            string
+		stripEmailDomain bool
 	}
 	tests := []struct {
 		name    string
@@ -448,7 +449,8 @@ func Test_expandGroup(t *testing.T) {
 						"group:foo":  []string{"user2", "user3"},
 					},
 				},
-				group: "group:test",
+				group:            "group:test",
+				stripEmailDomain: true,
 			},
 			want:    []string{"user1", "user2", "user3"},
 			wantErr: false,
@@ -462,15 +464,54 @@ func Test_expandGroup(t *testing.T) {
 						"group:foo":  []string{"user2", "user3"},
 					},
 				},
-				group: "group:undefined",
+				group:            "group:undefined",
+				stripEmailDomain: true,
 			},
 			want:    []string{},
 			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 {
 		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 {
 				t.Errorf("expandGroup() error = %v, wantErr %v", err, test.wantErr)
 
@@ -485,8 +526,9 @@ func Test_expandGroup(t *testing.T) {
 
 func Test_expandTagOwners(t *testing.T) {
 	type args struct {
-		aclPolicy ACLPolicy
-		tag       string
+		aclPolicy        ACLPolicy
+		tag              string
+		stripEmailDomain bool
 	}
 	tests := []struct {
 		name    string
@@ -500,7 +542,8 @@ func Test_expandTagOwners(t *testing.T) {
 				aclPolicy: ACLPolicy{
 					TagOwners: TagOwners{"tag:test": []string{"user1"}},
 				},
-				tag: "tag:test",
+				tag:              "tag:test",
+				stripEmailDomain: true,
 			},
 			want:    []string{"user1"},
 			wantErr: false,
@@ -512,7 +555,8 @@ func Test_expandTagOwners(t *testing.T) {
 					Groups:    Groups{"group:foo": []string{"user1", "user2"}},
 					TagOwners: TagOwners{"tag:test": []string{"group:foo"}},
 				},
-				tag: "tag:test",
+				tag:              "tag:test",
+				stripEmailDomain: true,
 			},
 			want:    []string{"user1", "user2"},
 			wantErr: false,
@@ -524,7 +568,8 @@ func Test_expandTagOwners(t *testing.T) {
 					Groups:    Groups{"group:foo": []string{"user1", "user2"}},
 					TagOwners: TagOwners{"tag:test": []string{"group:foo", "user3"}},
 				},
-				tag: "tag:test",
+				tag:              "tag:test",
+				stripEmailDomain: true,
 			},
 			want:    []string{"user1", "user2", "user3"},
 			wantErr: false,
@@ -535,7 +580,8 @@ func Test_expandTagOwners(t *testing.T) {
 				aclPolicy: ACLPolicy{
 					TagOwners: TagOwners{"tag:foo": []string{"group:foo", "user1"}},
 				},
-				tag: "tag:test",
+				tag:              "tag:test",
+				stripEmailDomain: true,
 			},
 			want:    []string{},
 			wantErr: true,
@@ -547,7 +593,8 @@ func Test_expandTagOwners(t *testing.T) {
 					Groups:    Groups{"group:bar": []string{"user1", "user2"}},
 					TagOwners: TagOwners{"tag:test": []string{"group:foo", "user2"}},
 				},
-				tag: "tag:test",
+				tag:              "tag:test",
+				stripEmailDomain: true,
 			},
 			want:    []string{},
 			wantErr: true,
@@ -555,7 +602,11 @@ func Test_expandTagOwners(t *testing.T) {
 	}
 	for _, test := range tests {
 		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 {
 				t.Errorf("expandTagOwners() error = %v, wantErr %v", err, test.wantErr)
 
@@ -717,9 +768,10 @@ func Test_listMachinesInNamespace(t *testing.T) {
 // nolint
 func Test_expandAlias(t *testing.T) {
 	type args struct {
-		machines  []Machine
-		aclPolicy ACLPolicy
-		alias     string
+		machines         []Machine
+		aclPolicy        ACLPolicy
+		alias            string
+		stripEmailDomain bool
 	}
 	tests := []struct {
 		name    string
@@ -739,7 +791,8 @@ func Test_expandAlias(t *testing.T) {
 						},
 					},
 				},
-				aclPolicy: ACLPolicy{},
+				aclPolicy:        ACLPolicy{},
+				stripEmailDomain: true,
 			},
 			want:    []string{"*"},
 			wantErr: false,
@@ -777,6 +830,7 @@ func Test_expandAlias(t *testing.T) {
 				aclPolicy: ACLPolicy{
 					Groups: Groups{"group:accountant": []string{"joe", "marc"}},
 				},
+				stripEmailDomain: true,
 			},
 			want:    []string{"100.64.0.1", "100.64.0.2", "100.64.0.3"},
 			wantErr: false,
@@ -814,6 +868,7 @@ func Test_expandAlias(t *testing.T) {
 				aclPolicy: ACLPolicy{
 					Groups: Groups{"group:accountant": []string{"joe", "marc"}},
 				},
+				stripEmailDomain: true,
 			},
 			want:    []string{},
 			wantErr: true,
@@ -821,9 +876,10 @@ func Test_expandAlias(t *testing.T) {
 		{
 			name: "simple ipaddress",
 			args: args{
-				alias:     "10.0.0.3",
-				machines:  []Machine{},
-				aclPolicy: ACLPolicy{},
+				alias:            "10.0.0.3",
+				machines:         []Machine{},
+				aclPolicy:        ACLPolicy{},
+				stripEmailDomain: true,
 			},
 			want:    []string{"10.0.0.3"},
 			wantErr: false,
@@ -838,6 +894,7 @@ func Test_expandAlias(t *testing.T) {
 						"homeNetwork": netaddr.MustParseIPPrefix("192.168.1.0/24"),
 					},
 				},
+				stripEmailDomain: true,
 			},
 			want:    []string{"192.168.1.0/24"},
 			wantErr: false,
@@ -845,9 +902,10 @@ func Test_expandAlias(t *testing.T) {
 		{
 			name: "simple host",
 			args: args{
-				alias:     "10.0.0.1",
-				machines:  []Machine{},
-				aclPolicy: ACLPolicy{},
+				alias:            "10.0.0.1",
+				machines:         []Machine{},
+				aclPolicy:        ACLPolicy{},
+				stripEmailDomain: true,
 			},
 			want:    []string{"10.0.0.1"},
 			wantErr: false,
@@ -855,9 +913,10 @@ func Test_expandAlias(t *testing.T) {
 		{
 			name: "simple CIDR",
 			args: args{
-				alias:     "10.0.0.0/16",
-				machines:  []Machine{},
-				aclPolicy: ACLPolicy{},
+				alias:            "10.0.0.0/16",
+				machines:         []Machine{},
+				aclPolicy:        ACLPolicy{},
+				stripEmailDomain: true,
 			},
 			want:    []string{"10.0.0.0/16"},
 			wantErr: false,
@@ -901,6 +960,7 @@ func Test_expandAlias(t *testing.T) {
 				aclPolicy: ACLPolicy{
 					TagOwners: TagOwners{"tag:hr-webserver": []string{"joe"}},
 				},
+				stripEmailDomain: true,
 			},
 			want:    []string{"100.64.0.1", "100.64.0.2"},
 			wantErr: false,
@@ -941,6 +1001,7 @@ func Test_expandAlias(t *testing.T) {
 						"tag:accountant-webserver": []string{"group:accountant"},
 					},
 				},
+				stripEmailDomain: true,
 			},
 			want:    []string{},
 			wantErr: true,
@@ -984,6 +1045,7 @@ func Test_expandAlias(t *testing.T) {
 				aclPolicy: ACLPolicy{
 					TagOwners: TagOwners{"tag:accountant-webserver": []string{"joe"}},
 				},
+				stripEmailDomain: true,
 			},
 			want:    []string{"100.64.0.4"},
 			wantErr: false,
@@ -995,6 +1057,7 @@ func Test_expandAlias(t *testing.T) {
 				test.args.machines,
 				test.args.aclPolicy,
 				test.args.alias,
+				test.args.stripEmailDomain,
 			)
 			if (err != nil) != test.wantErr {
 				t.Errorf("expandAlias() error = %v, wantErr %v", err, test.wantErr)

From b2dca80e7a2f0364d476972dc0d6de2e653939e5 Mon Sep 17 00:00:00 2001
From: Adrien Raffin-Caboisse <adrien.raffin@sekoia.fr>
Date: Tue, 1 Mar 2022 21:16:33 +0100
Subject: [PATCH 2/4] docs: update changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b2b93ef..c1b3ee8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@
 **Features**:
 
 - 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**:
 

From 361b4f7f4f48fc399da149ffc2bf4b8d5e76c074 Mon Sep 17 00:00:00 2001
From: Adrien Raffin-Caboisse <adrien.raffin@sekoia.fr>
Date: Tue, 1 Mar 2022 22:43:25 +0100
Subject: [PATCH 3/4] fix(machine): allow to use * in ACL sources

---
 machine.go      |  20 ++++-
 machine_test.go | 205 +++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 223 insertions(+), 2 deletions(-)

diff --git a/machine.go b/machine.go
index 3106d0b..479380a 100644
--- a/machine.go
+++ b/machine.go
@@ -173,6 +173,12 @@ func getFilteredByACLPeers(
 				machine.IPAddresses.ToStringSlice(),
 				peer.IPAddresses.ToStringSlice(),
 			) || // match source and destination
+				matchSourceAndDestinationWithRule(
+					rule.SrcIPs,
+					dst,
+					peer.IPAddresses.ToStringSlice(),
+					machine.IPAddresses.ToStringSlice(),
+				) || // match return path
 				matchSourceAndDestinationWithRule(
 					rule.SrcIPs,
 					dst,
@@ -182,9 +188,21 @@ func getFilteredByACLPeers(
 				matchSourceAndDestinationWithRule(
 					rule.SrcIPs,
 					dst,
+					[]string{"*"},
+					[]string{"*"},
+				) || // match source and all destination
+				matchSourceAndDestinationWithRule(
+					rule.SrcIPs,
+					dst,
+					[]string{"*"},
 					peer.IPAddresses.ToStringSlice(),
+				) || // match source and all destination
+				matchSourceAndDestinationWithRule(
+					rule.SrcIPs,
+					dst,
+					[]string{"*"},
 					machine.IPAddresses.ToStringSlice(),
-				) { // match return path
+				) { // match all sources and source
 				peers[peer.ID] = peer
 			}
 		}
diff --git a/machine_test.go b/machine_test.go
index e9c91f8..ed95aaf 100644
--- a/machine_test.go
+++ b/machine_test.go
@@ -296,6 +296,7 @@ func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) {
 	}
 }
 
+// nolint
 func Test_getFilteredByACLPeers(t *testing.T) {
 	type args struct {
 		machines []Machine
@@ -443,7 +444,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
 					},
 				},
 				machine: &Machine{ // current machine
-					ID:          1,
+					ID:          2,
 					IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
 					Namespace:   Namespace{Name: "marc"},
 				},
@@ -456,6 +457,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 {
 		t.Run(tt.name, func(t *testing.T) {

From 77fe0b01f7d42f51e67ea0307cf52c2c975844ab Mon Sep 17 00:00:00 2001
From: Adrien Raffin-Caboisse <adrien.raffin@sekoia.fr>
Date: Tue, 1 Mar 2022 22:50:22 +0100
Subject: [PATCH 4/4] docs: update changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c1b3ee8..d75388b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@
 **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 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):**