Skip to content
Merged
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
54 changes: 54 additions & 0 deletions .github/workflows/issue-production-release-admission.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Release admission issuer

on:
workflow_dispatch:

permissions: {}

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

jobs:
refuse-inactive-issuer:
permissions:
contents: read
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout the exact requested source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Verify the inactive release admission issuer interface and refuse issuance
env:
EVENT_NAME: ${{ github.event_name }}
EXPECTED_ACTIVATION_STATE: inactive
REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
SOURCE_COMMIT: ${{ github.sha }}
run: |
set -euo pipefail
test "$REPOSITORY" = 'OpenAdaptAI/.github'
test "$REF" = 'refs/heads/main'
test "$EVENT_NAME" = 'workflow_dispatch'
test "$(git rev-parse HEAD)" = "$SOURCE_COMMIT"
python3 scripts/qualification_issuer.py interface > "$RUNNER_TEMP/interface.json"
INTERFACE_PATH="$RUNNER_TEMP/interface.json" python3 - <<'PY'
import json
import os
from pathlib import Path

interface = json.loads(Path(os.environ["INTERFACE_PATH"]).read_text())
if interface.get("activation_state") != os.environ["EXPECTED_ACTIVATION_STATE"]:
raise SystemExit("the release admission issuer interface is not inactive")
if interface.get("accepted_evidence_class") != "remote-safe-synthetic":
raise SystemExit("the release admission issuer evidence class differs")
registry = json.loads(Path("evidence-registry.json").read_text())
if registry.get("signer_registry") is not None:
raise SystemExit("a live signer registry is present; keep this issuer refuse-closed")
PY
echo '::error::The release admission issuer is installed but inactive.' >&2
echo 'The signer registry, authority state, revocation state, and shared one-use storage must exist before activation.' >&2
exit 1
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ jobs:
with:
persist-credentials: false

- name: Verify the inactive KMS issuer interface and refuse issuance
- name: Verify the inactive software issuer interface and refuse issuance
env:
EVENT_NAME: ${{ github.event_name }}
EXPECTED_ACTIVATION_STATE: inactive
EXPECTED_GITHUB_SECRET_NAME: OPENADAPT_QUALIFICATION_ED25519_PRIVATE_KEY
REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
SOURCE_COMMIT: ${{ github.sha }}
Expand All @@ -34,18 +35,25 @@ jobs:
test "$REF" = 'refs/heads/main'
test "$EVENT_NAME" = 'workflow_dispatch'
test "$(git rev-parse HEAD)" = "$SOURCE_COMMIT"
python3 scripts/qualification_kms_ed25519.py interface > "$RUNNER_TEMP/interface.json"
python3 scripts/qualification_software_ed25519.py interface > "$RUNNER_TEMP/interface.json"
INTERFACE_PATH="$RUNNER_TEMP/interface.json" python3 - <<'PY'
import json
import os
from pathlib import Path

interface = json.loads(Path(os.environ["INTERFACE_PATH"]).read_text())
if interface.get("activation_state") != os.environ["EXPECTED_ACTIVATION_STATE"]:
raise SystemExit("the KMS issuer interface is not inactive")
raise SystemExit("the software issuer interface is not inactive")
if interface.get("allowed_evidence_class") != "remote-safe-synthetic":
raise SystemExit("the KMS issuer evidence class differs")
raise SystemExit("the software issuer evidence class differs")
if interface.get("aws_required") is not False:
raise SystemExit("the software issuer must not require AWS")
if interface.get("github_secret_name") != os.environ["EXPECTED_GITHUB_SECRET_NAME"]:
raise SystemExit("the software issuer GitHub secret name differs")
registry = json.loads(Path("evidence-registry.json").read_text())
if registry.get("signer_registry") is not None:
raise SystemExit("a live signer registry is present; keep this issuer refuse-closed")
PY
echo '::error::The synthetic qualification decision issuer is installed but inactive.' >&2
echo 'KMS/OIDC, the protected environment, the signer registry, revocation state, and shared one-use storage must exist before activation.' >&2
echo 'The GitHub Ed25519 secret can sign only after a published signer registry exists. Keep this issuer refuse-closed until then. This path does not use KMS or OIDC.' >&2
exit 1
27 changes: 25 additions & 2 deletions tests/test_governance_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@
"interface_command": "python3 scripts/qualification_issuer.py interface",
"interface_label": "admission issuer",
},
"issue-production-release-admission.yml": {
"interface_command": "python3 scripts/qualification_issuer.py interface",
"interface_label": "release admission issuer",
},
"issue-synthetic-qualification-evidence-decision.yml": {
"interface_command": (
"python3 scripts/qualification_kms_ed25519.py interface"
"python3 scripts/qualification_software_ed25519.py interface"
),
"interface_label": "KMS issuer",
"interface_label": "software issuer",
},
}

Expand Down Expand Up @@ -174,6 +178,25 @@ def test_inactive_issuer_workflows_cannot_issue_or_acquire_authority(self) -> No
for marker in forbidden:
self.assertNotIn(marker, content, f"{filename}: {marker}")

def test_synthetic_decision_issuer_uses_software_ed25519_not_kms(self) -> None:
content = _read(
".github/workflows/issue-synthetic-qualification-evidence-decision.yml"
)
self.assertIn(
"python3 scripts/qualification_software_ed25519.py interface",
content,
)
self.assertNotIn("qualification_kms_ed25519.py", content)
self.assertNotIn("KMS/OIDC", content)
self.assertIn("aws_required", content)
self.assertIn(
"OPENADAPT_QUALIFICATION_ED25519_PRIVATE_KEY",
content,
)
self.assertNotIn("${{ secrets.", content)
self.assertNotIn("id-token:", content)
self.assertIn("signer_registry", content)

def test_lifecycle_workflows_are_app_only_review_paths(self) -> None:
for filename, environment in LIFECYCLE_WORKFLOWS.items():
content = _read(f".github/workflows/{filename}")
Expand Down