From 0da584aee5e1791c7b502e2b5c13dac418392fde Mon Sep 17 00:00:00 2001 From: Paul Nathan Date: Wed, 27 Oct 2021 13:08:20 -0400 Subject: [PATCH 1/4] Init to add passing in KubeCtlLambdaHandler IAM role through cluster props --- packages/@aws-cdk/aws-eks/lib/cluster.ts | 44 +++++++++++++++ .../@aws-cdk/aws-eks/lib/kubectl-provider.ts | 1 + .../@aws-cdk/aws-eks/test/cluster.test.ts | 24 +++++++++ yarn.lock | 53 +------------------ 4 files changed, 70 insertions(+), 52 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index c1d00a9dcd767..fb51050f76dd9 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -117,6 +117,15 @@ export interface ICluster extends IResource, ec2.IConnectable { */ readonly kubectlPrivateSubnets?: ec2.ISubnet[]; + /** + * An IAM role that can perform kubectl operations against this cluster. + * + * The role should be mapped to the `system:masters` Kubernetes RBAC role. + * + * This role is directly passed to the lambda handler that sends Kube Ctl commands to the cluster. + */ + readonly kubeCtlLambdaRole?: iam.IRole; + /** * An AWS Lambda layer that includes `kubectl`, `helm` and the `aws` CLI. * @@ -260,6 +269,18 @@ export interface ClusterAttributes { */ readonly kubectlRoleArn?: string; + /** + * An IAM role that can perform kubectl operations against this cluster. + * + * The role should be mapped to the `system:masters` Kubernetes RBAC role. + * + * This role is directly passed to the lambda handler that sends Kube Ctl commands + * to the cluster. + * @default - if not specified, the default role created by a lambda function will + * be used. + */ + readonly kubeCtlLambdaRole?: iam.IRole; + /** * Environment variables to use when running `kubectl` against this cluster. * @default - no additional variables @@ -676,6 +697,14 @@ export interface ClusterProps extends ClusterOptions { * @default NODEGROUP */ readonly defaultCapacityType?: DefaultCapacityType; + + + /** + * The IAM role to pass to the KubeCtl Lambda Handler. + * + * @default - Default Lambda IAM Execution Role + */ + readonly kubeCtlLambdaRole?: iam.IRole; } /** @@ -745,6 +774,7 @@ abstract class ClusterBase extends Resource implements ICluster { public abstract readonly clusterSecurityGroup: ec2.ISecurityGroup; public abstract readonly clusterEncryptionConfigKeyArn: string; public abstract readonly kubectlRole?: iam.IRole; + public abstract readonly kubeCtlLambdaRole?: iam.IRole; public abstract readonly kubectlEnvironment?: { [key: string]: string }; public abstract readonly kubectlSecurityGroup?: ec2.ISecurityGroup; public abstract readonly kubectlPrivateSubnets?: ec2.ISubnet[]; @@ -1050,6 +1080,18 @@ export class Cluster extends ClusterBase { */ public readonly kubectlRole?: iam.IRole; + /** + * An IAM role that can perform kubectl operations against this cluster. + * + * The role should be mapped to the `system:masters` Kubernetes RBAC role. + * + * This role is directly passed to the lambda handler that sends Kube Ctl commands to the cluster. + * @default - if not specified, the default role created by a lambda function will + * be used. + */ + + public readonly kubeCtlLambdaRole?: iam.IRole; + /** * Custom environment variables when running `kubectl` against this cluster. */ @@ -1158,6 +1200,7 @@ export class Cluster extends ClusterBase { this.prune = props.prune ?? true; this.vpc = props.vpc || new ec2.Vpc(this, 'DefaultVpc'); this.version = props.version; + this.kubeCtlLambdaRole = props.kubeCtlLambdaRole ? props.kubeCtlLambdaRole : undefined; this.tagSubnets(); @@ -1823,6 +1866,7 @@ class ImportedCluster extends ClusterBase { public readonly clusterArn: string; public readonly connections = new ec2.Connections(); public readonly kubectlRole?: iam.IRole; + public readonly kubeCtlLambdaRole?: iam.IRole; public readonly kubectlEnvironment?: { [key: string]: string; } | undefined; public readonly kubectlSecurityGroup?: ec2.ISecurityGroup | undefined; public readonly kubectlPrivateSubnets?: ec2.ISubnet[] | undefined; diff --git a/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts b/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts index b5bd8ed51b876..20388851d7385 100644 --- a/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts +++ b/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts @@ -77,6 +77,7 @@ export class KubectlProvider extends NestedStack { description: 'onEvent handler for EKS kubectl resource provider', memorySize, environment: cluster.kubectlEnvironment, + role: cluster.kubeCtlLambdaRole ? cluster.kubeCtlLambdaRole : undefined, // defined only when using private access vpc: cluster.kubectlPrivateSubnets ? cluster.vpc : undefined, diff --git a/packages/@aws-cdk/aws-eks/test/cluster.test.ts b/packages/@aws-cdk/aws-eks/test/cluster.test.ts index 295092509ffd2..4b6c88e8ee3b1 100644 --- a/packages/@aws-cdk/aws-eks/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-eks/test/cluster.test.ts @@ -2168,6 +2168,30 @@ describe('cluster', () => { }, }); + }); + + test('kubectl provider passes iam role environment to kube ctl lambda', () => { + + const { stack } = testFixture(); + + const kubeCtlRole = new iam.Role(stack, 'KubeCtlIamRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + + const cluster = new eks.Cluster(stack, 'Cluster1', { + version: CLUSTER_VERSION, + prune: false, + endpointAccess: eks.EndpointAccess.PRIVATE, + kubeCtlLambdaRole: kubeCtlRole, + }); + + // the kubectl provider is inside a nested stack. + const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; + expect(nested).toHaveResourceLike('AWS::Lambda::Function', { + Role: { + Ref: 'KubeCtlIamRole', + }, + }); }); diff --git a/yarn.lock b/yarn.lock index c63b92155ae6a..d3a7a1e6e10c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2250,11 +2250,6 @@ ansi-regex@^2.0.0: resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" @@ -3078,11 +3073,6 @@ co@^4.6.0: resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - codemaker@^1.39.0: version "1.39.0" resolved "https://registry.npmjs.org/codemaker/-/codemaker-1.39.0.tgz#d8103f4b587210b1d6aa073d62ffb510ac20bc42" @@ -5536,18 +5526,6 @@ is-extglob@^2.1.1: resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -7972,11 +7950,6 @@ null-check@^1.0.0: resolved "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd" integrity sha1-l33/1xdgErnsMNKjnbXPcqBDnt0= -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - nwsapi@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" @@ -9599,7 +9572,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-width@*, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +string-width@*, string-width@^1.0.1, "string-width@^1.0.2 || 2", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -9608,23 +9581,6 @@ string-width@*, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - string.prototype.repeat@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz#aba36de08dcee6a5a337d49b2ea1da1b28fc0ecf" @@ -9677,13 +9633,6 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" From 81d947a7fb12570cfa5ed71fc5af252a645ddc25 Mon Sep 17 00:00:00 2001 From: Neel Shah Date: Wed, 27 Oct 2021 15:35:16 -0400 Subject: [PATCH 2/4] refactor(eks): updated variable name for eks construct --- packages/@aws-cdk/aws-eks/lib/cluster.ts | 16 ++++++++-------- .../@aws-cdk/aws-eks/lib/kubectl-provider.ts | 2 +- packages/@aws-cdk/aws-eks/test/cluster.test.ts | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index fb51050f76dd9..91d290489d715 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -124,7 +124,7 @@ export interface ICluster extends IResource, ec2.IConnectable { * * This role is directly passed to the lambda handler that sends Kube Ctl commands to the cluster. */ - readonly kubeCtlLambdaRole?: iam.IRole; + readonly kubectlLambdaRole?: iam.IRole; /** * An AWS Lambda layer that includes `kubectl`, `helm` and the `aws` CLI. @@ -279,7 +279,7 @@ export interface ClusterAttributes { * @default - if not specified, the default role created by a lambda function will * be used. */ - readonly kubeCtlLambdaRole?: iam.IRole; + readonly kubectlLambdaRole?: iam.IRole; /** * Environment variables to use when running `kubectl` against this cluster. @@ -700,11 +700,11 @@ export interface ClusterProps extends ClusterOptions { /** - * The IAM role to pass to the KubeCtl Lambda Handler. + * The IAM role to pass to the Kubectl Lambda Handler. * * @default - Default Lambda IAM Execution Role */ - readonly kubeCtlLambdaRole?: iam.IRole; + readonly kubectlLambdaRole?: iam.IRole; } /** @@ -774,7 +774,7 @@ abstract class ClusterBase extends Resource implements ICluster { public abstract readonly clusterSecurityGroup: ec2.ISecurityGroup; public abstract readonly clusterEncryptionConfigKeyArn: string; public abstract readonly kubectlRole?: iam.IRole; - public abstract readonly kubeCtlLambdaRole?: iam.IRole; + public abstract readonly kubectlLambdaRole?: iam.IRole; public abstract readonly kubectlEnvironment?: { [key: string]: string }; public abstract readonly kubectlSecurityGroup?: ec2.ISecurityGroup; public abstract readonly kubectlPrivateSubnets?: ec2.ISubnet[]; @@ -1090,7 +1090,7 @@ export class Cluster extends ClusterBase { * be used. */ - public readonly kubeCtlLambdaRole?: iam.IRole; + public readonly kubectlLambdaRole?: iam.IRole; /** * Custom environment variables when running `kubectl` against this cluster. @@ -1200,7 +1200,7 @@ export class Cluster extends ClusterBase { this.prune = props.prune ?? true; this.vpc = props.vpc || new ec2.Vpc(this, 'DefaultVpc'); this.version = props.version; - this.kubeCtlLambdaRole = props.kubeCtlLambdaRole ? props.kubeCtlLambdaRole : undefined; + this.kubectlLambdaRole = props.kubectlLambdaRole ? props.kubectlLambdaRole : undefined; this.tagSubnets(); @@ -1866,7 +1866,7 @@ class ImportedCluster extends ClusterBase { public readonly clusterArn: string; public readonly connections = new ec2.Connections(); public readonly kubectlRole?: iam.IRole; - public readonly kubeCtlLambdaRole?: iam.IRole; + public readonly kubectlLambdaRole?: iam.IRole; public readonly kubectlEnvironment?: { [key: string]: string; } | undefined; public readonly kubectlSecurityGroup?: ec2.ISecurityGroup | undefined; public readonly kubectlPrivateSubnets?: ec2.ISubnet[] | undefined; diff --git a/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts b/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts index 20388851d7385..0e5db3c6a51e3 100644 --- a/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts +++ b/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts @@ -77,7 +77,7 @@ export class KubectlProvider extends NestedStack { description: 'onEvent handler for EKS kubectl resource provider', memorySize, environment: cluster.kubectlEnvironment, - role: cluster.kubeCtlLambdaRole ? cluster.kubeCtlLambdaRole : undefined, + role: cluster.kubectlLambdaRole ? cluster.kubectlLambdaRole : undefined, // defined only when using private access vpc: cluster.kubectlPrivateSubnets ? cluster.vpc : undefined, diff --git a/packages/@aws-cdk/aws-eks/test/cluster.test.ts b/packages/@aws-cdk/aws-eks/test/cluster.test.ts index 4b6c88e8ee3b1..6e53db7374810 100644 --- a/packages/@aws-cdk/aws-eks/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-eks/test/cluster.test.ts @@ -2174,7 +2174,7 @@ describe('cluster', () => { const { stack } = testFixture(); - const kubeCtlRole = new iam.Role(stack, 'KubeCtlIamRole', { + const kubectlRole = new iam.Role(stack, 'KubectlIamRole', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), }); @@ -2182,14 +2182,14 @@ describe('cluster', () => { version: CLUSTER_VERSION, prune: false, endpointAccess: eks.EndpointAccess.PRIVATE, - kubeCtlLambdaRole: kubeCtlRole, + kubectlLambdaRole: kubectlRole, }); // the kubectl provider is inside a nested stack. const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; expect(nested).toHaveResourceLike('AWS::Lambda::Function', { Role: { - Ref: 'KubeCtlIamRole', + Ref: 'KubectlIamRole', }, }); From 384a37054efef08278abf244ec07037a7e78a4b5 Mon Sep 17 00:00:00 2001 From: Paul Nathan Date: Thu, 28 Oct 2021 11:38:37 -0400 Subject: [PATCH 3/4] Update test to better reflect generated cloudformation --- packages/@aws-cdk/aws-eks/test/cluster.test.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-eks/test/cluster.test.ts b/packages/@aws-cdk/aws-eks/test/cluster.test.ts index 6e53db7374810..6ed6b9fad17f7 100644 --- a/packages/@aws-cdk/aws-eks/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-eks/test/cluster.test.ts @@ -2178,6 +2178,7 @@ describe('cluster', () => { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), }); + // using _ syntax to silence warning about _cluster not being used, when it is const cluster = new eks.Cluster(stack, 'Cluster1', { version: CLUSTER_VERSION, prune: false, @@ -2185,11 +2186,22 @@ describe('cluster', () => { kubectlLambdaRole: kubectlRole, }); + cluster.addManifest('resource', { + kind: 'ConfigMap', + apiVersion: 'v1', + data: { + hello: 'world', + }, + metadata: { + name: 'config-map', + }, + }); + // the kubectl provider is inside a nested stack. const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; expect(nested).toHaveResourceLike('AWS::Lambda::Function', { Role: { - Ref: 'KubectlIamRole', + Ref: 'referencetoStackKubectlIamRole02F8947EArn', }, }); From 7a121070c51f313173fb64a0749b7fc0eecfea12 Mon Sep 17 00:00:00 2001 From: Paul Nathan Date: Thu, 11 Nov 2021 09:31:26 -0500 Subject: [PATCH 4/4] Update readme --- packages/@aws-cdk/aws-eks/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 3b04f5d9e7f61..e9fa2f2133c13 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -535,6 +535,8 @@ Breaking this down, it means that if the endpoint exposes private access (via `E If the endpoint does not expose private access (via `EndpointAccess.PUBLIC`) **or** the VPC does not contain private subnets, the function will not be provisioned within the VPC. +If your use-case requires control over the IAM role that the KubeCtl Handler assumes, a custom role can be passed through the ClusterProps (as `kubectlLambdaRole`) of the EKS Cluster construct. + #### Cluster Handler The `ClusterHandler` is a Lambda function responsible to interact with the EKS API in order to control the cluster lifecycle. To provision this function inside the VPC, set the `placeClusterHandlerInVpc` property to `true`. This will place the function inside the private subnets of the VPC based on the selection strategy specified in the [`vpcSubnets`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-eks.Cluster.html#vpcsubnetsspan-classapi-icon-api-icon-experimental-titlethis-api-element-is-experimental-it-may-change-without-noticespan) property.