Update README and remove script sources from the README body.
This commit is contained in:
1 parent
f50bd984fb
commit
fa3c612668
2 files changed
+16
-214
No files matched your search
@@ -10,7 +10,7 @@ This short guide provides step by step instructions how to replace a JSON key wi
|
||||
|
||||
## Configuration
|
||||
|
||||
The scripts read the names of the resources from `env.sh`. Replace the example values with your own before running them.
|
||||
The scripts read the names of the resources from `env.sh`. Replace the example values with your own before running them. Run all scripts from the repository directory.
|
||||
|
||||
`env.sh`:
|
||||
|
||||
@@ -32,69 +32,23 @@ BUCKET=my-project-wif-test
|
||||
BUCKET_LOCATION=europe-central2
|
||||
```
|
||||
|
||||
## About the shell scripts
|
||||
|
||||
The `.sh` files are meant as references for copying and pasting the commands one by one, so that each step can be inspected and adapted. Running them directly with `bash` also works.
|
||||
|
||||
> **Note:** The scripts are not idempotent. Running a whole script with `bash` a second time may fail, because the resources created by the first run already exist, for example the workload identity pool, the provider, the service account, the bucket and the `wif-test` gcloud configuration.
|
||||
|
||||
## 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`:
|
||||
`make_certs.sh` creates a root CA, an issuing CA and a client certificate. The client certificate subject CN (`test-client`) becomes the WiF subject.
|
||||
|
||||
```bash
|
||||
source ./env.sh
|
||||
|
||||
# Root CA certificate
|
||||
echo "Generating Root CA certificate..."
|
||||
openssl req \
|
||||
-x509 \
|
||||
-newkey rsa:4096 \
|
||||
-keyout ca_key.pem \
|
||||
-out ca_cert.pem \
|
||||
-days 3650 \
|
||||
-noenc \
|
||||
-subj "/CN=Test CA $(date +"%Y-%m-%d")" \
|
||||
-addext "basicConstraints=critical,CA:TRUE,pathlen:1" \
|
||||
-addext "keyUsage=critical,keyCertSign,cRLSign"
|
||||
|
||||
# Issuing CA certificate
|
||||
echo "Generating Issuing CA certificate..."
|
||||
openssl req \
|
||||
-newkey rsa:4096 \
|
||||
-keyout issuing_ca_key.pem \
|
||||
-noenc \
|
||||
-subj "/CN=Issuing Test CA $(date +"%Y-%m-%d")" \
|
||||
-addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
|
||||
-addext "keyUsage=critical,keyCertSign,cRLSign" |
|
||||
openssl x509 \
|
||||
-req \
|
||||
-CA ca_cert.pem \
|
||||
-CAkey ca_key.pem \
|
||||
-copy_extensions copyall \
|
||||
-days 3650 \
|
||||
-out issuing_ca_cert.pem
|
||||
|
||||
# Client certificate
|
||||
echo "Generating Client certificate..."
|
||||
openssl req \
|
||||
-newkey rsa:4096 \
|
||||
-keyout client_key.pem \
|
||||
-noenc \
|
||||
-subj "/CN=test-client" \
|
||||
-addext "basicConstraints=critical,CA:FALSE" \
|
||||
-addext "keyUsage=critical,digitalSignature,keyEncipherment" \
|
||||
-addext "extendedKeyUsage=clientAuth" |
|
||||
openssl x509 \
|
||||
-req \
|
||||
-CA issuing_ca_cert.pem \
|
||||
-CAkey issuing_ca_key.pem \
|
||||
-copy_extensions copyall \
|
||||
-days 365 \
|
||||
-out client_cert.pem
|
||||
bash make_certs.sh
|
||||
```
|
||||
|
||||
## 2. Create the trust store
|
||||
|
||||
The trust store is a YAML file that holds the root CA as the trust anchor and the issuing CA as an intermediate CA. The generator uses the `yaml` npm package.
|
||||
|
||||
`package.json`:
|
||||
`make_trust_store.mjs` creates `trust_store.yaml`, a YAML file that holds the root CA as the trust anchor and the issuing CA as an intermediate CA. It uses the `yaml` npm package, declared in `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -104,38 +58,6 @@ The trust store is a YAML file that holds the root CA as the trust anchor and th
|
||||
}
|
||||
```
|
||||
|
||||
`make_trust_store.mjs`:
|
||||
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { parseArgs } from "node:util";
|
||||
import YAML from "yaml";
|
||||
|
||||
const { values } = parseArgs({
|
||||
options: {
|
||||
"ca-cert": { type: "string", default: "ca_cert.pem" },
|
||||
"issuing-ca-cert": { type: "string", default: "issuing_ca_cert.pem" },
|
||||
output: { type: "string", default: "trust_store.yaml" },
|
||||
},
|
||||
});
|
||||
|
||||
// IMPORTANT: Strip the trailing newline from the PEM files,
|
||||
// otherwise gcloud will reject the trust store with a cryptic error message.
|
||||
const trustStore = {
|
||||
trustAnchors: [{ pemCertificate: readFileSync(values["ca-cert"], "utf8").trimEnd() }],
|
||||
};
|
||||
|
||||
// The issuing CA is optional; it is included only when its file exists.
|
||||
if (existsSync(values["issuing-ca-cert"])) {
|
||||
trustStore.intermediateCas = [
|
||||
{ pemCertificate: readFileSync(values["issuing-ca-cert"], "utf8").trimEnd() },
|
||||
];
|
||||
}
|
||||
|
||||
writeFileSync(values.output, YAML.stringify({ trustStore }, { indentSeq: false }));
|
||||
```
|
||||
|
||||
Install the dependency and generate `trust_store.yaml`:
|
||||
|
||||
```bash
|
||||
@@ -147,134 +69,14 @@ 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, 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.
|
||||
|
||||
`add_audit_config.mjs`:
|
||||
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import YAML from "yaml";
|
||||
|
||||
const policy = YAML.parse(readFileSync("iam_policy.yaml", "utf8"));
|
||||
|
||||
// Replace the Cloud Storage entry, if any, and keep the other audit configs.
|
||||
policy.auditConfigs = (policy.auditConfigs ?? []).filter(
|
||||
(c) => c.service !== "storage.googleapis.com",
|
||||
);
|
||||
policy.auditConfigs.push({
|
||||
service: "storage.googleapis.com",
|
||||
auditLogConfigs: [{ logType: "DATA_READ" }, { logType: "DATA_WRITE" }],
|
||||
});
|
||||
|
||||
writeFileSync("iam_policy.yaml", YAML.stringify(policy, { indentSeq: false }));
|
||||
```
|
||||
|
||||
`make_wif.sh`:
|
||||
`make_wif.sh` 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.
|
||||
|
||||
```bash
|
||||
source ./env.sh
|
||||
|
||||
# Create a workload identity pool and a X.509 provider
|
||||
gcloud iam workload-identity-pools create "${POOL_ID}" \
|
||||
--project="${PROJECT_ID}" \
|
||||
--location=global \
|
||||
--display-name="${POOL_DISPLAY_NAME}"
|
||||
|
||||
# Create X.509 provider
|
||||
gcloud iam workload-identity-pools providers create-x509 "${PROVIDER_ID}" \
|
||||
--project="${PROJECT_ID}" \
|
||||
--location=global \
|
||||
--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="${PROJECT_ID}" \
|
||||
--display-name="SA Test"
|
||||
|
||||
# Allow impersonation of the service account
|
||||
gcloud iam service-accounts add-iam-policy-binding \
|
||||
"${SA_EMAIL}" \
|
||||
--role=roles/iam.workloadIdentityUser \
|
||||
--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 "${PROJECT_ID}" \
|
||||
--member="serviceAccount:${SA_EMAIL}" \
|
||||
--role=roles/browser
|
||||
|
||||
# Create credentials configuration files
|
||||
gcloud iam workload-identity-pools create-cred-config \
|
||||
"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://${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://${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 "${PROJECT_ID}" --format=yaml > iam_policy.yaml
|
||||
node add_audit_config.mjs
|
||||
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
|
||||
|
||||
# Authenticate gcloud with the credentials configuration file
|
||||
gcloud auth login --cred-file=cred.json
|
||||
|
||||
# Set the project
|
||||
gcloud config set project "${PROJECT_ID}"
|
||||
|
||||
# Test the authentication by listing the project details
|
||||
gcloud auth list
|
||||
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://${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_EMAIL}\"" \
|
||||
--project="${PROJECT_ID}" \
|
||||
--freshness=1h \
|
||||
--order=asc \
|
||||
--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
|
||||
bash make_wif.sh
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
The audit log lists only Cloud Storage operations of the service account. Resource Manager calls such as `projects describe` are not covered by the Data Access configuration.
|
||||
|
||||
## Result
|
||||
|
||||
+2
-2
@@ -94,5 +94,5 @@ gcloud logging read \
|
||||
--order=asc \
|
||||
--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
|
||||
# optionally switch back to the service account for further testing
|
||||
# gcloud config configurations activate wif-test
|
||||
Reference in new issue
Block a user