Skip to content

Conversation

@raghav-bhutani
Copy link

@raghav-bhutani raghav-bhutani commented Nov 28, 2025

Description

Added support for AWS KMS grant tokens and source context to enable more flexible access control for KMS operations. This change extends the MasterKeyProvider interface and KMS implementation to pass grant tokens during encryption/decryption operations and adds source context headers to KMS requests.

Related Issues

Testing

[2025-11-28T09:05:12,066][INFO ][o.o.c.k.KmsService       ] [ip-172-31-51-181.us-west-2.compute.internal] adding encCtx {key1=value1, key2=value2}
[2025-11-28T08:50:28,390][INFO ][o.o.c.k.KmsService       ] [ip-172-31-51-181.us-west-2.compute.internal] adding grants [grant1,  grant2,  grant3]
[2025-11-28T08:50:28,391][INFO ][o.o.c.k.KmsService       ] [ip-172-31-51-181.us-west-2.compute.internal] adding details headers with key sourceArn, value arn:aws:iam::accountId:role/service-role/opensearch-domain-role
[2025-11-28T08:50:28,391][INFO ][o.o.c.k.KmsService       ] [ip-172-31-51-181.us-west-2.compute.internal] adding details headers with key sourceContext sourceAccount, value accountId

Check List

  • [Y] Functionality includes testing.
  • API changes companion pull request created, if applicable.
  • Public documentation issue/PR created, if applicable.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added support for AWS KMS grant tokens to enhance authorization and improve secure key operation handling
    • Added source context configuration option for improved request tracking and context management in KMS operations

✏️ Tip: You can customize this high-level summary in your review settings.

@raghav-bhutani raghav-bhutani requested a review from a team as a code owner November 28, 2025 09:14
@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

Walkthrough

The changes introduce grant tokens and source context support to the KMS encryption infrastructure. A new interface method getGrantTokens() is added to MasterKeyProvider. KmsClientSettings now manages source context via a new setting and field. KmsMasterKeyProvider is extended with grant tokens support throughout its lifecycle, including request builders. KmsService is enhanced to parse grant tokens, configure HTTP client interceptors for source context headers, and centralize key-value parsing logic.

Changes

Cohort / File(s) Summary
Interface Extension
libs/common/src/main/java/org/opensearch/common/crypto/MasterKeyProvider.java
Added getGrantTokens() method returning List<String> to expose grant tokens via public API; added java.util.List import.
KMS Configuration & Settings
plugins/crypto-kms/src/main/java/org/opensearch/crypto/kms/KmsClientSettings.java
Added SOURCE_CONTEXT_SETTING configuration setting; introduced sourceContext instance field; created overloaded constructor accepting sourceContext parameter; updated existing constructor to initialize sourceContext to empty string; modified getClientSettings() and getMetadataSettings() to pass source context to constructor; extended equals() and hashCode() to include sourceContext.
KMS Master Key Provider
plugins/crypto-kms/src/main/java/org/opensearch/crypto/kms/KmsMasterKeyProvider.java
Added grantTokens private final field; created overloaded constructor accepting List<String> grantTokens; existing constructor now initializes grantTokens to empty list; updated GenerateDataKeyRequest.Builder and DecryptRequest.Builder to conditionally attach grantTokens if non-empty; added public getGrantTokens() getter method.
KMS Service
plugins/crypto-kms/src/main/java/org/opensearch/crypto/kms/KmsService.java
Added GRANT_TOKENS_SETTING configuration setting; introduced buildOverrideConfiguration(KmsClientSettings) method that injects source_context headers via ExecutionInterceptor; refactored buildClient() to invoke new buildOverrideConfiguration variant; added private static parseKeyValuePairs(String, String) utility for parsing comma-separated Name=Value pairs; updated createMasterKeyProvider() to pass grantTokens and use centralized parsing logic; added supporting imports (Collections, List, ExecutionInterceptor, SdkHttpRequest).

Sequence Diagram

