Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 22 additions & 3 deletions crates/barbacane-test/src/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,24 +108,36 @@ impl TestGateway {
spec_path: &str,
extra_args: &[&str],
) -> Result<Self, TestError> {
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, TestError> {
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, TestError> {
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, TestError> {
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<Self, TestError> {
// Create temp directory for the artifact
let temp_dir = TempDir::new()?;
Expand Down Expand Up @@ -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());

Expand Down
6 changes: 4 additions & 2 deletions crates/barbacane-test/tests/security/ssrf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
2 changes: 1 addition & 1 deletion plugins/jwt-auth/plugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
2 changes: 1 addition & 1 deletion plugins/oidc-auth/plugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]