Bug: [feat] add multitenancy support to keymanager




diff --git a/config/config.example.toml b/config/config.example.toml
index 99926632114..eb30435d5f8 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -762,7 +762,7 @@ sdk_eligible_payment_methods = "card"
 
 [multitenancy]
 enabled = false
-global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
+global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
 
 [multitenancy.tenants.public]
 base_url = "http://localhost:8080"               # URL of the tenant
diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml
index 967b847dae5..809edf1bac6 100644
--- a/config/deployments/env_specific.toml
+++ b/config/deployments/env_specific.toml
@@ -303,7 +303,7 @@ region = "kms_region" # The AWS region used by the KMS SDK for decrypting data.
 
 [multitenancy]
 enabled = false
-global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
+global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
 
 [multitenancy.tenants.public]
 base_url = "http://localhost:8080" 
diff --git a/config/development.toml b/config/development.toml
index 4c9b8516b5a..e3631b96b17 100644
--- a/config/development.toml
+++ b/config/development.toml
@@ -794,7 +794,7 @@ sdk_eligible_payment_methods = "card"
 
 [multitenancy]
 enabled = false
-global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
+global_tenant = { tenant_id = "global" ,schema = "public", redis_key_prefix = "global", clickhouse_database = "default"}
 
 [multitenancy.tenants.public]
 base_url = "http://localhost:8080"
diff --git a/config/docker_compose.toml b/config/docker_compose.toml
index 75699d0a967..6f95380d2db 100644
--- a/config/docker_compose.toml
+++ b/config/docker_compose.toml
@@ -635,7 +635,7 @@ sdk_eligible_payment_methods = "card"
 
 [multitenancy]
 enabled = false
-global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default" }
+global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "", clickhouse_database = "default" }
 
 [multitenancy.tenants.public]
 base_url = "http://localhost:8080" 
diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs
index 3b437b703be..fdda26cc77c 100644
--- a/crates/common_utils/src/consts.rs
+++ b/crates/common_utils/src/consts.rs
@@ -149,3 +149,6 @@ pub const APPLEPAY_VALIDATION_URL: &str =
 
 /// Request ID
 pub const X_REQUEST_ID: &str = "x-request-id";
