packages/aws-cdk-lib/aws-iam/lib/role.ts (419 lines of code) (raw):

import { Construct, IConstruct, DependencyGroup, Node } from 'constructs'; import { Grant } from './grant'; import { CfnRole } from './iam.generated'; import { IIdentity } from './identity-base'; import { IManagedPolicy, ManagedPolicy } from './managed-policy'; import { Policy } from './policy'; import { PolicyDocument } from './policy-document'; import { PolicyStatement } from './policy-statement'; import { AccountPrincipal, AddToPrincipalPolicyResult, ArnPrincipal, IPrincipal, PrincipalPolicyFragment, ServicePrincipal } from './principals'; import { defaultAddPrincipalToAssumeRole } from './private/assume-role-policy'; import { ImmutableRole } from './private/immutable-role'; import { ImportedRole } from './private/imported-role'; import { MutatingPolicyDocumentAdapter } from './private/policydoc-adapter'; import { PrecreatedRole } from './private/precreated-role'; import { AttachedPolicies, UniqueStringSet } from './private/util'; import * as cxschema from '../../cloud-assembly-schema'; import { ArnFormat, Duration, Resource, Stack, Token, TokenComparison, Aspects, Annotations, RemovalPolicy, ContextProvider } from '../../core'; import { getCustomizeRolesConfig, getPrecreatedRoleConfig, CUSTOMIZE_ROLES_CONTEXT_KEY, CustomizeRoleConfig } from '../../core/lib/helpers-internal'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; import { mutatingAspectPrio32333 } from '../../core/lib/private/aspect-prio'; const MAX_INLINE_SIZE = 10000; const MAX_MANAGEDPOL_SIZE = 6000; const IAM_ROLE_SYMBOL = Symbol.for('@aws-cdk/packages/aws-iam/lib/role.Role'); /** * Properties for defining an IAM Role */ export interface RoleProps { /** * The IAM principal (i.e. `new ServicePrincipal('sns.amazonaws.com')`) * which can assume this role. * * You can later modify the assume role policy document by accessing it via * the `assumeRolePolicy` property. */ readonly assumedBy: IPrincipal; /** * ID that the role assumer needs to provide when assuming this role * * If the configured and provided external IDs do not match, the * AssumeRole operation will fail. * * @deprecated see `externalIds` * * @default No external ID required */ readonly externalId?: string; /** * List of IDs that the role assumer needs to provide one of when assuming this role * * If the configured and provided external IDs do not match, the * AssumeRole operation will fail. * * @default No external ID required */ readonly externalIds?: string[]; /** * A list of managed policies associated with this role. * * You can add managed policies later using * `addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName(policyName))`. * * @default - No managed policies. */ readonly managedPolicies?: IManagedPolicy[]; /** * A list of named policies to inline into this role. These policies will be * created with the role, whereas those added by ``addToPolicy`` are added * using a separate CloudFormation resource (allowing a way around circular * dependencies that could otherwise be introduced). * * @default - No policy is inlined in the Role resource. */ readonly inlinePolicies?: { [name: string]: PolicyDocument }; /** * The path associated with this role. For information about IAM paths, see * Friendly Names and Paths in IAM User Guide. * * @default / */ readonly path?: string; /** * AWS supports permissions boundaries for IAM entities (users or roles). * A permissions boundary is an advanced feature for using a managed policy * to set the maximum permissions that an identity-based policy can grant to * an IAM entity. An entity's permissions boundary allows it to perform only * the actions that are allowed by both its identity-based policies and its * permissions boundaries. * * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-permissionsboundary * @link https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html * * @default - No permissions boundary. */ readonly permissionsBoundary?: IManagedPolicy; /** * A name for the IAM role. For valid values, see the RoleName parameter for * the CreateRole action in the IAM API Reference. * * IMPORTANT: If you specify a name, you cannot perform updates that require * replacement of this resource. You can perform updates that require no or * some interruption. If you must replace the resource, specify a new name. * * If you specify a name, you must specify the CAPABILITY_NAMED_IAM value to * acknowledge your template's capabilities. For more information, see * Acknowledging IAM Resources in AWS CloudFormation Templates. * * @default - AWS CloudFormation generates a unique physical ID and uses that ID * for the role name. */ readonly roleName?: string; /** * The maximum session duration that you want to set for the specified role. * This setting can have a value from 1 hour (3600sec) to 12 (43200sec) hours. * * Anyone who assumes the role from the AWS CLI or API can use the * DurationSeconds API parameter or the duration-seconds CLI parameter to * request a longer session. The MaxSessionDuration setting determines the * maximum duration that can be requested using the DurationSeconds * parameter. * * If users don't specify a value for the DurationSeconds parameter, their * security credentials are valid for one hour by default. This applies when * you use the AssumeRole* API operations or the assume-role* CLI operations * but does not apply when you use those operations to create a console URL. * * @link https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html * * @default Duration.hours(1) */ readonly maxSessionDuration?: Duration; /** * A description of the role. It can be up to 1000 characters long. * * @default - No description. */ readonly description?: string; } /** * Options allowing customizing the behavior of `Role.fromRoleArn`. */ export interface FromRoleArnOptions { /** * Whether the imported role can be modified by attaching policy resources to it. * * @default true */ readonly mutable?: boolean; /** * For immutable roles: add grants to resources instead of dropping them * * If this is `false` or not specified, grant permissions added to this role are ignored. * It is your own responsibility to make sure the role has the required permissions. * * If this is `true`, any grant permissions will be added to the resource instead. * * @default false */ readonly addGrantsToResources?: boolean; /** * Any policies created by this role will use this value as their ID, if specified. * Specify this if importing the same role in multiple stacks, and granting it * different permissions in at least two stacks. If this is not specified * (or if the same name is specified in more than one stack), * a CloudFormation issue will result in the policy created in whichever stack * is deployed last overwriting the policies created by the others. * * @default 'Policy' */ readonly defaultPolicyName?: string; } /** * Options for customizing IAM role creation */ export interface CustomizeRolesOptions { /** * Whether or not to synthesize the resource into the CFN template. * * Set this to `false` if you still want to create the resources _and_ * you also want to create the policy report. * * @default true */ readonly preventSynthesis?: boolean; /** * A list of precreated IAM roles to substitute for roles * that CDK is creating. * * The constructPath can be either a relative or absolute path * from the scope that `customizeRoles` is used on to the role being created. * * @example * declare const app: App; * * const stack = new Stack(app, 'MyStack'); * new iam.Role(stack, 'MyRole', { * assumedBy: new iam.AccountPrincipal('1111111111'), * }); * * iam.Role.customizeRoles(stack, { * usePrecreatedRoles: { * // absolute path * 'MyStack/MyRole': 'my-precreated-role-name', * // or relative path from `stack` * 'MyRole': 'my-precreated-role', * }, * }); * * @default - there are no precreated roles. Synthesis will fail if `preventSynthesis=true` */ readonly usePrecreatedRoles?: { [constructPath: string]: string }; } /** * Options allowing customizing the behavior of `Role.fromRoleName`. */ export interface FromRoleNameOptions extends FromRoleArnOptions { } /** * Properties for looking up an existing Role. */ export interface RoleLookupOptions extends FromRoleArnOptions { /** * The name of the role to lookup. * * If the role you want to lookup is a service role, you need to specify * the role name without the 'service-role' prefix. For example, if the role arn is * 'arn:aws:iam::123456789012:role/service-role/ExampleServiceExecutionRole', * you need to specify the role name as 'ExampleServiceExecutionRole'. */ readonly roleName: string; } /** * IAM Role * * Defines an IAM role. The role is created with an assume policy document associated with * the specified AWS service principal defined in `serviceAssumeRole`. */ export class Role extends Resource implements IRole { /** * Lookup an existing Role. */ public static fromLookup(scope: Construct, id: string, options: RoleLookupOptions): IRole { if (Token.isUnresolved(options.roleName)) { throw new Error('All arguments to look up a role must be concrete (no Tokens)'); } const response: {[key: string]: any}[] = ContextProvider.getValue(scope, { provider: cxschema.ContextProvider.CC_API_PROVIDER, props: { typeName: 'AWS::IAM::Role', exactIdentifier: options.roleName, propertiesToReturn: [ 'Arn', ], } as cxschema.CcApiContextQuery, dummyValue: [ { Arn: Stack.of(scope).formatArn({ service: 'iam', account: '123456789012', resource: 'role', resourceName: 'DUMMY_ARN', }), }, ], }).value; // getValue returns a list of result objects. We are expecting 1 result or Error. const role = response[0]; return this.fromRoleArn(scope, id, role.Arn, options); } /** * Import an external role by ARN. * * If the imported Role ARN is a Token (such as a * `CfnParameter.valueAsString` or a `Fn.importValue()`) *and* the referenced * role has a `path` (like `arn:...:role/AdminRoles/Alice`), the * `roleName` property will not resolve to the correct value. Instead it * will resolve to the first path component. We unfortunately cannot express * the correct calculation of the full path name as a CloudFormation * expression. In this scenario the Role ARN should be supplied without the * `path` in order to resolve the correct role resource. * * @param scope construct scope * @param id construct id * @param roleArn the ARN of the role to import * @param options allow customizing the behavior of the returned role */ public static fromRoleArn(scope: Construct, id: string, roleArn: string, options: FromRoleArnOptions = {}): IRole { const scopeStack = Stack.of(scope); const parsedArn = scopeStack.splitArn(roleArn, ArnFormat.SLASH_RESOURCE_NAME); const resourceName = parsedArn.resourceName!; const roleAccount = parsedArn.account; // service roles have an ARN like 'arn:aws:iam::<account>:role/service-role/<roleName>' // or 'arn:aws:iam::<account>:role/service-role/servicename.amazonaws.com/service-role/<roleName>' // we want to support these as well, so we just use the element after the last slash as role name const roleName = resourceName.split('/').pop()!; if (getCustomizeRolesConfig(scope).enabled) { return new PrecreatedRole(scope, id, { rolePath: `${scope.node.path}/${id}`, role: new ImportedRole(scope, `Import${id}`, { account: roleAccount, roleArn, roleName, ...options, }), }); } if (options.addGrantsToResources !== undefined && options.mutable !== false) { throw new Error('\'addGrantsToResources\' can only be passed if \'mutable: false\''); } const roleArnAndScopeStackAccountComparison = Token.compareStrings(roleAccount ?? '', scopeStack.account); const equalOrAnyUnresolved = roleArnAndScopeStackAccountComparison === TokenComparison.SAME || roleArnAndScopeStackAccountComparison === TokenComparison.BOTH_UNRESOLVED || roleArnAndScopeStackAccountComparison === TokenComparison.ONE_UNRESOLVED; // if we are returning an immutable role then the 'importedRole' is just a throwaway construct // so give it a different id const mutableRoleId = (options.mutable !== false && equalOrAnyUnresolved) ? id : `MutableRole${id}`; const importedRole = new ImportedRole(scope, mutableRoleId, { roleArn, roleName, account: roleAccount, ...options, }); // we only return an immutable Role if both accounts were explicitly provided, and different return options.mutable !== false && equalOrAnyUnresolved ? importedRole : new ImmutableRole(scope, id, importedRole, options.addGrantsToResources ?? false); } /** * Return whether the given object is a Role */ public static isRole(x: any) : x is Role { return x !== null && typeof(x) === 'object' && IAM_ROLE_SYMBOL in x; } /** * Import an external role by name. * * The imported role is assumed to exist in the same account as the account * the scope's containing Stack is being deployed to. * * @param scope construct scope * @param id construct id * @param roleName the name of the role to import * @param options allow customizing the behavior of the returned role */ public static fromRoleName(scope: Construct, id: string, roleName: string, options: FromRoleNameOptions = {}) { return Role.fromRoleArn(scope, id, Stack.of(scope).formatArn({ region: '', service: 'iam', resource: 'role', resourceName: roleName, }), options); } /** * Customize the creation of IAM roles within the given scope * * It is recommended that you **do not** use this method and instead allow * CDK to manage role creation. This should only be used * in environments where CDK applications are not allowed to created IAM roles. * * This can be used to prevent the CDK application from creating roles * within the given scope and instead replace the references to the roles with * precreated role names. A report will be synthesized in the cloud assembly (i.e. cdk.out) * that will contain the list of IAM roles that would have been created along with the * IAM policy statements that the role should contain. This report can then be used * to create the IAM roles outside of CDK and then the created role names can be provided * in `usePrecreatedRoles`. * * @example * declare const app: App; * iam.Role.customizeRoles(app, { * usePrecreatedRoles: { * 'ConstructPath/To/Role': 'my-precreated-role-name', * }, * }); * * @param scope construct scope to customize role creation * @param options options for configuring role creation */ public static customizeRoles(scope: Construct, options?: CustomizeRolesOptions): void { const preventSynthesis = options?.preventSynthesis ?? true; const useRoles: { [constructPath: string]: string } = {}; for (const [constructPath, roleName] of Object.entries(options?.usePrecreatedRoles ?? {})) { const absPath = constructPath.startsWith(scope.node.path) ? constructPath : `${scope.node.path}/${constructPath}`; useRoles[absPath] = roleName; } scope.node.setContext(CUSTOMIZE_ROLES_CONTEXT_KEY, { preventSynthesis, usePrecreatedRoles: useRoles, }); } public readonly grantPrincipal: IPrincipal = this; public readonly principalAccount: string | undefined = this.env.account; public readonly assumeRoleAction: string = 'sts:AssumeRole'; /** * The assume role policy document associated with this role. */ public readonly assumeRolePolicy?: PolicyDocument; /** * Returns the ARN of this role. */ public readonly roleArn: string; /** * Returns the name of the role. */ public readonly roleName: string; /** * Returns the role. */ public readonly policyFragment: PrincipalPolicyFragment; /** * Returns the permissions boundary attached to this role */ public readonly permissionsBoundary?: IManagedPolicy; private defaultPolicy?: Policy; private readonly managedPolicies: IManagedPolicy[] = []; private readonly attachedPolicies = new AttachedPolicies(); private readonly inlinePolicies: { [name: string]: PolicyDocument }; private readonly dependables = new Map<PolicyStatement, DependencyGroup>(); private immutableRole?: IRole; private _didSplit = false; private readonly _roleId?: string; private readonly _precreatedRole?: IRole; constructor(scope: Construct, id: string, props: RoleProps) { super(scope, id, { physicalName: props.roleName, }); // Enhanced CDK Analytics Telemetry addConstructMetadata(this, props); if (props.roleName && !Token.isUnresolved(props.roleName) && !/^[\w+=,.@-]{1,64}$/.test(props.roleName)) { 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.'); } const externalIds = props.externalIds || []; if (props.externalId) { externalIds.push(props.externalId); } this.assumeRolePolicy = createAssumeRolePolicy(props.assumedBy, externalIds); this.managedPolicies.push(...props.managedPolicies || []); this.inlinePolicies = props.inlinePolicies || {}; this.permissionsBoundary = props.permissionsBoundary; const maxSessionDuration = props.maxSessionDuration && props.maxSessionDuration.toSeconds(); validateMaxSessionDuration(maxSessionDuration); const description = (props.description && props.description?.length > 0) ? props.description : undefined; if (description && description.length > 1000) { throw new Error('Role description must be no longer than 1000 characters.'); } validateRolePath(props.path); const config = this.getPrecreatedRoleConfig(); const roleArn = Stack.of(scope).formatArn({ region: '', service: 'iam', resource: 'role', resourceName: config.precreatedRoleName, }); const importedRole = new ImportedRole(this, 'Import'+id, { roleArn, roleName: config.precreatedRoleName ?? id, account: Stack.of(this).account, }); this.roleName = importedRole.roleName; this.roleArn = importedRole.roleArn; if (config.enabled) { const role = new PrecreatedRole(this, 'PrecreatedRole'+id, { rolePath: this.node.path, role: importedRole, missing: !config.precreatedRoleName, assumeRolePolicy: this.assumeRolePolicy, }); this.managedPolicies.forEach(policy => role.addManagedPolicy(policy)); Object.entries(this.inlinePolicies).forEach(([name, policy]) => { role.attachInlinePolicy(new Policy(this, name, { document: policy })); }); this._precreatedRole = role; } // synthesize the resource if preventSynthesis=false if (!config.preventSynthesis) { const role = new CfnRole(this, 'Resource', { assumeRolePolicyDocument: this.assumeRolePolicy as any, managedPolicyArns: UniqueStringSet.from(() => this.managedPolicies.map(p => p.managedPolicyArn)), policies: _flatten(this.inlinePolicies), path: props.path, permissionsBoundary: this.permissionsBoundary ? this.permissionsBoundary.managedPolicyArn : undefined, roleName: this.physicalName, maxSessionDuration, description, }); this._roleId = role.attrRoleId; this.roleArn = this.getResourceArnAttribute(role.attrArn, { region: '', // IAM is global in each partition service: 'iam', resource: 'role', // Removes leading slash from path resourceName: `${props.path ? props.path.substr(props.path.charAt(0) === '/' ? 1 : 0) : ''}${this.physicalName}`, }); this.roleName = this.getResourceNameAttribute(role.ref); Aspects.of(this).add({ visit: (c) => { if (c === this) { this.splitLargePolicy(); } }, }, { priority: mutatingAspectPrio32333(this), }); } this.policyFragment = new ArnPrincipal(this.roleArn).policyFragment; function _flatten(policies?: { [name: string]: PolicyDocument }) { if (policies == null || Object.keys(policies).length === 0) { return undefined; } const result = new Array<CfnRole.PolicyProperty>(); for (const policyName of Object.keys(policies)) { const policyDocument = policies[policyName]; result.push({ policyName, policyDocument }); } return result; } this.node.addValidation({ validate: () => this.validateRole() }); } /** * Adds a permission to the role's default policy document. * If there is no default policy attached to this role, it will be created. * @param statement The permission statement to add to the policy document */ @MethodMetadata() public addToPrincipalPolicy(statement: PolicyStatement): AddToPrincipalPolicyResult { if (this._precreatedRole) { return this._precreatedRole.addToPrincipalPolicy(statement); } else { if (!this.defaultPolicy) { this.defaultPolicy = new Policy(this, 'DefaultPolicy'); this.attachInlinePolicy(this.defaultPolicy); } this.defaultPolicy.addStatements(statement); // We might split this statement off into a different policy, so we'll need to // late-bind the dependable. const policyDependable = new DependencyGroup(); this.dependables.set(statement, policyDependable); return { statementAdded: true, policyDependable }; } } @MethodMetadata() public addToPolicy(statement: PolicyStatement): boolean { if (this._precreatedRole) { return this._precreatedRole.addToPolicy(statement); } else { return this.addToPrincipalPolicy(statement).statementAdded; } } /** * Attaches a managed policy to this role. * @param policy The the managed policy to attach. */ @MethodMetadata() public addManagedPolicy(policy: IManagedPolicy) { if (this._precreatedRole) { return this._precreatedRole.addManagedPolicy(policy); } else { if (this.managedPolicies.some(mp => mp.managedPolicyArn === policy.managedPolicyArn)) { return; } this.managedPolicies.push(policy); } } /** * Attaches a policy to this role. * @param policy The policy to attach */ @MethodMetadata() public attachInlinePolicy(policy: Policy) { if (this._precreatedRole) { this._precreatedRole.attachInlinePolicy(policy); } else { this.attachedPolicies.attach(policy); policy.attachToRole(this); } } /** * Grant the actions defined in actions to the identity Principal on this resource. */ @MethodMetadata() public grant(grantee: IPrincipal, ...actions: string[]) { return Grant.addToPrincipal({ grantee, actions, resourceArns: [this.roleArn], scope: this, }); } /** * Grant permissions to the given principal to pass this role. */ @MethodMetadata() public grantPassRole(identity: IPrincipal) { return this.grant(identity, 'iam:PassRole'); } /** * Grant permissions to the given principal to assume this role. */ @MethodMetadata() public grantAssumeRole(identity: IPrincipal) { // Service and account principals must use assumeRolePolicy if (identity instanceof ServicePrincipal || identity instanceof AccountPrincipal) { throw new Error('Cannot use a service or account principal with grantAssumeRole, use assumeRolePolicy instead.'); } return this.grant(identity, 'sts:AssumeRole'); } /** * Returns the stable and unique string identifying the role. For example, * AIDAJQABLZS4A3QDU576Q. * * @attribute */ public get roleId(): string { if (!this._roleId) { throw new Error('"roleId" is not available on precreated roles'); } return this._roleId; } /** * Return a copy of this Role object whose Policies will not be updated * * Use the object returned by this method if you want this Role to be used by * a construct without it automatically updating the Role's Policies. * * If you do, you are responsible for adding the correct statements to the * Role's policies yourself. */ @MethodMetadata() public withoutPolicyUpdates(options: WithoutPolicyUpdatesOptions = {}): IRole { if (!this.immutableRole) { this.immutableRole = new ImmutableRole(Node.of(this).scope as Construct, `ImmutableRole${this.node.id}`, this, options.addGrantsToResources ?? false); } return this.immutableRole; } /** * Skip applyRemovalPolicy if role synthesis is prevented by customizeRoles. * Because in this case, this construct does not have a CfnResource in the tree. * @override * @param policy RemovalPolicy */ @MethodMetadata() public applyRemovalPolicy(policy: RemovalPolicy): void { const config = this.getPrecreatedRoleConfig(); if (!config.preventSynthesis) { super.applyRemovalPolicy(policy); } } private validateRole(): string[] { const errors = new Array<string>(); errors.push(...this.assumeRolePolicy?.validateForResourcePolicy() ?? []); for (const policy of Object.values(this.inlinePolicies)) { errors.push(...policy.validateForIdentityPolicy()); } return errors; } /** * Split large inline policies into managed policies * * This gets around the 10k bytes limit on role policies. */ private splitLargePolicy() { if (!this.defaultPolicy || this._didSplit) { return; } this._didSplit = true; const self = this; const originalDoc = this.defaultPolicy.document; const splitOffDocs = originalDoc._splitDocument(this, MAX_INLINE_SIZE, MAX_MANAGEDPOL_SIZE); // Includes the "current" document const mpCount = this.managedPolicies.length + (splitOffDocs.size - 1); if (mpCount > 20) { Annotations.of(this).addWarningV2('@aws-cdk/aws-iam:rolePolicyTooLarge', `Policy too large: ${mpCount} exceeds the maximum of 20 managed policies attached to a Role`); } else if (mpCount > 10) { Annotations.of(this).addWarningV2('@aws-cdk/aws-iam:rolePolicyLarge', `Policy large: ${mpCount} exceeds 10 managed policies attached to a Role, this requires a quota increase`); } // Create the managed policies and fix up the dependencies markDeclaringConstruct(originalDoc, this.defaultPolicy); let i = 1; for (const newDoc of splitOffDocs.keys()) { if (newDoc === originalDoc) { continue; } const mp = new ManagedPolicy(this, `OverflowPolicy${i++}`, { description: `Part of the policies for ${this.node.path}`, document: newDoc, roles: [this], }); markDeclaringConstruct(newDoc, mp); } /** * Update the Dependables for the statements in the given PolicyDocument to point to the actual declaring construct */ function markDeclaringConstruct(doc: PolicyDocument, declaringConstruct: IConstruct) { for (const original of splitOffDocs.get(doc) ?? []) { self.dependables.get(original)?.add(declaringConstruct); } } } /** * Return configuration for precreated roles */ private getPrecreatedRoleConfig(): CustomizeRoleConfig { return getPrecreatedRoleConfig(this); } } /** * A Role object */ export interface IRole extends IIdentity { /** * Returns the ARN of this role. * * @attribute */ readonly roleArn: string; /** * Returns the name of this role. * * @attribute */ readonly roleName: string; /** * Grant the actions defined in actions to the identity Principal on this resource. */ grant(grantee: IPrincipal, ...actions: string[]): Grant; /** * Grant permissions to the given principal to pass this role. */ grantPassRole(grantee: IPrincipal): Grant; /** * Grant permissions to the given principal to assume this role. */ grantAssumeRole(grantee: IPrincipal): Grant; } function createAssumeRolePolicy(principal: IPrincipal, externalIds: string[]) { const actualDoc = new PolicyDocument(); // If requested, add externalIds to every statement added to this doc const addDoc = externalIds.length === 0 ? actualDoc : new MutatingPolicyDocumentAdapter(actualDoc, (statement) => { statement.addCondition('StringEquals', { 'sts:ExternalId': externalIds.length === 1 ? externalIds[0] : externalIds, }); return statement; }); defaultAddPrincipalToAssumeRole(principal, addDoc); 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; } if (duration < 3600 || duration > 43200) { throw new Error(`maxSessionDuration is set to ${duration}, but must be >= 3600sec (1hr) and <= 43200sec (12hrs)`); } } /** * Options for the `withoutPolicyUpdates()` modifier of a Role */ export interface WithoutPolicyUpdatesOptions { /** * Add grants to resources instead of dropping them * * If this is `false` or not specified, grant permissions added to this role are ignored. * It is your own responsibility to make sure the role has the required permissions. * * If this is `true`, any grant permissions will be added to the resource instead. * * @default false */ readonly addGrantsToResources?: boolean; } Object.defineProperty(Role.prototype, IAM_ROLE_SYMBOL, { value: true, enumerable: false, writable: false, });