diff --git a/.gitea/actions/caddy-validate/action.yml b/.gitea/actions/caddy-validate/action.yml index 107661f..7ce9ca7 100644 --- a/.gitea/actions/caddy-validate/action.yml +++ b/.gitea/actions/caddy-validate/action.yml @@ -1,5 +1,9 @@ name: Validate Caddyfile description: Run the pinned Caddy validator against the checked-out Caddyfile +inputs: + workspace: + description: Repository workspace path supplied by the workflow + required: true runs: using: docker image: Dockerfile @@ -7,6 +11,6 @@ runs: args: - validate - --config - - /github/workspace/deploy/production/Caddyfile + - ${{ inputs.workspace }}/deploy/production/Caddyfile - --adapter - caddyfile diff --git a/.gitea/actions/kaniko-build/action.yml b/.gitea/actions/kaniko-build/action.yml index c79057e..2c1877d 100644 --- a/.gitea/actions/kaniko-build/action.yml +++ b/.gitea/actions/kaniko-build/action.yml @@ -1,6 +1,9 @@ name: Build image with Kaniko in a rootless container description: Validate a Dockerfile by building it without publishing the image or cache inputs: + workspace: + description: Repository workspace path supplied by the workflow + required: true dockerfile: description: Dockerfile path relative to the checked-out repository required: true @@ -12,8 +15,8 @@ runs: image: Dockerfile entrypoint: /kaniko/executor args: - - --context=dir:///github/workspace - - --dockerfile=/github/workspace/${{ inputs.dockerfile }} + - --context=dir://${{ inputs.workspace }} + - --dockerfile=${{ inputs.workspace }}/${{ inputs.dockerfile }} - --destination=${{ inputs.destination }} - --no-push - --no-push-cache diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 6d0e6fb..4382f2f 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -60,13 +60,17 @@ jobs: REVIEW_SECRET_FILE="$secret_file" docker compose --env-file deploy/production/.env.example -f deploy/production/compose.yaml config --quiet - name: Validate Caddyfile in isolated container action uses: ./.gitea/actions/caddy-validate + with: + workspace: ${{ github.workspace }} - name: Build production images uses: ./.gitea/actions/kaniko-build with: + workspace: ${{ github.workspace }} dockerfile: deploy/production/Dockerfile.backend destination: akyldash-backend:ci - name: Build OpenSearch production image uses: ./.gitea/actions/kaniko-build with: + workspace: ${{ github.workspace }} dockerfile: deploy/production/Dockerfile.opensearch destination: akyldash-opensearch:ci diff --git a/docs/operations/CI_RUNNER.md b/docs/operations/CI_RUNNER.md index 1f8747c..73387ab 100644 --- a/docs/operations/CI_RUNNER.md +++ b/docs/operations/CI_RUNNER.md @@ -45,7 +45,10 @@ metrics ports are needed. Runner cache is disabled. The CI workflow and `runner-smoke.yaml` target the repository runner label `akyldash-ci`. CI keeps the backend, Telegram, backup/restore, deployment, bootstrap, runner, Compose, and Caddy checks. Caddy validation runs in a pinned -container action with explicit `/usr/bin/caddy` entrypoint. Both production +container action with explicit `/usr/bin/caddy` entrypoint. The workflow passes +Gitea's `${{ github.workspace }}` value into each container action; Caddy and +Kaniko use that supplied path for repository files instead of assuming a +container workspace location. Both production Dockerfiles are built in a separate pinned Kaniko container action with explicit `/kaniko/executor` entrypoint using the runner's rootless container engine; the job container receives no Docker socket. Kaniko uses `--no-push` and @@ -196,8 +199,12 @@ returned 403 and the scheduler's reason was not independently confirmed. On head `3a89dc21`, CI run 63 failed at the host audit root guard and smoke run 64 succeeded. On head `436806a8`, CI run 65 failed in the Caddy container action with `exec: "validate": executable file not found`; Kaniko was skipped and smoke -run 66 succeeded. This PR sets explicit Caddy and Kaniko entrypoints. Successful -execution and resource fit still require a new runner canary. Root-guard tests +run 66 succeeded. On head `d96fc4d3`, CI run 67 failed because the Caddy action +looked for `/github/workspace/deploy/production/Caddyfile`, which was not +mounted at that fixed path; Kaniko was skipped and smoke run 68 succeeded. This +PR passes `${{ github.workspace }}` to the container actions and uses it for the +Caddyfile, Kaniko context, and Dockerfile paths. A new runner canary must verify +those paths and resource fit. Root-guard tests use `setpriv` for negative cases when jobs start as UID 0 and separately test the host-audit guard with simulated UID 0 and UID 65534; the real host audit, production config setup, and runner installer are never executed as root by diff --git a/docs/operations/CURRENT_STATE.md b/docs/operations/CURRENT_STATE.md index 4e2b2c5..7d058e7 100644 --- a/docs/operations/CURRENT_STATE.md +++ b/docs/operations/CURRENT_STATE.md @@ -97,9 +97,12 @@ UFW rule details, and protected file metadata remain unverified. head `3a89dc21`, CI run 63 failed at the host audit root guard and smoke run 64 succeeded. On head `436806a8`, CI run 65 failed in the Caddy container action with `exec: "validate": executable file not found`; Kaniko was skipped and - smoke run 66 succeeded. This PR pins explicit action entrypoints. No VPS - changes are made; successful image builds and fit within the configured 3 GiB - / 2 CPU per-job limits remain unverified. + smoke run 66 succeeded. On head `d96fc4d3`, CI run 67 failed because the + Caddy action's fixed `/github/workspace/...` path did not exist; Kaniko was + skipped and smoke run 68 succeeded. The current branch passes + `${{ github.workspace }}` to Caddy and Kaniko actions; a new runner run must + verify workspace resolution, image builds, and fit within the configured + 3 GiB / 2 CPU per-job limits. No VPS changes are made. - An isolated local Compose smoke test built backend/OpenSearch images, reached healthy status for OpenSearch, backend, and Caddy, and returned HTTP 200 for `/review` through Caddy. Its containers, networks, and volumes were @@ -152,6 +155,9 @@ Caddy validation and the two production Dockerfile builds. Kaniko image publishing and cache publishing are disabled. This repository change does not modify the VPS or grant workflow containers a Docker socket. CI run 65 failed in the Caddy action because the runner tried to execute `validate` directly; -Kaniko was skipped. This PR sets explicit action entrypoints. A successful run -must verify Caddy, both image builds, and resource fit before these actions are -considered validated runner behavior. +Kaniko was skipped. CI run 67 then found that the action's fixed +`/github/workspace` path did not contain the repository; Kaniko was skipped. +The branch now passes Gitea's `${{ github.workspace }}` value into all three +container action invocations. A successful run must verify Caddy, both image +builds, and resource fit before these actions are considered validated runner +behavior. diff --git a/infra/runner/test-ci-container-builds.sh b/infra/runner/test-ci-container-builds.sh index e388c87..4556c90 100644 --- a/infra/runner/test-ci-container-builds.sh +++ b/infra/runner/test-ci-container-builds.sh @@ -3,18 +3,30 @@ set -euo pipefail workflow=.gitea/workflows/ci.yaml kaniko_action=.gitea/actions/kaniko-build/action.yml +caddy_action=.gitea/actions/caddy-validate/action.yml grep -Fq 'runs-on: akyldash-ci' "$workflow" grep -Fq 'uses: ./.gitea/actions/caddy-validate' "$workflow" [[ $(grep -Fc 'uses: ./.gitea/actions/kaniko-build' "$workflow") -eq 2 ]] +check_workspace_wiring() { + local workflow_file=$1 caddy_file=${2:-$caddy_action} kaniko_file=${3:-$kaniko_action} + [[ $(grep -Fc 'workspace: ${{ github.workspace }}' "$workflow_file") -eq 3 ]] && + grep -Fxq ' - ${{ inputs.workspace }}/deploy/production/Caddyfile' "$caddy_file" && + grep -Fxq ' - --context=dir://${{ inputs.workspace }}' "$kaniko_file" && + grep -Fxq ' - --dockerfile=${{ inputs.workspace }}/${{ inputs.dockerfile }}' "$kaniko_file" && + ! grep -Fq '/github/workspace' "$caddy_file" "$kaniko_file" +} +if ! check_workspace_wiring "$workflow"; then + echo 'Container actions must receive github.workspace and use it for repository paths.' >&2 + exit 1 +fi grep -Fq 'dockerfile: deploy/production/Dockerfile.backend' "$workflow" grep -Fq 'dockerfile: deploy/production/Dockerfile.opensearch' "$workflow" grep -Fq 'docker compose --env-file deploy/production/.env.example -f deploy/production/compose.yaml config --quiet' "$workflow" -caddy_action=.gitea/actions/caddy-validate/action.yml grep -Fxq ' entrypoint: /usr/bin/caddy' "$caddy_action" grep -Fxq ' - validate' "$caddy_action" grep -Fxq ' - --config' "$caddy_action" -grep -Fxq ' - /github/workspace/deploy/production/Caddyfile' "$caddy_action" +grep -Fxq ' - ${{ inputs.workspace }}/deploy/production/Caddyfile' "$caddy_action" grep -Fxq ' - --adapter' "$caddy_action" grep -Fxq ' - caddyfile' "$caddy_action" if grep -Eq 'docker (build|run)([[:space:]]|$)' "$workflow"; then @@ -23,8 +35,8 @@ if grep -Eq 'docker (build|run)([[:space:]]|$)' "$workflow"; then fi check_kaniko_action() { grep -Fxq ' entrypoint: /kaniko/executor' "$1" && - grep -Fxq ' - --context=dir:///github/workspace' "$1" && - grep -Fxq ' - --dockerfile=/github/workspace/${{ inputs.dockerfile }}' "$1" && + grep -Fxq ' - --context=dir://${{ inputs.workspace }}' "$1" && + grep -Fxq ' - --dockerfile=${{ inputs.workspace }}/${{ inputs.dockerfile }}' "$1" && grep -Fxq ' - --destination=${{ inputs.destination }}' "$1" && grep -Fxq -- ' - --no-push' "$1" && grep -Fxq -- ' - --no-push-cache' "$1" @@ -35,7 +47,9 @@ if ! check_kaniko_action "$kaniko_action"; then fi without_entrypoint=$(mktemp) without_no_push=$(mktemp) -trap 'rm -f "$without_entrypoint" "$without_no_push"' EXIT +without_workspace=$(mktemp) +wrong_workspace=$(mktemp) +trap 'rm -f "$without_entrypoint" "$without_no_push" "$without_workspace" "$wrong_workspace"' EXIT sed '/^ entrypoint: \/kaniko\/executor$/d' "$kaniko_action" >"$without_entrypoint" if check_kaniko_action "$without_entrypoint"; then echo 'CI isolation test accepted a Kaniko action without its executor entrypoint.' >&2 @@ -46,6 +60,16 @@ if check_kaniko_action "$without_no_push"; then echo 'CI isolation test accepted --no-push-cache without standalone --no-push.' >&2 exit 1 fi +sed '/workspace: \${{ github.workspace }}/d' "$workflow" >"$without_workspace" +if check_workspace_wiring "$without_workspace"; then + echo 'CI isolation test accepted container actions without workflow workspace inputs.' >&2 + exit 1 +fi +sed 's#${{ inputs.workspace }}#/github/workspace#g' "$kaniko_action" >"$wrong_workspace" +if check_workspace_wiring "$workflow" "$caddy_action" "$wrong_workspace"; then + echo 'CI isolation test accepted a fixed Kaniko workspace root.' >&2 + exit 1 +fi grep -Fq 'FROM ghcr.io/osscontainertools/kaniko:v1.28.5-debug@sha256:c73ca67a3b494bce79a8fb6090c4f73c18997ab272d80bffc460a62343aeb412' .gitea/actions/kaniko-build/Dockerfile grep -Fq 'FROM caddy:2.11.6-alpine@sha256:881bbc60f9986d5ab8e7cfd6cf7e4ef3c9c0439fef2429d035d065577882f028' .gitea/actions/caddy-validate/Dockerfile if grep -REn 'docker.sock|privileged:|container.options:|^[[:space:]]*volumes:' "$workflow" .gitea/actions/kaniko-build .gitea/actions/caddy-validate; then @@ -53,4 +77,4 @@ if grep -REn 'docker.sock|privileged:|container.options:|^[[:space:]]*volumes:' exit 1 fi -echo 'PASS CI container operations use pinned isolated actions without socket or publish access' +echo 'PASS CI container actions use the supplied workspace and remain isolated without socket or publish access'