+
+/// Default Tenant ID for the `Global` tenant
+pub const DEFAULT_GLOBAL_TENANT_ID: &str = "global";
diff --git a/crates/common_utils/src/id_type/tenant.rs b/crates/common_utils/src/id_type/tenant.rs
index 953bf82287a..0584496332a 100644
--- a/crates/common_utils/src/id_type/tenant.rs
+++ b/crates/common_utils/src/id_type/tenant.rs
@@ -1,4 +1,7 @@
-use crate::errors::{CustomResult, ValidationError};
+use crate::{
+    consts::DEFAULT_GLOBAL_TENANT_ID,
+    errors::{CustomResult, ValidationError},
+};
 
 crate::id_type!(
     TenantId,
@@ -15,6 +18,13 @@ crate::impl_queryable_id_type!(TenantId);
 crate::impl_to_sql_from_sql_id_type!(TenantId);
 
 impl TenantId {
+    /// Get the default global tenant ID
+    pub fn get_default_global_tenant_id() -> Self {
+        Self(super::LengthId::new_unchecked(
+            super::AlphaNumericId::new_unchecked(DEFAULT_GLOBAL_TENANT_ID.to_string()),
+        ))
+    }
+
     /// Get tenant id from String
     pub fn try_from_string(tenant_id: String) -> CustomResult<Self, ValidationError> {
         Self::try_from(std::borrow::Cow::from(tenant_id))
diff --git a/crates/common_utils/src/keymanager.rs b/crates/common_utils/src/keymanager.rs
index 53761951791..0dc05b22fd7 100644
--- a/crates/common_utils/src/keymanager.rs
+++ b/crates/common_utils/src/keymanager.rs
@@ -11,11 +11,11 @@ use once_cell::sync::OnceCell;
 use router_env::{instrument, logger, tracing};
 
 use crate::{
-    consts::BASE64_ENGINE,
+    consts::{BASE64_ENGINE, TENANT_HEADER},
     errors,
     types::keymanager::{
         BatchDecryptDataRequest, DataKeyCreateResponse, DecryptDataRequest,
-        EncryptionCreateRequest, EncryptionTransferRequest, KeyManagerState,
+        EncryptionCreateRequest, EncryptionTransferRequest, GetKeymanagerTenant, KeyManagerState,
         TransientBatchDecryptDataRequest, TransientDecryptDataRequest,
     },
 };
@@ -100,7 +100,7 @@ pub async fn call_encryption_service<T, R>(
     request_body: T,
 ) -> errors::CustomResult<R, errors::KeyManagerClientError>
 where
-    T: ConvertRaw + Send + Sync + 'static + Debug,
+    T: GetKeymanagerTenant + ConvertRaw + Send + Sync + 'static + Debug,
     R: serde::de::DeserializeOwned,
 {
     let url = format!("{}/{endpoint}", &state.url);
@@ -122,6 +122,15 @@ where
                 .change_context(errors::KeyManagerClientError::FailedtoConstructHeader)?,
         ))
     }
+
+    //Add Tenant ID
+    header.push((
+        HeaderName::from_str(TENANT_HEADER)
+            .change_context(errors::KeyManagerClientError::FailedtoConstructHeader)?,
+        HeaderValue::from_str(request_body.get_tenant_id(state).get_string_repr())
+            .change_context(errors::KeyManagerClientError::FailedtoConstructHeader)?,
+    ));
+
     let response = send_encryption_request(
         state,
         HeaderMap::from_iter(header.into_iter()),
diff --git a/crates/common_utils/src/types/keymanager.rs b/crates/common_utils/src/types/keymanager.rs
index 09d26bd91ef..f18c6656207 100644
--- a/crates/common_utils/src/types/keymanager.rs
+++ b/crates/common_utils/src/types/keymanager.rs
@@ -23,8 +23,23 @@ use crate::{
     transformers::{ForeignFrom, ForeignTryFrom},
 };
 
+macro_rules! impl_get_tenant_for_request {
+    ($ty:ident) => {
+        impl GetKeymanagerTenant for $ty {
+            fn get_tenant_id(&self, state: &KeyManagerState) -> id_type::TenantId {
+                match self.identifier {
+                    Identifier::User(_) | Identifier::UserAuth(_) => state.global_tenant_id.clone(),
+                    Identifier::Merchant(_) => state.tenant_id.clone(),
+                }
+            }
+        }
+    };
+}
+
 #[derive(Debug, Clone)]
 pub struct KeyManagerState {
+    pub tenant_id: id_type::TenantId,
+    pub global_tenant_id: id_type::TenantId,
     pub enabled: bool,
     pub url: String,
     pub client_idle_timeout: Option<u64>,
@@ -35,6 +50,11 @@ pub struct KeyManagerState {
     #[cfg(feature = "keymanager_mtls")]
     pub cert: Secret<String>,
 }
+
+pub trait GetKeymanagerTenant {
+    fn get_tenant_id(&self, state: &KeyManagerState) -> id_type::TenantId;
+}
+
 #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
 #[serde(tag = "data_identifier", content = "key_identifier")]
 pub enum Identifier {
@@ -70,6 +90,10 @@ pub struct BatchEncryptDataRequest {
     pub data: DecryptedDataGroup,
 }
 
+impl_get_tenant_for_request!(EncryptionCreateRequest);
+impl_get_tenant_for_request!(EncryptionTransferRequest);
+impl_get_tenant_for_request!(BatchEncryptDataRequest);
+
 impl<S> From<(Secret<Vec<u8>, S>, Identifier)> for EncryptDataRequest
 where
     S: Strategy<Vec<u8>>,
@@ -219,6 +243,12 @@ pub struct DecryptDataRequest {
     pub data: StrongSecret<String>,
 }
 
+impl_get_tenant_for_request!(EncryptDataRequest);
+impl_get_tenant_for_request!(TransientBatchDecryptDataRequest);
+impl_get_tenant_for_request!(TransientDecryptDataRequest);
+impl_get_tenant_for_request!(BatchDecryptDataRequest);
+impl_get_tenant_for_request!(DecryptDataRequest);
+
 impl<T, S> ForeignFrom<(FxHashMap<String, Secret<T, S>>, BatchEncryptDataResponse)>
     for FxHashMap<String, Encryptable<Secret<T, S>>>
 where
diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs
index 74f6502159d..3c38fa2a775 100644
--- a/crates/router/src/configs/defaults.rs
+++ b/crates/router/src/configs/defaults.rs
@@ -1,6 +1,7 @@
 use std::collections::{HashMap, HashSet};
 
 use api_models::{enums, payment_methods::RequiredFieldInfo};
+use common_utils::id_type;
 
 #[cfg(feature = "payouts")]
 pub mod payout_required_fields;
@@ -138,6 +139,17 @@ impl Default for super::settings::KvConfig {
     }
 }
 
+impl Default for super::settings::GlobalTenant {
+    fn default() -> Self {
+        Self {
+            tenant_id: id_type::TenantId::get_default_global_tenant_id(),
+            schema: String::from("global"),
+            redis_key_prefix: String::from("global"),
+            clickhouse_database: String::from("global"),
+        }
+    }
+}
+
 #[allow(clippy::derivable_impls)]
 impl Default for super::settings::ApiKeys {
     fn default() -> Self {
diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs
index ad5d9e89aaa..737f8dfb980 100644
--- a/crates/router/src/configs/settings.rs
+++ b/crates/router/src/configs/settings.rs
@@ -137,7 +137,7 @@ pub struct Platform {
     pub enabled: bool,
 }
 
-#[derive(Debug, Deserialize, Clone, Default)]
+#[derive(Debug, Clone, Default, Deserialize)]
 pub struct Multitenancy {
     pub tenants: TenantConfig,
     pub enabled: bool,
@@ -195,8 +195,10 @@ impl storage_impl::config::TenantConfig for Tenant {
     }
 }
 
-#[derive(Debug, Deserialize, Clone, Default)]
+#[derive(Debug, Deserialize, Clone)]
 pub struct GlobalTenant {
+    #[serde(default = "id_type::TenantId::get_default_global_tenant_id")]
+    pub tenant_id: id_type::TenantId,
     pub schema: String,
     pub redis_key_prefix: String,
     pub clickhouse_database: String,
diff --git a/crates/router/src/types/domain/types.rs b/crates/router/src/types/domain/types.rs
index d4cd9ef62d7..bb123659060 100644
--- a/crates/router/src/types/domain/types.rs
+++ b/crates/router/src/types/domain/types.rs
@@ -7,6 +7,8 @@ impl From<&crate::SessionState> for KeyManagerState {
     fn from(state: &crate::SessionState) -> Self {
         let conf = state.conf.key_manager.get_inner();
         Self {
+            global_tenant_id: state.conf.multitenancy.global_tenant.tenant_id.clone(),
+            tenant_id: state.tenant.tenant_id.clone(),
             enabled: conf.enabled,
             url: conf.url.clone(),
             client_idle_timeout: state.conf.proxy.idle_pool_connection_timeout,
diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml
index ec58ab08b87..b26b5b2e438 100644
--- a/loadtest/config/development.toml
+++ b/loadtest/config/development.toml
@@ -400,7 +400,7 @@ keys = "accept-language,user-agent,x-profile-id"
 
 [multitenancy]
 enabled = false
-global_tenant = { schema = "public", redis_key_prefix = "" }
+global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "" }
 
 [multitenancy.tenants.public]
 base_url = "http://localhost:8080" 

## Type of Change
<!-- Put an `x` in the boxes that apply -->
- [x] Enhancement

## Description
<!-- Describe your changes in detail -->
Adds tenant-id for every requests to keymanager

## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->
Added tenant id header for the keymanager service to classify key ids based on tenants.

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
- Create Merchant Account with x-tenant-id as `public`
```bash
curl --location 'http://localhost:8080/accounts' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'x-feature: integ-custom' \
--header 'x-tenant-id: public' \
--header 'api-key: test_admin' \
--data-raw '{
  "merchant_id": "1735815159",
  "locker_id": "m0010",
  "merchant_name": "NewAge Retailer",
  "merchant_details": {
    "primary_contact_person": "John Test",
    "primary_email": "JohnTest@test.com",
    "primary_phone": "sunt laborum",
    "secondary_contact_person": "John Test2",
    "secondary_email": "JohnTest2@test.com",
    "secondary_phone": "cillum do dolor id",
    "website": "www.example.com",
    "about_business": "Online Retail with a wide selection of organic products for North America",
    "address": {
      "line1": "1467",
      "line2": "Harrison Street",
      "line3": "Harrison Street",
      "city": "San Fransico",
      "state": "California",
      "zip": "94122",
      "country": "US"
    }
  },
  "return_url": "https://google.com/success",
  "webhook_details": {
    "webhook_version": "1.0.1",
    "webhook_username": "ekart_retail",
    "webhook_password": "password_ekart@123",
    "payment_created_enabled": true,
    "payment_succeeded_enabled": true,
    "payment_failed_enabled": true
  },
  "sub_merchants_enabled": false,
  "metadata": {
    "city": "NY",
    "unit": "245"
  },
  "primary_business_details": [
    {
      "country": "US",
      "business": "default"
    }
  ]
}'
```
- Get the `x-request-id` for the request and query it in grafana and see the if the tenant_id is `public`
![Screenshot 2025-01-02 at 4 24 01 PM](https://github.com/user-attachments/assets/eb9f2933-7390-4fa0-a9c7-33e6b522bc1e)
## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code