Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
34 changes: 33 additions & 1 deletion packages/aws-cdk-lib/aws-opensearchservice/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
40 changes: 38 additions & 2 deletions packages/aws-cdk-lib/aws-opensearchservice/lib/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -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;
}
}

/**
Expand Down
71 changes: 71 additions & 0 deletions packages/aws-cdk-lib/aws-opensearchservice/test/domain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
Loading