Skip to content

Commit 7c3099e

Browse files
authored
feat(eks): Service Account names validation (#19251)
Kubernetes has got specific requirements to names of resources: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/ Without checks user will learn about invalid name at `cdk deploy` stage. That could leave EKS cluster in an inconsistent state. fixes #18189 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 242836b commit 7c3099e

File tree

2 files changed

+131
-0
lines changed

2 files changed

+131
-0
lines changed

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

+34
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,18 @@ import { Construct as CoreConstruct } from '@aws-cdk/core';
1414
export interface ServiceAccountOptions {
1515
/**
1616
* The name of the service account.
17+
*
18+
* The name of a ServiceAccount object must be a valid DNS subdomain name.
19+
* https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/
1720
* @default - If no name is given, it will use the id of the resource.
1821
*/
1922
readonly name?: string;
2023

2124
/**
2225
* The namespace of the service account.
26+
*
27+
* All namespace names must be valid RFC 1123 DNS labels.
28+
* https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#namespaces-and-dns
2329
* @default "default"
2430
*/
2531
readonly namespace?: string;
@@ -65,6 +71,16 @@ export class ServiceAccount extends CoreConstruct implements IPrincipal {
6571
this.serviceAccountName = props.name ?? Names.uniqueId(this).toLowerCase();
6672
this.serviceAccountNamespace = props.namespace ?? 'default';
6773

74+
// From K8s docs: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/
75+
if (!this.isValidDnsSubdomainName(this.serviceAccountName)) {
76+
throw RangeError('The name of a ServiceAccount object must be a valid DNS subdomain name.');
77+
}
78+
79+
// From K8s docs: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#namespaces-and-dns
80+
if (!this.isValidDnsLabelName(this.serviceAccountNamespace)) {
81+
throw RangeError('All namespace names must be valid RFC 1123 DNS labels.');
82+
}
83+
6884
/* Add conditions to the role to improve security. This prevents other pods in the same namespace to assume the role.
6985
* See documentation: https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html
7086
*/
@@ -117,4 +133,22 @@ export class ServiceAccount extends CoreConstruct implements IPrincipal {
117133
public addToPrincipalPolicy(statement: PolicyStatement): AddToPrincipalPolicyResult {
118134
return this.role.addToPrincipalPolicy(statement);
119135
}
136+
137+
/**
138+
* If the value is a DNS subdomain name as defined in RFC 1123, from K8s docs.
139+
*
140+
* https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names
141+
*/
142+
private isValidDnsSubdomainName(value: string): boolean {
143+
return value.length <= 253 && /^[a-z0-9]+[a-z0-9-.]*[a-z0-9]+$/.test(value);
144+
}
145+
146+
/**
147+
* If the value follows DNS label standard as defined in RFC 1123, from K8s docs.
148+
*
149+
* https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names
150+
*/
151+
private isValidDnsLabelName(value: string): boolean {
152+
return value.length <= 63 && /^[a-z0-9]+[a-z0-9-]*[a-z0-9]+$/.test(value);
153+
}
120154
}

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

+97
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,101 @@ describe('service account', () => {
174174

175175
});
176176
});
177+
178+
describe('Service Account name must follow Kubernetes spec', () => {
179+
test('throw error on capital letters', () => {
180+
// GIVEN
181+
const { cluster } = testFixtureCluster();
182+
183+
// WHEN
184+
expect(() => cluster.addServiceAccount('InvalidServiceAccount', {
185+
name: 'XXX',
186+
}))
187+
// THEN
188+
.toThrowError(RangeError);
189+
});
190+
test('throw error if ends with dot', () => {
191+
// GIVEN
192+
const { cluster } = testFixtureCluster();
193+
194+
// WHEN
195+
expect(() => cluster.addServiceAccount('InvalidServiceAccount', {
196+
name: 'test.',
197+
}))
198+
// THEN
199+
.toThrowError(RangeError);
200+
});
201+
test('dot in the name is allowed', () => {
202+
// GIVEN
203+
const { cluster } = testFixtureCluster();
204+
const valueWithDot = 'test.name';
205+
206+
// WHEN
207+
const sa = cluster.addServiceAccount('InvalidServiceAccount', {
208+
name: valueWithDot,
209+
});
210+
211+
// THEN
212+
expect(sa.serviceAccountName).toEqual(valueWithDot);
213+
});
214+
test('throw error if name is too long', () => {
215+
// GIVEN
216+
const { cluster } = testFixtureCluster();
217+
218+
// WHEN
219+
expect(() => cluster.addServiceAccount('InvalidServiceAccount', {
220+
name: 'x'.repeat(255),
221+
}))
222+
// THEN
223+
.toThrowError(RangeError);
224+
});
225+
});
226+
227+
describe('Service Account namespace must follow Kubernetes spec', () => {
228+
test('throw error on capital letters', () => {
229+
// GIVEN
230+
const { cluster } = testFixtureCluster();
231+
232+
// WHEN
233+
expect(() => cluster.addServiceAccount('InvalidServiceAccount', {
234+
namespace: 'XXX',
235+
}))
236+
// THEN
237+
.toThrowError(RangeError);
238+
});
239+
test('throw error if ends with dot', () => {
240+
// GIVEN
241+
const { cluster } = testFixtureCluster();
242+
243+
// WHEN
244+
expect(() => cluster.addServiceAccount('InvalidServiceAccount', {
245+
namespace: 'test.',
246+
}))
247+
// THEN
248+
.toThrowError(RangeError);
249+
});
250+
test('throw error if dot is in the name', () => {
251+
// GIVEN
252+
const { cluster } = testFixtureCluster();
253+
const valueWithDot = 'test.name';
254+
255+
// WHEN
256+
expect(() => cluster.addServiceAccount('InvalidServiceAccount', {
257+
namespace: valueWithDot,
258+
}))
259+
// THEN
260+
.toThrowError(RangeError);
261+
});
262+
test('throw error if name is too long', () => {
263+
// GIVEN
264+
const { cluster } = testFixtureCluster();
265+
266+
// WHEN
267+
expect(() => cluster.addServiceAccount('InvalidServiceAccount', {
268+
namespace: 'x'.repeat(65),
269+
}))
270+
// THEN
271+
.toThrowError(RangeError);
272+
});
273+
});
177274
});

0 commit comments

Comments
 (0)