diff --git a/gcp-py-oidc-provider-pulumi-cloud/.gitignore b/gcp-py-oidc-provider-pulumi-cloud/.gitignore new file mode 100644 index 000000000..a3807e5bd --- /dev/null +++ b/gcp-py-oidc-provider-pulumi-cloud/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/gcp-py-oidc-provider-pulumi-cloud/Pulumi.yaml b/gcp-py-oidc-provider-pulumi-cloud/Pulumi.yaml new file mode 100644 index 000000000..27f2c605f --- /dev/null +++ b/gcp-py-oidc-provider-pulumi-cloud/Pulumi.yaml @@ -0,0 +1,6 @@ +name: gcp-oidc +runtime: + name: python + options: + virtualenv: venv +description: A minimal Google Cloud Python Pulumi program diff --git a/gcp-py-oidc-provider-pulumi-cloud/README.md b/gcp-py-oidc-provider-pulumi-cloud/README.md new file mode 100644 index 000000000..8e0e65084 --- /dev/null +++ b/gcp-py-oidc-provider-pulumi-cloud/README.md @@ -0,0 +1,74 @@ +# Provisioning an OIDC Provider in Google Cloud for Pulumi Cloud + +This example will create OIDC configuration between Pulumi Cloud and Google Cloud, specifically demonstrating connectivity with [Pulumi ESC](https://www.pulumi.com/docs/pulumi-cloud/esc/). The program automates the process detailed in the Google Cloud documentation for the following activities: + +- [Create Workload Identity Provider and Pool](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers#create_the_workload_identity_pool_and_provider) +- [Authenticate the Workload](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers#authenticate) + +## Prerequisites + +* [Install Pulumi](https://www.pulumi.com/docs/get-started/install/) +* [Configure Pulumi to Use GCP](https://www.pulumi.com/docs/clouds/gcp/get-started/begin/#configure-pulumi-to-access-your-google-cloud-account) +* [Create a Google Cloud Project with the required APIs enabled](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers#configure) + +## Running the Example + +Clone [the examples repo](https://github.com/pulumi/examples/tree/master/gcp-py-oidc-provider) and navigate to the folder for this example. + +```bash +git clone https://github.com/pulumi/examples.git +cd examples/gcp-py-oidc-provider-pulumi-cloud +``` + +Next, to deploy the application and its infrastructure, follow these steps: + +1. Create a new stack, which is an isolated deployment target for this example: + + ```bash + pulumi stack init dev + ``` + +1. Set your Pulumi ESC environment name and the name of your GCP Project: + + ```bash + pulumi config set environmentName # replace with your environment name + pulumi config set gcp:project # replace with your GCP project ID + ``` + +1. Install requirements. + + ```bash + python3 -m venv venv + venv/bin/pip install -r requirements.txt + ``` + +1. Run `pulumi up -y`. Once the program completes, it will output a YAML template for you to use in the next step. + +## Validating the OIDC Configuration + +This next section will walk you through validating your OIDC configuration using [Pulumi ESC](https://www.pulumi.com/docs/pulumi-cloud/esc/). + +Start by [creating a new Pulumi ESC environment](https://www.pulumi.com/docs/pulumi-cloud/esc/get-started/#create-an-environment). Then, copy the template definition from the output in the CLI and paste it into your environment. Save your environment file and run the `pulumi env open /` command in the CLI. You should see output similar to the following: + +```bash +$ pulumi env open myOrg/myEnvironment +{ + "gcp": { + "login": { + "accessToken": "N777Agel_gBF...", + "expiry": "2023-10-12T14:38:00Z", + "project": 842111111111, + "tokenType": "Bearer" + } + } +} +``` + +## Clean-Up Resources + +Once you are done, you can destroy all of the resources as well as the stack: + +```bash +$ pulumi destroy +$ pulumi stack rm +``` diff --git a/gcp-py-oidc-provider-pulumi-cloud/__main__.py b/gcp-py-oidc-provider-pulumi-cloud/__main__.py new file mode 100644 index 000000000..14f28bf60 --- /dev/null +++ b/gcp-py-oidc-provider-pulumi-cloud/__main__.py @@ -0,0 +1,87 @@ +import pulumi +from pulumi_gcp import organizations, iam, serviceaccount +import yaml +import random + +number = random.randint(1000,9999) + +issuer = "https://api.pulumi.com/oidc" + +# Retrieve local Pulumi configuration +pulumi_config = pulumi.Config() +audience = pulumi.get_organization() +env_name = pulumi_config.require("environmentName") +sub_id = f"pulumi:environments:org:{audience}:env:{env_name}" + +# Retrieve project details +project_config = organizations.get_project() +project_id = project_config.number + +# Create a Workload Identity Pool +identity_pool = iam.WorkloadIdentityPool("pulumiOidcWorkloadIdentityPool", + workload_identity_pool_id=f"pulumi-oidc-identity-pool-{number}", + description="Pulumi OIDC Workload Identity Pool", + display_name="Pulumi OIDC Identity Pool" +) + +# Create a Workload Identity Provider +identity_provider = iam.WorkloadIdentityPoolProvider("pulumiOidcIdentityProvider", + workload_identity_pool_id=identity_pool.workload_identity_pool_id, + workload_identity_pool_provider_id=f"pulumi-oidc-provider-{number}", + attribute_mapping={ + "google.subject": "assertion.sub", + }, + oidc=iam.WorkloadIdentityPoolProviderOidcArgs( + issuer_uri=issuer, + allowed_audiences=[ + audience + ] + ) +) + +# Create a service account +service_account = serviceaccount.Account("serviceAccount", + account_id=f"pulumi-oidc-service-acct-{number}", + display_name="Pulumi OIDC Service Account" +) + +# Create an IAM policy binding to grant the identity pool access to the service account +iam_policy_binding = serviceaccount.IAMBinding("iamPolicyBinding", + service_account_id=service_account.name, + role="roles/iam.workloadIdentityUser", + members=identity_pool.name.apply( + lambda name: [f"principalSet://iam.googleapis.com/{name}/*"] + ) +) + +# Generate Pulumi ESC YAML template +def create_yaml_structure(args): + gcp_project, workload_pool_id, provider_id, service_account_email = args + return { + 'values': { + 'gcp': { + 'login': { + 'fn::open::gcp-login': { + 'project': int(gcp_project), + 'oidc': { + 'workloadPoolId': workload_pool_id, + 'providerId': provider_id, + 'serviceAccount': service_account_email + } + } + } + } + } + } + +def print_yaml(args): + yaml_structure = create_yaml_structure(args) + yaml_string = yaml.dump(yaml_structure, sort_keys=False) + print(yaml_string) + +pulumi.Output.all( + project_id, + identity_provider.workload_identity_pool_id, + identity_provider.workload_identity_pool_provider_id, + service_account.email +).apply(print_yaml) diff --git a/gcp-py-oidc-provider-pulumi-cloud/requirements.txt b/gcp-py-oidc-provider-pulumi-cloud/requirements.txt new file mode 100644 index 000000000..e23e793f5 --- /dev/null +++ b/gcp-py-oidc-provider-pulumi-cloud/requirements.txt @@ -0,0 +1,3 @@ +pulumi>=3.0.0,<4.0.0 +pulumi-gcp>=6.0.0,<7.0.0 +PyYAML \ No newline at end of file