Skip to content

Commit

Permalink
feat(iam): validate role path at build time (aws#16165)
Browse files Browse the repository at this point in the history
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 aws#13747


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
i05nagai authored and wphilipw committed May 23, 2022
1 parent 8389eae commit 940e928
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 0 deletions.
19 changes: 19 additions & 0 deletions packages/@aws-cdk/aws-iam/lib/role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,8 @@ export class Role extends Resource implements IRole {
throw new Error('Role description must be no longer than 1000 characters.');
}

validateRolePath(props.path);

const role = new CfnRole(this, 'Resource', {
assumeRolePolicyDocument: this.assumeRolePolicy as any,
managedPolicyArns: UniqueStringSet.from(() => this.managedPolicies.map(p => p.managedPolicyArn)),
Expand Down Expand Up @@ -468,6 +470,7 @@ export class Role extends Resource implements IRole {
for (const policy of Object.values(this.inlinePolicies)) {
errors.push(...policy.validateForIdentityPolicy());
}

return errors;
}
}
Expand Down Expand Up @@ -519,6 +522,22 @@ function createAssumeRolePolicy(principal: IPrincipal, externalIds: string[]) {
return actualDoc;
}

function validateRolePath(path?: string) {
if (path === undefined || Token.isUnresolved(path)) {
return;
}

const validRolePath = /^(\/|\/[\u0021-\u007F]+\/)$/;

if (path.length == 0 || path.length > 512) {
throw new Error(`Role path must be between 1 and 512 characters. The provided role path is ${path.length} characters.`);
} else if (!validRolePath.test(path)) {
throw new Error(
'Role path must be either a slash or valid characters (alphanumerics and symbols) surrounded by slashes. '
+ `Valid characters are unicode characters in [\\u0021-\\u007F]. However, ${path} is provided.`);
}
}

function validateMaxSessionDuration(duration?: number) {
if (duration === undefined) {
return;
Expand Down
66 changes: 66 additions & 0 deletions packages/@aws-cdk/aws-iam/test/role.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,72 @@ describe('IAM role', () => {
});
});

test('role path can be used to specify the path', () => {
const stack = new Stack();

new Role(stack, 'MyRole', { path: '/', assumedBy: new ServicePrincipal('sns.amazonaws.com') });

Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', {
Path: '/',
});
});

test('role path can be 1 character', () => {
const stack = new Stack();

const assumedBy = new ServicePrincipal('bla');

expect(() => new Role(stack, 'MyRole', { assumedBy, path: '/' })).not.toThrowError();
});

test('role path cannot be empty', () => {
const stack = new Stack();

const assumedBy = new ServicePrincipal('bla');

expect(() => new Role(stack, 'MyRole', { assumedBy, path: '' }))
.toThrow('Role path must be between 1 and 512 characters. The provided role path is 0 characters.');
});

test('role path must be less than or equal to 512', () => {
const stack = new Stack();

const assumedBy = new ServicePrincipal('bla');

expect(() => new Role(stack, 'MyRole', { assumedBy, path: '/' + Array(512).join('a') + '/' }))
.toThrow('Role path must be between 1 and 512 characters. The provided role path is 513 characters.');
});

test('role path must start with a forward slash', () => {
const stack = new Stack();

const assumedBy = new ServicePrincipal('bla');

const expected = (val: any) => 'Role path must be either a slash or valid characters (alphanumerics and symbols) surrounded by slashes. '
+ `Valid characters are unicode characters in [\\u0021-\\u007F]. However, ${val} is provided.`;
expect(() => new Role(stack, 'MyRole', { assumedBy, path: 'aaa' })).toThrow(expected('aaa'));
});

test('role path must end with a forward slash', () => {
const stack = new Stack();

const assumedBy = new ServicePrincipal('bla');

const expected = (val: any) => 'Role path must be either a slash or valid characters (alphanumerics and symbols) surrounded by slashes. '
+ `Valid characters are unicode characters in [\\u0021-\\u007F]. However, ${val} is provided.`;
expect(() => new Role(stack, 'MyRole', { assumedBy, path: '/a' })).toThrow(expected('/a'));
});

test('role path must contain unicode chars within [\\u0021-\\u007F]', () => {
const stack = new Stack();

const assumedBy = new ServicePrincipal('bla');

const expected = (val: any) => 'Role path must be either a slash or valid characters (alphanumerics and symbols) surrounded by slashes. '
+ `Valid characters are unicode characters in [\\u0021-\\u007F]. However, ${val} is provided.`;
expect(() => new Role(stack, 'MyRole', { assumedBy, path: '/\u0020\u0080/' })).toThrow(expected('/\u0020\u0080/'));
});

describe('maxSessionDuration', () => {

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

0 comments on commit 940e928

Please sign in to comment.