From c764b9c884bfcd336e6332954a065822d4c8ebc1 Mon Sep 17 00:00:00 2001 From: David Sung Date: Sat, 22 Aug 2020 02:42:42 +0800 Subject: [PATCH 1/2] feat(eks): envelope encryption for secrets add envelope encryption support upon eks cluster creation add update event handling for secretEncryptionKey mutation attempt add test case for checking update event handling update readme Closes #9140 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-eks/README.md | 14 ++ .../lib/cluster-resource-handler/cluster.ts | 10 + packages/@aws-cdk/aws-eks/lib/cluster.ts | 32 +++ .../@aws-cdk/aws-eks/lib/legacy-cluster.ts | 18 ++ packages/@aws-cdk/aws-eks/package.json | 2 + .../test/integ.eks-cluster.expected.json | 185 +++++++++++++----- ...eks-cluster.kubectl-disabled.expected.json | 62 ++++++ .../integ.eks-cluster.kubectl-disabled.ts | 4 + .../aws-eks/test/integ.eks-cluster.ts | 10 +- .../test/test.cluster-resource-provider.ts | 43 ++++ .../@aws-cdk/aws-eks/test/test.cluster.ts | 30 +++ .../aws-eks/test/test.legacy-cluster.ts | 30 +++ 12 files changed, 382 insertions(+), 58 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 649f881102fed..d41c06e9b9ae9 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -477,6 +477,20 @@ 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. +The option `secretsEncryptionKey` will configure the cluster to use a KMS CMK +for encrypting Kubernetes secrets. + +NOTE: this setting can only be specified when the cluster is created and cannot +be updated due to a limitation in Amazon EKS. + +```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(); + }, }; From 24a1f6bed3c5caea167d60dbbcde6dabd7bed9fb Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Sat, 22 Aug 2020 00:13:52 +0300 Subject: [PATCH 2/2] Update readme --- packages/@aws-cdk/aws-eks/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index d41c06e9b9ae9..f99d88f2fde9c 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -477,11 +477,10 @@ 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. -The option `secretsEncryptionKey` will configure the cluster to use a KMS CMK -for encrypting Kubernetes secrets. +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. -NOTE: this setting can only be specified when the cluster is created and cannot -be updated due to a limitation in Amazon EKS. ```ts const secretsKey = new kms.Key(this, 'SecretsKey');