Skip to content

Land launch-hygiene onto main: kernel default-deny (#54/#55) + canary/wire/adversarial stack (#40/#41/#42) #53

Land launch-hygiene onto main: kernel default-deny (#54/#55) + canary/wire/adversarial stack (#40/#41/#42)

Land launch-hygiene onto main: kernel default-deny (#54/#55) + canary/wire/adversarial stack (#40/#41/#42) #53

Workflow file for this run

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