From 36772d7b3e947b8fd6f47e8bde71f8b600fc56eb Mon Sep 17 00:00:00 2001 From: Andrew Prokhorenkov Date: Mon, 4 May 2026 10:56:27 -0500 Subject: [PATCH 1/4] fix for long volume names --- hatchery/sharedworkspace.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/hatchery/sharedworkspace.go b/hatchery/sharedworkspace.go index 3756d75..4909a5f 100644 --- a/hatchery/sharedworkspace.go +++ b/hatchery/sharedworkspace.go @@ -2,6 +2,8 @@ package hatchery import ( "context" + "crypto/sha256" + "encoding/hex" "encoding/json" "fmt" "io" @@ -14,6 +16,7 @@ import ( "github.com/aws/aws-sdk-go/service/iam" k8sv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8svalidation "k8s.io/apimachinery/pkg/util/validation" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" ) @@ -190,6 +193,24 @@ func sharedPVCName(userName, prefixName string) string { return fmt.Sprintf("shared-claim-%s-%s", escapism(userName), escapism(prefixName)) } +// shortenedVolumeName returns the volume name hashed version. +func shortenedVolumeName(prefix string, name string) string { + const dns1123LabelMaxLength = 63 + sum := sha256.Sum256([]byte(name)) + hash := hex.EncodeToString(sum[:])[:16] + out := fmt.Sprintf("%s-%s", prefix, hash) + return out +} + +// sharedVolName returns the volume name or a hashed version. +func sharedVolName(name string) string { + old := fmt.Sprintf("shared-%s", escapism(name)) + if len(k8svalidation.IsDNS1123Label(old)) == 0 { + return old + } + return shortenedVolumeName("shared", name) +} + // getSharedWorkspacePrefixes calls the configured external API with the user's // bearer token and returns the list of S3 prefixes the user may access. func getSharedWorkspacePrefixes(ctx context.Context, accessToken string) ([]SharedWorkspacePrefix, error) { @@ -333,7 +354,7 @@ func addSharedWorkspaceVolumesToPod(pod *k8sv1.Pod, userName string, prefixes [] } for _, prefix := range prefixes { pvcName := sharedPVCName(userName, prefix.Name) - volName := fmt.Sprintf("shared-%s", escapism(prefix.Name)) + volName := sharedVolName(prefix.Name) relativePrefixPath := strings.Trim(prefix.Prefix, "/") if relativePrefixPath == "" { relativePrefixPath = prefix.Name From f8af61aee70a9f3413892e694f1e344bfcbe439b Mon Sep 17 00:00:00 2001 From: Andrew Prokhorenkov Date: Mon, 4 May 2026 14:16:40 -0500 Subject: [PATCH 2/4] rm extra line --- hatchery/sharedworkspace.go | 1 - 1 file changed, 1 deletion(-) diff --git a/hatchery/sharedworkspace.go b/hatchery/sharedworkspace.go index 4909a5f..a1ebfea 100644 --- a/hatchery/sharedworkspace.go +++ b/hatchery/sharedworkspace.go @@ -195,7 +195,6 @@ func sharedPVCName(userName, prefixName string) string { // shortenedVolumeName returns the volume name hashed version. func shortenedVolumeName(prefix string, name string) string { - const dns1123LabelMaxLength = 63 sum := sha256.Sum256([]byte(name)) hash := hex.EncodeToString(sum[:])[:16] out := fmt.Sprintf("%s-%s", prefix, hash) From 2cad00e5e3d7a09d17be44bc9d0b88f1b890bc51 Mon Sep 17 00:00:00 2001 From: Andrew Prokhorenkov Date: Tue, 5 May 2026 12:41:58 -0500 Subject: [PATCH 3/4] update IAM role name --- hatchery/sharedworkspace.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hatchery/sharedworkspace.go b/hatchery/sharedworkspace.go index a1ebfea..7a630ec 100644 --- a/hatchery/sharedworkspace.go +++ b/hatchery/sharedworkspace.go @@ -28,8 +28,8 @@ func sharedWorkspaceSAName(userName string) string { } // sharedWorkspaceRoleName returns the per-user AWS IAM role name. -func sharedWorkspaceRoleName(userName string) string { - return fmt.Sprintf("hatchery-shared-%s", escapism(userName)) +func sharedWorkspaceRoleName(namespace string, userName string) string { + return fmt.Sprintf("hatchery-shared-%s-%s", escapism(namespace), escapism(userName)) } // oidcProviderID returns the host portion of the OIDC provider ARN, @@ -56,7 +56,7 @@ func accountIDFromOIDCARN(providerARN string) string { // per-user IAM role whose S3 policy is scoped to exactly the given prefixes. // Returns the role ARN. func ensureSharedWorkspaceIAMRole(userName, namespace, oidcProviderARN string, prefixes []SharedWorkspacePrefix) (string, error) { - roleName := sharedWorkspaceRoleName(userName) + roleName := sharedWorkspaceRoleName(namespace, userName) accountID := accountIDFromOIDCARN(oidcProviderARN) providerID := oidcProviderID(oidcProviderARN) saName := sharedWorkspaceSAName(userName) From 3b5b881e8626637f5a9fcada80b6a75d6d94724a Mon Sep 17 00:00:00 2001 From: Andrew Prokhorenkov Date: Tue, 5 May 2026 13:20:48 -0500 Subject: [PATCH 4/4] create role with hashed name to avoid 64 char limit --- hatchery/sharedworkspace.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/hatchery/sharedworkspace.go b/hatchery/sharedworkspace.go index 7a630ec..cfc6cc2 100644 --- a/hatchery/sharedworkspace.go +++ b/hatchery/sharedworkspace.go @@ -27,9 +27,22 @@ func sharedWorkspaceSAName(userName string) string { return fmt.Sprintf("hatchery-shared-%s", escapism(userName)) } +// shortenedWorkspaceRoleName returns the workspace role name hashed version. +func shortenedWorkspaceRoleName(prefix string, namespace string, name string) string { + suffix := fmt.Sprintf("%s-%s", escapism(namespace), escapism(name)) + sum := sha256.Sum256([]byte(suffix)) + hash := hex.EncodeToString(sum[:])[:16] + out := fmt.Sprintf("%s-%s", prefix, hash) + return out +} + // sharedWorkspaceRoleName returns the per-user AWS IAM role name. func sharedWorkspaceRoleName(namespace string, userName string) string { - return fmt.Sprintf("hatchery-shared-%s-%s", escapism(namespace), escapism(userName)) + old := fmt.Sprintf("hatchery-shared-%s-%s", escapism(namespace), escapism(userName)) + if len(old) < 64 { + return old + } + return shortenedWorkspaceRoleName("hatchery-shared", namespace, userName) } // oidcProviderID returns the host portion of the OIDC provider ARN, @@ -114,6 +127,16 @@ func ensureSharedWorkspaceIAMRole(userName, namespace, oidcProviderARN string, p if _, err = svc.CreateRole(&iam.CreateRoleInput{ RoleName: aws.String(roleName), AssumeRolePolicyDocument: aws.String(trustPolicy), + Tags: []*iam.Tag{ + { + Key: aws.String("Namespace"), + Value: &namespace, + }, + { + Key: aws.String("Username"), + Value: &userName, + }, + }, }); err != nil { return "", fmt.Errorf("failed to create IAM role %s: %w", roleName, err) }