Skip to content
Open
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
17 changes: 0 additions & 17 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down
32 changes: 8 additions & 24 deletions cmd/extract-images/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)) {
Expand All @@ -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,
Expand All @@ -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 {
Expand Down
24 changes: 8 additions & 16 deletions cmd/extract-images/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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(),
}
Expand All @@ -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)
}
}

Expand Down
17 changes: 6 additions & 11 deletions cmd/record-release/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -117,8 +112,8 @@ func TestRecordReleaseRequestMarshalsAttestations(t *testing.T) {
},
},
Images: map[string]*ReleaseImage{
"ghcr": {
Platform: "ghcr",
"ecrPublic": {
Platform: "ecrPublic",
Attestations: []*ReleaseAttestation{{Type: slsaProvenance}},
},
},
Expand Down Expand Up @@ -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)
}
}

Expand Down
4 changes: 1 addition & 3 deletions docs/diagrams/release-workflow.dot
Original file line number Diff line number Diff line change
Expand Up @@ -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"];

Expand All @@ -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"];

Expand All @@ -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;
Expand Down
Binary file modified docs/diagrams/release-workflow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 4 additions & 6 deletions docs/release-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions pb/artifacts/v1/manifest.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions proto/artifacts/v1/manifest.proto
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ message Manifest {
// Keys are platform identifiers like "darwin-arm64", "linux-amd64", "windows-amd64", "checksums"
map<string, Asset> 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<string, Image> images = 7;

// signature_href is the URL to the manifest signature file (manifest.json.sig)
Expand Down Expand Up @@ -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:<hex>" (e.g., "sha256:abc123...")
Expand Down Expand Up @@ -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;
}
}
22 changes: 2 additions & 20 deletions scripts/validate-release-artifacts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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"
Expand All @@ -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"
Expand Down
21 changes: 1 addition & 20 deletions templates/.goreleaser-docker-oci-template.yaml.tmpl
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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}"
Expand Down