Skip to content

feat: convert external key manager feature flag to runtime config with enum-based validation pattern#155

Merged
ShankarSinghC merged 20 commits intomainfrom
feat/key_manager_config_changes
Feb 23, 2026
Merged

feat: convert external key manager feature flag to runtime config with enum-based validation pattern#155
ShankarSinghC merged 20 commits intomainfrom
feat/key_manager_config_changes

Conversation

@itsharshvb
Copy link
Contributor

@itsharshvb itsharshvb commented Jan 22, 2026

This pull request refactors the configuration and runtime handling of the external key manager, removing the need for compile-time feature flags and instead using runtime configuration to enable, disable, or require mTLS for the external key manager. It introduces a new KeyManagerMode enum to encapsulate the key manager's operational mode, updates configuration validation accordingly, and ensures all relevant code paths use the new runtime checks. This greatly improves flexibility and maintainability.

Key changes include:

External Key Manager Configuration & Runtime Mode Handling

  • Added enabled and mtls_enabled fields to ExternalKeyManagerConfig, allowing the external key manager and mTLS to be toggled at runtime via configuration instead of compile-time feature flags. Also added runtime validation for required fields.
  • Introduced KeyManagerMode enum in keymanager.rs to represent internal, external (plain), and external (mTLS) modes, with helper methods for checking mode and logic to derive the mode from config.
  • Updated get_dek_manager to select the implementation at runtime based on KeyManagerMode, instead of compile-time features.

Configuration & Validation

  • Updated config loading and validation to use the new fields and validate required parameters (e.g., url, cert, api_client.identity) when the external key manager or mTLS is enabled. [1] [2]
  • Updated Cargo.toml features to remove the external_key_manager_mtls feature and ensure external_key_manager always enables the correct dependencies.
  • Updated example and development config files to include the new runtime flags and document their usage. [1] [2]

Application & API Client Integration

  • Refactored ApiClient and TenantAppState to receive and use KeyManagerMode at runtime, including mTLS identity/cert only if required. [1] [2] [3] [4]
  • Ensured /key/transfer route and other external key manager logic are only registered or used when the external key manager is enabled, based on runtime config.

Code Cleanup & Generalization

  • Removed all compile-time feature gating for the external key manager and related code, simplifying the codebase and making it more maintainable. [1] [2] [3] [4] [5] [6]
  • Updated usages of get_dek_manager throughout the codebase to pass the runtime mode. [1] [2] [3]

Testing

All changes verified with:

cargo check --all-features

Diagnostics request :

curl --location --request GET 'http://localhost:3001/health/diagnostics' \
--header 'x-tenant-id: public' \
--header 'Content-Type: application/json' \
--data '{
    "key": "79587ad96ce722e5d9d62940f1c76232"
}'

Diagnostics response:

{"key_custodian_locked":false,"database":{"database_connection":"Working","database_read":"Working","database_write":"Working","database_delete":"Working"},"keymanager_status":"Working"}%

Saved card call from tenant :

curl -X POST "http://localhost:8080/payment_methods" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "api-key: dev_Ue7zBHhFzaX6grLpAvgZkWg47aQccrhQSmf3wyougsjZfJBIwfszSpsxQzJglKNs" \
  -d '{
    "payment_method": "card",
    "payment_method_type": "credit",
    "payment_method_issuer": "Visa",
    "card": {
      "card_number": "4242424242424242",
      "card_exp_month": "11",
      "card_exp_year": "27",
      "card_holder_name": "John Doe"
    },
    "customer_id": "cus_L02foZeF6BiTJRFL6jrp",
    "metadata": {
      "city": "NY",
      "unit": "245"
    }
  }'
{"merchant_id":"merchant_1769084753","customer_id":"cus_L02foZeF6BiTJRFL6jrp","payment_method_id":"pm_gI7I8PAXHouNbK0COfvI","payment_method":"card","payment_method_type":"credit","card":{"scheme":null,"issuer_country":null,"issuer_country_code":null,"last4_digits":"4242","expiry_month":"11","expiry_year":"27","card_token":null,"card_holder_name":"John Doe","card_fingerprint":null,"nick_name":null,"card_network":null,"card_isin":"424242","card_issuer":null,"card_type":null,"saved_to_locker":true},"recurring_enabled":false,"installment_payment_enabled":false,"payment_experience":["redirect_to_url"],"metadata":{"city":"NY","unit":"245"},"created":"2026-01-22T12:32:22.565Z","last_used_at":"2026-01-22T12:32:22.565Z","client_secret":"pm_gI7I8PAXHouNbK0COfvI_secret_GZ2xHWIDiSNYSh7hHBe8"}%

Tartarus logs :
Screenshot 2026-01-22 at 6 44 42 PM

Retrieve call from tenant:

 curl -X GET "http://localhost:8080/payment_methods/pm_gI7I8PAXHouNbK0COfvI" \
  -H "Accept: application/json" \
  -H "api-key: dev_Ue7zBHhFzaX6grLpAvgZkWg47aQccrhQSmf3wyougsjZfJBIwfszSpsxQzJglKNs"
{"merchant_id":"merchant_1769084753","customer_id":"cus_L02foZeF6BiTJRFL6jrp","payment_method_id":"pm_gI7I8PAXHouNbK0COfvI","payment_method":"card","payment_method_type":"credit","card":{"scheme":null,"issuer_country":null,"issuer_country_code":null,"last4_digits":"4242","expiry_month":"11","expiry_year":"27","card_token":null,"card_holder_name":"John Doe","card_fingerprint":null,"nick_name":null,"card_network":null,"card_isin":null,"card_issuer":null,"card_type":null,"saved_to_locker":true},"recurring_enabled":false,"installment_payment_enabled":false,"payment_experience":["redirect_to_url"],"metadata":{"city":"NY","unit":"245"},"created":"2026-01-22T12:32:22.569Z","last_used_at":"2026-01-22T12:32:22.569Z","client_secret":"pm_gI7I8PAXHouNbK0COfvI_secret_GZ2xHWIDiSNYSh7hHBe8"}%

