Skip to content

KMS: Add a way to get an IKey reference from an Alias ARN #37272

@BalmungSan

Description

@BalmungSan

Describe the feature

Currently, there is no way to get a proper IKey reference from an Alias ARN, that would produce a proper IAM PolicyStatement.

This would be especially useful for cross-account situations where the current available options like Key.fromLookup and Alias.fromAliasName are not sufficient.

Use Case

My current use case is creating a Lambda ESM from an encrypted cross-account Kinesis Stream.

From the external account, granting the permissions to the Stream and Key was pretty easy thanks to the existing CDK constructs.
On the reader account, however, we originally thought that we could centralize all the cross-account stuff in our custom Kinesis L3 Stream construct, and that the Lambda one would just create a KinesisEventSource without caring about the IStream origin. Sadly, that was not the case since there was no way to get a reference to the external account KMS Alias that would create a correct IAM PolicyStatement.

We tried the following CDK code:

const streamArn = Stack.of(this).formatArn({
  account: externalAccountId,
  service: 'kinesis',
  resource: 'stream',
  resourceName: streamName
})

const keyArn = Stack.of(this).formatArn({
  account: externalAccountId,
  service: 'kms',
  resource: 'alias',
  resourceName: `${streamName}-key`
})

const stream = Stream.fromStreamAttributes(this, 'external-stream', {
  streamArn,
  // Sadly, this doesn't produce the correct policy, and we have to explicitly modify the lambda policy.
  encryptionKey: Key.fromKeyArn(this, 'external-stream-key', keyArn)
})

But that created this IAM PolicyStatement (which is wrong):

{
  "Action": "kms:Decrypt",
  "Resource": "arn:aws:kms:<region>:<external-account-id>:alias/<stream-name>-key",
  "Effect": "Allow"
}

Thus, we had to expose the cross-account information to the Lambda module, and explicitly grant the correct IAM PolicyStatement.

// Workaround for lack of cross-account KMS alias lookup.
lambda.addToRolePolicy(
  new PolicyStatement({
    actions: ['kms:Decrypt'],
    resources: [
      Stack.of(this).formatArn({
        account: externalAccountId,
        service: 'kms',
        resource: 'key',
        resourceName: '*'
      })
    ],
    conditions: {
      'ForAnyValue:StringEquals': {
        'kms:ResourceAliases': `alias/${streamName}-key`
      }
    }
  })
)

Of course, another option would have been to use the external Key ARN instead of the Alias one. However, since Key's ARN values are not predictable, and we deploy this same Stack in multiple regions in multiple environments, we would need to have a hardcoded map of values. And we believe that is a worse solution since it is brittle and error-prone.

Proposed Solution

A static method like Key.fromAliasArn or Alias.fromArn that would return an IKey reference pointing to that Alias, and whose grant methods would produce a proper PolicyStatement with a kms:ResourceAliases condition.

Other Information

This feels related to #28284 & #8822

And, it is likely to be a recurring problem, since the official AWS recommendation is to use multiple accounts, and when sharing data, for example, using Kinesis Streams, it is a security best practice to encrypt it using KMS Keys.

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

AWS CDK Library version (aws-cdk-lib)

2.243.0

AWS CDK CLI version

2.1111.0

Environment details (OS name and version, etc.)

Any

Metadata

Metadata

Assignees

No one assigned

    Labels

    @aws-cdk/aws-kmsRelated to AWS Key Managementeffort/mediumMedium work item – several days of effortfeature-requestA feature should be added or improved.p1

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions