Skip to content
Open
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
102 changes: 102 additions & 0 deletions .github/workflows/cosign-sign-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Cosign image-signing workflow template — copy to <repo>/.github/workflows/cosign.yml
# Per ADR-041 (Trivy + Cosign deploy gate). Sample distribution scope: 18 app repos.
#
# Required GitHub secrets (pull from Infisical-CI MI):
# COSIGN_PRIVATE_KEY — base64-encoded private key
# COSIGN_PASSWORD — passphrase for private key
# GHCR_PAT — GitHub PAT with write:packages (or use built-in GITHUB_TOKEN if same org)
#
# Public key for verification: published at https://gist.githubusercontent.com/Cramraika/c246bdb779518a588617839bfe2a75c1/raw/cosign.pub
# Coolify pre-deploy hook (vps_host/scripts/coolify-hooks/trivy-pre-deploy.sh):
# - Pulls image
# - Runs Trivy CVE scan
# - cosign verify --key <pub-key-url> <image>
# - Refuses deploy on either Trivy CRITICAL or cosign verify fail

name: Cosign image signing

on:
push:
branches: [main]
tags: ['v*']
workflow_dispatch:

permissions:
contents: read
packages: write
id-token: write # for keyless via OIDC if desired (alternate path)

jobs:
build-sign-push:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

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

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha,format=short

- name: Build and push image
id: build
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

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

- name: Sign image with key
env:
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
run: |
set -euo pipefail
for tag in $(echo "${{ steps.meta.outputs.tags }}" | tr ',' ' '); do
echo "Signing $tag@${{ steps.build.outputs.digest }}"
cosign sign --yes --key env://COSIGN_PRIVATE_KEY \
"$tag@${{ steps.build.outputs.digest }}"
done

- name: Generate SBOM (syft)
uses: anchore/sbom-action@v0
with:
image: ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
format: spdx-json
output-file: sbom.spdx.json

- name: Attach SBOM to image
env:
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
run: |
cosign attest --yes --key env://COSIGN_PRIVATE_KEY \
--predicate sbom.spdx.json --type spdxjson \
"ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}"

- name: Verify signature (smoke test)
run: |
cosign verify \
--key https://gist.githubusercontent.com/Cramraika/c246bdb779518a588617839bfe2a75c1/raw/cosign.pub \
"ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}"
Comment on lines +98 to +102
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

GitHub gist URL immutable revision pinning raw.githubusercontent.com

💡 Result:

To pin a GitHub Gist to an immutable revision using raw.githubusercontent.com URLs: The standard raw URL format for a specific revision (immutable, as it won't change even if the Gist is updated) is: https://gist.githubusercontent.com/[username]/[gist_id]/raw/[revision_hash]/[filename] - [username]: Gist owner's username - [gist_id]: The Gist ID (e.g., from https://gist.github.com/username/123abc) - [revision_hash]: Git commit SHA for that revision (found in Gist history or "Raw" button URL) - [filename]: Name of the file in the Gist For single-file Gists without specifying filename, it returns the first/default file. Example: https://gist.githubusercontent.com/atenni/5604615/raw/30d74d258442c7c65512eafab474568dd706c430/testNewGist Omitting [revision_hash] (e.g., https://gist.githubusercontent.com/[username]/[gist_id]/raw/[filename]) points to the latest version (mutable). To get the revision hash: - View Gist revisions via the clock icon or API (GET /gists/{gist_id}). - Click "Raw" on a file; the URL includes the current revision hash. - Use GitHub Gist API: https://api.github.com/gists/[gist_id] returns files with raw_url (latest) and history. Note: https://gist.github.com/.../raw redirects to the gist.githubusercontent.com equivalent. Use curl -L if needed for redirects. Updates to non-pinned URLs may take 2-5 minutes due to caching. Gists are Git repos, so revisions are Git commits; pinning the SHA ensures immutability.

Citations:


🏁 Script executed:

# Check if the gist URL is accessible and look for gist structure
# GitHub gist URLs can be pinned to specific commits using the commit hash
# The format is: https://gist.githubusercontent.com/username/gist_id/raw/commit_hash/filename

# Let's verify the gist exists and understand its structure
curl -s -I "https://gist.githubusercontent.com/Cramraika/c246bdb779518a588617839bfe2a75c1/raw/cosign.pub" | head -20

Repository: Cramraika/bulk

Length of output: 784


🏁 Script executed:

# Also check if there's documentation in the repo about this approach
find . -type f \( -name "*.md" -o -name "*.txt" -o -name "*.yml" -o -name "*.yaml" \) | head -20
ls -la .github/workflows/ | grep -i cosign

Repository: Cramraika/bulk

Length of output: 542


🏁 Script executed:

cat -n .github/workflows/cosign-sign-image.yml | sed -n '95,105p'

Repository: Cramraika/bulk

Length of output: 532


Pin the gist URL to an immutable revision to prevent silent signature validation changes.

The current gist URL lacks a revision hash and points to the latest version, which can be edited at any time. Use the immutable format: https://gist.githubusercontent.com/Cramraika/c246bdb779518a588617839bfe2a75c1/raw/[commit_hash]/cosign.pub (obtain the commit hash from the gist's Raw button or revision history), or vendor the public key directly in the repository.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/cosign-sign-image.yml around lines 98 - 102, The Verify
signature (smoke test) step uses a mutable gist URL for the cosign key; update
the cosign verify invocation in the "Verify signature (smoke test)" step to use
an immutable key source by either replacing the gist URL with the gist raw URL
that includes a specific commit hash (format:
https://gist.githubusercontent.com/Cramraika/c246bdb779518a588617839bfe2a75c1/raw/<commit_hash>/cosign.pub)
or vendoring the public key into the repo and referencing it locally (e.g.,
--key ./cosign.pub), then ensure the workflow downloads or checks out that
pinned key before running cosign verify.

Loading