From 520cd56b21135d9f660a0a0ae91a3f14208c7972 Mon Sep 17 00:00:00 2001 From: Jonathan Dobson Date: Thu, 9 Oct 2025 16:18:55 -0600 Subject: [PATCH 1/3] scope roles/iam.serviceAccountUser to node service accounts --- deploy/common.sh | 15 +++++++++- deploy/kubernetes/deploy-driver.sh | 20 +++++++++++++ deploy/setup-project.sh | 17 +++++++++++ docs/kubernetes/user-guides/driver-install.md | 30 +++++++++++++++---- 4 files changed, 76 insertions(+), 6 deletions(-) diff --git a/deploy/common.sh b/deploy/common.sh index ad1705640..eab605fa5 100644 --- a/deploy/common.sh +++ b/deploy/common.sh @@ -4,6 +4,7 @@ readonly KUSTOMIZE_PATH="${PKGDIR}/bin/kustomize" readonly VERBOSITY="${GCE_PD_VERBOSITY:-2}" readonly KUBECTL="${GCE_PD_KUBECTL:-kubectl}" +readonly SA_USER_ROLE="roles/iam.serviceAccountUser" # Common functions @@ -17,9 +18,21 @@ function ensure_var(){ fi } +# Return true if scoping serviceAccountUser role to node service accounts +function use_scoped_sa_role() +{ + [ -n "${NODE_SERVICE_ACCOUNTS:-}" ] +} + function get_needed_roles() { - echo "roles/editor roles/compute.storageAdmin roles/iam.serviceAccountUser projects/${PROJECT}/roles/gcp_compute_persistent_disk_csi_driver_custom_role" + ROLES="roles/editor roles/compute.storageAdmin projects/${PROJECT}/roles/gcp_compute_persistent_disk_csi_driver_custom_role" + # Grant the role at the project level if no node service accounts are provided. + if ! use_scoped_sa_role; + then + ROLES+=" ${SA_USER_ROLE}" + fi + echo "${ROLES}" } # Installs kustomize in ${PKGDIR}/bin diff --git a/deploy/kubernetes/deploy-driver.sh b/deploy/kubernetes/deploy-driver.sh index 3c4a893cd..da231a0a0 100755 --- a/deploy/kubernetes/deploy-driver.sh +++ b/deploy/kubernetes/deploy-driver.sh @@ -12,6 +12,9 @@ # by setup-project.sh). Ignored if GCE_PD_DRIVER_VERSION == noauth. # GCE_PD_DRIVER_VERSION: The kustomize overlay to deploy. See # `deploy/kubernetes/overlays` for your choices. +# NODE_SERVICE_ACCOUNTS: (Optional) Comma-separated list of service accounts +# that the CSI driver should be allowed to impersonate. If not specified, +# defaults to project-level serviceAccountUser role. set -o nounset set -o errexit @@ -65,6 +68,23 @@ function check_service_account() MISSING_ROLES=true fi done + + # Check each node service account for serviceAccountUser role + if use_scoped_sa_role; + then + IFS=',' read -ra NODE_SA_ARRAY <<< "${NODE_SERVICE_ACCOUNTS}" + for node_sa in "${NODE_SA_ARRAY[@]}"; + do + node_sa=$(echo "${node_sa}" | xargs) # trim whitespace + SA_POLICY=$(gcloud iam service-accounts get-iam-policy "${node_sa}" --project="${PROJECT}" --flatten="bindings[].members" --format='table(bindings.role)' --filter="bindings.members:${IAM_NAME}") + if ! grep -q "${SA_USER_ROLE}" <<< "${SA_POLICY}"; + then + echo "Missing ${SA_USER_ROLE} for: ${node_sa}" + MISSING_ROLES=true + fi + done + fi + if [ "${MISSING_ROLES}" = true ]; then echo "Cannot deploy with missing roles in service account, please run setup-project.sh to setup Service Account" diff --git a/deploy/setup-project.sh b/deploy/setup-project.sh index c992dc1d1..a55ee155f 100755 --- a/deploy/setup-project.sh +++ b/deploy/setup-project.sh @@ -17,6 +17,9 @@ # ENABLE_KMS_ADMIN: Add service account permissions to destroy Cloud KMS keys. # CREATE_SA_KEY: (Optional) If true, creates a new service account key and # exports it if creating a new service account +# NODE_SERVICE_ACCOUNTS: (Optional) Comma-separated list of service accounts +# that the CSI driver should be allowed to impersonate. If not specified, +# defaults to project-level serviceAccountUser role. set -o nounset set -o errexit @@ -105,6 +108,20 @@ do gcloud projects add-iam-policy-binding "${PROJECT}" --member serviceAccount:"${IAM_NAME}" --role "${role}" done +# Grant scoped serviceAccountUser role for node service accounts +if use_scoped_sa_role; +then + IFS=',' read -ra NODE_SA_ARRAY <<< "${NODE_SERVICE_ACCOUNTS}" + for node_sa in "${NODE_SA_ARRAY[@]}"; + do + node_sa=$(echo "${node_sa}" | xargs) # trim whitespace + echo "Granting ${SA_USER_ROLE} for ${node_sa} to serviceAccount:${IAM_NAME}" + gcloud iam service-accounts add-iam-policy-binding "${node_sa}" \ + --member="serviceAccount:${IAM_NAME}" \ + --role="${SA_USER_ROLE}" --project="${PROJECT}" + done +fi + # Authorize GCE to encrypt/decrypt using Cloud KMS encryption keys. # https://cloud.google.com/compute/docs/disks/customer-managed-encryption#before_you_begin if [ "${ENABLE_KMS}" = true ]; diff --git a/docs/kubernetes/user-guides/driver-install.md b/docs/kubernetes/user-guides/driver-install.md index 727a9787b..40884c48c 100644 --- a/docs/kubernetes/user-guides/driver-install.md +++ b/docs/kubernetes/user-guides/driver-install.md @@ -2,13 +2,13 @@ ## Install Driver -1. Clone the driver to your local machine +### 1. Clone the driver to your local machine ```console $ git clone https://github.com/kubernetes-sigs/gcp-compute-persistent-disk-csi-driver $GOPATH/src/sigs.k8s.io/gcp-compute-persistent-disk-csi-driver ``` -2. [One-time per project] Set up or use an existing service account: +### 2. [One-time per project] Set up or use an existing service account: The driver requires a service account that has the following permissions and roles to function properly: @@ -18,7 +18,7 @@ compute.instances.get compute.instances.attachDisk compute.instances.detachDisk roles/compute.storageAdmin -roles/iam.serviceAccountUser +roles/iam.serviceAccountUser (see security note below) ``` If there is a pre-existing service account with these roles for use then the @@ -33,7 +33,24 @@ $ GCE_PD_SA_DIR=/my/safe/credentials/directory **Note**: The service account key *must* be named `cloud-sa.json` at driver deploy time However, if there is no pre-existing service account for use the provided script -can be used to create a new service account with all the required permissions: +can be used to create a new service account with all the required permissions. + +#### Security Note: Service Account Impersonation + +The CSI driver requires the `roles/iam.serviceAccountUser` role to impersonate node service accounts when attaching and detaching disks. This role can be configured in two ways: + +* **Recommended (Scoped)**: Grant the role only for specific node service accounts +* **Default (Project-wide)**: Allow project-wide service account impersonation (less secure) + +For improved security, specify the node service accounts that the CSI driver needs to impersonate using the `NODE_SERVICE_ACCOUNTS` environment variable. This limits the role to only the specified accounts. Without `NODE_SERVICE_ACCOUNTS`, the CSI driver can impersonate any service account in the project. + +```console +$ NODE_SERVICE_ACCOUNTS="master-sa@project.iam.gserviceaccount.com,worker-sa@project.iam.gserviceaccount.com" # Comma-separated list of node service accounts +``` + +For more details, see [How to remediate over privileged service account users](https://cloud.google.com/security-command-center/docs/how-to-remediate-security-health-analytics-findings#over_privileged_service_account_user). + +#### Create service account for the CSI driver ```console $ PROJECT=your-project-here # GCP project @@ -46,9 +63,10 @@ $ ./deploy/setup-project.sh deployment, all actions performed by the driver will be performed as the specified service account -3. Deploy driver to Kubernetes Cluster +### 3. Deploy driver to Kubernetes Cluster ```console +$ NODE_SERVICE_ACCOUNTS="master-sa@project.iam.gserviceaccount.com,worker-sa@project.iam.gserviceaccount.com" # Same as the setup-project.sh step $ GCE_PD_SA_DIR=/my/safe/credentials/directory # Directory to get the service account key $ GCE_PD_DRIVER_VERSION=stable-master # Driver version to deploy $ ./deploy/kubernetes/deploy-driver.sh @@ -74,6 +92,8 @@ additional permissions are required in order to create the new service account: ``` resourcemanager.projects.getIamPolicy resourcemanager.projects.setIamPolicy +iam.serviceAccounts.getIamPolicy +iam.serviceAccounts.setIamPolicy iam.serviceAccounts.create iam.serviceAccounts.delete ``` From bf0b487516e8817599bced0c3c634b85afcd06f2 Mon Sep 17 00:00:00 2001 From: Jonathan Dobson Date: Fri, 10 Oct 2025 18:05:58 -0600 Subject: [PATCH 2/3] add-iam-policy-binding should set condition=None to avoid prompt --- deploy/setup-project.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/setup-project.sh b/deploy/setup-project.sh index a55ee155f..dd1e87e37 100755 --- a/deploy/setup-project.sh +++ b/deploy/setup-project.sh @@ -105,7 +105,7 @@ gcloud iam roles $action gcp_compute_persistent_disk_csi_driver_custom_role --qu # Bind service account to roles for role in ${BIND_ROLES} do - gcloud projects add-iam-policy-binding "${PROJECT}" --member serviceAccount:"${IAM_NAME}" --role "${role}" + gcloud projects add-iam-policy-binding "${PROJECT}" --member serviceAccount:"${IAM_NAME}" --role "${role}" --condition=None done # Grant scoped serviceAccountUser role for node service accounts @@ -117,7 +117,7 @@ then node_sa=$(echo "${node_sa}" | xargs) # trim whitespace echo "Granting ${SA_USER_ROLE} for ${node_sa} to serviceAccount:${IAM_NAME}" gcloud iam service-accounts add-iam-policy-binding "${node_sa}" \ - --member="serviceAccount:${IAM_NAME}" \ + --member="serviceAccount:${IAM_NAME}" --condition=None \ --role="${SA_USER_ROLE}" --project="${PROJECT}" done fi @@ -127,14 +127,14 @@ fi if [ "${ENABLE_KMS}" = true ]; then gcloud services enable cloudkms.googleapis.com --project="${PROJECT}" - gcloud projects add-iam-policy-binding "${PROJECT}" --member serviceAccount:"service-${PROJECT_NUMBER}@compute-system.iam.gserviceaccount.com" --role "roles/cloudkms.cryptoKeyEncrypterDecrypter" + gcloud projects add-iam-policy-binding "${PROJECT}" --member serviceAccount:"service-${PROJECT_NUMBER}@compute-system.iam.gserviceaccount.com" --role "roles/cloudkms.cryptoKeyEncrypterDecrypter" --condition=None fi # Authorize SA to destroy Cloud KMS encryption keys. if [ "${ENABLE_KMS_ADMIN}" = true ]; then gcloud services enable cloudkms.googleapis.com --project="${PROJECT}" - gcloud projects add-iam-policy-binding "${PROJECT}" --member serviceAccount:"${IAM_NAME}" --role "roles/cloudkms.admin" + gcloud projects add-iam-policy-binding "${PROJECT}" --member serviceAccount:"${IAM_NAME}" --role "roles/cloudkms.admin" --condition=None fi # Export key if needed From 7ef4a52e57f9ff4a657ec3c8a67e68c4a4171bd4 Mon Sep 17 00:00:00 2001 From: Jonathan Dobson Date: Fri, 10 Oct 2025 18:10:36 -0600 Subject: [PATCH 3/3] remove invalid --enable-multitenancy option from deployments --- deploy/kubernetes/base/controller/controller.yaml | 1 - deploy/kubernetes/base/node_linux/node.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/deploy/kubernetes/base/controller/controller.yaml b/deploy/kubernetes/base/controller/controller.yaml index 302874f82..ae407e424 100644 --- a/deploy/kubernetes/base/controller/controller.yaml +++ b/deploy/kubernetes/base/controller/controller.yaml @@ -146,7 +146,6 @@ spec: - "--supports-dynamic-throughput-provisioning=hyperdisk-balanced,hyperdisk-throughput,hyperdisk-ml" - --enable-data-cache - --run-node-service=false - - --enable-multitenancy command: - /gce-pd-csi-driver env: diff --git a/deploy/kubernetes/base/node_linux/node.yaml b/deploy/kubernetes/base/node_linux/node.yaml index ef9e89bcf..f24c5af75 100644 --- a/deploy/kubernetes/base/node_linux/node.yaml +++ b/deploy/kubernetes/base/node_linux/node.yaml @@ -47,7 +47,6 @@ spec: - "--endpoint=unix:/csi/csi.sock" - "--run-controller-service=false" - "--enable-data-cache" - - "--enable-multitenancy" - "--node-name=$(KUBE_NODE_NAME)" securityContext: privileged: true