From 359494cfa76b82100eb25450c2c588d003c2d2c3 Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Fri, 5 Apr 2024 08:49:36 -0600 Subject: [PATCH 01/13] feat!: require a newer cdkv2 version --- .projen/deps.json | 2 +- .projenrc.ts | 2 +- package.json | 4 +-- yarn.lock | 77 ++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 67 insertions(+), 18 deletions(-) diff --git a/.projen/deps.json b/.projen/deps.json index c390362..7217be7 100644 --- a/.projen/deps.json +++ b/.projen/deps.json @@ -98,7 +98,7 @@ }, { "name": "aws-cdk-lib", - "version": "^2.1.0", + "version": "^2.73.0", "type": "peer" }, { diff --git a/.projenrc.ts b/.projenrc.ts index ab9da13..a5cfc25 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -4,7 +4,7 @@ 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", diff --git a/package.json b/package.json index 1932251..b99aaea 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "@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 +59,7 @@ "typescript": "^4.9.5" }, "peerDependencies": { - "aws-cdk-lib": "^2.1.0", + "aws-cdk-lib": "^2.73.0", "constructs": "^10.0.5" }, "keywords": [ diff --git a/yarn.lock b/yarn.lock index 30ef1a5..41bdf28 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" @@ -1024,7 +1039,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 +1191,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 +1213,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 +2820,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 +3792,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 +3887,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 +4431,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 +4677,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 +4765,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 +5049,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" From 764510a8f6ba06f093104b7ed01194b56b57a9c5 Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Fri, 5 Apr 2024 08:53:34 -0600 Subject: [PATCH 02/13] feat: generate RoleProps using struct builder --- .eslintrc.json | 6 +- .gitattributes | 1 + .gitignore | 1 + .projen/deps.json | 4 ++ .projen/files.json | 1 + .projenrc.ts | 16 ++++-- package.json | 1 + src/generated/iam-role-props.ts | 97 +++++++++++++++++++++++++++++++++ yarn.lock | 7 +++ 9 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 src/generated/iam-role-props.ts diff --git a/.eslintrc.json b/.eslintrc.json index b017cf4..051817b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -35,11 +35,7 @@ } }, "ignorePatterns": [ - "*.js", - "*.d.ts", - "node_modules/", - "*.generated.ts", - "coverage", + "generated/*.ts", "!.projenrc.ts", "!projenrc/**/*.ts" ], diff --git a/.gitattributes b/.gitattributes index 4958a7b..b1c57a0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,5 +19,6 @@ /API.md linguist-generated /LICENSE linguist-generated /package.json linguist-generated +/src/generated/iam-role-props.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..89ffa36 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ junit.xml tsconfig.json !/API.md !/.projenrc.ts +!/src/generated/iam-role-props.ts diff --git a/.projen/deps.json b/.projen/deps.json index 7217be7..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" diff --git a/.projen/files.json b/.projen/files.json index 9d31ccb..50143fc 100644 --- a/.projen/files.json +++ b/.projen/files.json @@ -14,6 +14,7 @@ ".projen/files.json", ".projen/tasks.json", "LICENSE", + "src/generated/iam-role-props.ts", "tsconfig.dev.json" ], "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." diff --git a/.projenrc.ts b/.projenrc.ts index a5cfc25..70238fb 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -1,3 +1,4 @@ +import { ProjenStruct, Struct } from "@mrgrain/jsii-struct-builder"; import { ReleasableCommits, awscdk } from "projen"; import { ProseWrap } from "projen/lib/javascript"; @@ -19,8 +20,14 @@ const project = new awscdk.AwsCdkConstructLibrary({ module: "cdk_circleci_oidc", }, + // deps: [], + devDeps: ["@mrgrain/jsii-struct-builder"], depsUpgrade: false, + eslintOptions: { + dirs: ["src"], + ignorePatterns: ["generated/*.ts"], // ignore generated files + }, prettier: true, prettierOptions: { settings: { @@ -28,11 +35,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/iam-role-props.ts" }).mixin( + Struct.fromFqn("aws-cdk-lib.aws_iam.RoleProps").omit("assumedBy").withoutDeprecated(), +); + project.synth(); diff --git a/package.json b/package.json index b99aaea..87ba930 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "organization": false }, "devDependencies": { + "@mrgrain/jsii-struct-builder": "^0.7.21", "@types/jest": "^27", "@types/node": "^18", "@typescript-eslint/eslint-plugin": "^6", diff --git a/src/generated/iam-role-props.ts b/src/generated/iam-role-props.ts new file mode 100644 index 0000000..70a1757 --- /dev/null +++ b/src/generated/iam-role-props.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/yarn.lock b/yarn.lock index 41bdf28..656426d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -640,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" From 6550a97a586918637f239e3b154cacc05c83a427 Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Fri, 5 Apr 2024 15:29:37 -0600 Subject: [PATCH 03/13] fix: build --- .eslintrc.json | 2 +- .projenrc.ts | 2 +- src/CircleCiOidcRole.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 051817b..85f17ab 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -35,7 +35,7 @@ } }, "ignorePatterns": [ - "generated/*.ts", + "src/generated/*.ts", "!.projenrc.ts", "!projenrc/**/*.ts" ], diff --git a/.projenrc.ts b/.projenrc.ts index 70238fb..9e09d97 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -26,7 +26,7 @@ const project = new awscdk.AwsCdkConstructLibrary({ eslintOptions: { dirs: ["src"], - ignorePatterns: ["generated/*.ts"], // ignore generated files + ignorePatterns: ["src/generated/*.ts"], // ignore generated files }, prettier: true, prettierOptions: { diff --git a/src/CircleCiOidcRole.ts b/src/CircleCiOidcRole.ts index 362f800..30308ab 100644 --- a/src/CircleCiOidcRole.ts +++ b/src/CircleCiOidcRole.ts @@ -72,6 +72,7 @@ export class CircleCiOidcRole extends Construct { this.role = new Role(this, "CircleCiOidcRole", { assumedBy: new OpenIdConnectPrincipal(provider, { StringEquals: { [`${oidcUrl}:aud`]: organizationId }, + // @ts-ignore ...this.generateProjectCondition(oidcUrl, organizationId, circleCiProjectIds), }), ...roleProps, From 14bd8bdc67a8cde8fcf8b0b9ca359e72570c5ba6 Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Fri, 5 Apr 2024 15:52:22 -0600 Subject: [PATCH 04/13] refactor: CircleCiOidcProvider --- .gitattributes | 2 +- .gitignore | 2 +- .projen/files.json | 2 +- .projenrc.ts | 2 +- API.md | 599 +++++++++++++++++- UPGRADING.md | 32 + src/CircleCiOidcProvider.ts | 37 +- src/CircleCiOidcRole.ts | 6 +- .../{iam-role-props.ts => IamRoleProps.ts} | 0 test/CircleCiOidcProvider.test.ts | 24 +- 10 files changed, 639 insertions(+), 67 deletions(-) create mode 100644 UPGRADING.md rename src/generated/{iam-role-props.ts => IamRoleProps.ts} (100%) diff --git a/.gitattributes b/.gitattributes index b1c57a0..6db0cbd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,6 +19,6 @@ /API.md linguist-generated /LICENSE linguist-generated /package.json linguist-generated -/src/generated/iam-role-props.ts 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 89ffa36..0496abb 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,4 @@ junit.xml tsconfig.json !/API.md !/.projenrc.ts -!/src/generated/iam-role-props.ts +!/src/generated/IamRoleProps.ts diff --git a/.projen/files.json b/.projen/files.json index 50143fc..89875e4 100644 --- a/.projen/files.json +++ b/.projen/files.json @@ -14,7 +14,7 @@ ".projen/files.json", ".projen/tasks.json", "LICENSE", - "src/generated/iam-role-props.ts", + "src/generated/IamRoleProps.ts", "tsconfig.dev.json" ], "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." diff --git a/.projenrc.ts b/.projenrc.ts index 9e09d97..5fc8369 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -37,7 +37,7 @@ const project = new awscdk.AwsCdkConstructLibrary({ }, }); -new ProjenStruct(project, { name: "RoleProps", filePath: "src/generated/iam-role-props.ts" }).mixin( +new ProjenStruct(project, { name: "RoleProps", filePath: "src/generated/IamRoleProps.ts" }).mixin( Struct.fromFqn("aws-cdk-lib.aws_iam.RoleProps").omit("assumedBy").withoutDeprecated(), ); diff --git a/API.md b/API.md index d76a1ed..86ab74b 100644 --- a/API.md +++ b/API.md @@ -209,7 +209,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,22 +236,368 @@ 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. + +--- + +##### `addDeletionOverride` + +```typescript +public addDeletionOverride(path: string): void +``` + +Syntactic sugar for `addOverride(path, undefined)`. + +###### `path`Required + +- *Type:* string + +The path of the value to delete. + +--- + +##### `addDependency` + +```typescript +public addDependency(target: CfnResource): void +``` + +Indicates that this resource depends on another resource and cannot be provisioned unless the other resource has been successfully provisioned. + +This can be used for resources across stacks (or nested stack) boundaries +and the dependency will automatically be transferred to the relevant scope. + +###### `target`Required + +- *Type:* aws-cdk-lib.CfnResource + +--- + +##### ~~`addDependsOn`~~ + +```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 + +--- + +##### `addMetadata` + +```typescript +public addMetadata(key: string, value: any): void +``` + +Add a value to the CloudFormation Resource Metadata. + +> [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 + +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 + +- *Type:* string + +--- + +###### `value`Required + +- *Type:* any + +--- + +##### `addOverride` + +```typescript +public addOverride(path: string, value: any): void +``` + +Adds an override to the synthesized CloudFormation resource. + +To add a +property override, either use `addPropertyOverride` or prefix `path` with +"Properties." (i.e. `Properties.TopicName`). + +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. + +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 +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" + ... + }, + ] + ... +} +``` + +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. + +###### `path`Required - *Type:* string +The path of the property, you can use dot notation to override values in complex types. + +Any intermediate keys +will be created as needed. + --- -###### `importName`Optional +###### `value`Required + +- *Type:* any + +The value. + +Could be primitive or complex. + +--- + +##### `addPropertyDeletionOverride` + +```typescript +public addPropertyDeletionOverride(propertyPath: string): void +``` + +Adds an override that deletes the value of a property from the resource definition. + +###### `propertyPath`Required - *Type:* string +The path to the property. + +--- + +##### `addPropertyOverride` + +```typescript +public addPropertyOverride(propertyPath: string, value: any): void +``` + +Adds an override to a resource property. + +Syntactic sugar for `addOverride("Properties.<...>", value)`. + +###### `propertyPath`Required + +- *Type:* string + +The path of the property. + +--- + +###### `value`Required + +- *Type:* any + +The value. + +--- + +##### `applyRemovalPolicy` + +```typescript +public applyRemovalPolicy(policy?: RemovalPolicy, options?: RemovalPolicyOptions): void +``` + +Sets the deletion policy of the resource based on the removal policy specified. + +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: + +> [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) + +###### `policy`Optional + +- *Type:* aws-cdk-lib.RemovalPolicy + +--- + +###### `options`Optional + +- *Type:* aws-cdk-lib.RemovalPolicyOptions + +--- + +##### `getAtt` + +```typescript +public getAtt(attributeName: string, typeHint?: ResolutionTypeHint): Reference +``` + +Returns a token for an runtime attribute of this resource. + +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 + +- *Type:* string + +The name of the attribute. + +--- + +###### `typeHint`Optional + +- *Type:* aws-cdk-lib.ResolutionTypeHint + +--- + +##### `getMetadata` + +```typescript +public getMetadata(key: string): any +``` + +Retrieve a value value from the CloudFormation Resource Metadata. + +> [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 + +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 + +- *Type:* string + +--- + +##### `obtainDependencies` + +```typescript +public obtainDependencies(): Stack | CfnResource[] +``` + +Retrieves an array of resources this resource depends on. + +This assembles dependencies on resources across stacks (including nested stacks) +automatically. + +##### `obtainResourceDependencies` + +```typescript +public obtainResourceDependencies(): CfnResource[] +``` + +Get a shallow copy of dependencies between this resource and other resources in the same stack. + +##### `removeDependency` + +```typescript +public removeDependency(target: CfnResource): void +``` + +Indicates that this resource no longer depends on another resource. + +This can be used for resources across stacks (including nested stacks) +and the dependency will automatically be removed from the relevant scope. + +###### `target`Required + +- *Type:* aws-cdk-lib.CfnResource + +--- + +##### `replaceDependency` + +```typescript +public replaceDependency(target: CfnResource, newTarget: CfnResource): void +``` + +Replaces one dependency with another. + +###### `target`Required + +- *Type:* aws-cdk-lib.CfnResource + +The dependency to replace. + +--- + +###### `newTarget`Required + +- *Type:* aws-cdk-lib.CfnResource + +The new dependency to add. + +--- + +##### `inspect` + +```typescript +public inspect(inspector: TreeInspector): void +``` + +Examines the CloudFormation resource and discloses attributes. + +###### `inspector`Required + +- *Type:* aws-cdk-lib.TreeInspector + +tree inspector to collect and process attributes. + --- #### Static Functions @@ -244,6 +605,8 @@ public getProviderForExport(accountId: string, importName?: string): ManualCircl | **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. | --- @@ -265,13 +628,58 @@ Any object. --- +##### `isCfnElement` + +```typescript +import { CircleCiOidcProvider } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcProvider.isCfnElement(x: any) +``` + +Returns `true` if a construct is a stack element (i.e. part of the synthesized cloudformation template). + +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 + +--- + +##### `isCfnResource` + +```typescript +import { CircleCiOidcProvider } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcProvider.isCfnResource(construct: IConstruct) +``` + +Check whether the given construct is a CfnResource. + +###### `construct`Required + +- *Type:* constructs.IConstruct + +--- + #### Properties | **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. | | organizationId | string | *No description.* | -| provider | aws-cdk-lib.aws_iam.CfnOIDCProvider | *No description.* | --- @@ -287,6 +695,162 @@ 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) + +--- + ##### `organizationId`Required ```typescript @@ -297,16 +861,25 @@ public readonly organizationId: string; --- -##### `provider`Required +#### 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 provider: CfnOIDCProvider; +public readonly CFN_RESOURCE_TYPE_NAME: string; ``` -- *Type:* aws-cdk-lib.aws_iam.CfnOIDCProvider +- *Type:* string ---- +The CloudFormation resource type name for this resource class. +--- ### CircleCiOidcRole @@ -493,7 +1066,7 @@ const circleCiOidcRoleProps: CircleCiOidcRoleProps = { ... } | **Name** | **Type** | **Description** | | --- | --- | --- | -| circleCiOidcProvider | ManualCircleCiOidcProviderProps \| CircleCiOidcProvider | *No description.* | +| circleCiOidcProvider | CircleCiOidcProvider \| ManualCircleCiOidcProviderProps | *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.* | @@ -505,10 +1078,10 @@ const circleCiOidcRoleProps: CircleCiOidcRoleProps = { ... } ##### `circleCiOidcProvider`Required ```typescript -public readonly circleCiOidcProvider: ManualCircleCiOidcProviderProps | CircleCiOidcProvider; +public readonly circleCiOidcProvider: CircleCiOidcProvider | ManualCircleCiOidcProviderProps; ``` -- *Type:* ManualCircleCiOidcProviderProps | CircleCiOidcProvider +- *Type:* CircleCiOidcProvider | ManualCircleCiOidcProviderProps --- diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 0000000..50a571f --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,32 @@ +# Upgrading + +## From 0.x to 1.0 + +The API underwent breaking changes between the 0.x and 1.x lines. The API changes make it easier to use the library by +_extending_ familiar constructs instead of creating new `Constructs` you have to learn. + +### `CircleCiOidcProvider` + +`CircleCiOidcProvider` now _extends_ `CfnOIDCProvider`. So, you might see changes like this in your CDK diff when +upgrading: + +```shell +Resources +[-] AWS::IAM::OIDCProvider CircleCiOidcProvider/CircleCiOidcProvider CircleCiOidcProviderBE49A2E7 destroy +[+] AWS::IAM::OIDCProvider CircleCiOidcProvider CircleCiOidcProvider +``` + +This diff means that the provider will be recreated because of the incompatible changes. You have two options to fix +this. + +1. Remove the old `CircleCiOidcProvider`, `cdk deploy`, re-add the `CircleCiOidcProvider`, and `cdk deploy` again. +1. If it would be disruptive to recreate the provider, you can use the `.overrideLogicalId` property to keep the old + logical ID and prevent the destroy. Grab the old logical ID (e.g., `CircleCiOidcProviderBE49A2E7` from the example + above) and use it like this: + + ```typescript + const provider = new CircleCiOidcProvider(this, "CircleCiOidcProvider", { + organizationId: "123e4567-e89b-12d3-a456-426614174000", + }); + provider.overrideLogicalId("CircleCiOidcProviderBE49A2E7"); // This will prevent the old provider from being destroyed + ``` diff --git a/src/CircleCiOidcProvider.ts b/src/CircleCiOidcProvider.ts index f035bb2..ac80935 100644 --- a/src/CircleCiOidcProvider.ts +++ b/src/CircleCiOidcProvider.ts @@ -1,6 +1,5 @@ -import { CfnOIDCProvider, OpenIdConnectProvider } from "aws-cdk-lib/aws-iam"; +import { CfnOIDCProvider } from "aws-cdk-lib/aws-iam"; import { Construct } from "constructs"; -import { ManualCircleCiOidcProviderProps } from "./CircleCiOidcRole"; export interface CircleCiOidcProviderProps { /** @@ -25,38 +24,16 @@ export interface CircleCiOidcProviderProps { * * 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 { public readonly organizationId: 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.circleCiOidcThumbprints ?? ["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.organizationId = props.organizationId; } } diff --git a/src/CircleCiOidcRole.ts b/src/CircleCiOidcRole.ts index 30308ab..c4ef8b0 100644 --- a/src/CircleCiOidcRole.ts +++ b/src/CircleCiOidcRole.ts @@ -82,11 +82,7 @@ export class CircleCiOidcRole extends Construct { private extractOpenIdConnectProvider(provider: CircleCiOidcProvider | ManualCircleCiOidcProviderProps) { if (provider instanceof CircleCiOidcProvider) { return { - provider: OpenIdConnectProvider.fromOpenIdConnectProviderArn( - this, - "ImportOidcProvider", - provider.provider.attrArn, - ), + provider: OpenIdConnectProvider.fromOpenIdConnectProviderArn(this, "ImportOidcProvider", provider.attrArn), organizationId: provider.organizationId, }; } else { diff --git a/src/generated/iam-role-props.ts b/src/generated/IamRoleProps.ts similarity index 100% rename from src/generated/iam-role-props.ts rename to src/generated/IamRoleProps.ts diff --git a/test/CircleCiOidcProvider.test.ts b/test/CircleCiOidcProvider.test.ts index 87c976f..ef5efcb 100644 --- a/test/CircleCiOidcProvider.test.ts +++ b/test/CircleCiOidcProvider.test.ts @@ -3,7 +3,7 @@ import { Template } from "aws-cdk-lib/assertions"; import { CircleCiOidcProvider } from "../src"; describe("CircleCiOidcProvider", () => { - it("uses the organization ID as the client ID", () => { + it("creates the proper URL", () => { const app = new App(); const stack = new Stack(app, "TestStack"); new CircleCiOidcProvider(stack, "CircleCiOidcProvider", { @@ -11,10 +11,10 @@ describe("CircleCiOidcProvider", () => { }); Template.fromStack(stack).hasResourceProperties("AWS::IAM::OIDCProvider", { - ClientIdList: ["1234"], + Url: "https://oidc.circleci.com/org/1234", }); }); - it("uses a default thumbprint list", () => { + it("uses the organization ID as the client ID", () => { const app = new App(); const stack = new Stack(app, "TestStack"); new CircleCiOidcProvider(stack, "CircleCiOidcProvider", { @@ -22,24 +22,18 @@ describe("CircleCiOidcProvider", () => { }); Template.fromStack(stack).hasResourceProperties("AWS::IAM::OIDCProvider", { - ThumbprintList: ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"], + ClientIdList: ["1234"], }); }); - - it("can export a provider for use in other stacks", () => { + it("uses a default thumbprint list", () => { const app = new App(); const stack = new Stack(app, "TestStack"); - const provider = new CircleCiOidcProvider(stack, "CircleCiOidcProvider", { + new CircleCiOidcProvider(stack, "CircleCiOidcProvider", { organizationId: "1234", }); - const accountId = "123456789012"; - const providerForExport = provider.getProviderForExport(accountId); - - 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"); + Template.fromStack(stack).hasResourceProperties("AWS::IAM::OIDCProvider", { + ThumbprintList: ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"], + }); }); }); From 0c2813fd73b1831ec7aeb815f939196c23c33deb Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Fri, 5 Apr 2024 16:23:38 -0600 Subject: [PATCH 05/13] refactor: CircleCiOidcRole --- package.json | 8 +++ src/CircleCiOidcProvider.ts | 19 +++++- src/CircleCiOidcRole.ts | 117 ++++++++++++------------------------ src/index.ts | 1 + 4 files changed, 67 insertions(+), 78 deletions(-) diff --git a/package.json b/package.json index 87ba930..66f6e8e 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,14 @@ } }, "types": "lib/index.d.ts", + "typesVersions": { + "<=3.9": { + "lib/*": [ + "lib/.types-compat/ts3.9/*", + "lib/.types-compat/ts3.9/*/index.d.ts" + ] + } + }, "stability": "stable", "jsii": { "outdir": "dist", diff --git a/src/CircleCiOidcProvider.ts b/src/CircleCiOidcProvider.ts index ac80935..84b78df 100644 --- a/src/CircleCiOidcProvider.ts +++ b/src/CircleCiOidcProvider.ts @@ -1,6 +1,12 @@ +import { Stack } from "aws-cdk-lib"; import { CfnOIDCProvider } from "aws-cdk-lib/aws-iam"; import { Construct } from "constructs"; +export interface ICircleCiOidcProvider { + readonly arn: string; + readonly organizationId: string; +} + export interface CircleCiOidcProviderProps { /** * The ID of your CircleCI organization. This is typically in a UUID format. You can find this ID in the CircleCI @@ -24,8 +30,18 @@ export interface CircleCiOidcProviderProps { * * To create a role that can be assumed by CircleCI jobs, use the `CircleCiOidcRole` construct. */ -export class CircleCiOidcProvider extends 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, { @@ -34,6 +50,7 @@ export class CircleCiOidcProvider extends CfnOIDCProvider { thumbprintList: props.circleCiOidcThumbprints ?? ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"], }); + this.arn = this.attrArn; this.organizationId = props.organizationId; } } diff --git a/src/CircleCiOidcRole.ts b/src/CircleCiOidcRole.ts index c4ef8b0..667b1fb 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,69 +20,48 @@ 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[]; } +export interface CircleCiOidcRoleProps extends CircleCiConfiguration, RoleProps {} + /** * 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. * * To create a role that can be assumed by CircleCI jobs, use the `CircleCiOidcRole` construct. */ -export class CircleCiOidcRole extends Construct { - readonly role: Role; - +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 }, - // @ts-ignore - ...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, "CircleCiOidcProvider", 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, + ), + }, + }, + ), }); } +} - private extractOpenIdConnectProvider(provider: CircleCiOidcProvider | ManualCircleCiOidcProviderProps) { - if (provider instanceof CircleCiOidcProvider) { - return { - provider: OpenIdConnectProvider.fromOpenIdConnectProviderArn(this, "ImportOidcProvider", 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/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"; From d06750056407220c473f5f2e79578adc21e02a4d Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Fri, 5 Apr 2024 16:27:04 -0600 Subject: [PATCH 06/13] fix: deconflict parent import --- API.md | 1093 +++++++++++++++++++++++++++++++++++---- package.json | 16 +- src/CircleCiOidcRole.ts | 6 +- 3 files changed, 1001 insertions(+), 114 deletions(-) diff --git a/API.md b/API.md index 86ab74b..a02b9bf 100644 --- a/API.md +++ b/API.md @@ -163,6 +163,8 @@ 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 @@ -607,6 +609,7 @@ tree inspector to collect and process attributes. | 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.* | --- @@ -663,6 +666,26 @@ Check whether the given construct is a CfnResource. --- +##### `fromOrganizationId` + +```typescript +import { CircleCiOidcProvider } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcProvider.fromOrganizationId(scope: Construct, organizationId: string) +``` + +###### `scope`Required + +- *Type:* constructs.Construct + +--- + +###### `organizationId`Required + +- *Type:* string + +--- + #### Properties | **Name** | **Type** | **Description** | @@ -679,6 +702,7 @@ Check whether the given construct is a CfnResource. | 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.* | --- @@ -851,6 +875,16 @@ For more information, see [CreateOpenIDConnectProvider](https://docs.aws.amazon. --- +##### `arn`Required + +```typescript +public readonly arn: string; +``` + +- *Type:* string + +--- + ##### `organizationId`Required ```typescript @@ -929,6 +963,15 @@ new CircleCiOidcRole(scope: Construct, id: string, props: CircleCiOidcRoleProps) | **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. | --- @@ -940,260 +983,1100 @@ public toString(): string Returns a string representation of this construct. -#### Static Functions +##### `applyRemovalPolicy` -| **Name** | **Description** | -| --- | --- | -| isConstruct | Checks if `x` is a construct. | +```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 --- -##### ~~`isConstruct`~~ +##### `addManagedPolicy` ```typescript -import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' - -CircleCiOidcRole.isConstruct(x: any) +public addManagedPolicy(policy: IManagedPolicy): void ``` -Checks if `x` is a construct. +Attaches a managed policy to this role. -###### `x`Required +###### `policy`Required -- *Type:* any +- *Type:* aws-cdk-lib.aws_iam.IManagedPolicy -Any object. +The the managed policy to attach. --- -#### Properties +##### `addToPolicy` -| **Name** | **Type** | **Description** | -| --- | --- | --- | -| node | constructs.Node | The tree node. | -| role | aws-cdk-lib.aws_iam.Role | *No description.* | +```typescript +public addToPolicy(statement: PolicyStatement): boolean +``` + +Add to the policy of this principal. + +###### `statement`Required + +- *Type:* aws-cdk-lib.aws_iam.PolicyStatement --- -##### `node`Required +##### `addToPrincipalPolicy` ```typescript -public readonly node: Node; +public addToPrincipalPolicy(statement: PolicyStatement): AddToPrincipalPolicyResult ``` -- *Type:* constructs.Node +Adds a permission to the role's default policy document. -The tree node. +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. --- -##### `role`Required +##### `attachInlinePolicy` ```typescript -public readonly role: Role; +public attachInlinePolicy(policy: Policy): void ``` -- *Type:* aws-cdk-lib.aws_iam.Role +Attaches a policy to this role. ---- +###### `policy`Required +- *Type:* aws-cdk-lib.aws_iam.Policy -## Structs +The policy to attach. -### CircleCiOidcProviderProps +--- -#### Initializer +##### `grant` ```typescript -import { CircleCiOidcProviderProps } from '@blimmer/cdk-circleci-oidc' +public grant(grantee: IPrincipal, actions: string): Grant +``` -const circleCiOidcProviderProps: CircleCiOidcProviderProps = { ... } +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 ``` -#### Properties +Grant permissions to the given principal to assume this role. -| **Name** | **Type** | **Description** | -| --- | --- | --- | -| organizationId | string | The ID of your CircleCI organization. | -| circleCiOidcThumbprints | string[] | The OIDC thumbprints used by the provider. | +###### `identity`Required + +- *Type:* aws-cdk-lib.aws_iam.IPrincipal --- -##### `organizationId`Required +##### `grantPassRole` ```typescript -public readonly organizationId: string; +public grantPassRole(identity: IPrincipal): Grant ``` -- *Type:* string +Grant permissions to the given principal to pass this role. -The ID of your CircleCI organization. +###### `identity`Required -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.aws_iam.IPrincipal --- -##### `circleCiOidcThumbprints`Optional +##### `withoutPolicyUpdates` ```typescript -public readonly circleCiOidcThumbprints: string[]; +public withoutPolicyUpdates(options?: WithoutPolicyUpdatesOptions): IRole ``` -- *Type:* string[] +Return a copy of this Role object whose Policies will not be updated. -The OIDC thumbprints used by the provider. +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. -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, you are responsible for adding the correct statements to the +Role's policies yourself. -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 +###### `options`Optional + +- *Type:* aws-cdk-lib.aws_iam.WithoutPolicyUpdatesOptions --- -### CircleCiOidcRoleProps +#### Static Functions -#### Initializer +| **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 { CircleCiOidcRoleProps } from '@blimmer/cdk-circleci-oidc' +import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' -const circleCiOidcRoleProps: CircleCiOidcRoleProps = { ... } +CircleCiOidcRole.isConstruct(x: any) ``` -#### Properties +Checks if `x` is a construct. -| **Name** | **Type** | **Description** | -| --- | --- | --- | -| circleCiOidcProvider | CircleCiOidcProvider \| ManualCircleCiOidcProviderProps | *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. | +###### `x`Required + +- *Type:* any + +Any object. --- -##### `circleCiOidcProvider`Required +##### `isOwnedResource` ```typescript -public readonly circleCiOidcProvider: CircleCiOidcProvider | ManualCircleCiOidcProviderProps; +import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcRole.isOwnedResource(construct: IConstruct) ``` -- *Type:* CircleCiOidcProvider | ManualCircleCiOidcProviderProps +Returns true if the construct was created by CDK, and false otherwise. + +###### `construct`Required + +- *Type:* constructs.IConstruct --- -##### `circleCiProjectIds`Optional +##### `isResource` ```typescript -public readonly circleCiProjectIds: string[]; +import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcRole.isResource(construct: IConstruct) ``` -- *Type:* string[] -- *Default:* All CircleCI projects in the provider's organization +Check whether the given construct is a Resource. -Provide the UUID(s) of the CircleCI project(s) you want to be allowed to use this role. +###### `construct`Required -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. +- *Type:* constructs.IConstruct --- -##### `description`Optional +##### `customizeRoles` ```typescript -public readonly description: string; +import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcRole.customizeRoles(scope: Construct, options?: CustomizeRolesOptions) ``` -- *Type:* string +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. -##### `inlinePolicies`Optional +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 -public readonly inlinePolicies: {[ key: string ]: PolicyDocument}; +declare const app: App; +Role.customizeRoles(app, { + usePrecreatedRoles: { + 'ConstructPath/To/Role': 'my-precreated-role-name', + }, +}); ``` -- *Type:* {[ key: string ]: aws-cdk-lib.aws_iam.PolicyDocument} + +###### `scope`Required + +- *Type:* constructs.Construct + +construct scope to customize role creation. --- -##### `managedPolicies`Optional +###### `options`Optional + +- *Type:* aws-cdk-lib.aws_iam.CustomizeRolesOptions + +options for configuring role creation. + +--- + +##### `fromRoleArn` ```typescript -public readonly managedPolicies: IManagedPolicy[]; +import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' + +CircleCiOidcRole.fromRoleArn(scope: Construct, id: string, roleArn: string, options?: FromRoleArnOptions) ``` -- *Type:* aws-cdk-lib.aws_iam.IManagedPolicy[] +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. --- -##### `roleName`Optional +###### `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 -public readonly roleName: string; +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 -- *Default:* CloudFormation will auto-generate you a role name -You can pass an explicit role name if you'd like, since you need to reference the Role ARN within your CircleCI configuration. +the name of the role to import. --- -### ManualCircleCiOidcProviderProps +###### `options`Optional + +- *Type:* aws-cdk-lib.aws_iam.FromRoleNameOptions -If you're using the {@link CircleCiOidcProvider} construct, pass it instead of these manually-defined props. +allow customizing the behavior of the returned role. + +--- -#### Initializer +##### `isRole` ```typescript -import { ManualCircleCiOidcProviderProps } from '@blimmer/cdk-circleci-oidc' +import { CircleCiOidcRole } from '@blimmer/cdk-circleci-oidc' -const manualCircleCiOidcProviderProps: ManualCircleCiOidcProviderProps = { ... } +CircleCiOidcRole.isRole(x: any) ``` +Return whether the given object is a Role. + +###### `x`Required + +- *Type:* any + +--- + #### Properties | **Name** | **Type** | **Description** | | --- | --- | --- | -| organizationId | string | The ID of your CircleCI organization. | -| provider | aws-cdk-lib.aws_iam.IOpenIdConnectProvider | The CircleCI OIDC provider. | +| 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. | --- -##### `organizationId`Required +##### `node`Required ```typescript -public readonly organizationId: string; +public readonly node: Node; ``` -- *Type:* string +- *Type:* constructs.Node -The ID of your CircleCI organization. +The tree node. -This is typically in a UUID format. You can find this ID in the CircleCI -dashboard UI under the "Organization Settings" tab. +--- + +##### `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. --- -##### `provider`Required +##### `assumeRoleAction`Required ```typescript -public readonly provider: IOpenIdConnectProvider; +public readonly assumeRoleAction: string; ``` -- *Type:* aws-cdk-lib.aws_iam.IOpenIdConnectProvider +- *Type:* string + +When this Principal is used in an AssumeRole policy, the action to use. + +--- + +##### `grantPrincipal`Required -The CircleCI OIDC provider. +```typescript +public readonly grantPrincipal: IPrincipal; +``` -You can either manually create it or import it. +- *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. | +| circleCiOidcThumbprints | 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. + +--- + +##### `circleCiOidcThumbprints`Optional + +```typescript +public readonly circleCiOidcThumbprints: 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 + +#### 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 + + +#### 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/package.json b/package.json index 66f6e8e..8ce2832 100644 --- a/package.json +++ b/package.json @@ -68,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": [ @@ -110,14 +118,6 @@ } }, "types": "lib/index.d.ts", - "typesVersions": { - "<=3.9": { - "lib/*": [ - "lib/.types-compat/ts3.9/*", - "lib/.types-compat/ts3.9/*/index.d.ts" - ] - } - }, "stability": "stable", "jsii": { "outdir": "dist", diff --git a/src/CircleCiOidcRole.ts b/src/CircleCiOidcRole.ts index 667b1fb..546aea6 100644 --- a/src/CircleCiOidcRole.ts +++ b/src/CircleCiOidcRole.ts @@ -39,7 +39,11 @@ export class CircleCiOidcRole extends Role { // 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, "CircleCiOidcProvider", props.provider.arn), + OpenIdConnectProvider.fromOpenIdConnectProviderArn( + scope, + `CircleCiOidcProviderImport${id}`, + props.provider.arn, + ), { StringEquals: { [`oidc.circleci.com/org/${props.provider.organizationId}:aud`]: props.provider.organizationId, From 02ef6951f2d0c5f427d3ec756d600bd0f163f688 Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Fri, 5 Apr 2024 16:45:56 -0600 Subject: [PATCH 07/13] docs: more upgrading docs --- UPGRADING.md | 74 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 50a571f..34a3e3e 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,32 +1,80 @@ # Upgrading -## From 0.x to 1.0 +## 0.x to 1.0 -The API underwent breaking changes between the 0.x and 1.x lines. The API changes make it easier to use the library by -_extending_ familiar constructs instead of creating new `Constructs` you have to learn. +The API underwent breaking changes between the 0.x and 1.x releases. -### `CircleCiOidcProvider` +### Removal of Outer Constructs -`CircleCiOidcProvider` now _extends_ `CfnOIDCProvider`. So, you might see changes like this in your CDK diff when -upgrading: +`CircleCiOidcProvider` and `CircleCiOidcRole` now _extend_ `CfnOIDCProvider` and `Role`, respectively. This makes them +simpler to work with and more idiomatic to the CDK. + +Because of this change, 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 ``` -This diff means that the provider will be recreated because of the incompatible changes. You have two options to fix -this. +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. -1. Remove the old `CircleCiOidcProvider`, `cdk deploy`, re-add the `CircleCiOidcProvider`, and `cdk deploy` again. -1. If it would be disruptive to recreate the provider, you can use the `.overrideLogicalId` property to keep the old - logical ID and prevent the destroy. Grab the old logical ID (e.g., `CircleCiOidcProviderBE49A2E7` from the example - above) and use it like this: +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"); // This will prevent the old provider from being destroyed + 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 ``` From 0e6d0f1431253218ca697587e9a5131c3a2279c3 Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Fri, 5 Apr 2024 16:57:48 -0600 Subject: [PATCH 08/13] docs: more upgrading docs --- UPGRADING.md | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 2 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 34a3e3e..49f0236 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,15 +1,115 @@ # Upgrading -## 0.x to 1.0 +## 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! + +### `CircleCiOidcRole` Constructor Property Changes + +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. -Because of this change, you might see changes like this in your CDK diff when upgrading: +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 From 2a1f799d1933521eedf8d7dd40a31108d0b5ef23 Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Fri, 5 Apr 2024 17:00:08 -0600 Subject: [PATCH 09/13] refactor: thumbprints prop shorter --- UPGRADING.md | 8 +++++++- src/CircleCiOidcProvider.ts | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 49f0236..b8b471a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -43,7 +43,13 @@ const role = new CircleCiOidcRole(this, "CircleCiOidcRole", { Much better! -### `CircleCiOidcRole` Constructor Property Changes +### 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. diff --git a/src/CircleCiOidcProvider.ts b/src/CircleCiOidcProvider.ts index 84b78df..93acdb9 100644 --- a/src/CircleCiOidcProvider.ts +++ b/src/CircleCiOidcProvider.ts @@ -21,7 +21,7 @@ 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[]; } /** @@ -47,7 +47,7 @@ export class CircleCiOidcProvider extends CfnOIDCProvider implements ICircleCiOi super(scope, id, { url: `https://oidc.circleci.com/org/${props.organizationId}`, clientIdList: [props.organizationId], - thumbprintList: props.circleCiOidcThumbprints ?? ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"], + thumbprintList: props.thumbprints ?? ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"], }); this.arn = this.attrArn; From bdb7d2fb7184c0a9a98d21d666c31bf3a6bd504b Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Fri, 5 Apr 2024 17:04:55 -0600 Subject: [PATCH 10/13] fix: tests for provider --- src/CircleCiOidcProvider.ts | 3 +++ test/CircleCiOidcProvider.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/CircleCiOidcProvider.ts b/src/CircleCiOidcProvider.ts index 93acdb9..03ad0d5 100644 --- a/src/CircleCiOidcProvider.ts +++ b/src/CircleCiOidcProvider.ts @@ -2,6 +2,7 @@ import { Stack } from "aws-cdk-lib"; import { CfnOIDCProvider } from "aws-cdk-lib/aws-iam"; import { Construct } from "constructs"; +/** Describes a CircleCI OpenID Connect Identity Provider for AWS IAM. */ export interface ICircleCiOidcProvider { readonly arn: string; readonly organizationId: string; @@ -28,6 +29,8 @@ export interface CircleCiOidcProviderProps { * 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 CfnOIDCProvider implements ICircleCiOidcProvider { diff --git a/test/CircleCiOidcProvider.test.ts b/test/CircleCiOidcProvider.test.ts index ef5efcb..b84949e 100644 --- a/test/CircleCiOidcProvider.test.ts +++ b/test/CircleCiOidcProvider.test.ts @@ -14,6 +14,7 @@ describe("CircleCiOidcProvider", () => { 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"); @@ -25,6 +26,7 @@ describe("CircleCiOidcProvider", () => { ClientIdList: ["1234"], }); }); + it("uses a default thumbprint list", () => { const app = new App(); const stack = new Stack(app, "TestStack"); @@ -36,4 +38,24 @@ describe("CircleCiOidcProvider", () => { ThumbprintList: ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"], }); }); + + 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(provider.arn).toEqual(`arn:aws:iam::${Stack.of(stack).account}:oidc-provider/oidc.circleci.com/org/1234`); + expect(provider.organizationId).toEqual("1234"); + }); }); From 7f344798cdf5af9872ba22586e6fb72d16170b68 Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Fri, 5 Apr 2024 17:20:03 -0600 Subject: [PATCH 11/13] feat: finalize testing --- src/CircleCiOidcRole.ts | 21 ++++++++++-------- test/CircleCiOidcProvider.test.ts | 13 +++++++++++ test/CircleCiOidcRole.test.ts | 37 +++++++++++++++++-------------- 3 files changed, 45 insertions(+), 26 deletions(-) diff --git a/src/CircleCiOidcRole.ts b/src/CircleCiOidcRole.ts index 546aea6..584ba9a 100644 --- a/src/CircleCiOidcRole.ts +++ b/src/CircleCiOidcRole.ts @@ -23,14 +23,16 @@ export interface CircleCiConfiguration { readonly projectIds?: string[]; } -export interface CircleCiOidcRoleProps extends CircleCiConfiguration, RoleProps {} - /** - * 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 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, { @@ -47,11 +49,12 @@ export class CircleCiOidcRole extends Role { { StringEquals: { [`oidc.circleci.com/org/${props.provider.organizationId}:aud`]: props.provider.organizationId, - ...generateProjectCondition( - `oidc.circleci.com/org/${props.provider.organizationId}`, - props.provider.organizationId, - ), }, + ...generateProjectCondition( + `oidc.circleci.com/org/${props.provider.organizationId}`, + props.provider.organizationId, + props.projectIds, + ), }, ), }); diff --git a/test/CircleCiOidcProvider.test.ts b/test/CircleCiOidcProvider.test.ts index b84949e..720093a 100644 --- a/test/CircleCiOidcProvider.test.ts +++ b/test/CircleCiOidcProvider.test.ts @@ -39,6 +39,19 @@ describe("CircleCiOidcProvider", () => { }); }); + it("can override the thumbprint list", () => { + const app = new App(); + const stack = new Stack(app, "TestStack"); + new CircleCiOidcProvider(stack, "CircleCiOidcProvider", { + organizationId: "1234", + thumbprints: ["thumbprint"], + }); + + 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", { 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: { From d9d1618dc6c03c7c71cda7c12236f5fe87d4bed6 Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Fri, 5 Apr 2024 17:29:18 -0600 Subject: [PATCH 12/13] docs: update readme --- API.md | 137 ++++++++++++++++++++++++------------------------------ README.md | 115 +++++++++++++++++++-------------------------- 2 files changed, 109 insertions(+), 143 deletions(-) diff --git a/API.md b/API.md index a02b9bf..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! @@ -170,6 +151,8 @@ This construct creates a CircleCI ODIC provider to allow AWS access from CircleC 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 @@ -917,12 +900,7 @@ The CloudFormation resource type name for this resource class. ### CircleCiOidcRole -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. - -To create a role that can be assumed by CircleCI jobs, use the `CircleCiOidcRole` construct. +Define an IAM Role that can be assumed by a CircleCI Job via the CircleCI OpenID Connect Identity Provider. #### Initializers @@ -1611,7 +1589,7 @@ const circleCiOidcProviderProps: CircleCiOidcProviderProps = { ... } | **Name** | **Type** | **Description** | | --- | --- | --- | | organizationId | string | The ID of your CircleCI organization. | -| circleCiOidcThumbprints | string[] | The OIDC thumbprints used by the provider. | +| thumbprints | string[] | The OIDC thumbprints used by the provider. | --- @@ -1630,10 +1608,10 @@ dashboard UI under the "Organization Settings" tab. --- -##### `circleCiOidcThumbprints`Optional +##### `thumbprints`Optional ```typescript -public readonly circleCiOidcThumbprints: string[]; +public readonly thumbprints: string[]; ``` - *Type:* string[] @@ -1650,6 +1628,11 @@ https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_ ### 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 @@ -2050,6 +2033,8 @@ Acknowledging IAM Resources in AWS CloudFormation Templates. - *Implemented By:* CircleCiOidcProvider, ICircleCiOidcProvider +Describes a CircleCI OpenID Connect Identity Provider for AWS IAM. + #### Properties 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! From 8de02dec7f123afda008b142a7db1875a688d932 Mon Sep 17 00:00:00 2001 From: Ben Limmer Date: Fri, 5 Apr 2024 17:29:52 -0600 Subject: [PATCH 13/13] feat!: bump to version 1.x --- .projen/tasks.json | 3 ++- .projenrc.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) 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 5fc8369..c427ca5 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -9,6 +9,7 @@ const project = new awscdk.AwsCdkConstructLibrary({ defaultReleaseBranch: "main", name: "@blimmer/cdk-circleci-oidc", repositoryUrl: "https://github.com/blimmer/cdk-circleci-oidc.git", + majorVersion: 1, projenrcTs: true,