309 lines
8.8 KiB
Rust
309 lines
8.8 KiB
Rust
//! Integration Tests for Dynamic Secrets System
|
|
//!
|
|
//! Tests the complete secrets workflow including generation, tracking, and
|
|
//! revocation.
|
|
|
|
#![allow(
|
|
clippy::absurd_extreme_comparisons,
|
|
clippy::len_zero,
|
|
unused_comparisons
|
|
)]
|
|
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
|
|
use chrono::Duration;
|
|
use provisioning_orchestrator::secrets::{
|
|
RenewRequest, RevokeRequest, SecretMetadata, SecretRequest, SecretType, SecretsConfig,
|
|
SecretsService,
|
|
};
|
|
|
|
/// Create a test secrets service
|
|
async fn create_test_service() -> Arc<SecretsService> {
|
|
let config = SecretsConfig {
|
|
vault_enabled: false,
|
|
vault_addr: None,
|
|
default_ttl_hours: 1,
|
|
max_ttl_hours: 12,
|
|
auto_revoke_on_expiry: true,
|
|
warning_threshold_minutes: 5,
|
|
aws_account_id: Some("123456789012".to_string()),
|
|
aws_default_region: Some("us-east-1".to_string()),
|
|
upcloud_username: Some("test-user".to_string()),
|
|
upcloud_password: Some("test-pass".to_string()),
|
|
};
|
|
|
|
let service = Arc::new(SecretsService::new(config));
|
|
service.initialize().await.unwrap();
|
|
service
|
|
}
|
|
|
|
/// Create test secret request
|
|
fn create_test_request(secret_type: SecretType, ttl_hours: i64) -> SecretRequest {
|
|
SecretRequest {
|
|
secret_type,
|
|
ttl: Duration::hours(ttl_hours),
|
|
renewable: true,
|
|
parameters: HashMap::new(),
|
|
metadata: SecretMetadata {
|
|
user_id: "test-user".to_string(),
|
|
workspace: "test-workspace".to_string(),
|
|
purpose: "integration testing".to_string(),
|
|
infra: Some("test-infra".to_string()),
|
|
tags: HashMap::new(),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_generate_ssh_keypair() {
|
|
let service = create_test_service().await;
|
|
|
|
let request = create_test_request(SecretType::SshKeyPair, 1);
|
|
let secret = service.generate(request).await.unwrap();
|
|
|
|
assert_eq!(secret.secret_type, SecretType::SshKeyPair);
|
|
assert!(!secret.is_expired());
|
|
assert!(!secret.renewable);
|
|
|
|
// Verify tracked
|
|
let retrieved = service.get(&secret.id).await.unwrap();
|
|
assert_eq!(retrieved.id, secret.id);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_generate_aws_credentials() {
|
|
let service = create_test_service().await;
|
|
|
|
let mut request = create_test_request(SecretType::AwsSts, 1);
|
|
request
|
|
.parameters
|
|
.insert("role".to_string(), "deploy".to_string());
|
|
|
|
let secret = service.generate(request).await.unwrap();
|
|
|
|
assert_eq!(secret.secret_type, SecretType::AwsSts);
|
|
assert!(secret.renewable);
|
|
assert!(!secret.is_expired());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_generate_upcloud_subaccount() {
|
|
let service = create_test_service().await;
|
|
|
|
let mut request = create_test_request(SecretType::ApiToken, 2);
|
|
request
|
|
.parameters
|
|
.insert("roles".to_string(), "server,network".to_string());
|
|
|
|
let secret = service.generate(request).await.unwrap();
|
|
|
|
assert_eq!(secret.secret_type, SecretType::ApiToken);
|
|
assert!(!secret.renewable);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_revoke_secret() {
|
|
let service = create_test_service().await;
|
|
|
|
let request = create_test_request(SecretType::SshKeyPair, 1);
|
|
let secret = service.generate(request).await.unwrap();
|
|
|
|
// Verify exists
|
|
assert!(service.get(&secret.id).await.is_ok());
|
|
|
|
// Revoke
|
|
let revoke_request = RevokeRequest {
|
|
secret_id: secret.id.clone(),
|
|
reason: "test revocation".to_string(),
|
|
};
|
|
service.revoke(revoke_request).await.unwrap();
|
|
|
|
// Verify removed
|
|
assert!(service.get(&secret.id).await.is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_renew_aws_secret() {
|
|
let service = create_test_service().await;
|
|
|
|
let mut request = create_test_request(SecretType::AwsSts, 1);
|
|
request
|
|
.parameters
|
|
.insert("role".to_string(), "deploy".to_string());
|
|
|
|
let secret = service.generate(request).await.unwrap();
|
|
let original_expiry = secret.expires_at;
|
|
|
|
// Renew with new TTL
|
|
let renew_request = RenewRequest {
|
|
secret_id: secret.id.clone(),
|
|
ttl: Some(Duration::hours(2)),
|
|
};
|
|
|
|
let renewed = service.renew(renew_request).await.unwrap();
|
|
|
|
assert_eq!(renewed.id, secret.id);
|
|
assert!(renewed.expires_at > original_expiry);
|
|
assert_eq!(renewed.ttl, Duration::hours(2));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cannot_renew_ssh_key() {
|
|
let service = create_test_service().await;
|
|
|
|
let request = create_test_request(SecretType::SshKeyPair, 1);
|
|
let secret = service.generate(request).await.unwrap();
|
|
|
|
// Attempt renewal (should fail)
|
|
let renew_request = RenewRequest {
|
|
secret_id: secret.id.clone(),
|
|
ttl: Some(Duration::hours(2)),
|
|
};
|
|
|
|
let result = service.renew(renew_request).await;
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_list_secrets() {
|
|
let service = create_test_service().await;
|
|
|
|
// Generate multiple secrets
|
|
let request1 = create_test_request(SecretType::SshKeyPair, 1);
|
|
service.generate(request1).await.unwrap();
|
|
|
|
let mut request2 = create_test_request(SecretType::AwsSts, 1);
|
|
request2
|
|
.parameters
|
|
.insert("role".to_string(), "deploy".to_string());
|
|
service.generate(request2).await.unwrap();
|
|
|
|
// List all
|
|
let secrets = service.list().await;
|
|
assert!(secrets.len() >= 2);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_expiring_soon() {
|
|
let service = create_test_service().await;
|
|
|
|
// Generate secret with very short TTL (will appear in expiring list)
|
|
let request = create_test_request(SecretType::SshKeyPair, 1);
|
|
// Note: With warning_threshold_minutes: 5, a 1-hour secret won't be expiring
|
|
// soon We'd need to modify the test or use a shorter TTL
|
|
|
|
service.generate(request).await.unwrap();
|
|
|
|
// For now, just verify the call works (may return empty)
|
|
let expiring = service.expiring_soon().await;
|
|
assert!(expiring.len() >= 0); // May or may not have any
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_statistics() {
|
|
let service = create_test_service().await;
|
|
|
|
// Generate some secrets
|
|
let request1 = create_test_request(SecretType::SshKeyPair, 1);
|
|
service.generate(request1).await.unwrap();
|
|
|
|
let mut request2 = create_test_request(SecretType::AwsSts, 1);
|
|
request2
|
|
.parameters
|
|
.insert("role".to_string(), "deploy".to_string());
|
|
service.generate(request2).await.unwrap();
|
|
|
|
// Get stats
|
|
let stats = service.stats().await;
|
|
|
|
assert!(stats.total_generated >= 2);
|
|
assert!(stats.active_secrets >= 2);
|
|
assert!(stats.by_type.len() > 0);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_ttl_bounds() {
|
|
let service = create_test_service().await;
|
|
|
|
// Try to generate with excessive TTL (should be clamped)
|
|
let mut request = create_test_request(SecretType::SshKeyPair, 24); // 24 hours
|
|
request.ttl = Duration::hours(24);
|
|
|
|
let secret = service.generate(request).await.unwrap();
|
|
|
|
// Should be clamped to max_ttl_hours (12)
|
|
assert!(secret.ttl <= Duration::hours(12));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_concurrent_generation() {
|
|
let service = create_test_service().await;
|
|
|
|
let mut handles = vec![];
|
|
|
|
// Generate multiple secrets concurrently
|
|
for i in 0..5 {
|
|
let service = service.clone();
|
|
let handle = tokio::spawn(async move {
|
|
let mut request = create_test_request(SecretType::SshKeyPair, 1);
|
|
request.metadata.purpose = format!("concurrent test {}", i);
|
|
service.generate(request).await
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
// Wait for all to complete
|
|
let mut successes = 0;
|
|
for handle in handles {
|
|
if handle.await.unwrap().is_ok() {
|
|
successes += 1;
|
|
}
|
|
}
|
|
|
|
assert_eq!(successes, 5);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_missing_required_parameters() {
|
|
let service = create_test_service().await;
|
|
|
|
// Try to generate AWS credentials without role parameter
|
|
let request = create_test_request(SecretType::AwsSts, 1);
|
|
// No role parameter added
|
|
|
|
let result = service.generate(request).await;
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_secret_lifecycle() {
|
|
let service = create_test_service().await;
|
|
|
|
// 1. Generate
|
|
let request = create_test_request(SecretType::SshKeyPair, 1);
|
|
let secret = service.generate(request).await.unwrap();
|
|
let secret_id = secret.id.clone();
|
|
|
|
// 2. Retrieve
|
|
let retrieved = service.get(&secret_id).await.unwrap();
|
|
assert_eq!(retrieved.id, secret_id);
|
|
|
|
// 3. List (should be in list)
|
|
let secrets = service.list().await;
|
|
assert!(secrets.iter().any(|s| s.id == secret_id));
|
|
|
|
// 4. Revoke
|
|
let revoke_request = RevokeRequest {
|
|
secret_id: secret_id.clone(),
|
|
reason: "lifecycle test complete".to_string(),
|
|
};
|
|
service.revoke(revoke_request).await.unwrap();
|
|
|
|
// 5. Verify removed
|
|
assert!(service.get(&secret_id).await.is_err());
|
|
|
|
// 6. Not in list anymore
|
|
let secrets = service.list().await;
|
|
assert!(!secrets.iter().any(|s| s.id == secret_id));
|
|
}
|