Some checks failed
Build Test Deploy / docs (push) Has been cancelled
Build Test Deploy / deploy (push) Has been cancelled
Build Test Deploy / build-test (push) Has been cancelled
Build Test Deploy / authority-container (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
164 lines
5.2 KiB
YAML
164 lines
5.2 KiB
YAML
# .gitea/workflows/authority-key-rotation.yml
|
|
# Manual workflow to push a new Authority signing key using OPS3 tooling
|
|
|
|
name: Authority Key Rotation
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
environment:
|
|
description: 'Target environment name (used to select secrets/vars)'
|
|
required: true
|
|
default: 'staging'
|
|
type: choice
|
|
options:
|
|
- staging
|
|
- production
|
|
authority_url:
|
|
description: 'Override Authority URL (leave blank to use env-specific secret)'
|
|
required: false
|
|
default: ''
|
|
type: string
|
|
key_id:
|
|
description: 'New signing key identifier (kid)'
|
|
required: true
|
|
type: string
|
|
key_path:
|
|
description: 'Path (as Authority sees it) to the PEM key'
|
|
required: true
|
|
type: string
|
|
source:
|
|
description: 'Signing key source loader (default: file)'
|
|
required: false
|
|
default: 'file'
|
|
type: string
|
|
algorithm:
|
|
description: 'Signing algorithm (default: ES256)'
|
|
required: false
|
|
default: 'ES256'
|
|
type: string
|
|
provider:
|
|
description: 'Preferred crypto provider hint'
|
|
required: false
|
|
default: ''
|
|
type: string
|
|
metadata:
|
|
description: 'Optional key=value metadata entries (comma-separated)'
|
|
required: false
|
|
default: ''
|
|
type: string
|
|
|
|
jobs:
|
|
rotate:
|
|
runs-on: ubuntu-22.04
|
|
environment: ${{ inputs.environment }}
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Resolve Authority configuration
|
|
id: config
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
env_name=${{ inputs.environment }}
|
|
echo "Environment: $env_name"
|
|
|
|
bootstrap_key=""
|
|
authority_url="${{ inputs.authority_url }}"
|
|
|
|
# Helper to prefer secrets over variables and fall back to shared defaults
|
|
resolve_var() {
|
|
local name="$1"
|
|
local default="$2"
|
|
local value="${{ secrets[name] }}"
|
|
if [ -z "$value" ]; then value="${{ vars[name] }}"; fi
|
|
if [ -z "$value" ]; then value="$default"; fi
|
|
printf '%s' "$value"
|
|
}
|
|
|
|
key_name="${env_name^^}_AUTHORITY_BOOTSTRAP_KEY"
|
|
bootstrap_key="$(resolve_var "$key_name" "")"
|
|
if [ -z "$bootstrap_key" ]; then
|
|
bootstrap_key="$(resolve_var "AUTHORITY_BOOTSTRAP_KEY" "")"
|
|
fi
|
|
|
|
if [ -z "$bootstrap_key" ]; then
|
|
echo "::error::Missing bootstrap key secret (expected $key_name or AUTHORITY_BOOTSTRAP_KEY)"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "$authority_url" ]; then
|
|
url_name="${env_name^^}_AUTHORITY_URL"
|
|
authority_url="$(resolve_var "$url_name" "")"
|
|
if [ -z "$authority_url" ]; then
|
|
authority_url="$(resolve_var "AUTHORITY_URL" "")"
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$authority_url" ]; then
|
|
echo "::error::Authority URL not provided and no secret/var found"
|
|
exit 1
|
|
fi
|
|
|
|
key_file="${RUNNER_TEMP}/authority-bootstrap-key"
|
|
printf '%s\n' "$bootstrap_key" > "$key_file"
|
|
chmod 600 "$key_file"
|
|
|
|
echo "bootstrap-key-file=$key_file" >> "$GITHUB_OUTPUT"
|
|
echo "authority-url=$authority_url" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Execute key rotation
|
|
id: rotate
|
|
shell: bash
|
|
env:
|
|
AUTHORITY_BOOTSTRAP_KEY_FILE: ${{ steps.config.outputs['bootstrap-key-file'] }}
|
|
AUTHORITY_URL: ${{ steps.config.outputs['authority-url'] }}
|
|
KEY_ID: ${{ inputs.key_id }}
|
|
KEY_PATH: ${{ inputs.key_path }}
|
|
KEY_SOURCE: ${{ inputs.source }}
|
|
KEY_ALGORITHM: ${{ inputs.algorithm }}
|
|
KEY_PROVIDER: ${{ inputs.provider }}
|
|
KEY_METADATA: ${{ inputs.metadata }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
bootstrap_key=$(cat "$AUTHORITY_BOOTSTRAP_KEY_FILE")
|
|
|
|
metadata_args=()
|
|
if [ -n "$KEY_METADATA" ]; then
|
|
IFS=',' read -ra META <<< "$KEY_METADATA"
|
|
for entry in "${META[@]}"; do
|
|
trimmed="$(echo "$entry" | xargs)"
|
|
[ -z "$trimmed" ] && continue
|
|
metadata_args+=(-m "$trimmed")
|
|
done
|
|
fi
|
|
|
|
provider_args=()
|
|
if [ -n "$KEY_PROVIDER" ]; then
|
|
provider_args+=(--provider "$KEY_PROVIDER")
|
|
fi
|
|
|
|
./ops/authority/key-rotation.sh \
|
|
--authority-url "$AUTHORITY_URL" \
|
|
--api-key "$bootstrap_key" \
|
|
--key-id "$KEY_ID" \
|
|
--key-path "$KEY_PATH" \
|
|
--source "$KEY_SOURCE" \
|
|
--algorithm "$KEY_ALGORITHM" \
|
|
"${provider_args[@]}" \
|
|
"${metadata_args[@]}"
|
|
|
|
- name: JWKS summary
|
|
run: |
|
|
echo "✅ Rotation complete"
|
|
echo "Environment: ${{ inputs.environment }}"
|
|
echo "Authority: ${{ steps.config.outputs['authority-url'] }}"
|
|
echo "Key ID: ${{ inputs.key_id }}"
|
|
echo "Key Path: ${{ inputs.key_path }}"
|
|
echo "Source: ${{ inputs.source }}"
|
|
echo "Algorithm: ${{ inputs.algorithm }}"
|