Skip to content

Commit c883c1b

Browse files
authored
Merge pull request #398 from adorsys/393-update-keystore-to-use-aws-secrets-manager
393 update keystore to use aws secrets manager
2 parents 13cf773 + 1e063b8 commit c883c1b

File tree

13 files changed

+399
-51
lines changed

13 files changed

+399
-51
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,12 @@ curve25519-dalek = "4.1.3"
9898
ed25519-dalek = "2.1.1"
9999
tracing-subscriber = "0.3.19"
100100
tower-http = "0.6.2"
101-
aws-config = "1.1"
101+
aws-config = "1.6"
102+
aws-sdk-kms = "1.76"
103+
aws-sdk-secretsmanager = "1.68"
104+
aws_secretsmanager_caching = "1.2"
102105
axum-prometheus = "0.8.0"
103106
prometheus-client = "0.23.1"
104-
aws-sdk-kms = "1.49"
105107
base64ct = { version = "1.6.0", default-features = false }
106108
zeroize = { version = "1.8.1", default-features = false }
107109

README.md

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,7 @@ docker run --name mongodb -d mongo
6666

6767
### Environmental variables
6868

69-
You need to create a **`.env`** file in the root directory of the project and add the following variables:
70-
71-
```sh
72-
SERVER_PUBLIC_DOMAIN="http://localhost:8080"
73-
STORAGE_DIRPATH="./storage"
74-
MONGO_DBN="DIDComm_DB"
75-
MONGO_URI="mongodb://localhost:27017"
76-
```
77-
78-
> The values can be changed according to your needs.
69+
You need to create a **`.env`** file in the root directory of the project. Take a look at the [example](.env.example) file for more information about the needed variables.
7970

8071
You can now start the mediator server:
8172

crates/keystore/Cargo.toml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
[package]
22
name = "keystore"
3-
version = "0.1.0"
4-
authors = ["adorsys GmbH Co. KG"]
3+
version = "0.1.0"
4+
authors = ["adorsys GmbH Co. KG"]
55
license = "Apache-2.0"
66
description = "The `keystore` is a core component of the DIDComm Mediator system, designed to facilitate secure, decentralized communication within the Self-Sovereign Identity (SSI) ecosystem."
77
repository = "https://github.com/adorsys/didcomm-mediator-rs/tree/main/crates/web-plugins/keystore"
8-
keywords = ["keystore", "didcomm", "didcomm mediator", "didcomm messaging", "rust mediator"]
8+
keywords = [
9+
"keystore",
10+
"didcomm",
11+
"didcomm mediator",
12+
"didcomm messaging",
13+
"rust mediator",
14+
]
915
categories = ["cryptography"]
1016
edition = "2021"
1117

@@ -19,9 +25,13 @@ mongodb.workspace = true
1925
database.workspace = true
2026
serde.workspace = true
2127
eyre.workspace = true
28+
tracing.workspace = true
2229
thiserror.workspace = true
2330
serde_json.workspace = true
31+
aws-config.workspace = true
2432
aws-sdk-kms.workspace = true
33+
aws-sdk-secretsmanager.workspace = true
34+
aws_secretsmanager_caching.workspace = true
2535
tokio = { workspace = true, features = ["full"] }
2636

2737
[features]

crates/keystore/src/error.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ use core::fmt::{Debug, Display};
22
use std::error::Error as StdError;
33

44
use aws_sdk_kms::operation::{decrypt::DecryptError, encrypt::EncryptError};
5+
use aws_sdk_secretsmanager::operation::{
6+
create_secret::CreateSecretError, delete_secret::DeleteSecretError,
7+
describe_secret::DescribeSecretError, get_secret_value::GetSecretValueError,
8+
};
59
use serde_json::error::Category;
610

711
/// Kind of error that can occur during key store operations.
@@ -106,6 +110,30 @@ impl From<aws_sdk_kms::error::SdkError<DecryptError>> for Error {
106110
}
107111
}
108112