Tartarus logs :
Screenshot 2026-01-22 at 6 47 34 PM

Tests pass successfully with no compilation errors or warnings.


Fixed required check

All clippy warnings have been fixed:

  1. Large enum variant warning in SecretsManagerClient - Boxed the HashiCorpVault variant
  2. Expect in Result-returning function warnings in bin/locker.rs - Added #[allow(clippy::expect_used, clippy:: unwrap_in_result)] attribute
  3. Benchmark compilation errors in benches/encryption.rs:
   - Fixed undefined JWE_PRIVATE_KEY and JWE_PUBLIC_KEY constants - replaced with dynamically generated keys
   - Changed criterion_jwe_jws function signature to return Result<(), Box<dyn std::error::Error>>
   - Added Ok(()) at the end of criterion_jwe_jws function
   - Restored the missing AES decryption benchmark group
   - Added #[allow(clippy::expect_used, clippy::unwrap_in_result, unused_must_use)] at the top level
   - Added #[allow(clippy::expect_used)] attribute to criterion_jwe_jws function
   - Added #[allow(clippy::expect_used)] attributes to PEM conversion statements

Additional test

Scenario 1: Legacy Cards API - Add Card

Step 1: Create the plaintext request

Screenshot 2026-02-20 at 3 48 17 PM

Step 2: Encrypt the request

Screenshot 2026-02-20 at 3 54 14 PM

Step 3: Send the encrypted request

Screenshot 2026-02-20 at 3 55 24 PM

Step 4: Decrypt the response

Screenshot 2026-02-20 at 3 56 44 PM

Scenario 2: Legacy Cards API - Retrieve Card

Step 1: Create the plaintext request

Screenshot 2026-02-20 at 3 59 06 PM

Step 2: Encrypt the request

Screenshot 2026-02-20 at 4 00 25 PM

Step 3: Send the encrypted request

Screenshot 2026-02-20 at 4 00 57 PM

Step 4: Decrypt the response

Screenshot 2026-02-20 at 4 02 18 PM

Scenario 3: Legacy Cards API - Delete Card

Screenshot 2026-02-20 at 4 04 43 PM

Scenario 4: V2 General Purpose Vault - Add Data

Screenshot 2026-02-20 at 4 07 59 PM

Scenario 5: V2 General Purpose Vault - Retrieve Data

Screenshot 2026-02-20 at 4 09 03 PM

Scenario 6: V2 General Purpose Vault - Delete Data

Screenshot 2026-02-20 at 4 10 01 PM

@itsharshvb itsharshvb self-assigned this Jan 22, 2026
@itsharshvb itsharshvb linked an issue Jan 22, 2026 that may be closed by this pull request
@itsharshvb itsharshvb force-pushed the feat/key_manager_config_changes branch from a64af21 to 9e6a20a Compare February 2, 2026 04:32
Copy link
Collaborator

@ShankarSinghC ShankarSinghC left a comment

Choose a reason for hiding this comment

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

I see that in most places we are using absolute imports. Could you please review it once and change them to relative imports?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need a separate file for this ? If not, can we please move this logic to src/config.rs ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

src/config.rs has global application configuration - server,database,logging, src/crypto/keymanager/config.rs would be better choice for handling domain-specific configuration specific to key manager.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I get the point about keeping domain-specific config closer to the key manager
That said, I’d still prefer keeping this in a single file for now. The logic is fairly small and having it split across multiple config files feels like extra indirection without much benefit at this stage.

Happy to revisit and split it out later if this grows or becomes more complex.

   - Fixed undefined JWE_PRIVATE_KEY and JWE_PUBLIC_KEY constants - replaced with dynamically generated keys
   - Changed criterion_jwe_jws function signature to return Result<(), Box<dyn std::error::Error>>
   - Added Ok(()) at the end of criterion_jwe_jws function
   - Restored the missing AES decryption benchmark group
   - Added #[allow(clippy::expect_used, clippy::unwrap_in_result, unused_must_use)] at the top level
   - Added #[allow(clippy::expect_used)] attribute to criterion_jwe_jws function
   - Added #[allow(clippy::expect_used)] attributes to PEM conversion statements
cargo clippy --all-features --all-targets now passes with only a future incompatibility warning from a dependency (num-bigint-dig v0.8.4) which is not our concern.
Copy link
Member

@SanchithHegde SanchithHegde left a comment

Choose a reason for hiding this comment

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

Other than that, looks good to me!

Comment on lines +116 to +123
#[cfg_attr(
all(
not(feature = "middleware"),
not(feature = "external_key_manager"),
not(feature = "key_custodian")
),
allow(unused_mut)
)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this required only for v2 routes ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Screenshot 2026-02-20 at 12 36 14 PM

these feature flag gates are global not consolidated to v2 .
Placing the mutable pattern after v2 routes was a design choice during the refactor to consolidate feature gate blocks after all route groups were defined.

the earlier code without mut would throw shadowing problem with cargo clippy --all-features

Copy link
Collaborator

@ShankarSinghC ShankarSinghC left a comment

Choose a reason for hiding this comment

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

@ShankarSinghC ShankarSinghC merged commit 0b07ee4 into main Feb 23, 2026
8 checks passed
@ShankarSinghC ShankarSinghC deleted the feat/key_manager_config_changes branch February 23, 2026 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Issue: Convert External Key Manager to Runtime Configuration

3 participants