diff --git a/packages/@aws-cdk/aws-msk-alpha/README.md b/packages/@aws-cdk/aws-msk-alpha/README.md index 8155f8829a756..ef1b18a6cc9f8 100644 --- a/packages/@aws-cdk/aws-msk-alpha/README.md +++ b/packages/@aws-cdk/aws-msk-alpha/README.md @@ -124,7 +124,7 @@ const cluster = new msk.Cluster(this, 'cluster', { }); ``` -### SASL/IAM +### IAM Enable client authentication with [IAM](https://docs.aws.amazon.com/msk/latest/developerguide/iam-access-control.html): diff --git a/packages/@aws-cdk/aws-msk-alpha/lib/cluster.ts b/packages/@aws-cdk/aws-msk-alpha/lib/cluster.ts index ed8041e6751a2..0f0e3ef13207d 100644 --- a/packages/@aws-cdk/aws-msk-alpha/lib/cluster.ts +++ b/packages/@aws-cdk/aws-msk-alpha/lib/cluster.ts @@ -443,11 +443,7 @@ export class Cluster extends ClusterBase { /** * Reference an existing cluster, defined outside of the CDK code, by name. */ - public static fromClusterArn( - scope: constructs.Construct, - id: string, - clusterArn: string, - ): ICluster { + public static fromClusterArn(scope: constructs.Construct, id: string, clusterArn: string): ICluster { class Import extends ClusterBase { public readonly clusterArn = clusterArn; public readonly clusterName = core.Fn.select(1, core.Fn.split('/', clusterArn)); // ['arn:partition:kafka:region:account-id', clusterName, clusterId] @@ -464,9 +460,7 @@ export class Cluster extends ClusterBase { private _clusterBootstrapBrokers?: cr.AwsCustomResource; constructor(scope: constructs.Construct, id: string, props: ClusterProps) { - super(scope, id, { - physicalName: props.clusterName, - }); + super(scope, id, { physicalName: props.clusterName }); const subnetSelection = props.vpc.selectSubnets(props.vpcSubnets); @@ -480,37 +474,18 @@ export class Cluster extends ClusterBase { }); if (subnetSelection.subnets.length < 2) { - throw Error( - `Cluster requires at least 2 subnets, got ${subnetSelection.subnets.length}`, - ); + throw Error(`Cluster requires at least 2 subnets, got ${subnetSelection.subnets.length}`); } - if ( - !core.Token.isUnresolved(props.clusterName) && - !/^[a-zA-Z0-9]+$/.test(props.clusterName) && - props.clusterName.length > 64 - ) { + if (!core.Token.isUnresolved(props.clusterName) && !/^[a-zA-Z0-9]+$/.test(props.clusterName) && props.clusterName.length > 64) { throw Error( 'The cluster name must only contain alphanumeric characters and have a maximum length of 64 characters.' + `got: '${props.clusterName}. length: ${props.clusterName.length}'`, ); } - if ( - props.clientAuthentication?.saslProps?.iam && - props.clientAuthentication?.saslProps?.scram - ) { - throw Error('Only one client authentication method can be enabled.'); - } - - if ( - props.encryptionInTransit?.clientBroker === - ClientBrokerEncryption.PLAINTEXT && - props.clientAuthentication - ) { - throw Error( - 'To enable client authentication, you must enabled TLS-encrypted traffic between clients and brokers.', - ); + if (props.encryptionInTransit?.clientBroker === ClientBrokerEncryption.PLAINTEXT && props.clientAuthentication) { + throw Error('To enable client authentication, you must enabled TLS-encrypted traffic between clients and brokers.'); } else if ( props.encryptionInTransit?.clientBroker === ClientBrokerEncryption.TLS_PLAINTEXT && @@ -522,13 +497,10 @@ export class Cluster extends ClusterBase { ); } - const volumeSize = - props.ebsStorageInfo?.volumeSize ?? 1000; + const volumeSize = props.ebsStorageInfo?.volumeSize ?? 1000; // Minimum: 1 GiB, maximum: 16384 GiB if (volumeSize < 1 || volumeSize > 16384) { - throw Error( - 'EBS volume size should be in the range 1-16384', - ); + throw Error('EBS volume size should be in the range 1-16384'); } const instanceType = props.instanceType @@ -650,13 +622,9 @@ export class Cluster extends ClusterBase { }, }; - if ( - props.clientAuthentication?.saslProps?.scram && - props.clientAuthentication?.saslProps?.key === undefined - ) { + if (props.clientAuthentication?.saslProps?.scram && props.clientAuthentication?.saslProps?.key === undefined) { this.saslScramAuthenticationKey = new kms.Key(this, 'SASLKey', { - description: - 'Used for encrypting MSK secrets for SASL/SCRAM authentication.', + description: 'Used for encrypting MSK secrets for SASL/SCRAM authentication.', alias: `msk/${props.clusterName}/sasl/scram`, }); @@ -685,40 +653,18 @@ export class Cluster extends ClusterBase { ); } - let clientAuthentication; - if (props.clientAuthentication?.saslProps?.iam) { - clientAuthentication = { - sasl: { iam: { enabled: props.clientAuthentication.saslProps.iam } }, - }; - if (props.clientAuthentication?.tlsProps) { - clientAuthentication = { - sasl: { iam: { enabled: props.clientAuthentication.saslProps.iam } }, - tls: { - certificateAuthorityArnList: props.clientAuthentication?.tlsProps?.certificateAuthorities?.map( - (ca) => ca.certificateAuthorityArn, - ), - }, - }; - } - } else if (props.clientAuthentication?.saslProps?.scram) { - clientAuthentication = { - sasl: { - scram: { - enabled: props.clientAuthentication.saslProps.scram, - }, - }, - }; - } else if ( - props.clientAuthentication?.tlsProps?.certificateAuthorities !== undefined - ) { - clientAuthentication = { - tls: { - certificateAuthorityArnList: props.clientAuthentication?.tlsProps?.certificateAuthorities.map( - (ca) => ca.certificateAuthorityArn, - ), - }, - }; - } + const clientAuthentication = { + sasl: props.clientAuthentication?.saslProps ? { + iam: props.clientAuthentication.saslProps.iam ? { enabled: true }: undefined, + scram: props.clientAuthentication.saslProps.scram ? { enabled: true }: undefined, + } : undefined, + tls: props.clientAuthentication?.tlsProps?.certificateAuthorities ? { + certificateAuthorityArnList: props.clientAuthentication.tlsProps.certificateAuthorities?.map( + (ca) => ca.certificateAuthorityArn, + ), + enabled: true, + } : undefined, + }; const resource = new CfnCluster(this, 'Resource', { clusterName: props.clusterName, diff --git a/packages/@aws-cdk/aws-msk-alpha/test/__snapshots__/cluster.test.ts.snap b/packages/@aws-cdk/aws-msk-alpha/test/__snapshots__/cluster.test.ts.snap index 8c5770b626607..16f489a90c0d4 100644 --- a/packages/@aws-cdk/aws-msk-alpha/test/__snapshots__/cluster.test.ts.snap +++ b/packages/@aws-cdk/aws-msk-alpha/test/__snapshots__/cluster.test.ts.snap @@ -422,6 +422,7 @@ exports[`MSK Cluster Snapshot test with all values set 1`] = ` "CertificateAuthorityArnList": [ "arn:aws:acm-pca:us-west-2:1234567890:certificate-authority/11111111-1111-1111-1111-111111111111", ], + "Enabled": true, }, }, "ClusterName": "test-cluster", @@ -493,7 +494,7 @@ exports[`MSK Cluster Snapshot test with all values set 1`] = ` } `; -exports[`MSK Cluster created with authentication enabled with sasl/iam auth and tls Snapshot test with all values set (iam/sasl) 1`] = ` +exports[`MSK Cluster created with authentication enabled with sasl/scram, iam, and tls Snapshot test with all values set (iam/scram/tls) 1`] = ` { "Resources": { "Vpc8378EB38": { @@ -915,11 +916,15 @@ exports[`MSK Cluster created with authentication enabled with sasl/iam auth and "Iam": { "Enabled": true, }, + "Scram": { + "Enabled": true, + }, }, "Tls": { "CertificateAuthorityArnList": [ "arn:aws:acm-pca:us-west-2:1234567890:certificate-authority/11111111-1111-1111-1111-111111111111", ], + "Enabled": true, }, }, "ClusterName": "test-cluster", @@ -965,6 +970,89 @@ exports[`MSK Cluster created with authentication enabled with sasl/iam auth and "Type": "AWS::MSK::Cluster", "UpdateReplacePolicy": "Retain", }, + "kafkaSASLKey69FC3AFA": { + "DeletionPolicy": "Retain", + "Properties": { + "Description": "Used for encrypting MSK secrets for SASL/SCRAM authentication.", + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":iam::", + { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": "*", + }, + { + "Action": [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:CreateGrant", + "kms:DescribeKey", + ], + "Condition": { + "StringEquals": { + "kms:CallerAccount": { + "Ref": "AWS::AccountId", + }, + "kms:ViaService": { + "Fn::Join": [ + "", + [ + "secretsmanager.", + { + "Ref": "AWS::Region", + }, + ".amazonaws.com", + ], + ], + }, + }, + }, + "Effect": "Allow", + "Principal": { + "AWS": "*", + }, + "Resource": "*", + "Sid": "Allow access through AWS Secrets Manager for all principals in the account that are authorized to use AWS Secrets Manager", + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::KMS::Key", + "UpdateReplacePolicy": "Retain", + }, + "kafkaSASLKeyAlias7A73E101": { + "Properties": { + "AliasName": "alias/msk/test-cluster/sasl/scram", + "TargetKeyId": { + "Fn::GetAtt": [ + "kafkaSASLKey69FC3AFA", + "Arn", + ], + }, + }, + "Type": "AWS::KMS::Alias", + }, "sg1fromsg32181E6F4C07E": { "Properties": { "Description": "from sg3:2181", diff --git a/packages/@aws-cdk/aws-msk-alpha/test/cluster.test.ts b/packages/@aws-cdk/aws-msk-alpha/test/cluster.test.ts index 7c4bd6a10f4ec..20c9f6a857b80 100644 --- a/packages/@aws-cdk/aws-msk-alpha/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-msk-alpha/test/cluster.test.ts @@ -226,8 +226,8 @@ describe('MSK Cluster', () => { }); }); - describe('with sasl/iam auth and tls', () => { - test('Snapshot test with all values set (iam/sasl)', () => { + describe('with sasl/scram, iam, and tls', () => { + test('Snapshot test with all values set (iam/scram/tls)', () => { const cluster = new msk.Cluster(stack, 'kafka', { clusterName: 'test-cluster', kafkaVersion: msk.KafkaVersion.V2_6_1, @@ -249,6 +249,7 @@ describe('MSK Cluster', () => { }, clientAuthentication: msk.ClientAuthentication.saslTls({ iam: true, + scram: true, certificateAuthorities: [ acmpca.CertificateAuthority.fromCertificateAuthorityArn( stack, @@ -372,24 +373,6 @@ describe('MSK Cluster', () => { }); }); }); - - test('fails if more than one authentication method is enabled', () => { - expect( - () => - new msk.Cluster(stack, 'Cluster', { - clusterName: 'cluster', - kafkaVersion: msk.KafkaVersion.V2_6_1, - vpc, - encryptionInTransit: { - clientBroker: msk.ClientBrokerEncryption.TLS, - }, - clientAuthentication: msk.ClientAuthentication.sasl({ - iam: true, - scram: true, - }), - }), - ).toThrow('Only one client authentication method can be enabled.'); - }); }); describe('created with an instance type set', () => {