Skip to content

Accept the .github publish-workflow signing identity before repointing publishers #242

Closed
1 of 1 issue completed

Description

@devantler

🤖 Generated by the Agentic Engineer

Evidence

The platform checks every tenant artifact's keyless signature against a certificate subject pinned to the publishing workflow in devantler-tech/actions: ^https://github\.com/devantler-tech/actions/\.github/workflows/<publish-workflow>\.yaml@[0-9a-f]{40}$, together with an approved-revisions list (platform scripts/tests/test-publish-workflow-signing-revisions.sh and test-publish-workflow-approved-revisions.sh). A signature's certificate names the reusable workflow that produced it. Once a publisher calls publish-app.yaml or publish-manifests.yaml from .github, its subject becomes https://github.com/devantler-tech/.github/.github/workflows/... and fails that check.

Publishers found on 2026-09-25: .github (its own deploy/ artifact), ascoachingogvaner, aws, data-product-controller, dotnet-template, platform-tenant-template, wedding-app. Several carry their own publish-pin-approved checks keyed on the old path.

Problem

Repointing any publisher first would stop the platform from accepting that tenant's next release, including the organization's own GitHub-as-code artifact.

Acceptance criteria

Blocks #239 for every publishing repository.

Activity

  1. added theissue type on Sep 25, 2026
  2. moved this to 📥 Backlog in 🌊 Project Boardon Sep 25, 2026
  3. devantler commented on Sep 25, 2026

    @devantler
    ContributorAuthor

    🤖 Generated with Claude Code in an interactive session with the maintainer.

    Where platform hard-codes the actions identity (static read of main @ 9eed5255, 2026-09-25)

    Live subjects, 7, each a single regex. Cosign and Kyverno take one identity entry, and guard-oci-repository-verify.sh and the approved-revisions lib both refuse a second one. So .github can only be added as an alternation inside the one regex, as devantler-tech/(actions|\.github)/…. The pin guard's exactly-one-@ rule rules out a whole-identity alternation.

    • Per consumer, narrowed to SHA sets: apps/{ascoachingogvaner,wedding-app,github-config}/oci-repository.yaml and providers/hetzner/apps/aws/oci-repository.yaml.
    • Generic @[0-9a-f]{40} form: verify-app-images.yaml, the tenant ResourceGraphDefinition and talos/cluster/verify-first-party-images.yaml.

    Scripts and Go, all single prefixes:

    • publish-workflow-approved-revisions.lib.sh (SUBJECT_PREFIX, plus a yq test()).
    • report-publish-workflow-signing-revisions.sh (SUBJECT_PATTERN, and the pin_at_ref uses: regex).
    • guard-shared-publish-workflow-pin.sh (SUBJECT_PATTERN, and EXPECTED_MIN_SUBJECTS=7).
    • generate-publish-workflow-approved-revisions.sh (a startswith filter, and the release-candidate lookup on devantler-tech/actions releases).
    • write-publish-workflow-matchers.sh, which rebuilds every subject from the one prefix. Left as is, it would move a .github-family consumer back to actions.
    • watch-actions-release-candidate.sh.
    • validate-eks-ci-role-policy/publish_matcher_projection.go, which projects to the actions pattern form and so feeds the EKS approval fingerprint.

    Approved-revisions TSV: it has no repository column, so every SHA is implicitly an actions SHA. Tenant repos that sign in their own CI read its header and columns, so adding a column is a cross-repository change.

    Proposed order

    1. Behaviour-preserving first step: every script, guard and Go projection accepts a second prefix family (devantler-tech/.github/.github/workflows/publish-*), and the writer keeps each consumer's recorded family. The 7 live subjects, the TSV and the release resolver stay unchanged, so nothing is accepted live yet. Add .github fixtures to each test suite: a SHA is accepted and the family is kept; @main, devantler-tech/github/ and devantler-tech/actionsx/ are refused.
    2. Record the repository per revision: either a TSV repository column, coordinated with the tenant guards, or the cross-product (actions|\.github)/…@(A|B) form. A consumer mid-switch needs actions@signer and .github@pin at once.
    3. Approve the first .github revision once feat!: import the devantler-tech/actions catalogue #243 has merged and released, then widen the live subjects consumer by consumer as Repoint the remaining consumers from actions to .github #239 repoints each one. The EKS fingerprint re-approval follows the Go projection change.
    4. github-config must call its own publish workflow by full SHA (devantler-tech/.github/.github/workflows/publish-manifests.yaml@<sha>), never through a local ./ call. A local call signs with a ref, which the pin guard refuses.
    5. Move the release-candidate source to .github releases.
  4. devantler commented on Sep 25, 2026

    @devantler
    ContributorAuthor

    🤖 Generated with Claude Code in an interactive session with the maintainer.

    Design correction: skip the repository column, alternate the repository with an exact SHA set

    Reading the approved-revisions lib closely, step 1 as proposed above would teach the guard to accept a .github-prefixed subject while the approved set still cannot say which repository each SHA belongs to. That widens what the guard admits without recording why, so it should not ship on its own.

    A simpler design removes the need for a repository column:

    • Each narrowed subject stays one regex, alternating only the repository: ^https://github\.com/devantler-tech/(actions|\.github)/\.github/workflows/publish-<x>\.yaml@(<sha>|<sha>)$.
    • The SHA set stays exact. A commit SHA cannot be produced in another repository on purpose (that would need a second preimage), and both repositories belong to this organization, so (actions|\.github) admits no signer the exact set did not already admit.
    • A consumer mid-switch, with its signer on actions@A and its pin on .github@B, is covered by the same subject with no special case.
    • The TSV keeps its eight columns, so the tenant repositories' own pin guards need only accept the .github path in their uses: lines, not a new column.

    What it changes:

    • The writer emits the alternated form for every consumer, and the lib, report and pin guard parse it.
    • The generator and report read pins and signers from either repository.
    • The release candidate and watcher move to .github once it is releasing.

    Watch-out: rewriting the live subjects moves the EKS approval fingerprint (the Go projection). That change therefore needs the fingerprint re-approval path, and must land as its own reviewed step, not mixed with anything else.

    I'm leaving the implementation for a dedicated session rather than squeezing it between review rounds.

  5. devantler commented on Sep 25, 2026

    @devantler
    ContributorAuthor

    🤖 Generated with Claude Code in an interactive session with the maintainer.

    Interim note (2026-09-25): Dependabot's #290 auto-merged tonight. It moves cd.yaml's pin on the actions copy of publish-manifests.yaml from be13607c (13.7.0) to df7fd4f (13.10.3). The platform's approved set for github-config currently allows only be13607c. A .github release cut before the platform's regeneration picks up the new pin would be signed by df7fd4f and refused by Flux: the tenant would stay on v2.0.0 until the approved set moves, then recover by itself.

    Regeneration runs several times a day and lands through a reviewed platform PR (#4206 is open now). Until it includes df7fd4f, avoid cutting a .github release. This is also one more reason to finish this issue: once cd.yaml calls this repository's own copy, Dependabot pin bumps here and the platform's approval move together by design.

  6. devantler commented on Oct 2, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Refreshed source evidence at Platform 2194d855bcf44029e398644049ec05f6a0c3dc55 and catalogue b6a20bd0b56e96ab941d4be6fe46987eb8dca8c0. Platform's approved-revision parser, generator and matching projection still accept the legacy publisher source. The catalogue CD caller also remains on that source.

    The maintainer's coordinated exact-revision design remains the implementation boundary: update all projections together, complete the approval-fingerprint path, and prove deployed acceptance before repointing publishers. A prefix-only change would not deliver that contract. Release-promotion PR #381 does not replace this migration gate.

    Blocker: coordinated source-identity support, deployed signature acceptance/readback and publisher repointing. This refresh reads source; it is not live Flux verification.

  7. devantler commented on Oct 4, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Verified publisher gate — 2026-10-04

    #235 is closed and no longer blocks this work. Actual catalogue CD succeeded at reviewed catalogue commit 0600006 while resolving legacy publish-manifests at 1d726483facb9340655489913f609082677be8f5. That is evidence for the existing publisher, not canonical publisher acceptance.

    At observed Platform commit 028747a284cdcc23f274e757accf7bdd754f4ea2, the catalogue artifact's subject policy, approved-revision parser and EKS projection still accept only the legacy publishing family. Source identities and blob hashes were verified and the observation was rebound at 11:17:32 UTC. This is declared-source evidence; deployed canonical verification is unproven.

    Implement the exact reviewed revisions for both families together across the policy generator, parser, writer, revision resolver, EKS projection and its fingerprint. Preserve the existing family while any publisher uses it. A prefix-only change cannot prove the approved-revision and deployed verifier contract. Require successful canonical artifact verification through the deployed consumer before catalogue CD or tenant publishers change. Blocker: coordinated dual-family implementation and actual deployed canonical acceptance. This continues to block #239 for publishing repositories.

  8. devantler commented on Oct 7, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    The source-policy blocker has advanced: Platform #4568 is merged in 6de1a8b9f3cac3c4b92c7abc14ac610c1bf7eb21. A bounded live read on 2026-10-07 verifies the catalogue consumer's deployed matcher admits exactly legacy publish-manifests 1d726483facb9340655489913f609082677be8f5 and canonical publish-manifests 2fa404276b0ce5c0527683b080e39045045c4e42, under the existing GitHub OIDC issuer. Generation and observed generation agree; Ready and SourceVerified are true. The canonical-approval regression matrix passes at the integrated source, including wrong input, withdrawal and invalid-record rejection.

    The currently selected public catalogue artifact is still produced by the legacy publisher. Its exact digest verifies under the deployed two-family matcher and the exact legacy identity; testing the exact canonical identity against the same artifact is rejected with a certificate identity mismatch. This proves retained legacy acceptance and deployed configuration, not native canonical publication or consumer acceptance of a canonically produced artifact.

    The remaining outcome gate is an actual canonical publication at the reviewed revision followed by deployed consumer verification/readback, before publisher caller migration or compatibility retirement. This issue, #239, #284 and #371 remain open. No publisher was repointed and no verification requirement was relaxed by this read-only audit.

  9. self-assigned this
    on Oct 8, 2026
  10. added a commit that references this issue on Oct 8, 2026
  11. devantler commented on Oct 8, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Delivered through #541.

    Production proof

    • Squash merge: fca583ac795d56928c5b542944b6e0963ea1e270
    • Release: v7.8.5
    • Canonical publication: CD run 37762433044, successful at the release commit
    • Signed artifact: ghcr.io/devantler-tech/github-config/manifests@sha256:6fbe25f41907416e676c07fdea2b8dbae759cc97ab6da0958bc21bdb95a30214
    • Independent Cosign verification passed against the exact canonical signing identity devantler-tech/.github/.github/workflows/publish-manifests.yaml@2fa404276b0ce5c0527683b080e39045045c4e42, including transparency-log and trusted-certificate validation
    • Live source: github-config/github-config reports 7.8.5@sha256:6fbe25f…, Ready=True, SourceVerified=True
    • Live consumer: github-config/github-config Kustomization has applied the same revision and reports Ready=True

    The canonical publisher identity is therefore accepted by the deployed verifier and the released configuration is live. Closing as completed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Fields

Priority

None yet

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions