Skip to content

Commit ed78c98

Browse files
author
Anish Cheraku
committed
apiclient: add support for secretsmanager:// URIs
1 parent e2d3aa2 commit ed78c98

File tree

5 files changed

+106
-11
lines changed

5 files changed

+106
-11
lines changed

sources/Cargo.lock

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sources/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ aws-sdk-cloudformation = "1"
115115
aws-sdk-ec2 = "1"
116116
aws-sdk-eks = "1"
117117
aws-sdk-s3 = "1"
118+
aws-sdk-secretsmanager = "1"
118119
aws-smithy-runtime = "1"
119120
aws-smithy-runtime-api = "1"
120121
aws-smithy-types = "1"

sources/api/apiclient/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ tls = ["dep:rustls", "dep:aws-lc-rs", "reqwest/rustls-tls-native-roots"]
1515
fips = ["tls", "aws-lc-rs/fips", "rustls/fips"]
1616

1717
[dependencies]
18-
async-trait = "0.1"
18+
async-trait.workspace = true
1919
aws-smithy-types.workspace = true
2020
aws-config.workspace = true
2121
aws-lc-rs = { workspace = true, optional = true, features = ["bindgen"] }
22-
aws-sdk-s3.workspace = true
22+
aws-sdk-s3.workspace = true
23+
aws-sdk-secretsmanager.workspace = true
2324
base64.workspace = true
2425
constants.workspace = true
2526
datastore.workspace = true

sources/api/apiclient/src/apply.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
//! This module allows application of settings from URIs or stdin. The inputs are expected to be
22
//! TOML settings files, in the same format as user data, or the JSON equivalent. The inputs are
33
//! pulled and applied to the API server in a single transaction.
4-
5-
use aws_sdk_s3::Client;
6-
use crate::uri_resolver::{StdinUri, FileUri, HttpUri, S3Uri, UriResolver};
4+
//! use aws_smithy_runtime_api::client::result::SdkError;
5+
use aws_sdk_secretsmanager::operation::get_secret_value::GetSecretValueError;
6+
use aws_sdk_secretsmanager::config::http::HttpResponse as SdkHttpResponse;
7+
use crate::uri_resolver::{StdinUri, FileUri, HttpUri, S3Uri, UriResolver, SecretsManagerUri};
78
use crate::rando;
89
use futures::future::{join, ready, TryFutureExt};
910
use futures::stream::{self, StreamExt};
@@ -83,11 +84,16 @@ fn select_resolver(input: &str) -> Result<Box<dyn UriResolver>> {
8384
return Ok(Box::new(r));
8485
}
8586

87+
// 6) secretsmanager://
88+
if let Ok(r) = SecretsManagerUri::try_from(input) {
89+
return Ok(Box::new(r));
90+
}
91+
8692
// 2) parse as a URL
8793
let url = Url::parse(input).context(error::UriSnafu { input_source: input.to_string() })?;
8894

8995
// 3) file://
90-
if let Ok(r) = FileUri::try_from(url.clone()) {
96+
if let Ok(r) = FileUri::try_from(&url) {
9197
return Ok(Box::new(r));
9298
}
9399

@@ -97,11 +103,11 @@ fn select_resolver(input: &str) -> Result<Box<dyn UriResolver>> {
97103
}
98104

99105
// 5) s3://
100-
if let Ok(r) = S3Uri::try_from(url) {
106+
if let Ok(r) = S3Uri::try_from(url.clone()) {
101107
return Ok(Box::new(r));
102108
}
103109

104-
NoResolverSnafu {
110+
error::NoResolverSnafu {
105111
input_source: input.to_string(),
106112
}
107113
.fail()
@@ -141,10 +147,12 @@ fn format_change(input: &str, input_source: &str) -> Result<String> {
141147
}
142148

143149
pub(crate) mod error {
150+
use aws_sdk_secretsmanager::operation::get_secret_value::GetSecretValueError;
144151
use snafu::Snafu;
145152

146153
#[derive(Debug, Snafu)]
147154
#[snafu(visibility(pub(crate)))]
155+
148156
pub enum Error {
149157
#[snafu(display("Failed to commit combined settings to '{}': {}", uri, source))]
150158
CommitApply {
@@ -234,6 +242,18 @@ pub(crate) mod error {
234242
#[snafu(display("Invalid S3 URI scheme for '{}', expected s3://", input_source))]
235243
S3UriScheme { input_source: String },
236244

245+
#[snafu(display("Invalid Secrets Manager URI scheme for '{}', expected secretsmanager://", input_source))]
246+
SecretsManagerUri { input_source: String },
247+
248+
#[snafu(display("Failed to fetch secret '{}' from Secrets Manager: {}", secret_id, source))]
249+
SecretsManagerGet {
250+
secret_id: String,
251+
source: aws_sdk_secretsmanager::error::SdkError<GetSecretValueError>,
252+
},
253+
254+
#[snafu(display("Secrets Manager secret '{}' did not return a string value", secret_id))]
255+
SecretsManagerStringMissing { secret_id: String },
256+
237257
#[snafu(display(
238258
"Failed to translate TOML from '{}' to JSON for API: {}",
239259
input_source,

sources/api/apiclient/src/uri_resolver.rs

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// src/uri_resolver.rs
22

33
use async_trait::async_trait;
4+
use aws_sdk_secretsmanager as sm;
5+
use aws_config;
46
use snafu::{ensure, ResultExt, OptionExt};
57
use std::convert::TryFrom;
68
use std::path::PathBuf;
@@ -9,7 +11,7 @@ use tokio::io::AsyncReadExt;
911
use reqwest::Url;
1012
use crate::apply::{Error, Result};
1113
use crate::apply::error::{
12-
FileReadSnafu, FileUriSnafu, ReqwestSnafu, StdinReadSnafu, S3UriMissingBucketSnafu, InvalidFileUriSnafu, InvalidHTTPUriSnafu, S3UriSchemeSnafu
14+
FileReadSnafu, FileUriSnafu, ReqwestSnafu, StdinReadSnafu, S3UriMissingBucketSnafu, InvalidFileUriSnafu, InvalidHTTPUriSnafu, S3UriSchemeSnafu, SecretsManagerUriSnafu, SecretsManagerGetSnafu, SecretsManagerStringMissingSnafu,
1315
};
1416

1517
/// Anything that can fetch itself as a UTF-8 `String`.
@@ -51,10 +53,10 @@ pub struct FileUri {
5153
path: PathBuf,
5254
}
5355

54-
impl TryFrom<Url> for FileUri {
56+
impl TryFrom<&Url> for FileUri {
5557
type Error = Error;
5658

57-
fn try_from(url: Url) -> std::result::Result<Self, Self::Error> {
59+
fn try_from(url: &Url) -> std::result::Result<Self, Self::Error> {
5860
// only accept file://
5961
ensure!(
6062
url.scheme() == "file",
@@ -133,6 +135,7 @@ impl TryFrom<Url> for S3Uri {
133135
type Error = Error;
134136

135137
fn try_from(url: Url) -> std::result::Result<Self, Self::Error> {
138+
log::error!("tryfromS3");
136139
ensure!(
137140
url.scheme() == "s3",
138141
S3UriSchemeSnafu { input_source: url.to_string() }
@@ -157,3 +160,49 @@ impl UriResolver for S3Uri {
157160
}
158161
}
159162

163+
/// secretsmanager://<secret_id>
164+
pub struct SecretsManagerUri {
165+
secret_id: String,
166+
}
167+
168+
impl TryFrom<&str> for SecretsManagerUri {
169+
type Error = Error;
170+
171+
fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
172+
const PREFIX: &str = "secretsmanager://";
173+
if let Some(id) = s.strip_prefix(PREFIX) {
174+
if id.is_empty() {
175+
return Err(Error::SecretsManagerUri { input_source: s.to_string() });
176+
}
177+
return Ok(SecretsManagerUri { secret_id: id.to_string() });
178+
}
179+
Err(Error::SecretsManagerUri { input_source: s.to_string() })
180+
}
181+
}
182+
183+
#[async_trait]
184+
impl UriResolver for SecretsManagerUri {
185+
async fn resolve(&self) -> Result<String> {
186+
use aws_config::{self, BehaviorVersion, Region};
187+
use aws_sdk_secretsmanager;
188+
189+
// 1) load AWS config (region/account via env)
190+
let cfg = aws_config::load_from_env().await;
191+
let client = aws_sdk_secretsmanager::Client::new(&cfg);
192+
193+
// 2) fetch the secret, propagating any SdkError into SecretsManagerGet
194+
let resp = client
195+
.get_secret_value()
196+
.secret_id(self.secret_id.clone())
197+
.send()
198+
.await
199+
.context(SecretsManagerGetSnafu { secret_id: self.secret_id.clone() })?;
200+
201+
// 3) extract the string payload, or error if it was missing
202+
resp.secret_string()
203+
.map(str::to_string)
204+
.context(SecretsManagerStringMissingSnafu { secret_id: self.secret_id.clone() })
205+
}
206+
}
207+
208+

0 commit comments

Comments
 (0)