pulumi / pulumi-aws

An Amazon Web Services (AWS) Pulumi resource package, providing multi-language access to AWS
Apache License 2.0
466 stars 157 forks source link

assumeRoleWithWebIdentity with webIdentityTokenCommand #3390

Open wvanderdeijl opened 9 months ago

wvanderdeijl commented 9 months ago

Hello!

Issue details

We love the new assumeRoleWithWebIdentity feature where we can use an OIDC token to authenticate to AWS. Currently it supports either webIdentityToken or webIdentityTokenFile in the config with the value of the token or a filename of the file that contains the token. It would be great if we can get a third option webIdentityTokenCommand (or something similar) that executes a shell command to get the token. We would use that to have pulumi invoke gcloud auth print-identity-token to get a GCP identity token as we have setup AWS to trust Google cloud tokens.

This feature could also be used if people have other complex methods to get a (fresh) id-token.

An alternative would be if we can get the token from our pulumi code itself (typescript in our case) and somehow inject it into the aws provider.

Affected area/feature

This affects the "Authenticate with WebIdentity and OpenID Connect (OIDC)" feature of @pulumi/aws

t0yv0 commented 9 months ago

Thank you for the suggestion and the feedback @wvanderdeijl

I'm curious which language are you using to drive Pulumi?

I am thinking perhaps of suggesting to try explicit providers here: https://www.pulumi.com/docs/concepts/resources/providers/#explicit-provider-configuration

In a language like TypeScript it may be possible to reach out to a shell command before passing the result as an input to the AWS resource. If you are using YAML it may be little trickier, but have you seen the command provider?

https://www.pulumi.com/registry/packages/command/api-docs/local/command/

With this little bit of indirection it might be possible to shell out to get the token and then pass it to the explicit provider to accomplish your goal.

wvanderdeijl commented 9 months ago

Thanks for the tip. I tried something from typescript. Not even with a shell command but just getting the token from typescript:

import * as aws from '@pulumi/aws';
import { all, output, secret } from '@pulumi/pulumi';
import { auth } from 'google-auth-library';

const serviceAccount = output('my-service-account@my-project.iam.gserviceaccount.com');
const audience = output('*****');
const awsRoleArn = output('arn:aws:iam::999999999999:role/pulumi-aws-federation');

const token = secret(
    all([serviceAccount, audience]).apply(async ([serviceAccount, audience]) => {
        const client = await auth.getClient();
        const response = await client.request<{ token: string }>({
            method: 'POST',
            url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${encodeURIComponent(
                serviceAccount,
            )}:generateIdToken`,
            data: {
                audience,
                includeEmail: true,
            },
        });
        return response.data.token;
    }),
);
const provider = new aws.Provider('aws', {
    region: 'eu-west-1',
    assumeRoleWithWebIdentity: {
        roleArn: awsRoleArn,
        webIdentityToken: token,
    },
});
new aws.s3.Bucket('my-bucket-53efcbd4499a768d', { tags: { foo: 'bar' } }, { provider });

This works, although every pulumi preview shows the aws provider as outdated due to the changing webIdentityToken. But the dependent resources (such as the S3 bucket) do not show up as outdated and are not touched during pulumi up.

Although I am sure this won't work if I remove both the explicit provider and S3 bucket from the typescript file and attempt a pulumi up. It will the use the provider and S3 information from the pulumi state file and that provider will have an expired token in it. I still have to test if it will work if I only delete the S3 bucket form the typescript code, but keep the provider. Will it then use the updated provider with a fresh token or the stale provider and token?

wvanderdeijl commented 9 months ago

I waited for a couple of days so the token in the provider in the pulumi state has expired. As expected removing both the provider and the s3 bucket from the pulumi typescript file will result in an error as the stale information from the state file is used:


  aws:s3:Bucket (my-bucket-53efcbd4499a768d):
    error: 1 error occurred:
        * Cannot assume IAM Role with web identity: IAM Role (arn:aws:iam:: 999999999999:role/pulumi-aws-federation) cannot be assumed with web identity token.

    There are a number of possible causes of this - the most common are:
      * The web identity token used in order to assume the role is invalid
      * The web identity token does not have appropriate permission to assume the role
      * The role ARN is not valid

    Error: failed to retrieve credentials, operation error STS: AssumeRoleWithWebIdentity, https response error StatusCode: 400, RequestID: 81525888-7396-431b-ab19-440fb6c93ae7, ExpiredTokenException: Token expired: current date/time 1707724980 must be before the expiration date/time 1707493014

Removing only the s3 bucket resource from the pulumi stack, but keeping the provider does work. Pulumi first updates the provider with a fresh token and than uses that updated provider to remove the s3 bucket.

With the caveat that you should not remove the provider in one go, we can use this workaround. However, I still feel having a webIdentityTokenCommand setting would be a preferable long term solution.