sequenceDiagram
    participant Config as Config/Settings
    participant Service as KmsService
    participant Settings as KmsClientSettings
    participant Provider as KmsMasterKeyProvider
    participant KMSClient as KMS Client
    participant HTTP as HTTP Layer

    Config->>Service: Parse GRANT_TOKENS_SETTING & SOURCE_CONTEXT_SETTING
    Service->>Settings: createClientSettings(sourceContext)
    Settings->>HTTP: buildClient() with sourceContext
    HTTP->>HTTP: buildOverrideConfiguration(sourceContext)
    Note over HTTP: Install ExecutionInterceptor<br/>to inject source_context header
    
    Service->>Provider: createMasterKeyProvider(grantTokens, clientRef)
    Provider->>Provider: Store grantTokens
    
    Provider->>KMSClient: GenerateDataKeyRequest (with grantTokens if present)
    KMSClient->>HTTP: Execute with source_context header
    HTTP->>KMSClient: Response
    
    Provider->>KMSClient: DecryptRequest (with grantTokens if present)
    KMSClient->>HTTP: Execute with source_context header
    HTTP->>KMSClient: Response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • HTTP interceptor logic in buildOverrideConfiguration() for injecting source_context headers requires verification of ExecutionInterceptor integration and SdkHttpRequest manipulation.
  • Grant tokens conditional attachment in request builders (GenerateDataKeyRequest and DecryptRequest) should be validated for correctness when tokens are empty vs. non-empty.
  • Constructor overload consistency across KmsMasterKeyProvider and KmsClientSettings needs verification to ensure backward compatibility and proper initialization chains.
  • parseKeyValuePairs() utility and its error handling should be reviewed for robustness when parsing comma-separated Name=Value pairs.

Poem

🐰 Grant tokens hopping through the encrypted air,
Source context headers whispered everywhere,
Request builders dance with conditional flair,
KMS calls now safer, with tokens to spare—
Encryption's grand adventure, beyond compare! ✨🔐

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 2 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.64% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title mentions 'consumer grants and source details' which partially aligns with the changeset's grant tokens and source context support, but is vague and uses informal phrasing ('consumer grants') that doesn't clearly convey the main technical changes. Consider a more specific title like 'Add AWS KMS grant tokens and source context support to crypto-kms plugin' to clearly convey the main technical improvements.
Description check ❓ Inconclusive Pull request description is incomplete. Missing required sections: Related Issues (empty), and incomplete Testing section with only log output but no actual test information. Add Related Issues section with issue number(s) this resolves, and provide details on what tests were added or run to validate the changes (unit tests, integration tests, etc.).
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (9)
libs/common/src/main/java/org/opensearch/common/crypto/MasterKeyProvider.java (1)

49-53: KMS-specific terminology in a generic interface.

The Javadoc mentions "KMS operations" but MasterKeyProvider is a generic interface in libs/common. Consider using more generic terminology like "cryptographic operations" to maintain abstraction and support potential non-KMS implementations.

     /**
-    * Returns grant tokens for KMS operations.
+    * Returns grant tokens for cryptographic operations.
     * @return list of grant tokens or empty list if not configured
     */
     List<String> getGrantTokens();
plugins/crypto-kms/src/main/java/org/opensearch/crypto/kms/KmsMasterKeyProvider.java (4)

71-73: Redundant null check for grantTokens.

Since grantTokens is always initialized (either to Collections.emptyList() in the first constructor or via null-safe assignment in the second), the grantTokens != null check is redundant. Only the emptiness check is necessary.

