Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ fabric.properties

# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
Expand Down
8 changes: 4 additions & 4 deletions aws_secretsmanager_agent/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,14 @@ mod tests {
use hyper::{client, Request, StatusCode};
use hyper_util::rt::TokioIo;
use serde_json::Value;
use std;

use std::net::{IpAddr, Ipv4Addr, SocketAddr};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::sync::{mpsc, Arc, Mutex};
use std::time::Duration;
use std::{fs, thread};
use tokio;

use tokio::net::TcpStream;
use tokio::task::JoinSet;
use tokio::time::timeout;
Expand Down Expand Up @@ -626,7 +626,7 @@ mod tests {
// Verify a query using the pending label
#[tokio::test]
async fn pending_success() {
let req = format!("/secretsmanager/get?secretId=MyTest&versionStage=AWSPENDING");
let req = "/secretsmanager/get?secretId=MyTest&versionStage=AWSPENDING".to_string();
let (status, body) = run_request(&req).await;
assert_eq!(status, StatusCode::OK);
validate_response_extra("MyTest", DEFAULT_VERSION, vec!["AWSPENDING"], body);
Expand Down Expand Up @@ -696,7 +696,7 @@ mod tests {
#[tokio::test]
async fn path_pending_success() {
let req = "/v1/My/Test?versionStage=AWSPENDING";
let (status, body) = run_request(&req).await;
let (status, body) = run_request(req).await;
assert_eq!(status, StatusCode::OK);
validate_response_extra("My/Test", DEFAULT_VERSION, vec!["AWSPENDING"], body);
}
Expand Down
26 changes: 11 additions & 15 deletions aws_secretsmanager_agent/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ pub mod tests {
// Used to inject env variable values for testing. Uses thread local data since
// multi-threaded tests setting process wide env variables can collide.
thread_local! {
static ENVVAR: RefCell<Option<Vec<(&'static str, &'static str)>>> = RefCell::new(None);
static ENVVAR: RefCell<Option<Vec<(&'static str, &'static str)>>> = const { RefCell::new(None) };
}
pub fn set_test_var(key: &'static str, val: &'static str) {
ENVVAR.set(Some(vec![(key, val)]));
Expand All @@ -227,9 +227,9 @@ pub mod tests {
return Err(VarError::NotPresent);
}
if let Some(varvec) = ENVVAR.with_borrow(|v| v.clone()) {
let found = varvec.iter().filter(|keyval| keyval.0 == key).next();
if found != None {
return Ok(found.unwrap().1.to_string());
let found = varvec.iter().find(|keyval| keyval.0 == key);
if let Some(found) = found {
return Ok(found.1.to_string());
}
} else {
// Return a default value if no value is injected.
Expand Down Expand Up @@ -310,18 +310,14 @@ pub mod tests {
file: Some(&tmpfile),
};
set_test_var("", "");
std::fs::write(&tmpfile, format!("ssrf_env_variables = [\"NOSUCHENV\"]"))
.expect("could not write");
std::fs::write(&tmpfile, "ssrf_env_variables = [\"NOSUCHENV\"]").expect("could not write");
let cfg = Config::new(Some(&tmpfile)).expect("config failed");
assert_eq!(
get_token(&cfg)
.err()
.unwrap()
.downcast_ref::<VarError>()
.unwrap()
.eq(&VarError::NotPresent),
true
);
assert!(get_token(&cfg)
.err()
.unwrap()
.downcast_ref::<VarError>()
.unwrap()
.eq(&VarError::NotPresent));
}

// Make sure the timeout functon returns the correct value.
Expand Down
57 changes: 53 additions & 4 deletions aws_secretsmanager_caching/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub mod secret_store;
use aws_sdk_secretsmanager::Client as SecretsManagerClient;
use secret_store::SecretStoreError;

use output::GetSecretValueOutputDef;
use output::{DescribeSecretOutputDef, GetSecretValueOutputDef};
use secret_store::{MemoryStore, SecretStore};
use std::{error::Error, num::NonZeroUsize, time::Duration};
use tokio::sync::RwLock;
Expand Down Expand Up @@ -206,6 +206,52 @@ impl SecretsManagerCachingClient {

Ok(false)
}

/// Retrieves the metadata of the secret
///
/// # Arguments
///
/// * `secret_id` - The ARN or name of the secret to retrieve.
async fn describe_secret(
&self,
secret_id: &str,
) -> Result<DescribeSecretOutputDef, Box<dyn Error>> {
let read_lock = self.store.read().await;

match read_lock.describe_secret(secret_id) {
Ok(r) => Ok(r),
Err(SecretStoreError::ResourceNotFound | SecretStoreError::DescribeCacheExpired) => {
drop(read_lock);
Ok(self.refresh_describe(secret_id).await?)
}
Err(e) => Err(Box::new(e)),
}
}

/// Refreshes the secret metadata through a DescribeSecret call to ASM
///
/// # Arguments
/// * `secret_id` - The ARN or name of the secret to retrieve.
async fn refresh_describe(
&self,
secret_id: &str,
) -> Result<DescribeSecretOutputDef, Box<dyn Error>> {
let response = self
.asm_client
.describe_secret()
.secret_id(secret_id)
.send()
.await?;

let result: DescribeSecretOutputDef = response.into();

self.store
.write()
.await
.write_describe_secret(secret_id.to_owned(), result.clone())?;

Ok(result)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -484,9 +530,12 @@ mod tests {

sleep(Duration::from_millis(10)).await;

match client.get_secret_value(secret_id, version_id, None).await {
Ok(_) => panic!("Expected failure"),
Err(_) => (),
if client
.get_secret_value(secret_id, version_id, None)
.await
.is_ok()
{
panic!("Expected failure")
}
}

Expand Down
224 changes: 224 additions & 0 deletions aws_secretsmanager_caching/src/output/describe_secret_output.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
use std::time::SystemTime;

use aws_sdk_secretsmanager::{
operation::describe_secret::DescribeSecretOutput,
types::{ReplicationStatusType, StatusType, Tag},
};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, TimestampSecondsWithFrac};

/// Structure to store the secret details
#[serde_as]
#[derive(::std::clone::Clone, ::std::cmp::PartialEq, Debug, Serialize, Deserialize, Default)]
#[serde(rename_all = "PascalCase")]
pub struct DescribeSecretOutputDef {
/// The ARN of the secret.
#[serde(rename(serialize = "ARN"))]
pub arn: Option<String>,

/// The name of the secret.
pub name: Option<String>,

/// The description of the secret.
pub description: Option<String>,

/// The key ID or alias ARN of the KMS key that Secrets Manager uses to encrypt the secret value. If the secret is encrypted with the Amazon Web Services managed key <code>aws/secretsmanager</code>, this field is omitted. Secrets created using the console use an KMS key ID.
pub kms_key_id: Option<String>,

/// Specifies whether automatic rotation is turned on for this secret.
/// To turn on rotation, use <code>RotateSecret</code>. To turn off rotation, use <code>CancelRotateSecret</code>.
pub rotation_enabled: Option<bool>,

/// The ARN of the Lambda function that Secrets Manager invokes to rotate the secret.
pub rotation_lambda_arn: Option<String>,

// Todo: Add support for this; skipping as not in scope for Ragnarok
// pub rotation_rules: Option<crate::types::RotationRulesType>,
/// The last date and time that Secrets Manager rotated the secret. If the secret isn't configured for rotation, Secrets Manager returns null.
#[serde_as(as = "Option<TimestampSecondsWithFrac<String>>")]
pub last_rotated_date: Option<SystemTime>,

/// The last date and time that this secret was modified in any way.
#[serde_as(as = "Option<TimestampSecondsWithFrac<String>>")]
pub last_changed_date: Option<SystemTime>,

/// The date that the secret was last accessed in the Region. This field is omitted if the secret has never been retrieved in the Region.
#[serde_as(as = "Option<TimestampSecondsWithFrac<String>>")]
pub last_accessed_date: Option<SystemTime>,

/// The date the secret is scheduled for deletion. If it is not scheduled for deletion, this field is omitted. When you delete a secret, Secrets Manager requires a recovery window of at least 7 days before deleting the secret. Some time after the deleted date, Secrets Manager deletes the secret, including all of its versions.
/// If a secret is scheduled for deletion, then its details, including the encrypted secret value, is not accessible. To cancel a scheduled deletion and restore access to the secret, use <code>RestoreSecret</code>.
#[serde_as(as = "Option<TimestampSecondsWithFrac<String>>")]
pub deleted_date: Option<SystemTime>,

/// The next rotation is scheduled to occur on or before this date. If the secret isn't configured for rotation, Secrets Manager returns null.
#[serde_as(as = "Option<TimestampSecondsWithFrac<String>>")]
pub next_rotation_date: Option<SystemTime>,

/// The list of tags attached to the secret. To add tags to a secret, use <code>TagResource</code>. To remove tags, use <code>UntagResource</code>.
pub tags: Option<Vec<TagDef>>,

/// A list of the versions of the secret that have staging labels attached. Versions that don't have staging labels are considered deprecated and Secrets Manager can delete them.
/// Secrets Manager uses staging labels to indicate the status of a secret version during rotation. The three staging labels for rotation are:
/// <ul>
/// <li> <code>AWSCURRENT</code>, which indicates the current version of the secret. </li>
/// <li> <code>AWSPENDING</code>, which indicates the version of the secret that contains new secret information that will become the next current version when rotation finishes. During rotation, Secrets Manager creates an <code>AWSPENDING</code> version ID before creating the new secret version. To check if a secret version exists, call <code>GetSecretValue</code>. </li>
/// <li> <code>AWSPREVIOUS</code>, which indicates the previous current version of the secret. You can use this as the <i>last known good</i> version. </li>
/// </ul>
/// For more information about rotation and staging labels, see <a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotate-secrets_how.html">How rotation works</a>.
pub version_ids_to_stages: Option<::std::collections::HashMap<String, Vec<String>>>,

/// The ID of the service that created this secret. For more information, see <a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/service-linked-secrets.html">Secrets managed by other Amazon Web Services services</a>.
pub owning_service: Option<String>,

/// The date the secret was created.
#[serde_as(as = "Option<TimestampSecondsWithFrac<String>>")]
pub created_date: Option<SystemTime>,

// Todo: Add support for this; skipping as not in scope for Ragnarok
// pub replication_status:
// Option<::std::vec::Vec<crate::types::ReplicationStatusType>>,
/// The Region the secret is in. If a secret is replicated to other Regions, the replicas are listed in <code>ReplicationStatus</code>.
pub primary_region: Option<String>,
/// <p>A list of the replicas of this secret and their status:</p>
/// <ul>
/// <li>
/// <p><code>Failed</code>, which indicates that the replica was not created.</p></li>
/// <li>
/// <p><code>InProgress</code>, which indicates that Secrets Manager is in the process of creating the replica.</p></li>
/// <li>
/// <p><code>InSync</code>, which indicates that the replica was created.</p></li>
/// </ul>
pub replication_status: Option<Vec<ReplicationStatusTypeDef>>,
}

impl DescribeSecretOutputDef {
/// Converts DescribeSecretOutput to DescribeSecretOutputDef
pub fn new(describe: DescribeSecretOutput) -> Self {
DescribeSecretOutputDef {
arn: describe.arn,
name: describe.name,
description: describe.description,
kms_key_id: describe.kms_key_id,
rotation_enabled: describe.rotation_enabled,
rotation_lambda_arn: describe.rotation_lambda_arn,
last_rotated_date: describe.last_rotated_date.map(|i| i.try_into().unwrap()),
last_changed_date: describe.last_changed_date.map(|i| i.try_into().unwrap()),
last_accessed_date: describe.last_accessed_date.map(|i| i.try_into().unwrap()),
deleted_date: describe.deleted_date.map(|i| i.try_into().unwrap()),
next_rotation_date: describe.next_rotation_date.map(|i| i.try_into().unwrap()),
tags: describe
.tags
.map(|o| o.iter().map(|tag| tag.into()).collect()),
version_ids_to_stages: describe.version_ids_to_stages,
owning_service: describe.owning_service,
created_date: describe.created_date.map(|i| i.try_into().unwrap()),
replication_status: describe.replication_status.map(|replication_status| {
replication_status
.iter()
.map(|status| status.into())
.collect()
}),
primary_region: describe.primary_region,
}
}
}

/// Copy of the remote aws_sdk_secretsmanager::types::DateTime type.
#[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct TagDef {
/// The key identifier, or name, of the tag.
pub key: Option<String>,

/// The string value associated with the key of the tag.
pub value: Option<String>,
}

impl From<&Tag> for TagDef {
fn from(value: &Tag) -> Self {
TagDef {
key: value.key.clone(),
value: value.value.clone(),
}
}
}

#[serde_as]
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
/// Replication status type
pub struct ReplicationStatusTypeDef {
/// <p>The Region where replication occurs.</p>
pub region: Option<String>,
/// <p>Can be an <code>ARN</code>, <code>Key ID</code>, or <code>Alias</code>.</p>
pub kms_key_id: Option<String>,
/// <p>The status can be <code>InProgress</code>, <code>Failed</code>, or <code>InSync</code>.</p>
pub status: Option<StatusTypeDef>,
/// <p>Status message such as "<i>Secret with this name already exists in this region</i>".</p>
pub status_message: Option<String>,
/// <p>The date that the secret was last accessed in the Region. This field is omitted if the secret has never been retrieved in the Region.</p>
pub last_accessed_date: Option<SystemTime>,
}

impl From<ReplicationStatusTypeDef> for ReplicationStatusType {
fn from(value: ReplicationStatusTypeDef) -> Self {
ReplicationStatusType::builder()
.set_region(value.region)
.set_kms_key_id(value.kms_key_id)
.set_status(value.status.map(Into::into))
.set_last_accessed_date(value.last_accessed_date.map(Into::into))
.set_status_message(value.status_message)
.build()
}
}

impl From<&ReplicationStatusType> for ReplicationStatusTypeDef {
fn from(value: &ReplicationStatusType) -> Self {
ReplicationStatusTypeDef {
region: value.region().map(String::from),
kms_key_id: value.kms_key_id().map(String::from),
status: value.status().map(Into::into),
status_message: value.status_message().map(String::from),
last_accessed_date: value.last_accessed_date().map(|i| (*i).try_into().unwrap()),
}
}
}

#[serde_as]
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd, Debug, Hash, Serialize, Deserialize)]
/// Status type
pub enum StatusTypeDef {
#[allow(missing_docs)] // documentation missing in model
Failed,
#[allow(missing_docs)] // documentation missing in model
InProgress,
#[allow(missing_docs)] // documentation missing in model
InSync,
}

impl From<StatusTypeDef> for StatusType {
fn from(value: StatusTypeDef) -> Self {
match value {
StatusTypeDef::Failed => StatusType::Failed,
StatusTypeDef::InProgress => StatusType::InProgress,
StatusTypeDef::InSync => StatusType::InSync,
}
}
}

impl From<&StatusType> for StatusTypeDef {
fn from(value: &StatusType) -> Self {
match value {
StatusType::Failed => StatusTypeDef::Failed,
StatusType::InProgress => StatusTypeDef::InProgress,
StatusType::InSync => StatusTypeDef::InSync,
_ => panic!("Invalid value for StatusTypeDef: {}", value),
}
}
}

impl From<DescribeSecretOutput> for DescribeSecretOutputDef {
fn from(input: DescribeSecretOutput) -> Self {
Self::new(input)
}
}
Loading