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
147 changes: 147 additions & 0 deletions .github/workflows/production-lifecycle-ref.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
name: Production lifecycle feed ref

on:
workflow_dispatch:
inputs:
feed_update_json:
description: Closed openadapt.production-lifecycle-feed-update/v1 JSON
required: true
type: string

permissions:
contents: read
attestations: read

concurrency:
group: production-lifecycle-feed-ref
cancel-in-progress: false

jobs:
authorize:
runs-on: ubuntu-latest
timeout-minutes: 2
permissions: {}
steps:
- name: Require the registered lifecycle App dispatcher
env:
ACTOR: ${{ github.actor }}
ACTOR_ID: ${{ github.actor_id }}
TRIGGERING_ACTOR: ${{ github.triggering_actor }}
shell: bash
run: |
set -euo pipefail
test "$ACTOR" = 'openadapt-lifecycle[bot]'
test "$ACTOR_ID" = '321544198'
test "$TRIGGERING_ACTOR" = 'openadapt-lifecycle[bot]'

update:
needs: authorize
runs-on: ubuntu-latest
timeout-minutes: 10
environment: production-lifecycle-feed
steps:
- name: Check out current protected main
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 2
persist-credentials: false

- name: Mint the lifecycle App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: '4730735'
private-key: ${{ secrets.OPENADAPT_LIFECYCLE_APP_PRIVATE_KEY }}
owner: OpenAdaptAI
repositories: .github

- name: Install the pinned GitHub attestation verifier
shell: bash
run: |
set -euo pipefail
archive="$RUNNER_TEMP/gh_2.98.0_linux_amd64.tar.gz"
curl --fail --location --silent --show-error \
--output "$archive" \
https://github.com/cli/cli/releases/download/v2.98.0/gh_2.98.0_linux_amd64.tar.gz
echo "3b8ac6b30336802fc1a858d7c084e11cdf24ac1a761ca90b68022d7d729208de $archive" \
| sha256sum --check --strict
tar --extract --gzip --file "$archive" --directory "$RUNNER_TEMP"
echo "$RUNNER_TEMP/gh_2.98.0_linux_amd64/bin" >> "$GITHUB_PATH"

- name: Install the pinned message-signature verifier
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.0
with:
cosign-release: v3.1.3

- name: Bind Cosign and the Sigstore trust material
shell: bash
run: |
set -euo pipefail
cosign_path=$(command -v cosign)
test "$(wc -c < "$cosign_path" | tr -d ' ')" = '141178250'
echo "4629c757b7618056f8ddd7e2625ae9fdd94c0372a65049520bc7d9df9efc7f71 $cosign_path" \
| sha256sum --check --strict
trusted_root="$RUNNER_TEMP/sigstore-trusted-root.json"
signing_config="$RUNNER_TEMP/sigstore-signing-config.json"
curl --fail --location --silent --show-error \
--output "$trusted_root" \
https://raw.githubusercontent.com/sigstore/root-signing/c6f23ff62645fb0c46ebc7945675835b52f91aa8/targets/trusted_root.json
test "$(wc -c < "$trusted_root" | tr -d ' ')" = '6787'
echo "6494e21ea73fa7ee769f85f57d5a3e6a08725eae1e38c755fc3517c9e6bc0b66 $trusted_root" \
| sha256sum --check --strict
curl --fail --location --silent --show-error \
--output "$signing_config" \
https://raw.githubusercontent.com/sigstore/root-signing/c6f23ff62645fb0c46ebc7945675835b52f91aa8/targets/signing_config.json
test "$(wc -c < "$signing_config" | tr -d ' ')" = '219'
echo "d358c75d032833f4193500f5b01b5760409410558fac962c599439adbb268b0f $signing_config" \
| sha256sum --check --strict
echo "OPENADAPT_SIGSTORE_TRUSTED_ROOT=$trusted_root" >> "$GITHUB_ENV"

- name: Validate the exact feed commit
env:
FEED_UPDATE_JSON: ${{ inputs.feed_update_json }}
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
new_commit=$(python3 -c 'import json,os; print(json.loads(os.environ["FEED_UPDATE_JSON"])["new_commit"])')
expected_old=$(python3 -c 'import json,os; print(json.loads(os.environ["FEED_UPDATE_JSON"])["expected_old_commit"] or "")')
git fetch --no-tags origin "$new_commit"
if [ -n "$expected_old" ]; then
git fetch --no-tags origin "$expected_old"
fi
python3 scripts/validate_production_lifecycle_ref.py \
--update-json "$FEED_UPDATE_JSON" \
--trusted-main "${{ github.sha }}"

- name: Verify lifecycle App installation
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
shell: bash
run: |
set -euo pipefail
test "$(gh api /installation --jq '.app_id|tostring')" = '4730735'
test "$(gh api /installation --jq '.id|tostring')" = '156835618'
test "$(gh api /installation --jq '.account.id|tostring')" = '132681217'

- name: Compare and swap the protected feed ref
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
FEED_UPDATE_JSON: ${{ inputs.feed_update_json }}
shell: bash
run: |
set -euo pipefail
expected_old=$(python3 -c 'import json,os; print(json.loads(os.environ["FEED_UPDATE_JSON"])["expected_old_commit"] or "")')
new_commit=$(python3 -c 'import json,os; print(json.loads(os.environ["FEED_UPDATE_JSON"])["new_commit"])')
current=$(gh api /repos/OpenAdaptAI/.github/git/ref/heads/production-lifecycle-feed \
--jq .object.sha 2>/dev/null || true)
test "$current" = "$expected_old"
if [ -z "$expected_old" ]; then
gh api --method POST /repos/OpenAdaptAI/.github/git/refs \
-f ref=refs/heads/production-lifecycle-feed -f sha="$new_commit" >/dev/null
else
test "$(gh api /repos/OpenAdaptAI/.github/git/ref/heads/production-lifecycle-feed --jq .object.sha)" = "$expected_old"
gh api --method PATCH /repos/OpenAdaptAI/.github/git/refs/heads/production-lifecycle-feed \
-f sha="$new_commit" -F force=false >/dev/null
fi
test "$(gh api /repos/OpenAdaptAI/.github/git/ref/heads/production-lifecycle-feed --jq .object.sha)" = "$new_commit"
4 changes: 3 additions & 1 deletion .github/workflows/profile-consistency.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ jobs:
else
printf '{"admissions":[]}\n' > "$RUNNER_TEMP/previous-production-lifecycle-admissions.json"
fi
python3 scripts/validate_production_lifecycle.py --previous-admissions "$RUNNER_TEMP/previous-production-lifecycle-admissions.json"
python3 scripts/validate_production_lifecycle.py \
--history-only \
--previous-admissions "$RUNNER_TEMP/previous-production-lifecycle-admissions.json"
- name: Validate product truth and links
run: python3 scripts/check_profile.py

Expand Down
184 changes: 184 additions & 0 deletions .github/workflows/verify-production-release-admission.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
name: Verify Production release admission

on:
workflow_call:
inputs:
admission_reference_json: {required: true, type: string}
artifact_inventory_json: {required: true, type: string}
candidate_artifact_name: {required: true, type: string}
expected_target: {required: true, type: string}
expected_repository: {required: true, type: string}
expected_repository_id: {required: true, type: string}
expected_source_commit: {required: true, type: string}
expected_version: {required: true, type: string}
expected_tag: {required: true, type: string}
central_verifier_sha: {required: true, type: string}
outputs:
admission_object_sha256:
value: ${{ jobs.verify.outputs.admission_object_sha256 }}
release_sha256:
value: ${{ jobs.verify.outputs.release_sha256 }}
artifact_inventory_sha256:
value: ${{ jobs.verify.outputs.artifact_inventory_sha256 }}
publication_staging_json:
value: ${{ jobs.verify.outputs.publication_staging_json }}
publication_staging_sha256:
value: ${{ jobs.verify.outputs.publication_staging_sha256 }}
draft_release_id:
value: ${{ jobs.verify.outputs.draft_release_id }}
expires_at:
value: ${{ jobs.verify.outputs.expires_at }}
registry_source_commit:
value: ${{ jobs.verify.outputs.registry_source_commit }}

