Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions integration-tests/tests/cache_behavior.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
mod common;

use common::*;
use std::time::Duration;
use tokio::time::sleep;

#[tokio::test]
async fn test_stale_cache_during_secret_update() {
let secrets = TestSecrets::setup().await;
let secret_name = secrets.secret_name(SecretType::Basic);

let agent = AgentProcess::start().await;

// First request - populate cache with original value
let query = AgentQueryBuilder::default()
.secret_id(&secret_name)
.build()
.unwrap();
let response1 = agent.make_request(&query).await;
let json1: serde_json::Value = serde_json::from_str(&response1).unwrap();
let original_secret = json1["SecretString"].as_str().unwrap();
assert!(original_secret.contains("testuser"));

// Update the secret in AWS (simulating manual update, not automatic rotation)
let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
let client = aws_sdk_secretsmanager::Client::new(&config);

let updated_secret_value = r#"{"username":"rotateduser","password":"rotatedpass123"}"#;
client
.update_secret()
.secret_id(&secret_name)
.secret_string(updated_secret_value)
.send()
.await
.expect("Failed to update secret");

// Wait for the update to propagate
sleep(Duration::from_secs(2)).await;

// Second request without refreshNow - should return stale cached value
let response2 = agent.make_request(&query).await;
let json2: serde_json::Value = serde_json::from_str(&response2).unwrap();
let cached_secret = json2["SecretString"].as_str().unwrap();

// Should still have the old value from cache
assert!(cached_secret.contains("testuser"));
assert!(!cached_secret.contains("rotateduser"));

// Third request with refreshNow=true - should get fresh value
let refresh_query = AgentQueryBuilder::default()
.secret_id(&secret_name)
.refresh_now(true)
.build()
.unwrap();
let response3 = agent.make_request(&refresh_query).await;
let json3: serde_json::Value = serde_json::from_str(&response3).unwrap();
let fresh_secret = json3["SecretString"].as_str().unwrap();

// Should now have the updated value
assert!(fresh_secret.contains("rotateduser"));
assert!(!fresh_secret.contains("testuser"));
}

