-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
kubectl-provider.ts
230 lines (189 loc) · 7.61 KB
/
kubectl-provider.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
import * as path from 'path';
import { Construct, IConstruct } from 'constructs';
import { ICluster, Cluster } from './cluster';
import * as iam from '../../aws-iam';
import * as lambda from '../../aws-lambda';
import { Duration, Stack, NestedStack, Names, CfnCondition, Fn, Aws } from '../../core';
import * as cr from '../../custom-resources';
import { AwsCliLayer } from '../../lambda-layer-awscli';
import { KubectlLayer } from '../../lambda-layer-kubectl';
/**
* Properties for a KubectlProvider
*/
export interface KubectlProviderProps {
/**
* The cluster to control.
*/
readonly cluster: ICluster;
}
/**
* Kubectl Provider Attributes
*/
export interface KubectlProviderAttributes {
/**
* The custom resource provider's service token.
*/
readonly functionArn: string;
/**
* The IAM role to assume in order to perform kubectl operations against this cluster.
*/
readonly kubectlRoleArn: string;
/**
* The IAM execution role of the handler. This role must be able to assume kubectlRoleArn
*/
readonly handlerRole: iam.IRole;
}
/**
* Imported KubectlProvider that can be used in place of the default one created by CDK
*/
export interface IKubectlProvider extends IConstruct {
/**
* The custom resource provider's service token.
*/
readonly serviceToken: string;
/**
* The IAM role to assume in order to perform kubectl operations against this cluster.
*/
readonly roleArn: string;
/**
* The IAM execution role of the handler.
*/
readonly handlerRole: iam.IRole;
}
/**
* Implementation of Kubectl Lambda
*/
export class KubectlProvider extends NestedStack implements IKubectlProvider {
/**
* Take existing provider or create new based on cluster
*
* @param scope Construct
* @param cluster k8s cluster
*/
public static getOrCreate(scope: Construct, cluster: ICluster) {
// if this is an "owned" cluster, it has a provider associated with it
if (cluster instanceof Cluster) {
return cluster._attachKubectlResourceScope(scope);
}
// if this is an imported cluster, it maybe has a predefined kubectl provider?
if (cluster.kubectlProvider) {
return cluster.kubectlProvider;
}
// if this is an imported cluster and there is no kubectl provider defined, we need to provision a custom resource provider in this stack
// we will define one per stack for each cluster based on the cluster uniqueid
const uid = `${Names.nodeUniqueId(cluster.node)}-KubectlProvider`;
const stack = Stack.of(scope);
let provider = stack.node.tryFindChild(uid) as KubectlProvider;
if (!provider) {
provider = new KubectlProvider(stack, uid, { cluster });
}
return provider;
}
/**
* Import an existing provider
*
* @param scope Construct
* @param id an id of resource
* @param attrs attributes for the provider
*/
public static fromKubectlProviderAttributes(scope: Construct, id: string, attrs: KubectlProviderAttributes): IKubectlProvider {
return new ImportedKubectlProvider(scope, id, attrs);
}
/**
* The custom resource provider's service token.
*/
public readonly serviceToken: string;
/**
* The IAM role to assume in order to perform kubectl operations against this cluster.
*/
public readonly roleArn: string;
/**
* The IAM execution role of the handler.
*/
public readonly handlerRole: iam.IRole;
public constructor(scope: Construct, id: string, props: KubectlProviderProps) {
super(scope, id);
const cluster = props.cluster;
if (!cluster.kubectlRole) {
throw new Error('"kubectlRole" is not defined, cannot issue kubectl commands against this cluster');
}
if (cluster.kubectlPrivateSubnets && !cluster.kubectlSecurityGroup) {
throw new Error('"kubectlSecurityGroup" is required if "kubectlSubnets" is specified');
}
const memorySize = cluster.kubectlMemory ? cluster.kubectlMemory.toMebibytes() : 1024;
const handler = new lambda.Function(this, 'Handler', {
code: lambda.Code.fromAsset(path.join(__dirname, 'kubectl-handler')),
runtime: lambda.Runtime.PYTHON_3_7,
handler: 'index.handler',
timeout: Duration.minutes(15),
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,
securityGroups: cluster.kubectlPrivateSubnets && cluster.kubectlSecurityGroup ? [cluster.kubectlSecurityGroup] : undefined,
vpcSubnets: cluster.kubectlPrivateSubnets ? { subnets: cluster.kubectlPrivateSubnets } : undefined,
});
// allow user to customize the layers with the tools we need
handler.addLayers(props.cluster.awscliLayer ?? new AwsCliLayer(this, 'AwsCliLayer'));
handler.addLayers(props.cluster.kubectlLayer ?? new KubectlLayer(this, 'KubectlLayer'));
this.handlerRole = handler.role!;
this.handlerRole.addToPrincipalPolicy(new iam.PolicyStatement({
actions: ['eks:DescribeCluster'],
resources: [cluster.clusterArn],
}));
// taken from the lambda default role logic.
// makes it easier for roles to be passed in.
if (handler.isBoundToVpc) {
handler.role?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole'));
}
// For OCI helm chart authorization.
this.handlerRole.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryReadOnly'),
);
/**
* For OCI helm chart public ECR authorization. As ECR public is only available in `aws` partition,
* we conditionally attach this policy when the AWS partition is `aws`.
*/
const hasEcrPublicCondition = new CfnCondition(this.handlerRole.node.scope!, 'HasEcrPublic', {
expression: Fn.conditionEquals(Aws.PARTITION, 'aws'),
});
const conditionalPolicy = iam.ManagedPolicy.fromManagedPolicyArn(this, 'ConditionalPolicyArn',
Fn.conditionIf(hasEcrPublicCondition.logicalId,
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonElasticContainerRegistryPublicReadOnly').managedPolicyArn,
Aws.NO_VALUE).toString(),
);
this.handlerRole.addManagedPolicy(iam.ManagedPolicy.fromManagedPolicyArn(this, 'conditionalPolicy', conditionalPolicy.managedPolicyArn));
// allow this handler to assume the kubectl role
cluster.kubectlRole.grant(this.handlerRole, 'sts:AssumeRole');
const provider = new cr.Provider(this, 'Provider', {
onEventHandler: handler,
vpc: cluster.kubectlPrivateSubnets ? cluster.vpc : undefined,
vpcSubnets: cluster.kubectlPrivateSubnets ? { subnets: cluster.kubectlPrivateSubnets } : undefined,
securityGroups: cluster.kubectlPrivateSubnets && cluster.kubectlSecurityGroup ? [cluster.kubectlSecurityGroup] : undefined,
});
this.serviceToken = provider.serviceToken;
this.roleArn = cluster.kubectlRole.roleArn;
}
}
class ImportedKubectlProvider extends Construct implements IKubectlProvider {
/**
* The custom resource provider's service token.
*/
public readonly serviceToken: string;
/**
* The IAM role to assume in order to perform kubectl operations against this cluster.
*/
public readonly roleArn: string;
/**
* The IAM execution role of the handler.
*/
public readonly handlerRole: iam.IRole;
constructor(scope: Construct, id: string, props: KubectlProviderAttributes) {
super(scope, id);
this.serviceToken = props.functionArn;
this.roleArn = props.kubectlRoleArn;
this.handlerRole = props.handlerRole;
}
}