The release workflow could not complete on the self-hosted sibling-container runner. Four defects are corrected together because three of them share the same release.yml plumbing. Exclude the tag under release from the prior-tag probe. check-release-tag.sh already requires the tag to point at HEAD, so the unfiltered listing always matched itself and rejected every first release before the initial baseline branch could run. Resolve host bind sources once per job. Steps run inside the runner container while docker run starts siblings on the host daemon, so GITHUB_WORKSPACE and RUNNER_TEMP are not usable as bind sources. A new step maps both to host paths through the runner's own mounts and exports them; the build, provenance, smoke, finalize, and publish steps reuse them, and publish-release.sh receives them by environment instead of repeating the inspection. Bind destinations stay at the container-internal paths so runner-side reads keep working unchanged. Prove the signing material before the first push. Gitea package versions are immutable, so a missing or undecryptable cosign key must abort ahead of the require_absent gate rather than after packages and the image are published. Authenticate the pinned consumer fetches. Both consumer repositories are private, so anonymous fetches fail; the release token is passed to the verification step and applied as a URL-scoped extra header through git's config environment, keeping it out of argv, remotes, and on-disk config. Anonymous fetch remains the fallback for local runs.
261 lines
12 KiB
YAML
261 lines
12 KiB
YAML
name: immutable-release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "v*.*.*"
|
|
|
|
concurrency:
|
|
group: release-${{ gitea.ref_name }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
release:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
environment: production
|
|
steps:
|
|
- name: Check out immutable tag
|
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Resolve host bind sources for sibling containers
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
# This job's steps run inside the runner container while every
|
|
# `docker run` starts a sibling container on the host daemon, so bind
|
|
# sources must be host paths. Resolve them once from the runner's own
|
|
# mounts and reuse them in every later step.
|
|
if ! mounts="$(docker inspect "$HOSTNAME" 2>/dev/null | jq -c '.[0].Mounts')"; then
|
|
echo "Runner is not containerized; using workspace and temp paths as host paths."
|
|
echo "RENDEZVOUS_WORKSPACE_SOURCE=$GITHUB_WORKSPACE" >>"$GITHUB_ENV"
|
|
echo "RENDEZVOUS_TEMP_SOURCE=$RUNNER_TEMP" >>"$GITHUB_ENV"
|
|
exit 0
|
|
fi
|
|
resolve_host_path() {
|
|
jq -er --arg path "$1" '
|
|
[ .[]
|
|
| .Destination as $destination
|
|
| select($path == $destination
|
|
or ($path | startswith($destination + "/"))) ]
|
|
| if length == 0 then
|
|
error("No runner mount exposes \($path) on the Docker host.")
|
|
else
|
|
sort_by(.Destination | length) | last
|
|
end
|
|
| (.Destination | length) as $prefix
|
|
| .Source + $path[$prefix:]' <<<"$mounts"
|
|
}
|
|
workspace_source="$(resolve_host_path "$GITHUB_WORKSPACE")"
|
|
temp_source="$(resolve_host_path "$RUNNER_TEMP")"
|
|
echo "RENDEZVOUS_WORKSPACE_SOURCE=$workspace_source" >>"$GITHUB_ENV"
|
|
echo "RENDEZVOUS_TEMP_SOURCE=$temp_source" >>"$GITHUB_ENV"
|
|
|
|
- name: Install pinned .NET SDKs
|
|
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
|
|
with:
|
|
dotnet-version: |
|
|
8.0.128
|
|
10.0.301
|
|
|
|
- name: Install pinned Buildx and BuildKit
|
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
|
with:
|
|
version: v0.35.0
|
|
install: true
|
|
driver-opts: image=moby/buildkit:v0.25.2@sha256:0f63d66f8d2de0bd16438284831a3e9ee6ca7cd57b6eb3ed6e38a7a456590fa7
|
|
|
|
- name: Validate tag and produce reproducible artifacts
|
|
shell: bash
|
|
env:
|
|
RENDEZVOUS_RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
version="${GITHUB_REF_NAME#v}"
|
|
./scripts/check-release-tag.sh "$GITHUB_REF_NAME"
|
|
previous_tag="$(git tag --merged HEAD^ --list 'v*.*.*' --sort=-version:refname | sed -n '1p')"
|
|
# check-release-tag.sh already required this tag to point at HEAD, so
|
|
# it must be excluded before asking whether any earlier release exists.
|
|
unrelated_tag="$(git tag --list 'v*.*.*' | grep -vFx "$GITHUB_REF_NAME" | sed -n '1p' || true)"
|
|
if [[ -n "$previous_tag" ]]; then
|
|
./scripts/check-compatibility.sh "$previous_tag"
|
|
elif [[ -n "$unrelated_tag" ]]; then
|
|
echo "No prior release tag is an ancestor of $GITHUB_REF_NAME." >&2
|
|
exit 1
|
|
else
|
|
./scripts/check-compatibility.sh __initial_release_without_base__
|
|
fi
|
|
release_builder="rendezvous-release-builder:${GITHUB_SHA}"
|
|
docker buildx build \
|
|
--platform linux/amd64 \
|
|
--file eng/release-builder.Dockerfile \
|
|
--target release-builder \
|
|
--load \
|
|
--tag "$release_builder" .
|
|
mkdir -p "${RUNNER_TEMP}/release-home" "${RUNNER_TEMP}/nuget"
|
|
docker run --rm \
|
|
--user "$(id -u):$(id -g)" \
|
|
--env HOME="${RUNNER_TEMP}/release-home" \
|
|
--env NUGET_PACKAGES="${RUNNER_TEMP}/nuget" \
|
|
--volume "$RENDEZVOUS_WORKSPACE_SOURCE:/source" \
|
|
--volume "$RENDEZVOUS_TEMP_SOURCE:${RUNNER_TEMP}" \
|
|
--workdir /source \
|
|
"$release_builder" \
|
|
./scripts/build-release.sh "$version" "${RUNNER_TEMP}/release/$version"
|
|
./scripts/verify-real-consumers.sh "$version" "${RUNNER_TEMP}/release/$version"
|
|
echo "RENDEZVOUS_VERSION=$version" >>"$GITHUB_ENV"
|
|
echo "RENDEZVOUS_RELEASE_DIR=${RUNNER_TEMP}/release/$version" >>"$GITHUB_ENV"
|
|
echo "RENDEZVOUS_RELEASE_DIR_SOURCE=${RENDEZVOUS_TEMP_SOURCE}/release/$version" >>"$GITHUB_ENV"
|
|
echo "RENDEZVOUS_RELEASE_BUILDER=$release_builder" >>"$GITHUB_ENV"
|
|
|
|
- name: Build exact container candidate
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
export SOURCE_DATE_EPOCH="$(git show -s --format=%ct HEAD)"
|
|
common=(
|
|
--no-cache
|
|
--pull=false
|
|
--provenance=false
|
|
--platform linux/amd64
|
|
--build-arg SOURCE_DATE_EPOCH="$SOURCE_DATE_EPOCH"
|
|
--build-arg SOURCE_REVISION_ID="$GITHUB_SHA"
|
|
)
|
|
release_tag="git.finalfactory.de/heikyu/rendezvous:${RENDEZVOUS_VERSION}"
|
|
image_one="${RUNNER_TEMP}/rendezvous-image-1.tar"
|
|
image_two="${RUNNER_TEMP}/rendezvous-image-2.tar"
|
|
docker buildx build "${common[@]}" --tag "$release_tag" \
|
|
--output "type=docker,dest=$image_one,rewrite-timestamp=true" .
|
|
docker buildx build "${common[@]}" --tag "$release_tag" \
|
|
--output "type=docker,dest=$image_two,rewrite-timestamp=true" .
|
|
cmp --silent "$image_one" "$image_two"
|
|
docker load --input "$image_one"
|
|
candidate_id="$(docker image inspect --format '{{.Id}}' "$release_tag")"
|
|
buildkit_version="$(docker buildx inspect --bootstrap | sed -n 's/.*BuildKit version: *//p' | sed -n '1p')"
|
|
docker run --rm \
|
|
--user "$(id -u):$(id -g)" \
|
|
--volume "$RENDEZVOUS_WORKSPACE_SOURCE:/source" \
|
|
--volume "$RENDEZVOUS_TEMP_SOURCE:${RUNNER_TEMP}" \
|
|
--workdir /source \
|
|
"$RENDEZVOUS_RELEASE_BUILDER" \
|
|
python3 eng/release_artifacts.py record-container-build \
|
|
--provenance "$RENDEZVOUS_RELEASE_DIR/release-provenance.json" \
|
|
--buildx-version "$(docker buildx version)" \
|
|
--buildkit-version "$buildkit_version" \
|
|
--image-id "$candidate_id"
|
|
|
|
- name: Stage HTTP registration, browse, and authenticated UDP traversal
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
secret="deploy/compose/secrets/signing-key"
|
|
cleanup() {
|
|
RENDEZVOUS_UID=1654 RENDEZVOUS_GID=1654 RENDEZVOUS_IMAGE="git.finalfactory.de/heikyu/rendezvous:${RENDEZVOUS_VERSION}" \
|
|
docker compose -f deploy/compose/compose.yaml down --volumes >/dev/null 2>&1 || true
|
|
rm -f "$secret"
|
|
}
|
|
trap cleanup EXIT
|
|
umask 077
|
|
install -d -m 0700 deploy/compose/secrets
|
|
openssl rand -out "$secret" 32
|
|
chmod 0600 "$secret"
|
|
publisher_credential="$(RENDEZVOUS_SMOKE_LOCAL_KEY="$secret" \
|
|
./scripts/mint-local-publisher-credential.sh)"
|
|
export RENDEZVOUS_PUBLISHER_CREDENTIAL="$publisher_credential"
|
|
chmod 0444 "$secret"
|
|
export RENDEZVOUS_UID=1654
|
|
export RENDEZVOUS_GID=1654
|
|
port_suffix="$(( ${GITHUB_RUN_ID:-$$} % 10000 ))"
|
|
export RENDEZVOUS_HTTP_HOST_PORT="$(( 20000 + port_suffix ))"
|
|
export RENDEZVOUS_UDP_HOST_PORT="$(( 40000 + port_suffix ))"
|
|
runner_workspace_source="${RENDEZVOUS_WORKSPACE_SOURCE:?host workspace bind source was not resolved}"
|
|
export RENDEZVOUS_CONFIG_SOURCE="$runner_workspace_source/deploy/compose/appsettings.Production.json"
|
|
export RENDEZVOUS_SECRET_SOURCE="$runner_workspace_source/deploy/compose/secrets/signing-key"
|
|
export RENDEZVOUS_IMAGE="git.finalfactory.de/heikyu/rendezvous:${RENDEZVOUS_VERSION}"
|
|
docker compose -f deploy/compose/compose.yaml up --detach --no-build
|
|
container_id="$(docker compose -f deploy/compose/compose.yaml ps -q rendezvous)"
|
|
test -n "$container_id"
|
|
docker run --rm \
|
|
--network "container:${container_id}" \
|
|
--user "$(id -u):$(id -g)" \
|
|
--env HOME=/tmp \
|
|
--env RENDEZVOUS_SMOKE_HTTP_URL=http://127.0.0.1:8080/ \
|
|
--env RENDEZVOUS_SMOKE_UDP_ENDPOINT=127.0.0.1:9050 \
|
|
--env RENDEZVOUS_PUBLISHER_CREDENTIAL \
|
|
--volume "$runner_workspace_source:/source:ro" \
|
|
--workdir /source \
|
|
"$RENDEZVOUS_RELEASE_BUILDER" \
|
|
bash -lc '
|
|
for attempt in {1..180}; do
|
|
curl --fail --silent "${RENDEZVOUS_SMOKE_HTTP_URL%/}/health/ready" >/dev/null 2>&1 && exec ./scripts/smoke-deployment.sh
|
|
sleep 1
|
|
done
|
|
exit 1
|
|
' || {
|
|
docker compose -f deploy/compose/compose.yaml logs rendezvous
|
|
exit 1
|
|
}
|
|
|
|
- name: Scan candidate for high and critical vulnerabilities
|
|
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0, post-incident safe SHA
|
|
with:
|
|
image-ref: git.finalfactory.de/heikyu/rendezvous:${{ env.RENDEZVOUS_VERSION }}
|
|
version: v0.69.3
|
|
format: table
|
|
exit-code: "1"
|
|
ignore-unfixed: false
|
|
severity: HIGH,CRITICAL
|
|
|
|
- name: Generate container SPDX inventory
|
|
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0, post-incident safe SHA
|
|
with:
|
|
image-ref: git.finalfactory.de/heikyu/rendezvous:${{ env.RENDEZVOUS_VERSION }}
|
|
version: v0.69.3
|
|
format: spdx-json
|
|
output: ${{ env.RENDEZVOUS_RELEASE_DIR }}/FinalFactory.Rendezvous.Container.${{ env.RENDEZVOUS_VERSION }}.spdx.json
|
|
|
|
- name: Finalize checksums over the publish-ready candidate
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
source_date_epoch="$(git show -s --format=%ct HEAD)"
|
|
docker run --rm \
|
|
--user "$(id -u):$(id -g)" \
|
|
--volume "$RENDEZVOUS_WORKSPACE_SOURCE:/source" \
|
|
--volume "$RENDEZVOUS_TEMP_SOURCE:${RUNNER_TEMP}" \
|
|
--workdir /source \
|
|
"$RENDEZVOUS_RELEASE_BUILDER" \
|
|
bash -c 'python3 eng/release_artifacts.py normalize-container-sbom \
|
|
--file "$1/FinalFactory.Rendezvous.Container.$2.spdx.json" \
|
|
--version "$2" \
|
|
--commit "$3" \
|
|
--source-date-epoch "$4" \
|
|
&& ./scripts/finalize-release-candidate.sh "$2" "$1"' \
|
|
_ "$RENDEZVOUS_RELEASE_DIR" "$RENDEZVOUS_VERSION" "$GITHUB_SHA" "$source_date_epoch"
|
|
|
|
- name: Preserve verified candidate artifacts
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: rendezvous-${{ env.RENDEZVOUS_VERSION }}
|
|
path: ${{ env.RENDEZVOUS_RELEASE_DIR }}
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
|
|
- name: Install pinned signing client
|
|
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
|
with:
|
|
cosign-release: v3.0.6
|
|
|
|
- name: Publish once, sign, attest, and create release
|
|
shell: bash
|
|
env:
|
|
RENDEZVOUS_RELEASE_USERNAME: ${{ secrets.RELEASE_USERNAME }}
|
|
RENDEZVOUS_RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
|
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
|
RENDEZVOUS_WORKSPACE_SOURCE: ${{ env.RENDEZVOUS_WORKSPACE_SOURCE }}
|
|
RENDEZVOUS_RELEASE_DIR_SOURCE: ${{ env.RENDEZVOUS_RELEASE_DIR_SOURCE }}
|
|
run: ./scripts/publish-release.sh "$RENDEZVOUS_VERSION" "$RENDEZVOUS_RELEASE_DIR"
|