Skip to content

Commit 999c01a

Browse files
authored
feat(iam): validate roleName (#28509)
> Validates roleName Closes #28502 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 1f9788f commit 999c01a

File tree

2 files changed

+86
-1
lines changed

2 files changed

+86
-1
lines changed

packages/aws-cdk-lib/aws-iam/lib/role.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,10 @@ export class Role extends Resource implements IRole {
413413
physicalName: props.roleName,
414414
});
415415

416+
if (props.roleName && !Token.isUnresolved(props.roleName) && !/^[\w+=,.@-]{1,64}$/.test(props.roleName)) {
417+
throw new Error('Invalid roleName. The name must be a string of characters consisting of upper and lowercase alphanumeric characters with no spaces. You can also include any of the following characters: _+=,.@-. Length must be between 1 and 64 characters.');
418+
}
419+
416420
const externalIds = props.externalIds || [];
417421
if (props.externalId) {
418422
externalIds.push(props.externalId);

packages/aws-cdk-lib/aws-iam/test/role.test.ts

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { testDeprecated } from '@aws-cdk/cdk-build-tools';
22
import { Construct } from 'constructs';
33
import { Template, Match, Annotations } from '../../assertions';
4-
import { Duration, Stack, App, CfnResource, RemovalPolicy, Lazy, Stage, DefaultStackSynthesizer, CliCredentialsStackSynthesizer, PERMISSIONS_BOUNDARY_CONTEXT_KEY, PermissionsBoundary } from '../../core';
4+
import { Duration, Stack, App, CfnResource, RemovalPolicy, Lazy, Stage, DefaultStackSynthesizer, CliCredentialsStackSynthesizer, PERMISSIONS_BOUNDARY_CONTEXT_KEY, PermissionsBoundary, Token } from '../../core';
55
import { AnyPrincipal, ArnPrincipal, CompositePrincipal, FederatedPrincipal, ManagedPolicy, PolicyStatement, Role, ServicePrincipal, User, Policy, PolicyDocument, Effect } from '../lib';
66

77
describe('isRole() returns', () => {
@@ -1325,3 +1325,84 @@ test('cross-env role ARNs include path', () => {
13251325
},
13261326
});
13271327
});
1328+
1329+
test('doesn\'t throw with roleName of 64 chars', () => {
1330+
const app = new App();
1331+
const stack = new Stack(app, 'MyStack');
1332+
const valdName = 'a'.repeat(64);
1333+
1334+
expect(() => {
1335+
new Role(stack, 'Test', {
1336+
assumedBy: new ServicePrincipal('sns.amazonaws.com'),
1337+
roleName: valdName,
1338+
});
1339+
}).not.toThrow('Invalid roleName');
1340+
});
1341+
1342+
test('throws with roleName over 64 chars', () => {
1343+
const app = new App();
1344+
const stack = new Stack(app, 'MyStack');
1345+
const longName = 'a'.repeat(65);
1346+
1347+
expect(() => {
1348+
new Role(stack, 'Test', {
1349+
assumedBy: new ServicePrincipal('sns.amazonaws.com'),
1350+
roleName: longName,
1351+
});
1352+
}).toThrow('Invalid roleName');
1353+
});
1354+
1355+
describe('roleName validation', () => {
1356+
const app = new App();
1357+
const stack = new Stack(app, 'MyStack');
1358+
const invalidChars = '!#$%^&*()';
1359+
1360+
it('rejects names with spaces', () => {
1361+
expect(() => {
1362+
new Role(stack, 'test spaces', {
1363+
assumedBy: new ServicePrincipal('sns.amazonaws.com'),
1364+
roleName: 'invalid name',
1365+
});
1366+
}).toThrow('Invalid roleName');
1367+
});
1368+
1369+
invalidChars.split('').forEach(char => {
1370+
it(`rejects name with ${char}`, () => {
1371+
expect(() => {
1372+
new Role(stack, `test ${char}`, {
1373+
assumedBy: new ServicePrincipal('sns.amazonaws.com'),
1374+
roleName: `invalid${char}`,
1375+
});
1376+
}).toThrow('Invalid roleName');
1377+
});
1378+
});
1379+
1380+
});
1381+
1382+
test('roleName validation with Tokens', () =>{
1383+
const app = new App();
1384+
const stack = new Stack(app, 'MyStack');
1385+
const token = Lazy.string({ produce: () => 'token' });
1386+
1387+
// Mock isUnresolved to return false
1388+
jest.spyOn(Token, 'isUnresolved').mockReturnValue(false);
1389+
1390+
expect(() => {
1391+
new Role(stack, 'Valid', {
1392+
assumedBy: new ServicePrincipal('sns.amazonaws.com'),
1393+
roleName: token,
1394+
});
1395+
}).toThrow('Invalid roleName');
1396+
1397+
// Mock isUnresolved to return true
1398+
jest.spyOn(Token, 'isUnresolved').mockReturnValue(true);
1399+
1400+
expect(() => {
1401+
new Role(stack, 'Invalid', {
1402+
assumedBy: new ServicePrincipal('sns.amazonaws.com'),
1403+
roleName: token,
1404+
});
1405+
}).not.toThrow('Invalid roleName');
1406+
1407+
jest.clearAllMocks();
1408+
});

0 commit comments

Comments
 (0)