csaf-models 0.3.2

CSAF 2.0/2.1 data models, SQLite management, and user models
Documentation

ndaal CSAF-CRUD

Secure, single-binary Rust CRUD application for managing CSAF 2.0 and 2.1 security advisories. Provides both a server-side rendered web UI (Hyper + Bootstrap 5 + HTMX) and a RESTful HATEOAS JSON API under /api/v1/.

Features

  • CSAF 2.0 / 2.1 — Full serde types, strict validation, dual CVSS v3.1 + v4.0 scoring.
  • Classification support — TLP 2.0 colour-coded selector (CLEAR/GREEN/AMBER/AMBER+STRICT/RED), German Verschlusssache (VS-NfD / VS-Vertr. / Geh. / Str. Geh.), and NATO (NR / NC / NS / CTS). Written into the exported CSAF either to document.distribution.text, to document.notes[], or to both — configurable per-deployment.
  • Dual embedded databaseredb for CSAF documents, rusqlite (bundled) for user management and audit logging.
  • Zero external dependencies — Single binary deployment.
  • Protocols — HTTP/1.1, HTTP/2, and HTTP/3 (QUIC) over TLS 1.3 via rustls, ring crypto, and quinn.
  • Self-signed certificates — Auto-generated via rcgen with 45-day validity (Let's Encrypt short-lived cert model).
  • Sidecar hashes — SHA-256 (.sha-256), SHA-512 (.sha-512), and SHA3-512 (.sha3-512) generated for every export and database dump, each algorithm individually toggleable via settings. The hyphenated extension form matches CLAUDE.md §"Cryptographic hashes (release + CSAF)".
  • Rolling file loggingtracing-appender writes a daily csaf-crud.<date>.log under the configurable Log Directory (default ./data_log); stderr output is preserved. RUST_LOG governs both sinks.
  • Audit Log export — The /admin/export page grows an Export Audit Log card that emits the audit_log table as Markdown, CSV, JSON, and SARIF 2.1.0 — each payload ships the three hash sidecars above. SARIF output is self-validated before write (version, schema, tool.driver.name, every result has a ruleId / message.text / recognised level).
  • Reset to defaults — The /settings page carries a confirmation- guarded Reset to default settings button that restores every field to Settings::default() and records a settings_reset row in the audit log.
  • HATEOAS API — HAL-like _links in every response, RFC 9457 Problem Details error format.
  • Menu structure — CSAF (CRUD), Administration (Import/Export), Settings, Info (About/License/System/Privacy/ Security).
  • Audit logging — Every create/update/delete/import/export recorded with ISO 8601 timestamps.
  • Argon2id password hashing per RFC 9106.

Quick start

# Build
cargo build --release

# Start the server (TLS on 8180 for TCP, 8181 for QUIC)
cargo run --bin csaf-crud

# Use the CLI (published to crates.io as `ndaal-csaf-cli`)
cargo run --bin ndaal-csaf-cli -- import --directory test/csaf
cargo run --bin ndaal-csaf-cli -- validate \
  test/csaf/2026/003/ndaal-sa-2026-003.json
cargo run --bin ndaal-csaf-cli -- stats

Workspace layout

crates/
├── csaf-models/   # CSAF 2.0/2.1 serde types, SQLite pool,
│                  # User, AuditLog, Settings models
├── csaf-core/     # redb storage, validation, sidecar generation,
│                  # import/export, configuration
├── csaf-crud/      # Hyper server, HATEOAS API, HTML routes,
│                  # router, static files, TLS 1.3 setup
└── csaf-cli/      # clap-based CLI: import, export, validate,
                   # stats (published as `ndaal-csaf-cli`)

Testing

# All tests (unit + integration + crate integration)
cargo test --workspace

# Just integration tests
cargo test --workspace --test csaf_crud_cycle
cargo test --workspace --test test_crate_integrations

# Load test (oha, HTTP/2 keep-alive, --insecure for dev cert)
OHA_REQUESTS=1000 OHA_CONCURRENCY=20 test/loadtest/run.sh

Load-test results — port 8180, HTTP/2 over TLS 1.3

1000 requests per endpoint × 20 concurrent connections, oha --http2 --insecure against the locally-built release binary on Apple Silicon (MacBook). All seven endpoints hit 100 % success:

Endpoint req/s p95 latency
/api/v1 12,037 0.74 ms
/api/v1/settings 11,844 0.77 ms
/api/v1/system/health 9,070 0.96 ms
/api/v1/csaf?page=1&per_page=5 8,781 1.64 ms
/ (HTML dashboard) 7,918 2.19 ms
/api/v1/audit-log?page=1&per_page=5 7,572 2.24 ms
/static/img/logo.png 3,076 1.50 ms

Port 8181 (HTTP/3 over QUIC) is not exercised by this load test — oha, h2load, and Homebrew curl on this platform do not include an HTTP/3 / QUIC client. A dedicated QUIC-capable tool (e.g. h2load built with nghttp3 + ngtcp2, or neqo-client) is needed to benchmark 8181.

Test coverage includes:

  • Unit tests per crate (193 tests; +15 for SHA-512, log directory, reset-to-defaults, and the four audit-log export formats)
  • Integration tests — 10 end-to-end CRUD cycle tests, extended with a post-export assertion that all three hyphenated hash sidecars (.sha-256, .sha-512, .sha3-512) land on disk and no unhyphenated legacy form leaks.
  • Crate integration tests — 129 tests reusing patterns from redb, rusqlite, sha2/sha3, argon2, serde_json, chrono, regex, uuid, matchit, rustls/rcgen, sysinfo, and tracing-appender.
  • Fuzzing — 19 cargo-fuzz targets including three new 0.3.0 targets: fuzz_sidecar_hash_triplet, fuzz_sidecar_write_ext, and fuzz_audit_sarif. Parser changes are gated on 300 s per-target in CI.

Total: 302 tests passing.

Quality toolchain

cargo fmt --all
cargo clippy --workspace --all-targets --all-features -- \
  -D warnings
cargo test --workspace
cargo audit
cargo deny check
cargo machete --with-metadata
cargo llvm-cov --lcov --output-path target/coverage/lcov.info \
  --workspace --all-features
rust-doctor
cargo kani --workspace --output-format=terse   # nightly

Linters applied: cargo clippy (pedantic + nursery), yamllint, ryl, markdownlint, rumdl, hadolint, htmlhint, oxlint, ruff, bandit.

rust-doctor — workspace health score

┌────────────────────────────────────────────────────────┐
│ rust-doctor                                            │
│                                                        │
│ 100 / 100  Great                                       │
│                                                        │
│ ████████████████████████████████████████               │
│                                                        │
│ Security:       100                                    │
│ Reliability:    100                                    │
│ Maintainability: 100                                   │
│ Performance:    100                                    │
│ Dependencies:   100                                    │
│                                                        │
│ ✓ 0 error(s)  ✓ 0 warning(s)  ℹ 1 info(s)  38 files   │
└────────────────────────────────────────────────────────┘

Run rust-doctor locally (or rust-doctor --json for machine output) to reproduce. The session-level cache lives in each crate's .rust-doctor-cache.json; any regression greater than 5 points vs the previous run blocks the merge. Configuration is in rust-doctor.toml; every ignore rule carries a justification comment referencing the equivalent [workspace.lints.clippy] entry in Cargo.toml.

Security

  • TLS 1.3 only (no TLS 1.2 fallback)
  • rustls with ring crypto provider (no OpenSSL)
  • Argon2id for password hashing (RFC 9106)
  • Parameterized SQL queries throughout
  • No unsafe code (#![deny(unsafe_code)])
  • SHA-256, SHA-512, and SHA3-512 integrity sidecars — three orthogonal hash families for defence in depth
  • SARIF 2.1.0 audit-log export with built-in schema validation

Source code

Canonical repository (used by crates.io and cargo install):

Please open issues and merge requests there. No GitHub mirror is maintained; treat any third-party GitHub copy as untrusted.

License

Apache-2.0. See SPDX headers in each source file.

Author

Pierre Gronau · ndaal Gesellschaft für Sicherheit in der Informationstechnik mbH & Co KG · Cologne