Skip to content

Commit 65a5a46

Browse files
authored
feat(iam): validate role path at build time (#16165)
Role paths can be validated at build time. According to the [API document](https://docs.aws.amazon.com/IAM/latest/APIReference/API_Role.html), `u007F`, DELETE special char, is valid. However, the creation with a role path `/\u007F/` fails due to validation failure. I don't see any use case for the special char, so I ignored the discrepancy. closes #13747 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 38e62a7 commit 65a5a46

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed

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

+19
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ export class Role extends Resource implements IRole {
355355
throw new Error('Role description must be no longer than 1000 characters.');
356356
}
357357

358+
validateRolePath(props.path);
359+
358360
const role = new CfnRole(this, 'Resource', {
359361
assumeRolePolicyDocument: this.assumeRolePolicy as any,
360362
managedPolicyArns: UniqueStringSet.from(() => this.managedPolicies.map(p => p.managedPolicyArn)),
@@ -468,6 +470,7 @@ export class Role extends Resource implements IRole {
468470
for (const policy of Object.values(this.inlinePolicies)) {
469471
errors.push(...policy.validateForIdentityPolicy());
470472
}
473+
471474
return errors;
472475
}
473476
}
@@ -519,6 +522,22 @@ function createAssumeRolePolicy(principal: IPrincipal, externalIds: string[]) {
519522
return actualDoc;
520523
}
521524

525+
function validateRolePath(path?: string) {
526+
if (path === undefined || Token.isUnresolved(path)) {
527+
return;
528+
}
529+
530+
const validRolePath = /^(\/|\/[\u0021-\u007F]+\/)$/;
531+
532+
if (path.length == 0 || path.length > 512) {
533+
throw new Error(`Role path must be between 1 and 512 characters. The provided role path is ${path.length} characters.`);
534+
} else if (!validRolePath.test(path)) {
535+
throw new Error(
536+
'Role path must be either a slash or valid characters (alphanumerics and symbols) surrounded by slashes. '
537+
+ `Valid characters are unicode characters in [\\u0021-\\u007F]. However, ${path} is provided.`);
538+
}
539+
}
540+
522541
function validateMaxSessionDuration(duration?: number) {
523542
if (duration === undefined) {
524543
return;

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

+66
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,72 @@ describe('IAM role', () => {
235235
});
236236
});
237237

238+
test('role path can be used to specify the path', () => {
239+
const stack = new Stack();
240+
241+
new Role(stack, 'MyRole', { path: '/', assumedBy: new ServicePrincipal('sns.amazonaws.com') });
242+
243+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', {
244+
Path: '/',
245+
});
246+
});
247+
248+
test('role path can be 1 character', () => {
249+
const stack = new Stack();
250+
251+
const assumedBy = new ServicePrincipal('bla');
252+
253+
expect(() => new Role(stack, 'MyRole', { assumedBy, path: '/' })).not.toThrowError();
254+
});
255+
256+
test('role path cannot be empty', () => {
257+
const stack = new Stack();
258+
259+
const assumedBy = new ServicePrincipal('bla');
260+
261+
expect(() => new Role(stack, 'MyRole', { assumedBy, path: '' }))
262+
.toThrow('Role path must be between 1 and 512 characters. The provided role path is 0 characters.');
263+
});
264+
265+
test('role path must be less than or equal to 512', () => {
266+
const stack = new Stack();
267+
268+
const assumedBy = new ServicePrincipal('bla');
269+
270+
expect(() => new Role(stack, 'MyRole', { assumedBy, path: '/' + Array(512).join('a') + '/' }))
271+
.toThrow('Role path must be between 1 and 512 characters. The provided role path is 513 characters.');
272+
});
273+
274+
test('role path must start with a forward slash', () => {
275+
const stack = new Stack();
276+
277+
const assumedBy = new ServicePrincipal('bla');
278+
279+
const expected = (val: any) => 'Role path must be either a slash or valid characters (alphanumerics and symbols) surrounded by slashes. '
280+
+ `Valid characters are unicode characters in [\\u0021-\\u007F]. However, ${val} is provided.`;
281+
expect(() => new Role(stack, 'MyRole', { assumedBy, path: 'aaa' })).toThrow(expected('aaa'));
282+
});
283+
284+
test('role path must end with a forward slash', () => {
285+
const stack = new Stack();
286+
287+
const assumedBy = new ServicePrincipal('bla');
288+
289+
const expected = (val: any) => 'Role path must be either a slash or valid characters (alphanumerics and symbols) surrounded by slashes. '
290+
+ `Valid characters are unicode characters in [\\u0021-\\u007F]. However, ${val} is provided.`;
291+
expect(() => new Role(stack, 'MyRole', { assumedBy, path: '/a' })).toThrow(expected('/a'));
292+
});
293+
294+
test('role path must contain unicode chars within [\\u0021-\\u007F]', () => {
295+
const stack = new Stack();
296+
297+
const assumedBy = new ServicePrincipal('bla');
298+
299+
const expected = (val: any) => 'Role path must be either a slash or valid characters (alphanumerics and symbols) surrounded by slashes. '
300+
+ `Valid characters are unicode characters in [\\u0021-\\u007F]. However, ${val} is provided.`;
301+
expect(() => new Role(stack, 'MyRole', { assumedBy, path: '/\u0020\u0080/' })).toThrow(expected('/\u0020\u0080/'));
302+
});
303+
238304
describe('maxSessionDuration', () => {
239305

240306
test('is not specified by default', () => {

0 commit comments

Comments
 (0)