Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/kms-vendor-shared-test-harness.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cipherstash/auth': patch
---

No change to the binding. The `stack-kms-vendor` crate that it compiles in has a new test-only module, `replay`, behind its `test-support` feature. The binding does not turn on that feature, so the module is not in the binary.
149 changes: 149 additions & 0 deletions .github/workflows/_test-kms-live.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
name: Key vendor backend live test

# The one job behind the opt-in live workflows of the key vendor backends
# that have no emulator: integration-gcp-kms.yml (Google Cloud KMS) and
# integration-azure-key-vault.yml (Azure Key Vault). Each caller keeps its
# triggers, its opt-in and fork-PR condition on the calling job, and the
# names of its repository variables and secrets. This file holds what was
# the same in both: the secret pre-flight, the toolchain, and the
# `mise run test:live:<crate>` step.
#
# A caller gives:
#
# * `crate`: the crate. The test task is `test:live:<crate>`.
# * `name`: the name of the test step.
# * `live-key-variable`: the repository variable that turns the caller on,
# for the pre-flight message.
# * `required-secrets`: one line for each secret the test needs:
# `<secret of this workflow> <repository secret>`. The pre-flight fails
# the job when one of them is empty: a credentialed job that skips for
# want of a secret reads exactly like one that passes.
# * `live-env`: `NAME=value` lines (repository variables, not secrets)
# that the test reads.
# * the secrets, by the names below. `credentials-json` goes to a file
# only this user can read, outside the checkout, and its path goes in
# the variable `credentials-file-env` names. The `azure-*` secrets reach
# only the test step's environment.

on:
workflow_call:
inputs:
crate:
type: string
required: true
name:
type: string
required: true
live-key-variable:
type: string
required: true
required-secrets:
type: string
required: true
live-env:
type: string
required: true
credentials-file-env:
type: string
default: ''
secrets:
credentials-json:
required: false
azure-tenant-id:
required: false
azure-client-id:
required: false
azure-client-secret:
required: false

permissions:
contents: read

defaults:
run:
shell: bash

jobs:
live:
runs-on: blacksmith-16vcpu-ubuntu-2204
timeout-minutes: 30
env:
MISE_ENV: test
steps:
# Fast pre-flight: fail in seconds if a secret was rotated or cleared,
# before the toolchain install and the compile.
- name: Require the secrets
env:
LIVE_KEY_VARIABLE: ${{ inputs.live-key-variable }}
REQUIRED_SECRETS: ${{ inputs.required-secrets }}
credentials_json: ${{ secrets.credentials-json }}
azure_tenant_id: ${{ secrets.azure-tenant-id }}
azure_client_id: ${{ secrets.azure-client-id }}
azure_client_secret: ${{ secrets.azure-client-secret }}
run: |
missing=0
while read -r input secret; do
[ -n "$input" ] || continue
name="${input//-/_}"
if [ -z "${!name}" ]; then
echo "::error::$LIVE_KEY_VARIABLE is set but the secret $secret is empty"
missing=1
fi
done <<< "$REQUIRED_SECRETS"
exit "$missing"

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4
with:
version: 2026.4.0
install: true
working_directory: .
cache: true
# Must name runs-on's image; see scripts/__tests__/workflow-mise-cache-image.test.mjs
cache_key_prefix: mise-v1-ubuntu-2204

- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2

# Repository variables, not secrets. Each line must be NAME=value.
- name: Set the live key variables
env:
LIVE_ENV: ${{ inputs.live-env }}
run: |
while IFS= read -r line; do
[ -n "$line" ] || continue
if ! [[ "$line" =~ ^[A-Z][A-Z0-9_]*= ]]; then
echo "::error::live-env line is not NAME=value"
exit 1
fi
echo "$line" >> "$GITHUB_ENV"
done <<< "$LIVE_ENV"

- name: Write the credentials file
if: inputs.credentials-file-env != ''
env:
CREDENTIALS_JSON: ${{ secrets.credentials-json }}
CREDENTIALS_FILE_ENV: ${{ inputs.credentials-file-env }}
CRATE: ${{ inputs.crate }}
run: |
umask 077
printf '%s' "$CREDENTIALS_JSON" > "$RUNNER_TEMP/$CRATE-live.json"
echo "$CREDENTIALS_FILE_ENV=$RUNNER_TEMP/$CRATE-live.json" >> "$GITHUB_ENV"

- name: ${{ inputs.name }}
env:
AZURE_TENANT_ID: ${{ secrets.azure-tenant-id }}
AZURE_CLIENT_ID: ${{ secrets.azure-client-id }}
AZURE_CLIENT_SECRET: ${{ secrets.azure-client-secret }}
# The input is a constant in a caller in this repository. It is
# written into the command, not passed through the environment, so
# scripts/__tests__/crates-ci.test.mjs can see which task runs.
run: mise run test:live:${{ inputs.crate }}

