Skip to content

Commit f3305ce

Browse files
committed
Implement collision-safe naming for CSI credentials
1 parent 90280f9 commit f3305ce

File tree

8 files changed

+616
-151
lines changed

8 files changed

+616
-151
lines changed

pkg/steps/multi_stage/csi_utils.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package multi_stage
2+
3+
import (
4+
"crypto/sha256"
5+
"fmt"
6+
"sort"
7+
"strings"
8+
9+
"github.com/GoogleCloudPlatform/secrets-store-csi-driver-provider-gcp/config"
10+
"github.com/openshift/ci-tools/pkg/api"
11+
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
csiapi "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
13+
"sigs.k8s.io/yaml"
14+
)
15+
16+
// GSMproject is the name of the GCP Secret Manager project where the secrets are stored.
17+
var GSMproject = "openshift-ci-secrets"
18+
19+
// groupCredentialsByCollectionAndMountPath groups credentials by (collection, mount_path)
20+
// to avoid duplicate mount paths.
21+
func groupCredentialsByCollectionAndMountPath(credentials []api.CredentialReference) map[string][]api.CredentialReference {
22+
mountGroups := make(map[string][]api.CredentialReference)
23+
for _, credential := range credentials {
24+
key := fmt.Sprintf("%s:%s", credential.Collection, credential.MountPath)
25+
mountGroups[key] = append(mountGroups[key], credential)
26+
}
27+
return mountGroups
28+
}
29+
30+
func buildGCPSecretsParameter(credentials []api.CredentialReference) (string, error) {
31+
var secrets []config.Secret
32+
for _, credential := range credentials {
33+
secrets = append(secrets, config.Secret{
34+
ResourceName: fmt.Sprintf("projects/%s/secrets/%s__%s/versions/latest", GSMproject, credential.Collection, credential.Name),
35+
FileName: credential.Name, // we want to mount the secret as a file named without the collection prefix
36+
})
37+
}
38+
secretsYaml, err := yaml.Marshal(secrets)
39+
if err != nil {
40+
return "", fmt.Errorf("could not marshal secrets: %w", err)
41+
}
42+
return string(secretsYaml), nil
43+
}
44+
45+
// getSPCName gets the unique SPC name for a collection, mount path, and credential contents
46+
func getSPCName(namespace, collection, mountPath string, credentials []api.CredentialReference) string {
47+
var parts []string
48+
parts = append(parts, collection, mountPath)
49+
50+
// Sort credential names for deterministic hashing
51+
var credNames []string
52+
for _, cred := range credentials {
53+
credNames = append(credNames, cred.Name)
54+
}
55+
sort.Strings(credNames)
56+
parts = append(parts, credNames...)
57+
58+
hash := sha256.Sum256([]byte(strings.Join(parts, "-")))
59+
hashStr := fmt.Sprintf("%x", hash[:12])
60+
name := fmt.Sprintf("%s-%s-spc", namespace, hashStr)
61+
62+
return strings.ToLower(name)
63+
}
64+
65+
// getCSIVolumeName generates a deterministic, DNS-compliant name for a CSI volume
66+
// based on the namespace, collection, and mountPath. The name is constructed as
67+
// "<namespace>-<hash>", where the hash is computed from the collection and mountPath.
68+
// If the resulting name exceeds 63 characters (the Kubernetes DNS label limit),
69+
// only the hash is used as the name.
70+
func getCSIVolumeName(ns, collection, mountPath string) string {
71+
// Hash both collection and mountPath together for consistent length
72+
hash := sha256.Sum256([]byte(strings.Join([]string{collection, mountPath}, "-")))
73+
hashStr := fmt.Sprintf("%x", hash[:8])
74+
name := fmt.Sprintf("%s-%s", ns, hashStr)
75+
76+
// If namespace + hash is still too long, use just the hash
77+
if len(name) > 63 {
78+
hashStr := fmt.Sprintf("%x", hash[:16])
79+
name = hashStr
80+
}
81+
82+
return strings.ToLower(name)
83+
}
84+
85+
func getCensorMountPath(secretName string) string {
86+
return fmt.Sprintf("/censor/%s", secretName)
87+
}
88+
89+
func buildSecretProviderClass(name, namespace, secrets string) *csiapi.SecretProviderClass {
90+
return &csiapi.SecretProviderClass{
91+
TypeMeta: meta.TypeMeta{
92+
Kind: "SecretProviderClass",
93+
APIVersion: csiapi.GroupVersion.String(),
94+
},
95+
ObjectMeta: meta.ObjectMeta{
96+
Name: name,
97+
Namespace: namespace,
98+
},
99+
Spec: csiapi.SecretProviderClassSpec{
100+
Provider: "gcp",
101+
Parameters: map[string]string{
102+
"auth": "provider-adc",
103+
"secrets": string(secrets),
104+
},
105+
},
106+
}
107+
}

0 commit comments

Comments
 (0)