Skip to content

Commit 0d8d581

Browse files
committed
Add TypeScript OIDC setup for Pulumi Cloud/Google Cloud
1 parent ef31a15 commit 0d8d581

File tree

5 files changed

+151
-0
lines changed

5 files changed

+151
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/bin/
2+
/node_modules/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name: gcp-ts-oidc-provider-pulumi-cloud
2+
description: A minimal TypeScript Pulumi program
3+
runtime:
4+
name: nodejs
5+
options:
6+
packagemanager: npm
7+
config:
8+
pulumi:tags:
9+
value:
10+
pulumi:template: typescript
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import * as pulumi from "@pulumi/pulumi";
2+
import * as gcp from "@pulumi/gcp";
3+
import * as random from "@pulumi/random";
4+
import * as pcloud from "@pulumi/pulumiservice";
5+
6+
const config = new pulumi.Config();
7+
const gcpConfig = new pulumi.Config("gcp");
8+
9+
const gcpProjectName = gcpConfig.require("project");
10+
11+
// In most cases, it's safe to assume that this stack is run in the same Pulumi
12+
// org in which the OIDC environment is being configured. If not, set the
13+
// escEnvOrg config to the name of the org where the environment is going to be
14+
// configured.
15+
const escEnvOrg = config.get("escEnvOrg") || pulumi.getOrganization();
16+
const escEnvProject = config.get("escEnvProject") || `gcloud`;
17+
const escEnvName = config.get("escEnvName") || `${gcpProjectName}-admin`;
18+
19+
// We use a shorter name for the Workload Identity Pool and Service Account IDs
20+
// because they have character limits of 30 and 32 respectively, and the Google
21+
// Cloud project name is redundant in this context anyway, since we already know
22+
// what Google Cloud project we are in:
23+
const workloadIdentityPoolId = `${escEnvOrg}-admin`;
24+
const serviceAccountId = workloadIdentityPoolId.replace("-", "");
25+
26+
const randomSuffix = new random.RandomString(`random-suffix`, {
27+
length: 5,
28+
lower: true,
29+
upper: false,
30+
special: false
31+
});
32+
33+
// The Workload Identity Pool id uses a random suffix so that this stack can be
34+
// brought up and down repeatably: Workload Identity Pools only soft deletes and
35+
// will auto-purge after 30 days. It is not possible to force a hard delete:
36+
const identityPool = new gcp.iam.WorkloadIdentityPool(`identity-pool`, {
37+
workloadIdentityPoolId: pulumi.interpolate`${workloadIdentityPoolId}-${randomSuffix.result}`
38+
});
39+
40+
const oidcProvider = new gcp.iam.WorkloadIdentityPoolProvider(`identity-pool-provider`, {
41+
workloadIdentityPoolId: identityPool.workloadIdentityPoolId,
42+
workloadIdentityPoolProviderId: `pulumi-cloud-${pulumi.getOrganization()}-oidc`,
43+
oidc: {
44+
issuerUri: "https://api.pulumi.com/oidc",
45+
allowedAudiences: [`gcp:${pulumi.getOrganization()}`]
46+
},
47+
attributeMapping: {
48+
"google.subject": "assertion.sub"
49+
}
50+
});
51+
52+
const serviceAccount = new gcp.serviceaccount.Account("service-account", {
53+
accountId: serviceAccountId,
54+
project: gcpProjectName
55+
});
56+
57+
new gcp.projects.IAMMember("service-account", {
58+
member: pulumi.interpolate`serviceAccount:${serviceAccount.email}`,
59+
role: "roles/admin",
60+
project: gcpProjectName
61+
});
62+
63+
new gcp.serviceaccount.IAMBinding("service-account", {
64+
serviceAccountId: serviceAccount.id,
65+
role: "roles/iam.workloadIdentityUser",
66+
members: [pulumi.interpolate`principalSet://iam.googleapis.com/${identityPool.name}/*`]
67+
});
68+
69+
// fn::open::gcp-login requires project number instead of project name:
70+
const projectNumber = gcp.projects.getProjectOutput({
71+
filter: `name:${gcpProjectName}`
72+
}).projects[0].number
73+
.apply(projectNumber => +projectNumber); // this casts it from string to a number
74+
75+
const envYaml = pulumi.interpolate`
76+
values:
77+
gcp:
78+
login:
79+
fn::open::gcp-login:
80+
project: ${projectNumber}
81+
oidc:
82+
workloadPoolId: ${oidcProvider.workloadIdentityPoolId}
83+
providerId: ${oidcProvider.workloadIdentityPoolProviderId}
84+
serviceAccount: ${serviceAccount.email}
85+
subjectAttributes:
86+
- currentEnvironment.name
87+
pulumiConfig:
88+
gpc:project: \${gcp.login.project}
89+
environmentVariables:
90+
# The Google Cloud SDK (which is used by the Pulumi provider) requires the project to be set by number:
91+
GOOGLE_CLOUD_PROJECT: \${gcp.login.project}
92+
# The gcloud CLI requires the project be set by name, and via a different env var.
93+
# See: https://cloud.google.com/sdk/docs/properties#setting_properties_using_environment_variables
94+
CLOUDSDK_CORE_PROJECT: ${gcpProjectName}
95+
GOOGLE_OAUTH_ACCESS_TOKEN: \${gcp.login.accessToken}
96+
CLOUDSDK_AUTH_ACCESS_TOKEN: \${gcp.login.accessToken}
97+
USE_GKE_GCLOUD_AUTH_PLUGIN: True
98+
`;
99+
100+
const environment = new pcloud.Environment("environment", {
101+
organization: escEnvOrg,
102+
project: escEnvProject,
103+
name: escEnvName,
104+
yaml: envYaml,
105+
});
106+
107+
export const escEnvId = environment.id;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "gcp-ts-oidc-provider-pulumi-cloud",
3+
"main": "index.ts",
4+
"devDependencies": {
5+
"@types/node": "^18",
6+
"typescript": "^5.0.0"
7+
},
8+
"dependencies": {
9+
"@pulumi/gcp": "^8.25.1",
10+
"@pulumi/pulumi": "^3.113.0",
11+
"@pulumi/pulumiservice": "^0.29.1",
12+
"@pulumi/random": "^4.18.0"
13+
}
14+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"compilerOptions": {
3+
"strict": true,
4+
"outDir": "bin",
5+
"target": "es2020",
6+
"module": "commonjs",
7+
"moduleResolution": "node",
8+
"sourceMap": true,
9+
"experimentalDecorators": true,
10+
"pretty": true,
11+
"noFallthroughCasesInSwitch": true,
12+
"noImplicitReturns": true,
13+
"forceConsistentCasingInFileNames": true
14+
},
15+
"files": [
16+
"index.ts"
17+
]
18+
}

0 commit comments

Comments
 (0)