113+
impl From<aws_sdk_secretsmanager::error::SdkError<DescribeSecretError>> for Error {
114+
fn from(err: aws_sdk_secretsmanager::error::SdkError<DescribeSecretError>) -> Self {
115+
Error::new(ErrorKind::RepositoryFailure, err)
116+
}
117+
}
118+
119+
impl From<aws_sdk_secretsmanager::error::SdkError<CreateSecretError>> for Error {
120+
fn from(err: aws_sdk_secretsmanager::error::SdkError<CreateSecretError>) -> Self {
121+
Error::new(ErrorKind::RepositoryFailure, err)
122+
}
123+
}
124+
125+
impl From<aws_sdk_secretsmanager::error::SdkError<GetSecretValueError>> for Error {
126+
fn from(err: aws_sdk_secretsmanager::error::SdkError<GetSecretValueError>) -> Self {
127+
Error::new(ErrorKind::RepositoryFailure, err)
128+
}
129+
}
130+
131+
impl From<aws_sdk_secretsmanager::error::SdkError<DeleteSecretError>> for Error {
132+
fn from(err: aws_sdk_secretsmanager::error::SdkError<DeleteSecretError>) -> Self {
133+
Error::new(ErrorKind::RepositoryFailure, err)
134+
}
135+
}
136+
109137
impl std::error::Error for Error {
110138
fn source(&self) -> Option<&(dyn StdError + 'static)> {
111139
Some(self.source())

crates/keystore/src/lib.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ mod repository;
1010
#[cfg(any(test, feature = "test-utils"))]
1111
mod tests;
1212

13+
use aws_config::SdkConfig;
1314
// Public re-exports
1415
pub use encryptor::KeyEncryption;
1516
pub use error::{Error, ErrorKind};
1617
pub use repository::SecretRepository;
1718

1819
use aws_sdk_kms::Client as KmsClient;
1920
use encryptor::{aws_kms::AwsKmsEncryptor, plaintext::NoEncryption};
20-
use repository::{mongodb::MongoSecretRepository, no_repo::NoRepository};
21+
use repository::{aws::AwsSecretsManager, mongodb::MongoSecretRepository, no_repo::NoRepository};
2122
use serde::{Deserialize, Serialize};
2223
use std::sync::Arc;
2324

@@ -98,6 +99,25 @@ impl Keystore {
9899
}
99100
}
100101

102+
/// Create a new key store with AWS Secrets Manager as storage backend.
103+
///
104+
/// # Example
105+
///
106+
/// ```no_run
107+
/// use keystore::Keystore;
108+
///
109+
/// let config = aws_config::load_from_env().await;
110+
/// let keystore = Keystore::with_aws_secrets_manager(&config);
111+
/// ```
112+
pub async fn with_aws_secrets_manager(config: &SdkConfig) -> Self {
113+
let repository = AwsSecretsManager::new(config).await;
114+
let encryptor = NoEncryption;
115+
Self {
116+
repository: Arc::new(repository),
117+
encryptor: Arc::new(encryptor),
118+
}
119+
}
120+
101121
/// Create a new key store with **AWS KMS** as encryption backend and mongoDB as storage backend.
102122
///
103123
/// # Example

crates/keystore/src/repository.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub(crate) mod aws;
12
pub(crate) mod mongodb;
23
pub(crate) mod no_repo;
34

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use std::{num::NonZeroUsize, time::Duration};
2+
3+
use async_trait::async_trait;
4+
use aws_config::SdkConfig;
5+
use aws_sdk_secretsmanager::{
6+
operation::get_secret_value::GetSecretValueError, Client as SecretsClient,
7+
Config as SecretsConfig,
8+
};
9+
use aws_secretsmanager_caching::SecretsManagerCachingClient as SecretsCacheClient;
10+
use eyre::eyre;
11+
use tracing::warn;
12+
13+
use crate::{Error, ErrorKind};
14+
15+
use super::SecretRepository;
16+
17+
/// Type used for AWS Secrets Manager operations
18+
pub(crate) struct AwsSecretsManager {
19+
client: SecretsClient,
20+
cache: SecretsCacheClient,
21+
}
22+
23+
impl AwsSecretsManager {
24+
/// Create a new instance of [AwsSecretsManager] with the given AWS SDK config
25+
pub async fn new(config: &SdkConfig) -> Self {
26+
let client = SecretsClient::new(config);
27+
// Cache size: 100 and a TTL of 5 minutes
28+
let cache = SecretsCacheClient::from_builder(
29+
SecretsConfig::from(config).to_builder(),
30+
NonZeroUsize::new(100).unwrap(),
31+
Duration::from_secs(300),
32+
true,
33+
)
34+
.await
35+
.unwrap();
36+
37+
Self { client, cache }
38+
}
39+
}
40+
41+
#[async_trait]
42+
impl SecretRepository for AwsSecretsManager {
43+
async fn store(&self, name: &str, data: &[u8]) -> Result<(), Error> {
44+
use aws_sdk_secretsmanager::error::SdkError;
45+
46+
// Store a secret only if it does not already exist
47+
match self.client.describe_secret().secret_id(name).send().await {
48+
Ok(_) => {
49+
warn!("Secret {name} already exists. Skipping...");
50+
Ok(())
51+
}
52+
Err(SdkError::ServiceError(err)) if err.err().is_resource_not_found_exception() => {
53+
let secret = String::from_utf8_lossy(data).to_string();
54+
// Secret does not exist, try to create it
55+
self.client
56+
.create_secret()
57+
.name(name)
58+
.secret_string(secret)
59+
.send()
60+
.await?;
61+
Ok(())
62+
}
63+
Err(sdk_err) => Err(sdk_err.into()),
64+
}
65+
}
66+
67+
async fn find(&self, name: &str) -> Result<Option<Vec<u8>>, Error> {
68+
use aws_sdk_secretsmanager::error::SdkError;
69+
70+
match self.cache.get_secret_value(name, None, None, false).await {
71+
Ok(value) => Ok(value.secret_string.map(|s| s.into_bytes())),
72+
Err(err) => {
73+
// Check for ResourceNotFoundException
74+
if let Some(SdkError::ServiceError(service_err)) =
75+
err.downcast_ref::<SdkError<GetSecretValueError>>()
76+
{
77+
if service_err.err().is_resource_not_found_exception() {
78+
return Ok(None);
79+
}
80+
}
81+
Err(Error::msg(ErrorKind::RepositoryFailure, eyre!("{err}")))
82+
}
83+
}
84+
}
85+
86+
async fn delete(&self, name: &str) -> Result<(), Error> {
87+
self.client.delete_secret().secret_id(name).send().await?;
88+
89+
// Invalidate cache by refreshing the secret
90+
let _ = self.cache.get_secret_value(name, None, None, true).await;
91+
Ok(())
92+
}
93+
}

crates/web-plugins/did-endpoint/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ dotenv-flow.workspace = true
2323
multibase.workspace = true
2424
tracing.workspace = true
2525
http-body-util.workspace = true
26+
aws-config.workspace = true
2627
uuid = { workspace = true, features = ["v4"] }
2728
hyper = { workspace = true, features = ["full"] }
2829
tokio = { workspace = true, features = ["full"] }

crates/web-plugins/did-endpoint/src/plugin.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use super::{didgen, web};
2+
use aws_config::BehaviorVersion;
23
use axum::Router;
34
use filesystem::FileSystem;
45
use keystore::Keystore;
56
use plugin_api::{Plugin, PluginError};
67
use std::sync::{Arc, Mutex};
8+
use tokio::{runtime::Handle, task};
79

810
#[derive(Default)]
911
pub struct DidEndpoint {
@@ -44,7 +46,12 @@ impl Plugin for DidEndpoint {
4446
fn mount(&mut self) -> Result<(), PluginError> {
4547
let env = get_env()?;
4648
let mut filesystem = filesystem::StdFileSystem;
47-
let keystore = Keystore::with_mongodb();
49+
let keystore = task::block_in_place(move || {
50+
Handle::current().block_on(async move {
51+
let aws_config = aws_config::load_defaults(BehaviorVersion::latest()).await;
52+
Keystore::with_aws_secrets_manager(&aws_config).await
53+
})
54+
});
4855

4956
if didgen::validate_diddoc(env.storage_dirpath.as_ref(), &keystore, &mut filesystem)
5057
.is_err()

0 commit comments

Comments
 (0)