stemedb/crates/stemedb-api/tests/security_hardening.rs
jordan 02ecac9a07 fix: merge upstream 10 commits, fix DashMap deadlock, deterministic sim ingestion
Merged 10 upstream commits (MemTable, read-your-writes tests, feed endpoint,
security hardening, signed assertions, source registry, dashboard enhancements)
and fixed all test failures across the full workspace (2656/2656 passing).

Key fixes:
- fix(cluster): DashMap deadlock in swim.rs suspect_node/fail_node/alive_node
  - DashMap::get_mut RefMut + iter() on same map = non-reentrant write lock deadlock
  - Fix: extract clone in scoped block to drop RefMut before calling update_node_gauges()
  - 6 previously-hanging SWIM tests now pass in <2s
- fix(sim): replace background-task+polling ingestion with synchronous process_pending()
  - smoke_high_volume_simulation was CPU-starved under 2656 parallel tests
  - Removed ingestor.start() + wait_until_ingested() pattern throughout sim
  - All arena functions now call ingestor.process_pending() directly (deterministic)
- fix(test): v2 signature helper used wrong hash (rkyv vs canonical compute_content_hash_v2)
- fix(test): quota test signed "test" but v1 requires "subject:predicate" format
- fix(test): http_validation now accepts 400 for valid-format-but-invalid-crypto hex
- fix(test): scale_adaptive micro tier assertions updated (auto_promote upstream change)
- config: add nextest.toml with slow-timeout for background-task-tests group

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 20:27:32 -07:00

247 lines
7.7 KiB
Rust

