Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fail if the provided user name is not k8s compliant #94

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions pkg/cmd/generate/cluster.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package generate

import (
"errors"
"strings"

"github.com/kubesaw/ksctl/pkg/configuration"
"k8s.io/apimachinery/pkg/util/validation"
)

type clusterContext struct {
Expand Down Expand Up @@ -49,6 +53,9 @@ func ensureUsers(ctx *clusterContext, objsCache objectsCache) error {
if user.Selector.ShouldBeSkippedForMember(ctx.specificKMemberName) {
continue
}
if err := validateUserName(user.Name); err != nil {
return err
}
m := &permissionsManager{
objectsCache: objsCache,
createSubject: ensureUserIdentityAndGroups(user.ID, user.Groups),
Expand All @@ -67,3 +74,27 @@ func ensureUsers(ctx *clusterContext, objsCache objectsCache) error {

return nil
}

func validateUserName(userName string) error {
// check if the userName matches the format for k8s resource names
validationErrors := validation.IsDNS1123Subdomain(userName)
mfrancisc marked this conversation as resolved.
Show resolved Hide resolved
if len(validationErrors) == 0 {
// check if the userName matches the format for k8s label values
validationErrors = validation.IsValidLabelValue(userName)
if len(validationErrors) == 0 {
return nil
}
}
errs := make([]error, len(validationErrors))
for i := 0; i < len(validationErrors); i++ {
// the returned messages from the validators contain strings mentioning
// labels (in the case of label value validators), and subdomains (in the
// case of resource name validators). This can be confusing so let's replace
// these two strings with "User name" so it refers to the name defined in
// kubesaw-admins.yaml file.
message := strings.ReplaceAll(validationErrors[i], "label", "User name")
message = strings.ReplaceAll(message, "subdomain", "User name")
errs[i] = errors.New(message)
}
return errors.Join(errs...)
}
54 changes: 54 additions & 0 deletions pkg/cmd/generate/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/kubesaw/ksctl/pkg/assets"
"github.com/kubesaw/ksctl/pkg/configuration"
. "github.com/kubesaw/ksctl/pkg/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -230,6 +231,26 @@ func TestUsers(t *testing.T) {
hasNsClusterRole("toolchain-member-operator", "view", configuration.Member.AsSuffix("clusterrole-view-bob-crtadmin")).
hasClusterRoleBinding("cluster-monitoring-view", configuration.Member.AsSuffix("clusterrole-cluster-monitoring-view-bob-crtadmin"))
})

t.Run("returns error for invalid username", func(t *testing.T) {
for _, username := range invalidUserNames {
t.Run("username: "+username, func(t *testing.T) {
// given
kubeSawAdmins := newKubeSawAdminsWithDefaultClusters(
ServiceAccounts(),
Users(User("john+user", []string{"12345"}, true, "", permissionsForAllNamespaces...)),
)
ctx := newAdminManifestsContextWithDefaultFiles(t, kubeSawAdmins)
clusterCtx := newFakeClusterContext(ctx, configuration.Host)

// when
err := ensureUsers(clusterCtx, objectsCache{})

// then
require.Error(t, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually prefer checking the actual error just to make sure the error message is informative and it's actually the expected error and not something else. Unless it's tricky to hardcode the expected error message in the test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to checking the actual error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

})
}
})
}

func newKubeSawAdminsWithDefaultClusters(serviceAccounts []assets.ServiceAccount, users []assets.User) *assets.KubeSawAdmins {
Expand All @@ -238,3 +259,36 @@ func newKubeSawAdminsWithDefaultClusters(serviceAccounts []assets.ServiceAccount
serviceAccounts,
users)
}

var invalidUserNames = []string{
"special#-name", "special:-name", "-name", "name-", "special_name", "specialName",
mfrancisc marked this conversation as resolved.
Show resolved Hide resolved
"some-very-very-very-very-very-very-very-very-very-very-very-long-name",
}

func TestSanitizeUserName(t *testing.T) {
t.Run("valid usernames", func(t *testing.T) {
require.NoError(t, validateUserName("special-name"))
require.NoError(t, validateUserName("special.name"))
})
t.Run("invalid usernames", func(t *testing.T) {
for _, username := range invalidUserNames {
t.Run("username: "+username, func(t *testing.T) {
// when
err := validateUserName(username)

// then
// Let's not hard-code the actual expected errors here. It's something that we don't own,
// and it would be just matter of copy-pasting what is in the apimachinery library. Apart
// from that, the error are not short which would make the code really messy.
require.Error(t, err)
// the returned messages from the k8s validators contain strings mentioning
// labels (in the case of label value validators), and subdomains (in the
// case of resource name validators). The validateUserName function replaced
// these words to not make the final error message confusing.
// Let's verify that the final error doesn't contain these words.
assert.NotContains(t, err.Error(), "label")
assert.NotContains(t, err.Error(), "subdomain")
})
}
})
}
Loading