aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.47k stars 3.82k forks source link

@aws-cdk/custom-resources: adding tagging to resource creation causes deployment errors #29816

Open cjhelloletsgo opened 4 months ago

cjhelloletsgo commented 4 months ago

Describe the bug

Creating a custom resource with tagging causes errors with some resource types, the example below all deploy with no problems but fail when the tag parameter is added.

connect_case_app_integration_custom_resource = custom_resources.AwsCustomResource(
    self,
    "Connect Case App Integration Custom Resource",
    on_create=custom_resources.AwsSdkCall(
        service="AppIntegrations",
        action="CreateEventIntegration",
        parameters={
            "EventBridgeBus": "default",
            "EventFilter": {
                "Source": "aws.cases",
            },
            "Name": name,
            "Description": "Amazon Connect app integration for enabling case event streams",
            # Apr 12 2024 - Tags Cause an error with this custom resource for some reason
            # "Tags": self.tags.rendered_tags,
        },
        physical_resource_id=custom_resources.PhysicalResourceId.from_response(
            "EventIntegrationArn"
        ),
    ),
    on_delete=custom_resources.AwsSdkCall(
        service="AppIntegrations",
        action="DeleteEventIntegration",
        parameters={
            "Name": name,
        },
    ),
    policy=custom_resources.AwsCustomResourcePolicy.from_sdk_calls(
        resources=custom_resources.AwsCustomResourcePolicy.ANY_RESOURCE
    ),
    timeout=Duration.seconds(15),
)

connect_cases_app_integration_association_custom_resource = custom_resources.AwsCustomResource(
            self,
            "Connect Cases App Integration Association Custom Resource",
            on_create=custom_resources.AwsSdkCall(
                service="Connect",
                action="createIntegrationAssociation",
                parameters={
                    "InstanceId": connect_instance.attr_id,
                    "IntegrationArn": connect_case_app_integration_custom_resource.get_response_field(
                        "EventIntegrationArn"
                    ),
                    "IntegrationType": "EVENT",
                    "SourceType": "CASES",
                    # Apr 12 2024 - Tags Cause an error with this custom resource for some reason
                    # "Tags": self.tags.rendered_tags,
                },
                physical_resource_id=custom_resources.PhysicalResourceId.from_response(
                    "IntegrationAssociationId"
                ),
            ),
            on_delete=custom_resources.AwsSdkCall(
                service="Connect",
                action="deleteIntegrationAssociation",
                parameters={
                    "InstanceId": connect_instance.attr_id,
                    "IntegrationAssociationId": custom_resources.PhysicalResourceIdReference(),
                },
            ),
            policy=custom_resources.AwsCustomResourcePolicy.from_sdk_calls(
                resources=custom_resources.AwsCustomResourcePolicy.ANY_RESOURCE
            ),
            timeout=Duration.seconds(15),
        )

 connect_cases_association_custom_resource = custom_resources.AwsCustomResource(
            self,
            "Connect Cases Association Custom Resource",
            on_create=custom_resources.AwsSdkCall(
                service="Connect",
                action="createIntegrationAssociation",
                parameters={
                    "InstanceId": connect_instance.attr_arn,
                    "IntegrationArn": connect_cases_domain_custom_resource.get_response_field(
                        "domainArn"
                    ),
                    "IntegrationType": "CASES_DOMAIN",
                    # Apr 12 2024 - Tags Cause an error with this custom resource for some reason
                    # "Tags": self.tags.rendered_tags,
                },
                physical_resource_id=custom_resources.PhysicalResourceId.from_response(
                    "IntegrationAssociationId"
                ),
            ),
            on_delete=custom_resources.AwsSdkCall(
                service="Connect",
                action="deleteIntegrationAssociation",
                parameters={
                    "InstanceId": connect_instance.attr_arn,
                    "IntegrationAssociationId": custom_resources.PhysicalResourceIdReference(),
                },
            ),
            policy=custom_resources.AwsCustomResourcePolicy.from_statements(
                [
                    # This resource will fail to delete without admin permissions with this error: Received response status [FAILED] from custom resource. Message returned: Access denied updating the Amazon Connect service-linked role.
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=[
                            "*",
                        ],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=[
                            "Connect:DescribeInstance",
                        ],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=[
                            "ds:DescribeDirectories",
                        ],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=["app-integrations:CreateEventIntegrationAssociation"],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=["mobiletargeting:GetApp"],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=["cases:GetDomain"],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=["wisdom:GetAssistant"],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=["wisdom:GetKnowledgeBase"],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=["wisdom:TagResource"],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=["voiceid:DescribeDomain"],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=["events:PutTargets"],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=["events:PutRule"],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=["iam:AttachRolePolicy"],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=["iam:CreateServiceLinkedRole"],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=["iam:PutRolePolicy"],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=[
                            "Connect:CreateIntegrationAssociation",
                        ],
                        resources=["*"],
                    ),
                    # Extra Delete Permissions
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=[
                            "connect:DeleteIntegrationAssociation",
                        ],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=[
                            "app-integrations:DeleteEventIntegrationAssociation",
                        ],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=[
                            "app-integrations:DeleteApplicationAssociation",
                        ],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=[
                            "events:ListTargetsByRule",
                        ],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=[
                            "events:RemoveTargets",
                        ],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=[
                            "events:DeleteRule",
                        ],
                        resources=["*"],
                    ),
                ]
            ),
            timeout=Duration.seconds(15),
        )

Expected Behavior

Tags should be added

Current Behavior

A deployment failure

Reproduction Steps

Create a custom resource without tags, it works fine. Create a custom resource with tags it fails to deploy.

