-
Notifications
You must be signed in to change notification settings - Fork 878
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create Pulumi to AWS OIDC Configuration Example #1507
Changes from all commits
4367c2e
a2494fa
0f86092
1fe0784
351273e
239a8bb
1835913
971c049
8adac12
0f4e1ad
d8edd55
825e282
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
*.pyc | ||
venv/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
# Provisioning an OIDC Provider in AWS for Pulumi Cloud | ||
|
||
WIP - This folder probably needs a better name to reflect that this example is configuring OIDC connection between Pulumi and AWS. | ||
|
||
This example is an automation of the process detailed in the [AWS documentation for creating an OIDC provider](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html). This automation will create OIDC configuration between Pulumi Cloud and AWS. | ||
|
||
## Prerequisites | ||
|
||
* [Install Pulumi](https://www.pulumi.com/docs/get-started/install/) | ||
* [Configure Pulumi to Use AWS](https://www.pulumi.com/docs/intro/cloud-providers/aws/setup/) (if your AWS CLI is configured, no further changes are required) | ||
|
||
## Running the Example | ||
|
||
Clone [the examples repo](https://github.com/pulumi/examples/tree/master/aws-py-oidc-provider) and navigate to the folder for this example. | ||
|
||
```bash | ||
git clone https://github.com/pulumi/examples.git | ||
cd examples/aws-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 organization name and desired AWS region: | ||
|
||
```bash | ||
pulumi config set pulumiOrg <your-pulumi-org-name> # replace with your Pulumi organization name | ||
pulumi config set aws:region us-east-1 # any valid AWS region will work | ||
``` | ||
|
||
1. Install requirements. | ||
|
||
```bash | ||
pip3 install -r requirements.txt | ||
``` | ||
|
||
1. Run `pulumi up`. | ||
|
||
```bash | ||
$ pulumi up -y | ||
Updating (dev) | ||
|
||
Type Name Status Info | ||
+ pulumi:pulumi:Stack oidc-python-dev created (4s) 8 messages | ||
+ ├─ aws:iam:OpenIdConnectProvider oidcProvider created (0.78s) | ||
+ └─ aws:iam:Role oidcProviderRole created (0.75s) | ||
|
||
Diagnostics: | ||
pulumi:pulumi:Stack (oidc-python-dev): | ||
Forming configuration document URL... | ||
Extracting domain name from jwks_uri... | ||
Retrieving OpenSSL certificates (this will take some time)... | ||
Retrieving last OpenSSL certificate... | ||
Saving certificate to file... | ||
Retrieving certificate thumbprint... | ||
Creating OIDC provider... | ||
Creating Provider IAM role... | ||
|
||
Outputs: | ||
OidcProviderRoleArn: "arn:aws:iam::219544202541:role/oidcProviderRole-c368d93" | ||
|
||
Resources: | ||
+ 3 created | ||
``` | ||
## 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, add the following environment definition, replacing the value of `roleArn` with the value of the `OidcProviderRoleArn` from your stack outputs. | ||
|
||
``` | ||
values: | ||
aws: | ||
login: | ||
fn::open::aws-login: | ||
oidc: | ||
duration: 1h | ||
roleArn: <your-oidc-role-arn> | ||
sessionName: pulumi-environments-session | ||
``` | ||
|
||
Save your environment file and run the `pulumi env open <your-pulumi-org>/<your-environment>` command in the CLI. You should see output similar to the following: | ||
|
||
```bash | ||
{ | ||
"aws": { | ||
"login": { | ||
"accessKeyId": "ASIA......", | ||
"secretAccessKey": "PYP.....", | ||
"sessionToken": "FwoGZ....." | ||
} | ||
} | ||
} | ||
``` | ||
|
||
You can configure more granular access control by adding the `sub` claim to the Provider role's trust policy conditions with the appropriate pattern. In the following example, the role may only be assumed by the specific Pulumi ESC environment that you designate. | ||
|
||
```json | ||
{ | ||
"Version": "2012-10-17", | ||
"Statement": [ | ||
{ | ||
"Effect": "Allow", | ||
"Principal": { | ||
"Federated": "arn:aws:iam::616138583583:oidc-provider/api.pulumi.com/oidc" | ||
}, | ||
"Action": "sts:AssumeRoleWithWebIdentity", | ||
"Condition": { | ||
"StringEquals": { | ||
"api.pulumi.com/oidc:aud": "<your-pulumi-org>", | ||
"api.pulumi.com/oidc:sub": "pulumi:environments:org:<your-pulumi-org>:env:<your-environment-name>" | ||
} | ||
} | ||
} | ||
] | ||
} | ||
``` | ||
Once you are done, you can destroy all of the resources as well as the stack: | ||
|
||
```bash | ||
$ pulumi destroy | ||
$ pulumi stack rm | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import pulumi | ||
from pulumi_aws import iam | ||
import requests | ||
import subprocess | ||
import OpenSSL | ||
import json | ||
|
||
config = pulumi.Config() | ||
|
||
audience = config.require("pulumiOrg") | ||
oidc_idp_url = 'https://api.pulumi.com/oidc' | ||
base_url = 'api.pulumi.com/oidc' | ||
|
||
# Obtain the OIDC IdP URL and form the configuration document URL | ||
print("Forming configuration document URL...") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You probably don't want There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a Pulumi-specific way of doing this to where it doesn't print if there's been no changes? I tried with |
||
configuration_url = f'{oidc_idp_url}/.well-known/openid-configuration' | ||
|
||
# Locate "jwks_uri" and extract the domain name | ||
print("Extracting domain name from jwks_uri...") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably be modeled as a resource using the Command provider: https://www.pulumi.com/registry/packages/command/api-docs/local/command/ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really understand the ask here. Can we sync on this and the TLS comment? |
||
response = requests.get(configuration_url) | ||
jwks_uri = response.json().get('jwks_uri', '') | ||
domain_name = jwks_uri.split('/')[2] | ||
|
||
# Run OpenSSL command to get certificates | ||
print("Retrieving OpenSSL certificates (this will take some time)...") | ||
command = f'openssl s_client -servername {domain_name} -showcerts -connect {domain_name}:443' | ||
result = subprocess.run(command, shell=True, capture_output=True, text=True) | ||
certificates = result.stdout.split('-----END CERTIFICATE-----') | ||
|
||
# Get the last certificate from the output | ||
print("Retrieving last OpenSSL certificate...") | ||
last_certificate = certificates[-2] + '-----END CERTIFICATE-----' | ||
|
||
# Save the certificate to a file | ||
print("Saving certificate to file...") | ||
with open('certificate.crt', 'w') as file: | ||
file.write(last_certificate) | ||
|
||
# Get the thumbprint of the final certificate | ||
print("Retrieving certificate thumbprint...") | ||
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, last_certificate) | ||
thumbprint = (x509.digest('sha1').decode()).replace(":", "") | ||
|
||
# Create an OIDC identity provider | ||
print("Creating OIDC provider...") | ||
oidc_provider = iam.OpenIdConnectProvider("oidcProvider", | ||
client_id_lists=[audience], | ||
thumbprint_lists=[thumbprint], | ||
url=oidc_idp_url | ||
) | ||
|
||
# Create an IAM role with a trust policy that trusts the OIDC provider | ||
print("Creating Provider IAM role...") | ||
def create_assume_role_policy(args): | ||
url, arn, audience = args | ||
policy = { | ||
"Version": "2012-10-17", | ||
"Statement": [{ | ||
"Effect": "Allow", | ||
"Principal": {"Federated": arn}, | ||
"Action": "sts:AssumeRoleWithWebIdentity", | ||
"Condition": {"StringEquals": {f"{url}:aud": audience}} | ||
}] | ||
} | ||
return json.dumps(policy) | ||
|
||
oidc_role = iam.Role("oidcProviderRole", | ||
assume_role_policy=pulumi.Output.all(oidc_provider.url, oidc_provider.arn, audience).apply(create_assume_role_policy) | ||
) | ||
|
||
# Export the ARN of the IAM role | ||
pulumi.export("OidcProviderRoleArn", oidc_role.arn) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
pulumi>=3.0.0,<4.0.0 | ||
pulumi-aws>=6.0.2,<7.0.0 | ||
certifi==2023.7.22 | ||
cffi==1.15.1 | ||
charset-normalizer==3.3.0 | ||
cryptography==41.0.4 | ||
idna==3.4 | ||
pycparser==2.21 | ||
pyOpenSSL==23.2.0 | ||
requests==2.31.0 | ||
urllib3==1.26.7 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use the TLS provider to generate certs: https://www.pulumi.com/registry/packages/tls/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The steps that use OpenSSL are obtaining Pulumi's OIDC IdP certificate chain and using that to produce its thumbprint (it's not creating a new one). It wasn't clear to me how pulumi_tls would recreate this process, so happy to pair with you on this!