//! Integration tests for P5.1 Security Hardening features.
//!
//! This test suite validates all 5 security hardening features:
//! 1. TLS/HTTPS (certificate validation)
//! 2. Body Limit Middleware (1MB write, 64KB read)
//! 3. Timeout Middleware (30s HTTP, 5s store)
//! 4. Secret Sanitization (no raw keys in logs)
//! 5. Rate Limiting (1 req/sec per IP for /v1/health)
// NOTE: These tests require additional setup and are marked as #[ignore] for now.
// Run with: cargo test --test security_hardening -- --ignored
#[cfg(test)]
mod tls_tests {
#[test]
#[ignore = "TLS tests require self-signed certificate generation"]
fn test_tls_connection() {
// TODO: Start server with self-signed cert
// Make HTTPS request with reqwest
// Verify successful connection
todo!("Implement TLS connection test")
}
#[test]
#[ignore = "TLS tests require self-signed certificate generation"]
fn test_tls_certificate_validation() {
// TODO: Start server with invalid cert
// Request should fail with TLS error
todo!("Implement certificate validation test")
}
#[test]
#[ignore = "TLS tests require certificate setup"]
fn test_plaintext_mode_when_no_tls_config() {
// TODO: Start server without TLS env vars
// Verify server starts in plaintext mode
// Verify HTTP (not HTTPS) works
todo!("Implement plaintext fallback test")
}
}
#[cfg(test)]
mod body_limit_tests {
#[test]
#[ignore = "Body limit tests require test server"]
fn test_write_endpoint_rejects_oversized_payload() {
// TODO: POST to /v1/assert with 1MB + 1 byte
// Should get 413 Payload Too Large
todo!("Implement write body limit test")
}
#[test]
#[ignore = "Body limit tests require test server"]
fn test_read_endpoint_rejects_oversized_payload() {
// TODO: GET to /v1/query with 64KB + 1 byte
// Should get 413 Payload Too Large
todo!("Implement read body limit test")
}
#[test]
#[ignore = "Body limit tests require test server"]
fn test_health_endpoint_no_limit() {
// TODO: GET to /v1/health
// Should succeed regardless of size
todo!("Implement health endpoint no-limit test")
}
#[test]
#[ignore = "Body limit tests require test server"]
fn test_write_endpoint_accepts_max_size() {
// TODO: POST to /v1/assert with exactly 1MB
// Should succeed
todo!("Implement write max size test")
}
}
#[cfg(test)]
mod timeout_tests {
#[test]
#[ignore = "Timeout tests require mock slow handlers"]
fn test_http_timeout() {
// TODO: Mock slow handler (>30s)
// Should timeout with 408
todo!("Implement HTTP timeout test")
}
#[test]
#[ignore = "Timeout tests require mock slow store"]
fn test_store_timeout() {
// TODO: Mock slow store operation (>5s)
// Should timeout with 500
todo!("Implement store timeout test")
}
#[test]
#[ignore = "Timeout tests require metrics verification"]
fn test_timeout_metrics_increment() {
// TODO: Trigger timeout
// Verify stemedb_operation_timeouts_total increments
todo!("Implement timeout metrics test")
}
}
#[cfg(test)]
mod secret_sanitization_tests {
#[test]
#[ignore = "Secret sanitization tests require log capture"]
fn test_no_raw_keys_in_logs() {
// TODO: Capture logs during API key operations
// Verify no raw keys appear (no strings matching [A-Za-z0-9]{12,})
// Should only see hashes (16-char hex strings)
todo!("Implement log sanitization test")
}
#[test]
#[ignore = "Secret sanitization tests require API key bootstrap"]
fn test_bootstrap_logs_hash_not_prefix() {
// TODO: Bootstrap root API key
// Capture logs
// Verify log contains key_hash, not key_prefix
todo!("Implement bootstrap sanitization test")
}
#[test]
#[ignore = "Secret sanitization tests require API key creation"]
fn test_create_api_key_logs_hash_not_prefix() {
// TODO: Create API key via POST /v1/admin/api-keys
// Capture logs
// Verify log contains key_hash, not key_prefix
todo!("Implement create API key sanitization test")
}
#[test]
#[ignore = "Secret sanitization tests require API key rotation"]
fn test_rotate_api_key_logs_hash_not_prefix() {
// TODO: Rotate API key via POST /v1/admin/api-keys/:hash/rotate
// Capture logs
// Verify log contains key_hash, not key_prefix
todo!("Implement rotate API key sanitization test")
}
}
#[cfg(test)]
mod rate_limit_tests {
#[test]
#[ignore = "Rate limit tests require test server"]
fn test_health_endpoint_rate_limit() {
// TODO: Send 10 requests to /v1/health in <1s
// 9 should get 429 Too Many Requests
todo!("Implement health endpoint rate limit test")
}
#[test]
#[ignore = "Rate limit tests require test server"]
fn test_rate_limit_per_ip() {
// TODO: Send from different IPs
// No interference between IPs
todo!("Implement per-IP rate limit test")
}
#[test]
#[ignore = "Rate limit tests require test server"]
fn test_rate_limit_allows_one_per_second() {
// TODO: Send 1 req/sec to /v1/health
// All should succeed
todo!("Implement 1 req/sec success test")
}
#[test]
#[ignore = "Rate limit tests require metrics verification"]
fn test_rate_limit_metrics_increment() {
// TODO: Trigger rate limit rejection
// Verify stemedb_rate_limit_rejections_total increments
todo!("Implement rate limit metrics test")
}
#[test]
#[ignore = "Rate limit tests require test server"]
fn test_rate_limit_retry_after_header() {
// TODO: Trigger rate limit
// Verify 429 response has retry_after_secs field
todo!("Implement retry-after header test")
}
}
#[cfg(test)]
mod integration_tests {
#[test]
#[ignore = "Integration tests require full server setup"]
fn test_all_security_features_enabled() {
// TODO: Start server with:
// - TLS enabled
// - Body limits active
// - Timeouts configured
// - Rate limiting active
// Verify all features work together
todo!("Implement full integration test")
}
#[test]
#[ignore = "Integration tests require configuration testing"]
fn test_security_features_configurable_via_env() {
// TODO: Test that all env vars work:
// - STEMEDB_TLS_CERT_PATH / STEMEDB_TLS_KEY_PATH
// - STEMEDB_WRITE_BODY_LIMIT / STEMEDB_READ_BODY_LIMIT (when implemented)
// - STEMEDB_HTTP_TIMEOUT_SECS (when implemented)
// - STEMEDB_HEALTH_RATE_LIMIT (when implemented)
todo!("Implement configuration test")
}
}
// Helper functions for test setup
#[cfg(test)]
mod test_helpers {
/// Generate self-signed certificate for testing.
#[allow(dead_code)]
fn generate_self_signed_cert() -> (Vec<u8>, Vec<u8>) {
// TODO: Implement self-signed cert generation
// Return (cert_pem, key_pem)
todo!("Implement self-signed cert generation")
}
/// Start test server with given configuration.
#[allow(dead_code)]
async fn start_test_server(/* config */) {
// TODO: Implement test server startup
todo!("Implement test server startup")
}
/// Capture log output during test.
#[allow(dead_code)]
fn capture_logs<F>(_f: F) -> String
where
F: FnOnce(),
{
// TODO: Implement log capture using tracing-subscriber test subscriber
todo!("Implement log capture")
}
}