Hey there, I'm trying to use the pulumi-cdk packag...
# aws
w
Hey there, I'm trying to use the pulumi-cdk package to deploy one of my AWS CDK service stacks (brought in as an npm dependency in my pulumi typescript app). I expected my
provider
argument I setup to pass through into the CDK related harness to just work , but it didn't. My provider is one which assumes a role in another account, and I need this to be able to deploy this CDK lib into different AWS accounts from a 'central' account. The default provider that the pulumi-cdk harness seems to use is therefore one with credentials from my 'central' account and everything gets deployed into that account, which is not my desired state. Am I missing anything here, or is this a limitation that I'm not going to be able to get around? This issue seems to be the one I am facing: https://github.com/pulumi/pulumi-cdk/issues/61
n
You should be able to pass in a provider to your cdk app https://www.pulumi.com/docs/iac/clouds/aws/guides/cdk/#customizing-providers
w
Thanks @numerous-book-75463, I'll double check things. Maybe I missed one of the optional provider args somewhere...
Ah, that seemed to fix the pulumi related cdk native resources and where they should go - those are working now (I had forgotten to pass the provider to the
<http://pulumicdk.App|pulumicdk.App>
constructor. However, the stack I'm creating in the
<http://pulumicdk.App|pulumicdk.App>
callback seems to be going into the 'central' AWS account now. So I'm halfway there 😅
This is what I'm working with, and it looks like I'm passing my assume role AWS provider into all the correct places. I can't see what else I'm missing.
Copy code
import * as aws from '@pulumi/aws';
import * as pulumi from '@pulumi/pulumi';
import * as pulumicdk from '@pulumi/cdk';
import { ConsumerInfraConstruct } from "@redacted/service-hub-consumer-infra";
import { ServiceHubEnvironment, ServiceHubManagementAccountId } from '../../types/service-hub';

interface ServiceHubTargetInfraStackOptions extends pulumicdk.StackOptions {
    managementAccountId: ServiceHubManagementAccountId;

    serviceHubS3BucketArn: string;

    secretArn: string;

    environment: ServiceHubEnvironment;
}

class ServiceHubInfraStack extends pulumicdk.Stack {
    serviceRoleArn: pulumi.Output<string>;

    constructor(scope: pulumicdk.App, id: string, options: ServiceHubTargetInfraStackOptions) {
        super(scope, id, options);

        const service = new ConsumerInfraConstruct(this, 'service', {
            managementAccountId: options.managementAccountId,
            serviceHubS3BucketArn: options.serviceHubS3BucketArn,
            secretArn: options.secretArn,
            environment: options.environment,
        });

        this.serviceRoleArn = this.asOutput(service.serviceRole.roleArn);
    }
}

export class ServiceHubTargetInfraComponent extends pulumi.ComponentResource {

    constructor(id: string, args: { 
        provider: aws.Provider,
        serviceHubEnvironment: ServiceHubEnvironment,
    }, opts?: pulumi.ComponentResourceOptions) {

        // By calling super(), we ensure any instantiation of this class
        // inherits from the ComponentResource class so we don't have to
        // declare all the same things all over again.
        super("pkg:index:ServiceHubTargetInfraComponent", id, args, opts);

        const { provider, serviceHubEnvironment } = args;

        // Work out service hub settings based on only one service hub setting (the serviceHubEnvironment), so that we reduce setting fatigue for users/cloud operations.
        const managementAccountId = 'redacted';
        const serviceHubS3BucketArn = 'redacted';
        const secretArn = 'redacted';

        // Pulumi's CDK adapter requires you to define an App and a Stack
        const app = new pulumicdk.App('app', (scope: pulumicdk.App): pulumicdk.AppOutputs => {
            const stack = new ServiceHubInfraStack(scope, 'service-hub-target-account-infra', {
                managementAccountId: managementAccountId,
                serviceHubS3BucketArn: serviceHubS3BucketArn,
                secretArn: secretArn,
                environment: serviceHubEnvironment,
                provider: provider,
            });

            return {
                serviceRoleArn: stack.serviceRoleArn,
            };
        }, {
            // This is fine, since we are using a cross-account provider, and the stack is deployed in the desired target account.
            provider: provider
        });


        // Register any Pulumi outputs from the stack if needed
        this.registerOutputs({}); // This is required for a ComponentResource
    }
}
n
You have to pass in both the
aws
provider and the
aws-native
provider
w
Thanks Cory. I made progress off the back of your suggestion. Am I right in thinking the Cloud Control API is the one that is used in my case as the 'target account' provider (
aws-native
?) and the other provider is what my 'central' account uses (and hosts the pulumi CDK policies bucket (e.g.
pulumi-cdk-rol-policies-prod-staging...
) ? I passed through both providers to the app and stack, and also set the
props
on the stack to provide an explicit
account
and
region
to the cdk stack (as it looks like it becomes environment-agnostic after passing in more than one provider). This got me to a state where my target account received the expected CDK-defined resources, but my SNS topic resource failed to fully deploy with this error:
Copy code
error: 1 error occurred:
        * setting SNS Topic (arn:aws:sns:eu-west-1:123456789012:service-hub-build-status-changes) attribute (Policy): operation error SNS: SetTopicAttributes, https response error StatusCode: 403, RequestID: 123456-a513-5144-acdc-1762061ac1ff, AuthorizationError: User: arn:aws:sts::666666666666:assumed-role/AWSReservedSSO_redacted/redacted@redacted.com is not authorized to perform: SNS:SetTopicAttributes on resource: arn:aws:sns:eu-west-1:TARGET_ACCOUNT_ID_HERE:service-hub-build-status-changes because no resource-based policy allows the SNS:SetTopicAttributes action
This error suggests that the resource referred to (the SNS topic) is trying to be modified by my calling user account, which exists in my 'central' account - so somehow most CDK resources are being deployed by the
aws-native
provider, but one part of those CDK resources are falling back to the
aws
'central' account provider. This is what that part of my CDK library code looks like:
Copy code
const snsTopic = new Topic(this, 'CodeBuildProjectTopic', {
      topicName: 'service-hub-build-status-changes',
    });

    snsTopic.addSubscription(new LambdaSubscription(lambda));

    // add the <http://codestar-notifications.amazonaws.com|codestar-notifications.amazonaws.com> service principal to the sns topic
    snsTopic.grantPublish(new ServicePrincipal('<http://codestar-notifications.amazonaws.com|codestar-notifications.amazonaws.com>'));
Any idea why this might be happening?
n
With Pulumi CDK apps most of the resources are deployed with the
aws-native
provider, but some of the resources do not exist in
aws-native
and are deployed with the
aws
provider. I know that the
sns.Topic
resource is deployed with
aws-native
and
sns.TopicPolicy
is deployed with
aws
. Are you using the
providers
option and passing both the
aws-native
and
aws
providers?
w
Thanks @numerous-book-75463, I just read your message now, and had in the meantime just arrived at this conclusion. I got it working. After some experimentation providing different combos of the aws and aws-native ones, I observed the error message wording changing and concluded that some resources needed the native, and some not. As a user, I found the docs could have done a little better at perhaps explaining this point exactly. (Or maybe I missed them!) So yes, I am using the
providers
option and passing both now, with them both being configured with my dynamic target account role that needs to be assumed. This got me there. The last point I'm on now is trying to customise the staging bucket. I need my target accounts to be able to Get/List the assets in this (lambda pkgs etc), and noticed it is created in my 'central' account. I found that I could create a custom stack synthesizer, and specify the bucket name, and then pulumi-cdk seems to create that bucket + a random ID suffix. For now, I've manually edited the bucket's policy to add a policy that allows any Account principal in my organization to have Get access which is working now, but it required that one manual intervention. Is there a way to customise this staging bucket's policy myself with pulumi?
What I ended up with on the staging bucket (after manually updating the policy (with exact ARNs/names/IDs changed for privacy)
Copy code
{
  "Version": "2012-10-17",
  "Id": "require-ssl",
  "Statement": [
    {
      "Sid": "ssl",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::pulumi-cdk-staging-assets-access-123456789012-9f15c80",
        "arn:aws:s3:::pulumi-cdk-staging-assets-access-123456789012-9f15c80/*"
      ],
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    },
    {
      "Sid": "AllowOrgGetListAccess",
      "Effect": "Allow",
      "Principal": "*",
      "Action": [
        "s3:Get*",
        "s3:List*"
      ],
      "Resource": [
        "arn:aws:s3:::pulumi-cdk-staging-assets-access-123456789012-9f15c80",
        "arn:aws:s3:::pulumi-cdk-staging-assets-access-123456789012-9f15c80/*"
      ],
      "Condition": {
        "StringEquals": {
          "aws:PrincipalOrgID": "o-MYORGIDHERE"
        }
      }
    }
  ]
}
n
I don't think we have this documented anywhere, but the way you would do this is to create your own [PulumiSynthesizer](https://github.com/pulumi/pulumi-cdk/blob/ff43bfcbbd4ace37eaf8efd32f494193f4f9693a/src/synthesizer.ts#L149-L149). I think you should be able to create your own synthesizer that extends this one. We haven't made it super easy right now to do this, we probably should switch a lot of the
private
methods to
protected
so that you can override them.
w
That's what I've done so far:
Copy code
const stackSynthesizer = new pulumicdk.synthesizer.PulumiSynthesizer({
            appId: `pulcdk-${pulumi.getStack()}`,
            autoDeleteStagingAssets: true,
            stagingBucketName: 'pulumi-cdk-staging-assets-for-access-123456789012',
            // Note: this bucket is automatically created by Pulumi, and gets some random suffix added. I needed to manually edit the bucket resource policy 
            // to allow org-wide Get/List access to assets in the bucket so our target accounts can access the assets when pulumi-cdk the deploy-hub-infra stack to them.
            // TODO: it would be nice to be able to set this bucket policy here on the stackSynthesizer.
        });
I needed to dig into the .d.ts files to figure out that my import needed
/synthesizer
added too. Yes - that would be amazing if the private methods could be changed so that they are able to be overridden, specifically allowing customisation on the
getCreateBucket
method options such as policy would be perfect - https://github.com/pulumi/pulumi-cdk/blob/ff43bfcbbd4ace37eaf8efd32f494193f4f9693a/src/synthesizer.ts#L318