Skip to content

Commit 0b07ee4

Browse files
feat: convert external key manager feature flag to runtime config with enum-based validation pattern (#155)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
1 parent 7309812 commit 0b07ee4

18 files changed

Lines changed: 300 additions & 115 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,15 @@ rust-version = "1.85"
99

1010
[features]
1111
default = ["caching"]
12-
release = ["kms-aws", "middleware", "key_custodian", "limit", "kms-hashicorp-vault", "caching", "external_key_manager_mtls"]
13-
dev = ["kms-aws", "middleware", "key_custodian", "limit", "kms-hashicorp-vault", "caching"]
12+
release = ["kms-aws", "middleware", "key_custodian", "limit", "kms-hashicorp-vault", "caching", "external_key_manager"]
1413
kms-aws = ["dep:aws-config", "dep:aws-sdk-kms"]
1514
kms-hashicorp-vault = ["dep:vaultrs"]
1615
limit = []
1716
middleware = []
1817
key_custodian = []
1918
caching = ["dep:moka"]
2019
console = ["tokio/tracing", "dep:console-subscriber"]
21-
external_key_manager = []
22-
external_key_manager_mtls = ["external_key_manager", "reqwest/rustls-tls"]
20+
external_key_manager = ["reqwest/rustls-tls"]
2321

2422
[dependencies]
2523
async-trait = "0.1.81"
Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,17 @@ client_idle_timeout = 90 # timeout for idle sockets being kept-alive
6969
pool_max_idle_per_host = 10 # maximum idle connection per host allowed in the pool.
7070
identity = "" # identity to be used for client certificate authentication in mtls.
7171

72-
# Configuration for the external Key Manager Service
72+
# External key manager configuration
7373
[external_key_manager]
74-
url = "http://localhost:5000" # URL of the encryption service
75-
cert = "" # Represents a server X509 certificate for mtls
74+
mode = "disabled" # Options: "disabled", "enabled", "enabled_with_mtls"
75+
76+
# Required when mode is "enabled" or "enabled_with_mtls"
77+
# url = "https://external-key-manager.example.com"
78+
79+
# Required when mode is "enabled_with_mtls"
80+
# ca_cert = """
81+
# -----BEGIN CERTIFICATE-----
82+
# ...
83+
# -----END CERTIFICATE-----
84+
# """
85+

config/development.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ locker_private_key = ""
2929
public = { master_key = "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", public_key = "", schema = "public" }
3030

3131
[external_key_manager]
32-
url = "http://localhost:5000"
32+
mode = "disabled"

src/api_client.rs

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use std::str::FromStr;
22

3+
#[cfg(feature = "external_key_manager")]
4+
use crate::config::ExternalKeyManagerConfig;
35
use crate::{
46
config::GlobalConfig,
57
error::{self, ResultContainerExt},
68
};
79
use masking::Maskable;
8-
#[cfg(feature = "external_key_manager_mtls")]
10+
#[cfg(feature = "external_key_manager")]
911
use masking::PeekInterface;
1012
use reqwest::StatusCode;
1113
use reqwest::{
@@ -51,10 +53,29 @@ pub struct ApiClientConfig {
5153
pub client_idle_timeout: u64,
5254
pub pool_max_idle_per_host: usize,
5355
// KMS encrypted
54-
#[cfg(feature = "external_key_manager_mtls")]
56+
#[cfg(feature = "external_key_manager")]
5557
pub identity: masking::Secret<String>,
5658
}
5759

60+
impl ApiClientConfig {
61+
#[cfg(feature = "external_key_manager")]
62+
pub fn validate_for_mtls(
63+
&self,
64+
external_key_manager_config: &ExternalKeyManagerConfig,
65+
) -> Result<(), crate::error::ConfigurationError> {
66+
// Only validate if external key manager is enabled with mTLS
67+
if external_key_manager_config.is_mtls_enabled() && self.identity.peek().is_empty() {
68+
return Err(
69+
crate::error::ConfigurationError::InvalidConfigurationValueError(
70+
"api_client.identity is required when mTLS is enabled".into(),
71+
),
72+
);
73+
}
74+
75+
Ok(())
76+
}
77+
}
78+
5879
#[derive(Clone)]
5980
pub struct ApiClient {
6081
pub inner: reqwest::Client,
@@ -80,24 +101,36 @@ impl ApiClient {
80101
))
81102
.pool_max_idle_per_host(global_config.api_client.pool_max_idle_per_host);
82103

83-
#[cfg(feature = "external_key_manager_mtls")]
104+
#[cfg(feature = "external_key_manager")]
84105
{
85-
let client_identity =
86-
reqwest::Identity::from_pem(global_config.api_client.identity.peek().as_ref())
87-
.change_error(error::ApiClientError::IdentityParseFailed)?;
88-
89-
let external_key_manager_cert = reqwest::Certificate::from_pem(
90-
global_config.external_key_manager.cert.peek().as_ref(),
91-
)
92-
.change_error(error::ApiClientError::CertificateParseFailed {
93-
service: "external_key_manager",
94-
})?;
95-
96-
client = client
97-
.use_rustls_tls()
98-
.identity(client_identity)
99-
.add_root_certificate(external_key_manager_cert)
100-
.https_only(true);
106+
// mTLS-specific configuration
107+
if global_config.external_key_manager.is_mtls_enabled() {
108+
let client_identity =
109+
reqwest::Identity::from_pem(global_config.api_client.identity.peek().as_ref())
110+
.change_error(error::ApiClientError::IdentityParseFailed)?;
111+
112+
let ca_cert = global_config
113+
.external_key_manager
114+
.get_ca_cert()
115+
.ok_or_else(|| {
116+
error::ApiClientError::MissingConfigurationError(
117+
"CA certificate not configured for mTLS",
118+
)
119+
})?;
120+
121+
let key_manager_ca_cert = reqwest::Certificate::from_pem(ca_cert.peek().as_ref())
122+
.change_error(
123+
error::ApiClientError::CertificateParseFailed {
124+
service: "external_key_manager",
125+
},
126+
)?;
127+
128+
client = client
129+
.use_rustls_tls()
130+
.identity(client_identity)
131+
.add_root_certificate(key_manager_ca_cert)
132+
.https_only(true);
133+
}
101134
}
102135

103136
let client = client

src/app.rs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,15 @@ where
113113
);
114114

115115
// v2 routes
116-
let router = router.nest(
116+
#[cfg_attr(
117+
all(
118+
not(feature = "middleware"),
119+
not(feature = "external_key_manager"),
120+
not(feature = "key_custodian")
121+
),
122+
allow(unused_mut)
123+
)]
124+
let mut router = router.nest(
117125
"/api/v2/vault",
118126
axum::Router::new()
119127
.route("/delete", post(routes_v2::data::delete_data))
@@ -126,16 +134,28 @@ where
126134
);
127135

