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>
247 lines
7.7 KiB
Rust
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")
|
|
}
|
|
}
|