diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5418669a..44315fed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,6 +142,10 @@ jobs: with: add-rust-environment-hash-key: false save-if: ${{ github.ref == 'refs/heads/main' }} + # One cache per leg: they build different things, and a shared key + # let the quicker 5.10 leg save first, so the runner leg's debug + # build for the live tests was never cached. + key: ${{ matrix.kernel }} - name: Download eBPF artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -390,26 +394,11 @@ jobs: test_binary="$(cargo test --locked --lib --no-run --message-format=json | jq -r 'select(.reason == "compiler-artifact" and .target.kind == ["lib"] and .executable != null) | .executable')" sudo "$test_binary" integration_group_ --ignored --test-threads=1 - atomic: - name: ${{ matrix.shard == '1/1' && format('Atomic ({0})', matrix.platform) || format('Atomic ({0} shard {1})', matrix.platform, matrix.shard) }} + atomic-linux: + name: Atomic (linux) if: ${{ !inputs.skip_atomics }} - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest needs: ebpf-build - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - platform: linux - shard: 1/1 - # Windows runs its ~75 atomics sequentially and is the long pole, so - # it is split across two runners. - - os: windows-latest - platform: windows - shard: 1/2 - - os: windows-latest - platform: windows - shard: 2/2 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: @@ -426,23 +415,14 @@ jobs: save-if: ${{ github.ref == 'refs/heads/main' }} - name: Download eBPF artifact - if: matrix.platform == 'linux' uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: rustinel-ebpf-object path: ebpf/ - # Each shard builds its own binary. With a warm cache this is quicker - # than a shared build job plus an artifact hop, and the Linux leg does - # not wait on the Windows build. - name: Build PR binary run: cargo build --locked --release - - name: Run native capture contract (Windows) - if: matrix.platform == 'windows' && matrix.shard == '1/2' - run: python tests\native_capture_contract.py --binary "$env:GITHUB_WORKSPACE\target\release\rustinel.exe" - shell: pwsh - - name: Read shared atomic rules pin id: rules-pin shell: bash @@ -479,13 +459,7 @@ jobs: working-directory: rustinel-rules run: python tests/atomic/run_atomics.py --check-coverage --strict-essential - - name: Exclude workspace from Defender (Windows, for EICAR) - if: matrix.platform == 'windows' - run: Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE" -ErrorAction SilentlyContinue - shell: pwsh - - name: Run atomic tests (Linux) - if: matrix.platform == 'linux' working-directory: rustinel-rules run: | # The hosted runner leaves /opt writable by other accounts. @@ -497,12 +471,113 @@ jobs: --engine-bin "$GITHUB_WORKSPACE/scripts/atomic_managed_service.sh" \ --engine-dir /opt/rustinel \ --rules-dir "$GITHUB_WORKSPACE/rustinel-rules" \ - --pack auto \ - --shard "${{ matrix.shard }}" + --pack auto shell: bash + - name: Prepare atomic logs for upload (Unix) + if: always() + run: | + if [ -d /opt/rustinel/logs ]; then + sudo cp -a /opt/rustinel/logs rustinel-rules/tests/atomic/managed-logs + sudo chown -R "$(id -u):$(id -g)" rustinel-rules/tests/atomic/managed-logs + fi + if [ -f /opt/rustinel/engine.stdout.log ]; then + sudo cp /opt/rustinel/engine.stdout.log rustinel-rules/tests/atomic/engine.stdout.log + sudo chown "$(id -u):$(id -g)" rustinel-rules/tests/atomic/engine.stdout.log + fi + shell: bash + + - name: Upload atomic report and logs + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: atomic-linux + path: | + rustinel-rules/tests/atomic/report-*.json + rustinel-rules/tests/atomic/.engine/engine.stdout.log + rustinel-rules/tests/atomic/.engine/logs/ + rustinel-rules/tests/atomic/engine.stdout.log + rustinel-rules/tests/atomic/managed-logs/ + include-hidden-files: true + if-no-files-found: warn + + # Windows needs no eBPF object, so it starts at once instead of waiting on + # ebpf-build. Its ~75 atomics run sequentially and are the long pole, so + # they are split across three runners. + atomic: + name: Atomic (windows shard ${{ matrix.shard }}) + if: ${{ !inputs.skip_atomics }} + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + shard: [1/3, 2/3, 3/3] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + + - uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2.0.0 + with: + cache: false + rustflags: "" + + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + with: + add-rust-environment-hash-key: false + save-if: ${{ github.ref == 'refs/heads/main' }} + + # Each shard builds its own binary: with a warm cache that beats a + # shared build job plus an artifact hop. + - name: Build PR binary + run: cargo build --locked --release + + - name: Run native capture contract (Windows) + if: matrix.shard == '1/3' + run: python tests\native_capture_contract.py --binary "$env:GITHUB_WORKSPACE\target\release\rustinel.exe" + shell: pwsh + + - name: Read shared atomic rules pin + id: rules-pin + shell: bash + run: | + ref="$(cat .github/atomic-rules-ref)" + if [[ ! "$ref" =~ ^[0-9a-f]{40}$ ]]; then + echo "Expected a full commit SHA in .github/atomic-rules-ref" >&2 + exit 1 + fi + echo "ref=$ref" >> "$GITHUB_OUTPUT" + + - name: Check out pinned rules + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + repository: Karib0u/rustinel-rules + ref: ${{ steps.rules-pin.outputs.ref }} + path: rustinel-rules + persist-credentials: false + + - name: Install uv + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + with: + enable-cache: false + + - name: Build pinned rules pack + working-directory: rustinel-rules + run: | + uv sync --frozen + # Validate a rules commit, not the enclosing engine release tag. + uv run python -c "import os, subprocess, sys; os.environ.pop('GITHUB_REF_TYPE', None); sys.exit(subprocess.call([sys.executable, 'tools/validate.py']))" + uv run python tools/build_packs.py + + - name: Check atomic coverage metadata + working-directory: rustinel-rules + run: python tests/atomic/run_atomics.py --check-coverage --strict-essential + + - name: Exclude workspace from Defender (Windows, for EICAR) + run: Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE" -ErrorAction SilentlyContinue + shell: pwsh + - name: Run atomic tests (Windows) - if: matrix.platform == 'windows' working-directory: rustinel-rules run: | # The runner's D:\a grants Users write, and the agent refuses inputs @@ -527,30 +602,15 @@ jobs: --shard "${{ matrix.shard }}" shell: pwsh - - name: Prepare atomic logs for upload (Unix) - if: always() && matrix.platform != 'windows' - run: | - if [ -d /opt/rustinel/logs ]; then - sudo cp -a /opt/rustinel/logs rustinel-rules/tests/atomic/managed-logs - sudo chown -R "$(id -u):$(id -g)" rustinel-rules/tests/atomic/managed-logs - fi - if [ -f /opt/rustinel/engine.stdout.log ]; then - sudo cp /opt/rustinel/engine.stdout.log rustinel-rules/tests/atomic/engine.stdout.log - sudo chown "$(id -u):$(id -g)" rustinel-rules/tests/atomic/engine.stdout.log - fi - shell: bash - - name: Upload atomic report and logs if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: atomic-${{ matrix.platform }}-${{ strategy.job-index }} + name: atomic-windows-${{ strategy.job-index }} path: | rustinel-rules/tests/atomic/report-*.json rustinel-rules/tests/atomic/.engine/engine.stdout.log rustinel-rules/tests/atomic/.engine/logs/ - rustinel-rules/tests/atomic/engine.stdout.log - rustinel-rules/tests/atomic/managed-logs/ include-hidden-files: true if-no-files-found: warn