diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a8e181c..512de33 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -773,14 +773,6 @@ jobs: envsubst '$REPO_NAME' < templates/.Dockerfile-lambda-template.tmpl | tee "${GENERATED_DIR}/Dockerfile.lambda" envsubst < templates/.goreleaser-docker-lambda-template.yaml.tmpl | tee "${GENERATED_DIR}/.goreleaser.lambda.yaml" - - name: Docker Login - if: inputs.docker == true - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.RELENG_GITHUB_TOKEN }} - - name: Set up QEMU if: inputs.docker == true || inputs.lambda == true uses: docker/setup-qemu-action@v3 @@ -1267,7 +1259,6 @@ jobs: permissions: id-token: write # Required for cosign verification contents: read - packages: read # Required for GHCR image verification runs-on: ubuntu-latest steps: - name: Checkout shared workflows @@ -1280,14 +1271,6 @@ jobs: - name: Install cosign uses: sigstore/cosign-installer@v3 - - name: Docker Login - if: inputs.docker == true - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.RELENG_GITHUB_TOKEN }} - - name: Validate release artifacts working-directory: _workflows env: diff --git a/cmd/extract-images/main.go b/cmd/extract-images/main.go index 12c7b0e..23705e6 100644 --- a/cmd/extract-images/main.go +++ b/cmd/extract-images/main.go @@ -31,7 +31,7 @@ func main() { flag.StringVar(&lambdaDigestFile, "lambda-digest-file", "", "Path to Lambda digest file (if not provided, will be constructed from repo-name, tag, and lambda-asset-dir)") flag.StringVar(&repoName, "repo-name", "", "Repository name") flag.StringVar(&tag, "tag", "", "Release tag (e.g., v0.1.65 or 0.1.65)") - flag.BoolVar(&includePublic, "include-public", true, "Extract GHCR and ECR public image metadata") + flag.BoolVar(&includePublic, "include-public", true, "Extract ECR public image metadata") flag.BoolVar(&includeLambda, "include-lambda", false, "Extract private Lambda image metadata") flag.Parse() @@ -65,18 +65,12 @@ func main() { os.Exit(1) } - foundGHCR, foundECR := extractPublicImages(content, version, images) - if !foundGHCR && !foundECR { - fmt.Fprintf(os.Stderr, "extract-images: ::error::Could not find GHCR or ECR public index image in %s\n", digestFile) + foundECR := extractPublicImages(content, version, images) + if !foundECR { + fmt.Fprintf(os.Stderr, "extract-images: ::error::Could not find ECR public index image in %s\n", digestFile) fmt.Fprintf(os.Stderr, "extract-images: Contents of digest file:\n%s\n", content) os.Exit(1) } - if !foundGHCR { - fmt.Fprintf(os.Stderr, "extract-images: ::warning::Could not find GHCR index image in %s\n", digestFile) - } - if !foundECR { - fmt.Fprintf(os.Stderr, "extract-images: ::warning::Could not find ECR public index image in %s\n", digestFile) - } } if includeLambda { @@ -118,8 +112,8 @@ func digestPath(assetDir, repoName, version string) string { return fmt.Sprintf("%s/%s_%s_digests.txt", assetDir, repoName, version) } -func extractPublicImages(content []byte, version string, images map[string]*pb.Image) (bool, bool) { - var foundGHCR, foundECR bool +func extractPublicImages(content []byte, version string, images map[string]*pb.Image) bool { + var foundECR bool for _, line := range parseDigestLines(content) { // Only capture the multi-arch index images (tagged with just version, not version-arch) if !strings.HasSuffix(line.ref, fmt.Sprintf(":%s", version)) { @@ -131,17 +125,7 @@ func extractPublicImages(content []byte, version string, images map[string]*pb.I continue } - if strings.HasPrefix(line.ref, "ghcr.io/conductorone/") { - isIndex := true - images["ghcr"] = pb.Image_builder{ - Ref: &line.ref, - Digest: &line.digest, - Tag: &version, - Uri: &uri, - IsIndex: &isIndex, - }.Build() - foundGHCR = true - } else if strings.HasPrefix(line.ref, "public.ecr.aws/conductorone/") { + if strings.HasPrefix(line.ref, "public.ecr.aws/conductorone/") { isIndex := true images["ecrPublic"] = pb.Image_builder{ Ref: &line.ref, @@ -154,7 +138,7 @@ func extractPublicImages(content []byte, version string, images map[string]*pb.I } } - return foundGHCR, foundECR + return foundECR } func extractLambdaImage(content []byte, repoName, version string, images map[string]*pb.Image) bool { diff --git a/cmd/extract-images/main_test.go b/cmd/extract-images/main_test.go index 8287682..b757a55 100644 --- a/cmd/extract-images/main_test.go +++ b/cmd/extract-images/main_test.go @@ -9,20 +9,12 @@ import ( func TestExtractPublicImages(t *testing.T) { images := make(map[string]*pb.Image) - foundGHCR, foundECR := extractPublicImages([]byte(` -aaa111 ghcr.io/conductorone/baton-example:0.1.2 -bbb222 ghcr.io/conductorone/baton-example:0.1.2-amd64 + foundECR := extractPublicImages([]byte(` ccc333 public.ecr.aws/conductorone/baton-example:0.1.2 `), "0.1.2", images) - if !foundGHCR || !foundECR { - t.Fatalf("foundGHCR=%t foundECR=%t, want both true", foundGHCR, foundECR) - } - if images["ghcr"].GetDigest() != "sha256:aaa111" { - t.Fatalf("ghcr digest = %q", images["ghcr"].GetDigest()) - } - if !images["ghcr"].GetIsIndex() { - t.Fatal("ghcr image should be marked as an index") + if !foundECR { + t.Fatal("ECR public image was not found") } if images["ecrPublic"].GetUri() != "public.ecr.aws/conductorone/baton-example@sha256:ccc333" { t.Fatalf("ecrPublic uri = %q", images["ecrPublic"].GetUri()) @@ -61,9 +53,9 @@ func TestMarshalImagesSortsKeys(t *testing.T) { Digest: strPtr("sha256:lambda"), IsIndex: boolPtr(false), }.Build(), - "ghcr": pb.Image_builder{ - Ref: strPtr("ghcr.io/conductorone/baton-example:0.1.2"), - Digest: strPtr("sha256:ghcr"), + "ecrPublic": pb.Image_builder{ + Ref: strPtr("public.ecr.aws/conductorone/baton-example:0.1.2"), + Digest: strPtr("sha256:ecr"), IsIndex: &isIndex, }.Build(), } @@ -80,8 +72,8 @@ func TestMarshalImagesSortsKeys(t *testing.T) { if len(decoded) != 2 { t.Fatalf("decoded keys = %d, want 2", len(decoded)) } - if got[4:10] != `"ghcr"` { - t.Fatalf("first key was not ghcr in sorted output:\n%s", got) + if got[4:15] != `"ecrPublic"` { + t.Fatalf("first key was not ecrPublic in sorted output:\n%s", got) } } diff --git a/cmd/record-release/main_test.go b/cmd/record-release/main_test.go index a82d703..7f5027b 100644 --- a/cmd/record-release/main_test.go +++ b/cmd/record-release/main_test.go @@ -67,11 +67,6 @@ func TestTransformImagesAppliesManifestImageAttestation(t *testing.T) { Digest: strPtr("sha256:ecr"), IsIndex: &isIndex, }.Build(), - "ghcr": pb.Image_builder{ - Ref: strPtr("ghcr.io/example/baton-example:v1.2.3"), - Digest: strPtr("sha256:ghcr"), - IsIndex: &isIndex, - }.Build(), }, }.Build() @@ -117,8 +112,8 @@ func TestRecordReleaseRequestMarshalsAttestations(t *testing.T) { }, }, Images: map[string]*ReleaseImage{ - "ghcr": { - Platform: "ghcr", + "ecrPublic": { + Platform: "ecrPublic", Attestations: []*ReleaseAttestation{{Type: slsaProvenance}}, }, }, @@ -147,11 +142,11 @@ func TestRecordReleaseRequestMarshalsAttestations(t *testing.T) { if got.Assets["linux-amd64"].Attestations[0].URL == "" { t.Fatal("asset attestation URL was not marshaled") } - if len(got.Images["ghcr"].Attestations) != 1 { - t.Fatalf("image attestations = %#v, want one entry", got.Images["ghcr"].Attestations) + if len(got.Images["ecrPublic"].Attestations) != 1 { + t.Fatalf("image attestations = %#v, want one entry", got.Images["ecrPublic"].Attestations) } - if got.Images["ghcr"].Attestations[0].URL != "" { - t.Fatalf("image attestation URL = %q, want empty", got.Images["ghcr"].Attestations[0].URL) + if got.Images["ecrPublic"].Attestations[0].URL != "" { + t.Fatalf("image attestation URL = %q, want empty", got.Images["ecrPublic"].Attestations[0].URL) } } diff --git a/docs/diagrams/release-workflow.dot b/docs/diagrams/release-workflow.dot index 30c3d07..1433f22 100644 --- a/docs/diagrams/release-workflow.dot +++ b/docs/diagrams/release-workflow.dot @@ -21,7 +21,7 @@ digraph ReleaseWorkflow { windows [label="goreleaser-windows\n• Windows zip + MSI\n• WiX Toolset\n• SBOMs, provenance\n• upload to S3", fillcolor="#ecfeff"]; - docker [label="goreleaser-docker\n• multi-arch OCI images\n• Lambda image (arm64)\n• GHCR push\n• ECR Public push\n• image attestations", fillcolor="#ecfeff"]; + docker [label="goreleaser-docker\n• multi-arch OCI images\n• Lambda image (arm64)\n• ECR Public push\n• image attestations", fillcolor="#ecfeff"]; record [label="publish-release-manifest\n• merge manifests\n• sign manifest\n• upload manifest/checksums", fillcolor="#ecfeff"]; @@ -32,7 +32,6 @@ digraph ReleaseWorkflow { // Outputs s3 [label="S3\n• archives\n• attestations\n• manifest", fillcolor="#fef9c3"]; - ghcr [label="GHCR\n• images\n• OCI attestations", fillcolor="#f9fafb"]; ecr [label="ECR Public\n• OCI images\n• Lambda image", fillcolor="#fef9c3"]; registry [label="Connector Registry API\n• release metadata\n• assets + images", fillcolor="#dcfce7"]; @@ -48,7 +47,6 @@ digraph ReleaseWorkflow { docker -> record; binaries -> s3 [label="artifacts"]; windows -> s3 [label="artifacts"]; - docker -> ghcr [label="push"]; docker -> ecr [label="push"]; record -> s3 [label="manifest"]; record -> registry_api; diff --git a/docs/diagrams/release-workflow.png b/docs/diagrams/release-workflow.png index 000961d..3f2b98e 100644 Binary files a/docs/diagrams/release-workflow.png and b/docs/diagrams/release-workflow.png differ diff --git a/docs/release-workflow.md b/docs/release-workflow.md index 1be0a33..e7c97be 100644 --- a/docs/release-workflow.md +++ b/docs/release-workflow.md @@ -13,7 +13,7 @@ When a tag is pushed to a connector repository, the shared release workflow: 3. Builds multi-arch Docker images 4. Signs all artifacts with Sigstore (keyless) 5. Generates SLSA provenance attestations -6. Publishes to S3, GHCR, and ECR Public +6. Publishes to S3 and ECR Public 7. Records the release in the connector registry API ## Jobs @@ -66,11 +66,10 @@ Builds Windows zip and MSI installer: Builds and publishes container images: - Multi-arch Docker images (amd64/arm64) -- Pushes to GHCR (public registry) - Pushes to ECR Public (for Lambda deployment) - Attaches provenance attestations to images (OCI referrers) -**Outputs:** GHCR and ECR Public images with attached attestations +**Outputs:** ECR Public images with attached attestations ### publish-release-manifest @@ -161,7 +160,7 @@ cosign verify-attestation \ --type https://slsa.dev/provenance/v1 \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ --certificate-identity-regexp 'https://github.com/ConductorOne/github-workflows/.github/workflows/release.yaml@.*' \ - ghcr.io/conductorone/baton-foo@sha256:abc123 + public.ecr.aws/conductorone/baton-foo@sha256:abc123 ``` ### Certificate Identity @@ -249,7 +248,7 @@ The `scripts/validate-release-artifacts.sh` script validates: - Manifest structure and version match - All binary assets are downloadable - Provenance and SBOM attestations exist and verify -- GHCR and ECR Public image attestations (if present) +- ECR Public image attestations (if present) - Manifest signature (if present) ```bash @@ -305,7 +304,6 @@ Test the MSI installer on an actual Windows machine: ### Common Issues - **Cosign version:** Ensure using latest cosign (`cosign version`) -- **GHCR access:** May need `docker login ghcr.io` for image verification - **MSI UpgradeCode:** If upgrades don't work, verify UpgradeCode is consistent across versions ## Future Work diff --git a/pb/artifacts/v1/manifest.pb.go b/pb/artifacts/v1/manifest.pb.go index a550ae6..7764f44 100644 --- a/pb/artifacts/v1/manifest.pb.go +++ b/pb/artifacts/v1/manifest.pb.go @@ -334,8 +334,8 @@ type Manifest_builder struct { // assets is a map of platform/type identifiers to asset metadata. // Keys are platform identifiers like "darwin-arm64", "linux-amd64", "windows-amd64", "checksums" Assets map[string]*Asset - // images is a map of registry identifiers to container image metadata. - // Keys are registry identifiers like "ghcr", "ecrPublic" + // images is a map of image identifiers to container image metadata. + // Keys are image identifiers like "ecrPublic", "lambda-arm64" Images map[string]*Image // signature_href is the URL to the manifest signature file (manifest.json.sig) SignatureHref *string @@ -902,7 +902,7 @@ func (x *Image) ClearIsIndex() { type Image_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. - // ref is the tag-based image reference (e.g., "ghcr.io/conductorone/baton-ukg:0.1.98") + // ref is the tag-based image reference (e.g., "public.ecr.aws/conductorone/baton-ukg:0.1.98") Ref *string // digest is the immutable image digest in the format "sha256:" (e.g., "sha256:abc123...") Digest *string diff --git a/proto/artifacts/v1/manifest.proto b/proto/artifacts/v1/manifest.proto index 19c3f0c..8f08f50 100644 --- a/proto/artifacts/v1/manifest.proto +++ b/proto/artifacts/v1/manifest.proto @@ -32,8 +32,8 @@ message Manifest { // Keys are platform identifiers like "darwin-arm64", "linux-amd64", "windows-amd64", "checksums" map assets = 6; - // images is a map of registry identifiers to container image metadata. - // Keys are registry identifiers like "ghcr", "ecrPublic" + // images is a map of image identifiers to container image metadata. + // Keys are image identifiers like "ecrPublic", "lambda-arm64" map images = 7; // signature_href is the URL to the manifest signature file (manifest.json.sig) @@ -88,7 +88,7 @@ message Asset { // Image represents metadata for a container image (digest-first). message Image { - // ref is the tag-based image reference (e.g., "ghcr.io/conductorone/baton-ukg:0.1.98") + // ref is the tag-based image reference (e.g., "public.ecr.aws/conductorone/baton-ukg:0.1.98") string ref = 1; // digest is the immutable image digest in the format "sha256:" (e.g., "sha256:abc123...") @@ -124,4 +124,4 @@ message AttestationDescriptor { // the attestation, signature, certificate, and transparency log proof. // For OCI-native attestations (images), this is omitted - use OCI referrers. string bundle_href = 3; -} \ No newline at end of file +} diff --git a/scripts/validate-release-artifacts.sh b/scripts/validate-release-artifacts.sh index 3d0ff4a..f7e977f 100755 --- a/scripts/validate-release-artifacts.sh +++ b/scripts/validate-release-artifacts.sh @@ -9,7 +9,6 @@ # - Binary assets exist and are downloadable # - Provenance attestations exist and verify with cosign # - SBOM attestations exist and verify with cosign -# - GHCR image attestation (if present) # - ECR Public image attestation (if present) # # Exit codes: @@ -195,26 +194,9 @@ for platform in $(echo "$MANIFEST" | jq -r '.assets | keys[]'); do rm -f "$TEMP_DIR/$FILENAME" done -# 4. Validate GHCR image attestation (if present) echo "" echo "=== Container Image Validation ===" -GHCR_URI=$(echo "$MANIFEST" | jq -r '.images.ghcr.uri // empty') -if [[ -n "$GHCR_URI" ]]; then - info "Validating GHCR image: $GHCR_URI" - if cosign verify-attestation \ - --type https://slsa.dev/provenance/v1 \ - --certificate-oidc-issuer "$CERT_OIDC_ISSUER" \ - --certificate-identity-regexp "$CERT_IDENTITY_REGEXP" \ - "$GHCR_URI" > /dev/null 2>&1; then - pass "GHCR image attestation verified" - else - fail "GHCR image attestation verification failed" - fi -else - warn "No GHCR image in manifest (docker may have been skipped)" -fi - -# 5. Validate ECR Public image attestation (if present) +# 4. Validate ECR Public image attestation (if present) ECR_URI=$(echo "$MANIFEST" | jq -r '.images.ecrPublic.uri // empty') if [[ -n "$ECR_URI" ]]; then info "Validating ECR Public image: $ECR_URI" @@ -231,7 +213,7 @@ else warn "No ECR Public image in manifest (docker may have been skipped)" fi -# 6. Validate manifest signature (if present) +# 5. Validate manifest signature (if present) echo "" echo "=== Manifest Signature Validation ===" MANIFEST_SIG_URL="${BASE_URL}/${ORG_REPO}/${VERSION}/manifest.json.sig" diff --git a/templates/.goreleaser-docker-oci-template.yaml.tmpl b/templates/.goreleaser-docker-oci-template.yaml.tmpl index 3dde85a..45f7a71 100644 --- a/templates/.goreleaser-docker-oci-template.yaml.tmpl +++ b/templates/.goreleaser-docker-oci-template.yaml.tmpl @@ -1,4 +1,4 @@ -## Docker template for signed OCI images, pushes to GHCR and ECR public +## Docker template for signed OCI images, pushes to ECR public ## GoReleaser >= 2.12 required for dockers_v2 ## ## Template variables (substituted via envsubst): @@ -18,25 +18,6 @@ builds: - amd64 - arm64 dockers_v2: - - id: ghcr - images: - - "ghcr.io/conductorone/${REPO_NAME}" - tags: - - "{{ .Version }}" - - latest - platforms: - - linux/amd64 - - linux/arm64 - dockerfile: ${DOCKERFILE_PATH} - ids: - - linux -${EXTRA_FILES_BLOCK} labels: - "org.opencontainers.image.created": "{{.Date}}" - "org.opencontainers.image.title": "{{.ProjectName}}" - "org.opencontainers.image.revision": "{{.FullCommit}}" - "org.opencontainers.image.version": "{{.Version}}" - "org.opencontainers.image.source": "{{ .GitURL }}" - "org.opencontainers.image.description": "ConductorOne Baton connector" - id: ecr-public images: - "public.ecr.aws/conductorone/${REPO_NAME}"