-            if (grantTokens != null && !grantTokens.isEmpty()) {
+            if (!grantTokens.isEmpty()) {
                 requestBuilder.grantTokens(grantTokens);
             }

89-91: Same redundant null check pattern.

Same observation as above—grantTokens can never be null due to constructor initialization.

-            if (grantTokens != null && !grantTokens.isEmpty()) {
+            if (!grantTokens.isEmpty()) {
                 requestBuilder.grantTokens(grantTokens);
             }

111-114: Consider returning an unmodifiable view of grantTokens.

Returning the internal list directly allows callers to mutate it. Since grantTokens may be the caller-supplied list (not defensively copied in the constructor), returning it directly could lead to unexpected behavior if modified externally.

     @Override
     public List<String> getGrantTokens() {
-        return grantTokens;
+        return Collections.unmodifiableList(grantTokens);
     }

Alternatively, wrap the list in an unmodifiable view in the constructor at line 56.


54-56: Defensive copy recommended for grantTokens parameter.

The constructor stores the caller-provided list directly. If the caller later modifies that list, the internal state of this provider changes unexpectedly.

-        this.grantTokens = grantTokens != null ? grantTokens : Collections.emptyList();
+        this.grantTokens = grantTokens != null ? List.copyOf(grantTokens) : Collections.emptyList();

List.copyOf() (Java 10+) creates an immutable copy. Alternatively, use Collections.unmodifiableList(new ArrayList<>(grantTokens)) for older Java versions.

plugins/crypto-kms/src/main/java/org/opensearch/crypto/kms/KmsClientSettings.java (2)

67-68: Missing Property.NodeScope on SOURCE_CONTEXT_SETTING.

Other similar settings like ENC_CTX_SETTING and KEY_ARN_SETTING in KmsService include Setting.Property.NodeScope. For consistency and proper settings behavior, consider adding it here.

     /** Source context for grant-based access. */
-    static final Setting<String> SOURCE_CONTEXT_SETTING = Setting.simpleString("kms.source_context");
+    static final Setting<String> SOURCE_CONTEXT_SETTING = Setting.simpleString("kms.source_context", Property.NodeScope);

107-148: Consider using constructor chaining to reduce duplication.

The two constructors share significant code. You could have the shorter constructor delegate to the longer one.

     protected KmsClientSettings(
         AwsCredentials credentials,
         String endpoint,
         String region,
         String proxyHost,
         int proxyPort,
         String proxyUsername,
         String proxyPassword,
         int readTimeoutMillis
     ) {
-        this.credentials = credentials;
-        this.endpoint = endpoint;
-        this.region = region;
-        this.proxyHost = proxyHost;
-        this.proxyPort = proxyPort;
-        this.proxyUsername = proxyUsername;
-        this.proxyPassword = proxyPassword;
-        this.readTimeoutMillis = readTimeoutMillis;
-        this.sourceContext = "";
+        this(credentials, endpoint, region, proxyHost, proxyPort, proxyUsername, proxyPassword, readTimeoutMillis, "");
     }
plugins/crypto-kms/src/main/java/org/opensearch/crypto/kms/KmsService.java (2)

153-153: Lambda can be simplified to method reference.

Minor style improvement.

-                    sourceHeaders.forEach((key, value) -> { requestBuilder.putHeader(key, value); });
+                    sourceHeaders.forEach(requestBuilder::putHeader);

144-160: Consider logging when source context headers are added.

For debugging and audit purposes, it would be helpful to log when custom headers are being injected into KMS requests, similar to how other configuration is logged (e.g., lines 116-117, 121-122).

         if (Strings.hasText(clientSettings.sourceContext)) {
             Map<String, String> sourceHeaders = parseKeyValuePairs(clientSettings.sourceContext, "source_context");
+            logger.debug("adding source context headers to KMS requests: {}", sourceHeaders.keySet());
             builder.addExecutionInterceptor(new ExecutionInterceptor() {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ee30dc and cb89d94.

📒 Files selected for processing (4)
  • libs/common/src/main/java/org/opensearch/common/crypto/MasterKeyProvider.java (2 hunks)
  • plugins/crypto-kms/src/main/java/org/opensearch/crypto/kms/KmsClientSettings.java (7 hunks)
  • plugins/crypto-kms/src/main/java/org/opensearch/crypto/kms/KmsMasterKeyProvider.java (3 hunks)
  • plugins/crypto-kms/src/main/java/org/opensearch/crypto/kms/KmsService.java (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
plugins/crypto-kms/src/main/java/org/opensearch/crypto/kms/KmsMasterKeyProvider.java (1)
libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/AccessController.java (1)
  • AccessController (44-144)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: gradle-check
🔇 Additional comments (1)
plugins/crypto-kms/src/main/java/org/opensearch/crypto/kms/KmsService.java (1)

149-156: Verify cache key composition in KmsService.

Unable to access the repository to confirm whether ClientOverrideConfiguration is part of the cache key identity. The concern that anonymous ExecutionInterceptor affects client caching is theoretically valid only if ClientOverrideConfiguration (or an object containing it) is used as a cache key. AWS SDK v2's ClientOverrideConfiguration does not override equals()/hashCode(), so it relies on object identity.

However, if clientsCache keys are based on a separate KmsClientSettings object that does not include the configuration instance itself, the anonymous class would not affect cache behavior. Clarification needed: Verify whether buildOverrideConfiguration() is called fresh on each invocation and confirm if ClientOverrideConfiguration participates in the cache key logic.

Comment on lines +286 to +288
// Extract grant details
String grantTokensStr = GRANT_TOKENS_SETTING.get(cryptoSettings);
List<String> grantTokens = Strings.hasText(grantTokensStr) ? Arrays.asList(grantTokensStr.split(",")) : Collections.emptyList();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Grant tokens are not trimmed after splitting.

Unlike parseKeyValuePairs which trims whitespace, splitting grant tokens by comma retains any surrounding whitespace (e.g., "grant1, grant2" yields [" grant2"]). The testing logs show spaces: [grant1, grant2, grant3].

-        List<String> grantTokens = Strings.hasText(grantTokensStr) ? Arrays.asList(grantTokensStr.split(",")) : Collections.emptyList();
+        List<String> grantTokens = Strings.hasText(grantTokensStr)
+            ? Arrays.stream(grantTokensStr.split(",")).map(String::trim).collect(Collectors.toList())
+            : Collections.emptyList();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Extract grant details
String grantTokensStr = GRANT_TOKENS_SETTING.get(cryptoSettings);
List<String> grantTokens = Strings.hasText(grantTokensStr) ? Arrays.asList(grantTokensStr.split(",")) : Collections.emptyList();
// Extract grant details
String grantTokensStr = GRANT_TOKENS_SETTING.get(cryptoSettings);
List<String> grantTokens = Strings.hasText(grantTokensStr)
? Arrays.stream(grantTokensStr.split(",")).map(String::trim).collect(Collectors.toList())
: Collections.emptyList();
🤖 Prompt for AI Agents
In plugins/crypto-kms/src/main/java/org/opensearch/crypto/kms/KmsService.java
around lines 286 to 288, the grant token string is split on commas but
individual tokens are not trimmed so tokens can retain surrounding whitespace;
after splitting, trim each token and filter out empty tokens (e.g., using a
stream or loop to map String::trim and discard zero-length entries) so the
resulting List<String> contains clean tokens like "grant2" rather than "
grant2".

Comment on lines +298 to 304
private static Map<String, String> parseKeyValuePairs(String input, String settingName) {
try {
return Arrays.stream(input.split(",")).map(s -> s.split("=")).collect(Collectors.toMap(e -> e[0].trim(), e -> e[1].trim()));
} catch (Exception ex) {
throw new IllegalArgumentException(settingName + " Format should be: Name1=Value1, Name2=Value2");
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

parseKeyValuePairs will throw ArrayIndexOutOfBoundsException for malformed input.

If the input contains an entry without an = (e.g., "key1=value1,invalidkey"), accessing e[1] will throw ArrayIndexOutOfBoundsException. The generic Exception catch then wraps it, losing the specific error context. Consider explicit validation.

     private static Map<String, String> parseKeyValuePairs(String input, String settingName) {
-        try {
-            return Arrays.stream(input.split(",")).map(s -> s.split("=")).collect(Collectors.toMap(e -> e[0].trim(), e -> e[1].trim()));
-        } catch (Exception ex) {
-            throw new IllegalArgumentException(settingName + " Format should be: Name1=Value1, Name2=Value2");
-        }
+        Map<String, String> result = new HashMap<>();
+        for (String pair : input.split(",")) {
+            String[] keyValue = pair.split("=", 2);
+            if (keyValue.length != 2) {
+                throw new IllegalArgumentException(
+                    settingName + " contains invalid entry '" + pair.trim() + "'. Format should be: Name1=Value1,Name2=Value2"
+                );
+            }
+            result.put(keyValue[0].trim(), keyValue[1].trim());
+        }
+        return result;
     }

Using split("=", 2) also handles values containing = characters.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static Map<String, String> parseKeyValuePairs(String input, String settingName) {
try {
return Arrays.stream(input.split(",")).map(s -> s.split("=")).collect(Collectors.toMap(e -> e[0].trim(), e -> e[1].trim()));
} catch (Exception ex) {
throw new IllegalArgumentException(settingName + " Format should be: Name1=Value1, Name2=Value2");
}
}
private static Map<String, String> parseKeyValuePairs(String input, String settingName) {
Map<String, String> result = new HashMap<>();
for (String pair : input.split(",")) {
String[] keyValue = pair.split("=", 2);
if (keyValue.length != 2) {
throw new IllegalArgumentException(
settingName + " contains invalid entry '" + pair.trim() + "'. Format should be: Name1=Value1,Name2=Value2"
);
}
result.put(keyValue[0].trim(), keyValue[1].trim());
}
return result;
}
🤖 Prompt for AI Agents
In plugins/crypto-kms/src/main/java/org/opensearch/crypto/kms/KmsService.java
around lines 298 to 304, the current parsing can throw
ArrayIndexOutOfBoundsException for malformed entries and the generic catch hides
context; change the method to split each entry with split("=", 2), trim both
sides, validate that the result has exactly 2 non-empty parts, and if any entry
is invalid throw an IllegalArgumentException including settingName and the
offending entry; remove the broad Exception catch (or catch only expected
parsing exceptions) so real errors are not masked and support values containing
"=" by using the limit=2 split.

@github-actions
Copy link
Contributor

❌ Gradle check result for cb89d94: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

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.

1 participant