diff --git a/.eslintrc.json b/.eslintrc.json index b017cf4..85f17ab 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -35,11 +35,7 @@ } }, "ignorePatterns": [ - "*.js", - "*.d.ts", - "node_modules/", - "*.generated.ts", - "coverage", + "src/generated/*.ts", "!.projenrc.ts", "!projenrc/**/*.ts" ], diff --git a/.gitattributes b/.gitattributes index 4958a7b..6db0cbd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,5 +19,6 @@ /API.md linguist-generated /LICENSE linguist-generated /package.json linguist-generated +/src/generated/IamRoleProps.ts linguist-generated /tsconfig.dev.json linguist-generated /yarn.lock linguist-generated \ No newline at end of file diff --git a/.gitignore b/.gitignore index c6be6c4..0496abb 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ junit.xml tsconfig.json !/API.md !/.projenrc.ts +!/src/generated/IamRoleProps.ts diff --git a/.projen/deps.json b/.projen/deps.json index c390362..5ccfbef 100644 --- a/.projen/deps.json +++ b/.projen/deps.json @@ -1,5 +1,9 @@ { "dependencies": [ + { + "name": "@mrgrain/jsii-struct-builder", + "type": "build" + }, { "name": "@types/jest", "type": "build" @@ -98,7 +102,7 @@ }, { "name": "aws-cdk-lib", - "version": "^2.1.0", + "version": "^2.73.0", "type": "peer" }, { diff --git a/.projen/files.json b/.projen/files.json index 9d31ccb..89875e4 100644 --- a/.projen/files.json +++ b/.projen/files.json @@ -14,6 +14,7 @@ ".projen/files.json", ".projen/tasks.json", "LICENSE", + "src/generated/IamRoleProps.ts", "tsconfig.dev.json" ], "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." diff --git a/.projen/tasks.json b/.projen/tasks.json index 32cb622..723808c 100644 --- a/.projen/tasks.json +++ b/.projen/tasks.json @@ -206,7 +206,8 @@ "name": "release", "description": "Prepare a release from \"main\" branch", "env": { - "RELEASE": "true" + "RELEASE": "true", + "MAJOR": "1" }, "steps": [ { diff --git a/.projenrc.ts b/.projenrc.ts index ab9da13..c427ca5 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -1,13 +1,15 @@ +import { ProjenStruct, Struct } from "@mrgrain/jsii-struct-builder"; import { ReleasableCommits, awscdk } from "projen"; import { ProseWrap } from "projen/lib/javascript"; const project = new awscdk.AwsCdkConstructLibrary({ author: "Ben Limmer", authorAddress: "hello@benlimmer.com", - cdkVersion: "2.1.0", + cdkVersion: "2.73.0", // Released in April 2023 defaultReleaseBranch: "main", name: "@blimmer/cdk-circleci-oidc", repositoryUrl: "https://github.com/blimmer/cdk-circleci-oidc.git", + majorVersion: 1, projenrcTs: true, @@ -19,8 +21,14 @@ const project = new awscdk.AwsCdkConstructLibrary({ module: "cdk_circleci_oidc", }, + // deps: [], + devDeps: ["@mrgrain/jsii-struct-builder"], depsUpgrade: false, + eslintOptions: { + dirs: ["src"], + ignorePatterns: ["src/generated/*.ts"], // ignore generated files + }, prettier: true, prettierOptions: { settings: { @@ -28,11 +36,10 @@ const project = new awscdk.AwsCdkConstructLibrary({ proseWrap: ProseWrap.ALWAYS, }, }, - - // deps: [], /* Runtime dependencies of this module. */ - // description: undefined, /* The description is just a string that helps people understand the purpose of the package. */ - // devDeps: [], /* Build dependencies for this module. */ - // packageName: undefined, /* The "name" in package.json. */ }); +new ProjenStruct(project, { name: "RoleProps", filePath: "src/generated/IamRoleProps.ts" }).mixin( + Struct.fromFqn("aws-cdk-lib.aws_iam.RoleProps").omit("assumedBy").withoutDeprecated(), +); + project.synth(); diff --git a/API.md b/API.md index d76a1ed..4de29fb 100644 --- a/API.md +++ b/API.md @@ -1,14 +1,14 @@ # CircleCI OIDC -This repository contains constructs to communicate between CircleCI and AWS via an Open ID Connect (OIDC) provider. -The process is described in [this CircleCI blog post](https://circleci.com/blog/openid-connect-identity-tokens/). +This repository contains constructs to communicate between CircleCI and AWS via an Open ID Connect (OIDC) provider. The +process is described in [this CircleCI blog post](https://circleci.com/blog/openid-connect-identity-tokens/). ## Security Benefits By using the OpenID Connect provider, you can communicate with AWS from CircleCI without saving static credentials -(e.g., `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`) in your CircleCI project settings or a context. Removing -static credentials, especially in light of the early 2023 [breach](https://circleci.com/blog/jan-4-2023-incident-report/), -is a best practice for security. +(e.g., `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`) in your CircleCI project settings or a context. Removing static +credentials, especially in light of the early 2023 [breach](https://circleci.com/blog/jan-4-2023-incident-report/), is a +best practice for security. ## Quick Start @@ -25,46 +25,48 @@ yarn add @blimmer/cdk-circleci-oidc Then, create the provider and role(s). ```typescript -import { Stack, StackProps } from 'aws-cdk-lib'; -import { CircleCiOidcProvider, CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc'; -import { Construct } from 'constructs'; -import { ManagedPolicy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; -import { Bucket } from 'aws-cdk-lib/aws-s3'; +import { Stack, StackProps } from "aws-cdk-lib"; +import { CircleCiOidcProvider, CircleCiOidcRole } from "@blimmer/cdk-circleci-oidc"; +import { Construct } from "constructs"; +import { ManagedPolicy, PolicyStatement } from "aws-cdk-lib/aws-iam"; +import { Bucket } from "aws-cdk-lib/aws-s3"; export class CircleCiStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); - const provider = new CircleCiOidcProvider(this, 'OidcProvider', { + // The provider is only created _once per AWS account_. It might make sense to define this in a separate stack + // that defines more global resources. See below for how to use import the provider in stacks that don't define it. + const provider = new CircleCiOidcProvider(this, "OidcProvider", { // Find your organization ID in the CircleCI dashboard under "Organization Settings" - organizationId: '11111111-2222-3333-4444-555555555555', + organizationId: "11111111-2222-3333-4444-555555555555", }); - const myCircleCiRole = new CircleCiOidcRole(this, 'MyCircleCiRole', { - circleCiOidcProvider: provider, + const myCircleCiRole = new CircleCiOidcRole(this, "MyCircleCiRole", { + provider, roleName: "MyCircleCiRole", // Pass some managed policies to the role - managedPolicies: [ - ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess'), - ], - }) - - // You can also access the role from the construct. This allows adding roles and using `grant` methods after the - // construct has been created. - myCircleCiRole.role.addToPolicy(new PolicyStatement({ - actions: ['s3:ListAllMyBuckets'], - resources: ['*'], - })); - - const bucket = new Bucket(this, 'MyBucket'); - bucket.grantRead(myCircleCiRole.role); + managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName("AmazonS3ReadOnlyAccess")], + }); + + // You can work with the CircleCI role like any other role + myCircleCiRole.addToPolicy( + new PolicyStatement({ + actions: ["s3:ListAllMyBuckets"], + resources: ["*"], + }), + ); + + // Including using `.grant` convenience methods + const bucket = new Bucket(this, "MyBucket"); + bucket.grantRead(myCircleCiRole); } } ``` -Now, in your `.circleci/config.yml` file, you can use the [AWS CLI Orb](https://circleci.com/developer/orbs/orb/circleci/aws-cli) -to assume your new role. +Now, in your `.circleci/config.yml` file, you can use the +[AWS CLI Orb](https://circleci.com/developer/orbs/orb/circleci/aws-cli) to assume your new role. ```yaml version: 2.1 @@ -87,56 +89,30 @@ jobs: - checkout # https://circleci.com/developer/orbs/orb/circleci/aws-cli#commands-setup - aws-cli/setup: - role_arn: 'arn:aws:iam::123456789101:role/MyCircleCiRole' + role_arn: "arn:aws:iam::123456789101:role/MyCircleCiRole" - run: name: List S3 Buckets command: aws s3 ls ``` -## Cross Stack Usage +## Usage in Stacks that Don't Define the Provider -If you want to use the OIDC provider in another stack, you can use the `getProviderForExport` method. +The `CircleCiOidcProvider` is only created **once per account**. You can use the +`CircleCiOidcProvider.fromOrganizationId` method to import a previously created provider into any stack. ```typescript -import { Stack, StackProps } from 'aws-cdk-lib'; -import { CircleCiOidcProvider } from '@blimmer/cdk-circleci-oidc'; -import { Construct } from 'constructs'; - -export class CircleCiStack extends Stack { - readonly circleCiOidcProvider: ManualCircleCiOidcProviderProps; // export for use in other stacks +import { Stack, StackProps } from "aws-cdk-lib"; +import { CircleCiOidcRole, CircleCiOidcProvider } from "@blimmer/cdk-circleci-oidc"; +import { Construct } from "constructs"; +export class MyStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); - const provider = new CircleCiOidcProvider(this, 'OidcProvider', { - // Find your organization ID in the CircleCI dashboard under "Organization Settings" - organizationId: '11111111-2222-3333-4444-555555555555', - }); - - this.circleCiOidcProvider = provider.getProviderForExport(this.account); - } -} -``` - -```typescript -import { Stack, StackProps } from 'aws-cdk-lib'; -import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc'; -import { Construct } from 'constructs'; -import type { CircleCiStack } from './CircleCiStack'; - -interface ConsumingStackProps { - circleci: CircleCi; -} - -export class ConsumingStack extends Stack { - constructor(scope: Construct, id: string, props: ConsumingStackProps) { - super(scope, id, props); - const { circleCiOidcProvider } = props.circleci; - - const myCircleCiRole = new CircleCiOidcRole(this, 'MyCircleCiRole', { - circleCiOidcProvider, + const myCircleCiRole = new CircleCiOidcRole(this, "MyCircleCiRole", { + provider: CircleCiOidcProvider.fromOrganizationId(this, "11111111-2222-3333-4444-555555555555"), roleName: "MyCircleCiRole", - }) + }); } } ``` @@ -153,6 +129,11 @@ This package is available for Python as `cdk-circleci-oidc`. pip install cdk-circleci-oidc ``` +## Upgrading Between Major Versions + +The API can be expected to change between major versions. Please consult the [UPGRADING docs](/UPGRADING.md.md) for for +information. + ## Contributing Contributions, issues, and feedback are welcome! @@ -163,11 +144,15 @@ Contributions, issues, and feedback are welcome! ### CircleCiOidcProvider +- *Implements:* ICircleCiOidcProvider + This construct creates a CircleCI ODIC provider to allow AWS access from CircleCI jobs. You'll need to instantiate this construct once per AWS account you want to use CircleCI OIDC with. +You can import a existing provider using `CircleCiOidcProvider.fromOrganizationId`. + To create a role that can be assumed by CircleCI jobs, use the `CircleCiOidcRole` construct. #### Initializers @@ -209,7 +194,22 @@ new CircleCiOidcProvider(scope: Construct, id: string, props: CircleCiOidcProvid | **Name** | **Description** | | --- | --- | | toString | Returns a string representation of this construct. | -| getProviderForExport | *No description.* | +| overrideLogicalId | Overrides the auto-generated logical ID with a specific ID. | +| addDeletionOverride | Syntactic sugar for `addOverride(path, undefined)`. | +| addDependency | Indicates that this resource depends on another resource and cannot be provisioned unless the other resource has been successfully provisioned. | +| addDependsOn | Indicates that this resource depends on another resource and cannot be provisioned unless the other resource has been successfully provisioned. | +| addMetadata | Add a value to the CloudFormation Resource Metadata. | +| addOverride | Adds an override to the synthesized CloudFormation resource. | +| addPropertyDeletionOverride | Adds an override that deletes the value of a property from the resource definition. | +| addPropertyOverride | Adds an override to a resource property. | +| applyRemovalPolicy | Sets the deletion policy of the resource based on the removal policy specified. | +| getAtt | Returns a token for an runtime attribute of this resource. | +| getMetadata | Retrieve a value value from the CloudFormation Resource Metadata. | +| obtainDependencies | Retrieves an array of resources this resource depends on. | +| obtainResourceDependencies | Get a shallow copy of dependencies between this resource and other resources in the same stack. | +| removeDependency | Indicates that this resource no longer depends on another resource. | +| replaceDependency | Replaces one dependency with another. | +| inspect | Examines the CloudFormation resource and discloses attributes. | --- @@ -221,406 +221,1847 @@ public toString(): string Returns a string representation of this construct. -##### `getProviderForExport` +##### `overrideLogicalId` ```typescript -public getProviderForExport(accountId: string, importName?: string): ManualCircleCiOidcProviderProps +public overrideLogicalId(newLogicalId: string): void ``` -###### `accountId`Required +Overrides the auto-generated logical ID with a specific ID. + +###### `newLogicalId`Required - *Type:* string +The new logical ID to use for this stack element. + --- -###### `importName`Optional +##### `addDeletionOverride` -- *Type:* string +```typescript +public addDeletionOverride(path: string): void +``` ---- +Syntactic sugar for `addOverride(path, undefined)`. -#### Static Functions +###### `path`Required -| **Name** | **Description** | -| --- | --- | -| isConstruct | Checks if `x` is a construct. | +- *Type:* string + +The path of the value to delete. --- -##### ~~`isConstruct`~~ +##### `addDependency` ```typescript -import { CircleCiOidcProvider } from '@blimmer/cdk-circleci-oidc' - -CircleCiOidcProvider.isConstruct(x: any) +public addDependency(target: CfnResource): void ``` -Checks if `x` is a construct. +Indicates that this resource depends on another resource and cannot be provisioned unless the other resource has been successfully provisioned. -###### `x`Required +This can be used for resources across stacks (or nested stack) boundaries +and the dependency will automatically be transferred to the relevant scope. -- *Type:* any +###### `target`Required -Any object. +- *Type:* aws-cdk-lib.CfnResource --- -#### Properties +##### ~~`addDependsOn`~~ -| **Name** | **Type** | **Description** | -| --- | --- | --- | -| node | constructs.Node | The tree node. | -| organizationId | string | *No description.* | -| provider | aws-cdk-lib.aws_iam.CfnOIDCProvider | *No description.* | +```typescript +public addDependsOn(target: CfnResource): void +``` + +Indicates that this resource depends on another resource and cannot be provisioned unless the other resource has been successfully provisioned. + +###### `target`Required + +- *Type:* aws-cdk-lib.CfnResource --- -##### `node`Required +##### `addMetadata` ```typescript -public readonly node: Node; +public addMetadata(key: string, value: any): void ``` -- *Type:* constructs.Node +Add a value to the CloudFormation Resource Metadata. -The tree node. +> [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html ---- +Note that this is a different set of metadata from CDK node metadata; this +metadata ends up in the stack template under the resource, whereas CDK +node metadata ends up in the Cloud Assembly.](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html -##### `organizationId`Required +Note that this is a different set of metadata from CDK node metadata; this +metadata ends up in the stack template under the resource, whereas CDK +node metadata ends up in the Cloud Assembly.) -```typescript -public readonly organizationId: string; -``` +###### `key`Required - *Type:* string --- -##### `provider`Required - -```typescript -public readonly provider: CfnOIDCProvider; -``` +###### `value`Required -- *Type:* aws-cdk-lib.aws_iam.CfnOIDCProvider +- *Type:* any --- +##### `addOverride` -### CircleCiOidcRole +```typescript +public addOverride(path: string, value: any): void +``` -This construct creates a CircleCI ODIC provider to allow AWS access from CircleCI jobs. +Adds an override to the synthesized CloudFormation resource. -You'll need to instantiate -this construct once per AWS account you want to use CircleCI OIDC with. +To add a +property override, either use `addPropertyOverride` or prefix `path` with +"Properties." (i.e. `Properties.TopicName`). -To create a role that can be assumed by CircleCI jobs, use the `CircleCiOidcRole` construct. +If the override is nested, separate each nested level using a dot (.) in the path parameter. +If there is an array as part of the nesting, specify the index in the path. -#### Initializers +To include a literal `.` in the property name, prefix with a `\`. In most +programming languages you will need to write this as `"\\."` because the +`\` itself will need to be escaped. +For example, ```typescript -import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' - -new CircleCiOidcRole(scope: Construct, id: string, props: CircleCiOidcRoleProps) +cfnResource.addOverride('Properties.GlobalSecondaryIndexes.0.Projection.NonKeyAttributes', ['myattribute']); +cfnResource.addOverride('Properties.GlobalSecondaryIndexes.1.ProjectionType', 'INCLUDE'); +``` +would add the overrides +```json +"Properties": { + "GlobalSecondaryIndexes": [ + { + "Projection": { + "NonKeyAttributes": [ "myattribute" ] + ... + } + ... + }, + { + "ProjectionType": "INCLUDE" + ... + }, + ] + ... +} ``` -| **Name** | **Type** | **Description** | -| --- | --- | --- | -| scope | constructs.Construct | *No description.* | -| id | string | *No description.* | -| props | CircleCiOidcRoleProps | *No description.* | - ---- - -##### `scope`Required +The `value` argument to `addOverride` will not be processed or translated +in any way. Pass raw JSON values in here with the correct capitalization +for CloudFormation. If you pass CDK classes or structs, they will be +rendered with lowercased key names, and CloudFormation will reject the +template. -- *Type:* constructs.Construct +###### `path`Required ---- +- *Type:* string -##### `id`Required +The path of the property, you can use dot notation to override values in complex types. -- *Type:* string +Any intermediate keys +will be created as needed. --- -##### `props`Required - -- *Type:* CircleCiOidcRoleProps +###### `value`Required ---- +- *Type:* any -#### Methods +The value. -| **Name** | **Description** | -| --- | --- | -| toString | Returns a string representation of this construct. | +Could be primitive or complex. --- -##### `toString` +##### `addPropertyDeletionOverride` ```typescript -public toString(): string +public addPropertyDeletionOverride(propertyPath: string): void ``` -Returns a string representation of this construct. +Adds an override that deletes the value of a property from the resource definition. -#### Static Functions +###### `propertyPath`Required -| **Name** | **Description** | -| --- | --- | -| isConstruct | Checks if `x` is a construct. | +- *Type:* string + +The path to the property. --- -##### ~~`isConstruct`~~ +##### `addPropertyOverride` ```typescript -import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' - -CircleCiOidcRole.isConstruct(x: any) +public addPropertyOverride(propertyPath: string, value: any): void ``` -Checks if `x` is a construct. +Adds an override to a resource property. -###### `x`Required +Syntactic sugar for `addOverride("Properties.<...>", value)`. -- *Type:* any +###### `propertyPath`Required -Any object. +- *Type:* string + +The path of the property. --- -#### Properties +###### `value`Required -| **Name** | **Type** | **Description** | -| --- | --- | --- | -| node | constructs.Node | The tree node. | -| role | aws-cdk-lib.aws_iam.Role | *No description.* | +- *Type:* any + +The value. --- -##### `node`Required +##### `applyRemovalPolicy` ```typescript -public readonly node: Node; +public applyRemovalPolicy(policy?: RemovalPolicy, options?: RemovalPolicyOptions): void ``` -- *Type:* constructs.Node +Sets the deletion policy of the resource based on the removal policy specified. -The tree node. +The Removal Policy controls what happens to this resource when it stops +being managed by CloudFormation, either because you've removed it from the +CDK application or because you've made a change that requires the resource +to be replaced. ---- +The resource can be deleted (`RemovalPolicy.DESTROY`), or left in your AWS +account for data recovery and cleanup later (`RemovalPolicy.RETAIN`). In some +cases, a snapshot can be taken of the resource prior to deletion +(`RemovalPolicy.SNAPSHOT`). A list of resources that support this policy +can be found in the following link: -##### `role`Required +> [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html#aws-attribute-deletionpolicy-options](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html#aws-attribute-deletionpolicy-options) -```typescript -public readonly role: Role; -``` +###### `policy`Optional -- *Type:* aws-cdk-lib.aws_iam.Role +- *Type:* aws-cdk-lib.RemovalPolicy --- +###### `options`Optional -## Structs +- *Type:* aws-cdk-lib.RemovalPolicyOptions -### CircleCiOidcProviderProps +--- -#### Initializer +##### `getAtt` ```typescript -import { CircleCiOidcProviderProps } from '@blimmer/cdk-circleci-oidc' - -const circleCiOidcProviderProps: CircleCiOidcProviderProps = { ... } +public getAtt(attributeName: string, typeHint?: ResolutionTypeHint): Reference ``` -#### Properties +Returns a token for an runtime attribute of this resource. -| **Name** | **Type** | **Description** | -| --- | --- | --- | -| organizationId | string | The ID of your CircleCI organization. | -| circleCiOidcThumbprints | string[] | The OIDC thumbprints used by the provider. | +Ideally, use generated attribute accessors (e.g. `resource.arn`), but this can be used for future compatibility +in case there is no generated attribute. ---- +###### `attributeName`Required -##### `organizationId`Required +- *Type:* string -```typescript -public readonly organizationId: string; -``` +The name of the attribute. -- *Type:* string +--- -The ID of your CircleCI organization. +###### `typeHint`Optional -This is typically in a UUID format. You can find this ID in the CircleCI -dashboard UI under the "Organization Settings" tab. +- *Type:* aws-cdk-lib.ResolutionTypeHint --- -##### `circleCiOidcThumbprints`Optional +##### `getMetadata` ```typescript -public readonly circleCiOidcThumbprints: string[]; +public getMetadata(key: string): any ``` -- *Type:* string[] +Retrieve a value value from the CloudFormation Resource Metadata. -The OIDC thumbprints used by the provider. +> [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html -You should not need to provide this value unless CircleCI suddenly -rotates their OIDC thumbprints (e.g., in response to a security incident). +Note that this is a different set of metadata from CDK node metadata; this +metadata ends up in the stack template under the resource, whereas CDK +node metadata ends up in the Cloud Assembly.](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html -If you do need to generate this thumbprint, you can follow the instructions here: -https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html +Note that this is a different set of metadata from CDK node metadata; this +metadata ends up in the stack template under the resource, whereas CDK +node metadata ends up in the Cloud Assembly.) ---- +###### `key`Required -### CircleCiOidcRoleProps +- *Type:* string -#### Initializer +--- -```typescript -import { CircleCiOidcRoleProps } from '@blimmer/cdk-circleci-oidc' +##### `obtainDependencies` -const circleCiOidcRoleProps: CircleCiOidcRoleProps = { ... } +```typescript +public obtainDependencies(): Stack | CfnResource[] ``` -#### Properties - -| **Name** | **Type** | **Description** | -| --- | --- | --- | -| circleCiOidcProvider | ManualCircleCiOidcProviderProps \| CircleCiOidcProvider | *No description.* | -| circleCiProjectIds | string[] | Provide the UUID(s) of the CircleCI project(s) you want to be allowed to use this role. | -| description | string | *No description.* | -| inlinePolicies | {[ key: string ]: aws-cdk-lib.aws_iam.PolicyDocument} | *No description.* | -| managedPolicies | aws-cdk-lib.aws_iam.IManagedPolicy[] | *No description.* | -| roleName | string | You can pass an explicit role name if you'd like, since you need to reference the Role ARN within your CircleCI configuration. | +Retrieves an array of resources this resource depends on. ---- +This assembles dependencies on resources across stacks (including nested stacks) +automatically. -##### `circleCiOidcProvider`Required +##### `obtainResourceDependencies` ```typescript -public readonly circleCiOidcProvider: ManualCircleCiOidcProviderProps | CircleCiOidcProvider; +public obtainResourceDependencies(): CfnResource[] ``` -- *Type:* ManualCircleCiOidcProviderProps | CircleCiOidcProvider - ---- +Get a shallow copy of dependencies between this resource and other resources in the same stack. -##### `circleCiProjectIds`Optional +##### `removeDependency` ```typescript -public readonly circleCiProjectIds: string[]; +public removeDependency(target: CfnResource): void ``` -- *Type:* string[] -- *Default:* All CircleCI projects in the provider's organization +Indicates that this resource no longer depends on another resource. -Provide the UUID(s) of the CircleCI project(s) you want to be allowed to use this role. +This can be used for resources across stacks (including nested stacks) +and the dependency will automatically be removed from the relevant scope. -If you don't provide this -value, the role will be allowed to be assumed by any CircleCI project in your organization. You can find a -project's ID in the CircleCI dashboard UI under the "Project Settings" tab. It's usually in a UUID format. +###### `target`Required + +- *Type:* aws-cdk-lib.CfnResource --- -##### `description`Optional +##### `replaceDependency` ```typescript -public readonly description: string; +public replaceDependency(target: CfnResource, newTarget: CfnResource): void ``` -- *Type:* string +Replaces one dependency with another. + +###### `target`Required + +- *Type:* aws-cdk-lib.CfnResource + +The dependency to replace. --- -##### `inlinePolicies`Optional +###### `newTarget`Required -```typescript -public readonly inlinePolicies: {[ key: string ]: PolicyDocument}; -``` +- *Type:* aws-cdk-lib.CfnResource -- *Type:* {[ key: string ]: aws-cdk-lib.aws_iam.PolicyDocument} +The new dependency to add. --- -##### `managedPolicies`Optional +##### `inspect` ```typescript -public readonly managedPolicies: IManagedPolicy[]; +public inspect(inspector: TreeInspector): void ``` -- *Type:* aws-cdk-lib.aws_iam.IManagedPolicy[] +Examines the CloudFormation resource and discloses attributes. + +###### `inspector`Required + +- *Type:* aws-cdk-lib.TreeInspector + +tree inspector to collect and process attributes. --- -##### `roleName`Optional +#### Static Functions + +| **Name** | **Description** | +| --- | --- | +| isConstruct | Checks if `x` is a construct. | +| isCfnElement | Returns `true` if a construct is a stack element (i.e. part of the synthesized cloudformation template). | +| isCfnResource | Check whether the given construct is a CfnResource. | +| fromOrganizationId | *No description.* | + +--- + +##### ~~`isConstruct`~~ ```typescript -public readonly roleName: string; +import { CircleCiOidcProvider } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcProvider.isConstruct(x: any) ``` -- *Type:* string -- *Default:* CloudFormation will auto-generate you a role name +Checks if `x` is a construct. -You can pass an explicit role name if you'd like, since you need to reference the Role ARN within your CircleCI configuration. +###### `x`Required ---- +- *Type:* any -### ManualCircleCiOidcProviderProps +Any object. -If you're using the {@link CircleCiOidcProvider} construct, pass it instead of these manually-defined props. +--- -#### Initializer +##### `isCfnElement` ```typescript -import { ManualCircleCiOidcProviderProps } from '@blimmer/cdk-circleci-oidc' +import { CircleCiOidcProvider } from '@blimmer/cdk-circleci-oidc' -const manualCircleCiOidcProviderProps: ManualCircleCiOidcProviderProps = { ... } +CircleCiOidcProvider.isCfnElement(x: any) ``` -#### Properties +Returns `true` if a construct is a stack element (i.e. part of the synthesized cloudformation template). -| **Name** | **Type** | **Description** | -| --- | --- | --- | -| organizationId | string | The ID of your CircleCI organization. | -| provider | aws-cdk-lib.aws_iam.IOpenIdConnectProvider | The CircleCI OIDC provider. | +Uses duck-typing instead of `instanceof` to allow stack elements from different +versions of this library to be included in the same stack. + +###### `x`Required + +- *Type:* any --- -##### `organizationId`Required +##### `isCfnResource` ```typescript -public readonly organizationId: string; +import { CircleCiOidcProvider } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcProvider.isCfnResource(construct: IConstruct) ``` -- *Type:* string +Check whether the given construct is a CfnResource. -The ID of your CircleCI organization. +###### `construct`Required -This is typically in a UUID format. You can find this ID in the CircleCI -dashboard UI under the "Organization Settings" tab. +- *Type:* constructs.IConstruct --- -##### `provider`Required +##### `fromOrganizationId` ```typescript -public readonly provider: IOpenIdConnectProvider; +import { CircleCiOidcProvider } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcProvider.fromOrganizationId(scope: Construct, organizationId: string) ``` -- *Type:* aws-cdk-lib.aws_iam.IOpenIdConnectProvider +###### `scope`Required + +- *Type:* constructs.Construct + +--- + +###### `organizationId`Required + +- *Type:* string + +--- -The CircleCI OIDC provider. +#### Properties -You can either manually create it or import it. +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| node | constructs.Node | The tree node. | +| creationStack | string[] | *No description.* | +| logicalId | string | The logical ID for this CloudFormation stack element. | +| stack | aws-cdk-lib.Stack | The stack in which this element is defined. | +| ref | string | Return a string that will be resolved to a CloudFormation `{ Ref }` for this element. | +| cfnOptions | aws-cdk-lib.ICfnResourceOptions | Options for this resource, such as condition, update policy etc. | +| cfnResourceType | string | AWS resource type. | +| attrArn | string | Returns the Amazon Resource Name (ARN) for the specified `AWS::IAM::OIDCProvider` resource. | +| tags | aws-cdk-lib.TagManager | A list of tags that are attached to the specified IAM OIDC provider. | +| thumbprintList | string[] | A list of certificate thumbprints that are associated with the specified IAM OIDC provider resource object. | +| clientIdList | string[] | A list of client IDs (also known as audiences) that are associated with the specified IAM OIDC provider resource object. | +| url | string | The URL that the IAM OIDC provider resource object is associated with. | +| arn | string | *No description.* | +| organizationId | string | *No description.* | --- +##### `node`Required +```typescript +public readonly node: Node; +``` + +- *Type:* constructs.Node + +The tree node. + +--- + +##### `creationStack`Required + +```typescript +public readonly creationStack: string[]; +``` + +- *Type:* string[] + +--- + +##### `logicalId`Required + +```typescript +public readonly logicalId: string; +``` + +- *Type:* string + +The logical ID for this CloudFormation stack element. + +The logical ID of the element +is calculated from the path of the resource node in the construct tree. + +To override this value, use `overrideLogicalId(newLogicalId)`. + +--- + +##### `stack`Required + +```typescript +public readonly stack: Stack; +``` + +- *Type:* aws-cdk-lib.Stack + +The stack in which this element is defined. + +CfnElements must be defined within a stack scope (directly or indirectly). + +--- + +##### `ref`Required + +```typescript +public readonly ref: string; +``` + +- *Type:* string + +Return a string that will be resolved to a CloudFormation `{ Ref }` for this element. + +If, by any chance, the intrinsic reference of a resource is not a string, you could +coerce it to an IResolvable through `Lazy.any({ produce: resource.ref })`. + +--- + +##### `cfnOptions`Required + +```typescript +public readonly cfnOptions: ICfnResourceOptions; +``` + +- *Type:* aws-cdk-lib.ICfnResourceOptions + +Options for this resource, such as condition, update policy etc. + +--- + +##### `cfnResourceType`Required + +```typescript +public readonly cfnResourceType: string; +``` + +- *Type:* string + +AWS resource type. + +--- + +##### `attrArn`Required + +```typescript +public readonly attrArn: string; +``` + +- *Type:* string + +Returns the Amazon Resource Name (ARN) for the specified `AWS::IAM::OIDCProvider` resource. + +--- + +##### `tags`Required + +```typescript +public readonly tags: TagManager; +``` + +- *Type:* aws-cdk-lib.TagManager + +A list of tags that are attached to the specified IAM OIDC provider. + +The returned list of tags is sorted by tag key. For more information about tagging, see [Tagging IAM resources](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_tags.html) in the *IAM User Guide* . + +> [http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-oidcprovider.html#cfn-iam-oidcprovider-tags](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-oidcprovider.html#cfn-iam-oidcprovider-tags) + +--- + +##### `thumbprintList`Required + +```typescript +public readonly thumbprintList: string[]; +``` + +- *Type:* string[] + +A list of certificate thumbprints that are associated with the specified IAM OIDC provider resource object. + +For more information, see [CreateOpenIDConnectProvider](https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateOpenIDConnectProvider.html) . + +> [http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-oidcprovider.html#cfn-iam-oidcprovider-thumbprintlist](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-oidcprovider.html#cfn-iam-oidcprovider-thumbprintlist) + +--- + +##### `clientIdList`Optional + +```typescript +public readonly clientIdList: string[]; +``` + +- *Type:* string[] + +A list of client IDs (also known as audiences) that are associated with the specified IAM OIDC provider resource object. + +For more information, see [CreateOpenIDConnectProvider](https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateOpenIDConnectProvider.html) . + +> [http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-oidcprovider.html#cfn-iam-oidcprovider-clientidlist](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-oidcprovider.html#cfn-iam-oidcprovider-clientidlist) + +--- + +##### `url`Optional + +```typescript +public readonly url: string; +``` + +- *Type:* string + +The URL that the IAM OIDC provider resource object is associated with. + +For more information, see [CreateOpenIDConnectProvider](https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateOpenIDConnectProvider.html) . + +> [http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-oidcprovider.html#cfn-iam-oidcprovider-url](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-oidcprovider.html#cfn-iam-oidcprovider-url) + +--- + +##### `arn`Required + +```typescript +public readonly arn: string; +``` + +- *Type:* string + +--- + +##### `organizationId`Required + +```typescript +public readonly organizationId: string; +``` + +- *Type:* string + +--- + +#### Constants + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| CFN_RESOURCE_TYPE_NAME | string | The CloudFormation resource type name for this resource class. | + +--- + +##### `CFN_RESOURCE_TYPE_NAME`Required + +```typescript +public readonly CFN_RESOURCE_TYPE_NAME: string; +``` + +- *Type:* string + +The CloudFormation resource type name for this resource class. + +--- + +### CircleCiOidcRole + +Define an IAM Role that can be assumed by a CircleCI Job via the CircleCI OpenID Connect Identity Provider. + +#### Initializers + +```typescript +import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' + +new CircleCiOidcRole(scope: Construct, id: string, props: CircleCiOidcRoleProps) +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| scope | constructs.Construct | *No description.* | +| id | string | *No description.* | +| props | CircleCiOidcRoleProps | *No description.* | + +--- + +##### `scope`Required + +- *Type:* constructs.Construct + +--- + +##### `id`Required + +- *Type:* string + +--- + +##### `props`Required + +- *Type:* CircleCiOidcRoleProps + +--- + +#### Methods + +| **Name** | **Description** | +| --- | --- | +| toString | Returns a string representation of this construct. | +| applyRemovalPolicy | Apply the given removal policy to this resource. | +| addManagedPolicy | Attaches a managed policy to this role. | +| addToPolicy | Add to the policy of this principal. | +| addToPrincipalPolicy | Adds a permission to the role's default policy document. | +| attachInlinePolicy | Attaches a policy to this role. | +| grant | Grant the actions defined in actions to the identity Principal on this resource. | +| grantAssumeRole | Grant permissions to the given principal to assume this role. | +| grantPassRole | Grant permissions to the given principal to pass this role. | +| withoutPolicyUpdates | Return a copy of this Role object whose Policies will not be updated. | + +--- + +##### `toString` + +```typescript +public toString(): string +``` + +Returns a string representation of this construct. + +##### `applyRemovalPolicy` + +```typescript +public applyRemovalPolicy(policy: RemovalPolicy): void +``` + +Apply the given removal policy to this resource. + +The Removal Policy controls what happens to this resource when it stops +being managed by CloudFormation, either because you've removed it from the +CDK application or because you've made a change that requires the resource +to be replaced. + +The resource can be deleted (`RemovalPolicy.DESTROY`), or left in your AWS +account for data recovery and cleanup later (`RemovalPolicy.RETAIN`). + +###### `policy`Required + +- *Type:* aws-cdk-lib.RemovalPolicy + +--- + +##### `addManagedPolicy` + +```typescript +public addManagedPolicy(policy: IManagedPolicy): void +``` + +Attaches a managed policy to this role. + +###### `policy`Required + +- *Type:* aws-cdk-lib.aws_iam.IManagedPolicy + +The the managed policy to attach. + +--- + +##### `addToPolicy` + +```typescript +public addToPolicy(statement: PolicyStatement): boolean +``` + +Add to the policy of this principal. + +###### `statement`Required + +- *Type:* aws-cdk-lib.aws_iam.PolicyStatement + +--- + +##### `addToPrincipalPolicy` + +```typescript +public addToPrincipalPolicy(statement: PolicyStatement): AddToPrincipalPolicyResult +``` + +Adds a permission to the role's default policy document. + +If there is no default policy attached to this role, it will be created. + +###### `statement`Required + +- *Type:* aws-cdk-lib.aws_iam.PolicyStatement + +The permission statement to add to the policy document. + +--- + +##### `attachInlinePolicy` + +```typescript +public attachInlinePolicy(policy: Policy): void +``` + +Attaches a policy to this role. + +###### `policy`Required + +- *Type:* aws-cdk-lib.aws_iam.Policy + +The policy to attach. + +--- + +##### `grant` + +```typescript +public grant(grantee: IPrincipal, actions: string): Grant +``` + +Grant the actions defined in actions to the identity Principal on this resource. + +###### `grantee`Required + +- *Type:* aws-cdk-lib.aws_iam.IPrincipal + +--- + +###### `actions`Required + +- *Type:* string + +--- + +##### `grantAssumeRole` + +```typescript +public grantAssumeRole(identity: IPrincipal): Grant +``` + +Grant permissions to the given principal to assume this role. + +###### `identity`Required + +- *Type:* aws-cdk-lib.aws_iam.IPrincipal + +--- + +##### `grantPassRole` + +```typescript +public grantPassRole(identity: IPrincipal): Grant +``` + +Grant permissions to the given principal to pass this role. + +###### `identity`Required + +- *Type:* aws-cdk-lib.aws_iam.IPrincipal + +--- + +##### `withoutPolicyUpdates` + +```typescript +public withoutPolicyUpdates(options?: WithoutPolicyUpdatesOptions): IRole +``` + +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. + +###### `options`Optional + +- *Type:* aws-cdk-lib.aws_iam.WithoutPolicyUpdatesOptions + +--- + +#### Static Functions + +| **Name** | **Description** | +| --- | --- | +| isConstruct | Checks if `x` is a construct. | +| isOwnedResource | Returns true if the construct was created by CDK, and false otherwise. | +| isResource | Check whether the given construct is a Resource. | +| customizeRoles | Customize the creation of IAM roles within the given scope. | +| fromRoleArn | Import an external role by ARN. | +| fromRoleName | Import an external role by name. | +| isRole | Return whether the given object is a Role. | + +--- + +##### ~~`isConstruct`~~ + +```typescript +import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcRole.isConstruct(x: any) +``` + +Checks if `x` is a construct. + +###### `x`Required + +- *Type:* any + +Any object. + +--- + +##### `isOwnedResource` + +```typescript +import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcRole.isOwnedResource(construct: IConstruct) +``` + +Returns true if the construct was created by CDK, and false otherwise. + +###### `construct`Required + +- *Type:* constructs.IConstruct + +--- + +##### `isResource` + +```typescript +import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcRole.isResource(construct: IConstruct) +``` + +Check whether the given construct is a Resource. + +###### `construct`Required + +- *Type:* constructs.IConstruct + +--- + +##### `customizeRoles` + +```typescript +import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcRole.customizeRoles(scope: Construct, options?: CustomizeRolesOptions) +``` + +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* + +```typescript +declare const app: App; +Role.customizeRoles(app, { + usePrecreatedRoles: { + 'ConstructPath/To/Role': 'my-precreated-role-name', + }, +}); +``` + + +###### `scope`Required + +- *Type:* constructs.Construct + +construct scope to customize role creation. + +--- + +###### `options`Optional + +- *Type:* aws-cdk-lib.aws_iam.CustomizeRolesOptions + +options for configuring role creation. + +--- + +##### `fromRoleArn` + +```typescript +import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcRole.fromRoleArn(scope: Construct, id: string, roleArn: string, options?: FromRoleArnOptions) +``` + +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. + +###### `scope`Required + +- *Type:* constructs.Construct + +construct scope. + +--- + +###### `id`Required + +- *Type:* string + +construct id. + +--- + +###### `roleArn`Required + +- *Type:* string + +the ARN of the role to import. + +--- + +###### `options`Optional + +- *Type:* aws-cdk-lib.aws_iam.FromRoleArnOptions + +allow customizing the behavior of the returned role. + +--- + +##### `fromRoleName` + +```typescript +import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcRole.fromRoleName(scope: Construct, id: string, roleName: string, options?: FromRoleNameOptions) +``` + +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. + +###### `scope`Required + +- *Type:* constructs.Construct + +construct scope. + +--- + +###### `id`Required + +- *Type:* string + +construct id. + +--- + +###### `roleName`Required + +- *Type:* string + +the name of the role to import. + +--- + +###### `options`Optional + +- *Type:* aws-cdk-lib.aws_iam.FromRoleNameOptions + +allow customizing the behavior of the returned role. + +--- + +##### `isRole` + +```typescript +import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcRole.isRole(x: any) +``` + +Return whether the given object is a Role. + +###### `x`Required + +- *Type:* any + +--- + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| node | constructs.Node | The tree node. | +| env | aws-cdk-lib.ResourceEnvironment | The environment this resource belongs to. | +| stack | aws-cdk-lib.Stack | The stack in which this resource is defined. | +| assumeRoleAction | string | When this Principal is used in an AssumeRole policy, the action to use. | +| grantPrincipal | aws-cdk-lib.aws_iam.IPrincipal | The principal to grant permissions to. | +| policyFragment | aws-cdk-lib.aws_iam.PrincipalPolicyFragment | Returns the role. | +| roleArn | string | Returns the ARN of this role. | +| roleId | string | Returns the stable and unique string identifying the role. | +| roleName | string | Returns the name of the role. | +| assumeRolePolicy | aws-cdk-lib.aws_iam.PolicyDocument | The assume role policy document associated with this role. | +| permissionsBoundary | aws-cdk-lib.aws_iam.IManagedPolicy | Returns the permissions boundary attached to this role. | +| principalAccount | string | The AWS account ID of this principal. | + +--- + +##### `node`Required + +```typescript +public readonly node: Node; +``` + +- *Type:* constructs.Node + +The tree node. + +--- + +##### `env`Required + +```typescript +public readonly env: ResourceEnvironment; +``` + +- *Type:* aws-cdk-lib.ResourceEnvironment + +The environment this resource belongs to. + +For resources that are created and managed by the CDK +(generally, those created by creating new class instances like Role, Bucket, etc.), +this is always the same as the environment of the stack they belong to; +however, for imported resources +(those obtained from static methods like fromRoleArn, fromBucketName, etc.), +that might be different than the stack they were imported into. + +--- + +##### `stack`Required + +```typescript +public readonly stack: Stack; +``` + +- *Type:* aws-cdk-lib.Stack + +The stack in which this resource is defined. + +--- + +##### `assumeRoleAction`Required + +```typescript +public readonly assumeRoleAction: string; +``` + +- *Type:* string + +When this Principal is used in an AssumeRole policy, the action to use. + +--- + +##### `grantPrincipal`Required + +```typescript +public readonly grantPrincipal: IPrincipal; +``` + +- *Type:* aws-cdk-lib.aws_iam.IPrincipal + +The principal to grant permissions to. + +--- + +##### `policyFragment`Required + +```typescript +public readonly policyFragment: PrincipalPolicyFragment; +``` + +- *Type:* aws-cdk-lib.aws_iam.PrincipalPolicyFragment + +Returns the role. + +--- + +##### `roleArn`Required + +```typescript +public readonly roleArn: string; +``` + +- *Type:* string + +Returns the ARN of this role. + +--- + +##### `roleId`Required + +```typescript +public readonly roleId: string; +``` + +- *Type:* string + +Returns the stable and unique string identifying the role. + +For example, +AIDAJQABLZS4A3QDU576Q. + +--- + +##### `roleName`Required + +```typescript +public readonly roleName: string; +``` + +- *Type:* string + +Returns the name of the role. + +--- + +##### `assumeRolePolicy`Optional + +```typescript +public readonly assumeRolePolicy: PolicyDocument; +``` + +- *Type:* aws-cdk-lib.aws_iam.PolicyDocument + +The assume role policy document associated with this role. + +--- + +##### `permissionsBoundary`Optional + +```typescript +public readonly permissionsBoundary: IManagedPolicy; +``` + +- *Type:* aws-cdk-lib.aws_iam.IManagedPolicy + +Returns the permissions boundary attached to this role. + +--- + +##### `principalAccount`Optional + +```typescript +public readonly principalAccount: string; +``` + +- *Type:* string + +The AWS account ID of this principal. + +Can be undefined when the account is not known +(for example, for service principals). +Can be a Token - in that case, +it's assumed to be AWS::AccountId. + +--- + + +## Structs + +### CircleCiConfiguration + +#### Initializer + +```typescript +import { CircleCiConfiguration } from '@blimmer/cdk-circleci-oidc' + +const circleCiConfiguration: CircleCiConfiguration = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| provider | ICircleCiOidcProvider | Reference to CircleCI OpenID Connect Provider configured in AWS IAM. | +| projectIds | string[] | Provide the UUID(s) of the CircleCI project(s) you want to be allowed to use this role. | + +--- + +##### `provider`Required + +```typescript +public readonly provider: ICircleCiOidcProvider; +``` + +- *Type:* ICircleCiOidcProvider + +Reference to CircleCI OpenID Connect Provider configured in AWS IAM. + +Either pass an construct defined by `new CircleCiOidcProvider` +or a retrieved reference from `CircleCiOidcProvider.fromOrganizationId`. +There can be only one (per AWS Account). + +--- + +##### `projectIds`Optional + +```typescript +public readonly projectIds: string[]; +``` + +- *Type:* string[] +- *Default:* All CircleCI projects in the provider's organization + +Provide the UUID(s) of the CircleCI project(s) you want to be allowed to use this role. + +If you don't provide this +value, the role will be allowed to be assumed by any CircleCI project in your organization. You can find a +project's ID in the CircleCI dashboard UI under the "Project Settings" tab. It's usually in a UUID format. + +--- + +### CircleCiOidcProviderProps + +#### Initializer + +```typescript +import { CircleCiOidcProviderProps } from '@blimmer/cdk-circleci-oidc' + +const circleCiOidcProviderProps: CircleCiOidcProviderProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| organizationId | string | The ID of your CircleCI organization. | +| thumbprints | string[] | The OIDC thumbprints used by the provider. | + +--- + +##### `organizationId`Required + +```typescript +public readonly organizationId: string; +``` + +- *Type:* string + +The ID of your CircleCI organization. + +This is typically in a UUID format. You can find this ID in the CircleCI +dashboard UI under the "Organization Settings" tab. + +--- + +##### `thumbprints`Optional + +```typescript +public readonly thumbprints: string[]; +``` + +- *Type:* string[] + +The OIDC thumbprints used by the provider. + +You should not need to provide this value unless CircleCI suddenly +rotates their OIDC thumbprints (e.g., in response to a security incident). + +If you do need to generate this thumbprint, you can follow the instructions here: +https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html + +--- + +### CircleCiOidcRoleProps + +Props that define the IAM Role that can be assumed by a CircleCI job via the CircleCI OpenID Connect Identity Provider. + +Besides {@link CircleCiConfiguration}, you may pass in any {@link RoleProps} except `assumedBy` +which will be defined by this construct. + +#### Initializer + +```typescript +import { CircleCiOidcRoleProps } from '@blimmer/cdk-circleci-oidc' + +const circleCiOidcRoleProps: CircleCiOidcRoleProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| provider | ICircleCiOidcProvider | Reference to CircleCI OpenID Connect Provider configured in AWS IAM. | +| projectIds | string[] | Provide the UUID(s) of the CircleCI project(s) you want to be allowed to use this role. | +| description | string | A description of the role. | +| externalIds | string[] | List of IDs that the role assumer needs to provide one of when assuming this role. | +| inlinePolicies | {[ key: string ]: aws-cdk-lib.aws_iam.PolicyDocument} | A list of named policies to inline into this role. | +| managedPolicies | aws-cdk-lib.aws_iam.IManagedPolicy[] | A list of managed policies associated with this role. | +| maxSessionDuration | aws-cdk-lib.Duration | The maximum session duration that you want to set for the specified role. | +| path | string | The path associated with this role. | +| permissionsBoundary | aws-cdk-lib.aws_iam.IManagedPolicy | AWS supports permissions boundaries for IAM entities (users or roles). | +| roleName | string | A name for the IAM role. | + +--- + +##### `provider`Required + +```typescript +public readonly provider: ICircleCiOidcProvider; +``` + +- *Type:* ICircleCiOidcProvider + +Reference to CircleCI OpenID Connect Provider configured in AWS IAM. + +Either pass an construct defined by `new CircleCiOidcProvider` +or a retrieved reference from `CircleCiOidcProvider.fromOrganizationId`. +There can be only one (per AWS Account). + +--- + +##### `projectIds`Optional + +```typescript +public readonly projectIds: string[]; +``` + +- *Type:* string[] +- *Default:* All CircleCI projects in the provider's organization + +Provide the UUID(s) of the CircleCI project(s) you want to be allowed to use this role. + +If you don't provide this +value, the role will be allowed to be assumed by any CircleCI project in your organization. You can find a +project's ID in the CircleCI dashboard UI under the "Project Settings" tab. It's usually in a UUID format. + +--- + +##### `description`Optional + +```typescript +public readonly description: string; +``` + +- *Type:* string +- *Default:* No description. + +A description of the role. + +It can be up to 1000 characters long. + +--- + +##### `externalIds`Optional + +```typescript +public readonly externalIds: string[]; +``` + +- *Type:* string[] +- *Default:* No external ID required + +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. + +--- + +##### `inlinePolicies`Optional + +```typescript +public readonly inlinePolicies: {[ key: string ]: PolicyDocument}; +``` + +- *Type:* {[ key: string ]: aws-cdk-lib.aws_iam.PolicyDocument} +- *Default:* No policy is inlined in the Role resource. + +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). + +--- + +##### `managedPolicies`Optional + +```typescript +public readonly managedPolicies: IManagedPolicy[]; +``` + +- *Type:* aws-cdk-lib.aws_iam.IManagedPolicy[] +- *Default:* No managed policies. + +A list of managed policies associated with this role. + +You can add managed policies later using +`addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName(policyName))`. + +--- + +##### `maxSessionDuration`Optional + +```typescript +public readonly maxSessionDuration: Duration; +``` + +- *Type:* aws-cdk-lib.Duration +- *Default:* Duration.hours(1) + +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. + +> [https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html) + +--- + +##### `path`Optional + +```typescript +public readonly path: string; +``` + +- *Type:* string +- *Default:* / + +The path associated with this role. + +For information about IAM paths, see +Friendly Names and Paths in IAM User Guide. + +--- + +##### `permissionsBoundary`Optional + +```typescript +public readonly permissionsBoundary: IManagedPolicy; +``` + +- *Type:* aws-cdk-lib.aws_iam.IManagedPolicy +- *Default:* No permissions boundary. + +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. + +> [https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) + +--- + +##### `roleName`Optional + +```typescript +public readonly roleName: string; +``` + +- *Type:* string +- *Default:* AWS CloudFormation generates a unique physical ID and uses that ID for the role name. + +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. + +--- + +### RoleProps + +RoleProps. + +#### Initializer + +```typescript +import { RoleProps } from '@blimmer/cdk-circleci-oidc' + +const roleProps: RoleProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| description | string | A description of the role. | +| externalIds | string[] | List of IDs that the role assumer needs to provide one of when assuming this role. | +| inlinePolicies | {[ key: string ]: aws-cdk-lib.aws_iam.PolicyDocument} | A list of named policies to inline into this role. | +| managedPolicies | aws-cdk-lib.aws_iam.IManagedPolicy[] | A list of managed policies associated with this role. | +| maxSessionDuration | aws-cdk-lib.Duration | The maximum session duration that you want to set for the specified role. | +| path | string | The path associated with this role. | +| permissionsBoundary | aws-cdk-lib.aws_iam.IManagedPolicy | AWS supports permissions boundaries for IAM entities (users or roles). | +| roleName | string | A name for the IAM role. | + +--- + +##### `description`Optional + +```typescript +public readonly description: string; +``` + +- *Type:* string +- *Default:* No description. + +A description of the role. + +It can be up to 1000 characters long. + +--- + +##### `externalIds`Optional + +```typescript +public readonly externalIds: string[]; +``` + +- *Type:* string[] +- *Default:* No external ID required + +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. + +--- + +##### `inlinePolicies`Optional + +```typescript +public readonly inlinePolicies: {[ key: string ]: PolicyDocument}; +``` + +- *Type:* {[ key: string ]: aws-cdk-lib.aws_iam.PolicyDocument} +- *Default:* No policy is inlined in the Role resource. + +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). + +--- + +##### `managedPolicies`Optional + +```typescript +public readonly managedPolicies: IManagedPolicy[]; +``` + +- *Type:* aws-cdk-lib.aws_iam.IManagedPolicy[] +- *Default:* No managed policies. + +A list of managed policies associated with this role. + +You can add managed policies later using +`addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName(policyName))`. + +--- + +##### `maxSessionDuration`Optional + +```typescript +public readonly maxSessionDuration: Duration; +``` + +- *Type:* aws-cdk-lib.Duration +- *Default:* Duration.hours(1) + +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. + +> [https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html) + +--- + +##### `path`Optional + +```typescript +public readonly path: string; +``` + +- *Type:* string +- *Default:* / + +The path associated with this role. + +For information about IAM paths, see +Friendly Names and Paths in IAM User Guide. + +--- + +##### `permissionsBoundary`Optional + +```typescript +public readonly permissionsBoundary: IManagedPolicy; +``` + +- *Type:* aws-cdk-lib.aws_iam.IManagedPolicy +- *Default:* No permissions boundary. + +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. + +> [https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) + +--- + +##### `roleName`Optional + +```typescript +public readonly roleName: string; +``` + +- *Type:* string +- *Default:* AWS CloudFormation generates a unique physical ID and uses that ID for the role name. + +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. + +--- + + +## Protocols + +### ICircleCiOidcProvider + +- *Implemented By:* CircleCiOidcProvider, ICircleCiOidcProvider + +Describes a CircleCI OpenID Connect Identity Provider for AWS IAM. + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| arn | string | *No description.* | +| organizationId | string | *No description.* | + +--- + +##### `arn`Required + +```typescript +public readonly arn: string; +``` + +- *Type:* string + +--- + +##### `organizationId`Required + +```typescript +public readonly organizationId: string; +``` + +- *Type:* string + +--- diff --git a/README.md b/README.md index 64edac4..80f75d0 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # CircleCI OIDC -This repository contains constructs to communicate between CircleCI and AWS via an Open ID Connect (OIDC) provider. -The process is described in [this CircleCI blog post](https://circleci.com/blog/openid-connect-identity-tokens/). +This repository contains constructs to communicate between CircleCI and AWS via an Open ID Connect (OIDC) provider. The +process is described in [this CircleCI blog post](https://circleci.com/blog/openid-connect-identity-tokens/). ## Security Benefits By using the OpenID Connect provider, you can communicate with AWS from CircleCI without saving static credentials -(e.g., `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`) in your CircleCI project settings or a context. Removing -static credentials, especially in light of the early 2023 [breach](https://circleci.com/blog/jan-4-2023-incident-report/), -is a best practice for security. +(e.g., `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`) in your CircleCI project settings or a context. Removing static +credentials, especially in light of the early 2023 [breach](https://circleci.com/blog/jan-4-2023-incident-report/), is a +best practice for security. ## Quick Start @@ -25,46 +25,48 @@ yarn add @blimmer/cdk-circleci-oidc Then, create the provider and role(s). ```typescript -import { Stack, StackProps } from 'aws-cdk-lib'; -import { CircleCiOidcProvider, CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc'; -import { Construct } from 'constructs'; -import { ManagedPolicy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; -import { Bucket } from 'aws-cdk-lib/aws-s3'; +import { Stack, StackProps } from "aws-cdk-lib"; +import { CircleCiOidcProvider, CircleCiOidcRole } from "@blimmer/cdk-circleci-oidc"; +import { Construct } from "constructs"; +import { ManagedPolicy, PolicyStatement } from "aws-cdk-lib/aws-iam"; +import { Bucket } from "aws-cdk-lib/aws-s3"; export class CircleCiStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); - const provider = new CircleCiOidcProvider(this, 'OidcProvider', { + // The provider is only created _once per AWS account_. It might make sense to define this in a separate stack + // that defines more global resources. See below for how to use import the provider in stacks that don't define it. + const provider = new CircleCiOidcProvider(this, "OidcProvider", { // Find your organization ID in the CircleCI dashboard under "Organization Settings" - organizationId: '11111111-2222-3333-4444-555555555555', + organizationId: "11111111-2222-3333-4444-555555555555", }); - const myCircleCiRole = new CircleCiOidcRole(this, 'MyCircleCiRole', { - circleCiOidcProvider: provider, + const myCircleCiRole = new CircleCiOidcRole(this, "MyCircleCiRole", { + provider, roleName: "MyCircleCiRole", // Pass some managed policies to the role - managedPolicies: [ - ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess'), - ], - }) - - // You can also access the role from the construct. This allows adding roles and using `grant` methods after the - // construct has been created. - myCircleCiRole.role.addToPolicy(new PolicyStatement({ - actions: ['s3:ListAllMyBuckets'], - resources: ['*'], - })); - - const bucket = new Bucket(this, 'MyBucket'); - bucket.grantRead(myCircleCiRole.role); + managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName("AmazonS3ReadOnlyAccess")], + }); + + // You can work with the CircleCI role like any other role + myCircleCiRole.addToPolicy( + new PolicyStatement({ + actions: ["s3:ListAllMyBuckets"], + resources: ["*"], + }), + ); + + // Including using `.grant` convenience methods + const bucket = new Bucket(this, "MyBucket"); + bucket.grantRead(myCircleCiRole); } } ``` -Now, in your `.circleci/config.yml` file, you can use the [AWS CLI Orb](https://circleci.com/developer/orbs/orb/circleci/aws-cli) -to assume your new role. +Now, in your `.circleci/config.yml` file, you can use the +[AWS CLI Orb](https://circleci.com/developer/orbs/orb/circleci/aws-cli) to assume your new role. ```yaml version: 2.1 @@ -87,56 +89,30 @@ jobs: - checkout # https://circleci.com/developer/orbs/orb/circleci/aws-cli#commands-setup - aws-cli/setup: - role_arn: 'arn:aws:iam::123456789101:role/MyCircleCiRole' + role_arn: "arn:aws:iam::123456789101:role/MyCircleCiRole" - run: name: List S3 Buckets command: aws s3 ls ``` -## Cross Stack Usage +## Usage in Stacks that Don't Define the Provider -If you want to use the OIDC provider in another stack, you can use the `getProviderForExport` method. +The `CircleCiOidcProvider` is only created **once per account**. You can use the +`CircleCiOidcProvider.fromOrganizationId` method to import a previously created provider into any stack. ```typescript -import { Stack, StackProps } from 'aws-cdk-lib'; -import { CircleCiOidcProvider } from '@blimmer/cdk-circleci-oidc'; -import { Construct } from 'constructs'; - -export class CircleCiStack extends Stack { - readonly circleCiOidcProvider: ManualCircleCiOidcProviderProps; // export for use in other stacks +import { Stack, StackProps } from "aws-cdk-lib"; +import { CircleCiOidcRole, CircleCiOidcProvider } from "@blimmer/cdk-circleci-oidc"; +import { Construct } from "constructs"; +export class MyStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); - const provider = new CircleCiOidcProvider(this, 'OidcProvider', { - // Find your organization ID in the CircleCI dashboard under "Organization Settings" - organizationId: '11111111-2222-3333-4444-555555555555', - }); - - this.circleCiOidcProvider = provider.getProviderForExport(this.account); - } -} -``` - -```typescript -import { Stack, StackProps } from 'aws-cdk-lib'; -import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc'; -import { Construct } from 'constructs'; -import type { CircleCiStack } from './CircleCiStack'; - -interface ConsumingStackProps { - circleci: CircleCi; -} - -export class ConsumingStack extends Stack { - constructor(scope: Construct, id: string, props: ConsumingStackProps) { - super(scope, id, props); - const { circleCiOidcProvider } = props.circleci; - - const myCircleCiRole = new CircleCiOidcRole(this, 'MyCircleCiRole', { - circleCiOidcProvider, + const myCircleCiRole = new CircleCiOidcRole(this, "MyCircleCiRole", { + provider: CircleCiOidcProvider.fromOrganizationId(this, "11111111-2222-3333-4444-555555555555"), roleName: "MyCircleCiRole", - }) + }); } } ``` @@ -153,6 +129,11 @@ This package is available for Python as `cdk-circleci-oidc`. pip install cdk-circleci-oidc ``` +## Upgrading Between Major Versions + +The API can be expected to change between major versions. Please consult the [UPGRADING docs](/UPGRADING.md.md) for for +information. + ## Contributing Contributions, issues, and feedback are welcome! diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 0000000..b8b471a --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,186 @@ +# Upgrading + +## 0.x to 1.x + +The API underwent breaking changes between the 0.x and 1.x releases. + +### Minimum CDK Version + +This construct is only tested with CDK 2.73.0 and later. You should upgrade to at least this version before upgrading to +1.x. + +### Simpler Import of Existing Providers + +Previously, using an existing `CircleCiOidcProvider` was confusing and complicated. Now, you can very easily import an +existing provider (e.g., one that's created in another stack or repo) by using the +`CircleCiOidcProvider.fromOrganizationId` static method. + +Before: + +```typescript +const provider: ManualCircleCiOidcProviderProps = { + provider: OpenIdConnectProvider.fromOpenIdConnectProviderArn( + this, + "CircleCiOidcProviderImport", + "arn:aws:iam::12345678910:oidc-provider/oidc.circleci.com/org/123e4567-e89b-12d3-a456-426614174000", + ), + organizationId: "123e4567-e89b-12d3-a456-426614174000", +}; + +const role = new CircleCiOidcRole(this, "CircleCiOidcRole", { + circleCiOidcProvider: provider, +}); +``` + +After: + +```typescript +const provider = CircleCiOidcProvider.fromOrganizationId(this, "123e4567-e89b-12d3-a456-426614174000"); +const role = new CircleCiOidcRole(this, "CircleCiOidcRole", { + provider, +}); +``` + +Much better! + +### Constructor Property Changes + +#### `CircleCiOidcProvider` + +The property `circleCiOidcThumbprints` has been renamed to `thumbprints` for brevity. + +#### `CircleCiOidcRole` + +The properties `circleCiOidcProvider` and `circleCiProjectIds` have been renamed to `provider` and `projectIds` for +brevity. + +Before: + +```typescript +const role = new CircleCiOidcRole(this, "CircleCiOidcRole", { + circleCiOidcProvider: provider, + circleCiProjectIds: ["b4f04e57-c8b2-4d80-9526-dc9b1b7a63ad"], +}); +``` + +After: + +```typescript +const role = new CircleCiOidcRole(this, "CircleCiOidcRole", { + provider, + projectIds: ["b4f04e57-c8b2-4d80-9526-dc9b1b7a63ad"], +}); +``` + +### Removal of Outer Constructs + +`CircleCiOidcProvider` and `CircleCiOidcRole` now _extend_ `CfnOIDCProvider` and `Role`, respectively. This makes them +simpler to work with and more idiomatic to the CDK. + +Before: + +```typescript +const role = new CircleCiOidcRole(this, "CircleCiOidcRole", { + circleCiOidcProvider: provider, + circleCiProjectIds: ["b4f04e57-c8b2-4d80-9526-dc9b1b7a63ad"], +}); + +// It was annoying to have to access role.role to add permissions +role.role.addToPolicy( + new PolicyStatement({ + actions: ["s3:GetObject"], + resources: ["arn:aws:s3:::my-bucket/*"], + }), +); +bucket.grantRead(role.role); +``` + +After: + +```typescript +const role = new CircleCiOidcRole(this, "CircleCiOidcRole", { + provider, + projectIds: ["b4f04e57-c8b2-4d80-9526-dc9b1b7a63ad"], +}); + +// Now the `CircleCiOidcRole` is a `Role` and you can add permissions directly +role.addToPolicy( + new PolicyStatement({ + actions: ["s3:GetObject"], + resources: ["arn:aws:s3:::my-bucket/*"], + }), +); +``` + +### `cdk diff` Caused by Internal Refactoring + +Because of the [removal of outer constructs](#removal-of-outer-constructs), you might see changes like this in your CDK +diff when upgrading: + +```shell +Resources +[-] AWS::IAM::OIDCProvider CircleCiOidcProvider/CircleCiOidcProvider CircleCiOidcProviderBE49A2E7 destroy +[-] AWS::IAM::Role CircleCiOidcRole/CircleCiOidcRole CircleCiOidcRoleDC0C8DDB destroy +[+] AWS::IAM::OIDCProvider CircleCiOidcProvider CircleCiOidcProvider +[+] AWS::IAM::Role CircleCiOidcRole CircleCiOidcRoleC059EF20 +``` + +If applied, this diff will destroy the old `CircleCiOidcProvider` and `CircleCiOidcRole` and create new ones. You have +two options to deal with this internal refactoring. + +1. Destroy and recreate the affected providers and roles. +2. Prevent the destroy by overriding the logical ID of the new providers and roles. + +#### Destroy and Recreate + +This is the "cleanest" option because your CDK code won't contain overrides to work around the internal refactoring. +However, you'll likely have to issue two separate `cdk deploy` commands to destroy, then recreate the resources. + +1. Delete the old resource from your stack and run a `cdk deploy`. This will destroy the old resource. +1. Add the new resource to your stack and run a `cdk deploy`. This will create the new resource. Note that if you're not + passing the `roleName` property, you will likely get a new role name generated by CloudFormation. + +NOTE: If you didn't specify an explicit `roleName` when creating the `CircleCiOidcRole`, you don't need to trigger two +deploys. The role name will be generated by CloudFormation and will be different from the old role name, so the new role +will be created, then the old one will be destroyed. You _must_ trigger two deploys with the `CircleCiOidcProvider` +because the name is static. + +#### Prevent Destroy by Overriding Logical ID + +If it would be disruptive to recreate your `CircleCiOidcProvider` or `CircleCiOidcRole`s, you can use the +`.overrideLogicalId` property to keep the old logical ID and prevent the destroy. + +1. Run a `cdk diff` on your stack to see the logical IDs of the old resources that would be destroyed. You'll see output + like this: + + ```shell + Resources + [-] AWS::IAM::OIDCProvider CircleCiOidcProvider/CircleCiOidcProvider CircleCiOidcProviderBE49A2E7 destroy + [-] AWS::IAM::Role CircleCiOidcRole/CircleCiOidcRole CircleCiOidcRoleDC0C8DDB destroy + [+] AWS::IAM::OIDCProvider CircleCiOidcProvider CircleCiOidcProvider + [+] AWS::IAM::Role CircleCiOidcRole CircleCiOidcRoleC059EF20 + ``` + +1. Grab the old logical IDs and use them to override the logical IDs of the new resources. For example: + + ```typescript + const provider = new CircleCiOidcProvider(this, "CircleCiOidcProvider", { + organizationId: "123e4567-e89b-12d3-a456-426614174000", + }); + provider.overrideLogicalId("CircleCiOidcProviderBE49A2E7"); + + const role = new CircleCiOidcRole(this, "CircleCiOidcRole", { + provider: provider, + }); + (role.node.defaultChild as unknown as CfnResource).overrideLogicalId("CircleCiOidcRoleDC0C8DDB"); + ``` + + By specifying the old logical IDs, you prevent the destroy of the old resources while still using the new underlying + constructs. + +1. Validate that the resources will not be destroyed by running another `cdk diff`. You should see that the old + resources are no longer marked for destruction. + + ```shell + There were no differences + ``` diff --git a/package.json b/package.json index 1932251..8ce2832 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,12 @@ "organization": false }, "devDependencies": { + "@mrgrain/jsii-struct-builder": "^0.7.21", "@types/jest": "^27", "@types/node": "^18", "@typescript-eslint/eslint-plugin": "^6", "@typescript-eslint/parser": "^6", - "aws-cdk-lib": "2.1.0", + "aws-cdk-lib": "2.73.0", "constructs": "10.0.5", "eslint": "^8", "eslint-config-prettier": "^9.1.0", @@ -59,7 +60,7 @@ "typescript": "^4.9.5" }, "peerDependencies": { - "aws-cdk-lib": "^2.1.0", + "aws-cdk-lib": "^2.73.0", "constructs": "^10.0.5" }, "keywords": [ @@ -67,6 +68,14 @@ ], "main": "lib/index.js", "license": "Apache-2.0", + "typesVersions": { + "<=3.9": { + "lib/*": [ + "lib/.types-compat/ts3.9/*", + "lib/.types-compat/ts3.9/*/index.d.ts" + ] + } + }, "version": "0.0.0", "jest": { "testMatch": [ diff --git a/src/CircleCiOidcProvider.ts b/src/CircleCiOidcProvider.ts index f035bb2..03ad0d5 100644 --- a/src/CircleCiOidcProvider.ts +++ b/src/CircleCiOidcProvider.ts @@ -1,6 +1,12 @@ -import { CfnOIDCProvider, OpenIdConnectProvider } from "aws-cdk-lib/aws-iam"; +import { Stack } from "aws-cdk-lib"; +import { CfnOIDCProvider } from "aws-cdk-lib/aws-iam"; import { Construct } from "constructs"; -import { ManualCircleCiOidcProviderProps } from "./CircleCiOidcRole"; + +/** Describes a CircleCI OpenID Connect Identity Provider for AWS IAM. */ +export interface ICircleCiOidcProvider { + readonly arn: string; + readonly organizationId: string; +} export interface CircleCiOidcProviderProps { /** @@ -16,47 +22,38 @@ export interface CircleCiOidcProviderProps { * If you do need to generate this thumbprint, you can follow the instructions here: * https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html */ - readonly circleCiOidcThumbprints?: string[]; + readonly thumbprints?: string[]; } /** * This construct creates a CircleCI ODIC provider to allow AWS access from CircleCI jobs. You'll need to instantiate * this construct once per AWS account you want to use CircleCI OIDC with. * + * You can import a existing provider using `CircleCiOidcProvider.fromOrganizationId`. + * * To create a role that can be assumed by CircleCI jobs, use the `CircleCiOidcRole` construct. */ -export class CircleCiOidcProvider extends Construct { - public readonly provider: CfnOIDCProvider; +export class CircleCiOidcProvider extends CfnOIDCProvider implements ICircleCiOidcProvider { + public static fromOrganizationId(scope: Construct, organizationId: string): ICircleCiOidcProvider { + const accountId = Stack.of(scope).account; + const providerArn = `arn:aws:iam::${accountId}:oidc-provider/oidc.circleci.com/org/${organizationId}`; + return { + arn: providerArn, + organizationId, + }; + } + public readonly organizationId: string; + public readonly arn: string; constructor(scope: Construct, id: string, props: CircleCiOidcProviderProps) { - super(scope, id); - - const { organizationId, circleCiOidcThumbprints = ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"] } = props; - - // The L2 construct uses a Custom Resource, which is slow and has a few known issues - // (see https://github.com/aws/aws-cdk/issues/21197#issuecomment-1312843734) - // Therefore, we use the L1 OIDC provider construct directly instead. - this.provider = new CfnOIDCProvider(this, "CircleCiOidcProvider", { - url: `https://oidc.circleci.com/org/${organizationId}`, - clientIdList: [organizationId], - thumbprintList: circleCiOidcThumbprints, + super(scope, id, { + url: `https://oidc.circleci.com/org/${props.organizationId}`, + clientIdList: [props.organizationId], + thumbprintList: props.thumbprints ?? ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"], }); - this.organizationId = organizationId; - } - - public getProviderForExport( - accountId: string, - importName = "CircleCiOidcProviderForExport", - ): ManualCircleCiOidcProviderProps { - return { - provider: OpenIdConnectProvider.fromOpenIdConnectProviderArn( - this, - importName, - `arn:aws:iam::${accountId}:oidc-provider/oidc.circleci.com/org/${this.organizationId}`, - ), - organizationId: this.organizationId, - }; + this.arn = this.attrArn; + this.organizationId = props.organizationId; } } diff --git a/src/CircleCiOidcRole.ts b/src/CircleCiOidcRole.ts index 362f800..584ba9a 100644 --- a/src/CircleCiOidcRole.ts +++ b/src/CircleCiOidcRole.ts @@ -1,33 +1,17 @@ -import { - Condition, - IManagedPolicy, - IOpenIdConnectProvider, - OpenIdConnectPrincipal, - OpenIdConnectProvider, - PolicyDocument, - Role, -} from "aws-cdk-lib/aws-iam"; +import { OpenIdConnectPrincipal, OpenIdConnectProvider, Role } from "aws-cdk-lib/aws-iam"; import { Construct } from "constructs"; -import { CircleCiOidcProvider } from "./CircleCiOidcProvider"; +import { type ICircleCiOidcProvider } from "./CircleCiOidcProvider"; +import type { RoleProps } from "./generated/IamRoleProps"; -/** - * If you're using the {@link CircleCiOidcProvider} construct, pass it instead of these manually-defined props. - */ -export interface ManualCircleCiOidcProviderProps { +export interface CircleCiConfiguration { /** - * The CircleCI OIDC provider. You can either manually create it or import it. + * Reference to CircleCI OpenID Connect Provider configured in AWS IAM. + * + * Either pass an construct defined by `new CircleCiOidcProvider` + * or a retrieved reference from `CircleCiOidcProvider.fromOrganizationId`. + * There can be only one (per AWS Account). */ - readonly provider: IOpenIdConnectProvider; - - /** - * The ID of your CircleCI organization. This is typically in a UUID format. You can find this ID in the CircleCI - * dashboard UI under the "Organization Settings" tab. - */ - readonly organizationId: string; -} - -export interface CircleCiOidcRoleProps { - readonly circleCiOidcProvider: CircleCiOidcProvider | ManualCircleCiOidcProviderProps; + readonly provider: ICircleCiOidcProvider; /** * Provide the UUID(s) of the CircleCI project(s) you want to be allowed to use this role. If you don't provide this @@ -36,72 +20,55 @@ export interface CircleCiOidcRoleProps { * * @default - All CircleCI projects in the provider's organization */ - readonly circleCiProjectIds?: string[]; - - /** - * You can pass an explicit role name if you'd like, since you need to reference the Role ARN within your CircleCI - * configuration. - * - * @default - CloudFormation will auto-generate you a role name - */ - readonly roleName?: string; - - readonly managedPolicies?: IManagedPolicy[]; - readonly inlinePolicies?: { - [name: string]: PolicyDocument; - }; - readonly description?: string; + readonly projectIds?: string[]; } /** - * This construct creates a CircleCI ODIC provider to allow AWS access from CircleCI jobs. You'll need to instantiate - * this construct once per AWS account you want to use CircleCI OIDC with. + * Props that define the IAM Role that can be assumed by a CircleCI job + * via the CircleCI OpenID Connect Identity Provider. * - * To create a role that can be assumed by CircleCI jobs, use the `CircleCiOidcRole` construct. + * Besides {@link CircleCiConfiguration}, you may pass in any {@link RoleProps} except `assumedBy` + * which will be defined by this construct. */ -export class CircleCiOidcRole extends Construct { - readonly role: Role; +export interface CircleCiOidcRoleProps extends CircleCiConfiguration, RoleProps {} +/** Define an IAM Role that can be assumed by a CircleCI Job via the CircleCI OpenID Connect Identity Provider. */ +export class CircleCiOidcRole extends Role { constructor(scope: Construct, id: string, props: CircleCiOidcRoleProps) { - super(scope, id); - - const { circleCiProjectIds, circleCiOidcProvider, ...roleProps } = props; - const { provider, organizationId } = this.extractOpenIdConnectProvider(circleCiOidcProvider); - const oidcUrl = `oidc.circleci.com/org/${organizationId}`; - - this.role = new Role(this, "CircleCiOidcRole", { - assumedBy: new OpenIdConnectPrincipal(provider, { - StringEquals: { [`${oidcUrl}:aud`]: organizationId }, - ...this.generateProjectCondition(oidcUrl, organizationId, circleCiProjectIds), - }), - ...roleProps, + super(scope, id, { + assumedBy: new OpenIdConnectPrincipal( + // We use the CfnOIDCProvider instead of the OpenIdConnectProvider since it's overly complex + // See https://github.com/aws/aws-cdk/issues/21197 + // However, the OpenIdConnectPrincipal still expects the L2 OpenIdConnectProvider, so we "import" it here to + // make TypeScript happy with the types. + OpenIdConnectProvider.fromOpenIdConnectProviderArn( + scope, + `CircleCiOidcProviderImport${id}`, + props.provider.arn, + ), + { + StringEquals: { + [`oidc.circleci.com/org/${props.provider.organizationId}:aud`]: props.provider.organizationId, + }, + ...generateProjectCondition( + `oidc.circleci.com/org/${props.provider.organizationId}`, + props.provider.organizationId, + props.projectIds, + ), + }, + ), }); } +} - private extractOpenIdConnectProvider(provider: CircleCiOidcProvider | ManualCircleCiOidcProviderProps) { - if (provider instanceof CircleCiOidcProvider) { - return { - provider: OpenIdConnectProvider.fromOpenIdConnectProviderArn( - this, - "ImportOidcProvider", - provider.provider.attrArn, - ), - organizationId: provider.organizationId, - }; - } else { - return provider; - } +function generateProjectCondition(oidcUrl: string, organizationId: string, projectIds?: string[]) { + if (!projectIds || projectIds.length === 0) { + return {}; } - private generateProjectCondition(oidcUrl: string, organizationId: string, circleCiProjectIds?: string[]): Condition { - if (!circleCiProjectIds || circleCiProjectIds.length === 0) { - return {}; - } - - return { - StringLike: { - [`${oidcUrl}:sub`]: circleCiProjectIds.map((projectId) => `org/${organizationId}/project/${projectId}/*`), - }, - }; - } + return { + StringLike: { + [`${oidcUrl}:sub`]: projectIds.map((projectId) => `org/${organizationId}/project/${projectId}/*`), + }, + }; } diff --git a/src/generated/IamRoleProps.ts b/src/generated/IamRoleProps.ts new file mode 100644 index 0000000..70a1757 --- /dev/null +++ b/src/generated/IamRoleProps.ts @@ -0,0 +1,97 @@ +// ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". +import type { aws_iam, Duration } from 'aws-cdk-lib'; + +/** + * RoleProps + */ +export interface RoleProps { + /** + * 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. + * @stability stable + */ + readonly roleName?: 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. + * @default - No permissions boundary. + * @stability stable + * @link https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html + */ + readonly permissionsBoundary?: aws_iam.IManagedPolicy; + /** + * The path associated with this role. + * For information about IAM paths, see + * Friendly Names and Paths in IAM User Guide. + * @default / + * @stability stable + */ + readonly path?: 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. + * @default Duration.hours(1) + * @stability stable + * @link https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html + */ + readonly maxSessionDuration?: Duration; + /** + * A list of managed policies associated with this role. + * You can add managed policies later using + * `addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName(policyName))`. + * @default - No managed policies. + * @stability stable + */ + readonly managedPolicies?: Array; + /** + * 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. + * @stability stable + */ + readonly inlinePolicies?: Record; + /** + * 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 + * @stability stable + */ + readonly externalIds?: Array; + /** + * A description of the role. + * It can be up to 1000 characters long. + * @default - No description. + * @stability stable + */ + readonly description?: string; +} diff --git a/src/index.ts b/src/index.ts index cfa1665..10a9e24 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export * from "./CircleCiOidcProvider"; export * from "./CircleCiOidcRole"; +export * from "./generated/IamRoleProps"; diff --git a/test/CircleCiOidcProvider.test.ts b/test/CircleCiOidcProvider.test.ts index 87c976f..720093a 100644 --- a/test/CircleCiOidcProvider.test.ts +++ b/test/CircleCiOidcProvider.test.ts @@ -3,6 +3,18 @@ import { Template } from "aws-cdk-lib/assertions"; import { CircleCiOidcProvider } from "../src"; describe("CircleCiOidcProvider", () => { + it("creates the proper URL", () => { + const app = new App(); + const stack = new Stack(app, "TestStack"); + new CircleCiOidcProvider(stack, "CircleCiOidcProvider", { + organizationId: "1234", + }); + + Template.fromStack(stack).hasResourceProperties("AWS::IAM::OIDCProvider", { + Url: "https://oidc.circleci.com/org/1234", + }); + }); + it("uses the organization ID as the client ID", () => { const app = new App(); const stack = new Stack(app, "TestStack"); @@ -14,6 +26,7 @@ describe("CircleCiOidcProvider", () => { ClientIdList: ["1234"], }); }); + it("uses a default thumbprint list", () => { const app = new App(); const stack = new Stack(app, "TestStack"); @@ -26,20 +39,36 @@ describe("CircleCiOidcProvider", () => { }); }); - it("can export a provider for use in other stacks", () => { + it("can override the thumbprint list", () => { const app = new App(); const stack = new Stack(app, "TestStack"); - const provider = new CircleCiOidcProvider(stack, "CircleCiOidcProvider", { + new CircleCiOidcProvider(stack, "CircleCiOidcProvider", { organizationId: "1234", + thumbprints: ["thumbprint"], }); - const accountId = "123456789012"; - const providerForExport = provider.getProviderForExport(accountId); + Template.fromStack(stack).hasResourceProperties("AWS::IAM::OIDCProvider", { + ThumbprintList: ["thumbprint"], + }); + }); + + it("can import an existing provider", () => { + const app = new App(); + const stack = new Stack(app, "TestStack", { + env: { account: "123456789012", region: "us-west-2" }, + }); + const provider = CircleCiOidcProvider.fromOrganizationId(stack, "1234"); + + expect(provider.arn).toEqual("arn:aws:iam::123456789012:oidc-provider/oidc.circleci.com/org/1234"); + expect(provider.organizationId).toEqual("1234"); + }); + + it("can import an existing provider when the stack is environment agnostic", () => { + const app = new App(); + const stack = new Stack(app, "TestStack"); + const provider = CircleCiOidcProvider.fromOrganizationId(stack, "1234"); - expect(providerForExport.organizationId).toEqual("1234"); - expect(providerForExport.provider.openIdConnectProviderArn).toEqual( - "arn:aws:iam::123456789012:oidc-provider/oidc.circleci.com/org/1234", - ); - expect(providerForExport.provider.openIdConnectProviderIssuer).toEqual("oidc.circleci.com/org/1234"); + expect(provider.arn).toEqual(`arn:aws:iam::${Stack.of(stack).account}:oidc-provider/oidc.circleci.com/org/1234`); + expect(provider.organizationId).toEqual("1234"); }); }); diff --git a/test/CircleCiOidcRole.test.ts b/test/CircleCiOidcRole.test.ts index dff9af6..ccbf0ac 100644 --- a/test/CircleCiOidcRole.test.ts +++ b/test/CircleCiOidcRole.test.ts @@ -1,6 +1,5 @@ import { App, Stack } from "aws-cdk-lib"; import { Match, Template } from "aws-cdk-lib/assertions"; -import { OpenIdConnectProvider } from "aws-cdk-lib/aws-iam"; import { Queue } from "aws-cdk-lib/aws-sqs"; import { CircleCiOidcProvider, CircleCiOidcRole } from "../src"; @@ -12,7 +11,7 @@ describe("CircleCiOidcRole", () => { organizationId: "1234", }); new CircleCiOidcRole(stack, "CircleCiOidcRole", { - circleCiOidcProvider: provider, + provider, }); Template.fromStack(stack).hasResourceProperties("AWS::IAM::Role", { @@ -28,7 +27,7 @@ describe("CircleCiOidcRole", () => { }, Principal: { Federated: { - "Fn::GetAtt": ["CircleCiOidcProviderBE49A2E7", "Arn"], + "Fn::GetAtt": ["CircleCiOidcProvider", "Arn"], }, }, }), @@ -41,14 +40,7 @@ describe("CircleCiOidcRole", () => { const app = new App(); const stack = new Stack(app, "TestStack"); new CircleCiOidcRole(stack, "CircleCiOidcRole", { - circleCiOidcProvider: { - provider: OpenIdConnectProvider.fromOpenIdConnectProviderArn( - stack, - "ImportProvider", - "arn:aws:iam::12345678910:oidc-provider/circleci", - ), - organizationId: "1234", - }, + provider: CircleCiOidcProvider.fromOrganizationId(stack, "1234"), }); Template.fromStack(stack).hasResourceProperties("AWS::IAM::Role", { @@ -63,7 +55,18 @@ describe("CircleCiOidcRole", () => { }, }, Principal: { - Federated: "arn:aws:iam::12345678910:oidc-provider/circleci", + Federated: { + "Fn::Join": [ + "", + [ + "arn:aws:iam::", + { + Ref: "AWS::AccountId", + }, + ":oidc-provider/oidc.circleci.com/org/1234", + ], + ], + }, }, }), ], @@ -78,8 +81,8 @@ describe("CircleCiOidcRole", () => { organizationId: "1234", }); new CircleCiOidcRole(stack, "CircleCiOidcRole", { - circleCiOidcProvider: provider, - circleCiProjectIds: ["1234", "5678"], + provider: provider, + projectIds: ["1234", "5678"], }); Template.fromStack(stack).hasResourceProperties("AWS::IAM::Role", { @@ -108,8 +111,8 @@ describe("CircleCiOidcRole", () => { const provider = new CircleCiOidcProvider(stack, "CircleCiOidcProvider", { organizationId: "1234", }); - const { role } = new CircleCiOidcRole(stack, "CircleCiOidcRole", { - circleCiOidcProvider: provider, + const role = new CircleCiOidcRole(stack, "CircleCiOidcRole", { + provider, }); const queue = new Queue(stack, "Queue"); @@ -119,7 +122,7 @@ describe("CircleCiOidcRole", () => { // Attached to the role Roles: [ { - Ref: "CircleCiOidcRoleDC0C8DDB", + Ref: "CircleCiOidcRoleC059EF20", }, ], PolicyDocument: { diff --git a/yarn.lock b/yarn.lock index 30ef1a5..656426d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,6 +15,21 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" +"@aws-cdk/asset-awscli-v1@^2.2.97": + version "2.2.202" + resolved "https://registry.yarnpkg.com/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz#4627201d71f6a5c60db36385ce09cb81005f4b32" + integrity sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg== + +"@aws-cdk/asset-kubectl-v20@^2.1.1": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz#d8e20b5f5dc20128ea2000dc479ca3c7ddc27248" + integrity sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg== + +"@aws-cdk/asset-node-proxy-agent-v5@^2.0.77": + version "2.0.166" + resolved "https://registry.yarnpkg.com/@aws-cdk/asset-node-proxy-agent-v5/-/asset-node-proxy-agent-v5-2.0.166.tgz#467507db141cd829ff8aa9d6ea5519310a4276b8" + integrity sha512-j0xnccpUQHXJKPgCwQcGGNu4lRiC1PptYfdxBIH1L4dRK91iBxtSQHESRQX+yB47oGLaF/WfNN/aF3WXwlhikg== + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1", "@babel/code-frame@^7.24.2": version "7.24.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" @@ -625,6 +640,13 @@ dependencies: ajv "^8.12.0" +"@mrgrain/jsii-struct-builder@^0.7.21": + version "0.7.21" + resolved "https://registry.yarnpkg.com/@mrgrain/jsii-struct-builder/-/jsii-struct-builder-0.7.21.tgz#c08842b3bb451fbd9d3473d16325226ef48462c8" + integrity sha512-+DgF8/TzBRG98Xc/uq9XeZjBJ28NO5k0y8oBV5nIY8j/OD3jOWHPHC7VaxI6FubNQ3UhZaqaQ6UoqcLeZy8m+w== + dependencies: + "@jsii/spec" "^1.96.0" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1024,7 +1046,7 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.12.0: +ajv@^8.0.1, ajv@^8.12.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== @@ -1176,6 +1198,11 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1193,19 +1220,23 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -aws-cdk-lib@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.1.0.tgz#2497484cfd4e2eeaba99b070bbfa54486d52ae34" - integrity sha512-W607G3aSrWpawpcqzIuUYKlU+grfvkbszyqikyVYqJgMHFCCQXq0S1ynPMzfQ49CwjlwZsu4LIsPM+dNR+Yj6g== +aws-cdk-lib@2.73.0: + version "2.73.0" + resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.73.0.tgz#1cba582bc36a98d613b1f89680df1b17d71f43f9" + integrity sha512-r9CUe3R7EThr9U0Eb7kQCK4Ee34TDeMH+bonvGD9rNRRTYDauvAgNCsx4DZYYksPrXLRzWjzVbuXAHaDDzWt+A== dependencies: + "@aws-cdk/asset-awscli-v1" "^2.2.97" + "@aws-cdk/asset-kubectl-v20" "^2.1.1" + "@aws-cdk/asset-node-proxy-agent-v5" "^2.0.77" "@balena/dockerignore" "^1.0.2" case "1.6.3" fs-extra "^9.1.0" - ignore "^5.1.9" - jsonschema "^1.4.0" - minimatch "^3.0.4" - punycode "^2.1.1" - semver "^7.3.5" + ignore "^5.2.4" + jsonschema "^1.4.1" + minimatch "^3.1.2" + punycode "^2.3.0" + semver "^7.3.8" + table "^6.8.1" yaml "1.10.2" babel-jest@^27.5.1: @@ -2796,7 +2827,7 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -ignore@^5.1.9, ignore@^5.2.0, ignore@^5.2.4: +ignore@^5.2.0, ignore@^5.2.4: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== @@ -3768,7 +3799,7 @@ jsonparse@^1.2.0: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -jsonschema@^1.4.0: +jsonschema@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ== @@ -3863,6 +3894,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + lodash@^4.17.15, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -4402,7 +4438,7 @@ psl@^1.1.33: resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -4648,7 +4684,7 @@ semver-intersect@^1.4.0, semver-intersect@^1.5.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@7.x, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.5.0, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: +semver@7.x, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.8, semver@^7.5.0, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: version "7.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== @@ -4736,6 +4772,15 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + sort-json@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/sort-json/-/sort-json-2.0.1.tgz#7338783bef807185dc37d5b02e3afd905d537cfb" @@ -5011,6 +5056,17 @@ synckit@^0.8.6: "@pkgr/core" "^0.1.0" tslib "^2.6.2" +table@^6.8.1: + version "6.8.2" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.2.tgz#c5504ccf201213fa227248bdc8c5569716ac6c58" + integrity sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + tapable@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"