/** * `sign` capability — keyless cosign signing + SLSA provenance attestation * (#622, epic #551 follow-up to #610's referrer-attach path and #614's * reproducibility/provenance material). The companion gate is * `./verify.ts`'s `verify` capability. * * **Beginner footgun #1 neutralized: sign the digest, never a tag.** `input` * requires an already-resolved `sha256:` image reference (typically wired * from a prior publish step, `"@Publish.uri"`, which is itself * `repo@sha256:...` — see ./publish.ts) — never a bare tag. A tag is mutable; * signing one says nothing about which bytes were actually signed by the * time someone pulls it. `assertDigestRef` below throws before ever shelling * out if a caller passes a tag-only reference. * * **Beginner footgun #2 neutralized: keyless by default.** Sign via cosign's * keyless flow (a short-lived Fulcio cert bound to an OIDC identity, logged * to the public Rekor transparency log) — `cosign sign --yes ` with no * `--key`. There is no private key file for a first-timer to generate, lose, * leak, or forget to rotate. Key-based signing (`input.key`) is an opt-in * override for teams with an existing KMS/file-based key policy, never the * default — see `buildSignArgs`/`buildAttestArgs` below: `--key` is added * only when `input.key` is explicitly supplied, and it is the *only* thing * that changes about the invocation (still by digest, still attached the * same way). * * **SLSA provenance.** `attestProvenance` builds an in-toto * `https://slsa.dev/provenance/v1` statement from the same * `ProvenanceLink`/`ArtifactReproducibility` material #614's * ./reproducibility.ts already records per build-archive entry (source ref, * artifact digest) plus a builder id and timestamp, then signs+attaches it * via `cosign attest` (keyless by the same default). This reuses #614's * provenance model rather than inventing a second one — the in-toto * statement's `predicate.buildDefinition.resolvedDependencies` / * `.internalParameters` are populated from the same source-ref/digest link a * build archive entry already carries. * * Both `sign` and `attestProvenance` shell out to `cosign` through the * injectable `ProcessRunner` (./process-runner.ts) — mirroring * ./tool-sbom-generator.ts and ./publish.ts's referrer-attach step exactly: * `requireTool` throws `ToolNotAvailableError` with an actionable message if * `cosign` is absent, and no test here ever spawns a real process or talks * to Rekor/Fulcio. */ import type { Capability } from "../capability.js"; import { type ProcessRunner } from "./process-runner.js"; import type { ProvenanceLink } from "./reproducibility.js"; /** * Thrown when a `sign`/`attest` input's image reference has no `@sha256:...` * digest — refusing to sign a mutable tag (beginner footgun #1) rather than * silently signing whatever the tag happens to resolve to right now. */ export declare class SignTargetNotDigestError extends Error { readonly ref: string; readonly verb: "sign" | "attest"; constructor(ref: string, verb: "sign" | "attest"); } /** * cosign keyless signing config. Keyless is the default story this whole * module encodes — every field here is optional because `cosign sign --yes` * with no flags at all already does the right thing in CI (ambient OIDC from * the CI provider's own token, e.g. GitHub Actions' `id-token: write`). * `identityToken`/`oidcIssuer`/`oidcClientId` are the explicit overrides for * environments without ambient OIDC detection. */ export interface KeylessSigningConfig { /** Explicit OIDC identity token, when the environment has no ambient CI OIDC cosign can auto-detect. */ identityToken?: string; /** OIDC issuer URL override (`cosign sign --oidc-issuer`). */ oidcIssuer?: string; /** OIDC client id override (`cosign sign --oidc-client-id`). */ oidcClientId?: string; /** Fulcio URL override, for a private Sigstore instance. */ fulcioUrl?: string; /** Rekor URL override, for a private transparency log. */ rekorUrl?: string; } /** * Key-based signing override config — opt-in, never the default (see this * module's doc comment). Supplying `input.key` on `sign`/`attestProvenance` * switches the invocation to `cosign sign --key ` instead of the keyless * flow; every other part of the invocation (digest-only target, referrer * attach) is unchanged. */ export interface KeyBasedSigningConfig { /** Key reference cosign accepts: a local path, `kms://...`, `k8s://...`, `azurekms://...`, `awskms://...`, etc. An encrypted key file's password is not accepted here — `ProcessRunner.run` (./process-runner.ts) has no env-var passthrough today, and interpolating a secret into the shell command string would leak it into process listings/logs; supply an unencrypted key reference (e.g. a KMS URI) or export `COSIGN_PASSWORD` in the calling process's own environment instead. */ key: string; } export interface SignInput { /** Digest-qualified image reference to sign, e.g. `"123.dkr.ecr.us-east-1.amazonaws.com/search@sha256:abc..."` — typically wired from a prior publish step's `"@Publish.uri"`. Never a bare tag; see `SignTargetNotDigestError`. */ imageRef: string; /** Keyless signing config (OIDC identity + Rekor/Fulcio overrides). Default: cosign's own ambient keyless detection, no explicit config needed. Ignored when `key` is supplied. */ keyless?: KeylessSigningConfig; /** Opt-in key-based override — supply to sign with a private/KMS key instead of the keyless default. Not the default; see this module's doc comment. */ key?: KeyBasedSigningConfig; /** Annotations to attach to the signature (`cosign sign -a k=v`), e.g. build metadata. */ annotations?: Record; } export interface SignOutput { /** The digest-qualified reference that was signed — echoed back for downstream wiring (e.g. into `attestProvenance`/`verify`). */ imageRef: string; /** True once `cosign sign` completed without error. */ signed: true; /** `"keyless"` or `"key"`, reflecting which flow actually ran. */ method: "keyless" | "key"; } /** Build the `cosign sign` argv (as a shell command string) for `input` — factored out so tests can assert on the exact invocation without needing a full mock run. */ export declare function buildSignArgs(input: SignInput): string; /** * Sign an artifact by digest via keyless cosign (default) or an opt-in * key-based override, and attach the signature as an OCI referrer on that * digest — `cosign sign` itself performs the attach (cosign's signatures are * always stored as registry referrers/the legacy tag convention; no separate * `oras attach` step is needed the way SBOM/BOM referrer attach in * ./publish.ts requires one, since cosign natively pushes to the registry). * Refuses to run at all against a non-digest reference (`SignTargetNotDigestError`) * — sign is never invoked for a tag, by construction, before any process is spawned. * * No rollback: an already-signed, content-addressed image's signature is * immutable evidence sitting in the registry — nothing to compensate, the * same opt-out `publish-image`/`generate-sbom` already take for * no-mutable-remote-state operations. */ export declare function createSignCapability(processRunner?: ProcessRunner): Capability; /** Default `sign` capability, backed by the real `ProcessRunner`. */ export declare const signCapability: Capability; /** Minimal in-toto `https://slsa.dev/provenance/v1` predicate — the fields this module can honestly populate from #614's `ProvenanceLink` material, not a full SLSA Level 3+ builder identity/hermeticity claim. */ export interface SlsaProvenancePredicate { buildDefinition: { buildType: string; externalParameters: Record; internalParameters?: Record; resolvedDependencies?: Array<{ uri: string; digest?: Record; }>; }; runDetails: { builder: { id: string; }; metadata?: { invocationId?: string; startedOn?: string; finishedOn?: string; }; }; } /** The full in-toto statement wrapping the SLSA predicate — what `cosign attest` signs+attaches. */ export interface InTotoProvenanceStatement { _type: "https://in-toto.io/Statement/v1"; predicateType: "https://slsa.dev/provenance/v1"; subject: Array<{ name: string; digest: { sha256: string; }; }>; predicate: SlsaProvenancePredicate; } export interface BuildProvenanceStatementInput { /** The digest-qualified artifact reference this provenance describes (subject). */ imageRef: string; /** Source -> output link, reused from #614 (./reproducibility.ts) rather than re-deriving a second source-of-truth for "what commit produced this." */ provenance: ProvenanceLink; /** Builder identity URI, e.g. a CI run URL or `"https://github.com/actions/runner"`. */ builderId: string; /** Build type identifier (an arbitrary URI naming the recipe kind, e.g. `"https://chant.dev/build-archive/v1"`). Defaults to a chant-generic build type. */ buildType?: string; /** ISO-8601 timestamp of the build's completion. Defaults to `new Date().toISOString()`. */ finishedOn?: string; /** CI invocation/run id, when available (e.g. a GitHub Actions run id). */ invocationId?: string; } /** * Build an in-toto SLSA provenance statement for `input.imageRef`, reusing * #614's `ProvenanceLink` (source ref -> artifact digest) as the statement's * `resolvedDependencies`/`internalParameters` material — the honest subset * chant can actually attest to (which source commit, which builder, when), * matching ./reproducibility.ts's "honesty over convenience" stance rather * than fabricating a stronger provenance claim than the build system backs. */ export declare function buildProvenanceStatement(input: BuildProvenanceStatementInput): InTotoProvenanceStatement; export interface AttestProvenanceInput extends BuildProvenanceStatementInput { /** Keyless signing config, same shape/default as `SignInput.keyless`. */ keyless?: KeylessSigningConfig; /** Opt-in key-based override, same shape/default as `SignInput.key`. */ key?: KeyBasedSigningConfig; } export interface AttestProvenanceOutput { /** The digest-qualified reference the provenance was attested for. */ imageRef: string; /** The in-toto statement that was signed+attached (returned for inspection/logging/tests). */ statement: InTotoProvenanceStatement; /** True once `cosign attest` completed without error. */ attested: true; /** `"keyless"` or `"key"`, reflecting which flow actually ran. */ method: "keyless" | "key"; } /** * Build the `cosign attest` argv (as a shell command string) for a * predicate-type/payload pair. Factored out (like `buildSignArgs`) so tests * can assert on the exact invocation. `--predicate` reads from a scratch * file path (`cosign attest` does not accept the payload on stdin any more * reliably than `oras attach` does — see ./publish.ts's `attachOneReferrer` * for the same scratch-file convention) — the caller passes the path already * written. */ export declare function buildAttestArgs(input: AttestProvenanceInput, predicatePath: string): string; /** * Build an in-toto SLSA provenance statement for a digest-qualified artifact * (reusing #614's `ProvenanceLink` material) and sign+attach it via keyless * `cosign attest` (default) or an opt-in key-based override — attached as a * referrer alongside the signature (`sign`) and SBOM (`generate-sbom`) on * the same digest. Refuses a non-digest `imageRef` the same way `sign` does. * * No rollback: same reasoning as `sign` — an attached, content-addressed * attestation is immutable evidence, nothing to compensate. */ export declare function createAttestProvenanceCapability(processRunner?: ProcessRunner): Capability; /** Default `attest-provenance` capability, backed by the real `ProcessRunner`. */ export declare const attestProvenanceCapability: Capability; //# sourceMappingURL=sign.d.ts.map