1
+ //! Cache AWS Secrets Manager secrets in your AWS Lambda function, reducing latency (we don't need to query another service) and cost ([Secrets Manager charges based on queries]).
2
+ //!
3
+ //! # Quickstart
4
+ //! Add the [AWS Parameters and Secrets Lambda Extension] [layer to your Lambda function]. Only version 2 of this layer is currently supported.
5
+ //!
6
+ //! Assuming a secret exists with the name "backend-server" containing a key/value pair with a key of "api_key" and a value of
7
+ //! "dd96eeda-16d3-4c86-975f-4986e603ec8c" (our super secret API key to our backend), this code will get the secret from the cache, querying
8
+ //! Secrets Manager if it is not in the cache, and present it in a strongly-typed `BackendServer` object.
9
+ //!
10
+ //! ```rust
11
+ //! use aws_parameters_and_secrets_lambda::Manager;
12
+ //! use serde::Deserialize;
13
+ //!
14
+ //! #[derive(Deserialize)]
15
+ //! struct BackendServer {
16
+ //! api_key: String
17
+ //! }
18
+ //!
19
+ //! # let server = httpmock::MockServer::start();
20
+ //! # let mock = server.mock(|when, then| {
21
+ //! # when.method("GET").path("/secretsmanager/get");
22
+ //! # then.status(200).body("{\"SecretString\": \"{\\\"api_key\\\": \\\"dd96eeda-16d3-4c86-975f-4986e603ec8c\\\"}\"}");
23
+ //! # });
24
+ //! #
25
+ //! # temp_env::with_vars(
26
+ //! # vec![
27
+ //! # ("AWS_SESSION_TOKEN", Some("xyz")),
28
+ //! # ("PARAMETERS_SECRETS_EXTENSION_HTTP_PORT", Some(&server.port().to_string()))
29
+ //! # ],
30
+ //! # || {
31
+ //! # tokio_test::block_on(
32
+ //! # std::panic::AssertUnwindSafe(
33
+ //! # async {
34
+ //! let manager = Manager::default();
35
+ //! let secret = manager.get_secret("backend-server");
36
+ //! let secret_value: BackendServer = secret.get_typed().await?;
37
+ //! assert_eq!("dd96eeda-16d3-4c86-975f-4986e603ec8c", secret_value.api_key);
38
+ //! # Ok::<_, anyhow::Error>(())
39
+ //! # }
40
+ //! # )
41
+ //! # );
42
+ //! # }
43
+ //! # );
44
+ //! #
45
+ //! # mock.assert();
46
+ //! ```
47
+ //!
48
+ //! [Secrets Manager charges based on queries]: https://aws.amazon.com/secrets-manager/pricing/
49
+ //! [AWS Parameters and Secrets Lambda Extension]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_lambda.html
50
+ //! [layer to your Lambda function]: https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html
51
+
52
+ #![ deny( missing_docs) ]
53
+
1
54
use std:: fmt:: Debug ;
2
55
use std:: { env, sync:: Arc } ;
3
56
@@ -17,6 +70,17 @@ assert_impl_all!(Secret: Send, Sync, Debug, Clone);
17
70
assert_impl_all ! ( VersionIdQuery : Send , Sync , Debug , Clone ) ;
18
71
assert_impl_all ! ( VersionStageQuery : Send , Sync , Debug , Clone ) ;
19
72
73
+ /// Flexible builder for a [`Manager`].
74
+ ///
75
+ /// This sample should be all you ever need to use. It is identical to [`Manager::default`](struct.Manager.html#method.default) but does not panic on failure.
76
+ ///
77
+ /// ```rust
78
+ /// # use aws_parameters_and_secrets_lambda::ManagerBuilder;
79
+ /// # temp_env::with_var("AWS_SESSION_TOKEN", Some("xyz"), || {
80
+ /// let manager = ManagerBuilder::new().build()?;
81
+ /// # Ok::<_, anyhow::Error>(())
82
+ /// # });
83
+ /// ```
20
84
#[ derive( Debug ) ]
21
85
#[ must_use = "construct a `Manager` with the `build` method" ]
22
86
pub struct ManagerBuilder {
@@ -25,23 +89,34 @@ pub struct ManagerBuilder {
25
89
}
26
90
27
91
impl ManagerBuilder {
92
+ /// Create a new builder with the default values.
93
+ #[ allow( clippy:: new_without_default) ]
28
94
pub fn new ( ) -> Self {
29
95
Self {
30
96
port : None ,
31
97
token : None ,
32
98
}
33
99
}
34
100
101
+ /// Use the given port for the extension server instead of the default.
102
+ ///
103
+ /// If this is not called before [`build`](Self::build), then the "PARAMETERS_SECRETS_EXTENSION_HTTP_PORT"
104
+ /// environment variable will be used, or 2773 if this is not set.
35
105
pub fn with_port ( mut self , port : u16 ) -> Self {
36
106
self . port = Some ( port) ;
37
107
self
38
108
}
39
109
110
+ /// Use the given token to authenticate with the extension server instead of the default.
111
+ ///
112
+ /// If this is not called before [`build`](Self::build), then the "AWS_SESSION_TOKEN"
113
+ /// environment variable will be used.
40
114
pub fn with_token ( mut self , token : String ) -> Self {
41
115
self . token = Some ( token) ;
42
116
self
43
117
}
44
118
119
+ /// Create a [`Manager`] from the given values.
45
120
pub fn build ( self ) -> Result < Manager > {
46
121
let port = match self . port {
47
122
Some ( port) => port,
@@ -70,18 +145,18 @@ impl ManagerBuilder {
70
145
}
71
146
}
72
147
73
- impl Default for ManagerBuilder {
74
- fn default ( ) -> Self {
75
- Self :: new ( )
76
- }
77
- }
78
-
148
+ /// Manages connections to the cache. Create one via a [`ManagerBuilder`].
149
+ ///
150
+ /// Ideally, only one of these should exist in a single executable (cloning is fine as it will reuse the connections).
79
151
#[ derive( Debug , Clone ) ]
80
152
pub struct Manager {
81
153
connection : Arc < Connection > ,
82
154
}
83
155
84
156
impl Manager {
157
+ /// Get a representation of a secret that matches a given query.
158
+ ///
159
+ /// Note that this does not return the value of the secret; see [`Secret`] for how to get it.
85
160
pub fn get_secret ( & self , query : impl Query ) -> Secret {
86
161
Secret {
87
162
query : query. get_query_string ( ) ,
@@ -91,6 +166,11 @@ impl Manager {
91
166
}
92
167
93
168
impl Default for Manager {
169
+ /// Initialise a default `Manager` from the environment.
170
+ ///
171
+ /// # Panics
172
+ /// If the AWS Lambda environment is invalid, this will panic.
173
+ /// It is strongly recommended to use a [`ManagerBuilder`] instead as it is more flexible and has proper error handling.
94
174
fn default ( ) -> Self {
95
175
ManagerBuilder :: new ( ) . build ( ) . unwrap ( )
96
176
}
@@ -122,17 +202,22 @@ impl Connection {
122
202
}
123
203
}
124
204
205
+ /// A representation of a secret in Secrets Manager.
125
206
#[ derive( Debug , Clone ) ]
126
207
pub struct Secret {
127
208
query : String ,
128
209
connection : Arc < Connection > ,
129
210
}
130
211
131
212
impl Secret {
213
+ /// Get the plaintext value of this secret.
214
+ ///
215
+ /// Usually, this is in json format, but it can be any data format that you provide to Secrets Manager.
132
216
pub async fn get_raw ( & self ) -> Result < String > {
133
217
self . connection . get_secret ( & self . query ) . await
134
218
}
135
219
220
+ /// Get a value by name from within this secret.
136
221
pub async fn get_single ( & self , name : impl AsRef < str > ) -> Result < String > {
137
222
let raw = & self . get_raw ( ) . await ?;
138
223
let name = name. as_ref ( ) ;
@@ -147,6 +232,7 @@ impl Secret {
147
232
Ok ( String :: from ( secret) )
148
233
}
149
234
235
+ /// Get the value of this secret, represented as a strongly-typed T.
150
236
pub async fn get_typed < T : DeserializeOwned > ( & self ) -> Result < T > {
151
237
let raw = self . get_raw ( ) . await ?;
152
238
Ok ( serde_json:: from_str ( & raw ) ?)
@@ -167,28 +253,37 @@ struct ExtensionResponse {
167
253
secret_string : String ,
168
254
}
169
255
256
+ /// A query for a specific [`Secret`] in AWS Secrets Manager. See [`Manager::get_secret`] for usage.
257
+ ///
258
+ /// # Sealed
259
+ /// You cannot implement this trait yourself.
170
260
#[ sealed]
171
261
pub trait Query {
262
+ #[ doc( hidden) ]
172
263
fn get_query_string ( & self ) -> String ;
173
264
}
174
265
266
+ /// Flexible builder for a complex [`Query`].
175
267
#[ must_use = "continue building a query with the `with_version_id` or `with_version_stage` method" ]
176
268
pub struct QueryBuilder < ' a > {
177
269
secret_id : & ' a str ,
178
270
}
179
271
180
272
impl < ' a > QueryBuilder < ' a > {
273
+ /// Create a new builder with the secret name or ARN.
181
274
pub fn new ( secret_id : & ' a str ) -> Self {
182
275
Self { secret_id }
183
276
}
184
277
278
+ /// Create a query with a version id.
185
279
pub fn with_version_id ( self , version_id : & ' a str ) -> VersionIdQuery < ' a > {
186
280
VersionIdQuery {
187
281
secret_id : self . secret_id ,
188
282
version_id,
189
283
}
190
284
}
191
285
286
+ /// Create a query with a version stage.
192
287
pub fn with_version_stage ( self , version_stage : & ' a str ) -> VersionStageQuery < ' a > {
193
288
VersionStageQuery {
194
289
secret_id : self . secret_id ,
@@ -197,32 +292,79 @@ impl<'a> QueryBuilder<'a> {
197
292
}
198
293
}
199
294
295
+ /// Query by the secret name or ARN.
296
+ ///
297
+ /// This returns the current value of the secret (stage = "AWSCURRENT") and is usually what you want to use.
298
+ ///
299
+ /// Any string-like type can be used, including [`String`], [`&str`], and [`std::borrow::Cow<str>`].
300
+ ///
301
+ /// ```rust
302
+ /// # use aws_parameters_and_secrets_lambda::ManagerBuilder;
303
+ /// # temp_env::with_var("AWS_SESSION_TOKEN", Some("xyz"), || {
304
+ /// # let manager = ManagerBuilder::new().build()?;
305
+ /// let secret = manager.get_secret("secret-name");
306
+ /// # Ok::<_, anyhow::Error>(())
307
+ /// # });
308
+ /// ```
200
309
#[ sealed]
201
310
impl < T : AsRef < str > > Query for T {
202
311
fn get_query_string ( & self ) -> String {
203
312
format ! ( "secretId={}" , self . as_ref( ) )
204
313
}
205
314
}
206
315
316
+ /// A query for a secret with a version id. Create one via [`QueryBuilder::with_version_id`].
317
+ ///
318
+ /// The version id is a unique identifier returned by Secrets Manager when a secret is created or updated.
207
319
#[ derive( Debug , Clone ) ]
208
320
pub struct VersionIdQuery < ' a > {
209
321
secret_id : & ' a str ,
210
322
version_id : & ' a str ,
211
323
}
212
324
325
+ /// Query by the version id of the secret as well as the secret name or ARN.
326
+ ///
327
+ /// ```rust
328
+ /// # use aws_parameters_and_secrets_lambda::ManagerBuilder;
329
+ /// # temp_env::with_var("AWS_SESSION_TOKEN", Some("xyz"), || {
330
+ /// # let manager = ManagerBuilder::new().build()?;
331
+ /// use aws_parameters_and_secrets_lambda::QueryBuilder;
332
+ ///
333
+ /// let query = QueryBuilder::new("secret-name")
334
+ /// .with_version_id("18b94218-543d-4d67-aec5-f8e6a41f7813");
335
+ /// let secret = manager.get_secret(query);
336
+ /// # Ok::<_, anyhow::Error>(())
337
+ /// # });
213
338
#[ sealed]
214
339
impl Query for VersionIdQuery < ' _ > {
215
340
fn get_query_string ( & self ) -> String {
216
341
format ! ( "secretId={}&versionId={}" , self . secret_id, self . version_id)
217
342
}
218
343
}
219
344
345
+ /// A query for a secret with a version stage. Create one via [`QueryBuilder::with_version_stage`].
346
+ ///
347
+ /// The "AWSCURRENT" stage is the current value of the secret, while the "AWSPREVIOUS" stage is the last value of the "AWSCURRENT" stage.
348
+ /// You can also use your own stages.
220
349
#[ derive( Debug , Clone ) ]
221
350
pub struct VersionStageQuery < ' a > {
222
351
secret_id : & ' a str ,
223
352
version_stage : & ' a str ,
224
353
}
225
354
355
+ /// Query by the stage of the secret as well as the secret name or ARN.
356
+ ///
357
+ /// ```rust
358
+ /// # use aws_parameters_and_secrets_lambda::ManagerBuilder;
359
+ /// # temp_env::with_var("AWS_SESSION_TOKEN", Some("xyz"), || {
360
+ /// # let manager = ManagerBuilder::new().build()?;
361
+ /// use aws_parameters_and_secrets_lambda::QueryBuilder;
362
+ ///
363
+ /// let query = QueryBuilder::new("secret-name")
364
+ /// .with_version_stage("AWSPREVIOUS");
365
+ /// let secret = manager.get_secret(query);
366
+ /// # Ok::<_, anyhow::Error>(())
367
+ /// # });
226
368
#[ sealed]
227
369
impl Query for VersionStageQuery < ' _ > {
228
370
fn get_query_string ( & self ) -> String {
0 commit comments