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
10 changes: 5 additions & 5 deletions .gds/bundle.lock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ schema_version: 1
bundle:
version: "0.9.7-dev"
release_sequence: 0
source_tree_digest: "sha256:864917988777e8b9f61f87f08e4f08d94173e37c1352373332b5e02e76562f97"
digest: "sha256:eb2048f08b6e1dbc84d89792a6e3f658f6258ff7c7e3b11c87fd5c2fbd48b0de"
source_tree_digest: "sha256:624cc8e3d9318169eb170ba7cb677b06e11a7ff68bef59b86bb7318bd8652ce8"
digest: "sha256:d5b8988c4c228533eea9f8c22b5e080088494d380ff01688f999d285236c9360"

projection:
input_digest: "sha256:f2fb932fd0598eaf673a8cd2b3232f73f7c5b3cfad377fb807dda30ec134e977"
output_digest: "sha256:0caadd7bca7495045f29598f2908a15bab55a2c421b10202095ff99e3c833d69"
input_digest: "sha256:55d339ca3fd58f5eba0795c38f68a878fe03c4e4d6b0f149d3c273dea56c5b8b"
output_digest: "sha256:daf1d7ea532a9ed356eeb053f73bc08cb5b3fe4e863ead0f440b4492c2f06ba4"
files:
- path: ".gds/compiled-policy.json"
digest: "sha256:9f498788bdc34e52a0ab793c536e0e6a7b360c2e1a20446cbf03ed51986cdc6f"
- path: ".github/workflows/gds-ci.yml"
digest: "sha256:275145c60b6f99f946085685a66b7eabb4a693043588cc55a87250401af28612"
digest: "sha256:18dfb90210b2fb933ae5141aabfecee9ec074fdb74d412aea5a8234dd406cdc7"
4 changes: 2 additions & 2 deletions .github/workflows/gds-ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# GENERATED FILE - DO NOT EDIT DIRECTLY
# generator: gds
# bundle: 0.9.7-dev
# source-tree-digest: sha256:864917988777e8b9f61f87f08e4f08d94173e37c1352373332b5e02e76562f97
# input-digest: sha256:f2fb932fd0598eaf673a8cd2b3232f73f7c5b3cfad377fb807dda30ec134e977
# source-tree-digest: sha256:624cc8e3d9318169eb170ba7cb677b06e11a7ff68bef59b86bb7318bd8652ce8
# input-digest: sha256:55d339ca3fd58f5eba0795c38f68a878fe03c4e4d6b0f149d3c273dea56c5b8b
# output-digest: sha256:b9bf3d0c64c0fb371596e7d090e82e62aebbfde91929115fc15fb28644e4fd38
# edit-source:
# - .gds/repository.yaml
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/release-bundle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ jobs:
--sequence "$RELEASE_SEQUENCE" \
--minimum-cli-version "$MINIMUM_CLI_VERSION" \
--source-ref "refs/tags/$RELEASE_TAG" \
--trigger-ref "$GITHUB_REF" \
--go-binary "$GO_BINARY" \
> "$RUNNER_TEMP/gds-release-result.json"
go run ./core/cmd/gds-release-builder \
Expand Down Expand Up @@ -395,10 +396,11 @@ jobs:
--arg version "$RELEASE_VERSION" \
--argjson sequence "$RELEASE_SEQUENCE" \
--arg commit "$GITHUB_SHA" \
--arg ref "$GITHUB_REF" \
--arg ref "refs/tags/$RELEASE_TAG" \
--arg trigger "$GITHUB_REF" \
--argjson run_id "$GITHUB_RUN_ID" \
--slurpfile failed "$failed_jobs" \
'{schema_version:1,status:"failed",bundle_version:$version,release_sequence:$sequence,source_commit:$commit,source_ref:$ref,workflow_run_id:$run_id,failed_jobs:$failed[0],superseded_by:null}' \
'{schema_version:1,status:"failed",bundle_version:$version,release_sequence:$sequence,source_commit:$commit,source_ref:$ref,trigger_ref:$trigger,workflow_run_id:$run_id,failed_jobs:$failed[0],superseded_by:null}' \
> "$envelope"
if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release upload "$RELEASE_TAG" "$envelope" --repo "$GITHUB_REPOSITORY"
Expand Down
14 changes: 12 additions & 2 deletions core/bundle/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,24 @@ func Build(
if err != nil {
return Candidate{}, []domain.Finding{bundleFinding("GDS_BUNDLE_ARCHIVE_FAILED", err)}
}
// The attestation binds the ref the release run was triggered on, not the
// release boundary tag the build checked out. When the resolve step creates
// the tag inside the same run those differ, and the identity digest must
// cover the ref the signer will actually record.
attestationRef := options.SourceRef
if options.TriggerRef != "" {
attestationRef = options.TriggerRef
}
identityDigest := digestJSON(map[string]any{
"owner": trust.Source.Owner, "repository": trust.Source.Repository,
"workflow": options.Workflow, "ref": options.SourceRef,
"workflow": options.Workflow, "ref": attestationRef,
"source_commit": options.SourceCommit,
})
envelope := ReleaseEnvelope{
SchemaVersion: domain.SchemaVersion, BundleVersion: options.BundleVersion,
ReleaseSequence: options.ReleaseSequence,
SourceCommit: options.SourceCommit, SourceRef: options.SourceRef,
TriggerRef: options.TriggerRef,
ExecutableFiles: executableFiles,
ManifestDigest: digest(manifestBytes), ArtifactDigest: digest(artifact),
ExpectedAttestationIdentityDigest: identityDigest,
Expand Down Expand Up @@ -325,7 +334,8 @@ func writeArchive(files []sourceFile, manifest []byte) ([]byte, error) {
func validateBuildOptions(options BuildOptions, trust TrustPolicy) *domain.Finding {
if options.ReleaseSequence < trust.Release.MinimumReleaseSequence ||
!contains(trust.Source.AllowedWorkflows, options.Workflow) ||
!allowedRef(trust.Source.AllowedRefs, options.SourceRef) {
!allowedRef(trust.Source.AllowedRefs, options.SourceRef) ||
(options.TriggerRef != "" && !allowedRef(trust.Source.AllowedRefs, options.TriggerRef)) {
finding := bundleFinding(
"GDS_BUNDLE_BUILD_POLICY_BLOCKED",
fmt.Errorf("release sequence, workflow, or ref is outside trust policy"),
Expand Down
17 changes: 11 additions & 6 deletions core/bundle/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,16 @@ type SupplyChain struct {
}

type ReleaseEnvelope struct {
SchemaVersion int `json:"schema_version"`
BundleVersion string `json:"bundle_version"`
ReleaseSequence int `json:"release_sequence"`
Channel string `json:"channel,omitempty"`
SourceCommit string `json:"source_commit"`
SourceRef string `json:"source_ref"`
SchemaVersion int `json:"schema_version"`
BundleVersion string `json:"bundle_version"`
ReleaseSequence int `json:"release_sequence"`
Channel string `json:"channel,omitempty"`
SourceCommit string `json:"source_commit"`
SourceRef string `json:"source_ref"`
// TriggerRef records the ref the release run was triggered on, which is
// the ref the provenance attestation cryptographically binds. It differs
// from SourceRef whenever the release tag is created inside the same run.
TriggerRef string `json:"trigger_ref,omitempty"`
ExecutableFiles int `json:"executable_files"`
ManifestDigest string `json:"manifest_digest"`
ArtifactDigest string `json:"artifact_digest"`
Expand All @@ -103,6 +107,7 @@ type BuildOptions struct {
MinimumCLIVersion string
Workflow string
SourceRef string
TriggerRef string
TrackedSources []string
AdditionalFiles []AdditionalFile
}
Expand Down
9 changes: 8 additions & 1 deletion core/bundle/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,17 @@ func Verify(
"GDS_BUNDLE_ATTESTATION_INVALID", "Artifact provenance or digest was not verified.",
))
}
// The attestation binds the ref the run was triggered on; when the release
// tag is created inside that same run, TriggerRef is the only ref the
// signer could have recorded.
expectedRef := envelope.SourceRef
if envelope.TriggerRef != "" {
expectedRef = envelope.TriggerRef
}
if evidence.SourceOwner != trust.Source.Owner ||
evidence.SourceRepository != trust.Source.Repository ||
!contains(trust.Source.AllowedWorkflows, evidence.Workflow) ||
!allowedRef(trust.Source.AllowedRefs, evidence.SourceRef) || evidence.SourceRef != envelope.SourceRef ||
!allowedRef(trust.Source.AllowedRefs, evidence.SourceRef) || evidence.SourceRef != expectedRef ||
evidence.SourceCommit != envelope.SourceCommit {
findings = append(findings, verificationFinding(
"GDS_BUNDLE_ATTESTATION_IDENTITY_MISMATCH",
Expand Down
15 changes: 1 addition & 14 deletions core/cmd/gds-release-builder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,40 +30,27 @@ func run(ctx context.Context, arguments []string, stdout, stderr io.Writer) int
verifyDirectory := ""
verifyTrustedRoot := ""
trustPolicy := ""
extractEvidenceArchive := ""
extractEvidenceDestination := ""
flags.StringVar(&request.Root, "root", ".", "exact clean GDS Git worktree root")
flags.StringVar(&request.OutputDirectory, "output", "", "new release output directory")
flags.StringVar(&request.Version, "version", "", "release SemVer without a v prefix")
flags.IntVar(&request.ReleaseSequence, "sequence", 0, "monotonic release sequence")
flags.StringVar(&request.MinimumCLIVersion, "minimum-cli-version", "", "minimum compatible CLI SemVer")
flags.StringVar(&request.SourceRef, "source-ref", "", "exact refs/heads/* or refs/tags/* source ref")
flags.StringVar(&request.TriggerRef, "trigger-ref", "", "ref the release run was triggered on; the attestation binds it")
flags.StringVar(
&request.GoBinary, "go-binary", "",
"absolute Go executable (defaults to the running toolchain GOROOT when available)",
)
flags.StringVar(&verifyDirectory, "verify-directory", "", "verify an existing release output directory")
flags.StringVar(&verifyTrustedRoot, "verify-trusted-root", "", "verify one offline trusted-root.jsonl")
flags.StringVar(&trustPolicy, "trust-policy", "", "independent local consumer trust policy")
flags.StringVar(&extractEvidenceArchive, "extract-harness-evidence-archive", "", "bounded harness evidence tar.gz")
flags.StringVar(&extractEvidenceDestination, "extract-harness-evidence-destination", "", "new private evidence directory")
if err := flags.Parse(arguments); err != nil || flags.NArg() != 0 {
return writeFailure(stderr, "GDS_RELEASE_ARGUMENTS_INVALID", "Release builder arguments are invalid.", 4)
}
schemas, err := validation.NewSchemaSet()
if err != nil {
return writeFailure(stderr, "GDS_RELEASE_SCHEMAS_UNAVAILABLE", "Embedded schemas are unavailable.", 14)
}
if extractEvidenceArchive != "" || extractEvidenceDestination != "" {
if extractEvidenceArchive == "" || extractEvidenceDestination == "" || flags.NArg() != 0 ||
request.OutputDirectory != "" || verifyDirectory != "" || verifyTrustedRoot != "" || trustPolicy != "" {
return writeFailure(stderr, "GDS_RELEASE_ARGUMENTS_CONFLICT", "Evidence extraction requires exactly one archive and destination.", 4)
}
if err := releasebuilder.MaterializeHarnessEvidenceArchive(extractEvidenceArchive, extractEvidenceDestination); err != nil {
return writeFailureDetail(stderr, "GDS_HARNESS_EVIDENCE_ARCHIVE_INVALID", "Harness evidence archive was rejected.", err, 2)
}
return writeResult(stdout, map[string]any{"status": "materialized", "directory": extractEvidenceDestination})
}
if verifyDirectory != "" {
if request.OutputDirectory != "" || request.Version != "" || request.ReleaseSequence != 0 ||
verifyTrustedRoot != "" || trustPolicy != "" {
Expand Down
1 change: 1 addition & 0 deletions core/releasebuilder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ func Build(ctx context.Context, request Request, schemas *validation.Set) (resul
SourceCommit: source.Commit,
MinimumCLIVersion: request.MinimumCLIVersion,
Workflow: trust.Source.AllowedWorkflows[0], SourceRef: source.Ref,
TriggerRef: request.TriggerRef,
TrackedSources: trackedSources, AdditionalFiles: additional,
}
first, findings := bundle.Build(root, options, trust, schemas)
Expand Down
Loading
Loading