Skip to content

Commit

Permalink
feat(eks): Service Account names validation (#19251)
Browse files Browse the repository at this point in the history
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*
  • Loading branch information
adambro authored Mar 7, 2022
1 parent 242836b commit 7c3099e
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 0 deletions.
34 changes: 34 additions & 0 deletions packages/@aws-cdk/aws-eks/lib/service-account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@ import { Construct as CoreConstruct } from '@aws-cdk/core';
export interface ServiceAccountOptions {
/**
* The name of the service account.
*
* The name of a ServiceAccount object must be a valid DNS subdomain name.
* https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/
* @default - If no name is given, it will use the id of the resource.
*/
readonly name?: string;

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

// From K8s docs: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/
if (!this.isValidDnsSubdomainName(this.serviceAccountName)) {
throw RangeError('The name of a ServiceAccount object must be a valid DNS subdomain name.');
}

// From K8s docs: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#namespaces-and-dns
if (!this.isValidDnsLabelName(this.serviceAccountNamespace)) {
throw RangeError('All namespace names must be valid RFC 1123 DNS labels.');
}

/* Add conditions to the role to improve security. This prevents other pods in the same namespace to assume the role.
* See documentation: https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html
*/
Expand Down Expand Up @@ -117,4 +133,22 @@ export class ServiceAccount extends CoreConstruct implements IPrincipal {
public addToPrincipalPolicy(statement: PolicyStatement): AddToPrincipalPolicyResult {
return this.role.addToPrincipalPolicy(statement);
}

/**
* If the value is a DNS subdomain name as defined in RFC 1123, from K8s docs.
*
* https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names
*/
private isValidDnsSubdomainName(value: string): boolean {
return value.length <= 253 && /^[a-z0-9]+[a-z0-9-.]*[a-z0-9]+$/.test(value);
}

/**
* If the value follows DNS label standard as defined in RFC 1123, from K8s docs.
*
* https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names
*/
private isValidDnsLabelName(value: string): boolean {
return value.length <= 63 && /^[a-z0-9]+[a-z0-9-]*[a-z0-9]+$/.test(value);
}
}
97 changes: 97 additions & 0 deletions packages/@aws-cdk/aws-eks/test/service-account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,101 @@ describe('service account', () => {

});
});

describe('Service Account name must follow Kubernetes spec', () => {
test('throw error on capital letters', () => {
// GIVEN
const { cluster } = testFixtureCluster();

// WHEN
expect(() => cluster.addServiceAccount('InvalidServiceAccount', {
name: 'XXX',
}))
// THEN
.toThrowError(RangeError);
});
test('throw error if ends with dot', () => {
// GIVEN
const { cluster } = testFixtureCluster();

// WHEN
expect(() => cluster.addServiceAccount('InvalidServiceAccount', {
name: 'test.',
}))
// THEN
.toThrowError(RangeError);
});
test('dot in the name is allowed', () => {
// GIVEN
const { cluster } = testFixtureCluster();
const valueWithDot = 'test.name';

// WHEN
const sa = cluster.addServiceAccount('InvalidServiceAccount', {
name: valueWithDot,
});

// THEN
expect(sa.serviceAccountName).toEqual(valueWithDot);
});
test('throw error if name is too long', () => {
// GIVEN
const { cluster } = testFixtureCluster();

// WHEN
expect(() => cluster.addServiceAccount('InvalidServiceAccount', {
name: 'x'.repeat(255),
}))
// THEN
.toThrowError(RangeError);
});
});

describe('Service Account namespace must follow Kubernetes spec', () => {
test('throw error on capital letters', () => {
// GIVEN
const { cluster } = testFixtureCluster();

// WHEN
expect(() => cluster.addServiceAccount('InvalidServiceAccount', {
namespace: 'XXX',
}))
// THEN
.toThrowError(RangeError);
});
test('throw error if ends with dot', () => {
// GIVEN
const { cluster } = testFixtureCluster();

// WHEN
expect(() => cluster.addServiceAccount('InvalidServiceAccount', {
namespace: 'test.',
}))
// THEN
.toThrowError(RangeError);
});
test('throw error if dot is in the name', () => {
// GIVEN
const { cluster } = testFixtureCluster();
const valueWithDot = 'test.name';

// WHEN
expect(() => cluster.addServiceAccount('InvalidServiceAccount', {
namespace: valueWithDot,
}))
// THEN
.toThrowError(RangeError);
});
test('throw error if name is too long', () => {
// GIVEN
const { cluster } = testFixtureCluster();

// WHEN
expect(() => cluster.addServiceAccount('InvalidServiceAccount', {
namespace: 'x'.repeat(65),
}))
// THEN
.toThrowError(RangeError);
});
});
});

0 comments on commit 7c3099e

Please sign in to comment.