#[tokio::test]
async fn test_cache_expiration_and_refresh() {
let secrets = TestSecrets::setup().await;
let secret_name = secrets.secret_name(SecretType::Basic);

// Start agent with short TTL (5 seconds) for faster testing
let agent = AgentProcess::start_with_config(2777, 5).await;

let query = AgentQueryBuilder::default()
.secret_id(&secret_name)
.build()
.unwrap();

// First request - populate cache
let response1 = agent.make_request(&query).await;
let json1: serde_json::Value = serde_json::from_str(&response1).unwrap();
let version1 = json1["VersionId"].as_str().unwrap();
assert!(json1["SecretString"].as_str().unwrap().contains("testuser"));

// Second request immediately - should hit cache (same version)
let response2 = agent.make_request(&query).await;
let json2: serde_json::Value = serde_json::from_str(&response2).unwrap();
assert_eq!(json1["VersionId"], json2["VersionId"]);

// Update secret while cache is still valid
let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
let client = aws_sdk_secretsmanager::Client::new(&config);

client
.update_secret()
.secret_id(&secret_name)
.secret_string(r#"{"username":"expireduser","password":"expiredpass789"}"#)
.send()
.await
.expect("Failed to update secret");

// Wait for update to propagate
sleep(Duration::from_secs(2)).await;

// Third request before TTL expires - should still return cached value
let response3 = agent.make_request(&query).await;
let json3: serde_json::Value = serde_json::from_str(&response3).unwrap();
assert_eq!(json3["VersionId"], version1); // Same version as cached
assert!(json3["SecretString"].as_str().unwrap().contains("testuser"));

// Wait for TTL to expire (5 seconds + buffer)
sleep(Duration::from_secs(4)).await;

// Fourth request after TTL expiry - should fetch fresh value from AWS
let response4 = agent.make_request(&query).await;
let json4: serde_json::Value = serde_json::from_str(&response4).unwrap();

// Should now have the updated value and different version
assert_ne!(json4["VersionId"], version1);
assert!(json4["SecretString"]
.as_str()
.unwrap()
.contains("expireduser"));
assert!(!json4["SecretString"].as_str().unwrap().contains("testuser"));

// Fifth request immediately after - should use newly cached value
let response5 = agent.make_request(&query).await;
let json5: serde_json::Value = serde_json::from_str(&response5).unwrap();
assert_eq!(json4["VersionId"], json5["VersionId"]); // Same as previous
assert!(json5["SecretString"]
.as_str()
.unwrap()
.contains("expireduser"));
}
81 changes: 81 additions & 0 deletions integration-tests/tests/cross_account_access.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
mod common;

use common::*;

#[tokio::test]
async fn test_cross_account_secret_access() {
let secrets = TestSecrets::setup().await;
let secret_name = secrets.secret_name(SecretType::Basic);

let agent = AgentProcess::start().await;

// Get the ARN of the secret for cross-account testing
let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
let client = aws_sdk_secretsmanager::Client::new(&config);

let describe_response = client
.describe_secret()
.secret_id(&secret_name)
.send()
.await
.expect("Failed to describe secret");

let secret_arn = describe_response.arn().expect("Secret ARN not found");

// Test accessing secret by ARN (simulates cross-account access pattern)
let arn_query = AgentQueryBuilder::default()
.secret_id(secret_arn)
.build()
.unwrap();
let arn_response = agent.make_request(&arn_query).await;
let arn_json: serde_json::Value = serde_json::from_str(&arn_response).unwrap();

// Verify the response contains the correct data
assert_eq!(arn_json["ARN"], secret_arn);
assert_eq!(arn_json["Name"], secret_name);
assert!(arn_json["SecretString"]
.as_str()
.unwrap()
.contains("testuser"));
assert!(arn_json["VersionId"].is_string());

// Test accessing secret by name (same account pattern)
let name_query = AgentQueryBuilder::default()
.secret_id(&secret_name)
.build()
.unwrap();
let name_response = agent.make_request(&name_query).await;
let name_json: serde_json::Value = serde_json::from_str(&name_response).unwrap();

// Both responses should contain the same secret data
assert_eq!(arn_json["VersionId"], name_json["VersionId"]);
assert_eq!(arn_json["SecretString"], name_json["SecretString"]);
assert_eq!(arn_json["CreatedDate"], name_json["CreatedDate"]);

// Test that ARN-based access works with version stages
let arn_version_query = AgentQueryBuilder::default()
.secret_id(secret_arn)
.version_stage("AWSCURRENT")
.build()
.unwrap();
let arn_version_response = agent.make_request(&arn_version_query).await;
let arn_version_json: serde_json::Value = serde_json::from_str(&arn_version_response).unwrap();

assert_eq!(arn_version_json["ARN"], secret_arn);
assert!(arn_version_json["VersionStages"]
.as_array()
.unwrap()
.contains(&serde_json::Value::String("AWSCURRENT".to_string())));

// Test that ARN-based access works with refreshNow
let arn_refresh_query = AgentQueryBuilder::default()
.secret_id(secret_arn)
.refresh_now(true)
.build()
.unwrap();
let arn_refresh_response = agent.make_request(&arn_refresh_query).await;
let arn_refresh_json: serde_json::Value = serde_json::from_str(&arn_refresh_response).unwrap();

assert_eq!(arn_refresh_json["ARN"], secret_arn);
assert_eq!(arn_refresh_json["VersionId"], arn_json["VersionId"]);
}
111 changes: 111 additions & 0 deletions integration-tests/tests/version_management.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
mod common;

use common::*;
use std::time::Duration;
use tokio::time::sleep;

#[tokio::test]
async fn test_version_stage_transitions() {
let secrets = TestSecrets::setup().await;
let secret_name = secrets.secret_name(SecretType::Versioned);

let agent = AgentProcess::start().await;

// Wait for AWSPENDING version to be available
let _ = secrets
.wait_for_pending_version(SecretType::Versioned)
.await;

// Get the version IDs for both stages
let (current_version_id, pending_version_id) =
secrets.get_version_ids(SecretType::Versioned).await;

// Test AWSPENDING stage before promotion
let pending_query = AgentQueryBuilder::default()
.secret_id(&secret_name)
.version_stage("AWSPENDING")
.build()
.unwrap();
let pending_response = agent.make_request(&pending_query).await;
let pending_json: serde_json::Value = serde_json::from_str(&pending_response).unwrap();

assert_eq!(pending_json["VersionId"], pending_version_id);
assert!(pending_json["SecretString"]
.as_str()
.unwrap()
.contains("pendinguser"));

// Test AWSCURRENT stage before promotion
let current_query = AgentQueryBuilder::default()
.secret_id(&secret_name)
.version_stage("AWSCURRENT")
.build()
.unwrap();
let current_response = agent.make_request(&current_query).await;
let current_json: serde_json::Value = serde_json::from_str(&current_response).unwrap();

assert_eq!(current_json["VersionId"], current_version_id);
assert!(current_json["SecretString"]
.as_str()
.unwrap()
.contains("currentuser"));

// Promote AWSPENDING to AWSCURRENT using update_secret_version_stage
let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
let client = aws_sdk_secretsmanager::Client::new(&config);

client
.update_secret_version_stage()
.secret_id(&secret_name)
.version_stage("AWSCURRENT")
.move_to_version_id(&pending_version_id)
.remove_from_version_id(&current_version_id)
.send()
.await
.expect("Failed to promote version stage");

// Wait for the promotion to propagate
sleep(Duration::from_secs(3)).await;

// Test that AWSCURRENT now points to the previously pending version (with refreshNow)
let promoted_query = AgentQueryBuilder::default()
.secret_id(&secret_name)
.version_stage("AWSCURRENT")
.refresh_now(true)
.build()
.unwrap();
let promoted_response = agent.make_request(&promoted_query).await;
let promoted_json: serde_json::Value = serde_json::from_str(&promoted_response).unwrap();

// After promotion, AWSCURRENT should now have the pending version ID and content
assert_eq!(promoted_json["VersionId"], pending_version_id);
assert!(promoted_json["SecretString"]
.as_str()
.unwrap()
.contains("pendinguser"));
assert!(promoted_json["VersionStages"]
.as_array()
.unwrap()
.contains(&serde_json::Value::String("AWSCURRENT".to_string())));

// Verify the old current version is no longer AWSCURRENT
let old_current_query = AgentQueryBuilder::default()
.secret_id(&secret_name)
.version_id(&current_version_id)
.refresh_now(true)
.build()
.unwrap();
let old_current_response = agent.make_request(&old_current_query).await;
let old_current_json: serde_json::Value = serde_json::from_str(&old_current_response).unwrap();

// The old version should still exist but not have AWSCURRENT stage
assert_eq!(old_current_json["VersionId"], current_version_id);
assert!(old_current_json["SecretString"]
.as_str()
.unwrap()
.contains("currentuser"));
assert!(!old_current_json["VersionStages"]
.as_array()
.unwrap()
.contains(&serde_json::Value::String("AWSCURRENT".to_string())));
}
Loading