Skip to content

Commit 544c7f6

Browse files
feat(security): deploy signed release image locks (#1565)
1 parent 4baf4e4 commit 544c7f6

49 files changed

Lines changed: 762 additions & 700 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"hephaestus": minor
3+
---
4+
5+
**Operators:** Install, upgrade, and roll back with the signed release image lock; production and staging now refuse mutable tags or any image digest that did not pass the release evidence gate.

.github/workflows/cd-staging.yml

Lines changed: 0 additions & 109 deletions
This file was deleted.

.github/workflows/ci-compose-validate.yml

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ jobs:
2323
validate:
2424
name: "Render compose stacks"
2525
runs-on: ubuntu-latest
26+
env:
27+
COMPOSE_ENV_FILES: .env,release-lock.env
2628
steps:
2729
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
2830

@@ -42,6 +44,13 @@ jobs:
4244
-e 's|^HEPHAESTUS_AUTH_STATE_COOKIE_KEY=$|HEPHAESTUS_AUTH_STATE_COOKIE_KEY=Y2ktbm90LWEtcmVhbC1zdGF0ZS1jb29raWUta2V5|' \
4345
-e 's|^WEBHOOK_SECRET=$|WEBHOOK_SECRET=ci000000000000000000000000000000000|' \
4446
.env
47+
while IFS= read -r image; do
48+
name="HEPHAESTUS_IMAGE_${image^^}"
49+
name=${name//-/_}
50+
printf '%s=%s@sha256:%s\n' "$name" "example.invalid/${image}" "$(printf '0%.0s' {1..64})" >> release-lock.env
51+
done < <(jq -r '.images[], .upstream[].name' ../../security/release-images.json)
52+
printf 'IMAGE_TAG=0.0.0\nHEPHAESTUS_RELEASE=v0.0.0\nHEPHAESTUS_RELEASE_COMMIT=%s\n' \
53+
"$(printf '0%.0s' {1..40})" >> release-lock.env
4554
docker compose config > /dev/null
4655
echo "Rendered services:"
4756
docker compose config --services | sort
@@ -81,16 +90,6 @@ jobs:
8190
[ "$published" = "80 443 " ] || {
8291
echo "::error::reverse-proxy publishes '$published', expected '80 443 ' — !override was ignored"; exit 1; }
8392
84-
- name: Assert the pinned release version has not drifted
85-
run: |
86-
set -euo pipefail
87-
# scripts/sync-release-version.ts keeps these equal to the release on
88-
# every Version PR; a mismatch means someone edited one by hand.
89-
pkg=$(jq -r '.version' package.json)
90-
env_tag=$(grep -m1 '^IMAGE_TAG=' docker/self-host/.env.example | cut -d= -f2)
91-
[ "$pkg" = "$env_tag" ] || {
92-
echo "::error::IMAGE_TAG=$env_tag in .env.example != package.json $pkg (run scripts/sync-release-version.ts)"; exit 1; }
93-
9493
- name: Render the reference deployment
9594
working-directory: docker
9695
run: |
@@ -99,4 +98,4 @@ jobs:
9998
# still render, not that they are correct for production.
10099
docker compose \
101100
-f compose.proxy.yaml -f compose.core.yaml -f compose.app.yaml \
102-
--env-file self-host/.env config --quiet
101+
--env-file self-host/.env --env-file self-host/release-lock.env config --quiet

.github/workflows/ci-docker-build.yml

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ on:
2525
required: false
2626
type: string
2727
default: "true"
28-
release_pin_fetcher_changed:
29-
description: "Whether release-pin-fetcher image inputs changed"
30-
required: false
31-
type: string
32-
default: "true"
3328
postgres_image_changed:
3429
description: "Whether the PostgreSQL image inputs changed"
3530
required: false
@@ -133,32 +128,6 @@ jobs:
133128
org.opencontainers.image.licenses=MIT
134129
hephaestus.component=agent-pi
135130
136-
release-pin-fetcher-build:
137-
name: "Release Pin Fetcher"
138-
if: >-
139-
inputs.should_skip != 'true' &&
140-
(inputs.release_pin_fetcher_changed == 'true' || github.event_name != 'pull_request')
141-
uses: ./.github/workflows/reusable-docker-build.yml
142-
with:
143-
image-name: "ls1intum/hephaestus/release-pin-fetcher"
144-
docker-file: "./docker/release-pin-fetcher/Dockerfile"
145-
docker-context: "./docker/release-pin-fetcher"
146-
registry: "ghcr.io"
147-
# amd64-only on PRs (main/release build both arches). See reusable-docker-build.yml.
148-
single-arch: ${{ github.event_name == 'pull_request' }}
149-
tags: |
150-
${{ github.ref_name }}
151-
${{ github.sha }}
152-
${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}
153-
ci-${{ github.run_number }}
154-
${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || '' }}
155-
labels: |
156-
org.opencontainers.image.title=Hephaestus Release Pin Fetcher
157-
org.opencontainers.image.description=Init container that downloads + cosign-verifies the signed release pin asset
158-
org.opencontainers.image.vendor=AET TUM
159-
org.opencontainers.image.licenses=MIT
160-
hephaestus.component=release-pin-fetcher
161-
162131
postgres-build:
163132
name: "Postgres (pg_partman)"
164133
if: >-
@@ -192,7 +161,7 @@ jobs:
192161
if: >-
193162
inputs.should_skip != 'true' && github.event_name == 'pull_request' &&
194163
(inputs.webapp_changed != 'true' || inputs.application_server_changed != 'true' ||
195-
inputs.agent_images_changed != 'true' || inputs.release_pin_fetcher_changed != 'true' ||
164+
inputs.agent_images_changed != 'true' ||
196165
inputs.postgres_image_changed != 'true')
197166
runs-on: ubuntu-latest
198167
outputs:
@@ -216,7 +185,6 @@ jobs:
216185
WEBAPP_CHANGED: ${{ inputs.webapp_changed }}
217186
APPLICATION_SERVER_CHANGED: ${{ inputs.application_server_changed }}
218187
AGENT_IMAGES_CHANGED: ${{ inputs.agent_images_changed }}
219-
RELEASE_PIN_FETCHER_CHANGED: ${{ inputs.release_pin_fetcher_changed }}
220188
POSTGRES_IMAGE_CHANGED: ${{ inputs.postgres_image_changed }}
221189
PR_NUMBER: ${{ github.event.number }}
222190
BASE_SHA: ${{ github.event.pull_request.base.sha }}
@@ -260,5 +228,4 @@ jobs:
260228
echo "application-server-published=true" >> "$GITHUB_OUTPUT"
261229
fi
262230
[ "$AGENT_IMAGES_CHANGED" = "true" ] || tag_image agent-pi
263-
[ "$RELEASE_PIN_FETCHER_CHANGED" = "true" ] || tag_image release-pin-fetcher
264231
[ "$POSTGRES_IMAGE_CHANGED" = "true" ] || tag_image postgres

.github/workflows/ci-security-scan.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ jobs:
163163
if grep -rEnI "$NEEDLE" \
164164
--exclude-dir=node_modules --exclude-dir=target --exclude-dir=.git \
165165
. \
166-
| grep -vE '^\./(MIGRATION\.md|docs/admin/agent-image-digests\.md):' ; then
166+
| grep -vE '^\./(MIGRATION\.md|docs/admin/release-image-lock\.md):' ; then
167167
echo "::error::Legacy agent image is removed in issue #1076; relocate any new references to MIGRATION.md."
168168
exit 1
169169
fi
@@ -177,7 +177,7 @@ jobs:
177177
if grep -rEnI "$NEEDLE" \
178178
--exclude-dir=node_modules --exclude-dir=target --exclude-dir=.git \
179179
. \
180-
| grep -vE '^\./(MIGRATION\.md|docs/admin/agent-image-digests\.md|docs/decisions/0031-.*\.md|server/application/src/test/java/de/tum/cit/aet/hephaestus/agent/sandbox/AgentImageReferenceGuardTest\.java):' ; then
180+
| grep -vE '^\./(MIGRATION\.md|docs/admin/release-image-lock\.md|docs/decisions/0031-.*\.md|server/application/src/test/java/de/tum/cit/aet/hephaestus/agent/sandbox/AgentImageReferenceGuardTest\.java):' ; then
181181
echo "::error::A release-channel agent image tag resolves to another release's image (ADR 0031). Leave the reference unset so it follows this deployment's image tag, or pin a digest."
182182
exit 1
183183
fi
@@ -191,7 +191,7 @@ jobs:
191191
if grep -rEnI 'docker/agent-image-pin(\.local)?\.env' \
192192
--exclude-dir=node_modules --exclude-dir=target --exclude-dir=.git \
193193
. \
194-
| grep -vE '^\./(MIGRATION\.md|docs/admin/agent-image-digests\.md):' \
194+
| grep -vE '^\./(MIGRATION\.md|docs/admin/release-image-lock\.md):' \
195195
| grep -vE '^\./scripts/check-agent-instructions\.ts:[0-9]+:[[:space:]]*value: "docker/agent-image-pin(\.local)?\.env",$' ; then
196196
echo "::error::Legacy pin-file names are allowed only in migration history, the digest guide, and exact instruction-checker exceptions."
197197
exit 1

.github/workflows/cicd.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ jobs:
2525
application-server-image: ${{ steps.filter.outputs.application-server-image }}
2626
e2e: ${{ steps.filter.outputs.e2e }}
2727
agent-images: ${{ steps.filter.outputs.agent-images }}
28-
release-pin-fetcher: ${{ steps.filter.outputs.release-pin-fetcher }}
2928
postgres-image: ${{ steps.filter.outputs.postgres-image }}
3029
docs: ${{ steps.filter.outputs.docs }}
3130
ci-config: ${{ steps.filter.outputs.ci-config }}
@@ -138,8 +137,6 @@ jobs:
138137
- '.github/actions/setup-bun/**'
139138
agent-images:
140139
- 'docker/agents/**'
141-
release-pin-fetcher:
142-
- 'docker/release-pin-fetcher/**'
143140
postgres-image:
144141
- 'docker/postgres/**'
145142
docs:
@@ -324,7 +321,6 @@ jobs:
324321
webapp_changed: ${{ (needs.detect-changes.outputs.webapp-image == 'true' || needs.detect-changes.outputs.docker-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
325322
application_server_changed: ${{ (needs.detect-changes.outputs.application-server-image == 'true' || needs.detect-changes.outputs.docker-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
326323
agent_images_changed: ${{ (needs.detect-changes.outputs.agent-images == 'true' || needs.detect-changes.outputs.docker-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
327-
release_pin_fetcher_changed: ${{ (needs.detect-changes.outputs.release-pin-fetcher == 'true' || needs.detect-changes.outputs.docker-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
328324
postgres_image_changed: ${{ (needs.detect-changes.outputs.postgres-image == 'true' || needs.detect-changes.outputs.docker-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
329325

330326
all-ci-passed:

0 commit comments

Comments
 (0)