diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 649f881102fed..f99d88f2fde9c 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -477,6 +477,19 @@ Kubernetes secrets using the AWS Key Management Service (AWS KMS) can be enabled on [creating a cluster](https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html) can provide more details about the customer master key (CMK) that can be used for the encryption. +You can use the `secretsEncryptionKey` to configure which key the cluster will use to encrypt Kubernetes secrets. By default, an AWS Managed key will be used. + +> This setting can only be specified when the cluster is created and cannot be updated. + + +```ts +const secretsKey = new kms.Key(this, 'SecretsKey'); +const cluster = new eks.Cluster(this, 'MyCluster', { + secretsEncryptionKey: secretsKey, + // ... +}); +``` + The Amazon Resource Name (ARN) for that CMK can be retrieved. ```ts diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts index b69e9db8c35bc..d11a0a04f7233 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts @@ -105,6 +105,11 @@ export class ClusterResourceHandler extends ResourceHandler { const updates = analyzeUpdate(this.oldProps, this.newProps); console.log('onUpdate:', JSON.stringify({ updates }, undefined, 2)); + // updates to encryption config is not supported + if (updates.updateEncryption) { + throw new Error('Cannot update cluster encryption configuration'); + } + // if there is an update that requires replacement, go ahead and just create // a new cluster with the new config. The old cluster will automatically be // deleted by cloudformation upon success. @@ -287,8 +292,10 @@ interface UpdateMap { replaceName: boolean; // name replaceVpc: boolean; // resourcesVpcConfig.subnetIds and securityGroupIds replaceRole: boolean; // roleArn + updateVersion: boolean; // version updateLogging: boolean; // logging + updateEncryption: boolean; // encryption (cannot be updated) updateAccess: boolean; // resourcesVpcConfig.endpointPrivateAccess and endpointPublicAccess } @@ -301,6 +308,8 @@ function analyzeUpdate(oldProps: Partial, newProps const oldPublicAccessCidrs = new Set(oldVpcProps.publicAccessCidrs ?? []); const newPublicAccessCidrs = new Set(newVpcProps.publicAccessCidrs ?? []); + const newEnc = newProps.encryptionConfig || { }; + const oldEnc = oldProps.encryptionConfig || { }; return { replaceName: newProps.name !== oldProps.name, @@ -313,6 +322,7 @@ function analyzeUpdate(oldProps: Partial, newProps !setsEqual(newPublicAccessCidrs, oldPublicAccessCidrs), replaceRole: newProps.roleArn !== oldProps.roleArn, updateVersion: newProps.version !== oldProps.version, + updateEncryption: JSON.stringify(newEnc) !== JSON.stringify(oldEnc), updateLogging: JSON.stringify(newProps.logging) !== JSON.stringify(oldProps.logging), }; } diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 7db245a814efb..fa1fa36df110b 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -3,6 +3,7 @@ import * as path from 'path'; import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; import * as ssm from '@aws-cdk/aws-ssm'; import { CfnOutput, CfnResource, Construct, IResource, Resource, Stack, Tags, Token, Duration } from '@aws-cdk/core'; import * as YAML from 'yaml'; @@ -379,6 +380,15 @@ export interface ClusterProps extends ClusterOptions { * @default NODEGROUP */ readonly defaultCapacityType?: DefaultCapacityType; + + /** + * KMS secret for envelope encryption for Kubernetes secrets. + * + * @default - By default, Kubernetes stores all secret object data within etcd and + * all etcd volumes used by Amazon EKS are encrypted at the disk-level + * using AWS-Managed encryption keys. + */ + readonly secretsEncryptionKey?: kms.IKey; } /** @@ -630,6 +640,14 @@ export class Cluster extends Resource implements ICluster { securityGroupIds: [securityGroup.securityGroupId], subnetIds, }, + ...(props.secretsEncryptionKey ? { + encryptionConfig: [{ + provider: { + keyArn: props.secretsEncryptionKey.keyArn, + }, + resources: ['secrets'], + }], + } : {} ), }; this.endpointAccess = props.endpointAccess ?? EndpointAccess.PUBLIC_AND_PRIVATE; @@ -671,6 +689,20 @@ export class Cluster extends Resource implements ICluster { })], })); + // grant cluster creation role sufficient permission to access the specified key + // see https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html + if (props.secretsEncryptionKey) { + this._clusterResource.creationRole.addToPolicy(new iam.PolicyStatement({ + actions: [ + 'kms:Encrypt', + 'kms:Decrypt', + 'kms:DescribeKey', + 'kms:CreateGrant', + ], + resources: [props.secretsEncryptionKey.keyArn], + })); + } + // we use an SSM parameter as a barrier because it's free and fast. this._kubectlReadyBarrier = new CfnResource(this, 'KubectlReadyBarrier', { type: 'AWS::SSM::Parameter', diff --git a/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts b/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts index 7aec7be0cd693..58d4bd425a919 100644 --- a/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts @@ -1,6 +1,7 @@ import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; import * as ssm from '@aws-cdk/aws-ssm'; import { CfnOutput, Construct, Resource, Stack, Token, Tags } from '@aws-cdk/core'; import { ICluster, ClusterAttributes, KubernetesVersion, NodeType, DefaultCapacityType, EksOptimizedImage, CapacityOptions, MachineImageType, AutoScalingGroupOptions, CommonClusterOptions } from './cluster'; @@ -43,6 +44,15 @@ export interface LegacyClusterProps extends CommonClusterOptions { * @default NODEGROUP */ readonly defaultCapacityType?: DefaultCapacityType; + + /** + * KMS secret for envelope encryption for Kubernetes secrets. + * + * @default - By default, Kubernetes stores all secret object data within etcd and + * all etcd volumes used by Amazon EKS are encrypted at the disk-level + * using AWS-Managed encryption keys. + */ + readonly secretsEncryptionKey?: kms.IKey; } /** @@ -184,6 +194,14 @@ export class LegacyCluster extends Resource implements ICluster { securityGroupIds: [securityGroup.securityGroupId], subnetIds, }, + ...(props.secretsEncryptionKey ? { + encryptionConfig: [{ + provider: { + keyArn: props.secretsEncryptionKey.keyArn, + }, + resources: ['secrets'], + }], + } : {} ), }; const resource = new CfnCluster(this, 'Resource', clusterProps); diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 4e5d793058cf8..24426f0acb0d1 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -77,6 +77,7 @@ "@aws-cdk/aws-autoscaling": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-ssm": "0.0.0", "@aws-cdk/core": "0.0.0", @@ -92,6 +93,7 @@ "@aws-cdk/aws-autoscaling": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-ssm": "0.0.0", "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json index 62e1e78b850c1..c6ad064928fc6 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json @@ -28,6 +28,53 @@ } } }, + "SecretsKey317DCF94": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, "Vpc8378EB38": { "Type": "AWS::EC2::VPC", "Properties": { @@ -856,6 +903,21 @@ ] } }, + { + "Action": [ + "kms:Encrypt", + "kms:Decrypt", + "kms:DescribeKey", + "kms:CreateGrant" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "SecretsKey317DCF94", + "Arn" + ] + } + }, { "Action": "iam:PassRole", "Effect": "Allow", @@ -926,6 +988,21 @@ "Arn" ] }, + "encryptionConfig": [ + { + "provider": { + "keyArn": { + "Fn::GetAtt": [ + "SecretsKey317DCF94", + "Arn" + ] + } + }, + "resources": [ + "secrets" + ] + } + ], "resourcesVpcConfig": { "subnetIds": [ { @@ -2909,7 +2986,7 @@ }, "/", { - "Ref": "AssetParameters63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1S3BucketC7BC4682" + "Ref": "AssetParameters6ecca35abf6e120e05482cd9c32ba6b63ca36c403162c80cac6aa3af3f69d5dfS3Bucket9C877664" }, "/", { @@ -2919,7 +2996,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1S3VersionKeyEFC9702C" + "Ref": "AssetParameters6ecca35abf6e120e05482cd9c32ba6b63ca36c403162c80cac6aa3af3f69d5dfS3VersionKeyD136CE00" } ] } @@ -2932,7 +3009,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1S3VersionKeyEFC9702C" + "Ref": "AssetParameters6ecca35abf6e120e05482cd9c32ba6b63ca36c403162c80cac6aa3af3f69d5dfS3VersionKeyD136CE00" } ] } @@ -2942,17 +3019,17 @@ ] }, "Parameters": { - "referencetoawscdkeksclustertestAssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3Bucket49014916Ref": { - "Ref": "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3BucketAEF9EB6C" + "referencetoawscdkeksclustertestAssetParameters7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4S3BucketAAB1A713Ref": { + "Ref": "AssetParameters7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4S3Bucket086F94BB" }, - "referencetoawscdkeksclustertestAssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3VersionKey2D51245BRef": { - "Ref": "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3VersionKey912C763C" + "referencetoawscdkeksclustertestAssetParameters7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4S3VersionKey481F0807Ref": { + "Ref": "AssetParameters7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4S3VersionKeyA4B5C598" }, - "referencetoawscdkeksclustertestAssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3BucketA9A24CF5Ref": { - "Ref": "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3BucketF1BD2256" + "referencetoawscdkeksclustertestAssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3Bucket85526CA7Ref": { + "Ref": "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3BucketD25BCC90" }, - "referencetoawscdkeksclustertestAssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3VersionKey6036F880Ref": { - "Ref": "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3VersionKeyF47FA401" + "referencetoawscdkeksclustertestAssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3VersionKey46BA60C1Ref": { + "Ref": "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3VersionKey72DFE7A5" } } } @@ -2970,7 +3047,7 @@ }, "/", { - "Ref": "AssetParameters2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364eS3BucketB126CD55" + "Ref": "AssetParametersc5632c43ffe4f4e631b88fb1e6ba409c43088afcd9a566a1d2d59fcbb6a13892S3Bucket55EC5E2E" }, "/", { @@ -2980,7 +3057,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364eS3VersionKey6DD5D685" + "Ref": "AssetParametersc5632c43ffe4f4e631b88fb1e6ba409c43088afcd9a566a1d2d59fcbb6a13892S3VersionKey4D11177E" } ] } @@ -2993,7 +3070,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364eS3VersionKey6DD5D685" + "Ref": "AssetParametersc5632c43ffe4f4e631b88fb1e6ba409c43088afcd9a566a1d2d59fcbb6a13892S3VersionKey4D11177E" } ] } @@ -3009,11 +3086,11 @@ "referencetoawscdkeksclustertestAssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3VersionKey901D947ARef": { "Ref": "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3VersionKey40FF2C4A" }, - "referencetoawscdkeksclustertestAssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3BucketA9A24CF5Ref": { - "Ref": "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3BucketF1BD2256" + "referencetoawscdkeksclustertestAssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3Bucket85526CA7Ref": { + "Ref": "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3BucketD25BCC90" }, - "referencetoawscdkeksclustertestAssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3VersionKey6036F880Ref": { - "Ref": "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3VersionKeyF47FA401" + "referencetoawscdkeksclustertestAssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3VersionKey46BA60C1Ref": { + "Ref": "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3VersionKey72DFE7A5" } } } @@ -3144,7 +3221,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameterseb7a9b73a02dcd848325fc3abc22c1923c364d7480e06bd68a337dc3f33143d3S3BucketB6A9971A" + "Ref": "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3Bucket14156880" }, "S3Key": { "Fn::Join": [ @@ -3157,7 +3234,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterseb7a9b73a02dcd848325fc3abc22c1923c364d7480e06bd68a337dc3f33143d3S3VersionKey08BBD845" + "Ref": "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3VersionKey5225BCA4" } ] } @@ -3170,7 +3247,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterseb7a9b73a02dcd848325fc3abc22c1923c364d7480e06bd68a337dc3f33143d3S3VersionKey08BBD845" + "Ref": "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3VersionKey5225BCA4" } ] } @@ -3413,7 +3490,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3BucketF1BD2256" + "Ref": "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3BucketD25BCC90" }, "S3Key": { "Fn::Join": [ @@ -3426,7 +3503,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3VersionKeyF47FA401" + "Ref": "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3VersionKey72DFE7A5" } ] } @@ -3439,7 +3516,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3VersionKeyF47FA401" + "Ref": "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3VersionKey72DFE7A5" } ] } @@ -3600,29 +3677,29 @@ } }, "Parameters": { - "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3BucketAEF9EB6C": { + "AssetParameters7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4S3Bucket086F94BB": { "Type": "String", - "Description": "S3 bucket for asset \"1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3\"" + "Description": "S3 bucket for asset \"7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4\"" }, - "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3VersionKey912C763C": { + "AssetParameters7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4S3VersionKeyA4B5C598": { "Type": "String", - "Description": "S3 key for asset version \"1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3\"" + "Description": "S3 key for asset version \"7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4\"" }, - "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3ArtifactHashD59B0951": { + "AssetParameters7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4ArtifactHash9B26D532": { "Type": "String", - "Description": "Artifact hash for asset \"1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3\"" + "Description": "Artifact hash for asset \"7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4\"" }, - "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3BucketF1BD2256": { + "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3BucketD25BCC90": { "Type": "String", - "Description": "S3 bucket for asset \"974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74c\"" + "Description": "S3 bucket for asset \"34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1\"" }, - "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3VersionKeyF47FA401": { + "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3VersionKey72DFE7A5": { "Type": "String", - "Description": "S3 key for asset version \"974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74c\"" + "Description": "S3 key for asset version \"34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1\"" }, - "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cArtifactHash5C0B1EA0": { + "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1ArtifactHashAA0236EE": { "Type": "String", - "Description": "Artifact hash for asset \"974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74c\"" + "Description": "Artifact hash for asset \"34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1\"" }, "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3Bucket9ABBD5A2": { "Type": "String", @@ -3648,17 +3725,17 @@ "Type": "String", "Description": "Artifact hash for asset \"952bd1c03e8201c4c1c67d6de0f3fdaaf88fda05f89a1232c3f6364343cd5344\"" }, - "AssetParameterseb7a9b73a02dcd848325fc3abc22c1923c364d7480e06bd68a337dc3f33143d3S3BucketB6A9971A": { + "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3Bucket14156880": { "Type": "String", - "Description": "S3 bucket for asset \"eb7a9b73a02dcd848325fc3abc22c1923c364d7480e06bd68a337dc3f33143d3\"" + "Description": "S3 bucket for asset \"b075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0de\"" }, - "AssetParameterseb7a9b73a02dcd848325fc3abc22c1923c364d7480e06bd68a337dc3f33143d3S3VersionKey08BBD845": { + "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3VersionKey5225BCA4": { "Type": "String", - "Description": "S3 key for asset version \"eb7a9b73a02dcd848325fc3abc22c1923c364d7480e06bd68a337dc3f33143d3\"" + "Description": "S3 key for asset version \"b075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0de\"" }, - "AssetParameterseb7a9b73a02dcd848325fc3abc22c1923c364d7480e06bd68a337dc3f33143d3ArtifactHashADF25EB1": { + "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deArtifactHashC509349A": { "Type": "String", - "Description": "Artifact hash for asset \"eb7a9b73a02dcd848325fc3abc22c1923c364d7480e06bd68a337dc3f33143d3\"" + "Description": "Artifact hash for asset \"b075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0de\"" }, "AssetParameters2acc31b34c05692ab3ea9831a27e5f241cffb21857e633d8256b8f0ebf5f3f43S3BucketB43AFE04": { "Type": "String", @@ -3672,29 +3749,29 @@ "Type": "String", "Description": "Artifact hash for asset \"2acc31b34c05692ab3ea9831a27e5f241cffb21857e633d8256b8f0ebf5f3f43\"" }, - "AssetParameters63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1S3BucketC7BC4682": { + "AssetParameters6ecca35abf6e120e05482cd9c32ba6b63ca36c403162c80cac6aa3af3f69d5dfS3Bucket9C877664": { "Type": "String", - "Description": "S3 bucket for asset \"63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1\"" + "Description": "S3 bucket for asset \"6ecca35abf6e120e05482cd9c32ba6b63ca36c403162c80cac6aa3af3f69d5df\"" }, - "AssetParameters63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1S3VersionKeyEFC9702C": { + "AssetParameters6ecca35abf6e120e05482cd9c32ba6b63ca36c403162c80cac6aa3af3f69d5dfS3VersionKeyD136CE00": { "Type": "String", - "Description": "S3 key for asset version \"63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1\"" + "Description": "S3 key for asset version \"6ecca35abf6e120e05482cd9c32ba6b63ca36c403162c80cac6aa3af3f69d5df\"" }, - "AssetParameters63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1ArtifactHash36EF9FF1": { + "AssetParameters6ecca35abf6e120e05482cd9c32ba6b63ca36c403162c80cac6aa3af3f69d5dfArtifactHash95C25BEB": { "Type": "String", - "Description": "Artifact hash for asset \"63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1\"" + "Description": "Artifact hash for asset \"6ecca35abf6e120e05482cd9c32ba6b63ca36c403162c80cac6aa3af3f69d5df\"" }, - "AssetParameters2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364eS3BucketB126CD55": { + "AssetParametersc5632c43ffe4f4e631b88fb1e6ba409c43088afcd9a566a1d2d59fcbb6a13892S3Bucket55EC5E2E": { "Type": "String", - "Description": "S3 bucket for asset \"2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364e\"" + "Description": "S3 bucket for asset \"c5632c43ffe4f4e631b88fb1e6ba409c43088afcd9a566a1d2d59fcbb6a13892\"" }, - "AssetParameters2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364eS3VersionKey6DD5D685": { + "AssetParametersc5632c43ffe4f4e631b88fb1e6ba409c43088afcd9a566a1d2d59fcbb6a13892S3VersionKey4D11177E": { "Type": "String", - "Description": "S3 key for asset version \"2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364e\"" + "Description": "S3 key for asset version \"c5632c43ffe4f4e631b88fb1e6ba409c43088afcd9a566a1d2d59fcbb6a13892\"" }, - "AssetParameters2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364eArtifactHash70870D76": { + "AssetParametersc5632c43ffe4f4e631b88fb1e6ba409c43088afcd9a566a1d2d59fcbb6a13892ArtifactHash1733F1C4": { "Type": "String", - "Description": "Artifact hash for asset \"2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364e\"" + "Description": "Artifact hash for asset \"c5632c43ffe4f4e631b88fb1e6ba409c43088afcd9a566a1d2d59fcbb6a13892\"" }, "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.expected.json index 8b055a913524d..7d8d3e659bd0d 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.expected.json @@ -586,6 +586,53 @@ } } }, + "SecretsKey317DCF94": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, "EKSClusterRoleC0AEAC3D": { "Type": "AWS::IAM::Role", "Properties": { @@ -693,6 +740,21 @@ "Arn" ] }, + "EncryptionConfig": [ + { + "Provider": { + "KeyArn": { + "Fn::GetAtt": [ + "SecretsKey317DCF94", + "Arn" + ] + } + }, + "Resources": [ + "secrets" + ] + } + ], "Version": "1.16" } }, diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.ts index eaf0dfa1c5e9e..81a749fef2c93 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.ts @@ -1,4 +1,5 @@ import * as ec2 from '@aws-cdk/aws-ec2'; +import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; import * as eks from '../lib'; import { TestStack } from './util'; @@ -9,10 +10,13 @@ class EksClusterStack extends TestStack { const vpc = new ec2.Vpc(this, 'VPC'); + const secretsEncryptionKey = new kms.Key(this, 'SecretsKey'); + const cluster = new eks.LegacyCluster(this, 'EKSCluster', { vpc, defaultCapacity: 0, version: eks.KubernetesVersion.V1_16, + secretsEncryptionKey, }); cluster.addCapacity('Nodes', { diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts index 30bd5144a2015..f4405b8f6913a 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts @@ -1,6 +1,7 @@ /// !cdk-integ pragma:ignore-assets import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; import { App, CfnOutput, Duration, Token } from '@aws-cdk/core'; import * as eks from '../lib'; import * as hello from './hello-k8s'; @@ -20,6 +21,8 @@ class EksClusterStack extends TestStack { assumedBy: new iam.AccountRootPrincipal(), }); + const secretsEncryptionKey = new kms.Key(this, 'SecretsKey'); + // just need one nat gateway to simplify the test this.vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 3, natGateways: 1 }); @@ -29,6 +32,7 @@ class EksClusterStack extends TestStack { mastersRole, defaultCapacity: 2, version: eks.KubernetesVersion.V1_16, + secretsEncryptionKey, }); this.assertFargateProfile(); @@ -217,12 +221,10 @@ class EksClusterStack extends TestStack { } } -// this test uses the bottlerocket image, which is only supported in these +// this test uses both the bottlerocket image and the inf1 instance, which are only supported in these // regions. see https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/aws-eks#bottlerocket +// and https://aws.amazon.com/about-aws/whats-new/2019/12/introducing-amazon-ec2-inf1-instances-high-performance-and-the-lowest-cost-machine-learning-inference-in-the-cloud/ const supportedRegions = [ - 'ap-northeast-1', - 'ap-south-1', - 'eu-central-1', 'us-east-1', 'us-west-2', ]; diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts b/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts index be381ddd996e3..1e0438905917d 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts @@ -132,6 +132,27 @@ export = { test.done(); }, + async 'encryption config'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Create', { + ...mocks.MOCK_PROPS, + encryptionConfig: [{ provider: { keyArn: 'aws:kms:key' }, resources: ['secrets'] }], + })); + + await handler.onEvent(); + + test.deepEqual(mocks.actualRequest.createClusterRequest, { + roleArn: 'arn:of:role', + resourcesVpcConfig: { + subnetIds: ['subnet1', 'subnet2'], + securityGroupIds: ['sg1', 'sg2', 'sg3'], + }, + encryptionConfig: [{ provider: { keyArn: 'aws:kms:key' }, resources: ['secrets'] }], + name: 'MyResourceId-fakerequestid', + }); + + test.done(); + }, + }, delete: { @@ -381,6 +402,28 @@ export = { }, }, + async 'encryption config cannot be updated'(test: Test) { + // GIVEN + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { + encryptionConfig: [{ resources: ['secrets'], provider: { keyArn: 'key:arn:1' } }], + }, { + encryptionConfig: [{ resources: ['secrets'], provider: { keyArn: 'key:arn:2' } }], + })); + + // WHEN + let error; + try { + await handler.onEvent(); + } catch (e) { + error = e; + } + + // THEN + test.ok(error); + test.equal(error.message, 'Cannot update cluster encryption configuration'); + test.done(); + }, + 'isUpdateComplete with EKS update ID': { async 'with "Failed" status'(test: Test) { diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index c5f0048150f5a..9f7de1738d000 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -3,6 +3,7 @@ import * as path from 'path'; import { countResources, expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as YAML from 'yaml'; @@ -1838,4 +1839,33 @@ export = { test.deepEqual(rawTemplate.Outputs.LoadBalancerAddress.Value, { 'Fn::GetAtt': [expectedKubernetesGetId, 'Value'] }); test.done(); }, + 'create a cluster using custom resource with secrets encryption using KMS CMK'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + + // WHEN + new eks.Cluster(stack, 'Cluster', { + vpc, + version: CLUSTER_VERSION, + secretsEncryptionKey: new kms.Key(stack, 'Key'), + }); + + // THEN + expect(stack).to(haveResourceLike('Custom::AWSCDK-EKS-Cluster', { + Config: { + encryptionConfig: [{ + provider: { + keyArn: { + 'Fn::GetAtt': [ + 'Key961B73FD', + 'Arn', + ], + }, + }, + resources: ['secrets'], + }], + }, + })); + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-eks/test/test.legacy-cluster.ts b/packages/@aws-cdk/aws-eks/test/test.legacy-cluster.ts index f70827649b7ef..646a0e948ef00 100644 --- a/packages/@aws-cdk/aws-eks/test/test.legacy-cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.legacy-cluster.ts @@ -1,6 +1,7 @@ import { expect, haveResource, haveResourceLike, not } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as eks from '../lib'; @@ -587,4 +588,33 @@ export = { test.done(); }, }, + + 'create a cluster with secrets encryption using KMS CMK'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + + // WHEN + new eks.LegacyCluster(stack, 'Cluster', { + vpc, + version: CLUSTER_VERSION, + secretsEncryptionKey: new kms.Key(stack, 'Key'), + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::EKS::Cluster', { + EncryptionConfig: [{ + Provider: { + KeyArn: { + 'Fn::GetAtt': [ + 'Key961B73FD', + 'Arn', + ], + }, + }, + Resources: ['secrets'], + }], + })); + + test.done(); + }, };