diff --git a/.github/workflows/integration-parity.yml b/.github/workflows/integration-parity.yml deleted file mode 100644 index 5605e43f..00000000 --- a/.github/workflows/integration-parity.yml +++ /dev/null @@ -1,240 +0,0 @@ -name: Integration runner parity - -on: - workflow_dispatch: - inputs: - mode: - description: Exact integration behavior to exercise - required: true - default: parity - type: choice - options: - - parity - - network-negative - - timeout - - cancellation - -permissions: - contents: read - -concurrency: - group: integration-runner-parity - cancel-in-progress: false - -jobs: - host-docker: - if: ${{ inputs.mode == 'parity' }} - name: VM-local Docker boundary - runs-on: nddev-linux-integration - timeout-minutes: 10 - steps: - - name: Validate integration image and daemon boundary - shell: bash - run: | - set -euo pipefail - [[ "${RUNNER_OS}" == Linux ]] - [[ "$(id -un)" == runner ]] - actual_groups="$(id --groups --name runner | tr ' ' '\n' | LC_ALL=C sort | paste -sd' ' -)" - if [[ "${actual_groups}" != "docker runner sudo" ]]; then - printf 'runner groups are %q, expected %q\n' \ - "${actual_groups}" "docker runner sudo" >&2 - exit 1 - fi - jq -e ' - .schema_version == 2 and - .runner_version == "v2.336.0" and - .runner_tool_cache == "/home/runner/actions-runner/_work/_tool" and - (.toolchains | keys) == ["bun", "go", "rust", "uv"] and - .image_variant == "integration" and - .docker_engine_version == "29.1.3" and - .docker_storage_driver == "overlay2" and - .docker_cgroup_driver == "systemd" and - .docker_action_base_ref == "nddev/gha-action-base:busybox-1-1.36.1-6ubuntu3.1" and - (.docker_action_base_id | test("^sha256:[0-9a-f]{64}$")) - ' /etc/nddev/image-build.json >/dev/null - test ! -e /dev/kvm - test ! -S /run/incus/unix.socket - test ! -S /var/lib/incus/unix.socket - test ! -S /var/snap/lxd/common/lxd/unix.socket - test -S /run/docker.sock - [[ "$(readlink -f /var/run/docker.sock)" == /run/docker.sock ]] - [[ "$(stat -c '%U:%G:%a' /run/docker.sock)" == root:docker:660 ]] - socket_mount="$(findmnt --json --target /run/docker.sock)" - [[ "$(jq -er '.filesystems | if length == 1 then .[0].target else error("ambiguous socket mount") end' <<<"${socket_mount}")" == /run ]] - [[ "$(jq -er '.filesystems[0].source' <<<"${socket_mount}")" == tmpfs ]] - [[ "$(jq -er '.filesystems[0].fstype' <<<"${socket_mount}")" == tmpfs ]] - systemctl is-active --quiet docker.service - systemctl is-active --quiet containerd.service - [[ "$(docker info --format '{{.Driver}}')" == overlay2 ]] - [[ "$(docker info --format '{{.CgroupDriver}}')" == systemd ]] - docker buildx version - docker compose version - expected_base_id="$(jq -er .docker_action_base_id /etc/nddev/image-build.json)" - actual_base_id="$(docker image inspect --format '{{.Id}}' \ - nddev/gha-action-base:busybox-1-1.36.1-6ubuntu3.1)" - [[ "${actual_base_id}" == "${expected_base_id}" ]] - test ! -e /tmp/nddev-integration-cross-job-sentinel - install -m 0600 /dev/null /tmp/nddev-integration-cross-job-sentinel - - local-docker-action: - if: ${{ inputs.mode == 'parity' }} - name: Local Docker container action - needs: host-docker - runs-on: nddev-linux-integration - timeout-minutes: 10 - steps: - - name: Prove fresh VM and exact preloaded action base - shell: bash - run: | - set -euo pipefail - test ! -e /tmp/nddev-integration-cross-job-sentinel - expected_base_id="$(jq -er .docker_action_base_id /etc/nddev/image-build.json)" - actual_base_id="$(docker image inspect --format '{{.Id}}' \ - nddev/gha-action-base:busybox-1-1.36.1-6ubuntu3.1)" - [[ "${actual_base_id}" == "${expected_base_id}" ]] - - - name: Check out source - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 1 - persist-credentials: false - - - name: Exercise local Docker action semantics - id: docker-action - uses: ./.github/actions/docker-parity - with: - marker: official-docker-action-ok - - - name: Verify Docker action output - shell: bash - env: - DOCKER_ACTION_OUTPUT: ${{ steps.docker-action.outputs.value }} - run: | - set -euo pipefail - [[ "${DOCKER_ACTION_OUTPUT}" == docker-official-docker-action-ok ]] - git diff --exit-code - git status --short - - job-and-service-containers: - if: ${{ inputs.mode == 'parity' }} - name: Job and service containers - needs: local-docker-action - runs-on: nddev-linux-integration - timeout-minutes: 10 - container: - image: ubuntu:24.04@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea - options: --user=1001:1002 --cap-drop=ALL --security-opt=no-new-privileges - services: - web: - image: nginx:1.29-alpine@sha256:5616878291a2eed594aee8db4dade5878cf7edcb475e59193904b198d9b830de - options: >- - --health-cmd "wget --quiet --output-document=- http://127.0.0.1/ >/dev/null" - --health-interval 2s - --health-timeout 2s - --health-retries 20 - steps: - - name: Validate job-container isolation - shell: bash - run: | - set -euo pipefail - [[ "${RUNNER_OS}" == Linux ]] - [[ "$(id -u)" == 1001 ]] - [[ "$(id -g)" == 1002 ]] - test -S /var/run/docker.sock - test -S /run/docker.sock - [[ "$(readlink -f /var/run/docker.sock)" == /run/docker.sock ]] - test ! -r /var/run/docker.sock - test ! -w /var/run/docker.sock - [[ "$(awk '/^CapEff:/{print $2}' /proc/self/status)" == 0000000000000000 ]] - [[ "$(awk '/^NoNewPrivs:/{print $2}' /proc/self/status)" == 1 ]] - getent hosts web >/dev/null - exec 3<>/dev/tcp/web/80 - printf 'GET / HTTP/1.0\r\nHost: web\r\n\r\n' >&3 - IFS=$'\r' read -r status <&3 - [[ "${status}" == 'HTTP/1.1 200 OK' || "${status}" == 'HTTP/1.0 200 OK' ]] - exec 3<&- - exec 3>&- - - - name: Exercise JavaScript action inside job container - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 1 - persist-credentials: false - - - name: Verify mounted workspace - shell: bash - run: | - set -euo pipefail - test -f README.md - - network-negative: - if: ${{ inputs.mode == 'network-negative' }} - name: VM and nested-container egress boundary - runs-on: nddev-linux-integration - timeout-minutes: 10 - steps: - - name: Require public egress and deny protected routes - shell: bash - run: | - set -euo pipefail - curl --fail --silent --show-error --head \ - --connect-timeout 5 --max-time 10 https://github.com/ >/dev/null - - expect_blocked() { - local address="$1" - local port="$2" - if timeout 3 bash -c "exec 3<>/dev/tcp/${address}/${port}" 2>/dev/null; then - printf 'protected endpoint %s:%s is reachable from the VM\n' \ - "${address}" "${port}" >&2 - exit 1 - fi - } - expect_blocked 192.0.2.1 22 - expect_blocked 192.0.2.1 8443 - expect_blocked 192.0.2.1 9997 - expect_blocked 169.254.169.254 80 - expect_blocked 10.0.0.1 443 - expect_blocked 172.16.0.1 443 - expect_blocked 192.168.0.1 443 - - for target in 192.0.2.1:8443 169.254.169.254:80 10.0.0.1:443; do - address="${target%:*}" - port="${target##*:}" - if docker run --rm --network bridge \ - nddev/gha-action-base:busybox-1-1.36.1-6ubuntu3.1 \ - /bin/busybox nc -z -w 2 "${address}" "${port}"; then - printf 'protected endpoint %s is reachable from a nested container\n' \ - "${target}" >&2 - exit 1 - fi - done - - timeout: - if: ${{ inputs.mode == 'timeout' }} - name: Bounded job timeout - runs-on: nddev-linux-integration - timeout-minutes: 1 - steps: - - name: Wait for runner-enforced timeout - shell: bash - run: | - set -euo pipefail - trap 'printf "timeout signal received\n"; exit 0' TERM INT - printf 'integration timeout proof is ready\n' - sleep 180 & - wait $! - - cancellation: - if: ${{ inputs.mode == 'cancellation' }} - name: In-job cancellation - runs-on: nddev-linux-integration - timeout-minutes: 10 - steps: - - name: Await cancellation signal - shell: bash - run: | - set -euo pipefail - trap 'printf "cancellation signal received\n"; exit 0' TERM INT - printf 'integration cancellation proof is ready\n' - sleep 300 & - wait $! diff --git a/.github/workflows/representative-benchmark.yml b/.github/workflows/representative-benchmark.yml index 7cc2e517..d75ffa31 100644 --- a/.github/workflows/representative-benchmark.yml +++ b/.github/workflows/representative-benchmark.yml @@ -10,7 +10,6 @@ on: type: choice options: - github-hosted - - nddev cache_mode: description: Dependency and build cache condition required: true @@ -48,7 +47,7 @@ jobs: go: name: Go build and test if: ${{ inputs.workload == 'all' || inputs.workload == 'go' }} - runs-on: ${{ inputs.environment == 'github-hosted' && 'ubuntu-24.04' || 'nddev-linux-standard' }} + runs-on: ubuntu-24.04 timeout-minutes: 30 env: NDDEV_BENCHMARK_WORKLOAD: go @@ -123,7 +122,7 @@ jobs: rust: name: Rust build and test if: ${{ inputs.workload == 'all' || inputs.workload == 'rust' }} - runs-on: ${{ inputs.environment == 'github-hosted' && 'ubuntu-24.04' || 'nddev-linux-standard' }} + runs-on: ubuntu-24.04 timeout-minutes: 30 env: NDDEV_BENCHMARK_WORKLOAD: rust @@ -154,7 +153,7 @@ jobs: run: scripts/configure-sccache.sh benchmark/rust/Cargo.lock rustc-1.97.1 benchmark - name: Restore dependency cache - if: ${{ inputs.environment == 'github-hosted' && inputs.cache_mode == 'warm' }} + if: ${{ inputs.cache_mode == 'warm' }} id: dependency-cache uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: @@ -226,7 +225,7 @@ jobs: python-uv: name: Python and uv build and test if: ${{ inputs.workload == 'all' || inputs.workload == 'python-uv' }} - runs-on: ${{ inputs.environment == 'github-hosted' && 'ubuntu-24.04' || 'nddev-linux-standard' }} + runs-on: ubuntu-24.04 timeout-minutes: 30 env: NDDEV_BENCHMARK_WORKLOAD: python-uv @@ -303,7 +302,7 @@ jobs: bun-next: name: Bun and Next build and test if: ${{ inputs.workload == 'all' || inputs.workload == 'bun-next' }} - runs-on: ${{ inputs.environment == 'github-hosted' && 'ubuntu-24.04' || 'nddev-linux-standard' }} + runs-on: ubuntu-24.04 timeout-minutes: 30 env: NDDEV_BENCHMARK_WORKLOAD: bun-next @@ -378,7 +377,7 @@ jobs: docker: name: Docker and Compose integration if: ${{ inputs.workload == 'all' || inputs.workload == 'docker' }} - runs-on: ${{ inputs.environment == 'github-hosted' && 'ubuntu-24.04' || 'nddev-linux-integration' }} + runs-on: ubuntu-24.04 timeout-minutes: 30 env: NDDEV_BENCHMARK_WORKLOAD: docker diff --git a/.github/workflows/self-hosted-canary.yml b/.github/workflows/self-hosted-canary.yml deleted file mode 100644 index 787c394a..00000000 --- a/.github/workflows/self-hosted-canary.yml +++ /dev/null @@ -1,323 +0,0 @@ -name: Self-hosted canary - -on: - workflow_dispatch: - inputs: - runner_label: - description: Exact manual-JIT label or GARM Scale Set name - required: true - default: nddev-canary - type: choice - options: - - nddev-canary - - nddev-linux-standard - # Served by gha-runner-2, the Docker-capable failure domain. A scale - # set name is unique per repository, so each host serves a different - # one and this choice also selects which host is exercised. - - nddev-linux-integration - # The class most of this estate's jobs actually need: a shell, no job - # credential and no Docker. It was unreachable from here for as long - # as it was unreachable everywhere else. - - nddev-linux-fast - mode: - description: Canary behavior to exercise - required: true - default: basic - type: choice - options: - - basic - - reconciliation - - cancellation - -permissions: - contents: read - -concurrency: - group: self-hosted-canary - cancel-in-progress: false - -jobs: - canary: - name: Official runner one-job canary - runs-on: ${{ inputs.runner_label }} - timeout-minutes: 10 - env: - NDDEV_CANARY_RUNNER_LABEL: ${{ inputs.runner_label }} - steps: - - name: Validate immutable worker boundary - shell: bash - run: | - set -euo pipefail - [[ "${RUNNER_OS}" == Linux ]] - [[ "$(id -un)" == runner ]] - case "${NDDEV_CANARY_RUNNER_LABEL}" in - nddev-linux-standard) expected_groups="runner sudo" ;; - nddev-linux-integration) expected_groups="docker runner sudo" ;; - *) expected_groups="" ;; - esac - if [[ -n "${expected_groups}" ]]; then - actual_groups="$(id --groups --name runner | tr ' ' '\n' | LC_ALL=C sort | paste -sd' ' -)" - if [[ "${actual_groups}" != "${expected_groups}" ]]; then - printf 'runner groups are %q, expected %q\n' "${actual_groups}" "${expected_groups}" >&2 - exit 1 - fi - fi - test -r /etc/nddev/image-build.json - jq -e ' - .schema_version == 2 and - .runner_version == "v2.336.0" and - (.manifest_fingerprint | test("^sha256:[0-9a-f]{64}$")) and - (.recipe_fingerprint | test("^sha256:[0-9a-f]{64}$")) and - (.package_manifest_sha256 | test("^[0-9a-f]{64}$")) and - .runner_tool_cache == "/home/runner/actions-runner/_work/_tool" and - (.toolchains | keys) == ["bun", "go", "rust", "uv"] and - (.toolchains | to_entries | all( - (.value.version | test("^[0-9]+[.][0-9]+[.][0-9]+$")) and - (.value.archive_sha256 | test("^[0-9a-f]{64}$")))) - ' \ - /etc/nddev/image-build.json >/dev/null - go_version="$(jq -er .toolchains.go.version /etc/nddev/image-build.json)" - test -f "/home/runner/actions-runner/_work/_tool/go/${go_version}/x64.complete" - [[ "$(rustc --version)" == "rustc $(jq -er .toolchains.rust.version /etc/nddev/image-build.json) "* ]] - [[ "$(uv --version)" == "uv $(jq -er .toolchains.uv.version /etc/nddev/image-build.json)"* ]] - [[ "$(bun --version)" == "$(jq -er .toolchains.bun.version /etc/nddev/image-build.json)" ]] - [[ "$(cat /etc/machine-id)" =~ ^[0-9a-f]{32}$ ]] - # The property being defended is that the *host's* Docker socket never - # reaches a worker. On the standard flavour that means no socket at - # all. The Docker-capable flavour runs its own daemon inside the VM, - # which is the entire point of it, so there the socket must exist, be - # served by the guest's own dockerd, and not be a mount from outside: - # a passed-in host socket would show up as a mountpoint at this path. - if [[ "${NDDEV_CANARY_RUNNER_LABEL}" == nddev-linux-integration ]]; then - test -S /var/run/docker.sock - systemctl is-active --quiet docker - if findmnt --noheadings --output SOURCE /var/run/docker.sock 2>/dev/null | grep -q .; then - printf 'docker socket is mounted in from outside the VM\n' >&2 - exit 1 - fi - else - test ! -S /var/run/docker.sock - fi - test ! -S /run/incus/unix.socket - test ! -S /var/lib/incus/unix.socket - test ! -S /var/snap/lxd/common/lxd/unix.socket - test ! -e /dev/kvm - if dpkg -s openssh-server >/dev/null 2>&1; then - printf 'OpenSSH server package is installed\n' >&2 - exit 1 - fi - for unit in ssh.service ssh.socket sshd.service sshd.socket; do - [[ "$(systemctl is-enabled "${unit}" 2>/dev/null || true)" == masked ]] - if systemctl is-active --quiet "${unit}"; then - printf 'SSH unit %s is active\n' "${unit}" >&2 - exit 1 - fi - done - if ss -H -lnt 'sport = :22' | grep -q .; then - printf 'unexpected SSH listener\n' >&2 - exit 1 - fi - if find /opt/cache/actions-runner -type f \ - \( -name .runner -o -name .credentials -o -name .credentials_rsaparams -o -name .service \) \ - -print -quit | grep -q .; then - printf 'runner registration state leaked into immutable cache\n' >&2 - exit 1 - fi - test ! -e /tmp/nddev-runner-canary-sentinel - install -m 0600 /dev/null /tmp/nddev-runner-canary-sentinel - - - name: Check out source - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 1 - persist-credentials: false - - - name: Verify one-job RustFS cache delivery - if: ${{ inputs.runner_label == 'nddev-linux-standard' }} - shell: bash - run: | - set -euo pipefail - set +x - - [[ "${NDDEV_CACHE_ROLE:-}" == trusted-writer ]] - [[ "${NDDEV_CACHE_MODE:-}" == read-write ]] - [[ "${NDDEV_CACHE_PREFIX_ROOT:-}" == NDDev-OpenNetwork/github-actions/trust/trusted ]] - [[ "${NDDEV_CACHE_DELIVERY_ID:-}" =~ ^[0-9a-f]{64}$ ]] - [[ "${AWS_ACCESS_KEY_ID:-}" =~ ^AKIA[0-9A-F]{16}$ ]] - [[ "${AWS_SECRET_ACCESS_KEY:-}" =~ ^[A-Za-z0-9_-]{64}$ ]] - [[ "${AWS_REGION:-}" == us-east-1 ]] - [[ "${AWS_DEFAULT_REGION:-}" == us-east-1 ]] - [[ "${SCCACHE_BUCKET:-}" == github-actions-cache ]] - [[ "${SCCACHE_ENDPOINT:-}" == https://192.0.2.1:9002 ]] - [[ "${SCCACHE_REGION:-}" == us-east-1 ]] - [[ "${SCCACHE_S3_USE_SSL:-}" == true ]] - [[ -z "${SCCACHE_S3_KEY_PREFIX:-}" ]] - printf 'cache-canary-stage=environment\n' - - delivery_root=/home/runner/.gha-cache - for removed in provider-assignment.json provider-assignment.ready; do - if [[ -e "${delivery_root}/${removed}" || -L "${delivery_root}/${removed}" ]]; then - printf 'cache delivery staging file remains: %s\n' "${removed}" >&2 - exit 1 - fi - done - consumed="${delivery_root}/provider-assignment.consumed" - if [[ -L "${consumed}" || ! -f "${consumed}" ]]; then - printf 'cache delivery consumed marker is absent or not a regular file\n' >&2 - exit 1 - fi - consumed_metadata="$(stat --format='%u:%g:%a:%h:%F' -- "${consumed}")" - if [[ "${consumed_metadata}" != '1001:1002:600:1:regular file' ]]; then - printf 'cache delivery consumed marker metadata is %q\n' "${consumed_metadata}" >&2 - exit 1 - fi - if [[ "$(<"${consumed}")" != "${NDDEV_CACHE_DELIVERY_ID}" ]]; then - printf 'cache delivery consumed marker is not bound to this delivery ID\n' >&2 - exit 1 - fi - printf 'cache-canary-stage=consumed-assignment\n' - - cache_ca=/home/runner/.gha-cache/rustfs-ca.pem - cache_ca_bundle=/home/runner/.gha-cache/rustfs-ca-bundle.pem - test ! -L "${cache_ca}" - test -r "${cache_ca}" - [[ "$(stat --format='%u:%g:%a:%h:%F' -- "${cache_ca}")" == "$(id -u):$(id -g):400:1:regular file" ]] - openssl x509 -in "${cache_ca}" -noout -checkend 86400 >/dev/null - openssl verify -CAfile "${cache_ca}" "${cache_ca}" >/dev/null - [[ "$(openssl x509 -in "${cache_ca}" -noout -subject)" == 'subject=CN = NDDev-GHA-Cache-CA' ]] - test ! -L "${cache_ca_bundle}" - test -r "${cache_ca_bundle}" - [[ "$(stat --format='%u:%g:%a:%h:%F' -- "${cache_ca_bundle}")" == "$(id -u):$(id -g):400:1:regular file" ]] - [[ "${SSL_CERT_FILE:-}" == "${cache_ca_bundle}" ]] - [[ "${CURL_CA_BUNDLE:-}" == "${cache_ca_bundle}" ]] - [[ "${AWS_CA_BUNDLE:-}" == "${cache_ca_bundle}" ]] - openssl verify -CAfile "${cache_ca_bundle}" "${cache_ca}" >/dev/null - printf 'cache-canary-stage=trust-anchor\n' - - [[ "${RUNNER_ARCH}" == X64 ]] - lock_digest="$(sha256sum go.sum | awk '{print $1}')" - [[ "${lock_digest}" =~ ^[0-9a-f]{64}$ ]] - object_key="${NDDEV_CACHE_PREFIX_ROOT}/linux/amd64/curl-sigv4-v1/${lock_digest}/canary/cache-delivery-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}.txt" - cross_key="NDDev-OpenNetwork/github-actions/trust/untrusted/linux/amd64/curl-sigv4-v1/${lock_digest}/canary/cache-delivery-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}.txt" - object_url="${SCCACHE_ENDPOINT}/${SCCACHE_BUCKET}/${object_key}" - cross_url="${SCCACHE_ENDPOINT}/${SCCACHE_BUCKET}/${cross_key}" - payload="${RUNNER_TEMP}/cache-delivery-payload" - downloaded="${RUNNER_TEMP}/cache-delivery-downloaded" - response="${RUNNER_TEMP}/cache-delivery-response" - curl_config="${RUNNER_TEMP}/cache-delivery-curl.conf" - printf 'nddev-cache-delivery-v1:%s:%s\n' "${GITHUB_RUN_ID}" "${GITHUB_RUN_ATTEMPT}" >"${payload}" - printf 'user = "%s:%s"\n' "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}" >"${curl_config}" - chmod 0600 "${curl_config}" - - require_status() { - local operation="$1" - local expected="$2" - local actual="$3" - if [[ "${actual}" != "${expected}" ]]; then - printf '%s returned HTTP %s, expected %s\n' "${operation}" "${actual}" "${expected}" >&2 - exit 1 - fi - } - - signed_request=( - --config "${curl_config}" - --silent --show-error --connect-timeout 5 --max-time 30 - --cacert "${cache_ca}" - --aws-sigv4 "aws:amz:${AWS_REGION}:s3" - ) - put_code="$(curl "${signed_request[@]}" --output "${response}" --write-out '%{http_code}' --request PUT --data-binary "@${payload}" "${object_url}")" - require_status own-prefix-put 200 "${put_code}" - get_code="$(curl "${signed_request[@]}" --output "${downloaded}" --write-out '%{http_code}' "${object_url}")" - require_status own-prefix-get 200 "${get_code}" - cmp --silent "${payload}" "${downloaded}" - cross_code="$(curl "${signed_request[@]}" --output "${response}" --write-out '%{http_code}' --request PUT --data-binary "@${payload}" "${cross_url}")" - require_status cross-trust-put 403 "${cross_code}" - delete_code="$(curl "${signed_request[@]}" --output "${response}" --write-out '%{http_code}' --request DELETE "${object_url}")" - require_status own-prefix-delete 403 "${delete_code}" - - printf 'RustFS one-job delivery, own-prefix read/write, cross-trust denial and delete denial passed.\n' - - - name: Exercise composite action semantics - id: composite - uses: ./.github/actions/canary-composite - with: - marker: official-runner-ok - - - name: Exercise command files and annotations - id: protocol - shell: bash - run: | - set -euo pipefail - printf 'value=official-runner-ok\n' >>"${GITHUB_OUTPUT}" - printf 'NDDEV_CANARY_ENV=command-file-ok\n' >>"${GITHUB_ENV}" - printf '### NDDev one-job canary\n\n' >>"${GITHUB_STEP_SUMMARY}" - printf -- "- commit: \`%s\`\n- image: immutable golden VM\n" \ - "${GITHUB_SHA}" >>"${GITHUB_STEP_SUMMARY}" - printf '::notice title=NDDev canary::official runner command files are operational\n' - - - name: Verify step output and environment propagation - shell: bash - env: - PROTOCOL_OUTPUT: ${{ steps.protocol.outputs.value }} - COMPOSITE_OUTPUT: ${{ steps.composite.outputs.value }} - run: | - set -euo pipefail - [[ "${PROTOCOL_OUTPUT}" == official-runner-ok ]] - [[ "${COMPOSITE_OUTPUT}" == composite-official-runner-ok ]] - [[ "${NDDEV_CANARY_ENV}" == command-file-ok ]] - git diff --exit-code - git status --short - - - name: Hold runner across reconciliation windows - if: ${{ inputs.mode == 'reconciliation' }} - shell: bash - run: | - set -euo pipefail - printf 'holding official runner identity across reconciliation windows\n' - sleep 45 - - - name: Build non-secret diagnostic artifact - if: ${{ inputs.mode != 'cancellation' }} - shell: bash - run: | - set -euo pipefail - install -d -m 0700 "${RUNNER_TEMP}/nddev-canary" - jq -n \ - --slurpfile image /etc/nddev/image-build.json \ - --arg commit "${GITHUB_SHA}" \ - --arg runner_os "${RUNNER_OS}" \ - --arg runner_arch "${RUNNER_ARCH}" \ - --arg machine_id "$(cat /etc/machine-id)" \ - --arg composite_output "${{ steps.composite.outputs.value }}" \ - '{ - schema_version: 2, - commit: $commit, - runner_os: $runner_os, - runner_arch: $runner_arch, - machine_id: $machine_id, - runner_version: $image[0].runner_version, - manifest_fingerprint: $image[0].manifest_fingerprint, - recipe_fingerprint: $image[0].recipe_fingerprint, - package_manifest_sha256: $image[0].package_manifest_sha256, - composite_output: $composite_output - }' \ - >"${RUNNER_TEMP}/nddev-canary/result.json" - - - name: Upload canary artifact - if: ${{ inputs.mode != 'cancellation' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: nddev-self-hosted-canary-${{ github.run_id }} - path: ${{ runner.temp }}/nddev-canary/result.json - if-no-files-found: error - retention-days: 1 - - - name: Await cancellation signal - if: ${{ inputs.mode == 'cancellation' }} - shell: bash - run: | - set -euo pipefail - trap 'printf "cancellation signal received\n"; exit 0' TERM INT - printf 'canary is ready for cancellation\n' - sleep 300 & - wait $! diff --git a/internal/benchmarkcontract/contract_test.go b/internal/benchmarkcontract/contract_test.go index a7490aca..77d213d3 100644 --- a/internal/benchmarkcontract/contract_test.go +++ b/internal/benchmarkcontract/contract_test.go @@ -80,9 +80,6 @@ func TestRepresentativeWorkflowIsManualPinnedAndLeastPrivilege(t *testing.T) { if len(workflow.Jobs) != len(expectedWorkloads) { t.Fatalf("expected exactly five representative jobs, got %d", len(workflow.Jobs)) } - standardLabel := "${{ inputs.environment == 'github-hosted' && 'ubuntu-24.04' || 'nddev-linux-standard' }}" - integrationLabel := "${{ inputs.environment == 'github-hosted' && 'ubuntu-24.04' || 'nddev-linux-integration' }}" - usesCount := make(map[string]int) for jobID, workload := range expectedWorkloads { job, exists := workflow.Jobs[jobID] @@ -90,11 +87,7 @@ func TestRepresentativeWorkflowIsManualPinnedAndLeastPrivilege(t *testing.T) { t.Errorf("workflow is missing %s job", jobID) continue } - expectedLabel := standardLabel - if jobID == "docker" { - expectedLabel = integrationLabel - } - if job.RunsOn != expectedLabel || job.TimeoutMinutes != 30 { + if job.RunsOn != "ubuntu-24.04" || job.TimeoutMinutes != 30 { t.Errorf("%s runner/timeout contract drifted: %#v", jobID, job) } expectedSelector := "${{ inputs.workload == 'all' || inputs.workload == '" + jobID + "' }}" @@ -130,9 +123,6 @@ func TestRepresentativeWorkflowIsManualPinnedAndLeastPrivilege(t *testing.T) { } cache := stepsByName["Restore dependency cache"] expectedCacheCondition := "${{ inputs.cache_mode == 'warm' }}" - if jobID == "rust" { - expectedCacheCondition = "${{ inputs.environment == 'github-hosted' && inputs.cache_mode == 'warm' }}" - } if cache.If != expectedCacheCondition { t.Errorf("%s can restore/save a cache during a cold sample: %q", jobID, cache.If) } diff --git a/internal/cachenamespace/binding_test.go b/internal/cachenamespace/binding_test.go index 795dbed6..0bb06363 100644 --- a/internal/cachenamespace/binding_test.go +++ b/internal/cachenamespace/binding_test.go @@ -1,8 +1,6 @@ package cachenamespace_test import ( - "os" - "strings" "testing" "github.com/NDDev-OpenNetwork/github-actions/internal/cachenamespace" @@ -35,44 +33,3 @@ func TestIdentityManifestUsesTheNamespaceThePackageBuilds(t *testing.T) { } } } - -// The canary is the only end-to-end proof that a credential can write its own -// namespace and cannot write another's. It writes both keys as text, so a -// namespace change that missed it would leave the canary proving isolation -// between two namespaces nobody uses. -func TestCanaryProvesIsolationBetweenNamespacesThePackageBuilds(t *testing.T) { - t.Parallel() - raw, err := os.ReadFile(repositoryRoot + "/.github/workflows/self-hosted-canary.yml") - if err != nil { - t.Fatal(err) - } - workflow := string(raw) - - trusted := cachenamespace.MustPrefixRoot(cachenamespace.Trusted) - untrusted := cachenamespace.MustPrefixRoot(cachenamespace.Untrusted) - if !strings.Contains(workflow, trusted) { - t.Errorf("the canary does not assert its own namespace %q", trusted) - } - // The cross-namespace key it must be refused. - if !strings.Contains(workflow, untrusted+"/") { - t.Errorf("the canary does not attempt the counterpart namespace %q", untrusted) - } - // Any trust-scoped path in the workflow has to be one this package builds. - for _, field := range strings.Fields(workflow) { - index := strings.Index(field, cachenamespace.Organization+"/"+cachenamespace.Repository+"/trust/") - if index < 0 { - continue - } - candidate := strings.Trim(field[index:], `"'`) - matched := false - for _, root := range cachenamespace.PrefixRoots() { - if strings.HasPrefix(candidate, root) { - matched = true - break - } - } - if !matched { - t.Errorf("the canary names trust-scoped path %q, which no namespace this package builds covers", candidate) - } - } -} diff --git a/internal/deploycontract/contract_test.go b/internal/deploycontract/contract_test.go index 29911194..1d81cecd 100644 --- a/internal/deploycontract/contract_test.go +++ b/internal/deploycontract/contract_test.go @@ -2,7 +2,6 @@ package deploycontract import ( "encoding/json" - "fmt" "os" "path/filepath" "slices" @@ -696,97 +695,6 @@ func TestCacheSystemUsersAreDedicated(t *testing.T) { } } -func TestIntegrationParityWorkflowRetainsDockerAndNetworkBoundary(t *testing.T) { - workflowPath := "../../.github/workflows/integration-parity.yml" - workflowData, err := os.ReadFile(workflowPath) - if err != nil { - t.Fatal(err) - } - workflow := string(workflowData) - var providerConfig providerconfig.Incus - if _, err := toml.Decode(read(t, "provider-incus.toml"), &providerConfig); err != nil { - t.Fatal(err) - } - integrationImage, exists := providerConfig.WorkerImageForFlavor("nddev-linux-integration") - if !exists { - t.Fatal("integration worker image is not configured") - } - runnerIdentity := fmt.Sprintf("%d:%d", integrationImage.RunnerUID, integrationImage.RunnerGID) - for _, required := range []string{ - "workflow_dispatch:", "cancel-in-progress: false", "mode == 'parity'", - "mode == 'network-negative'", "mode == 'timeout'", "mode == 'cancellation'", - "needs: host-docker", "needs: local-docker-action", - "ubuntu:24.04@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea", - "nginx:1.29-alpine@sha256:5616878291a2eed594aee8db4dade5878cf7edcb475e59193904b198d9b830de", - "--user=" + runnerIdentity + " --cap-drop=ALL --security-opt=no-new-privileges", - fmt.Sprintf(`[[ "$(id -u)" == %d ]]`, integrationImage.RunnerUID), - fmt.Sprintf(`[[ "$(id -g)" == %d ]]`, integrationImage.RunnerGID), - "test -S /var/run/docker.sock", "test -S /run/docker.sock", - `[[ "$(readlink -f /var/run/docker.sock)" == /run/docker.sock ]]`, - "test ! -r /var/run/docker.sock", "test ! -w /var/run/docker.sock", - "expect_blocked 192.0.2.1 8443", "expect_blocked 192.0.2.1 9997", - "expect_blocked 169.254.169.254 80", "timeout-minutes: 1", - } { - if !strings.Contains(workflow, required) { - t.Errorf("integration parity workflow is missing %q", required) - } - } - if strings.Count(workflow, "runs-on: nddev-linux-integration") != 6 { - t.Fatal("every integration parity job must use the exact integration Scale Set") - } - for _, forbidden := range []string{ - "ubuntu-latest", "--privileged", "--network=host", "network: host", - "/var/run/docker.sock:/var/run/docker.sock", "--volume /var/run/docker.sock", - } { - if strings.Contains(workflow, forbidden) { - t.Errorf("integration parity workflow contains forbidden %q", forbidden) - } - } - - actionPath := "../../.github/actions/docker-parity/action.yml" - actionData, err := os.ReadFile(actionPath) - if err != nil { - t.Fatal(err) - } - action := string(actionData) - if !strings.Contains(action, "using: docker") || !strings.Contains(action, "image: Dockerfile") || - !strings.Contains(action, "args:") || !strings.Contains(action, "${{ inputs.marker }}") || - !strings.Contains(action, "value:") { - t.Fatal("local Docker action metadata is incomplete") - } - dockerfileData, err := os.ReadFile("../../.github/actions/docker-parity/Dockerfile") - if err != nil { - t.Fatal(err) - } - dockerfile := string(dockerfileData) - if !strings.Contains(dockerfile, "FROM nddev/gha-action-base:busybox-1-1.36.1-6ubuntu3.1") || - !strings.Contains(dockerfile, `ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]`) || - strings.Contains(dockerfile, "RUN ") || strings.Contains(dockerfile, "ADD http") { - t.Fatal("local Docker action must use only the preloaded sealed base") - } - entrypointPath := "../../.github/actions/docker-parity/entrypoint.sh" - entrypointData, err := os.ReadFile(entrypointPath) - if err != nil { - t.Fatal(err) - } - entrypoint := string(entrypointData) - for _, required := range []string{ - "if [ ! -S /var/run/docker.sock ]", "if [ -S /run/docker.sock ]", - "official runner did not mount the VM-local Docker socket", "GITHUB_OUTPUT", "GITHUB_STEP_SUMMARY", - } { - if !strings.Contains(entrypoint, required) { - t.Errorf("local Docker action entrypoint is missing %q", required) - } - } - info, err := os.Stat(entrypointPath) - if err != nil { - t.Fatal(err) - } - if info.Mode().Perm()&0o111 == 0 { - t.Fatal("local Docker action entrypoint is not executable") - } -} - func containsAll(values []string, wanted ...string) bool { set := make(map[string]struct{}, len(values)) for _, value := range values { diff --git a/internal/repositorycontract/canary_test.go b/internal/repositorycontract/canary_test.go deleted file mode 100644 index 1447c712..00000000 --- a/internal/repositorycontract/canary_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package repositorycontract - -import ( - "os" - "slices" - "strings" - "testing" - - "github.com/NDDev-OpenNetwork/github-actions/internal/garmbootstrap" - "gopkg.in/yaml.v3" -) - -const canaryWorkflowPath = repositoryRoot + "/.github/workflows/self-hosted-canary.yml" - -type canaryWorkflow struct { - On struct { - WorkflowDispatch struct { - Inputs map[string]struct { - Type string `yaml:"type"` - Default string `yaml:"default"` - Options []string `yaml:"options"` - } `yaml:"inputs"` - } `yaml:"workflow_dispatch"` - } `yaml:"on"` -} - -// The canary is the only way to put a real job through a class before a -// consumer does, and it can only exercise a class its dispatch input offers. A -// class that is published and missing here is a class whose first real job is -// the proof, which is the opposite of what a canary is for -- nddev-linux-fast -// sat published and unofferable while it was also unbuildable everywhere. -// -// The list is held to garmbootstrap.PublishedScaleSets rather than to a second -// copy of the names, so registering a new class and forgetting to canary it -// fails here instead of in production. -func TestCanaryCanReachEveryPublishedClass(t *testing.T) { - t.Parallel() - raw, err := os.ReadFile(canaryWorkflowPath) - if err != nil { - t.Fatal(err) - } - var workflow canaryWorkflow - if err := yaml.Unmarshal(raw, &workflow); err != nil { - t.Fatal(err) - } - input, exists := workflow.On.WorkflowDispatch.Inputs["runner_label"] - if !exists { - t.Fatal("the canary no longer takes a runner_label input") - } - if input.Type != "choice" { - t.Fatalf("runner_label is a %q input; a free-text label cannot be held to the published set", input.Type) - } - for _, class := range garmbootstrap.PublishedScaleSets() { - if !slices.Contains(input.Options, class.Name) { - t.Fatalf("scale set %q is published and the canary cannot select it", class.Name) - } - } - // The manual-JIT label is deliberately not a scale set and stays offered. - for _, option := range input.Options { - if option == "nddev-canary" { - continue - } - if !slices.ContainsFunc(garmbootstrap.PublishedScaleSets(), func(c garmbootstrap.ScaleSetClass) bool { - return c.Name == option - }) { - t.Fatalf("the canary offers %q, which no published class serves", option) - } - } - if !strings.HasPrefix(input.Default, "nddev-") { - t.Fatalf("the canary defaults to %q", input.Default) - } -} diff --git a/internal/repositorycontract/public_routing_test.go b/internal/repositorycontract/public_routing_test.go new file mode 100644 index 00000000..63cc25b3 --- /dev/null +++ b/internal/repositorycontract/public_routing_test.go @@ -0,0 +1,31 @@ +package repositorycontract + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestPublicRepositoryHasNoPrivateFleetRouting(t *testing.T) { + t.Parallel() + paths, err := filepath.Glob(repositoryRoot + "/.github/workflows/*.yml") + if err != nil { + t.Fatal(err) + } + if len(paths) == 0 { + t.Fatal("public repository has no workflows to validate") + } + for _, path := range paths { + raw, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + lower := strings.ToLower(string(raw)) + for _, forbidden := range []string{"self-hosted", "nddev-linux", "amsterdam"} { + if strings.Contains(lower, forbidden) { + t.Errorf("public workflow %s contains private runner identity %q", filepath.Base(path), forbidden) + } + } + } +}