diff --git a/namespaces.go b/namespaces.go index ec674b91..5062cc5e 100644 --- a/namespaces.go +++ b/namespaces.go @@ -27,7 +27,7 @@ const ( labelHostnameLength = 63 ) -var normalizeNamespaceRegex = regexp.MustCompile("[^a-z0-9-.]+") +var invalidCharsInNamespaceRegex = regexp.MustCompile("[^a-z0-9-.]+") // Namespace is the way Headscale implements the concept of users in Tailscale // @@ -281,7 +281,7 @@ func NormalizeNamespaceName(name string) (string, error) { name = strings.ToLower(name) name = strings.ReplaceAll(name, "@", ".") name = strings.ReplaceAll(name, "'", "") - name = normalizeNamespaceRegex.ReplaceAllString(name, "-") + name = invalidCharsInNamespaceRegex.ReplaceAllString(name, "-") for _, elt := range strings.Split(name, ".") { if len(elt) > labelHostnameLength { @@ -295,3 +295,29 @@ func NormalizeNamespaceName(name string) (string, error) { return name, nil } + +func CheckNamespaceName(name string) error { + if len(name) > labelHostnameLength { + return fmt.Errorf( + "Namespace must not be over 63 chars. %v doesn't comply with this rule: %w", + name, + errInvalidNamespaceName, + ) + } + if strings.ToLower(name) != name { + return fmt.Errorf( + "Namespace name should be lowercase. %v doesn't comply with this rule: %w", + name, + errInvalidNamespaceName, + ) + } + if invalidCharsInNamespaceRegex.MatchString(name) { + return fmt.Errorf( + "Namespace name should only be composed of lowercase ASCII letters numbers, hyphen and dots. %v doesn't comply with theses rules: %w", + name, + errInvalidNamespaceName, + ) + } + + return nil +} diff --git a/namespaces_test.go b/namespaces_test.go index d3519f9e..df4c5b00 100644 --- a/namespaces_test.go +++ b/namespaces_test.go @@ -74,13 +74,13 @@ func (s *Suite) TestRenameNamespace(c *check.C) { c.Assert(err, check.IsNil) c.Assert(len(namespaces), check.Equals, 1) - err = app.RenameNamespace("test", "test_renamed") + err = app.RenameNamespace("test", "test-renamed") c.Assert(err, check.IsNil) _, err = app.GetNamespace("test") c.Assert(err, check.Equals, errNamespaceNotFound) - _, err = app.GetNamespace("test_renamed") + _, err = app.GetNamespace("test-renamed") c.Assert(err, check.IsNil) err = app.RenameNamespace("test_does_not_exit", "test") @@ -90,7 +90,7 @@ func (s *Suite) TestRenameNamespace(c *check.C) { c.Assert(err, check.IsNil) c.Assert(namespaceTest2.Name, check.Equals, "test2") - err = app.RenameNamespace("test2", "test_renamed") + err = app.RenameNamespace("test2", "test-renamed") c.Assert(err, check.Equals, errNamespaceExists) } @@ -301,3 +301,49 @@ func TestNormalizeNamespaceName(t *testing.T) { }) } } + +func TestCheckNamespaceName(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "valid: namespace", + args: args{name: "valid-namespace"}, + wantErr: false, + }, + { + name: "invalid: capitalized namespace", + args: args{name: "Invalid-CapItaLIzed-namespace"}, + wantErr: true, + }, + { + name: "invalid: email as namespace", + args: args{name: "foo.bar@example.com"}, + wantErr: true, + }, + { + name: "invalid: chars in namespace name", + args: args{name: "super-namespace+name"}, + wantErr: true, + }, + { + name: "invalid: too long name for namespace", + args: args{ + name: "super-long-namespace-name-that-should-be-a-little-more-than-63-chars", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := CheckNamespaceName(tt.args.name); (err != nil) != tt.wantErr { + t.Errorf("CheckNamespaceName() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +}