Land launch-hygiene onto main: kernel default-deny (#54/#55) + canary/wire/adversarial stack (#40/#41/#42) #53
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: ["main"] | |
| pull_request: | |
| # Manual trigger for the privileged real-kernel enforcement job, which can only | |
| # run on a self-hosted BPF-LSM runner (see the kernel-lsm-real job below). | |
| workflow_dispatch: | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| jobs: | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Job 1: Release build | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| build: | |
| name: Build (release) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y libz3-dev keyutils | |
| - name: Cache Cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-registry- | |
| - name: Cache target directory | |
| uses: actions/cache@v4 | |
| with: | |
| path: target | |
| key: ${{ runner.os }}-cargo-target-release-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-target-release- | |
| - name: Build release | |
| run: cargo build --release 2>&1 | |
| - name: Upload release binary | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ts_cli-release-${{ github.sha }} | |
| path: target/release/ts_cli | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Job 2: Test suite (unit + integration) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| test: | |
| name: Tests | |
| runs-on: ubuntu-latest | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y libz3-dev keyutils | |
| - name: Cache Cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-registry- | |
| - name: Cache target directory | |
| uses: actions/cache@v4 | |
| with: | |
| path: target | |
| key: ${{ runner.os }}-cargo-target-test-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-target-test- | |
| ${{ runner.os }}-cargo-target-release- | |
| - name: Download release binary | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: ts_cli-release-${{ github.sha }} | |
| path: target/release/ | |
| - name: Mark binary executable | |
| run: chmod +x target/release/ts_cli | |
| - name: Run all tests | |
| env: | |
| JINNGUARD_TEST_BINARY: ${{ github.workspace }}/target/release/ts_cli | |
| run: cargo test --workspace 2>&1 | |
| # Anti-lockout / safe-mode invariants in the LSM verdict loop only build | |
| # under the kernel feature. Run them so a regression that could strand the | |
| # operator fails CI. | |
| - name: Run kernel-feature safety invariants | |
| run: cargo test --bin ts_cli --features kernel_telemetry 2>&1 | |
| # The privileged kernel_lsm enforcement tests (incl. #54 default-deny egress | |
| # and #55 AF_UNIX deputy denylist) need BPF LSM + root, so they cannot run on | |
| # a GitHub-hosted runner — they execute on the self-hosted kernel-lsm-real job | |
| # below. Compile them here under --features enterprise so a change that breaks | |
| # the harness (or the daemon-side enforcement wiring) fails PR CI immediately. | |
| - name: Compile kernel_lsm enforcement tests (gating, no-run) | |
| run: cargo test -p ts_cli --features enterprise --test kernel_lsm --no-run 2>&1 | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Job 2b: Fleet feature gate (open-core enterprise client) | |
| # The signed-bundle client (`--fleet-policy-url`) compiles only with the | |
| # `fleet` feature (off by default). The other jobs build feature-off; this one | |
| # guarantees the gated code still compiles, lints, and that the accept/reject | |
| # decision (apply-forward / reject-rollback / reject-bad-signature) is correct. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| fleet: | |
| name: Fleet feature gate | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y libz3-dev keyutils | |
| - name: Cache Cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-registry- | |
| - name: Cache target directory | |
| uses: actions/cache@v4 | |
| with: | |
| path: target | |
| key: ${{ runner.os }}-cargo-target-fleet-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-target-fleet- | |
| - name: Install clippy | |
| run: rustup component add clippy | |
| - name: Clippy (fleet feature, deny warnings) | |
| run: cargo clippy -p ts_cli --features fleet -- -D warnings 2>&1 | |
| - name: Test fleet bundle accept/reject decision | |
| run: cargo test -p ts_cli --features fleet --bin ts_cli fleet_policy 2>&1 | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Job 3: Benchmark compilation check | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| bench: | |
| name: Bench (compile check) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y libz3-dev keyutils | |
| - name: Cache Cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-registry- | |
| - name: Cache target directory | |
| uses: actions/cache@v4 | |
| with: | |
| path: target | |
| key: ${{ runner.os }}-cargo-target-bench-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-target-bench- | |
| - name: Compile benchmarks (no-run) | |
| run: cargo bench --no-run 2>&1 | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Job 4: Clippy (deny warnings) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| clippy: | |
| name: Clippy | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y libz3-dev keyutils | |
| - name: Cache Cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-registry- | |
| - name: Install clippy | |
| run: rustup component add clippy | |
| - name: Run clippy | |
| run: cargo clippy -- -D warnings 2>&1 | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Job 5: Rustfmt check | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| fmt: | |
| name: Format check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install rustfmt | |
| run: rustup component add rustfmt | |
| - name: Check formatting | |
| run: cargo fmt --check 2>&1 | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Job 6: eBPF build | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| build-ebpf: | |
| name: Build eBPF LSM objects | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install clang, llvm, libbpf-dev | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y clang llvm libbpf-dev | |
| # Compile each of the five LSM objects the daemon actually loads against the | |
| # vendored bpf/vmlinux.h (committed to the repo). This deliberately does NOT | |
| # use bpftool or the runner's live BTF: obtaining a working bpftool on | |
| # GitHub's Azure-kernel runner is unreliable, and a compile sanity check | |
| # doesn't need the runner's own kernel types. This GATES CI — a change that | |
| # breaks BPF compilation (bad map, missing helper, etc.) fails the build. | |
| - name: Compile LSM objects (gating) | |
| run: | | |
| set -euo pipefail | |
| test -f bpf/vmlinux.h || { echo "ERROR: bpf/vmlinux.h missing (it is vendored)"; exit 1; } | |
| cd bpf | |
| for f in lsm/jg_socket_connect lsm/jg_socket_sendmsg \ | |
| lsm/jg_bprm_check_security lsm/jg_inode_create lsm/jg_inode_unlink; do | |
| echo " CLANG $f.o" | |
| clang -O2 -g -target bpf -D__TARGET_ARCH_x86 -I/usr/include -I. -c "$f.c" -o "$f.o" | |
| done | |
| - name: Upload eBPF LSM objects | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: jinnguard-lsm-objects-${{ github.sha }} | |
| path: bpf/lsm/*.o | |
| if-no-files-found: error | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Job 7: Real-kernel LSM enforcement matrix (self-hosted, privileged) | |
| # GitHub-hosted runners cannot enable the BPF LSM (boot param) or attach LSM | |
| # programs as root, so the #[ignore]d kernel_lsm tests — the actual allow/deny | |
| # enforcement proofs, including #54 default-deny IPv4 egress and #55 the AF_UNIX | |
| # deputy denylist — can only execute on self-hosted runners whose kernel was | |
| # booted with `lsm=...,bpf` and cgroup v2 mounted. | |
| # | |
| # Fans out across the validated distro/kernel runners (each registered with the | |
| # shared `bpf-lsm` label plus a per-host label): AlphaOS 6.12, Ubuntu 24.04 6.17 | |
| # (jinn2), AlmaLinux 9.8 5.14 (jinn3). 5.14 is the floor that exercises the | |
| # oldest supported verifier. fail-fast: false so one distro's failure still lets | |
| # the others report. The steps are package-manager agnostic (build deps are baked | |
| # into each runner image), so no per-distro branching is needed. | |
| # | |
| # Manually dispatched (workflow_dispatch) so normal push/PR CI is never queued | |
| # against a self-hosted runner that may be offline. Mirrors the proven incantation | |
| # from scripts/validate/kernel_floor_demo.sh: compile the test binary as the build | |
| # user, then exec the raw binary as root (no cargo at root, no root-owned target | |
| # churn). JINNGUARD_TEST_BINARY must be ABSOLUTE. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| kernel-lsm-real: | |
| name: Real-kernel LSM (${{ matrix.distro }} k${{ matrix.kernel }}) | |
| if: github.event_name == 'workflow_dispatch' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - distro: AlphaOS | |
| label: alphaos | |
| kernel: "6.12" | |
| - distro: Ubuntu 24.04 | |
| label: jinn2 | |
| kernel: "6.17" | |
| - distro: AlmaLinux 9.8 | |
| label: jinn3 | |
| kernel: "5.14" | |
| runs-on: [self-hosted, bpf-lsm, "${{ matrix.label }}"] | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Show host kernel / distro | |
| run: | | |
| echo "matrix: ${{ matrix.distro }} (expected kernel ${{ matrix.kernel }}, label ${{ matrix.label }})" | |
| uname -a | |
| - name: Build + install LSM objects (-> /usr/lib/jinnguard/lsm) | |
| run: sudo make -C bpf install | |
| - name: Build enterprise daemon | |
| run: cargo build -p ts_cli --features enterprise | |
| - name: Compile kernel_lsm test binary (as build user) | |
| run: cargo test -p ts_cli --features enterprise --test kernel_lsm --no-run | |
| - name: Run armed kernel-LSM enforcement tests (root) | |
| run: | | |
| set -euo pipefail | |
| BIN="$PWD/target/debug/ts_cli" | |
| test -x "$BIN" || { echo "enterprise daemon not built at $BIN"; exit 1; } | |
| TESTBIN="$(find "$PWD/target/debug/deps" -maxdepth 1 -type f -executable \ | |
| -name 'kernel_lsm-*' ! -name '*.d' -printf '%T@ %p\n' \ | |
| | sort -rn | head -1 | cut -d' ' -f2-)" | |
| test -n "$TESTBIN" && test -x "$TESTBIN" \ | |
| || { echo "kernel_lsm test binary not found under target/debug/deps"; exit 1; } | |
| echo " daemon: $BIN" | |
| echo " testbin: $TESTBIN" | |
| export PATH="$PATH:/usr/sbin" | |
| # Runs the whole #[ignore]d kernel_lsm suite — the 6 baseline surfaces plus | |
| # the new #54 default-deny egress and #55 deputy-denylist enforcement tests. | |
| sudo -E env "PATH=$PATH" JINNGUARD_TEST_BINARY="$BIN" \ | |
| "$TESTBIN" --ignored --test-threads=1 --nocapture |