1pub struct SecretId(pub String);
2
3pub trait SecretType:
4 Clone
5 + std::fmt::Display
6 + std::marker::Send
7 + std::marker::Sync
8 + std::str::FromStr<Err = strum::ParseError>
9 + strum::IntoEnumIterator
10{
11 fn to_arn_output_key(&self) -> crate::types::OutputKey;
12 fn to_env_variable_name(&self) -> &str;
13 fn validate(&self, input: &str) -> Result<(), String>;
14}
15
16pub async fn read_stack_secret_string_json<T: for<'a> serde::Deserialize<'a>, S: SecretType>(
17 secretsmanager: &aws_sdk_secretsmanager::client::Client,
18 stack: &aws_sdk_cloudformation::types::Stack,
19 secret: S,
20) -> T {
21 serde_json::from_str(&read_stack_secret_string(secretsmanager, stack, secret).await).unwrap()
22}
23
24pub async fn read_stack_secret_string<S: SecretType>(
25 secretsmanager: &aws_sdk_secretsmanager::client::Client,
26 stack: &aws_sdk_cloudformation::types::Stack,
27 secret: S,
28) -> String {
29 read_secret_value_string(
30 secretsmanager,
31 &SecretId(crate::stack::fetch_stack_output(
32 stack,
33 &secret.to_arn_output_key(),
34 )),
35 )
36 .await
37}
38
39pub async fn read_env_json<T: for<'a> serde::Deserialize<'a>, S: SecretType>(
40 secretsmanager: &aws_sdk_secretsmanager::client::Client,
41 secret: S,
42) -> T {
43 serde_json::from_str(&read_env_secret_string(secretsmanager, secret).await).unwrap()
44}
45
46pub async fn read_env_secret_string<S: SecretType>(
47 secretsmanager: &aws_sdk_secretsmanager::client::Client,
48 secret: S,
49) -> String {
50 let secret_id = read_env_secret_id(secret);
51
52 read_secret_value_string(secretsmanager, &secret_id).await
53}
54
55pub async fn read_secret_value_string(
56 secretsmanager: &aws_sdk_secretsmanager::client::Client,
57 secret_id: &SecretId,
58) -> String {
59 log::info!("Reading secret: {}", secret_id.0);
60
61 secretsmanager
62 .get_secret_value()
63 .secret_id(&secret_id.0)
64 .send()
65 .await
66 .unwrap()
67 .secret_string()
68 .unwrap()
69 .to_string()
70}
71
72fn read_env_secret_id<S: SecretType>(secret: S) -> SecretId {
73 let env_variable_name = secret.to_env_variable_name();
74
75 SecretId(
76 std::env::var(env_variable_name)
77 .unwrap_or_else(|_| panic!("Secret env variable: {env_variable_name} is not present!"))
78 .to_string(),
79 )
80}
81
82pub async fn put_secret_value_string(
83 secretsmanager: &aws_sdk_secretsmanager::client::Client,
84 secret_id: &SecretId,
85 secret_string: &str,
86) {
87 log::info!("Writing secret: {}", secret_id.0);
88
89 secretsmanager
90 .put_secret_value()
91 .secret_id(&secret_id.0)
92 .secret_string(secret_string)
93 .send()
94 .await
95 .unwrap();
96}
97
98pub mod cli {
99 use crate::secrets::{SecretId, SecretType};
100
101 #[derive(Clone, Debug, Eq, PartialEq, clap::Parser)]
102 pub struct App<T: SecretType + 'static> {
103 #[clap(subcommand)]
104 command: Command<T>,
105 #[clap(skip)]
106 _phantom: std::marker::PhantomData<T>,
107 }
108
109 impl<T: SecretType> App<T> {
110 pub async fn run(
111 &self,
112 cloudformation: &aws_sdk_cloudformation::client::Client,
113 secretsmanager: &aws_sdk_secretsmanager::client::Client,
114 ) {
115 self.command.run(cloudformation, secretsmanager).await
116 }
117 }
118
119 #[derive(Clone, Debug, Eq, PartialEq, clap::Parser)]
120 pub enum Command<T: SecretType + 'static> {
121 List,
123 PutSecretString {
125 #[clap(long = "secret")]
127 secret: T,
128 #[clap(long = "stack-name")]
130 stack_name: crate::types::StackName,
131 },
132 PrintSecretString {
134 #[clap(long = "secret")]
136 secret: T,
137 #[clap(long = "stack-name")]
139 stack_name: crate::types::StackName,
140 },
141 }
142
143 impl<T: SecretType> Command<T> {
144 pub async fn run(
145 &self,
146 cloudformation: &aws_sdk_cloudformation::client::Client,
147 secretsmanager: &aws_sdk_secretsmanager::client::Client,
148 ) {
149 match self {
150 Self::List => list::<T>(),
151 Self::PrintSecretString { secret, stack_name } => {
152 print_secret_string::<T>(cloudformation, secretsmanager, stack_name, secret)
153 .await
154 }
155 Self::PutSecretString { secret, stack_name } => {
156 put_secret_string::<T>(cloudformation, secretsmanager, stack_name, secret).await
157 }
158 }
159 }
160 }
161
162 fn list<T: SecretType>() {
163 for secret in T::iter() {
164 println!("{secret}")
165 }
166 }
167
168 async fn put_secret_string<T: SecretType>(
169 cloudformation: &aws_sdk_cloudformation::client::Client,
170 secretsmanager: &aws_sdk_secretsmanager::client::Client,
171 stack_name: &crate::types::StackName,
172 secret: &T,
173 ) {
174 use std::io::IsTerminal;
175
176 let secret_id = SecretId(
177 crate::stack::read_stack_output(
178 cloudformation,
179 stack_name,
180 &secret.to_arn_output_key(),
181 )
182 .await,
183 );
184
185 let stdin = std::io::stdin();
186 let is_tty = stdin.is_terminal();
187
188 if is_tty {
189 eprintln!("Enter secret (newline terminates). Secret will be echoed:");
190 }
191
192 let mut input = String::new();
193
194 match stdin.read_line(&mut input) {
195 Ok(_) => {
196 if is_tty && input.ends_with('\n') {
197 input.truncate(input.len() - 1);
198 }
199 match secret.validate(&input) {
200 Ok(()) => {
201 crate::secrets::put_secret_value_string(secretsmanager, &secret_id, &input)
202 .await;
203 }
204 Err(error) => {
205 eprintln!("Validation error: {error}");
206 std::process::exit(1);
207 }
208 }
209 }
210 Err(error) => panic!("Error: {error}"),
211 }
212 }
213
214 async fn print_secret_string<T: SecretType>(
215 cloudformation: &aws_sdk_cloudformation::client::Client,
216 secretsmanager: &aws_sdk_secretsmanager::client::Client,
217 stack_name: &crate::types::StackName,
218 secret: &T,
219 ) {
220 let secret_id = SecretId(
221 crate::stack::read_stack_output(
222 cloudformation,
223 stack_name,
224 &secret.to_arn_output_key(),
225 )
226 .await,
227 );
228
229 println!(
230 "{}",
231 crate::secrets::read_secret_value_string(secretsmanager, &secret_id).await
232 );
233 }
234}