From c413b808f114c711519205dc707ee526f05f4b79 Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Wed, 6 May 2026 12:11:37 +0100 Subject: [PATCH] fix(config): allow @ . : / in context names Real kubeconfig context names commonly contain characters the validator rejected: - 'admin@home', 'user@cluster.example.com' (kubectl rename, EKS aws-iam-authenticator) - 'cluster.example.com', 'gke_proj_zone_cluster.prod' (FQDN, GKE) - 'arn:aws:eks:us-east-1:123:cluster/foo' (EKS ARN) kubeconfig itself imposes no character restrictions, so requiring [a-zA-Z0-9_-] only was kportal-specific over-validation that blocked legitimate users. Widen the allowed set to add '@', '.', ':', '/'. Names must still start and end with a letter or digit so YAML specials and leading whitespace remain rejected. Tests cover the new positive cases and tighten negative coverage (starts-with-@, ends-with-/, ends-with-dot). --- internal/config/validator.go | 14 ++++++++++---- internal/config/validator_test.go | 17 +++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/internal/config/validator.go b/internal/config/validator.go index 9b4d90c..426932d 100644 --- a/internal/config/validator.go +++ b/internal/config/validator.go @@ -26,9 +26,15 @@ var ( // A series of DNS labels separated by dots (no consecutive dots allowed) dns1123SubdomainRegexp = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) - // contextNameRegexp matches valid context names - // Allows alphanumeric characters, hyphens, and underscores (to support various kubeconfig naming conventions) - contextNameRegexp = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$`) + // contextNameRegexp matches valid kubeconfig context names. + // kubeconfig itself imposes no character restriction; we accept the union + // of common naming conventions seen in the wild: + // - hyphens / underscores: minikube, docker-desktop, gke_proj_zone_cluster + // - "@": user@cluster (kubectl rename, EKS aws-iam-authenticator) + // - ".": cluster.example.com, GKE dotted names + // - ":" and "/": EKS ARNs (arn:aws:eks:us-east-1:123:cluster/foo) + // Must start and end with an alphanumeric character. + contextNameRegexp = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9._:/@_-]*[a-zA-Z0-9])?$`) // validResourceTypes contains the allowed Kubernetes resource types validResourceTypes = []string{"pod", "service"} @@ -571,7 +577,7 @@ func validateContextName(name, field string) *ValidationError { if !contextNameRegexp.MatchString(name) { return &ValidationError{ Field: field, - Message: fmt.Sprintf("Context name '%s' is not valid (must consist of alphanumeric characters, hyphens, or underscores, and start/end with alphanumeric)", name), + Message: fmt.Sprintf("Context name '%s' is not valid (allowed: letters, digits, hyphens, underscores, dots, '@', ':', '/'; must start and end with a letter or digit)", name), } } diff --git a/internal/config/validator_test.go b/internal/config/validator_test.go index 54018fc..6ccc93d 100644 --- a/internal/config/validator_test.go +++ b/internal/config/validator_test.go @@ -1524,7 +1524,7 @@ func TestValidator_ValidateContextAndNamespaceNames(t *testing.T) { }, }, expectErrors: true, - errorContains: []string{"not valid", "alphanumeric"}, + errorContains: []string{"not valid", "letter or digit"}, }, { name: "context name too long", @@ -1560,7 +1560,7 @@ func TestValidator_ValidateContextAndNamespaceNames(t *testing.T) { }, }, expectErrors: true, - errorContains: []string{"not valid", "start/end with alphanumeric"}, + errorContains: []string{"not valid", "letter or digit"}, }, { name: "invalid context name ends with underscore", @@ -1578,7 +1578,7 @@ func TestValidator_ValidateContextAndNamespaceNames(t *testing.T) { }, }, expectErrors: true, - errorContains: []string{"not valid", "start/end with alphanumeric"}, + errorContains: []string{"not valid", "letter or digit"}, }, { name: "invalid namespace name with spaces", @@ -1893,6 +1893,11 @@ func TestValidateContextName(t *testing.T) { {name: "valid single char", contextName: "a", errorMsg: "", expectError: false}, {name: "valid single digit", contextName: "1", errorMsg: "", expectError: false}, {name: "valid starts with digit", contextName: "123-cluster", errorMsg: "", expectError: false}, + {name: "valid user@cluster", contextName: "admin@home", errorMsg: "", expectError: false}, + {name: "valid user@fqdn", contextName: "user@cluster.example.com", errorMsg: "", expectError: false}, + {name: "valid dotted FQDN", contextName: "cluster.example.com", errorMsg: "", expectError: false}, + {name: "valid GKE dotted", contextName: "gke_proj_zone_cluster.prod", errorMsg: "", expectError: false}, + {name: "valid EKS ARN", contextName: "arn:aws:eks:us-east-1:123:cluster/foo", errorMsg: "", expectError: false}, // Invalid cases {name: "invalid empty", contextName: "", errorMsg: "not valid", expectError: true}, @@ -1900,10 +1905,10 @@ func TestValidateContextName(t *testing.T) { {name: "invalid ends with hyphen", contextName: "cluster-", errorMsg: "not valid", expectError: true}, {name: "invalid starts with underscore", contextName: "_cluster", errorMsg: "not valid", expectError: true}, {name: "invalid ends with underscore", contextName: "cluster_", errorMsg: "not valid", expectError: true}, + {name: "invalid starts with @", contextName: "@cluster", errorMsg: "not valid", expectError: true}, + {name: "invalid ends with /", contextName: "cluster/", errorMsg: "not valid", expectError: true}, + {name: "invalid ends with .", contextName: "cluster.", errorMsg: "not valid", expectError: true}, {name: "invalid with spaces", contextName: "my cluster", errorMsg: "not valid", expectError: true}, - {name: "invalid with dots", contextName: "my.cluster", errorMsg: "not valid", expectError: true}, - {name: "invalid with special chars", contextName: "cluster@123", errorMsg: "not valid", expectError: true}, - {name: "invalid with slash", contextName: "cluster/name", errorMsg: "not valid", expectError: true}, {name: "invalid too long", contextName: strings.Repeat("a", 254), errorMsg: "exceeds maximum length", expectError: true}, }