Skip to content

ci

ci #266

Workflow file for this run

name: ci
on:
push:
pull_request:
workflow_dispatch:
schedule:
- cron: "0 3 * * *"
permissions:
contents: read
jobs:
rust-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: cargo fmt
run: cargo fmt --all --check
- name: cargo clippy
run: cargo clippy --workspace --all-targets --features pqmsg-core/pq-rust -- -D warnings
- name: cargo test
run: cargo test --workspace --all-targets --features pqmsg-core/pq-rust
pq-backend-core-gates:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
backend:
- name: pq-rust
features: pq-rust
- name: pq-oqs
features: pq-oqs
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- name: cargo clippy (${{ matrix.backend.name }})
run: cargo clippy -p pqmsg-core --all-targets --no-default-features --features ${{ matrix.backend.features }} -- -D warnings
- name: cargo test (${{ matrix.backend.name }})
run: cargo test -p pqmsg-core --all-targets --no-default-features --features ${{ matrix.backend.features }}
fips-build-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: cargo clippy (FIPS)
run: cargo clippy -p pqmsg-core --no-default-features --features fips -- -D warnings
- name: cargo test (FIPS)
run: cargo test -p pqmsg-core --no-default-features --features fips
classical-only-build-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: cargo clippy (classical-only)
run: cargo clippy -p pqmsg-core --features classical-only-INSECURE --no-default-features -- -D warnings
- name: cargo test (classical-only)
run: cargo test -p pqmsg-core --features classical-only-INSECURE --no-default-features
hsm-pkcs11-build-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: install SoftHSM2
run: sudo apt-get update && sudo apt-get install -y softhsm2
- name: cargo clippy (hsm-pkcs11)
run: cargo clippy -p pqmsg-core --no-default-features --features pq-rust,hsm-pkcs11 -- -D warnings
- name: cargo test (hsm-pkcs11)
run: cargo test -p pqmsg-core --no-default-features --features pq-rust,hsm-pkcs11
postgres-integration:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: pqmsg
POSTGRES_PASSWORD: pqmsg_ci
POSTGRES_DB: pqmsg_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: run server tests against PostgreSQL
env:
PQMSG_DATABASE_URL: "postgres://pqmsg:pqmsg_ci@localhost:5432/pqmsg_test"
PQMSG_SECURITY_PROFILE: research
run: cargo test -p pqmsg-server --all-targets
sqlite-sqlcipher:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: install SQLCipher build prerequisites
run: sudo apt-get update && sudo apt-get install -y pkg-config libssl-dev
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: run SQLCipher SQLite server tests
run: cargo test -p pqmsg-server db::tests::sqlite_ --lib
sqlite-sqlcipher-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: install Strawberry Perl
run: choco install strawberryperl --yes --no-progress
- name: add perl to PATH
shell: pwsh
run: |
echo "C:\Strawberry\perl\bin" >> $env:GITHUB_PATH
echo "C:\Strawberry\c\bin" >> $env:GITHUB_PATH
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: check SQLCipher prerequisites
shell: pwsh
run: .\scripts\dev\check_sqlcipher_server_prereqs.ps1
- name: run SQLCipher SQLite server tests
shell: pwsh
run: cargo test -p pqmsg-server db::tests::sqlite_ --lib
helm-chart-smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: azure/setup-helm@v4
- name: render hardened chart without optional Sentry DSN
run: |
helm template pqmsg-server deploy/helm/pqmsg-server >/tmp/pqmsg-server-chart.yaml
- name: production chart allows missing Sentry DSN
run: |
helm template pqmsg-server deploy/helm/pqmsg-server >/tmp/pqmsg-server-chart-no-sentry.yaml
- name: hardened chart rejects wildcard cors
run: |
if helm template pqmsg-server deploy/helm/pqmsg-server \
--set-string env.PQMSG_CORS_ALLOWED_ORIGINS='*' >/tmp/pqmsg-server-chart-fail.yaml 2>/tmp/pqmsg-server-chart.err; then
echo "expected wildcard CORS to fail"
exit 1
fi
grep -q "reject wildcard" /tmp/pqmsg-server-chart.err
hardened-manifest-policy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: azure/setup-helm@v4
- name: render hardened production chart
run: |
helm template pqmsg-server deploy/helm/pqmsg-server >/tmp/pqmsg-server-chart.yaml
- name: validate raw and rendered hardened manifests
run: |
python scripts/security/validate_hardened_manifests.py \
deploy/k8s/deployment.yaml \
/tmp/pqmsg-server-chart.yaml
- name: validate raw and rendered network policy
run: |
python scripts/security/validate_network_policy.py \
deploy/k8s/networkpolicy.yaml \
/tmp/pqmsg-server-chart.yaml
- name: validate image pinning
run: |
python scripts/security/validate_image_pinning.py \
deploy/k8s/deployment.yaml \
/tmp/pqmsg-server-chart.yaml
- name: validate preview discovery envs stay out of hardened manifests
run: |
python scripts/security/validate_no_preview_contact_discovery.py \
deploy/k8s/configmap.yaml \
/tmp/pqmsg-server-chart.yaml
- name: validate raw namespace pod-security labels
run: |
python scripts/security/validate_namespace_policy.py \
deploy/k8s/namespace.yaml
support-matrix-contract:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: validate canonical support matrix
run: |
python scripts/security/validate_support_matrix.py
audit-findings-contract:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: validate audit findings registry
run: |
python scripts/security/validate_audit_findings.py
- name: validate release audit gate
run: |
python scripts/security/validate_release_audit_gate.py
release-governance-helper-smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: validate release governance workflows
run: |
python scripts/security/validate_release_governance_workflows.py
- name: smoke release governance helpers
run: |
python scripts/release/smoke_release_governance_helpers.py
audit-readiness-bundle:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: validate audit-readiness index
run: |
python scripts/security/validate_audit_readiness_index.py
- name: validate audit findings registry
run: |
python scripts/security/validate_audit_findings.py
- name: validate release audit gate
run: |
python scripts/security/validate_release_audit_gate.py
- name: build audit-readiness bundle
run: |
python scripts/security/build_audit_readiness_bundle.py --output-dir /tmp/pqmsg-audit-bundle
- name: verify audit-readiness bundle
run: |
python scripts/security/verify_audit_readiness_bundle.py --bundle-dir /tmp/pqmsg-audit-bundle
- name: prepare external audit handoff package
run: |
python scripts/security/prepare_external_audit_handoff.py --bundle-dir /tmp/pqmsg-audit-bundle --output-dir /tmp/pqmsg-audit-handoff
- name: attest audit-readiness bundle manifest provenance
uses: actions/attest@v4
with:
subject-path: /tmp/pqmsg-audit-bundle/audit-bundle-manifest.json
- name: attest external audit handoff archive provenance
uses: actions/attest@v4
with:
subject-path: /tmp/pqmsg-audit-handoff/audit-readiness-bundle.zip
- name: upload audit-readiness bundle
uses: actions/upload-artifact@v4
with:
name: audit-readiness-bundle
path: /tmp/pqmsg-audit-bundle/**
- name: upload external audit handoff package
uses: actions/upload-artifact@v4
with:
name: external-audit-handoff
path: /tmp/pqmsg-audit-handoff/**
benchmarks:
if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: run crypto benchmarks
run: cargo bench -p pqmsg-core --bench crypto_benchmarks --no-default-features --features pq-rust -- --output-format bencher 2>/dev/null | tee bench-output.txt
- name: run server load benchmarks
run: cargo bench -p pqmsg-server --bench server_load -- --output-format bencher 2>/dev/null | tee -a bench-output.txt
- name: upload benchmark results
uses: actions/upload-artifact@v4
with:
name: criterion-benchmarks
path: |
bench-output.txt
target/criterion/
dependency-policy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: install cargo-audit
run: cargo install cargo-audit --locked
- name: cargo audit
run: cargo audit
- name: install cargo-deny
run: cargo install cargo-deny --locked
- name: cargo deny
run: cargo deny check advisories licenses bans sources
coverage:
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@cargo-llvm-cov
- name: cargo llvm-cov
run: cargo llvm-cov --workspace --features pqmsg-core/pq-rust --exclude pqmsg-android --exclude pqmsg-ios --lcov --output-path lcov.info --fail-under-lines 50
- name: upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-lcov
path: lcov.info
android-build:
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: android-actions/setup-android@v3
- name: install android sdk packages
run: |
yes | sdkmanager --licenses >/dev/null
sdkmanager \
"platform-tools" \
"platforms;android-34" \
"build-tools;34.0.0" \
"ndk;26.3.11579264" \
"cmake;3.22.1"
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/26.3.11579264" >> "$GITHUB_ENV"
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android
- name: install cargo-ndk
run: cargo install cargo-ndk --locked
- name: build rust host library
run: cargo build -p pqmsg-android
- name: generate uniffi kotlin bindings
run: cargo run -p pqmsg-android --bin uniffi-bindgen -- generate --library target/debug/libpqmsg_android.so --language kotlin --out-dir mobile/android/app/build/generated/uniffi/kotlin --no-format
- name: build android rust shared libs
run: cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 -o mobile/android/app/src/main/jniLibs build -p pqmsg-android --release
- name: assemble debug apk
working-directory: mobile/android
run: |
chmod +x gradlew
./gradlew --no-daemon :app:assembleDebug
- name: upload android apk
uses: actions/upload-artifact@v4
with:
name: android-debug-apk
path: mobile/android/app/build/outputs/apk/debug/app-debug.apk
web-tests:
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: mobile/web
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: mobile/web/package-lock.json
- name: install web dependencies
run: npm ci
- name: run web tests
run: npm test
web-production-contract:
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: mobile/web/package-lock.json
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: install web dependencies
working-directory: mobile/web
run: npm ci
- name: install wasm-pack
run: cargo install wasm-pack --locked
- name: build wasm package
working-directory: mobile/web
run: npm run build:wasm
- name: build web production bundle
working-directory: mobile/web
run: npm run build
- name: validate hosted web production contract
run: python scripts/security/validate_web_production_contract.py
fuzz-smoke:
if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- name: install cargo-fuzz
run: cargo install cargo-fuzz
- name: fuzz tlv smoke
working-directory: crates/pqmsg-core
run: cargo fuzz run fuzz_tlv_decode -- -max_total_time=20
- name: fuzz wire smoke
working-directory: crates/pqmsg-core
run: cargo fuzz run fuzz_wire_decode -- -max_total_time=20
- name: fuzz handshake decode smoke
working-directory: crates/pqmsg-core
run: cargo fuzz run fuzz_handshake_decode -- -max_total_time=20
- name: fuzz sealed decode smoke
working-directory: crates/pqmsg-core
run: cargo fuzz run fuzz_sealed_decode -- -max_total_time=20
- name: fuzz algorithm dispatch smoke
working-directory: crates/pqmsg-core
run: cargo fuzz run fuzz_algorithm_dispatch -- -max_total_time=20
proverif-gate:
# Full symbolic proofs can diverge; keep them off the push/PR critical path.
if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: ocaml/setup-ocaml@v3
with:
ocaml-compiler: "4.14"
- name: install proverif
run: opam install -y proverif
- name: verify PQXDH hybrid model
run: |
opam exec -- proverif verification/proverif/pqxdh_hybrid_model.pv 2>&1 | tee /tmp/proverif.log
if grep -q "RESULT.*is true" /tmp/proverif.log && ! grep -q "RESULT.*is false" /tmp/proverif.log; then
echo "ProVerif: all queries verified"
else
echo "ProVerif: one or more queries FAILED"
exit 1
fi
pentest-smoke:
if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: start server and run penetration smoke script
run: |
chmod +x scripts/security/pentest_smoke.sh
export PQMSG_DATABASE_URL='sqlite://./pqmsg-server.db?mode=rwc'
export PQMSG_BIND='127.0.0.1:3000'
export PQMSG_SECURITY_PROFILE='research'
cargo run -p pqmsg-server > /tmp/pqmsg-server.log 2>&1 &
SERVER_PID=$!
trap 'kill $SERVER_PID' EXIT
sleep 5
./scripts/security/pentest_smoke.sh http://127.0.0.1:3000
sbom:
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: install cargo-cyclonedx
run: cargo install cargo-cyclonedx --locked
- name: generate SBOM
run: cargo cyclonedx --manifest-path Cargo.toml --format json --all
- name: upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom-cyclonedx
path: "**/bom.json"
alertmanager-config-smoke:
if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: validate alertmanager configuration
run: |
cp observability/alertmanager/alertmanager.yml /tmp/alertmanager.rendered.yml
sed -i 's|${ALERT_EMAIL_SMARTHOST}|smtp.example.com:25|g' /tmp/alertmanager.rendered.yml
sed -i 's|${ALERT_EMAIL_FROM}|pqmsg-alerts@example.com|g' /tmp/alertmanager.rendered.yml
sed -i 's|${ALERT_EMAIL_USERNAME}|pqmsg-alerts@example.com|g' /tmp/alertmanager.rendered.yml
sed -i 's|${ALERT_EMAIL_PASSWORD}|test-password|g' /tmp/alertmanager.rendered.yml
sed -i 's|${ALERT_EMAIL_REQUIRE_TLS}|false|g' /tmp/alertmanager.rendered.yml
sed -i 's|${ALERT_EMAIL_CRITICAL_TO}|secops-critical@example.com|g' /tmp/alertmanager.rendered.yml
sed -i 's|${ALERT_EMAIL_HIGH_TO}|secops-high@example.com|g' /tmp/alertmanager.rendered.yml
sed -i 's|${ALERT_EMAIL_STANDARD_TO}|secops-standard@example.com|g' /tmp/alertmanager.rendered.yml
docker run --rm \
--entrypoint amtool \
-v "/tmp/alertmanager.rendered.yml:/etc/alertmanager/alertmanager.yml:ro" \
prom/alertmanager:v0.28.0 \
check-config /etc/alertmanager/alertmanager.yml