Added a variant of the test that uses HashiCorp Vault to manage te
Certificate Authorities.
This commit is contained in:
1 parent
7f07e47138
commit
777b86ce33
7 files changed
+236
-48
No files matched your search
@@ -7,3 +7,7 @@ package-lock.json
|
|||||||
*.downloaded.md
|
*.downloaded.md
|
||||||
iam_policy.yaml
|
iam_policy.yaml
|
||||||
env.sh
|
env.sh
|
||||||
|
vault-data/
|
||||||
|
vault-init.txt
|
||||||
|
vault.hcl
|
||||||
|
*.csr
|
||||||
@@ -5,6 +5,7 @@ This short guide provides step by step instructions how to replace a JSON key wi
|
|||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- `openssl` 3.x
|
- `openssl` 3.x
|
||||||
|
- `vault` (only for the Vault based CA in step 1a)
|
||||||
- Node.js with `npm`
|
- Node.js with `npm`
|
||||||
- `gcloud` authenticated as an administrator of the target project
|
- `gcloud` authenticated as an administrator of the target project
|
||||||
|
|
||||||
@@ -50,6 +51,97 @@ The `.sh` files are meant as references for copying and pasting the commands one
|
|||||||
bash make_certs.sh
|
bash make_certs.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 1a. Alternative: create the certificates with Vault
|
||||||
|
|
||||||
|
HashiCorp Vault Community Edition can replace the OpenSSL based CA. The PKI secrets engine hosts the root CA and the issuing CA, and a role issues the client certificate. The commands below produce the same files as `make_certs.sh`: `ca_cert.pem`, `issuing_ca_cert.pem`, `client_key.pem` and `client_cert.pem`. Continue with step 2 afterwards.
|
||||||
|
|
||||||
|
Prerequisites: the `vault` command installed on the workstation.
|
||||||
|
|
||||||
|
### Start a local Vault server
|
||||||
|
|
||||||
|
Vault dev mode keeps all data in memory, and a dev server cannot be restarted on existing file storage. The local server therefore runs in normal mode with file storage in the gitignored `vault-data` directory.
|
||||||
|
|
||||||
|
Create `vault.hcl`, replacing the path with the absolute path of the `vault-data` directory:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
storage "file" {
|
||||||
|
path = "/path/to/repository/vault-data"
|
||||||
|
}
|
||||||
|
|
||||||
|
listener "tcp" {
|
||||||
|
address = "127.0.0.1:8200"
|
||||||
|
tls_disable = true
|
||||||
|
}
|
||||||
|
|
||||||
|
api_addr = "http://127.0.0.1:8200"
|
||||||
|
disable_mlock = true
|
||||||
|
ui = true
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the server in a separate terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vault server -config=vault.hcl
|
||||||
|
```
|
||||||
|
|
||||||
|
The web UI is available at `http://127.0.0.1:8200/ui`.
|
||||||
|
|
||||||
|
Initialize the server once with a single unseal key, unseal it, and set `VAULT_TOKEN` to the generated root token. Replace `<initial-root-token>` with the initial root token printed in `vault-init.txt`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export VAULT_ADDR=http://127.0.0.1:8200
|
||||||
|
vault operator init -key-shares=1 -key-threshold=1 > vault-init.txt
|
||||||
|
vault operator unseal
|
||||||
|
export VAULT_TOKEN=<initial-root-token>
|
||||||
|
vault status
|
||||||
|
```
|
||||||
|
|
||||||
|
`vault operator unseal` asks for the unseal key from `vault-init.txt`. After every server restart, run `vault operator unseal` again and set `VAULT_ADDR` and `VAULT_TOKEN` in new terminals. The data is kept in `vault-data`.
|
||||||
|
|
||||||
|
> **Note:** The unseal key and the root token stored in a file are suitable only for a local, disposable server.
|
||||||
|
|
||||||
|
### Configure the CA hierarchy
|
||||||
|
|
||||||
|
`vault_configure.sh` performs the following steps in the terminal where `VAULT_ADDR` and `VAULT_TOKEN` are set:
|
||||||
|
|
||||||
|
1. Enables the PKI secrets engine for the root CA and for the issuing CA, and sets the maximum TTLs.
|
||||||
|
2. Generates the root CA inside Vault.
|
||||||
|
3. Generates the issuing CA key and CSR, signs it with the root CA, and installs the signed certificate.
|
||||||
|
4. Creates a role that issues client authentication certificates.
|
||||||
|
5. Exports the root CA and the issuing CA certificates (`ca_cert.pem`, `issuing_ca_cert.pem`).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash vault_configure.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create the client certificate
|
||||||
|
|
||||||
|
`vault_make_client_cert.sh` performs the following steps in the same terminal:
|
||||||
|
|
||||||
|
1. Generates the client key and CSR locally with `openssl`. The key is never sent to Vault.
|
||||||
|
2. Signs the client CSR with the issuing CA. The certificate subject CN (`test-client`) becomes the WiF subject.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash vault_make_client_cert.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The client certificate is valid for 365 days, has the Digital Signature and Key Encipherment key usages, and the TLS Web Client Authentication extended key usage. Unlike the certificate from `make_certs.sh`, it has no `basicConstraints` extension.
|
||||||
|
|
||||||
|
### Update a WiF provider created with the OpenSSL CA
|
||||||
|
|
||||||
|
If the provider was already created by `make_wif.sh` with the OpenSSL based CA, `vault_gcp_update.sh` switches it to the Vault CA. The client certificate keeps the subject CN `test-client`, so the service account binding and `cred.json` stay valid. The script performs the following steps:
|
||||||
|
|
||||||
|
1. Generates `trust_store.yaml` from the Vault CA certificates with `make_trust_store.mjs`.
|
||||||
|
2. Replaces the trust store of the existing X.509 provider.
|
||||||
|
3. Shows the provider configuration.
|
||||||
|
4. Tests the federation with the client certificate issued by Vault.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash vault_gcp_update.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
For a new setup, continue with step 2 instead.
|
||||||
|
|
||||||
## 2. Create the trust store
|
## 2. Create the trust store
|
||||||
|
|
||||||
`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`:
|
`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`:
|
||||||
@@ -81,18 +173,27 @@ The PEM files must contain only the certificate block. A trailing newline after
|
|||||||
4. Grants the service account read access to the project.
|
4. Grants the service account read access to the project.
|
||||||
5. Writes `cred.json`.
|
5. Writes `cred.json`.
|
||||||
6. Creates a storage bucket and grants the service account read and write access to its objects.
|
6. Creates a storage bucket and grants the service account read and write access to its objects.
|
||||||
7. Enables Data Access audit logs for Cloud Storage.
|
7. Makes the `public/` managed folder of the bucket publicly readable.
|
||||||
8. Creates a separate gcloud configuration and logs in with `cred.json`.
|
8. Enables Data Access audit logs for Cloud Storage.
|
||||||
9. Checks the access to the project and the bucket, including an upload and a download of a file.
|
|
||||||
10. Switches back to the original gcloud configuration.
|
|
||||||
11. Makes the `public/` managed folder of the bucket publicly readable.
|
|
||||||
12. Lists the audit log of the service account operations.
|
|
||||||
13. Optionally switches back to the service account configuration.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash make_wif.sh
|
bash make_wif.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 4. Test the federation
|
||||||
|
|
||||||
|
`test_wif.sh` performs the following steps:
|
||||||
|
|
||||||
|
1. Creates a separate gcloud configuration and logs in with `cred.json`.
|
||||||
|
2. Checks the access to the project and the bucket, including an upload and a download of a file.
|
||||||
|
3. Switches back to the original gcloud configuration.
|
||||||
|
4. Lists the audit log of the service account operations.
|
||||||
|
5. Optionally switches back to the service account configuration.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash test_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.
|
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 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.
|
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.
|
||||||
|
|||||||
+4
-41
@@ -49,50 +49,13 @@ gcloud storage buckets add-iam-policy-binding "gs://${BUCKET}" \
|
|||||||
--member="serviceAccount:${SA_EMAIL}" \
|
--member="serviceAccount:${SA_EMAIL}" \
|
||||||
--role=roles/storage.objectUser
|
--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
|
# Create a managed folder and make the objects in it publicly readable
|
||||||
gcloud storage managed-folders create "gs://${BUCKET}/public/"
|
gcloud storage managed-folders create "gs://${BUCKET}/public/"
|
||||||
gcloud storage managed-folders add-iam-policy-binding "gs://${BUCKET}/public/" \
|
gcloud storage managed-folders add-iam-policy-binding "gs://${BUCKET}/public/" \
|
||||||
--member=allUsers \
|
--member=allUsers \
|
||||||
--role=roles/storage.objectViewer
|
--role=roles/storage.objectViewer
|
||||||
|
|
||||||
# List the audit log of the operations made by the service account
|
# Enable Data Access audit logs for Cloud Storage (project-wide, not per bucket)
|
||||||
gcloud logging read \
|
gcloud projects get-iam-policy "${PROJECT_ID}" --format=yaml > iam_policy.yaml
|
||||||
"protoPayload.authenticationInfo.principalEmail=\"${SA_EMAIL}\"" \
|
node add_audit_config.mjs
|
||||||
--project="${PROJECT_ID}" \
|
gcloud projects set-iam-policy "${PROJECT_ID}" iam_policy.yaml
|
||||||
--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)"
|
|
||||||
|
|
||||||
# optionally switch back to the service account for further testing
|
|
||||||
# gcloud config configurations activate wif-test
|
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
source ./env.sh
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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)"
|
||||||
|
|
||||||
|
# optionally switch back to the service account for further testing
|
||||||
|
# gcloud config configurations activate wif-test
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# Enable the PKI secrets engine for the root CA and for the issuing CA
|
||||||
|
vault secrets enable pki
|
||||||
|
vault secrets enable -path=pki_int pki
|
||||||
|
|
||||||
|
# Set the maximum TTLs of the CAs
|
||||||
|
vault secrets tune -max-lease-ttl=87600h pki
|
||||||
|
vault secrets tune -max-lease-ttl=43800h pki_int
|
||||||
|
|
||||||
|
# Generate the root CA inside Vault
|
||||||
|
vault write pki/root/generate/internal \
|
||||||
|
common_name="Test CA" \
|
||||||
|
issuer_name=root \
|
||||||
|
key_type=rsa \
|
||||||
|
key_bits=4096 \
|
||||||
|
ttl=87600h \
|
||||||
|
max_path_length=1
|
||||||
|
|
||||||
|
# Generate the issuing CA key and CSR
|
||||||
|
vault write -field=csr pki_int/intermediate/generate/internal \
|
||||||
|
common_name="Issuing Test CA" \
|
||||||
|
key_type=rsa \
|
||||||
|
key_bits=4096 > issuing_ca.csr
|
||||||
|
|
||||||
|
# Sign the issuing CA CSR with the root CA
|
||||||
|
vault write -field=certificate pki/root/sign-intermediate \
|
||||||
|
csr=@issuing_ca.csr \
|
||||||
|
format=pem_bundle \
|
||||||
|
ttl=43800h \
|
||||||
|
max_path_length=0 > issuing_ca_signed.pem
|
||||||
|
|
||||||
|
# Install the signed issuing CA certificate
|
||||||
|
vault write pki_int/intermediate/set-signed certificate=@issuing_ca_signed.pem
|
||||||
|
|
||||||
|
# Create a role that issues client authentication certificates
|
||||||
|
vault write pki_int/roles/wif-client \
|
||||||
|
allow_any_name=true \
|
||||||
|
enforce_hostnames=false \
|
||||||
|
server_flag=false \
|
||||||
|
client_flag=true \
|
||||||
|
key_type=rsa \
|
||||||
|
key_bits=4096 \
|
||||||
|
key_usage=DigitalSignature,KeyEncipherment \
|
||||||
|
ttl=8760h \
|
||||||
|
max_ttl=8760h \
|
||||||
|
no_store=true
|
||||||
|
|
||||||
|
# Export the CA certificates
|
||||||
|
vault read -field=certificate pki/cert/ca > ca_cert.pem
|
||||||
|
vault read -field=certificate pki_int/cert/ca > issuing_ca_cert.pem
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
source ./env.sh
|
||||||
|
|
||||||
|
# Generate the trust store from the Vault CA certificates
|
||||||
|
node make_trust_store.mjs
|
||||||
|
|
||||||
|
# Replace the trust store of the existing X.509 provider
|
||||||
|
gcloud iam workload-identity-pools providers update-x509 "${PROVIDER_ID}" \
|
||||||
|
--project="${PROJECT_ID}" \
|
||||||
|
--location=global \
|
||||||
|
--workload-identity-pool="${POOL_ID}" \
|
||||||
|
--trust-store-config-path=trust_store.yaml
|
||||||
|
|
||||||
|
# Show the provider configuration
|
||||||
|
gcloud iam workload-identity-pools providers describe "${PROVIDER_ID}" \
|
||||||
|
--project="${PROJECT_ID}" \
|
||||||
|
--location=global \
|
||||||
|
--workload-identity-pool="${POOL_ID}"
|
||||||
|
|
||||||
|
# Test the federation with the client certificate issued by Vault
|
||||||
|
gcloud storage ls "gs://${BUCKET}" --configuration=wif-test
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Generate the client key and CSR locally, the key is never sent to Vault
|
||||||
|
openssl req \
|
||||||
|
-newkey rsa:4096 \
|
||||||
|
-keyout client_key.pem \
|
||||||
|
-noenc \
|
||||||
|
-subj "/CN=test-client" \
|
||||||
|
-out client.csr
|
||||||
|
|
||||||
|
# Sign the client CSR
|
||||||
|
vault write -field=certificate pki_int/sign/wif-client \
|
||||||
|
csr=@client.csr \
|
||||||
|
common_name=test-client \
|
||||||
|
ttl=8760h > client_cert.pem
|
||||||
Reference in new issue
Block a user