128136
#[cfg(feature = "middleware")]
129-
let router = router.layer(middleware::from_fn_with_state(
130-
global_app_state.clone(),
131-
custom_middleware::middleware,
132-
));
137+
{
138+
router = router.layer(middleware::from_fn_with_state(
139+
global_app_state.clone(),
140+
custom_middleware::middleware,
141+
));
142+
}
133143

134144
#[cfg(feature = "external_key_manager")]
135-
let router = router.route("/key/transfer", post(routes::key_migration::transfer_keys));
145+
{
146+
if global_app_state
147+
.global_config
148+
.external_key_manager
149+
.is_external()
150+
{
151+
router = router.route("/key/transfer", post(routes::key_migration::transfer_keys));
152+
}
153+
}
136154

137155
#[cfg(feature = "key_custodian")]
138-
let router = router.nest("/custodian", routes::key_custodian::serve());
156+
{
157+
router = router.nest("/custodian", routes::key_custodian::serve());
158+
}
139159

140160
let router = router.layer(
141161
tower_trace::TraceLayer::new_for_http()

src/bin/locker.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@ use tartarus::{logger, tenant::GlobalAppState};
33
#[allow(clippy::expect_used)]
44
#[tokio::main]
55
async fn main() -> Result<(), Box<dyn std::error::Error>> {
6-
if cfg!(feature = "dev") {
7-
eprintln!("This is a dev build, not for production use");
8-
}
9-
106
let mut global_config =
117
tartarus::config::GlobalConfig::new().expect("Failed while parsing config");
128

0 commit comments

Comments
 (0)