Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add mtls for keymanager api requests #5048

Open
wants to merge 5 commits into
base: add_encryption_service
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ license.workspace = true

[features]
default = ["kv_store", "stripe", "oltp", "olap", "backwards_compatibility", "accounts_cache", "dummy_connector", "payouts", "payout_retry", "business_profile_routing", "connector_choice_mca_id", "profile_specific_fallback_routing", "retry", "frm"]
keymanager_mtls = ["reqwest/rustls-tls"]
email = ["external_services/email", "scheduler/email", "olap"]
frm = ["api_models/frm"]
stripe = ["dep:serde_qs"]
Expand Down
4 changes: 4 additions & 0 deletions crates/router/src/configs/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8782,6 +8782,10 @@ impl Default for super::settings::KeyManagerConfig {
fn default() -> Self {
Self {
url: String::from("localhost:5000"),
#[cfg(feature = "keymanager_mtls")]
ca: String::default().into(),
#[cfg(feature = "keymanager_mtls")]
cert: String::default().into(),
}
}
}
4 changes: 4 additions & 0 deletions crates/router/src/configs/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ pub struct KvConfig {
#[derive(Debug, Deserialize, Clone)]
pub struct KeyManagerConfig {
pub url: String,
#[cfg(feature = "keymanager_mtls")]
pub cert: Secret<String>,
#[cfg(feature = "keymanager_mtls")]
pub ca: Secret<String>,
}

#[derive(Debug, Deserialize, Clone, Default)]
Expand Down
116 changes: 97 additions & 19 deletions crates/router/src/encryption.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,116 @@
use crate::{
errors, headers, services,
types::{domain::EncryptionCreateRequest, RequestContent, Response},
SessionState,
};
use std::str::FromStr;

use error_stack::ResultExt;
use http::{HeaderMap, HeaderName, HeaderValue};
#[cfg(feature = "keymanager_mtls")]
use masking::PeekInterface;
use once_cell::sync::OnceCell;

use crate::{errors, headers, types::domain::EncryptionCreateRequest, SessionState};

static ENCRYPTION_API_CLIENT: OnceCell<reqwest::Client> = OnceCell::new();

#[allow(unused_mut)]
pub fn get_api_encryption_client(
state: &SessionState,
) -> errors::CustomResult<reqwest::Client, errors::ApiClientError> {
let proxy = &state.conf.proxy;

let get_client = || {
let mut client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.pool_idle_timeout(std::time::Duration::from_secs(
proxy.idle_pool_connection_timeout.unwrap_or_default(),
));

#[cfg(feature = "keymanager_mtls")]
{
let ca = state.conf.key_manager.ca.clone();
let cert = state.conf.key_manager.cert.clone();

let identity = reqwest::Identity::from_pem(cert.peek().as_ref())
.change_context(errors::ApiClientError::ClientConstructionFailed)?;
let ca_cert = reqwest::Certificate::from_pem(ca.peek().as_ref())
.change_context(errors::ApiClientError::ClientConstructionFailed)?;

Comment on lines +26 to +34
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use

if cfg!(feature = "keymanager_mtls") 

you would not need #[allow(unused_mut)] in that case

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot do this since ca and cert have compile time feature constraints

client = client
.use_rustls_tls()
.identity(identity)
.add_root_certificate(ca_cert)
.https_only(true);
}

client
.build()
.change_context(errors::ApiClientError::ClientConstructionFailed)
};

Ok(ENCRYPTION_API_CLIENT.get_or_try_init(get_client)?.clone())
}

pub async fn send_encryption_request<T>(
state: &SessionState,
headers: Vec<(String, String)>,
url: String,
request_body: T,
) -> errors::CustomResult<reqwest::Response, errors::ApiClientError>
where
T: serde::Serialize,
{
let client = get_api_encryption_client(state)?;
let url =
reqwest::Url::parse(&url).change_context(errors::ApiClientError::UrlEncodingFailed)?;

let headers = headers.into_iter().try_fold(
HeaderMap::new(),
|mut header_map, (header_name, header_value)| {
let header_name = HeaderName::from_str(&header_name)
.change_context(errors::ApiClientError::HeaderMapConstructionFailed)?;
let header_value = HeaderValue::from_str(&header_value)
.change_context(errors::ApiClientError::HeaderMapConstructionFailed)?;
header_map.append(header_name, header_value);
Ok::<_, error_stack::Report<errors::ApiClientError>>(header_map)
},
)?;

client
.post(url)
.json(&request_body)
.headers(headers)
.send()
.await
.change_context(errors::ApiClientError::RequestNotSent(
"Unable to send request to encryption service".to_string(),
))
}

pub async fn call_encryption_service<T>(
state: &SessionState,
endpoint: &str,
request_body: T,
) -> errors::CustomResult<Result<Response, Response>, errors::ApiClientError>
) -> errors::CustomResult<reqwest::Response, errors::ApiClientError>
where
T: masking::ErasedMaskSerialize + Send + Sync + 'static,
T: serde::Serialize + Send + Sync + 'static,
{
let url = format!("{}/{}", &state.conf.key_manager.url, endpoint);

let encryption_req = services::RequestBuilder::new()
.method(services::Method::Post)
.url(&url)
.attach_default_headers()
.headers(vec![(
send_encryption_request(
state,
vec![(
headers::CONTENT_TYPE.to_string(),
"application/json".to_string().into(),
)])
.set_body(RequestContent::Json(Box::new(request_body)))
.build();

services::call_connector_api(state, encryption_req, "EncryptionServiceRequest").await
"application/json".to_string(),
)],
url,
request_body,
)
.await
}

pub async fn create_key_in_key_manager(
state: &SessionState,
request_body: EncryptionCreateRequest,
) -> errors::CustomResult<(), errors::ApiClientError> {
let _ = call_encryption_service(state, "/key/create", request_body).await?;
let _ = call_encryption_service(state, "key/create", request_body).await?;

Ok(())
}
Loading