pulumi / pulumi-aws

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

Can't use runtime generated role for a Provider #673

Open beetahnator opened 4 years ago

beetahnator commented 4 years ago

I have a usecase where I need to generate an IAM Role and then use it as a provider to generate more AWS resources.

When trying the example below, the first pulumi up always fails since Pulumi tries to assume the role immediately after creation.

import * as aws from "@pulumi/aws";

const role = new aws.iam.Role("testrole", {
  assumeRolePolicy: aws.getCallerIdentity().then(id => {
    return {
      Version: "2012-10-17",
      Statement: [
        {
          Effect: "Allow",
          Principal: {
            AWS: `arn:aws:iam::${id.accountId}:root`
          },
          Action: "sts:AssumeRole"
        }
      ]
    };
  })
});

const rolePolicy = new aws.iam.RolePolicy("test", {
  role: role,
  policy: {
    Version: "2012-10-17",
    Statement: [
      {
        Effect: "Allow",
        Action: "s3:*",
        Resource: "*"
      }
    ]
  }
});

const roleProvider = new aws.Provider(
  "test",
  {
    assumeRole: {
      roleArn: role.arn
    }
  },
  { dependsOn: rolePolicy }
);

// try to make an s3 bucket using role provider
// this will fail once and then work
const s3Bucket = new aws.s3.Bucket(
  "test",
  {},
  { provider: roleProvider, dependsOn: roleProvider }
);

Error output when trying to apply

Do you want to perform this update? yes
Updating (dev):

     Type                     Name       Status         Info
 +   pulumi:pulumi:Stack      tests-dev  created        
 +   ├─ aws:iam:Role          testrole   created        
 +   ├─ aws:iam:RolePolicy    test       created        
 +   ├─ pulumi:providers:aws  test       created        
     └─ aws:s3:Bucket         test       **failed**     1 error

Diagnostics:
  aws:s3:Bucket (test):
    error: The role "arn:aws:iam::XXXXXXXXX:role/testrole-6671910" cannot be assumed.

      There are a number of possible causes of this - the most common are:
        * The credentials used in order to assume the role are invalid
        * The credentials do not have appropriate permission to assume the role
        * The role ARN is not valid
lukehoban commented 4 years ago

What is the error message that you get here?

Note that you could put a delay inside an apply (setTimeout inside the apply) as a workaround

Sounds like the root issue is that the provider needs to retry the assume role lookup to deal with eventual consistency of IAM. That likely require an upstream AWS provider fix.

beetahnator commented 4 years ago

@lukehoban I added the error output above.

~Do you have an example for using setTimeout inside apply?~

I found this example, will try the setTimeout method now.

UPDATE: This seems to work, could be further improved by checking if the role can be assumed inside the .apply instead of using setTimeout

const roleProvider = new aws.Provider(
  "test",
  {
    assumeRole: {
      roleArn: role.arn.apply(async(arn) => {
        if (!pulumi.runtime.isDryRun()) {
          await new Promise(resolve => setTimeout(resolve, 30 * 1000));
        }
        return arn
      })
    }
  },
  { dependsOn: rolePolicy}
);
bincyber commented 4 years ago

@mazamats Thanks for updating the issue with a clear example. I was stuck on the same issue and your example really helped.

For the Python users out there, here's a similar example of how to do the above, but with sts.assume_role() from boto3:

import asyncio
import boto3
import botocore.exceptions
import pulumi_aws as aws
import pulumi

async def wait_for_iam_eventual_consistency(args: list) -> str:
    role_arn          = args[0]
    role_session_name = args[1]

    duration = 3
    attempts = 10

    if not pulumi.runtime.is_dry_run():
        pulumi.log.info(f"Waiting up to {attempts * duration} seconds for IAM eventual consistency for IAM Role: {role_arn}")

        sts = boto3.client('sts')

        for i in range(attempts):
            try:
                sts.assume_role(
                    RoleArn=role_arn,
                    RoleSessionName=role_session_name
                )
            except botocore.exceptions.ClientError:
                await asyncio.sleep(duration)
            else:
                break
    return role_arn

account_id = aws.get_caller_identity().account_id

iam_role = aws.iam.Role(
    resource_name="vpc-admin",
    description='IAM Role for VPC Administration',
    path='/',
    force_detach_policies=True,
    assume_role_policy={
        "Version": "2012-10-17",
        "Statement": [{
            "Sid": "AllowAccountAssumeRole",
            "Action": "sts:AssumeRole",
            "Principal": {
                "AWS": f"arn:aws:iam::{account_id}:root"
            },
            "Effect": "Allow"
        }]
    }
)

iam_role_policy_attachment = aws.iam.RolePolicyAttachment(
    resource_name="vpc-admin-policy-attachment",
    role=iam_role.id,
    policy_arn="arn:aws:iam::aws:policy/AmazonVPCFullAccess"
)

session_name = "VpcAdmin"
role_arn     = pulumi.Output.all(iam_role.arn, session_name).apply(wait_for_iam_eventual_consistency)

iam_role_provider = aws.Provider(
    resource_name='vpc-admin-provider',
    assume_role={
        "role_arn": role_arn,
        "session_name": session_name
    },
    opts=pulumi.ResourceOptions(
        depends_on=[
            iam_role_policy_attachment
        ]
    )
)

aws.ec2.Vpc(
    resource_name="test-vpc",
    cidr_block="10.100.0.0/16",
    enable_dns_hostnames=True,
    enable_dns_support=True,
    tags={
        "Name": "test-vpc",
        "CreatedBy": "vpc-admin-provider"
    },
    opts=pulumi.ResourceOptions(
        parent=iam_role_provider,
        provider=iam_role_provider
    )
)
lukehoban commented 1 year ago

This appears to be due to upstream https://github.com/hashicorp/terraform-provider-aws/issues/6566.