Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eks): envelope encryption for secrets #9438

Merged
merged 3 commits into from
Aug 21, 2020
Merged
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
13 changes: 13 additions & 0 deletions packages/@aws-cdk/aws-eks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}

Expand All @@ -301,6 +308,8 @@ function analyzeUpdate(oldProps: Partial<aws.EKS.CreateClusterRequest>, 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,
Expand All @@ -313,6 +322,7 @@ function analyzeUpdate(oldProps: Partial<aws.EKS.CreateClusterRequest>, 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),
};
}
Expand Down
32 changes: 32 additions & 0 deletions packages/@aws-cdk/aws-eks/lib/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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',
Expand Down
18 changes: 18 additions & 0 deletions packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-eks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
Loading