permissions:
contents: read
attestations: read
id-token: write

jobs:
verify:
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
admission_object_sha256: ${{ steps.verify.outputs.admission_object_sha256 }}
release_sha256: ${{ steps.verify.outputs.release_sha256 }}
artifact_inventory_sha256: ${{ steps.verify.outputs.artifact_inventory_sha256 }}
publication_staging_json: ${{ steps.verify.outputs.publication_staging_json }}
publication_staging_sha256: ${{ steps.verify.outputs.publication_staging_sha256 }}
draft_release_id: ${{ steps.verify.outputs.draft_release_id }}
expires_at: ${{ steps.verify.outputs.expires_at }}
registry_source_commit: ${{ steps.verify.outputs.registry_source_commit }}
steps:
- name: Bind this reusable call to the requested central commit
shell: bash
env:
CENTRAL_VERIFIER_SHA: ${{ inputs.central_verifier_sha }}
run: |
set -euo pipefail
if [[ ! "$CENTRAL_VERIFIER_SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "central_verifier_sha must be one exact commit" >&2
exit 1
fi
audience="openadapt-production-release-verifier"
token_json="$(curl --fail --silent --show-error \
--header "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=${audience}")"
TOKEN_JSON="$token_json" AUDIENCE="$audience" python3 - <<'PY'
import base64
import json
import os

response = json.loads(os.environ["TOKEN_JSON"])
token = response.get("value")
if not isinstance(token, str) or token.count(".") != 2:
raise SystemExit("GitHub did not return one OIDC token")
payload = token.split(".")[1]
payload += "=" * (-len(payload) % 4)
claims = json.loads(base64.urlsafe_b64decode(payload))
commit = os.environ["CENTRAL_VERIFIER_SHA"]
expected = {
"aud": os.environ["AUDIENCE"],
"job_workflow_ref": (
"OpenAdaptAI/.github/.github/workflows/"
f"verify-production-release-admission.yml@{commit}"
),
"job_workflow_sha": commit,
"repository_owner_id": "132681217",
"runner_environment": "github-hosted",
}
for name, value in expected.items():
if claims.get(name) != value:
raise SystemExit(f"OIDC claim {name} differs from the central verifier contract")
PY

- name: Check out the exact central verifier
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: OpenAdaptAI/.github
ref: ${{ inputs.central_verifier_sha }}
path: central-trust
persist-credentials: false

- name: Download the exact candidate artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: ${{ inputs.candidate_artifact_name }}
path: candidate

- name: Install the pinned GitHub attestation verifier
shell: bash
run: |
set -euo pipefail
archive="$RUNNER_TEMP/gh_2.98.0_linux_amd64.tar.gz"
curl --fail --location --silent --show-error \
--output "$archive" \
https://github.com/cli/cli/releases/download/v2.98.0/gh_2.98.0_linux_amd64.tar.gz
echo "3b8ac6b30336802fc1a858d7c084e11cdf24ac1a761ca90b68022d7d729208de $archive" \
| sha256sum --check --strict
tar --extract --gzip --file "$archive" --directory "$RUNNER_TEMP"
echo "$RUNNER_TEMP/gh_2.98.0_linux_amd64/bin" >> "$GITHUB_PATH"

- name: Install the pinned message-signature verifier
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.0
with:
cosign-release: v3.1.3

- name: Bind Cosign and the Sigstore trust material
shell: bash
run: |
set -euo pipefail
cosign_path=$(command -v cosign)
test "$(wc -c < "$cosign_path" | tr -d ' ')" = '141178250'
echo "4629c757b7618056f8ddd7e2625ae9fdd94c0372a65049520bc7d9df9efc7f71 $cosign_path" \
| sha256sum --check --strict
trusted_root="$RUNNER_TEMP/sigstore-trusted-root.json"
signing_config="$RUNNER_TEMP/sigstore-signing-config.json"
curl --fail --location --silent --show-error \
--output "$trusted_root" \
https://raw.githubusercontent.com/sigstore/root-signing/c6f23ff62645fb0c46ebc7945675835b52f91aa8/targets/trusted_root.json
test "$(wc -c < "$trusted_root" | tr -d ' ')" = '6787'
echo "6494e21ea73fa7ee769f85f57d5a3e6a08725eae1e38c755fc3517c9e6bc0b66 $trusted_root" \
| sha256sum --check --strict
curl --fail --location --silent --show-error \
--output "$signing_config" \
https://raw.githubusercontent.com/sigstore/root-signing/c6f23ff62645fb0c46ebc7945675835b52f91aa8/targets/signing_config.json
test "$(wc -c < "$signing_config" | tr -d ' ')" = '219'
echo "d358c75d032833f4193500f5b01b5760409410558fac962c599439adbb268b0f $signing_config" \
| sha256sum --check --strict
echo "OPENADAPT_SIGSTORE_TRUSTED_ROOT=$trusted_root" >> "$GITHUB_ENV"

- name: Materialize closed verifier inputs
shell: bash
env:
ADMISSION_REFERENCE_JSON: ${{ inputs.admission_reference_json }}
ARTIFACT_INVENTORY_JSON: ${{ inputs.artifact_inventory_json }}
run: |
set -euo pipefail
printf '%s' "$ADMISSION_REFERENCE_JSON" > "$RUNNER_TEMP/admission-reference.json"
printf '%s' "$ARTIFACT_INVENTORY_JSON" > "$RUNNER_TEMP/artifact-inventory.json"

- name: Verify admission and exact candidate bytes
id: verify
shell: bash
env:
GH_TOKEN: ${{ github.token }}
EXPECTED_TARGET: ${{ inputs.expected_target }}
EXPECTED_REPOSITORY: ${{ inputs.expected_repository }}
EXPECTED_REPOSITORY_ID: ${{ inputs.expected_repository_id }}
EXPECTED_SOURCE_COMMIT: ${{ inputs.expected_source_commit }}
EXPECTED_VERSION: ${{ inputs.expected_version }}
EXPECTED_TAG: ${{ inputs.expected_tag }}
run: |
set -euo pipefail
python3 central-trust/scripts/verify_production_release_admission.py \
--admission-reference "$RUNNER_TEMP/admission-reference.json" \
--artifact-inventory "$RUNNER_TEMP/artifact-inventory.json" \
--artifact-root candidate \
--expected-target "$EXPECTED_TARGET" \
--expected-repository "$EXPECTED_REPOSITORY" \
--expected-repository-id "$EXPECTED_REPOSITORY_ID" \
--expected-source-commit "$EXPECTED_SOURCE_COMMIT" \
--expected-version "$EXPECTED_VERSION" \
--expected-tag "$EXPECTED_TAG" \
--github-output "$GITHUB_OUTPUT"
11 changes: 9 additions & 2 deletions evidence-registry.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
{
"$schema": "schemas/evidence-registry.schema.json",
"schema_version": "openadapt.production-evidence-registry/v1",
"head_entry_sha256": null,
"schema_version": "openadapt.production-evidence-registry/v2",
"repository": "OpenAdaptAI/.github",
"repository_id": "858454062",
"repository_owner_id": "132681217",
"revision": 1,
"previous_registry_head_sha256": null,
"registry_head_sha256": "sha256:3c88a4e945f1aa51ee080f05331c0d7a36b907ad288cf8a24b2fef08b1681a97",
"signer_registry": null,
"signer_registry_history": [],
"entries": []
}
Loading