ci #266
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |