From 54289784e5296982b655349b6e91cbdabfe9d52d Mon Sep 17 00:00:00 2001 From: Micah Hausler Date: Mon, 17 Feb 2025 18:02:48 +0000 Subject: [PATCH 1/2] Add VolumeID to Mount request Signed-off-by: Micah Hausler --- docs/book/src/providers.md | 12 ++++++++++++ pkg/secrets-store/nodeserver.go | 4 ++++ test/e2eprovider/server/server.go | 17 ++++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/book/src/providers.md b/docs/book/src/providers.md index 2a140a4fa..5eb5cb4dc 100644 --- a/docs/book/src/providers.md +++ b/docs/book/src/providers.md @@ -35,6 +35,18 @@ The driver uses gRPC to communicate with the provider. To implement a secrets-st See [design doc](https://docs.google.com/document/d/10-RHUJGM0oMN88AZNxjOmGz0NsWAvOYrWUEV-FbLWyw/edit?usp=sharing) for more details. +The `MountRequest` message structure includes several additional keys in the `Attributes` field that a provider can use when retrieving a secret. Keys beginning with `csi.storage.k8s.io` are [passed through from the Kubelet](https://kubernetes-csi.github.io/docs/pod-info.html?highlight=pod.name#pod-info-on-mount-with-csi-driver-object) if `podInfoOnMount` is `true` on the CSI driver. + +| Attribute Key | Description | +| --- | ---- | +| `csi.storage.k8s.io/pod-name` | Pod name | +| `csi.storage.k8s.io/pod.namespace` | Pod namespace | +| `csi.storage.k8s.io/pod.uid` | Pod UID | +| `csi.storage.k8s.io/serviceAccount.name` | The Pod's ServiceAccount name | +| `csi.storage.k8s.io/serviceAccount.tokens` | A JSON structure serialized to a string containing service account tokens belonging to a pod [when a CSI driver has `tokenRequests` configured](https://kubernetes-csi.github.io/docs/token-requests.html). | +| `secrets-store-csi-driver.sigs.k8s.io/volume.id` | The CSI Volume's ID from the `NodePublishVolumeRequest` call. This may be useful as a cache key if using Service Account tokens to fetch secrets. | + + ## Features supported by current providers | Features \ Providers | Azure | GCP | AWS | Vault | Akeyless | Conjur | diff --git a/pkg/secrets-store/nodeserver.go b/pkg/secrets-store/nodeserver.go index e2af8dcd0..6be4192d4 100644 --- a/pkg/secrets-store/nodeserver.go +++ b/pkg/secrets-store/nodeserver.go @@ -65,6 +65,8 @@ const ( // CSIPodServiceAccountTokens is the service account tokens of the pod that the mount is created for CSIPodServiceAccountTokens = "csi.storage.k8s.io/serviceAccount.tokens" //nolint + SecretStoreVolumeID = "secrets-store-csi-driver.sigs.k8s.io/volume.id" + secretProviderClassField = "secretProviderClass" ) @@ -181,6 +183,8 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis for k, v := range attrib { parameters[k] = v } + // Add the volume ID to the parameters + parameters[SecretStoreVolumeID] = volumeID // csi.storage.k8s.io/serviceAccount.tokens is empty for Kubernetes version < 1.20. // For 1.20+, if tokenRequests is set in the CSI driver spec, kubelet will generate // a token for the pod and send it to the CSI driver. diff --git a/test/e2eprovider/server/server.go b/test/e2eprovider/server/server.go index 1c60079d6..3fc3b7a0c 100644 --- a/test/e2eprovider/server/server.go +++ b/test/e2eprovider/server/server.go @@ -50,7 +50,7 @@ This is mock key podUIDAttribute = "csi.storage.k8s.io/pod.uid" serviceAccountTokensAttribute = "csi.storage.k8s.io/serviceAccount.tokens" //nolint - + secretStoreVolumeID = "secrets-store-csi-driver.sigs.k8s.io/volume.id" // RWMutex is to safely access podCache m sync.RWMutex ) @@ -186,6 +186,10 @@ func (s *Server) Mount(ctx context.Context, req *v1alpha1.MountRequest) (*v1alph } } + if err := validateVolumeIDAttr(attrib); err != nil { + return nil, fmt.Errorf("failed to validate volume ID, error: %w", err) + } + m.Lock() podCache[attrib[podUIDAttribute]] = true m.Unlock() @@ -266,3 +270,14 @@ func validateTokens(tokenAudiences, saTokens string) error { } return nil } + +func validateVolumeIDAttr(attributes map[string]string) error { + volumeID, ok := attributes[secretStoreVolumeID] + if !ok { + return fmt.Errorf("volume ID is not set") + } + if volumeID == "" { + return fmt.Errorf("volume ID is empty") + } + return nil +} From 3449397dfc9758bb3610ede472ca5fac71e201e6 Mon Sep 17 00:00:00 2001 From: Micah Hausler Date: Wed, 9 Apr 2025 16:43:30 +0000 Subject: [PATCH 2/2] fixup --- test/bats/e2e-provider.bats | 13 +++++++++++++ test/e2eprovider/e2e_provider.go | 1 + test/e2eprovider/server/server.go | 12 ++++++++++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/test/bats/e2e-provider.bats b/test/bats/e2e-provider.bats index a73b27200..99ec27479 100644 --- a/test/bats/e2e-provider.bats +++ b/test/bats/e2e-provider.bats @@ -55,6 +55,19 @@ export VALIDATE_TOKENS_AUDIENCE=$(get_token_requests_audience) log_token_requests_audience } +@test "ensure volume ID is set" { + if [[ -n "${VALIDATE_VOLUME_ID}" ]]; then + kubectl create ns validate-volume-id + local curl_pod_name=curl-$(openssl rand -hex 5) + kubectl run ${curl_pod_name} -n validate-volume-id --image=curlimages/curl:7.75.0 --labels="util=validate-volume-id" -- tail -f /dev/null + kubectl wait -n validate-volume-id --for=condition=Ready --timeout=60s pod ${curl_pod_name} + local pod_ip=$(kubectl get pod -n kube-system -l app=csi-secrets-store-e2e-provider -o jsonpath="{.items[0].status.podIP}") + run kubectl exec ${curl_pod_name} -n validate-volume-id -- curl http://${pod_ip}:8080/validate-volume-id + kubectl delete pod -l util=validate-volume-id -n validate-volume-id --force --grace-period 0 + kubectl delete ns validate-volume-id + fi +} + @test "secretproviderclasses crd is established" { kubectl wait --for condition=established --timeout=60s crd/secretproviderclasses.secrets-store.csi.x-k8s.io diff --git a/test/e2eprovider/e2e_provider.go b/test/e2eprovider/e2e_provider.go index 06af1d894..44373090c 100644 --- a/test/e2eprovider/e2e_provider.go +++ b/test/e2eprovider/e2e_provider.go @@ -78,6 +78,7 @@ func mainErr() error { http.HandleFunc("/rotation", server.RotationHandler) http.HandleFunc("/validate-token-requests", server.ValidateTokenAudienceHandler) + http.HandleFunc("/validate-volume-id", server.VolumeIDHandler) server := &http.Server{ Addr: ":8080", diff --git a/test/e2eprovider/server/server.go b/test/e2eprovider/server/server.go index 3fc3b7a0c..aa4737a18 100644 --- a/test/e2eprovider/server/server.go +++ b/test/e2eprovider/server/server.go @@ -186,8 +186,10 @@ func (s *Server) Mount(ctx context.Context, req *v1alpha1.MountRequest) (*v1alph } } - if err := validateVolumeIDAttr(attrib); err != nil { - return nil, fmt.Errorf("failed to validate volume ID, error: %w", err) + if _, ok := os.LookupEnv("VALIDATE_VOLUME_ID"); ok { + if err := validateVolumeIDAttr(attrib); err != nil { + return nil, fmt.Errorf("failed to validate volume ID, error: %w", err) + } } m.Lock() @@ -251,6 +253,12 @@ func ValidateTokenAudienceHandler(w http.ResponseWriter, r *http.Request) { klog.InfoS("Validation for token requests audience", "audience", os.Getenv("VALIDATE_TOKENS_AUDIENCE")) } +// VolumeIDHandler enables volume ID for the mock provider +func VolumeIDHandler(w http.ResponseWriter, r *http.Request) { + os.Setenv("VALIDATE_VOLUME_ID", "true") + klog.InfoS("Volume ID validation enabled") +} + // validateTokens checks there are tokens for distinct audiences in the // service account token attribute. func validateTokens(tokenAudiences, saTokens string) error {