diff --git a/.gitignore b/.gitignore index a0fa763..ab2a144 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ trust_store.yaml package-lock.json *.downloaded.md iam_policy.yaml +env.sh diff --git a/README.md b/README.md index 7d75a95..1141730 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,39 @@ This short guide provides step by step instructions how to replace a JSON key wi - Node.js with `npm` - `gcloud` authenticated as an administrator of the target project -The examples use the project ID `my-project` and the project number `123456789012`. Replace them with your own values. +## Configuration + +The scripts read the names of the resources from `env.sh`. Replace the example values with your own before running them. + +`env.sh`: + +```bash +# Project +PROJECT_ID=my-project +PROJECT_NUMBER=123456789012 + +# Workload identity pool and provider +POOL_ID=my-pool +POOL_DISPLAY_NAME="My Pool" +PROVIDER_ID=my-x509 + +# Service account +SA_EMAIL="sa-test@${PROJECT_ID}.iam.gserviceaccount.com" + +# Storage bucket +BUCKET=my-project-wif-test +BUCKET_LOCATION=europe-central2 +``` ## 1. Create the certificates The script creates a root CA, an issuing CA and a client certificate. The client certificate subject CN (`test-client`) becomes the WiF subject. +`make_certs.sh`: + ```bash +source ./env.sh + # Root CA certificate echo "Generating Root CA certificate..." openssl req \ @@ -121,7 +147,7 @@ The PEM files must contain only the certificate block. A trailing newline after ## 3. Create the pool, provider and credentials -The script creates the workload identity pool, the X.509 provider and a service account. It allows the client certificate subject to impersonate the service account, grants the service account read access to the project, creates a storage bucket with read and write access to its objects for the service account, enables Data Access audit logs for Cloud Storage, and writes `cred.json`. It then logs in with `cred.json` in a separate gcloud configuration, checks the access, uploads and downloads a file, and lists the audit log of the service account operations. +The script creates the workload identity pool, the X.509 provider and a service account. It allows the client certificate subject to impersonate the service account, grants the service account read access to the project, creates a storage bucket with read and write access to its objects for the service account, enables Data Access audit logs for Cloud Storage, and writes `cred.json`. It then logs in with `cred.json` in a separate gcloud configuration, checks the access, uploads and downloads a file, makes the public folder of the bucket publicly readable, and lists the audit log of the service account operations. Data Access audit logs are enabled for the whole project, not per bucket, by editing the project IAM policy. `add_audit_config.mjs` adds the `auditConfigs` section to the policy file fetched by gcloud. The policy must be fetched as YAML, because a different default gcloud output format makes `set-iam-policy` reject the file. @@ -149,59 +175,61 @@ writeFileSync("iam_policy.yaml", YAML.stringify(policy, { indentSeq: false })); `make_wif.sh`: ```bash +source ./env.sh + # Create a workload identity pool and a X.509 provider -gcloud iam workload-identity-pools create my-pool \ - --project=my-project \ +gcloud iam workload-identity-pools create "${POOL_ID}" \ + --project="${PROJECT_ID}" \ --location=global \ - --display-name="My Pool" + --display-name="${POOL_DISPLAY_NAME}" # Create X.509 provider -gcloud iam workload-identity-pools providers create-x509 my-x509 \ - --project=my-project \ +gcloud iam workload-identity-pools providers create-x509 "${PROVIDER_ID}" \ + --project="${PROJECT_ID}" \ --location=global \ - --workload-identity-pool=my-pool \ + --workload-identity-pool="${POOL_ID}" \ --trust-store-config-path=trust_store.yaml \ --attribute-mapping="google.subject=assertion.subject.dn.cn" # Create the service account gcloud iam service-accounts create sa-test \ - --project=my-project \ + --project="${PROJECT_ID}" \ --display-name="SA Test" # Allow impersonation of the service account gcloud iam service-accounts add-iam-policy-binding \ - sa-test@my-project.iam.gserviceaccount.com \ + "${SA_EMAIL}" \ --role=roles/iam.workloadIdentityUser \ - --member="principal://iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/my-pool/subject/test-client" + --member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/subject/test-client" # Grant the service account read access to the project -gcloud projects add-iam-policy-binding my-project \ - --member="serviceAccount:sa-test@my-project.iam.gserviceaccount.com" \ +gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ + --member="serviceAccount:${SA_EMAIL}" \ --role=roles/browser # Create credentials configuration files gcloud iam workload-identity-pools create-cred-config \ - projects/123456789012/locations/global/workloadIdentityPools/my-pool/providers/my-x509 \ - --service-account=sa-test@my-project.iam.gserviceaccount.com \ + "projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/providers/${PROVIDER_ID}" \ + --service-account="${SA_EMAIL}" \ --credential-cert-path=client_cert.pem \ --credential-cert-private-key-path=client_key.pem \ --output-file=cred.json # Create a storage bucket -gcloud storage buckets create gs://my-project-wif-test \ - --project=my-project \ - --location=europe-central2 \ +gcloud storage buckets create "gs://${BUCKET}" \ + --project="${PROJECT_ID}" \ + --location="${BUCKET_LOCATION}" \ --uniform-bucket-level-access # Allow the service account to read and write objects in the bucket -gcloud storage buckets add-iam-policy-binding gs://my-project-wif-test \ - --member="serviceAccount:sa-test@my-project.iam.gserviceaccount.com" \ +gcloud storage buckets add-iam-policy-binding "gs://${BUCKET}" \ + --member="serviceAccount:${SA_EMAIL}" \ --role=roles/storage.objectUser # Enable Data Access audit logs for Cloud Storage (project-wide, not per bucket) -gcloud projects get-iam-policy my-project --format=yaml > iam_policy.yaml +gcloud projects get-iam-policy "${PROJECT_ID}" --format=yaml > iam_policy.yaml node add_audit_config.mjs -gcloud projects set-iam-policy my-project iam_policy.yaml +gcloud projects set-iam-policy "${PROJECT_ID}" iam_policy.yaml # Create a new gcloud configuration for the workload identity federation test gcloud config configurations create wif-test @@ -209,33 +237,39 @@ gcloud config configurations create wif-test # Authenticate gcloud with the credentials configuration file gcloud auth login --cred-file=cred.json -# Set the project to my-project -gcloud config set project my-project +# Set the project +gcloud config set project "${PROJECT_ID}" # Test the authentication by listing the project details gcloud auth list -gcloud projects describe my-project -gcloud projects get-iam-policy my-project \ +gcloud projects describe "${PROJECT_ID}" +gcloud projects get-iam-policy "${PROJECT_ID}" \ --flatten="bindings[].members" \ --format="table(bindings.role, bindings.members)" # Test the bucket access by uploading and downloading a file -gcloud storage ls gs://my-project-wif-test -gcloud storage cp README.md gs://my-project-wif-test/README.md -gcloud storage ls gs://my-project-wif-test -gcloud storage cp gs://my-project-wif-test/README.md README.downloaded.md +gcloud storage ls "gs://${BUCKET}" +gcloud storage cp README.md "gs://${BUCKET}/README.md" +gcloud storage ls "gs://${BUCKET}" +gcloud storage cp "gs://${BUCKET}/README.md" README.downloaded.md cmp README.md README.downloaded.md # Then, switch back to your original account gcloud config configurations activate default +# Create a managed folder and make the objects in it publicly readable +gcloud storage managed-folders create "gs://${BUCKET}/public/" +gcloud storage managed-folders add-iam-policy-binding "gs://${BUCKET}/public/" \ + --member=allUsers \ + --role=roles/storage.objectViewer + # List the audit log of the operations made by the service account gcloud logging read \ - 'protoPayload.authenticationInfo.principalEmail="sa-test@my-project.iam.gserviceaccount.com"' \ - --project=my-project \ + "protoPayload.authenticationInfo.principalEmail=\"${SA_EMAIL}\"" \ + --project="${PROJECT_ID}" \ --freshness=1h \ --order=asc \ - --format="table(timestamp, protoPayload.serviceName, protoPayload.methodName)" + --format="table(timestamp, protoPayload.serviceName, protoPayload.methodName, protoPayload.authenticationInfo.principalEmail:label=EMAIL, protoPayload.authenticationInfo.principalSubject:label=SUBJECT, protoPayload.authenticationInfo.serviceAccountDelegationInfo[0].principalSubject:label=DELEGATED_BY)" # and again switch back to the service account gcloud config configurations activate wif-test diff --git a/make_certs.sh b/make_certs.sh index b401056..65bd13d 100755 --- a/make_certs.sh +++ b/make_certs.sh @@ -1,3 +1,5 @@ +source ./env.sh + # Root CA certificate echo "Generating Root CA certificate..." openssl req \ diff --git a/make_wif.sh b/make_wif.sh index 1fd3bdf..29ff4f2 100755 --- a/make_wif.sh +++ b/make_wif.sh @@ -1,56 +1,58 @@ +source ./env.sh + # Create a workload identity pool and a X.509 provider -gcloud iam workload-identity-pools create dom-lab-pool \ - --project=dom-lab \ +gcloud iam workload-identity-pools create "${POOL_ID}" \ + --project="${PROJECT_ID}" \ --location=global \ - --display-name="Dom Lab Pool" + --display-name="${POOL_DISPLAY_NAME}" # Create X.509 provider -gcloud iam workload-identity-pools providers create-x509 dom-lab-x509 \ - --project=dom-lab \ +gcloud iam workload-identity-pools providers create-x509 "${PROVIDER_ID}" \ + --project="${PROJECT_ID}" \ --location=global \ - --workload-identity-pool=dom-lab-pool \ + --workload-identity-pool="${POOL_ID}" \ --trust-store-config-path=trust_store.yaml \ --attribute-mapping="google.subject=assertion.subject.dn.cn" # Create the service account gcloud iam service-accounts create sa-test \ - --project=dom-lab \ + --project="${PROJECT_ID}" \ --display-name="SA Test" # Allow impersonation of the service account gcloud iam service-accounts add-iam-policy-binding \ - sa-test@dom-lab.iam.gserviceaccount.com \ + "${SA_EMAIL}" \ --role=roles/iam.workloadIdentityUser \ - --member="principal://iam.googleapis.com/projects/267098479356/locations/global/workloadIdentityPools/dom-lab-pool/subject/test-client" + --member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/subject/test-client" # Grant the service account read access to the project -gcloud projects add-iam-policy-binding dom-lab \ - --member="serviceAccount:sa-test@dom-lab.iam.gserviceaccount.com" \ +gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ + --member="serviceAccount:${SA_EMAIL}" \ --role=roles/browser # Create credentials configuration files gcloud iam workload-identity-pools create-cred-config \ - projects/267098479356/locations/global/workloadIdentityPools/dom-lab-pool/providers/dom-lab-x509 \ - --service-account=sa-test@dom-lab.iam.gserviceaccount.com \ + "projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/providers/${PROVIDER_ID}" \ + --service-account="${SA_EMAIL}" \ --credential-cert-path=client_cert.pem \ --credential-cert-private-key-path=client_key.pem \ --output-file=cred.json # Create a storage bucket -gcloud storage buckets create gs://dom-lab-wif-test \ - --project=dom-lab \ - --location=europe-central2 \ +gcloud storage buckets create "gs://${BUCKET}" \ + --project="${PROJECT_ID}" \ + --location="${BUCKET_LOCATION}" \ --uniform-bucket-level-access # Allow the service account to read and write objects in the bucket -gcloud storage buckets add-iam-policy-binding gs://dom-lab-wif-test \ - --member="serviceAccount:sa-test@dom-lab.iam.gserviceaccount.com" \ +gcloud storage buckets add-iam-policy-binding "gs://${BUCKET}" \ + --member="serviceAccount:${SA_EMAIL}" \ --role=roles/storage.objectUser # Enable Data Access audit logs for Cloud Storage (project-wide, not per bucket) -gcloud projects get-iam-policy dom-lab --format=yaml > iam_policy.yaml +gcloud projects get-iam-policy "${PROJECT_ID}" --format=yaml > iam_policy.yaml node add_audit_config.mjs -gcloud projects set-iam-policy dom-lab iam_policy.yaml +gcloud projects set-iam-policy "${PROJECT_ID}" iam_policy.yaml # Create a new gcloud configuration for the workload identity federation test gcloud config configurations create wif-test @@ -58,33 +60,39 @@ gcloud config configurations create wif-test # Authenticate gcloud with the credentials configuration file gcloud auth login --cred-file=cred.json -# Set the project to dom-lab -gcloud config set project dom-lab +# Set the project +gcloud config set project "${PROJECT_ID}" # Test the authentication by listing the project details gcloud auth list -gcloud projects describe dom-lab -gcloud projects get-iam-policy dom-lab \ +gcloud projects describe "${PROJECT_ID}" +gcloud projects get-iam-policy "${PROJECT_ID}" \ --flatten="bindings[].members" \ --format="table(bindings.role, bindings.members)" # Test the bucket access by uploading and downloading a file -gcloud storage ls gs://dom-lab-wif-test -gcloud storage cp README.md gs://dom-lab-wif-test/README.md -gcloud storage ls gs://dom-lab-wif-test -gcloud storage cp gs://dom-lab-wif-test/README.md README.downloaded.md +gcloud storage ls "gs://${BUCKET}" +gcloud storage cp README.md "gs://${BUCKET}/README.md" +gcloud storage ls "gs://${BUCKET}" +gcloud storage cp "gs://${BUCKET}/README.md" README.downloaded.md cmp README.md README.downloaded.md # Then, switch back to your original account gcloud config configurations activate default +# Create a managed folder and make the objects in it publicly readable +gcloud storage managed-folders create "gs://${BUCKET}/public/" +gcloud storage managed-folders add-iam-policy-binding "gs://${BUCKET}/public/" \ + --member=allUsers \ + --role=roles/storage.objectViewer + # List the audit log of the operations made by the service account gcloud logging read \ - 'protoPayload.authenticationInfo.principalEmail="sa-test@dom-lab.iam.gserviceaccount.com"' \ - --project=dom-lab \ + "protoPayload.authenticationInfo.principalEmail=\"${SA_EMAIL}\"" \ + --project="${PROJECT_ID}" \ --freshness=1h \ --order=asc \ - --format="table(timestamp, protoPayload.serviceName, protoPayload.methodName)" + --format="table(timestamp, protoPayload.serviceName, protoPayload.methodName, protoPayload.authenticationInfo.principalEmail:label=EMAIL, protoPayload.authenticationInfo.principalSubject:label=SUBJECT, protoPayload.authenticationInfo.serviceAccountDelegationInfo[0].principalSubject:label=DELEGATED_BY)" # and again switch back to the service account gcloud config configurations activate wif-test