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
329 changes: 329 additions & 0 deletions .github/workflows/image-signing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
# Container Image Signing with Cosign
# Signs all StreamSpace container images for supply chain security

name: Sign Container Images

on:
push:
branches: [main, master]
paths:
- 'api/**'
- 'controller/**'
- 'ui/**'
- '.github/workflows/image-signing.yml'
pull_request:
branches: [main, master]
release:
types: [published]
workflow_dispatch:

env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ${{ github.repository_owner }}/streamspace

permissions:
contents: read
packages: write
id-token: write # Required for OIDC token for Cosign

jobs:
build-and-sign-api:
name: Build and Sign API Image
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- 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
with:
cosign-release: 'v2.2.2'

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push API image
id: build-api
uses: docker/build-push-action@v5
with:
context: ./api
file: ./api/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
provenance: true
sbom: true

- name: Sign API image with Cosign
env:
COSIGN_EXPERIMENTAL: "true"
run: |
echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build-api.outputs.digest }}

- name: Verify API image signature
env:
COSIGN_EXPERIMENTAL: "true"
run: |
echo "${{ steps.meta.outputs.tags }}" | head -n 1 | xargs -I {} cosign verify {}@${{ steps.build-api.outputs.digest }}

build-and-sign-controller:
name: Build and Sign Controller Image
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- 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
with:
cosign-release: 'v2.2.2'

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-controller
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push Controller image
id: build-controller
uses: docker/build-push-action@v5
with:
context: ./controller
file: ./controller/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
provenance: true
sbom: true

- name: Sign Controller image with Cosign
env:
COSIGN_EXPERIMENTAL: "true"
run: |
echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build-controller.outputs.digest }}

- name: Verify Controller image signature
env:
COSIGN_EXPERIMENTAL: "true"
run: |
echo "${{ steps.meta.outputs.tags }}" | head -n 1 | xargs -I {} cosign verify {}@${{ steps.build-controller.outputs.digest }}

build-and-sign-ui:
name: Build and Sign UI Image
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- 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
with:
cosign-release: 'v2.2.2'

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ui
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push UI image
id: build-ui
uses: docker/build-push-action@v5
with:
context: ./ui
file: ./ui/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
provenance: true
sbom: true

- name: Sign UI image with Cosign
env:
COSIGN_EXPERIMENTAL: "true"
run: |
echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build-ui.outputs.digest }}

- name: Verify UI image signature
env:
COSIGN_EXPERIMENTAL: "true"
run: |
echo "${{ steps.meta.outputs.tags }}" | head -n 1 | xargs -I {} cosign verify {}@${{ steps.build-ui.outputs.digest }}

generate-attestations:
name: Generate SLSA Attestations
runs-on: ubuntu-latest
needs: [build-and-sign-api, build-and-sign-controller, build-and-sign-ui]
permissions:
contents: read
packages: write
id-token: write
attestations: write
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Cosign
uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.2.2'

- name: Generate SBOM for API
uses: anchore/sbom-action@v0
with:
path: ./api
artifact-name: streamspace-api-sbom.spdx.json
output-file: sbom-api.spdx.json
format: spdx-json

- name: Generate SBOM for Controller
uses: anchore/sbom-action@v0
with:
path: ./controller
artifact-name: streamspace-controller-sbom.spdx.json
output-file: sbom-controller.spdx.json
format: spdx-json

- name: Generate SBOM for UI
uses: anchore/sbom-action@v0
with:
path: ./ui
artifact-name: streamspace-ui-sbom.spdx.json
output-file: sbom-ui.spdx.json
format: spdx-json

- name: Upload SBOMs as artifacts
uses: actions/upload-artifact@v4
with:
name: sboms
path: |
sbom-*.spdx.json
retention-days: 90

- name: Attest API SBOM
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign attest --yes --type spdxjson \
--predicate sbom-api.spdx.json \
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api:latest

- name: Attest Controller SBOM
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign attest --yes --type spdxjson \
--predicate sbom-controller.spdx.json \
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-controller:latest

- name: Attest UI SBOM
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign attest --yes --type spdxjson \
--predicate sbom-ui.spdx.json \
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ui:latest

security-scan:
name: Security Scan Signed Images
runs-on: ubuntu-latest
needs: [build-and-sign-api, build-and-sign-controller, build-and-sign-ui]
strategy:
matrix:
component: [api, controller, ui]
steps:
- name: Install Cosign
uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.2.2'

- name: Verify image signature
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign verify ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.component }}:latest

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.component }}:latest
format: 'sarif'
output: 'trivy-${{ matrix.component }}-results.sarif'
severity: 'CRITICAL,HIGH'

- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-${{ matrix.component }}-results.sarif'
category: 'trivy-${{ matrix.component }}'

- name: Fail on critical vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.component }}:latest
format: 'table'
exit-code: '1'
severity: 'CRITICAL'
Loading
Loading