- name: Remove the credentials file
if: always() && inputs.credentials-file-env != ''
env:
CRATE: ${{ inputs.crate }}
run: rm -f "$RUNNER_TEMP/$CRATE-live.json"
76 changes: 24 additions & 52 deletions .github/workflows/integration-azure-key-vault.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ name: Integration — stack-kms-azure (Azure Key Vault)
# STACK_KMS_AZURE_CLIENT_SECRET.
#
# Once the variable is set, a missing or empty secret FAILS the job (the
# pre-flight below): a credentialed job that skips for want of a secret reads
# exactly like one that passes.
# pre-flight in _test-kms-live.yml): a credentialed job that skips for want
# of a secret reads exactly like one that passes.
#
# The job itself is _test-kms-live.yml, shared with integration-gcp-kms.yml.
# This file keeps the triggers, the opt-in, the fork-PR condition and the
# names of the variables and the secrets.
#
# Fork PRs have no secrets, so the job skips them; every other event runs.
# Enforced by scripts/__tests__/workflow-dispatch-job-conditions.test.mjs.
Expand All @@ -40,6 +44,7 @@ on:
- packages/stack-kms/**
- Cargo.lock
- .github/workflows/integration-azure-key-vault.yml
- .github/workflows/_test-kms-live.yml
pull_request:
# Repeated verbatim: GitHub Actions does not support YAML anchors/aliases.
paths:
Expand All @@ -48,69 +53,36 @@ on:
- packages/stack-kms/**
- Cargo.lock
- .github/workflows/integration-azure-key-vault.yml
- .github/workflows/_test-kms-live.yml
workflow_dispatch: {}

permissions:
contents: read

defaults:
run:
shell: bash

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
live:
name: stack-kms-azure against Azure Key Vault
runs-on: blacksmith-16vcpu-ubuntu-2204
timeout-minutes: 30
if: >-
vars.STACK_KMS_AZURE_LIVE_KEY != ''
&& (github.event_name != 'pull_request'
|| github.event.pull_request.head.repo.full_name == github.repository)
env:
MISE_ENV: test
STACK_KMS_AZURE_LIVE_KEY: ${{ vars.STACK_KMS_AZURE_LIVE_KEY }}
STACK_KMS_AZURE_LIVE_HSM_KEY: ${{ vars.STACK_KMS_AZURE_LIVE_HSM_KEY }}
steps:
# Fast pre-flight: fail in seconds if a secret was rotated or cleared,
# before the toolchain install and the compile.
- name: Require the Azure service principal
env:
TENANT_ID: ${{ secrets.STACK_KMS_AZURE_TENANT_ID }}
CLIENT_ID: ${{ secrets.STACK_KMS_AZURE_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.STACK_KMS_AZURE_CLIENT_SECRET }}
run: |
missing=0
for name in TENANT_ID CLIENT_ID CLIENT_SECRET; do
if [ -z "${!name}" ]; then
echo "::error::STACK_KMS_AZURE_LIVE_KEY is set but the secret STACK_KMS_AZURE_${name} is empty"
missing=1
fi
done
exit "$missing"

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4
with:
version: 2026.4.0
install: true
working_directory: .
cache: true
# Must name runs-on's image; see scripts/__tests__/workflow-mise-cache-image.test.mjs
cache_key_prefix: mise-v1-ubuntu-2204

- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2

# The secrets reach only this step's environment.
- name: stack-kms-azure against Azure Key Vault
env:
AZURE_TENANT_ID: ${{ secrets.STACK_KMS_AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.STACK_KMS_AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.STACK_KMS_AZURE_CLIENT_SECRET }}
run: mise run test:live:stack-kms-azure
uses: ./.github/workflows/_test-kms-live.yml
with:
crate: stack-kms-azure
name: stack-kms-azure against Azure Key Vault
live-key-variable: STACK_KMS_AZURE_LIVE_KEY
required-secrets: |
azure-tenant-id STACK_KMS_AZURE_TENANT_ID
azure-client-id STACK_KMS_AZURE_CLIENT_ID
azure-client-secret STACK_KMS_AZURE_CLIENT_SECRET
live-env: |
STACK_KMS_AZURE_LIVE_KEY=${{ vars.STACK_KMS_AZURE_LIVE_KEY }}
STACK_KMS_AZURE_LIVE_HSM_KEY=${{ vars.STACK_KMS_AZURE_LIVE_HSM_KEY }}
secrets:
azure-tenant-id: ${{ secrets.STACK_KMS_AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.STACK_KMS_AZURE_CLIENT_ID }}
azure-client-secret: ${{ secrets.STACK_KMS_AZURE_CLIENT_SECRET }}
73 changes: 19 additions & 54 deletions .github/workflows/integration-gcp-kms.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ name: Integration — stack-kms-gcp (Google Cloud KMS)
# repository secret STACK_KMS_GCP_SERVICE_ACCOUNT_KEY to the JSON key.
#
# Once the variable is set, a missing or empty secret FAILS the job (the
# pre-flight below): a credentialed job that skips for want of a secret reads
# exactly like one that passes.
# pre-flight in _test-kms-live.yml): a credentialed job that skips for want
# of a secret reads exactly like one that passes.
#
# The job itself is _test-kms-live.yml, shared with
# integration-azure-key-vault.yml. This file keeps the triggers, the opt-in,
# the fork-PR condition and the names of the variable and the secret.
#
# Fork PRs have no secrets, so the job skips them; every other event runs.
# Enforced by scripts/__tests__/workflow-dispatch-job-conditions.test.mjs.
Expand All @@ -33,6 +37,7 @@ on:
- packages/stack-kms/**
- Cargo.lock
- .github/workflows/integration-gcp-kms.yml
- .github/workflows/_test-kms-live.yml
pull_request:
# Repeated verbatim: GitHub Actions does not support YAML anchors/aliases.
paths:
Expand All @@ -41,71 +46,31 @@ on:
- packages/stack-kms/**
- Cargo.lock
- .github/workflows/integration-gcp-kms.yml
- .github/workflows/_test-kms-live.yml
workflow_dispatch: {}

permissions:
contents: read

defaults:
run:
shell: bash

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
live:
name: stack-kms-gcp against Cloud KMS
runs-on: blacksmith-16vcpu-ubuntu-2204
timeout-minutes: 30
if: >-
vars.STACK_KMS_GCP_LIVE_KEY != ''
&& (github.event_name != 'pull_request'
|| github.event.pull_request.head.repo.full_name == github.repository)
env:
MISE_ENV: test
STACK_KMS_GCP_LIVE_KEY: ${{ vars.STACK_KMS_GCP_LIVE_KEY }}
steps:
# Fast pre-flight: fail in seconds if the secret was rotated or
# cleared, before the toolchain install and the compile.
- name: Require the Cloud KMS service account key
env:
SERVICE_ACCOUNT_KEY: ${{ secrets.STACK_KMS_GCP_SERVICE_ACCOUNT_KEY }}
run: |
if [ -z "$SERVICE_ACCOUNT_KEY" ]; then
echo "::error::STACK_KMS_GCP_LIVE_KEY is set but the secret STACK_KMS_GCP_SERVICE_ACCOUNT_KEY is empty"
exit 1
fi

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4
with:
version: 2026.4.0
install: true
working_directory: .
cache: true
# Must name runs-on's image; see scripts/__tests__/workflow-mise-cache-image.test.mjs
cache_key_prefix: mise-v1-ubuntu-2204

- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2

# The key goes to a file only this user can read, outside the
# checkout, and is removed after the test.
- name: Write the service account key
env:
SERVICE_ACCOUNT_KEY: ${{ secrets.STACK_KMS_GCP_SERVICE_ACCOUNT_KEY }}
run: |
umask 077
printf '%s' "$SERVICE_ACCOUNT_KEY" > "$RUNNER_TEMP/stack-kms-gcp-live.json"
echo "GOOGLE_APPLICATION_CREDENTIALS=$RUNNER_TEMP/stack-kms-gcp-live.json" >> "$GITHUB_ENV"

- name: stack-kms-gcp against Cloud KMS
run: mise run test:live:stack-kms-gcp

- name: Remove the service account key
if: always()
run: rm -f "$RUNNER_TEMP/stack-kms-gcp-live.json"
uses: ./.github/workflows/_test-kms-live.yml
with:
crate: stack-kms-gcp
name: stack-kms-gcp against Cloud KMS
live-key-variable: STACK_KMS_GCP_LIVE_KEY
required-secrets: credentials-json STACK_KMS_GCP_SERVICE_ACCOUNT_KEY
live-env: STACK_KMS_GCP_LIVE_KEY=${{ vars.STACK_KMS_GCP_LIVE_KEY }}
# The test reads the service account key file this names.
credentials-file-env: GOOGLE_APPLICATION_CREDENTIALS
secrets:
credentials-json: ${{ secrets.STACK_KMS_GCP_SERVICE_ACCOUNT_KEY }}
Loading
Loading