Possible Solution

No response

Additional Information/Context

No response

CDK CLI Version

2.137.0 (build bb90b4c)

Framework Version

No response

Node.js Version

v18.13.0

OS

Ubuntu 23.10

Language

Python

Language Version

3.11

Other information

No response

pahud commented 4 months ago

Can you share your error message?

pahud commented 4 months ago

And what if you specify that with a static value like this? Are you still having the error?

"Tags": "dummy-tag"
cjhelloletsgo commented 4 months ago

Here is the error message: 3:49:25 PM | CREATE_FAILED | Custom::AWS | Connect Cases App ...e/Resource/Default Received response status [FAILED] from custom resource. Message returned: User: arn:aws:sts::308665918648:assumed-role/CustomerProfilePortalStac-AWS679f53fac002430cb0da5b-au5K 4NKnYJIu/CustomerProfilePortalStac-AWS679f53fac002430cb0da5-nmnQtaHpE3zi is not authorized to perform: connect: on resource: with an explicit deny (RequestId: 056c8e1e-d024 -42de-a8cf-f39873dfc5a1) 3:49:26 PM | UPDATE_ROLLBACK_IN_P | AWS::CloudFormation::Stack | CustomerProfilePortalStack The following resource(s) failed to create: [ConnectCasesAppIntegrationAssociationCustomResource5F5E5DF8]. 3:49:29 PM | UPDATE_ROLLBACK_COMP | AWS::CloudFormation::Stack | CustomerProfilePortalStack 3:49:33 PM | DELETE_FAILED | AWS::CloudFormation::CustomResource | Connect Cases App ...e/Resource/Default Received response status [FAILED] from custom resource. Message returned: Resource is not a valid ID or ARN. (RequestId: a808b03e-8c80-4a12-975a-61ef6da4d09e)

If I add the connect permission or even admin permissions it gives the same error as well.

If I use a static value like this: "Tags": {"test": "dummy-tag",}, It works as expected

pahud commented 2 months ago
"Tags": self.tags.rendered_tags,

It's not clear to me how rendered_tags is rendered. Can you show more details about it?

github-actions[bot] commented 2 months ago

This issue has not received a response in a while. If you want to keep this issue open, please leave a comment below and auto-close will be canceled.

cjhelloletsgo commented 2 months ago

rendered_tags is supposed to be able to get the tags applied to the stack so those tags can be added to custom resources since the tags won't be automatically applied if set at the stack level for those custom resources.

colifran commented 2 months ago

@cjhelloletsgo I think this is happening because adding tags is triggering an update. Since you're depending on the ARN from getResponseField but no SDK call was configured for onUpdate the update is returning an empty object and the data field you're trying to access doesn't exist hence the error. Can you try configuring an SDK call for the update scenario?

For example, you're trying to access EventIntegrationArn from connect_case_app_integration_custom_resource

IntegrationArn": connect_case_app_integration_custom_resource.get_response_field(
  "EventIntegrationArn"
),

However, you don't have an update SDK call configured for connect_case_app_integration_custom_resource. This means that when an update is triggered by adding tags your data is an empty object - literally {}. In the update scenario EventIntegrationArn won't exist.

cjhelloletsgo commented 2 months ago

I'm pretty sure it does it even when creating a new resource but I could be mistaken. I will try that out and report back on Friday.

colifran commented 2 months ago

@cjhelloletsgo thanks. Let us know!

cjhelloletsgo commented 2 months ago

I tried creating this resource fresh


connect_cases_app_integration_association_custom_resource = custom_resources.AwsCustomResource(
    self,
    "Connect Cases App Integration Association Custom Resource",
    on_create=custom_resources.AwsSdkCall(
        service="@aws-sdk/client-connect",
        action="CreateIntegrationAssociationCommand",
        parameters={
            "InstanceId": connect_instance.attr_id,
            "IntegrationArn": connect_case_app_integration_custom_resource.get_response_field(
                "EventIntegrationArn"
            ),
            "IntegrationType": "EVENT",
            "SourceType": "CASES",
            # Apr 12 2024 - Tags Cause an error with this custom resource for some reason
            "Tags": self.tags.rendered_tags,
        },
        physical_resource_id=custom_resources.PhysicalResourceId.from_response(
            "IntegrationAssociationId"
        ),
        logging=custom_resources.Logging.all(),
    ),
    on_delete=custom_resources.AwsSdkCall(
        service="@aws-sdk/client-connect",
        action="DeleteIntegrationAssociationCommand",
        parameters={
            "InstanceId": connect_instance.attr_id,
            "IntegrationAssociationId": custom_resources.PhysicalResourceIdReference(),
        },
        logging=custom_resources.Logging.all(),
    ),
    policy=custom_resources.AwsCustomResourcePolicy.from_sdk_calls(
        resources=custom_resources.AwsCustomResourcePolicy.ANY_RESOURCE
    ),
    timeout=Duration.minutes(2),
    log_group=custom_resource_log_group,
    install_latest_aws_sdk=False,
)

and received this error: 9:52:35 AM | CREATE_FAILED | Custom::AWS | Connect Cases App ...e/Resource/Default Received response status [FAILED] from custom resource. Message returned: User: arn:aws:sts::308665918648:assumed-role/CustomerProfilePortalStac-AWS679f53fac002430 cb0da5b-au5K4NKnYJIu/CustomerProfilePortalStac-AWS679f53fac002430cb0da5-nmnQtaHpE3zi is not authorized to perform: connect: on resource: with an explicit deny ( RequestId: 0b937e06-f125-45c7-ace2-188def737b08)