1mod aws_secret;
27mod env_var;
28mod json_field;
29
30use crate::cli::CliError;
31use crate::config::SecurityConfig;
32use crate::project::ast::Statement;
33use async_trait::async_trait;
34use aws_secret::{AwsSecretProvider, UnconfiguredAwsProvider};
35use env_var::EnvVarProvider;
36use mz_sql_parser::ast::{CreateSecretStatement, Expr, FunctionArgs, Raw, RawItemName, Value};
37use std::collections::BTreeMap;
38use std::ops::RangeInclusive;
39use thiserror::Error;
40
41#[derive(Debug, Error)]
43pub enum SecretResolveError {
44 #[error("function '{name}' expects {expected} argument(s), got {got}")]
46 WrongArgCount {
47 name: String,
48 expected: String,
49 got: usize,
50 },
51 #[error("function '{name}' requires string literal arguments")]
53 NonLiteralArg { name: String },
54 #[error("failed to resolve '{name}': {reason}")]
56 ResolutionFailed { name: String, reason: String },
57}
58
59#[async_trait]
61pub(crate) trait SecretProvider: Send + Sync {
62 fn name(&self) -> &str;
64 fn accepted_args(&self) -> RangeInclusive<usize>;
66 async fn resolve(&self, args: &[String]) -> Result<String, SecretResolveError>;
68}
69
70fn format_arg_range(range: &RangeInclusive<usize>) -> String {
74 let (start, end) = (*range.start(), *range.end());
75 if start == end {
76 start.to_string()
77 } else if end == start + 1 {
78 format!("{} or {}", start, end)
79 } else {
80 format!("{}..={}", start, end)
81 }
82}
83
84pub(crate) struct SecretResolver {
89 providers: BTreeMap<String, Box<dyn SecretProvider>>,
90}
91
92impl SecretResolver {
93 pub(crate) fn new(config: &SecurityConfig) -> Self {
99 let mut resolver = Self {
100 providers: BTreeMap::new(),
101 };
102 resolver.register(Box::new(EnvVarProvider));
103
104 if let Some(profile) = config.aws_profile() {
105 resolver.register(Box::new(AwsSecretProvider::new(profile)));
106 } else {
107 resolver.register(Box::new(UnconfiguredAwsProvider));
108 }
109
110 resolver
111 }
112
113 fn register(&mut self, provider: Box<dyn SecretProvider>) {
114 self.providers.insert(provider.name().to_string(), provider);
115 }
116
117 pub(crate) async fn resolve_expr(
122 &self,
123 expr: Expr<Raw>,
124 ) -> Result<Expr<Raw>, SecretResolveError> {
125 match &expr {
126 Expr::Function(func) => {
127 let func_name = match &func.name {
128 RawItemName::Name(name) if name.0.len() == 1 => {
129 Some(name.0[0].as_str().to_string())
130 }
131 _ => None,
132 };
133
134 let func_name = match func_name {
135 Some(name) => name,
136 None => return Ok(expr),
137 };
138
139 let provider = match self.providers.get(&func_name) {
140 Some(p) => p,
141 None => return Ok(expr),
142 };
143
144 let accepted = provider.accepted_args();
146 let arg_exprs = match &func.args {
147 FunctionArgs::Star => {
148 return Err(SecretResolveError::WrongArgCount {
149 name: func_name,
150 expected: format_arg_range(&accepted),
151 got: 0,
152 });
153 }
154 FunctionArgs::Args { args, .. } => args,
155 };
156
157 if !accepted.contains(&arg_exprs.len()) {
158 return Err(SecretResolveError::WrongArgCount {
159 name: func_name,
160 expected: format_arg_range(&accepted),
161 got: arg_exprs.len(),
162 });
163 }
164
165 let mut string_args = Vec::with_capacity(arg_exprs.len());
166 for arg in arg_exprs {
167 match arg {
168 Expr::Value(Value::String(s)) => string_args.push(s.clone()),
169 _ => {
170 return Err(SecretResolveError::NonLiteralArg { name: func_name });
171 }
172 }
173 }
174
175 let resolved = provider.resolve(&string_args).await?;
176 Ok(Expr::Value(Value::String(resolved)))
177 }
178 _ => Ok(expr),
179 }
180 }
181
182 pub(crate) async fn resolve_create_secret(
186 &self,
187 stmt: &CreateSecretStatement<Raw>,
188 ) -> Result<CreateSecretStatement<Raw>, SecretResolveError> {
189 let mut resolved = stmt.clone();
190 resolved.value = self.resolve_expr(stmt.value.clone()).await?;
191 Ok(resolved)
192 }
193
194 pub(crate) async fn resolve_secret_for_cli(
196 &self,
197 stmt: &CreateSecretStatement<Raw>,
198 ) -> Result<CreateSecretStatement<Raw>, CliError> {
199 self.resolve_create_secret(stmt)
200 .await
201 .map_err(|e| CliError::SecretResolution {
202 secret_name: stmt.name.to_string(),
203 source: e,
204 })
205 }
206
207 pub(crate) async fn resolve_statement_for_cli(
212 &self,
213 stmt: &Statement,
214 ) -> Result<Statement, CliError> {
215 match stmt {
216 Statement::CreateSecret(create_stmt) => {
217 let resolved = self.resolve_secret_for_cli(create_stmt).await?;
218 Ok(Statement::CreateSecret(resolved))
219 }
220 _ => Ok(stmt.clone()),
221 }
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use mz_sql_parser::ast::{Function, Ident, UnresolvedItemName};
229
230 fn make_env_var_expr(var_name: &str) -> Expr<Raw> {
232 Expr::Function(Function {
233 name: RawItemName::Name(UnresolvedItemName(vec![Ident::new("env_var").unwrap()])),
234 args: FunctionArgs::Args {
235 args: vec![Expr::Value(Value::String(var_name.to_string()))],
236 order_by: vec![],
237 },
238 filter: None,
239 over: None,
240 distinct: false,
241 })
242 }
243
244 fn make_function_expr(name: &str, args: Vec<Expr<Raw>>) -> Expr<Raw> {
245 Expr::Function(Function {
246 name: RawItemName::Name(UnresolvedItemName(vec![Ident::new(name).unwrap()])),
247 args: FunctionArgs::Args {
248 args,
249 order_by: vec![],
250 },
251 filter: None,
252 over: None,
253 distinct: false,
254 })
255 }
256
257 #[mz_ore::test(tokio::test)]
258 async fn test_resolve_string_literal_passthrough() {
259 let resolver = SecretResolver::new(&Default::default());
260 let expr = Expr::Value(Value::String("hello".to_string()));
261 let original = format!("{}", expr);
262 let resolved = resolver.resolve_expr(expr).await.unwrap();
263 assert_eq!(format!("{}", resolved), original);
264 }
265
266 #[mz_ore::test(tokio::test)]
267 async fn test_resolve_env_var_success() {
268 unsafe { std::env::set_var("MZ_TEST_SECRET_123", "my_secret_value") };
270 let resolver = SecretResolver::new(&Default::default());
271 let expr = make_env_var_expr("MZ_TEST_SECRET_123");
272 let resolved = resolver.resolve_expr(expr).await.unwrap();
273 match resolved {
274 Expr::Value(Value::String(s)) => assert_eq!(s, "my_secret_value"),
275 other => panic!("expected string literal, got: {:?}", other),
276 }
277 }
278
279 #[mz_ore::test(tokio::test)]
280 async fn test_resolve_env_var_not_set() {
281 let resolver = SecretResolver::new(&Default::default());
282 let expr = make_env_var_expr("MZ_DEFINITELY_NOT_SET_XYZ_999");
283 let err = resolver.resolve_expr(expr).await.unwrap_err();
284 assert!(matches!(err, SecretResolveError::ResolutionFailed { .. }));
285 }
286
287 #[mz_ore::test(tokio::test)]
288 async fn test_resolve_unknown_function_passthrough() {
289 let resolver = SecretResolver::new(&Default::default());
290 let expr = make_function_expr("vault", vec![Expr::Value(Value::String("foo".to_string()))]);
291 let original = format!("{}", expr);
292 let resolved = resolver.resolve_expr(expr).await.unwrap();
293 assert_eq!(format!("{}", resolved), original);
294 }
295
296 #[mz_ore::test(tokio::test)]
297 async fn test_resolve_arbitrary_expr_passthrough() {
298 let resolver = SecretResolver::new(&Default::default());
299
300 let expr = Expr::Value(Value::Number("42".to_string()));
302 let resolved = resolver.resolve_expr(expr).await.unwrap();
303 assert_eq!(format!("{}", resolved), "42");
304
305 let expr = Expr::Identifier(vec![Ident::new("some_col").unwrap()]);
307 let original = format!("{}", expr);
308 let resolved = resolver.resolve_expr(expr).await.unwrap();
309 assert_eq!(format!("{}", resolved), original);
310 }
311
312 #[mz_ore::test(tokio::test)]
313 async fn test_resolve_wrong_arg_count_zero() {
314 let resolver = SecretResolver::new(&Default::default());
315 let expr = make_function_expr("env_var", vec![]);
316 let err = resolver.resolve_expr(expr).await.unwrap_err();
317 match err {
318 SecretResolveError::WrongArgCount {
319 name,
320 expected,
321 got,
322 } => {
323 assert_eq!(name, "env_var");
324 assert_eq!(expected, "1");
325 assert_eq!(got, 0);
326 }
327 other => panic!("expected WrongArgCount, got: {:?}", other),
328 }
329 }
330
331 #[mz_ore::test(tokio::test)]
332 async fn test_resolve_wrong_arg_count_two() {
333 let resolver = SecretResolver::new(&Default::default());
334 let expr = make_function_expr(
335 "env_var",
336 vec![
337 Expr::Value(Value::String("A".to_string())),
338 Expr::Value(Value::String("B".to_string())),
339 ],
340 );
341 let err = resolver.resolve_expr(expr).await.unwrap_err();
342 match err {
343 SecretResolveError::WrongArgCount {
344 name,
345 expected,
346 got,
347 } => {
348 assert_eq!(name, "env_var");
349 assert_eq!(expected, "1");
350 assert_eq!(got, 2);
351 }
352 other => panic!("expected WrongArgCount, got: {:?}", other),
353 }
354 }
355
356 #[mz_ore::test(tokio::test)]
357 async fn test_resolve_aws_secret_wrong_arg_count() {
358 let resolver = SecretResolver::new(&Default::default());
361
362 let expr = make_function_expr("aws_secret", vec![]);
363 match resolver.resolve_expr(expr).await.unwrap_err() {
364 SecretResolveError::WrongArgCount {
365 name,
366 expected,
367 got,
368 } => {
369 assert_eq!(name, "aws_secret");
370 assert_eq!(expected, "1 or 2");
371 assert_eq!(got, 0);
372 }
373 other => panic!("expected WrongArgCount, got: {:?}", other),
374 }
375
376 let expr = make_function_expr(
377 "aws_secret",
378 vec![
379 Expr::Value(Value::String("a".into())),
380 Expr::Value(Value::String("b".into())),
381 Expr::Value(Value::String("c".into())),
382 ],
383 );
384 match resolver.resolve_expr(expr).await.unwrap_err() {
385 SecretResolveError::WrongArgCount {
386 name,
387 expected,
388 got,
389 } => {
390 assert_eq!(name, "aws_secret");
391 assert_eq!(expected, "1 or 2");
392 assert_eq!(got, 3);
393 }
394 other => panic!("expected WrongArgCount, got: {:?}", other),
395 }
396 }
397
398 #[mz_ore::test(tokio::test)]
399 async fn test_resolve_non_literal_arg() {
400 let resolver = SecretResolver::new(&Default::default());
401 let expr = make_function_expr(
402 "env_var",
403 vec![Expr::Identifier(vec![Ident::new("col").unwrap()])],
404 );
405 let err = resolver.resolve_expr(expr).await.unwrap_err();
406 assert!(matches!(err, SecretResolveError::NonLiteralArg { .. }));
407 }
408
409 #[mz_ore::test(tokio::test)]
410 async fn test_resolve_star_args() {
411 let resolver = SecretResolver::new(&Default::default());
412 let expr = Expr::Function(Function {
413 name: RawItemName::Name(UnresolvedItemName(vec![Ident::new("env_var").unwrap()])),
414 args: FunctionArgs::Star,
415 filter: None,
416 over: None,
417 distinct: false,
418 });
419 let err = resolver.resolve_expr(expr).await.unwrap_err();
420 match err {
421 SecretResolveError::WrongArgCount { got: 0, .. } => {}
422 other => panic!("expected WrongArgCount with got=0, got: {:?}", other),
423 }
424 }
425
426 #[mz_ore::test(tokio::test)]
427 async fn test_resolve_create_secret_with_env_var() {
428 unsafe { std::env::set_var("MZ_TEST_SECRET_456", "resolved_value") };
430 let resolver = SecretResolver::new(&Default::default());
431 let stmt = CreateSecretStatement::<Raw> {
432 name: UnresolvedItemName(vec![Ident::new("my_secret").unwrap()]),
433 if_not_exists: false,
434 value: make_env_var_expr("MZ_TEST_SECRET_456"),
435 };
436 let resolved = resolver.resolve_create_secret(&stmt).await.unwrap();
437 match &resolved.value {
438 Expr::Value(Value::String(s)) => assert_eq!(s, "resolved_value"),
439 other => panic!("expected string literal, got: {:?}", other),
440 }
441 assert_eq!(resolved.name.0[0].as_str(), stmt.name.0[0].as_str());
442 }
443
444 #[mz_ore::test(tokio::test)]
445 async fn test_resolve_create_secret_plain_string() {
446 let resolver = SecretResolver::new(&Default::default());
447 let stmt = CreateSecretStatement::<Raw> {
448 name: UnresolvedItemName(vec![Ident::new("my_secret").unwrap()]),
449 if_not_exists: false,
450 value: Expr::Value(Value::String("plain_value".to_string())),
451 };
452 let resolved = resolver.resolve_create_secret(&stmt).await.unwrap();
453 match &resolved.value {
454 Expr::Value(Value::String(s)) => assert_eq!(s, "plain_value"),
455 other => panic!("expected string literal, got: {:?}", other),
456 }
457 }
458
459 #[mz_ore::test(tokio::test)]
460 async fn test_unconfigured_aws_provider_error() {
461 let resolver = SecretResolver::new(&Default::default());
462 let expr = make_function_expr("aws_secret", vec![Expr::Value(Value::String("foo".into()))]);
463 let err = resolver.resolve_expr(expr).await.unwrap_err();
464 match err {
465 SecretResolveError::ResolutionFailed { name, reason } => {
466 assert_eq!(name, "aws_secret");
467 assert!(
468 reason.contains("aws_profile"),
469 "error should mention aws_profile, got: {}",
470 reason
471 );
472 }
473 other => panic!("expected ResolutionFailed, got: {:?}", other),
474 }
475 }
476
477 #[mz_ore::test(tokio::test)]
478 async fn test_aws_secret_passthrough_when_unconfigured() {
479 unsafe { std::env::set_var("MZ_TEST_AWS_PASSTHROUGH", "works") };
481 let resolver = SecretResolver::new(&Default::default());
482 let expr = make_env_var_expr("MZ_TEST_AWS_PASSTHROUGH");
483 let resolved = resolver.resolve_expr(expr).await.unwrap();
484 match resolved {
485 Expr::Value(Value::String(s)) => assert_eq!(s, "works"),
486 other => panic!("expected string literal, got: {:?}", other),
487 }
488 }
489}