Skip to content
200 changes: 200 additions & 0 deletions .github/workflows/container.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# Builds and publishes the funannotate container image to GitHub Container
# Registry (GHCR), signs it with Cosign (Sigstore), attaches SLSA provenance
# attestations, and verifies the signature.
#
# - Every push of a v* tag: builds the image (./Dockerfile) and publishes it
# as ghcr.io/<owner>/funannotate:vX.Y.Z and :X.Y.Z, plus :latest for stable
# (non-prerelease) releases. workflow_dispatch allows a manual rebuild.

name: Build and Sign Container Image

on:
push:
tags:
- 'v*'
workflow_dispatch:

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
outputs:
tags: ${{ steps.meta.outputs.tags }}
permissions:
contents: read
packages: write

steps:
- name: Checkout
uses: actions/checkout@v5

- name: Compute image tags
id: meta
env:
REF_NAME: ${{ github.ref_name }}
REF_TYPE: ${{ github.ref_type }}
REGISTRY: ${{ env.REGISTRY }}
IMAGE_NAME: ${{ env.IMAGE_NAME }}
run: |
ref="${REF_NAME}"
version="${ref#v}"
prerelease="false"
case "$version" in *-*) prerelease="true" ;; esac
{
echo "tags<<_EOF_"
echo "${REGISTRY}/${IMAGE_NAME}:${ref}"
if [ "${REF_TYPE}" = "tag" ]; then
echo "${REGISTRY}/${IMAGE_NAME}:${version}"
if [ "$prerelease" = "false" ]; then
echo "${REGISTRY}/${IMAGE_NAME}:latest"
fi
fi
echo "_EOF_"
echo "version=${version}"
} >> "$GITHUB_OUTPUT"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max

sign:
needs: build
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
packages: write

steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Install Cosign
uses: sigstore/cosign-installer@v3

- name: Sign images
env:
TAGS: ${{ needs.build.outputs.tags }}
run: |
printf '%s\n' "$TAGS" | while IFS= read -r tag; do
[ -n "${tag}" ] || continue
cosign sign --yes --recursive "${tag}"
done
provenance:
needs: [build, sign]
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
packages: write

steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Install Cosign
uses: sigstore/cosign-installer@v3

- name: Generate SLSA provenance predicate
run: |
started_on="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
predicate_file="${RUNNER_TEMP}/slsa-provenance.json"
cat > "${predicate_file}" <<JSON
{
"buildDefinition": {
"buildType": "https://github.com/${GITHUB_REPOSITORY}/.github/workflows/container.yml@${GITHUB_REF}",
"externalParameters": {},
"internalParameters": {},
"resolvedDependencies": []
},
"runDetails": {
"builder": {
"id": "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
},
"metadata": {
"invocationId": "${GITHUB_RUN_ID}",
"startedOn": "${started_on}"
},
"byproducts": []
}
}
JSON
- name: Attach SLSA provenance
env:
TAGS: ${{ needs.build.outputs.tags }}
run: |
printf '%s\n' "$TAGS" | while IFS= read -r tag; do
[ -n "${tag}" ] || continue
cosign attest --yes \
--predicate "${RUNNER_TEMP}/slsa-provenance.json" \
--type=slsaprovenance1 \
"${tag}"
done
verify:
needs: [sign, provenance]
runs-on: ubuntu-latest
permissions:
contents: read
packages: read

steps:
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Install Cosign
uses: sigstore/cosign-installer@v3

- name: Verify signatures
env:
TAGS: ${{ needs.build.outputs.tags }}
REPO: ${{ github.repository }}
run: |
printf '%s\n' "$TAGS" | while IFS= read -r tag; do
[ -n "${tag}" ] || continue
cosign verify \
--certificate-identity="https://github.com/${REPO}/.github/workflows/container.yml@${GITHUB_REF}" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
"${tag}"
done