diff --git a/packages/aws-cdk-lib/aws-opensearchservice/README.md b/packages/aws-cdk-lib/aws-opensearchservice/README.md index 236a3bf981852..e1f8477181f50 100644 --- a/packages/aws-cdk-lib/aws-opensearchservice/README.md +++ b/packages/aws-cdk-lib/aws-opensearchservice/README.md @@ -149,7 +149,39 @@ const domain = new Domain(this, 'Domain', { This sets up the domain with node to node encryption and encryption at rest. You can also choose to supply your own KMS key to use for encryption at -rest. +rest: + +```ts +import * as kms from 'aws-cdk-lib/aws-kms'; + +const encryptionKey = new kms.Key(this, 'EncryptionKey'); + +const domain = new Domain(this, 'Domain', { + version: EngineVersion.OPENSEARCH_1_0, + encryptionAtRest: { + kmsKey: encryptionKey, + }, +}); +``` + +The construct also supports using cross-account KMS keys for encryption at rest: + +```ts +import * as kms from 'aws-cdk-lib/aws-kms'; + +const crossAccountKey = kms.Key.fromKeyArn( + this, + 'CrossAccountKey', + 'arn:aws:kms:us-east-1:111111111111:key/12345678-1234-1234-1234-123456789012', +); + +const domain = new Domain(this, 'Domain', { + version: EngineVersion.OPENSEARCH_1_0, + encryptionAtRest: { + kmsKey: crossAccountKey, + }, +}); +``` ## VPC Support diff --git a/packages/aws-cdk-lib/aws-opensearchservice/lib/domain.ts b/packages/aws-cdk-lib/aws-opensearchservice/lib/domain.ts index a296c16785303..def3c10cfb0f4 100644 --- a/packages/aws-cdk-lib/aws-opensearchservice/lib/domain.ts +++ b/packages/aws-cdk-lib/aws-opensearchservice/lib/domain.ts @@ -1987,8 +1987,8 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { }, encryptionAtRestOptions: { enabled: encryptionAtRestEnabled, - kmsKeyId: encryptionAtRestEnabled - ? props.encryptionAtRest?.kmsKey?.keyRef.keyId + kmsKeyId: encryptionAtRestEnabled && props.encryptionAtRest?.kmsKey + ? this.selectKmsKeyIdentifier(props.encryptionAtRest.kmsKey) : undefined, }, nodeToNodeEncryptionOptions: { enabled: nodeToNodeEncryptionEnabled }, @@ -2197,6 +2197,42 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { } } } + + /** + * Selects the appropriate KMS key identifier (keyId or keyArn) based on whether + * the key is from the same account and region as the domain. + * + * For cross-account or cross-region KMS keys, OpenSearch requires the full ARN. + * For same-account, same-region keys, keyId is sufficient. + * + * @param key The KMS key to use for encryption + * @returns The key identifier to use in CloudFormation + */ + private selectKmsKeyIdentifier(key: kms.IKeyRef): string { + const stack = cdk.Stack.of(this); + + // Check if the key is from a different account or region + const keyAccount = key.env.account; + const keyRegion = key.env.region; + const stackAccount = stack.account; + const stackRegion = stack.region; + + // If either account or region is different (and not a token), use ARN + // Tokens are unresolved values that will be determined at deploy time + const isCrossAccount = !cdk.Token.isUnresolved(keyAccount) && + !cdk.Token.isUnresolved(stackAccount) && + keyAccount !== stackAccount; + const isCrossRegion = !cdk.Token.isUnresolved(keyRegion) && + !cdk.Token.isUnresolved(stackRegion) && + keyRegion !== stackRegion; + + if (isCrossAccount || isCrossRegion) { + return key.keyRef.keyArn; + } + + // For same-account, same-region keys, use keyId (maintains backward compatibility) + return key.keyRef.keyId; + } } /** diff --git a/packages/aws-cdk-lib/aws-opensearchservice/test/domain.test.ts b/packages/aws-cdk-lib/aws-opensearchservice/test/domain.test.ts index c6837c4eb3981..2b0342bb265b7 100644 --- a/packages/aws-cdk-lib/aws-opensearchservice/test/domain.test.ts +++ b/packages/aws-cdk-lib/aws-opensearchservice/test/domain.test.ts @@ -190,6 +190,77 @@ each([testedOpenSearchVersions]).test('grants kms permissions if needed', (engin expect(resources.AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E.Properties.PolicyDocument).toStrictEqual(expectedPolicy); }); +each([testedOpenSearchVersions]).test('uses key ARN for cross-account KMS keys', (engineVersion) => { + // Create a cross-account KMS key using fromKeyArn + const crossAccountKey = kms.Key.fromKeyArn( + stack, + 'CrossAccountKey', + 'arn:aws:kms:us-east-1:999999999999:key/12345678-1234-1234-1234-123456789012', + ); + + new Domain(stack, 'Domain', { + version: engineVersion, + encryptionAtRest: { + kmsKey: crossAccountKey, + }, + }); + + // Verify that the KMS key ID in the CloudFormation template is the full ARN + Template.fromStack(stack).hasResourceProperties('AWS::OpenSearchService::Domain', { + EncryptionAtRestOptions: { + Enabled: true, + KmsKeyId: 'arn:aws:kms:us-east-1:999999999999:key/12345678-1234-1234-1234-123456789012', + }, + }); +}); + +each([testedOpenSearchVersions]).test('uses key ARN for cross-region KMS keys', (engineVersion) => { + // Create a cross-region KMS key using fromKeyArn + const crossRegionKey = kms.Key.fromKeyArn( + stack, + 'CrossRegionKey', + 'arn:aws:kms:us-west-2:1234:key/12345678-1234-1234-1234-123456789012', + ); + + new Domain(stack, 'Domain', { + version: engineVersion, + encryptionAtRest: { + kmsKey: crossRegionKey, + }, + }); + + // Verify that the KMS key ID in the CloudFormation template is the full ARN + Template.fromStack(stack).hasResourceProperties('AWS::OpenSearchService::Domain', { + EncryptionAtRestOptions: { + Enabled: true, + KmsKeyId: 'arn:aws:kms:us-west-2:1234:key/12345678-1234-1234-1234-123456789012', + }, + }); +}); + +each([testedOpenSearchVersions]).test('uses key ID for same-account same-region KMS keys', (engineVersion) => { + // Create a same-account, same-region KMS key + const key = new kms.Key(stack, 'Key'); + + new Domain(stack, 'Domain', { + version: engineVersion, + encryptionAtRest: { + kmsKey: key, + }, + }); + + // Verify that the KMS key ID in the CloudFormation template uses keyId (not ARN) + // This maintains backward compatibility - keyId returns a Ref to the key + Template.fromStack(stack).hasResourceProperties('AWS::OpenSearchService::Domain', { + EncryptionAtRestOptions: { + Enabled: true, + KmsKeyId: { + Ref: 'Key961B73FD', + }, + }, + }); +}); + each([ [EngineVersion.OPENSEARCH_1_0, 'OpenSearch_1.0'], [EngineVersion.OPENSEARCH_1_1, 'OpenSearch_1.1'],