Skip to content

Commit f4059d7

Browse files
committed
Prepare for OIdC role custom resource
1 parent 96a142f commit f4059d7

8 files changed

Lines changed: 151 additions & 12 deletions

File tree

packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,6 @@ export interface EksClient {
7474
createOpenIDConnectProvider(request: aws.IAM.CreateOpenIDConnectProviderRequest): Promise<aws.IAM.CreateOpenIDConnectProviderResponse>;
7575
deleteOpenIDConnectProvider(request: aws.IAM.DeleteOpenIDConnectProviderRequest): Promise<{ $response: aws.Response<{}, aws.AWSError>; }>;
7676
getOpenIDConnectProvider(request: aws.IAM.GetOpenIDConnectProviderRequest): Promise<aws.IAM.GetOpenIDConnectProviderResponse>;
77+
getRole(request: aws.IAM.GetRoleRequest): Promise<aws.IAM.GetRoleResponse>;
78+
updateRole(request: aws.IAM.UpdateRoleRequest): Promise<aws.IAM.UpdateRoleResponse>;
7779
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export const CLUSTER_RESOURCE_TYPE = 'Custom::AWSCDK-EKS-Cluster';
22
export const FARGATE_PROFILE_RESOURCE_TYPE = 'Custom::AWSCDK-EKS-FargateProfile';
3+
export const OPENIDCONNECT_ROLE_RESOURCE_TYPE = 'Custom::AWSCDK-EKS-OpenIDConnectRole';
34
export const OPENIDCONNECT_PROVIDER_RESOURCE_TYPE = 'Custom::AWSCDK-EKS-OpenIDConnectProvider';

packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { ClusterResourceHandler } from './cluster';
77
import { EksClient } from './common';
88
import * as consts from './consts';
99
import { FargateProfileResourceHandler } from './fargate';
10-
import { OpenIDConnectResourceHandler } from './oidc';
10+
import { OpenIDConnectProviderResourceHandler } from './oidcprovider';
11+
import { OpenIDConnectRoleResourceHandler } from './oidcrole';
1112

1213
aws.config.logger = console;
1314

@@ -26,6 +27,8 @@ const defaultEksClient: EksClient = {
2627
createOpenIDConnectProvider: req => getIamClient().createOpenIDConnectProvider(req).promise(),
2728
deleteOpenIDConnectProvider: req => getIamClient().deleteOpenIDConnectProvider(req).promise(),
2829
getOpenIDConnectProvider: req => getIamClient().getOpenIDConnectProvider(req).promise(),
30+
getRole: req => getIamClient().getRole(req).promise(),
31+
updateRole: req => getIamClient().updateRole(req).promise(),
2932
configureAssumeRole: req => {
3033
console.log(JSON.stringify({ assumeRole: req }, undefined, 2));
3134
const creds = new aws.ChainableTemporaryCredentials({
@@ -67,7 +70,8 @@ function createResourceHandler(event: AWSLambda.CloudFormationCustomResourceEven
6770
switch (event.ResourceType) {
6871
case consts.CLUSTER_RESOURCE_TYPE: return new ClusterResourceHandler(defaultEksClient, event);
6972
case consts.FARGATE_PROFILE_RESOURCE_TYPE: return new FargateProfileResourceHandler(defaultEksClient, event);
70-
case consts.OPENIDCONNECT_PROVIDER_RESOURCE_TYPE: return new OpenIDConnectResourceHandler(defaultEksClient, event);
73+
case consts.OPENIDCONNECT_PROVIDER_RESOURCE_TYPE: return new OpenIDConnectProviderResourceHandler(defaultEksClient, event);
74+
case consts.OPENIDCONNECT_ROLE_RESOURCE_TYPE: return new OpenIDConnectRoleResourceHandler(defaultEksClient, event);
7175
default:
7276
throw new Error(`Unsupported resource type "${event.ResourceType}`);
7377
}

packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/oidc.ts renamed to packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/oidcprovider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ResourceHandler } from "./common";
44
import * as aws from "aws-sdk";
55
import * as https from "https";
66

7-
export class OpenIDConnectResourceHandler extends ResourceHandler {
7+
export class OpenIDConnectProviderResourceHandler extends ResourceHandler {
88
protected async onCreate() {
99
const clusterName = this.event.ResourceProperties.Config.clusterName;
1010

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { ResourceHandler } from "./common";
2+
3+
// eslint-disable-next-line import/no-extraneous-dependencies
4+
import * as aws from "aws-sdk";
5+
6+
export class OpenIDConnectRoleResourceHandler extends ResourceHandler {
7+
protected async onCreate() {
8+
const { roleName } = this.event.ResourceProperties.Config;
9+
10+
const role = await this.eks.getRole({ RoleName: roleName});
11+
if (!role) {
12+
throw new Error(`no role found with name ${roleName}`);
13+
}
14+
15+
return {
16+
PhysicalResourceId: roleName
17+
};
18+
}
19+
20+
protected async onDelete() {
21+
if (!this.physicalResourceId) {
22+
throw new Error(
23+
`Cannot delete a profile without a physical id`
24+
);
25+
}
26+
27+
const deleteOpenIDConnectProvider: aws.IAM.DeleteOpenIDConnectProviderRequest = {
28+
OpenIDConnectProviderArn: this.physicalResourceId
29+
};
30+
31+
this.log({ deleteOpenIDConnectProvider });
32+
const deleteOpenIDConnectProviderResponse = await this.eks.deleteOpenIDConnectProvider(
33+
deleteOpenIDConnectProvider
34+
);
35+
this.log({ deleteOpenIDConnectProviderResponse });
36+
37+
return;
38+
}
39+
40+
protected async onUpdate() {
41+
// all updates require a replacement. as long as name is generated, we are
42+
// good. if name is explicit, update will fail, which is common when trying
43+
// to replace cfn resources with explicit physical names
44+
return this.onCreate();
45+
}
46+
47+
protected async isCreateComplete() {
48+
return this.isUpdateComplete();
49+
}
50+
51+
protected async isUpdateComplete() {
52+
return {
53+
IsComplete: await this.checkResource()
54+
};
55+
}
56+
57+
protected async isDeleteComplete() {
58+
await this.checkResource();
59+
return {
60+
IsComplete: !(await this.checkResource())
61+
};
62+
}
63+
64+
private async checkResource(): Promise<boolean> {
65+
if (!this.physicalResourceId) {
66+
throw new Error(
67+
`Cannot get a profile without a physical id`
68+
);
69+
}
70+
71+
const getOpenIDConnectProvider: aws.IAM.GetOpenIDConnectProviderRequest = {
72+
OpenIDConnectProviderArn: this.physicalResourceId
73+
};
74+
75+
try {
76+
this.log({ getOpenIDConnectProvider });
77+
const getOpenIDConnectProviderResponse = await this.eks.getOpenIDConnectProvider(
78+
getOpenIDConnectProvider
79+
);
80+
this.log({ getOpenIDConnectProviderResponse });
81+
return true;
82+
} catch (getOpenIDConnectProviderError) {
83+
if (getOpenIDConnectProviderError.code === "NoSuchEntity") {
84+
this.log(
85+
"received NoSuchEntityFoundException, this means the profile has been deleted (or never existed)"
86+
);
87+
return false;
88+
}
89+
90+
this.log({ getOpenIDConnectProviderError });
91+
throw getOpenIDConnectProviderError;
92+
}
93+
}
94+
}

packages/@aws-cdk/aws-eks/lib/service-account.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,51 @@
11
import { CustomResource } from "@aws-cdk/aws-cloudformation";
22
import { FederatedPrincipal, PolicyStatement, Role } from "@aws-cdk/aws-iam";
3-
import { Construct, Lazy } from "@aws-cdk/core";
3+
import { Construct } from "@aws-cdk/core";
44
import { Cluster } from "./cluster";
55
import { OPENIDCONNECT_PROVIDER_RESOURCE_TYPE } from "./cluster-resource-handler/consts";
66
import { ClusterResourceProvider } from "./cluster-resource-provider";
77

8+
/**
9+
* Service Account
10+
*/
811
export interface ServiceAccountOptions {
12+
/**
13+
* The cluster to apply the patch to.
14+
*/
915
readonly name: string;
16+
/**
17+
* The cluster to apply the patch to.
18+
*/
1019
readonly namespace: string;
20+
/**
21+
* The cluster to apply the patch to.
22+
* @default No additional policies are applied
23+
*/
1124
readonly policyStatements?: PolicyStatement[];
1225
}
1326

27+
/**
28+
* Service Account
29+
*/
1430
export interface ServiceAccountProps extends ServiceAccountOptions {
31+
/**
32+
* The cluster to apply the patch to.
33+
* [disable-awslint:ref-via-interface]
34+
*/
1535
readonly cluster: Cluster;
1636
}
1737

38+
/**
39+
* Service Account
40+
*/
1841
export class ServiceAccount extends Construct {
19-
42+
/**
43+
* The cluster to apply the patch to.
44+
*/
2045
public readonly serviceAccountName: string;
2146

2247
private readonly role: Role;
2348

24-
private openIDConnectSubject: string | undefined;
2549
private openIdConnectProviderArn: string | undefined;
2650

2751
constructor(scope: Construct, id: string, props: ServiceAccountProps) {
@@ -31,11 +55,9 @@ export class ServiceAccount extends Construct {
3155
// Ensure OpenIDConnect association
3256
this.enableOpenIDConnectIAMProvider(cluster);
3357
// Create IAM Role
34-
const condition: { [id: string]: any; } = {};
35-
condition[Lazy.stringValue({ produce: () => this.openIDConnectSubject})] = `system:serviceaccount:${namespace}:${name}`;
3658
this.role = new Role(this, "Role", {
3759
assumedBy: new FederatedPrincipal(
38-
this.openIdConnectProviderArn!, { StringEquals: condition }, "sts:AssumeRoleWithWebIdentity"
60+
this.openIdConnectProviderArn!, { }, "sts:AssumeRoleWithWebIdentity"
3961
)
4062
});
4163
policyStatements?.forEach(this.role.addToPolicy);
@@ -58,6 +80,9 @@ export class ServiceAccount extends Construct {
5880
this.serviceAccountName = name;
5981
}
6082

83+
/**
84+
* The cluster to apply the patch to.
85+
*/
6186
public addToPolicy(statements: PolicyStatement) {
6287
this.role.addToPolicy(statements);
6388
}
@@ -79,7 +104,7 @@ export class ServiceAccount extends Construct {
79104
}
80105
});
81106
}
82-
this.openIDConnectSubject = resource.getAtt("openIDConnectSubject").toString();
107+
// this.openIDConnectSubject = resource.getAtt("openIDConnectSubject").toString();
83108
this.openIdConnectProviderArn = resource.ref;
84109
}
85110
}

packages/@aws-cdk/aws-eks/test/cluster-resource-handler-mocks.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as sdk from 'aws-sdk';
22
import { EksClient } from '../lib/cluster-resource-handler/common';
3-
import { Response } from 'aws-sdk';
43

54
/**
65
* Request objects will be assigned when a request of the relevant type will be
@@ -19,6 +18,8 @@ export let actualRequest: {
1918
createOpenIDConnectProvider?: sdk.IAM.CreateOpenIDConnectProviderRequest;
2019
deleteOpenIDConnectProvider?: sdk.IAM.DeleteOpenIDConnectProviderRequest;
2120
getOpenIDConnectProvider?: sdk.IAM.GetOpenIDConnectProviderRequest;
21+
getRole?: sdk.IAM.GetRoleRequest;
22+
updateRole?: sdk.IAM.UpdateRoleRequest;
2223
} = { };
2324

2425
/**
@@ -130,6 +131,16 @@ export const client: EksClient = {
130131
actualRequest.getOpenIDConnectProvider = req;
131132
return { };
132133
},
134+
getRole: async req => {
135+
actualRequest.getRole = req;
136+
return {
137+
Role: { Path: '/', RoleName: 'Role', RoleId: 'role', Arn: 'arn:role', CreateDate: new Date() }
138+
};
139+
},
140+
updateRole: async req => {
141+
actualRequest.updateRole = req;
142+
return { };
143+
},
133144
};
134145

135146
export const MOCK_PROPS = {

packages/@aws-cdk/aws-eks/test/test.fargate-resource-provider.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,8 @@ function newEksClientMock() {
300300
describeFargateProfile: sinon.fake.throws('not implemented'),
301301
createOpenIDConnectProvider: sinon.fake(),
302302
deleteOpenIDConnectProvider: sinon.fake(),
303-
getOpenIDConnectProvider: sinon.fake()
303+
getOpenIDConnectProvider: sinon.fake(),
304+
getRole: sinon.fake(),
305+
updateRole: sinon.fake()
304306
};
305307
}

0 commit comments

Comments
 (0)