diff --git a/aws-ts-oidc-provider-pulumi-cloud/.gitignore b/aws-ts-oidc-provider-pulumi-cloud/.gitignore index e37caccd6..63951cb6a 100644 --- a/aws-ts-oidc-provider-pulumi-cloud/.gitignore +++ b/aws-ts-oidc-provider-pulumi-cloud/.gitignore @@ -1,3 +1,4 @@ /bin/ /node_modules/ -test/ \ No newline at end of file +test/ +Pulumi.test.yaml \ No newline at end of file diff --git a/aws-ts-oidc-provider-pulumi-cloud/Pulumi.yaml b/aws-ts-oidc-provider-pulumi-cloud/Pulumi.yaml index 4632bdb67..eabe384c0 100644 --- a/aws-ts-oidc-provider-pulumi-cloud/Pulumi.yaml +++ b/aws-ts-oidc-provider-pulumi-cloud/Pulumi.yaml @@ -3,19 +3,9 @@ description: ${DESCRIPTION} runtime: nodejs template: - description: A minimal TypeScript Pulumi program to set up AWS OIDC+Pulumi ESC + description: A minimal TypeScript Pulumi program to set up AWS OIDC config: aws:region: - description: The AWS region to deploy into + description: AWS Region default: us-west-2 - oidcIdpUrl: - description: The URL of the OIDC IdP to use - default: https://api.pulumi.com/oidc - thumbprint: - description: The thumbprint of the OIDC IdP SSL certificate. - # This is a valid AWS OIDC thumbprint as of June 2024. - default: 9e99a48a9960b14926bb7f3b02e22da2b0ab7280 - escEnv: - description: The Pulumi ESC Environment to create ('.' to skip) - default: aws-oidc-env \ No newline at end of file diff --git a/aws-ts-oidc-provider-pulumi-cloud/README.md b/aws-ts-oidc-provider-pulumi-cloud/README.md index ab95e5de1..2532f4253 100644 --- a/aws-ts-oidc-provider-pulumi-cloud/README.md +++ b/aws-ts-oidc-provider-pulumi-cloud/README.md @@ -3,9 +3,9 @@ A Pulumi template to: - Create AWS resources for AWS OIDC (IdP + Role) -- Create a new Pulumi Cloud ESC Environment (optional) +- Create a new Pulumi Cloud ESC Environment -Last update: July 2024 +Last update: September 2024 ## 📋 Pre-requisites @@ -32,23 +32,7 @@ Once copied to your machine, feel free to edit as needed. ## 🎬 How to run -This template will pick up the thumbprint from the URL that you set in the stack configuration. By default it will use the OIDC IDP URL for Pulumi Cloud, unless you set a different one. - -To set a different URL you can run the following command: - -```bash -pulumi config set oidcIdpUrl {url} -``` - -(where `{url}` is the URL for the OIDC IDP) - -You must also set the name of the environment that you would like to use: - -```bash -pulumi config set escEnv {environment-name} -``` - -(Note that `{environment-name}` must be in the format `{orgname}/environmentname}` where `orgname` can be your individual account name or the the organization that you are adding the environment to) +This template will pick up the thumbprint from the URL that you set in the stack configuration. By default it will use the OIDC IDP URL for Pulumi Cloud. To deploy your infrastructure, run: diff --git a/aws-ts-oidc-provider-pulumi-cloud/index.ts b/aws-ts-oidc-provider-pulumi-cloud/index.ts index 8a0af2432..4cfc82a37 100644 --- a/aws-ts-oidc-provider-pulumi-cloud/index.ts +++ b/aws-ts-oidc-provider-pulumi-cloud/index.ts @@ -1,89 +1,103 @@ // Copyright 2024, Pulumi Corporation. All rights reserved. import * as aws from "@pulumi/aws"; +import * as command from "@pulumi/command"; import * as pulumi from "@pulumi/pulumi"; import * as pulumiservice from "@pulumi/pulumiservice"; import * as tls from "@pulumi/tls"; // Configurations const audience = pulumi.getOrganization(); -const config = new pulumi.Config(); -const oidcIdpUrl: string = config.get("oidcIdpUrl") || "https://api.pulumi.com/oidc"; -const escEnv: string = config.require("escEnv"); +const oidcIdpUrl: string = "https://api.pulumi.com/oidc"; // Get TLS thumbprint for OIDC Provider const certs = tls.getCertificateOutput({ url: oidcIdpUrl, }); - const thumbprint = certs.certificates[0].sha1Fingerprint; -// Create a new OIDC Provider -const oidcProvider = new aws.iam.OpenIdConnectProvider("oidcProvider", { - clientIdLists: [audience], - url: oidcIdpUrl, - thumbprintLists: [thumbprint], -}, { - protect: true, -}); +function getProviderArn() { + const existingProvider = aws.iam.getOpenIdConnectProviderOutput({ + url: oidcIdpUrl, + }); + if (existingProvider) { + console.log("OIDC Provider already exists ..."); + // upsert audience + const cmd = new command.local.Command("oidc-client-id", { + create: pulumi.interpolate`aws iam add-client-id-to-open-id-connect-provider --open-id-connect-provider-arn ${existingProvider.arn} --client-id aws:${audience}`, + delete: pulumi.interpolate`aws iam remove-client-id-from-open-id-connect-provider --open-id-connect-provider-arn ${existingProvider.arn} --client-id aws:${audience}`, + }); + return existingProvider.arn; + } else { + console.log("Creating OIDC Provider ..."); + const provider = new aws.iam.OpenIdConnectProvider("oidcProvider", { + clientIdLists: [audience], + url: oidcIdpUrl, + thumbprintLists: [thumbprint], + }, { + protect: true, + }); + return provider.arn; + } +} -// Create a new role that can be assumed by the OIDC provider -const role = new aws.iam.Role("oidcProviderRole", { - assumeRolePolicy: pulumi.all([oidcProvider.url, oidcProvider.arn, audience]).apply(([url, arn, audience]) => JSON.stringify({ - Version: "2012-10-17", - Statement: [{ - Effect: "Allow", - Principal: { Federated: arn }, - Action: "sts:AssumeRoleWithWebIdentity", - Condition: { StringEquals: { [`${url}:aud`]: [audience] } }, +export const arn: pulumi.Output = getProviderArn(); + +const policyDocument = arn.apply(arn => aws.iam.getPolicyDocument({ + version: "2012-10-17", + statements: [{ + effect: "Allow", + actions: ["sts:AssumeRoleWithWebIdentity"], + principals: [{ + type: "Federated", + identifiers: [arn], }], - })), -}); + conditions: [{ + test: "StringEquals", + variable: `api.pulumi.com/oidc:aud`, + values: [`aws:${audience}`], // new format + }], + }], +})); -// Get the existing AdministratorAccess policy -const existingPolicy = aws.iam.getPolicy({ - arn: "arn:aws:iam::aws:policy/AdministratorAccess", +// // Create a new role that can be assumed by the OIDC provider +const role = new aws.iam.Role("role", { + assumeRolePolicy: policyDocument.json, }); - -// Attach other policies to the role as needed -const attach = new aws.iam.RolePolicyAttachment("oidcProviderRolePolicyAttachment", { - role: role, - policyArn: existingPolicy.then(policy => policy.arn), +// Attach the AWS managed policy "AdministratorAccess" to the role. +const rpa = new aws.iam.RolePolicyAttachment("policy", { + policyArn: "arn:aws:iam::aws:policy/AdministratorAccess", + role: role.name, }); - -if (escEnv === ".") { - console.log("Skipping ESC Environment creation ..."); -} -else { - const envJson = pulumi.jsonStringify({ - "values": { - "aws": { - "login": { - "fn::open::aws-login": { - "oidc": { - "duration": "1h", - "roleArn": role.arn, - "sessionName": "pulumi-environments-session", - }, +const envJson = pulumi.jsonStringify({ + "values": { + "aws": { + "login": { + "fn::open::aws-login": { + "oidc": { + "duration": "1h", + "roleArn": role.arn, + "sessionName": "pulumi-environments-session", }, }, }, - "environmentVariables": { - "AWS_ACCESS_KEY_ID": "${aws.login.accessKeyId}", - "AWS_SECRET_ACCESS_KEY": "${aws.login.secretAccessKey}", - "AWS_SESSION_TOKEN": "${aws.login.sessionToken}", - }, }, - }); - - const envAsset = envJson.apply(json => new pulumi.asset.StringAsset(json)); + "environmentVariables": { + "AWS_ACCESS_KEY_ID": "${aws.login.accessKeyId}", + "AWS_SECRET_ACCESS_KEY": "${aws.login.secretAccessKey}", + "AWS_SESSION_TOKEN": "${aws.login.sessionToken}", + }, + }, +}); - const env = new pulumiservice.Environment("oidcEnvironment", { - name: escEnv, - organization: audience, - yaml: envAsset, - }); +const envAsset = envJson.apply(json => new pulumi.asset.StringAsset(json)); -} // end of else +// Create a new environment +const env = new pulumiservice.Environment("aws-oidc-admin", { + name: "test", + // project: "auth", // post esc-GA + organization: audience, + yaml: envAsset, +}); diff --git a/aws-ts-oidc-provider-pulumi-cloud/package.json b/aws-ts-oidc-provider-pulumi-cloud/package.json index 5f85e19aa..f53e2bea4 100644 --- a/aws-ts-oidc-provider-pulumi-cloud/package.json +++ b/aws-ts-oidc-provider-pulumi-cloud/package.json @@ -2,13 +2,14 @@ "name": "aws-ts-oidc-provider-pulumi-cloud", "main": "index.ts", "devDependencies": { - "@types/node": "^20", - "typescript": "^5.4.5" + "@types/node": "^22", + "typescript": "^5.6.2" }, "dependencies": { - "@pulumi/aws": "^6.40.0", - "@pulumi/pulumi": "^3.120.0", - "@pulumi/pulumiservice": "^0.21.2", - "@pulumi/tls": "^5.0.3" + "@pulumi/aws": "^6.52.0", + "@pulumi/command": "^1.0.1", + "@pulumi/pulumi": "^3.133.0", + "@pulumi/pulumiservice": "0.23.2", + "@pulumi/tls": "^5.0.6" } }