provisioning-platform/crates/orchestrator/tests/security_integration_tests.rs

319 lines
10 KiB
Rust
Raw Permalink Normal View History

//! Integration tests for complete authentication and authorization flow
//!
//! Tests the full security middleware chain:
//! 1. Rate limiting
//! 2. JWT authentication
//! 3. MFA verification
//! 4. Cedar authorization
//! 5. Audit logging
//!
//! NOTE: These tests are disabled due to API type mismatches
//! (Principal, Resource, AuthorizationContext struct changes)
// Disabled: API type mismatches
#![allow(unexpected_cfgs)]
#![cfg(feature = "security_integration_tests")]
#![allow(unused_imports, unused_variables)]
use std::net::IpAddr;
use std::str::FromStr;
use std::sync::Arc;
use axum::{
body::Body,
http::{header, Request, StatusCode},
Router,
};
use chrono::Utc;
use provisioning_orchestrator::{
audit::{AuditLogger, AuditLoggerConfig, FileStorage},
middleware::{AuthMiddleware, AuthzMiddleware, MfaMiddleware, RateLimitConfig, RateLimiter},
security::{
Action, AuthorizationContext, AuthorizationRequest, CedarEngine, Principal, Resource,
TokenValidator,
},
security_integration::{SecurityComponents, SecurityConfig},
};
use tempfile::TempDir;
use tower::ServiceExt;
/// Helper to create test security components
async fn create_test_security() -> (SecurityComponents, Arc<AuditLogger>, TempDir) {
// Create temp directory for audit logs
let temp_dir = TempDir::new().unwrap();
// Create audit logger
let storage = Arc::new(
FileStorage::new(temp_dir.path().to_path_buf(), 10 * 1024 * 1024)
.await
.unwrap(),
);
let audit_logger = Arc::new(
AuditLogger::new(AuditLoggerConfig::default(), storage)
.await
.unwrap(),
);
// Create security components (disabled for testing)
let security = SecurityComponents::disabled(audit_logger.clone());
(security, audit_logger, temp_dir)
}
/// Helper to create a test JWT token
fn create_test_token(
validator: &TokenValidator,
user_id: &str,
workspace: &str,
mfa_verified: bool,
) -> String {
use jsonwebtoken::{encode, EncodingKey, Header};
use provisioning_orchestrator::security::TokenClaims;
use rand::rngs::OsRng;
use rsa::{pkcs8::EncodePrivateKey, RsaPrivateKey};
// Generate a temporary private key for testing
let mut rng = OsRng;
let private_key = RsaPrivateKey::new(&mut rng, 2048).unwrap();
let private_pem = private_key
.to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)
.unwrap();
let claims = TokenClaims {
jti: uuid::Uuid::new_v4().to_string(),
sub: user_id.to_string(),
workspace: workspace.to_string(),
permissions_hash: "test_hash".to_string(),
token_type: provisioning_orchestrator::security::TokenType::Access,
iat: Utc::now().timestamp(),
exp: (Utc::now() + chrono::Duration::hours(1)).timestamp(),
iss: "control-center".to_string(),
aud: vec!["orchestrator".to_string()],
metadata: Some({
let mut map = std::collections::HashMap::new();
map.insert(
"permissions".to_string(),
serde_json::json!(["read", "write", "create", "delete"]),
);
map.insert("mfa_verified".to_string(), serde_json::json!(mfa_verified));
map
}),
};
encode(
&Header::new(jsonwebtoken::Algorithm::RS256),
&claims,
&EncodingKey::from_rsa_pem(private_pem.as_bytes()).unwrap(),
)
.unwrap()
}
#[tokio::test]
async fn test_rate_limiting_enforcement() {
let config = RateLimitConfig::new(3, 60); // 3 requests per minute
let limiter = Arc::new(RateLimiter::with_config(config));
let ip = IpAddr::from_str("192.168.1.100").unwrap();
// First 3 requests should succeed
for i in 1..=3 {
let result = limiter.check(ip).await;
assert!(result.is_ok(), "Request {} should succeed", i);
}
// 4th request should fail
let result = limiter.check(ip).await;
assert!(result.is_err(), "Request 4 should be rate limited");
// Check stats
let stats = limiter.get_stats().await;
assert_eq!(stats.total_ips, 1);
assert_eq!(stats.limited_ips, 1);
}
#[tokio::test]
async fn test_authentication_missing_token() {
let (security, _, _temp_dir) = create_test_security().await;
// Create a simple test router
let app = Router::new()
.route("/test", axum::routing::get(|| async { "OK" }))
.layer(axum::middleware::from_fn_with_state(
security.auth_middleware.clone(),
provisioning_orchestrator::middleware::auth_middleware,
));
// Request without token
let request = Request::builder().uri("/test").body(Body::empty()).unwrap();
let response = app.oneshot(request).await.unwrap();
// Should be unauthorized (note: our disabled middleware might allow it)
// In real scenario with enabled auth, this would be 401
println!("Status: {}", response.status());
}
#[tokio::test]
async fn test_mfa_verification_for_sensitive_operations() {
use provisioning_orchestrator::middleware::mfa_middleware;
// Test that DELETE operations require MFA
let paths_requiring_mfa = vec![
("/api/v1/servers/123", "DELETE"),
("/api/v1/production/deploy", "POST"),
("/api/v1/cluster/prod", "DELETE"),
("/batch/submit", "POST"),
];
for (path, method) in paths_requiring_mfa {
println!("Testing MFA requirement for {} {}", method, path);
// In actual implementation, we'd test with security context
// This demonstrates the test structure
}
}
#[tokio::test]
async fn test_authorization_policy_evaluation() {
let (security, _, _temp_dir) = create_test_security().await;
// Create a Cedar authorization request
let principal = Principal::User("user123".to_string());
let action = Action::Delete;
let resource = Resource::Server("prod-server-01".to_string());
let context = AuthorizationContext {
mfa_verified: true,
ip_address: "192.168.1.1".to_string(),
time: Utc::now(),
workspace: "production".to_string(),
};
let request = AuthorizationRequest {
principal,
action,
resource,
context,
};
// Evaluate (will use empty policy set in disabled mode)
let decision = security.cedar_engine.authorize(&request).await;
// With no policies, should deny by default
assert!(decision.is_ok());
}
#[tokio::test]
async fn test_complete_security_flow() {
let (security, audit_logger, _temp_dir) = create_test_security().await;
// Create a test router with all middleware
let app = Router::new()
.route("/test", axum::routing::get(|| async { "OK" }))
// Apply security middleware (in correct order)
.layer(axum::middleware::from_fn_with_state(
security.authz_middleware.clone(),
provisioning_orchestrator::middleware::authz_middleware,
))
.layer(axum::middleware::from_fn(
provisioning_orchestrator::middleware::mfa_middleware,
))
.layer(axum::middleware::from_fn_with_state(
security.auth_middleware.clone(),
provisioning_orchestrator::middleware::auth_middleware,
))
.layer(axum::middleware::from_fn({
let limiter = security.rate_limiter.clone();
move |req, next| {
let limiter = limiter.clone();
async move {
provisioning_orchestrator::middleware::rate_limit_middleware(limiter, req, next)
.await
}
}
}));
// Make a request (without proper auth in disabled mode)
let request = Request::builder()
.uri("/test")
.header("X-Forwarded-For", "192.168.1.1")
.body(Body::empty())
.unwrap();
let response = app.oneshot(request).await.unwrap();
println!("Complete flow status: {}", response.status());
}
#[tokio::test]
async fn test_rate_limit_stats() {
let config = RateLimitConfig::new(10, 60);
let limiter = Arc::new(RateLimiter::with_config(config));
let ip1 = IpAddr::from_str("192.168.1.1").unwrap();
let ip2 = IpAddr::from_str("192.168.1.2").unwrap();
// Make requests from two different IPs
for _ in 0..5 {
limiter.check(ip1).await.unwrap();
}
for _ in 0..15 {
let _ = limiter.check(ip2).await; // Will fail after 10
}
let stats = limiter.get_stats().await;
assert_eq!(stats.total_ips, 2);
assert_eq!(stats.total_requests, 15); // 5 + 10
assert_eq!(stats.limited_ips, 1); // ip2 hit limit
}
#[tokio::test]
async fn test_security_components_initialization() {
// Test that components can be disabled
let temp_dir = TempDir::new().unwrap();
let storage = Arc::new(
FileStorage::new(temp_dir.path().to_path_buf(), 10 * 1024 * 1024)
.await
.unwrap(),
);
let audit_logger = Arc::new(
AuditLogger::new(AuditLoggerConfig::default(), storage)
.await
.unwrap(),
);
let security = SecurityComponents::disabled(audit_logger);
assert!(!security.auth_middleware.is_enabled());
assert!(!security.mfa_middleware.is_enabled());
assert!(!security.authz_middleware.is_enabled());
}
#[test]
fn test_security_config_defaults() {
let config = SecurityConfig::default();
assert!(config.auth_enabled);
assert!(config.authz_enabled);
assert!(config.mfa_enabled);
assert_eq!(config.jwt_issuer, "control-center");
assert_eq!(config.jwt_audience, "orchestrator");
}
#[tokio::test]
async fn test_exempt_ip_rate_limiting() {
let exempt_ip = IpAddr::from_str("10.0.0.1").unwrap();
let config = RateLimitConfig::new(5, 60).with_exempt_ip(exempt_ip);
let limiter = Arc::new(RateLimiter::with_config(config));
// Exempt IP should be able to make unlimited requests
for _ in 0..20 {
assert!(limiter.check(exempt_ip).await.is_ok());
}
// Non-exempt IP should be limited
let normal_ip = IpAddr::from_str("192.168.1.1").unwrap();
for _ in 0..5 {
assert!(limiter.check(normal_ip).await.is_ok());
}
assert!(limiter.check(normal_ip).await.is_err());
}