diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19fef62..f010de9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -291,6 +291,65 @@ jobs: # Run gateway integration tests from barbacane-test crate run: cargo test -p barbacane-test --lib -- --test-threads=2 + # Adversarial security suite (crates/barbacane-test/tests/security/). Runs the + # gateway (and, for authz, the control plane) end-to-end, so it needs the + # built binaries, the WASM plugins, and a PostgreSQL instance. + security-suite: + name: Security Suite + runs-on: ubuntu-latest + needs: [changes, unit-tests] + if: needs.changes.outputs.code == 'true' + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: barbacane + POSTGRES_PASSWORD: barbacane + POSTGRES_DB: barbacane + options: >- + --health-cmd pg_isready + --health-interval 5s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + env: + DATABASE_URL: postgres://barbacane:barbacane@localhost:5432/barbacane + steps: + - uses: actions/checkout@v6 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + + - name: Cache cargo registry + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Download build artifacts + uses: actions/download-artifact@v8 + with: + name: build-artifacts + + - name: Make data-plane binary executable + run: chmod +x target/debug/barbacane + + - name: Build control-plane binary + # The authz category boots the barbacane-control binary; the rest of the + # suite uses the data-plane binary downloaded above. + run: cargo build -p barbacane-control + + - name: Run security suite + run: cargo test -p barbacane-test --test security -- --test-threads=2 + # Benchmark regression check # Triggered by: adding the "run-benchmarks" label, or commenting "/bench" on a PR. benchmarks: diff --git a/CHANGELOG.md b/CHANGELOG.md index dd8cb3e..873052c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **security**: panic on hostile `x-request-id` / `traceparent`; unbounded Prometheus path-label cardinality on unmatched routes. - **security**: panic vectors in WASM host functions — guest-controlled pointer/length slice reads now use saturating arithmetic, and cache/rate-limiter time arithmetic is overflow/underflow-safe. - **security**: chunked request bodies with no `Content-Length` are now size-capped while streaming (`http_body_util::Limited`) instead of being fully buffered before the limit check. +- **plugin**: `jwt-auth` and `oidc-auth` now declare the `log` capability they use, so they load under WASM capability enforcement (they emit a one-time warning when no `audience` is configured). +- **ci**: the adversarial security suite (`crates/barbacane-test/tests/security/`) now runs in CI. - **deps**: bump `anyhow` to 1.0.103 (RUSTSEC-2026-0190). ## [0.7.0] - 2026-05-05 diff --git a/crates/barbacane-test/src/gateway.rs b/crates/barbacane-test/src/gateway.rs index 36e7cf6..9081631 100644 --- a/crates/barbacane-test/src/gateway.rs +++ b/crates/barbacane-test/src/gateway.rs @@ -108,24 +108,36 @@ impl TestGateway { spec_path: &str, extra_args: &[&str], ) -> Result { - Self::create_gateway_with_args(&[spec_path], false, extra_args).await + Self::create_gateway_with_args(&[spec_path], false, extra_args, true).await + } + + /// Create a TestGateway with the plugin SSRF guard ACTIVE (internal egress + /// blocked). Use this for SSRF tests; the default constructors allow internal + /// egress so tests can reach loopback mock upstreams. + pub async fn from_spec_blocked_egress(spec_path: &str) -> Result { + Self::create_gateway_with_args(&[spec_path], false, &[], false).await } /// Create a TestGateway from multiple spec files. pub async fn from_specs(spec_paths: &[&str]) -> Result { - Self::create_gateway_with_args(spec_paths, false, &[]).await + Self::create_gateway_with_args(spec_paths, false, &[], true).await } /// Create a TLS-enabled TestGateway from multiple spec files. pub async fn from_specs_with_tls(spec_paths: &[&str]) -> Result { - Self::create_gateway_with_args(spec_paths, true, &[]).await + Self::create_gateway_with_args(spec_paths, true, &[], true).await } /// Internal method to create a gateway with optional TLS and extra CLI args. + /// + /// `allow_internal_egress` controls the plugin SSRF guard: most tests reach + /// loopback mock upstreams and need it `true`; SSRF tests pass `false` so the + /// guard is active and the block is observable. async fn create_gateway_with_args( spec_paths: &[&str], tls_enabled: bool, extra_args: &[&str], + allow_internal_egress: bool, ) -> Result { // Create temp directory for the artifact let temp_dir = TempDir::new()?; @@ -185,6 +197,13 @@ impl TestGateway { .arg(format!("127.0.0.1:{}", admin_port)) .arg("--dev") .arg("--allow-plaintext-upstream") // Allow HTTP calls to test mock servers + // Set egress policy explicitly so tests don't depend on the ambient + // environment: most tests reach loopback mocks (allow), SSRF tests + // exercise the guard (block). + .env( + "BARBACANE_ALLOW_INTERNAL_EGRESS", + if allow_internal_egress { "1" } else { "0" }, + ) .stdout(Stdio::piped()) .stderr(Stdio::piped()); diff --git a/crates/barbacane-test/tests/security/ssrf.rs b/crates/barbacane-test/tests/security/ssrf.rs index d89b00a..a4a3fd0 100644 --- a/crates/barbacane-test/tests/security/ssrf.rs +++ b/crates/barbacane-test/tests/security/ssrf.rs @@ -30,9 +30,11 @@ use barbacane_test::TestGateway; use crate::security_fixture; -/// Boot a gateway from the SSRF fixture. +/// Boot a gateway from the SSRF fixture with the egress guard ACTIVE, so the +/// block is what we actually observe (the default test harness allows internal +/// egress for loopback mocks). async fn ssrf_gateway() -> TestGateway { - TestGateway::from_spec(&security_fixture("ssrf.yaml")) + TestGateway::from_spec_blocked_egress(&security_fixture("ssrf.yaml")) .await .expect("ssrf fixture failed to compile/start") } diff --git a/plugins/jwt-auth/plugin.toml b/plugins/jwt-auth/plugin.toml index 3e41630..fa2856a 100644 --- a/plugins/jwt-auth/plugin.toml +++ b/plugins/jwt-auth/plugin.toml @@ -6,4 +6,4 @@ description = "JWT authentication middleware for validating Bearer tokens" wasm = "jwt-auth.wasm" [capabilities] -host_functions = ["verify_signature", "clock_now"] +host_functions = ["verify_signature", "clock_now", "log"] diff --git a/plugins/oidc-auth/plugin.toml b/plugins/oidc-auth/plugin.toml index ca04d68..5cf3ac4 100644 --- a/plugins/oidc-auth/plugin.toml +++ b/plugins/oidc-auth/plugin.toml @@ -6,4 +6,4 @@ description = "OIDC authentication middleware with auto-discovery and JWKS rotat wasm = "oidc-auth.wasm" [capabilities] -host_functions = ["http_call", "verify_signature", "clock_now"] +host_functions = ["http_call", "verify_signature", "clock_now", "log"]