pub struct SecretId(pub String);
pub trait SecretType:
Clone
+ std::fmt::Display
+ std::marker::Send
+ std::marker::Sync
+ std::str::FromStr<Err = strum::ParseError>
+ strum::IntoEnumIterator
{
fn to_arn_output_key(&self) -> crate::types::OutputKey;
fn to_env_variable_name(&self) -> &str;
fn validate(&self, input: &str) -> Result<(), String>;
}
pub async fn read_stack_secret_string_json<T: for<'a> serde::Deserialize<'a>, S: SecretType>(
secretsmanager: &aws_sdk_secretsmanager::client::Client,
stack: &aws_sdk_cloudformation::types::Stack,
secret: S,
) -> T {
serde_json::from_str(&read_stack_secret_string(secretsmanager, stack, secret).await).unwrap()
}
pub async fn read_stack_secret_string<S: SecretType>(
secretsmanager: &aws_sdk_secretsmanager::client::Client,
stack: &aws_sdk_cloudformation::types::Stack,
secret: S,
) -> String {
read_secret_value_string(
secretsmanager,
&SecretId(crate::stack::fetch_stack_output(
stack,
&secret.to_arn_output_key(),
)),
)
.await
}
pub async fn read_env_json<T: for<'a> serde::Deserialize<'a>, S: SecretType>(
secretsmanager: &aws_sdk_secretsmanager::client::Client,
secret: S,
) -> T {
serde_json::from_str(&read_env_secret_string(secretsmanager, secret).await).unwrap()
}
pub async fn read_env_secret_string<S: SecretType>(
secretsmanager: &aws_sdk_secretsmanager::client::Client,
secret: S,
) -> String {
let secret_id = read_env_secret_id(secret);
read_secret_value_string(secretsmanager, &secret_id).await
}
pub async fn read_secret_value_string(
secretsmanager: &aws_sdk_secretsmanager::client::Client,
secret_id: &SecretId,
) -> String {
log::info!("Reading secret: {}", secret_id.0);
secretsmanager
.get_secret_value()
.secret_id(&secret_id.0)
.send()
.await
.unwrap()
.secret_string()
.unwrap()
.to_string()
}
fn read_env_secret_id<S: SecretType>(secret: S) -> SecretId {
let env_variable_name = secret.to_env_variable_name();
SecretId(
std::env::var(env_variable_name)
.unwrap_or_else(|_| panic!("Secret env variable: {env_variable_name} is not present!"))
.to_string(),
)
}
pub async fn put_secret_value_string(
secretsmanager: &aws_sdk_secretsmanager::client::Client,
secret_id: &SecretId,
secret_string: &str,
) {
log::info!("Writing secret: {}", secret_id.0);
secretsmanager
.put_secret_value()
.secret_id(&secret_id.0)
.secret_string(secret_string)
.send()
.await
.unwrap();
}
pub mod cli {
use crate::secrets::{SecretId, SecretType};
#[derive(Clone, Debug, Eq, PartialEq, clap::Parser)]
pub struct App<T: SecretType + 'static> {
#[clap(subcommand)]
command: Command<T>,
#[clap(skip)]
_phantom: std::marker::PhantomData<T>,
}
impl<T: SecretType> App<T> {
pub async fn run(
&self,
cloudformation: &aws_sdk_cloudformation::client::Client,
secretsmanager: &aws_sdk_secretsmanager::client::Client,
) {
self.command.run(cloudformation, secretsmanager).await
}
}
#[derive(Clone, Debug, Eq, PartialEq, clap::Parser)]
pub enum Command<T: SecretType + 'static> {
List,
PutSecretString {
#[clap(long = "secret")]
secret: T,
#[clap(long = "stack-name")]
stack_name: crate::types::StackName,
},
PrintSecretString {
#[clap(long = "secret")]
secret: T,
#[clap(long = "stack-name")]
stack_name: crate::types::StackName,
},
}
impl<T: SecretType> Command<T> {
pub async fn run(
&self,
cloudformation: &aws_sdk_cloudformation::client::Client,
secretsmanager: &aws_sdk_secretsmanager::client::Client,
) {
match self {
Self::List => list::<T>(),
Self::PrintSecretString { secret, stack_name } => {
print_secret_string::<T>(cloudformation, secretsmanager, stack_name, secret)
.await
}
Self::PutSecretString { secret, stack_name } => {
put_secret_string::<T>(cloudformation, secretsmanager, stack_name, secret).await
}
}
}
}
fn list<T: SecretType>() {
for secret in T::iter() {
println!("{secret}")
}
}
async fn put_secret_string<T: SecretType>(
cloudformation: &aws_sdk_cloudformation::client::Client,
secretsmanager: &aws_sdk_secretsmanager::client::Client,
stack_name: &crate::types::StackName,
secret: &T,
) {
use std::io::IsTerminal;
let secret_id = SecretId(
crate::stack::read_stack_output(
cloudformation,
stack_name,
&secret.to_arn_output_key(),
)
.await,
);
let stdin = std::io::stdin();
let is_tty = stdin.is_terminal();
if is_tty {
eprintln!("Enter secret (newline terminates). Secret will be echoed:");
}
let mut input = String::new();
match stdin.read_line(&mut input) {
Ok(_) => {
if is_tty && input.ends_with('\n') {
input.truncate(input.len() - 1);
}
match secret.validate(&input) {
Ok(()) => {
crate::secrets::put_secret_value_string(secretsmanager, &secret_id, &input)
.await;
}
Err(error) => {
eprintln!("Validation error: {error}");
std::process::exit(1);
}
}
}
Err(error) => panic!("Error: {error}"),
}
}
async fn print_secret_string<T: SecretType>(
cloudformation: &aws_sdk_cloudformation::client::Client,
secretsmanager: &aws_sdk_secretsmanager::client::Client,
stack_name: &crate::types::StackName,
secret: &T,
) {
let secret_id = SecretId(
crate::stack::read_stack_output(
cloudformation,
stack_name,
&secret.to_arn_output_key(),
)
.await,
);
println!(
"{}",
crate::secrets::read_secret_value_string(secretsmanager, &secret_id).await
);
}
}