mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Implement periodic full rekey at both protocol layers using fresh DH key exchanges. Uses the existing K-bit flag (FLAG_KEY_EPOCH / FSP_FLAG_K) to coordinate cutover between peers. FMP layer (IK pattern): - ActivePeer gains rekey state: pending/previous sessions, K-bit epoch tracking, drain window, dampening timer - Handshake state stored on ActivePeer with msg1 sent on existing link - Encrypted frame handler detects K-bit flips, promotes pending sessions, falls back to previous session during drain - Handshake handlers distinguish rekey from new connections using addr_to_link lookup with identity-based fallback - Free all session indices (current, rekey, pending, previous) on peer removal FSP layer (XK pattern): - SessionEntry gains parallel rekey fields with XK-specific state for the 3-message handshake - Route availability check before FSP rekey initiation - Encrypted session handler adds K-bit flip detection and dual-session decrypt fallback - SessionSetup/Ack/Msg3 handlers extended for rekey paths Defense-in-depth: - Consecutive decryption failure detector (threshold=20) triggers forced peer removal instead of waiting for link-dead timeout - Identity-based rekey detection as fallback when addr_to_link doesn't match (e.g., TCP ephemeral ports) Configuration: RekeyConfig with enabled flag, after_secs (default 120), and after_messages (default 65536) thresholds. Logging: info for successful K-bit cutover completions, warn for failures, debug for intermediate handshake steps, trace for routine operations (resends, drain cleanup). Rekey lifecycle: 1. Timer/counter fires -> initiator starts new handshake 2. Old session continues handling traffic during handshake 3. Handshake completes -> initiator cuts over, flips K-bit 4. Responder sees flipped K-bit -> promotes new session 5. Both keep old session for 10s drain window 6. After drain, old session discarded Integration test: Docker-based multi-phase test exercising both FMP and FSP rekey with aggressive timers (35s). Verifies connectivity across all 20 directed pairs survives two consecutive rekey cycles. Includes rekey topology, docker-compose profile, and CI matrix entry. Increase ping test convergence wait from 3s to 5s for CI reliability.
330 lines
12 KiB
YAML
330 lines
12 KiB
YAML
name: CI
|
||
|
||
on:
|
||
push:
|
||
branches: ["**"]
|
||
pull_request:
|
||
workflow_dispatch:
|
||
inputs:
|
||
skip_integration:
|
||
description: "Skip integration tests"
|
||
type: boolean
|
||
default: false
|
||
|
||
permissions:
|
||
checks: write
|
||
contents: read
|
||
|
||
env:
|
||
CARGO_TERM_COLOR: always
|
||
RUST_BACKTRACE: 1
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# Job 1 – Build matrix
|
||
#
|
||
# Builds on Linux x86_64 and Linux aarch64. macOS and Windows are in a
|
||
# separate ci-compat.yml workflow so their failures don't mark this run red.
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
jobs:
|
||
build:
|
||
name: Build (${{ matrix.os }})
|
||
runs-on: ${{ matrix.os }}
|
||
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
- os: ubuntu-latest
|
||
- os: ubuntu-24.04-arm
|
||
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Install Rust toolchain
|
||
uses: dtolnay/rust-toolchain@stable
|
||
|
||
- name: Cache Cargo registry + build
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: |
|
||
~/.cargo/registry
|
||
~/.cargo/git
|
||
target
|
||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-cargo-
|
||
|
||
- name: Build
|
||
run: cargo build --release
|
||
|
||
# Upload the Linux binary so integration jobs can use it without rebuilding
|
||
- name: Upload Linux binary
|
||
if: matrix.os == 'ubuntu-latest'
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: fips-linux
|
||
path: |
|
||
target/release/fips
|
||
target/release/fipsctl
|
||
target/release/fipstop
|
||
retention-days: 1
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# Job 2 – Unit tests
|
||
#
|
||
# Runs `cargo test` on Linux. Gated on the build matrix completing so we
|
||
# don't waste runner time if compilation is broken.
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
test:
|
||
name: Unit tests
|
||
runs-on: ubuntu-latest
|
||
needs: [build]
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Install Rust toolchain
|
||
uses: dtolnay/rust-toolchain@stable
|
||
|
||
- name: Cache Cargo registry + build
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: |
|
||
~/.cargo/registry
|
||
~/.cargo/git
|
||
target
|
||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-cargo-
|
||
|
||
- name: Install cargo-nextest
|
||
uses: taiki-e/install-action@nextest
|
||
|
||
- name: Run unit tests
|
||
run: cargo nextest run --all --profile ci
|
||
|
||
- name: Publish test report (Checks tab)
|
||
uses: dorny/test-reporter@v2
|
||
if: always()
|
||
with:
|
||
name: Unit Tests
|
||
path: target/nextest/ci/junit.xml
|
||
reporter: java-junit
|
||
fail-on-error: false
|
||
|
||
- name: Publish test report (run summary)
|
||
uses: mikepenz/action-junit-report@v4
|
||
if: always()
|
||
with:
|
||
report_paths: target/nextest/ci/junit.xml
|
||
check_name: Unit Tests Summary
|
||
fail_on_failure: false
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# Job 3 – Integration tests (static mesh + chaos simulation)
|
||
#
|
||
# Runs only when both build and test succeed. Each topology / scenario is a
|
||
# separate matrix entry so they run in parallel.
|
||
#
|
||
# Static topologies → build Docker images, start containers, ping-test
|
||
# Chaos scenarios → build sim image, run stochastic simulation
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
integration:
|
||
name: Integration (${{ matrix.suite }})
|
||
runs-on: ubuntu-latest
|
||
needs: [build, test]
|
||
if: ${{ !inputs.skip_integration }}
|
||
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
# ── Static mesh topologies ─────────────────────────────────────────
|
||
- suite: static-mesh
|
||
type: static
|
||
topology: mesh
|
||
- suite: static-chain
|
||
type: static
|
||
topology: chain
|
||
# ── Rekey integration test ──────────────────────────────────────────
|
||
- suite: rekey
|
||
type: rekey
|
||
topology: rekey
|
||
# ── Chaos / stochastic scenarios ───────────────────────────────────
|
||
- suite: chaos-smoke-10
|
||
type: chaos
|
||
scenario: smoke-10
|
||
- suite: chaos-10
|
||
type: chaos
|
||
scenario: chaos-10
|
||
- suite: ethernet-mesh
|
||
type: chaos
|
||
scenario: ethernet-mesh
|
||
- suite: ethernet-only
|
||
type: chaos
|
||
scenario: ethernet-only
|
||
- suite: bottleneck-parent
|
||
type: chaos
|
||
scenario: bottleneck-parent
|
||
- suite: cost-avoidance
|
||
type: chaos
|
||
scenario: cost-avoidance
|
||
- suite: cost-mixed-7node
|
||
type: chaos
|
||
scenario: cost-mixed-7node
|
||
- suite: cost-reeval
|
||
type: chaos
|
||
scenario: cost-reeval
|
||
- suite: cost-stability
|
||
type: chaos
|
||
scenario: cost-stability
|
||
- suite: depth-vs-cost
|
||
type: chaos
|
||
scenario: depth-vs-cost
|
||
- suite: mixed-technology
|
||
type: chaos
|
||
scenario: mixed-technology
|
||
# ── Sidecar deployment ──────────────────────────────────────────
|
||
- suite: sidecar
|
||
type: sidecar
|
||
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
# Fetch the pre-built Linux binary from job 1
|
||
- name: Download Linux binary
|
||
uses: actions/download-artifact@v4
|
||
with:
|
||
name: fips-linux
|
||
path: _bin
|
||
|
||
# ── Static topology ────────────────────────────────────────────────────
|
||
- name: Install binary (static)
|
||
if: matrix.type == 'static'
|
||
run: |
|
||
chmod +x _bin/fips _bin/fipsctl
|
||
cp _bin/fips testing/static/fips
|
||
cp _bin/fipsctl testing/static/fipsctl
|
||
|
||
- name: Generate configs (static)
|
||
if: matrix.type == 'static'
|
||
run: bash testing/static/scripts/generate-configs.sh ${{ matrix.topology }}
|
||
|
||
- name: Build Docker images (static)
|
||
if: matrix.type == 'static'
|
||
run: |
|
||
docker compose -f testing/static/docker-compose.yml \
|
||
--profile ${{ matrix.topology }} build
|
||
|
||
- name: Start containers (static)
|
||
if: matrix.type == 'static'
|
||
run: |
|
||
docker compose -f testing/static/docker-compose.yml \
|
||
--profile ${{ matrix.topology }} up -d
|
||
|
||
- name: Run ping test (static)
|
||
if: matrix.type == 'static'
|
||
run: bash testing/static/scripts/ping-test.sh ${{ matrix.topology }}
|
||
|
||
- name: Collect logs on failure (static)
|
||
if: matrix.type == 'static' && failure()
|
||
run: |
|
||
docker compose -f testing/static/docker-compose.yml \
|
||
--profile ${{ matrix.topology }} logs --no-color
|
||
|
||
- name: Stop containers (static)
|
||
if: matrix.type == 'static' && always()
|
||
run: |
|
||
docker compose -f testing/static/docker-compose.yml \
|
||
--profile ${{ matrix.topology }} down --volumes --remove-orphans
|
||
|
||
# ── Rekey integration test ──────────────────────────────────────────────
|
||
- name: Install binary (rekey)
|
||
if: matrix.type == 'rekey'
|
||
run: |
|
||
chmod +x _bin/fips _bin/fipsctl
|
||
cp _bin/fips testing/static/fips
|
||
cp _bin/fipsctl testing/static/fipsctl
|
||
|
||
- name: Generate and inject configs (rekey)
|
||
if: matrix.type == 'rekey'
|
||
run: |
|
||
bash testing/static/scripts/generate-configs.sh rekey
|
||
bash testing/static/scripts/rekey-test.sh inject-config
|
||
|
||
- name: Build Docker images (rekey)
|
||
if: matrix.type == 'rekey'
|
||
run: |
|
||
docker compose -f testing/static/docker-compose.yml \
|
||
--profile rekey build
|
||
|
||
- name: Start containers (rekey)
|
||
if: matrix.type == 'rekey'
|
||
run: |
|
||
docker compose -f testing/static/docker-compose.yml \
|
||
--profile rekey up -d
|
||
|
||
- name: Run rekey test
|
||
if: matrix.type == 'rekey'
|
||
run: bash testing/static/scripts/rekey-test.sh
|
||
|
||
- name: Collect logs on failure (rekey)
|
||
if: matrix.type == 'rekey' && failure()
|
||
run: |
|
||
docker compose -f testing/static/docker-compose.yml \
|
||
--profile rekey logs --no-color
|
||
|
||
- name: Stop containers (rekey)
|
||
if: matrix.type == 'rekey' && always()
|
||
run: |
|
||
docker compose -f testing/static/docker-compose.yml \
|
||
--profile rekey down --volumes --remove-orphans
|
||
|
||
# ── Chaos simulation ───────────────────────────────────────────────────
|
||
- name: Install Python deps (chaos)
|
||
if: matrix.type == 'chaos'
|
||
run: pip3 install --quiet pyyaml jinja2
|
||
|
||
- name: Install binary (chaos)
|
||
if: matrix.type == 'chaos'
|
||
run: |
|
||
chmod +x _bin/fips _bin/fipsctl
|
||
cp _bin/fips testing/chaos/fips
|
||
cp _bin/fipsctl testing/chaos/fipsctl
|
||
|
||
- name: Build chaos Docker image
|
||
if: matrix.type == 'chaos'
|
||
run: docker build -t fips-chaos:latest testing/chaos
|
||
|
||
- name: Run chaos scenario
|
||
if: matrix.type == 'chaos'
|
||
run: bash testing/chaos/scripts/chaos.sh ${{ matrix.scenario }}
|
||
|
||
- name: Upload sim results on failure (chaos)
|
||
if: matrix.type == 'chaos' && failure()
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: sim-results-${{ matrix.scenario }}
|
||
path: testing/chaos/sim-results/
|
||
retention-days: 7
|
||
|
||
# ── Sidecar deployment ──────────────────────────────────────────────
|
||
- name: Install binary (sidecar)
|
||
if: matrix.type == 'sidecar'
|
||
run: |
|
||
chmod +x _bin/fips _bin/fipsctl _bin/fipstop
|
||
cp _bin/fips testing/sidecar/fips
|
||
cp _bin/fipsctl testing/sidecar/fipsctl
|
||
cp _bin/fipstop testing/sidecar/fipstop
|
||
|
||
- name: Run sidecar integration test
|
||
if: matrix.type == 'sidecar'
|
||
run: bash testing/sidecar/scripts/test-sidecar.sh
|
||
|
||
- name: Collect logs on failure (sidecar)
|
||
if: matrix.type == 'sidecar' && failure()
|
||
run: |
|
||
for node in a b c; do
|
||
echo "--- sidecar-${node} logs ---"
|
||
docker logs "sidecar-${node}-fips-1" 2>&1 || true
|
||
echo ""
|
||
done |