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.33k stars 3.76k forks source link

(event-targets): EcsTask uses invalid task definition arn in policy #30390

Open blimmer opened 4 weeks ago

blimmer commented 4 weeks ago

Describe the bug

AWS recently sent this warning:

We are contacting you because we recently identified an issue with 
Amazon Elastic Container Service (Amazon ECS) APIs that require your 
action. When calling the RunTask [1], StartTask [2], CreateService [3], 
CreateTaskSet [4], UpdateService [5] APIs, users can specify a task 
revision number to launch a specific version of that task [6]. We 
identified an issue that resulted in inconsistencies in how the Identity
 and Access Management (IAM) policies are enforced during request 
authorization to the above mentioned APIs. Specifically, resource 
condition keys specifying task-definition families without a revision 
number could potentially be interpreted differently if the task 
definition did not include a revision number when the API was called. As
 a result, the latest version of the task would be selected. We have 
implemented a fix and can confirm the service is operating as expected. 

We identified your account has made requests to one or more of the 
affected ECS APIs. We recommend you review the policies listed in the 
"Affected resources" tab of your AWS Health Dashboard to ensure the 
resource condition keys specifying task-definition families include a 
revision number. To give you time to review and make necessary changes, 
we have added your account to an allow list until October 15, 2024. If 
you wish to be removed from the allow list prior to October 15, 2024, 
you can do so by creating an AWS Support case [7]. If you do not take 
any action by that date, calls to the affected APIs will result in 
AccessDeniedException error messages. 

To correctly enforce IAM policy-based decisions after October 15, 2024, 
you must specify a revision number, or the wildcard (‘*’) for the task 
definition family, on task definition ARNs when used as a resource type.

An example of a policy that will correctly ALLOW for all tasks definition revisions is shown below:

{

    "Version": "2012-10-17",

    "Statement": [

        {

            "Effect": "Allow",

            "Action": "ecs:RunTask",

            "Resource": "arn:aws:ecs:*:*:task-definition/sleep360:*"

        }

    ]

}

An example of a policy that will correctly DENY for all tasks definition revisions is shown below:

{

    "Version": "2012-10-17",

    "Statement": [

        {

            "Effect": "Deny",

            "Action": "ecs:RunTask",

            "Resource": "arn:aws:ecs:*:*:task-definition/sleep360:*"

        }

    ]

}

When you intend to apply the policy for a specific revision of the task 
definition, you must specify the revision in the resource ARN. The 
following is an example of a policy would correctly enforce ALLOW for a 
specific task definition revision:

{

    "Version": "2012-10-17",

    "Statement": [

        {

            "Effect": "Allow",

            "Action": "ecs:RunTask",

            "Resource": "arn:aws:ecs:*:*:task-definition/sleep360:7"

        }

    ]

}

An example of a policy would correctly enforce DENY for a specific task definition revision:

{

    "Version": "2012-10-17",

    "Statement": [

        {

            "Effect": "Deny",

            "Action": "ecs:RunTask",

            "Resource": "arn:aws:ecs:*:*:task-definition/sleep360:7"

        }

    ]

}

If you have questions or concerns, please contact AWS Support [7].

[1] [https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_RunTask.html](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_RunTask.html)

[2] [https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_StartTask.html](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_StartTask.html)

[3] [https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_CreateService.html](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_CreateService.html)

[4] [https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_CreateTaskSet.html](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_CreateTaskSet.html)

[5] [https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_UpdateService.html](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_UpdateService.html)

[6] [https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonelasticcontainerservice.html#amazonelasticcontainerservice-resources-for-iam-policies](https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonelasticcontainerservice.html#amazonelasticcontainerservice-resources-for-iam-policies)

[7] [https://aws.amazon.com/support](https://aws.amazon.com/support)

Sincerely,

Amazon Web Services

Of our affected resources, many were policies generated by the EcsTask target: https://github.com/aws/aws-cdk/blob/8b234b71f2bbfec8ceca4e062674290eb51c8c9b/packages/aws-cdk-lib/aws-events-targets/lib/ecs-task.ts#L280-L292

Expected Behavior

The EcsTask construct should generate a valid task definition reference in the generated policy.

Current Behavior

The generated policy includes an invalid reference, like this:

{
  "TaskDefImportEventsRoleDefaultPolicyA6BD21B8": {
   "Type": "AWS::IAM::Policy",
   "Properties": {
    "PolicyDocument": {
     "Statement": [
      {
       "Action": "ecs:RunTask",
       "Condition": {
        "ArnEquals": {
         "ecs:cluster": {
          "Fn::GetAtt": [
           "ClusterEB0386A7",
           "Arn"
          ]
         }
        }
       },
       "Effect": "Allow",
       "Resource": "arn:aws:ecs:us-west-2:123456789101:task-definition/MyTask"
      },
      {
       "Action": "ecs:TagResource",
       "Effect": "Allow",
       "Resource": {
        "Fn::Join": [
         "",
         [
          "arn:",
          {
           "Ref": "AWS::Partition"
          },
          ":ecs:",
          {
           "Ref": "AWS::Region"
          },
          ":*:task/",
          {
           "Ref": "ClusterEB0386A7"
          },
          "/*"
         ]
        ]
       }
      },
      {
       "Action": "iam:PassRole",
       "Effect": "Allow",
       "Resource": "arn:aws:iam::123456789101:role/MyTaskRole"
      }
     ],
     "Version": "2012-10-17"
    },
    "PolicyName": "TaskDefImportEventsRoleDefaultPolicyA6BD21B8",
    "Roles": [
     {
      "Ref": "TaskDefImportEventsRole9184C90E"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/TaskDefImport/EventsRole/DefaultPolicy/Resource"
   }
  }
}

Specifically, "Resource": "arn:aws:ecs:us-west-2:123456789101:task-definition/MyTask" needs to be "Resource": "arn:aws:ecs:us-west-2:123456789101:task-definition/MyTask:*" to comply with the new requirements.

Reproduction Steps

This stack

import { Duration, Stack, type StackProps } from 'aws-cdk-lib';
import { Cluster, FargateTaskDefinition, NetworkMode } from 'aws-cdk-lib/aws-ecs';
import { Rule, Schedule } from 'aws-cdk-lib/aws-events';
import { EcsTask } from 'aws-cdk-lib/aws-events-targets';
import { Role } from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export class TestCdkStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const cluster = new Cluster(this, 'Cluster');
    const taskDefinition = FargateTaskDefinition.fromFargateTaskDefinitionAttributes(this, "TaskDefImport", {
      taskDefinitionArn: "arn:aws:ecs:us-west-2:123456789101:task-definition/MyTask",
      taskRole: Role.fromRoleArn(this, "RoleImport", "arn:aws:iam::123456789101:role/MyTaskRole"),
      networkMode: NetworkMode.AWS_VPC,
    });
    new Rule(this, "Rule", {
      targets: [new EcsTask({
        cluster,
        taskDefinition
      })],
      schedule: Schedule.rate(Duration.days(1)),
    });
  }
}

Produces

{
 "Resources": {
  "ClusterEB0386A7": {
   "Type": "AWS::ECS::Cluster",
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Resource"
   }
  },
  "ClusterVpcFAA3CEDF": {
   "Type": "AWS::EC2::VPC",
   "Properties": {
    "CidrBlock": "10.0.0.0/16",
    "EnableDnsHostnames": true,
    "EnableDnsSupport": true,
    "InstanceTenancy": "default",
    "Tags": [
     {
      "Key": "Name",
      "Value": "TestCdkStack/Cluster/Vpc"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/Resource"
   }
  },
  "ClusterVpcPublicSubnet1SubnetA9F7E0A5": {
   "Type": "AWS::EC2::Subnet",
   "Properties": {
    "AvailabilityZone": {
     "Fn::Select": [
      0,
      {
       "Fn::GetAZs": ""
      }
     ]
    },
    "CidrBlock": "10.0.0.0/18",
    "MapPublicIpOnLaunch": true,
    "Tags": [
     {
      "Key": "aws-cdk:subnet-name",
      "Value": "Public"
     },
     {
      "Key": "aws-cdk:subnet-type",
      "Value": "Public"
     },
     {
      "Key": "Name",
      "Value": "TestCdkStack/Cluster/Vpc/PublicSubnet1"
     }
    ],
    "VpcId": {
     "Ref": "ClusterVpcFAA3CEDF"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PublicSubnet1/Subnet"
   }
  },
  "ClusterVpcPublicSubnet1RouteTable5594A636": {
   "Type": "AWS::EC2::RouteTable",
   "Properties": {
    "Tags": [
     {
      "Key": "Name",
      "Value": "TestCdkStack/Cluster/Vpc/PublicSubnet1"
     }
    ],
    "VpcId": {
     "Ref": "ClusterVpcFAA3CEDF"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PublicSubnet1/RouteTable"
   }
  },
  "ClusterVpcPublicSubnet1RouteTableAssociation0FBEF1F4": {
   "Type": "AWS::EC2::SubnetRouteTableAssociation",
   "Properties": {
    "RouteTableId": {
     "Ref": "ClusterVpcPublicSubnet1RouteTable5594A636"
    },
    "SubnetId": {
     "Ref": "ClusterVpcPublicSubnet1SubnetA9F7E0A5"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PublicSubnet1/RouteTableAssociation"
   }
  },
  "ClusterVpcPublicSubnet1DefaultRoute62DA4B4B": {
   "Type": "AWS::EC2::Route",
   "Properties": {
    "DestinationCidrBlock": "0.0.0.0/0",
    "GatewayId": {
     "Ref": "ClusterVpcIGW1E358A6E"
    },
    "RouteTableId": {
     "Ref": "ClusterVpcPublicSubnet1RouteTable5594A636"
    }
   },
   "DependsOn": [
    "ClusterVpcVPCGW47AC17E9"
   ],
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PublicSubnet1/DefaultRoute"
   }
  },
  "ClusterVpcPublicSubnet1EIP433C56EE": {
   "Type": "AWS::EC2::EIP",
   "Properties": {
    "Domain": "vpc",
    "Tags": [
     {
      "Key": "Name",
      "Value": "TestCdkStack/Cluster/Vpc/PublicSubnet1"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PublicSubnet1/EIP"
   }
  },
  "ClusterVpcPublicSubnet1NATGateway0693C346": {
   "Type": "AWS::EC2::NatGateway",
   "Properties": {
    "AllocationId": {
     "Fn::GetAtt": [
      "ClusterVpcPublicSubnet1EIP433C56EE",
      "AllocationId"
     ]
    },
    "SubnetId": {
     "Ref": "ClusterVpcPublicSubnet1SubnetA9F7E0A5"
    },
    "Tags": [
     {
      "Key": "Name",
      "Value": "TestCdkStack/Cluster/Vpc/PublicSubnet1"
     }
    ]
   },
   "DependsOn": [
    "ClusterVpcPublicSubnet1DefaultRoute62DA4B4B",
    "ClusterVpcPublicSubnet1RouteTableAssociation0FBEF1F4"
   ],
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PublicSubnet1/NATGateway"
   }
  },
  "ClusterVpcPublicSubnet2Subnet059113C4": {
   "Type": "AWS::EC2::Subnet",
   "Properties": {
    "AvailabilityZone": {
     "Fn::Select": [
      1,
      {
       "Fn::GetAZs": ""
      }
     ]
    },
    "CidrBlock": "10.0.64.0/18",
    "MapPublicIpOnLaunch": true,
    "Tags": [
     {
      "Key": "aws-cdk:subnet-name",
      "Value": "Public"
     },
     {
      "Key": "aws-cdk:subnet-type",
      "Value": "Public"
     },
     {
      "Key": "Name",
      "Value": "TestCdkStack/Cluster/Vpc/PublicSubnet2"
     }
    ],
    "VpcId": {
     "Ref": "ClusterVpcFAA3CEDF"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PublicSubnet2/Subnet"
   }
  },
  "ClusterVpcPublicSubnet2RouteTable7B43F18C": {
   "Type": "AWS::EC2::RouteTable",
   "Properties": {
    "Tags": [
     {
      "Key": "Name",
      "Value": "TestCdkStack/Cluster/Vpc/PublicSubnet2"
     }
    ],
    "VpcId": {
     "Ref": "ClusterVpcFAA3CEDF"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PublicSubnet2/RouteTable"
   }
  },
  "ClusterVpcPublicSubnet2RouteTableAssociation8446B27D": {
   "Type": "AWS::EC2::SubnetRouteTableAssociation",
   "Properties": {
    "RouteTableId": {
     "Ref": "ClusterVpcPublicSubnet2RouteTable7B43F18C"
    },
    "SubnetId": {
     "Ref": "ClusterVpcPublicSubnet2Subnet059113C4"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PublicSubnet2/RouteTableAssociation"
   }
  },
  "ClusterVpcPublicSubnet2DefaultRoute97446C8A": {
   "Type": "AWS::EC2::Route",
   "Properties": {
    "DestinationCidrBlock": "0.0.0.0/0",
    "GatewayId": {
     "Ref": "ClusterVpcIGW1E358A6E"
    },
    "RouteTableId": {
     "Ref": "ClusterVpcPublicSubnet2RouteTable7B43F18C"
    }
   },
   "DependsOn": [
    "ClusterVpcVPCGW47AC17E9"
   ],
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PublicSubnet2/DefaultRoute"
   }
  },
  "ClusterVpcPublicSubnet2EIP203DF3E5": {
   "Type": "AWS::EC2::EIP",
   "Properties": {
    "Domain": "vpc",
    "Tags": [
     {
      "Key": "Name",
      "Value": "TestCdkStack/Cluster/Vpc/PublicSubnet2"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PublicSubnet2/EIP"
   }
  },
  "ClusterVpcPublicSubnet2NATGateway00B24686": {
   "Type": "AWS::EC2::NatGateway",
   "Properties": {
    "AllocationId": {
     "Fn::GetAtt": [
      "ClusterVpcPublicSubnet2EIP203DF3E5",
      "AllocationId"
     ]
    },
    "SubnetId": {
     "Ref": "ClusterVpcPublicSubnet2Subnet059113C4"
    },
    "Tags": [
     {
      "Key": "Name",
      "Value": "TestCdkStack/Cluster/Vpc/PublicSubnet2"
     }
    ]
   },
   "DependsOn": [
    "ClusterVpcPublicSubnet2DefaultRoute97446C8A",
    "ClusterVpcPublicSubnet2RouteTableAssociation8446B27D"
   ],
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PublicSubnet2/NATGateway"
   }
  },
  "ClusterVpcPrivateSubnet1SubnetA4EB481A": {
   "Type": "AWS::EC2::Subnet",
   "Properties": {
    "AvailabilityZone": {
     "Fn::Select": [
      0,
      {
       "Fn::GetAZs": ""
      }
     ]
    },
    "CidrBlock": "10.0.128.0/18",
    "MapPublicIpOnLaunch": false,
    "Tags": [
     {
      "Key": "aws-cdk:subnet-name",
      "Value": "Private"
     },
     {
      "Key": "aws-cdk:subnet-type",
      "Value": "Private"
     },
     {
      "Key": "Name",
      "Value": "TestCdkStack/Cluster/Vpc/PrivateSubnet1"
     }
    ],
    "VpcId": {
     "Ref": "ClusterVpcFAA3CEDF"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PrivateSubnet1/Subnet"
   }
  },
  "ClusterVpcPrivateSubnet1RouteTable5AAEDA3F": {
   "Type": "AWS::EC2::RouteTable",
   "Properties": {
    "Tags": [
     {
      "Key": "Name",
      "Value": "TestCdkStack/Cluster/Vpc/PrivateSubnet1"
     }
    ],
    "VpcId": {
     "Ref": "ClusterVpcFAA3CEDF"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PrivateSubnet1/RouteTable"
   }
  },
  "ClusterVpcPrivateSubnet1RouteTableAssociation9B8A88D9": {
   "Type": "AWS::EC2::SubnetRouteTableAssociation",
   "Properties": {
    "RouteTableId": {
     "Ref": "ClusterVpcPrivateSubnet1RouteTable5AAEDA3F"
    },
    "SubnetId": {
     "Ref": "ClusterVpcPrivateSubnet1SubnetA4EB481A"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PrivateSubnet1/RouteTableAssociation"
   }
  },
  "ClusterVpcPrivateSubnet1DefaultRoute3B4D40DD": {
   "Type": "AWS::EC2::Route",
   "Properties": {
    "DestinationCidrBlock": "0.0.0.0/0",
    "NatGatewayId": {
     "Ref": "ClusterVpcPublicSubnet1NATGateway0693C346"
    },
    "RouteTableId": {
     "Ref": "ClusterVpcPrivateSubnet1RouteTable5AAEDA3F"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PrivateSubnet1/DefaultRoute"
   }
  },
  "ClusterVpcPrivateSubnet2SubnetBD1ECB6E": {
   "Type": "AWS::EC2::Subnet",
   "Properties": {
    "AvailabilityZone": {
     "Fn::Select": [
      1,
      {
       "Fn::GetAZs": ""
      }
     ]
    },
    "CidrBlock": "10.0.192.0/18",
    "MapPublicIpOnLaunch": false,
    "Tags": [
     {
      "Key": "aws-cdk:subnet-name",
      "Value": "Private"
     },
     {
      "Key": "aws-cdk:subnet-type",
      "Value": "Private"
     },
     {
      "Key": "Name",
      "Value": "TestCdkStack/Cluster/Vpc/PrivateSubnet2"
     }
    ],
    "VpcId": {
     "Ref": "ClusterVpcFAA3CEDF"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PrivateSubnet2/Subnet"
   }
  },
  "ClusterVpcPrivateSubnet2RouteTable73064A66": {
   "Type": "AWS::EC2::RouteTable",
   "Properties": {
    "Tags": [
     {
      "Key": "Name",
      "Value": "TestCdkStack/Cluster/Vpc/PrivateSubnet2"
     }
    ],
    "VpcId": {
     "Ref": "ClusterVpcFAA3CEDF"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PrivateSubnet2/RouteTable"
   }
  },
  "ClusterVpcPrivateSubnet2RouteTableAssociationFB21349E": {
   "Type": "AWS::EC2::SubnetRouteTableAssociation",
   "Properties": {
    "RouteTableId": {
     "Ref": "ClusterVpcPrivateSubnet2RouteTable73064A66"
    },
    "SubnetId": {
     "Ref": "ClusterVpcPrivateSubnet2SubnetBD1ECB6E"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PrivateSubnet2/RouteTableAssociation"
   }
  },
  "ClusterVpcPrivateSubnet2DefaultRoute011666AF": {
   "Type": "AWS::EC2::Route",
   "Properties": {
    "DestinationCidrBlock": "0.0.0.0/0",
    "NatGatewayId": {
     "Ref": "ClusterVpcPublicSubnet2NATGateway00B24686"
    },
    "RouteTableId": {
     "Ref": "ClusterVpcPrivateSubnet2RouteTable73064A66"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/PrivateSubnet2/DefaultRoute"
   }
  },
  "ClusterVpcIGW1E358A6E": {
   "Type": "AWS::EC2::InternetGateway",
   "Properties": {
    "Tags": [
     {
      "Key": "Name",
      "Value": "TestCdkStack/Cluster/Vpc"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/IGW"
   }
  },
  "ClusterVpcVPCGW47AC17E9": {
   "Type": "AWS::EC2::VPCGatewayAttachment",
   "Properties": {
    "InternetGatewayId": {
     "Ref": "ClusterVpcIGW1E358A6E"
    },
    "VpcId": {
     "Ref": "ClusterVpcFAA3CEDF"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/VPCGW"
   }
  },
  "ClusterVpcRestrictDefaultSecurityGroupCustomResourceA87DC6B5": {
   "Type": "Custom::VpcRestrictDefaultSG",
   "Properties": {
    "ServiceToken": {
     "Fn::GetAtt": [
      "CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E",
      "Arn"
     ]
    },
    "DefaultSecurityGroupId": {
     "Fn::GetAtt": [
      "ClusterVpcFAA3CEDF",
      "DefaultSecurityGroup"
     ]
    },
    "Account": {
     "Ref": "AWS::AccountId"
    }
   },
   "UpdateReplacePolicy": "Delete",
   "DeletionPolicy": "Delete",
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Cluster/Vpc/RestrictDefaultSecurityGroupCustomResource/Default"
   }
  },
  "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0": {
   "Type": "AWS::IAM::Role",
   "Properties": {
    "AssumeRolePolicyDocument": {
     "Version": "2012-10-17",
     "Statement": [
      {
       "Action": "sts:AssumeRole",
       "Effect": "Allow",
       "Principal": {
        "Service": "lambda.amazonaws.com"
       }
      }
     ]
    },
    "ManagedPolicyArns": [
     {
      "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
     }
    ],
    "Policies": [
     {
      "PolicyName": "Inline",
      "PolicyDocument": {
       "Version": "2012-10-17",
       "Statement": [
        {
         "Effect": "Allow",
         "Action": [
          "ec2:AuthorizeSecurityGroupIngress",
          "ec2:AuthorizeSecurityGroupEgress",
          "ec2:RevokeSecurityGroupIngress",
          "ec2:RevokeSecurityGroupEgress"
         ],
         "Resource": [
          {
           "Fn::Join": [
            "",
            [
             "arn:",
             {
              "Ref": "AWS::Partition"
             },
             ":ec2:",
             {
              "Ref": "AWS::Region"
             },
             ":",
             {
              "Ref": "AWS::AccountId"
             },
             ":security-group/",
             {
              "Fn::GetAtt": [
               "ClusterVpcFAA3CEDF",
               "DefaultSecurityGroup"
              ]
             }
            ]
           ]
          }
         ]
        }
       ]
      }
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Role"
   }
  },
  "CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E": {
   "Type": "AWS::Lambda::Function",
   "Properties": {
    "Code": {
     "S3Bucket": {
      "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
     },
     "S3Key": "ee7de53d64cc9d6248fa6aa550f92358f6c907b5efd6f3298aeab1b5e7ea358a.zip"
    },
    "Timeout": 900,
    "MemorySize": 128,
    "Handler": "__entrypoint__.handler",
    "Role": {
     "Fn::GetAtt": [
      "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0",
      "Arn"
     ]
    },
    "Runtime": "nodejs18.x",
    "Description": "Lambda function for removing all inbound/outbound rules from the VPC default security group"
   },
   "DependsOn": [
    "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0"
   ],
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler",
    "aws:asset:path": "asset.ee7de53d64cc9d6248fa6aa550f92358f6c907b5efd6f3298aeab1b5e7ea358a",
    "aws:asset:property": "Code"
   }
  },
  "TaskDefImportEventsRole9184C90E": {
   "Type": "AWS::IAM::Role",
   "Properties": {
    "AssumeRolePolicyDocument": {
     "Statement": [
      {
       "Action": "sts:AssumeRole",
       "Effect": "Allow",
       "Principal": {
        "Service": "events.amazonaws.com"
       }
      }
     ],
     "Version": "2012-10-17"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/TaskDefImport/EventsRole/Resource"
   }
  },
  "TaskDefImportEventsRoleDefaultPolicyA6BD21B8": {
   "Type": "AWS::IAM::Policy",
   "Properties": {
    "PolicyDocument": {
     "Statement": [
      {
       "Action": "ecs:RunTask",
       "Condition": {
        "ArnEquals": {
         "ecs:cluster": {
          "Fn::GetAtt": [
           "ClusterEB0386A7",
           "Arn"
          ]
         }
        }
       },
       "Effect": "Allow",
       "Resource": "arn:aws:ecs:us-west-2:123456789101:task-definition/MyTask"
      },
      {
       "Action": "ecs:TagResource",
       "Effect": "Allow",
       "Resource": {
        "Fn::Join": [
         "",
         [
          "arn:",
          {
           "Ref": "AWS::Partition"
          },
          ":ecs:",
          {
           "Ref": "AWS::Region"
          },
          ":*:task/",
          {
           "Ref": "ClusterEB0386A7"
          },
          "/*"
         ]
        ]
       }
      },
      {
       "Action": "iam:PassRole",
       "Effect": "Allow",
       "Resource": "arn:aws:iam::123456789101:role/MyTaskRole"
      }
     ],
     "Version": "2012-10-17"
    },
    "PolicyName": "TaskDefImportEventsRoleDefaultPolicyA6BD21B8",
    "Roles": [
     {
      "Ref": "TaskDefImportEventsRole9184C90E"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/TaskDefImport/EventsRole/DefaultPolicy/Resource"
   }
  },
  "TaskDefImportSecurityGroup9D869A93": {
   "Type": "AWS::EC2::SecurityGroup",
   "Properties": {
    "GroupDescription": "TestCdkStack/TaskDefImport/SecurityGroup",
    "SecurityGroupEgress": [
     {
      "CidrIp": "0.0.0.0/0",
      "Description": "Allow all outbound traffic by default",
      "IpProtocol": "-1"
     }
    ],
    "VpcId": {
     "Ref": "ClusterVpcFAA3CEDF"
    }
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/TaskDefImport/SecurityGroup/Resource"
   }
  },
  "Rule4C995B7F": {
   "Type": "AWS::Events::Rule",
   "Properties": {
    "ScheduleExpression": "rate(1 day)",
    "State": "ENABLED",
    "Targets": [
     {
      "Arn": {
       "Fn::GetAtt": [
        "ClusterEB0386A7",
        "Arn"
       ]
      },
      "EcsParameters": {
       "LaunchType": "FARGATE",
       "NetworkConfiguration": {
        "AwsVpcConfiguration": {
         "AssignPublicIp": "DISABLED",
         "SecurityGroups": [
          {
           "Fn::GetAtt": [
            "TaskDefImportSecurityGroup9D869A93",
            "GroupId"
           ]
          }
         ],
         "Subnets": [
          {
           "Ref": "ClusterVpcPrivateSubnet1SubnetA4EB481A"
          },
          {
           "Ref": "ClusterVpcPrivateSubnet2SubnetBD1ECB6E"
          }
         ]
        }
       },
       "TaskCount": 1,
       "TaskDefinitionArn": "arn:aws:ecs:us-west-2:123456789101:task-definition/MyTask"
      },
      "Id": "Target0",
      "Input": "{}",
      "RoleArn": {
       "Fn::GetAtt": [
        "TaskDefImportEventsRole9184C90E",
        "Arn"
       ]
      }
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/Rule/Resource"
   }
  },
  "CDKMetadata": {
   "Type": "AWS::CDK::Metadata",
   "Properties": {
    "Analytics": "v2:deflate64:H4sIAAAAAAAA/3WQzW6DQAyEnyX3ZUtoX4CiKsqlQlDlWpnFSZ3AbrT2giLEu1f8KPTS04w/j6yRE71/e9X7HfQcmfoWNVTpoRQwNwU9fw9oWA9ZE1jQq+xsVzsqNIkeTnczwVOeqTxUDZkyVBZlYpsrXBD8gqrBjW8sZXaGQMjZZ3gyH8d8kk+QAwj28FC5pw4Et8NHK+gtPgNLk3VKRcD8tGhFlWiCJ3kcvAv3ucNfMCqCVg+FW/rNmruGzHxwcaPCDq2wHoqwxkKD46gKZBe8QZUFFtdu49n+s8q966hG/w6MKmVGKQUuZC/zd52taXrFqKyrUV/5pUtivY91vLsyUeSDFWpRF4v+At5yBN++AQAA"
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/CDKMetadata/Default"
   },
   "Condition": "CDKMetadataAvailable"
  }
 },
 "Conditions": {
  "CDKMetadataAvailable": {
   "Fn::Or": [
    {
     "Fn::Or": [
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "af-south-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "ap-east-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "ap-northeast-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "ap-northeast-2"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "ap-south-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "ap-southeast-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "ap-southeast-2"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "ca-central-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "cn-north-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "cn-northwest-1"
       ]
      }
     ]
    },
    {
     "Fn::Or": [
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "eu-central-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "eu-north-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "eu-south-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "eu-west-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "eu-west-2"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "eu-west-3"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "il-central-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "me-central-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "me-south-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "sa-east-1"
       ]
      }
     ]
    },
    {
     "Fn::Or": [
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "us-east-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "us-east-2"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "us-west-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "us-west-2"
       ]
      }
     ]
    }
   ]
  }
 },
 "Parameters": {
  "BootstrapVersion": {
   "Type": "AWS::SSM::Parameter::Value<String>",
   "Default": "/cdk-bootstrap/hnb659fds/version",
   "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
  }
 },
 "Rules": {
  "CheckBootstrapVersion": {
   "Assertions": [
    {
     "Assert": {
      "Fn::Not": [
       {
        "Fn::Contains": [
         [
          "1",
          "2",
          "3",
          "4",
          "5"
         ],
         {
          "Ref": "BootstrapVersion"
         }
        ]
       }
      ]
     },
     "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
    }
   ]
  }
 }
}

Which is invalid based on the deprecation notice we received.

Possible Solution

If the task definition contains no revision information, a wildcard should automatically be applied.

Additional Information/Context

This is not an issue if the task definition is not imported. For example:

import { Duration, Stack, type StackProps } from 'aws-cdk-lib';
import { Cluster, FargateTaskDefinition } from 'aws-cdk-lib/aws-ecs';
import { Rule, Schedule } from 'aws-cdk-lib/aws-events';
import { EcsTask } from 'aws-cdk-lib/aws-events-targets';
import { Role } from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export class TestCdkStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const cluster = new Cluster(this, 'Cluster');
    const taskDefinition = new FargateTaskDefinition(this, 'TaskDef', {});
    new Rule(this, "Rule", {
      targets: [new EcsTask({
        cluster,
        taskDefinition
      })],
      schedule: Schedule.rate(Duration.days(1)),
    });
  }
}

Produces this snippet:

{
  "TaskDefEventsRoleDefaultPolicyA124E85B": {
   "Type": "AWS::IAM::Policy",
   "Properties": {
    "PolicyDocument": {
     "Statement": [
      {
       "Action": "ecs:RunTask",
       "Condition": {
        "ArnEquals": {
         "ecs:cluster": {
          "Fn::GetAtt": [
           "ClusterEB0386A7",
           "Arn"
          ]
         }
        }
       },
       "Effect": "Allow",
       "Resource": {
        "Ref": "TaskDef54694570"
       }
      },
      {
       "Action": "ecs:TagResource",
       "Effect": "Allow",
       "Resource": {
        "Fn::Join": [
         "",
         [
          "arn:",
          {
           "Ref": "AWS::Partition"
          },
          ":ecs:",
          {
           "Ref": "AWS::Region"
          },
          ":*:task/",
          {
           "Ref": "ClusterEB0386A7"
          },
          "/*"
         ]
        ]
       }
      },
      {
       "Action": "iam:PassRole",
       "Effect": "Allow",
       "Resource": {
        "Fn::GetAtt": [
         "TaskDefTaskRole1EDB4A67",
         "Arn"
        ]
       }
      }
     ],
     "Version": "2012-10-17"
    },
    "PolicyName": "TaskDefEventsRoleDefaultPolicyA124E85B",
    "Roles": [
     {
      "Ref": "TaskDefEventsRoleFB3B67B8"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "TestCdkStack/TaskDef/EventsRole/DefaultPolicy/Resource"
   }
  }
}
"Resource": {
    "Ref": "TaskDef54694570"
}

Will reference the full ARN, including the revision, according to the docs.

Screenshot 2024-05-30 at 10 52 39

CDK CLI Version

2.143.1 (build 29b0d66)

Framework Version

No response

Node.js Version

20

OS

MacOS

Language

TypeScript

Language Version

No response

Other information

See also #30368.

blimmer commented 4 weeks ago

Here's my exteremely hacky workaround, in case anyone else needs to fix this before it's fixed upstream:

const role = (target as any).role as Role;
const statements = (role as any).defaultPolicy.document.statements as PolicyStatement[];
const withoutBadStatements = statements.filter((s) => {
  const isBadStatement = s.actions.length === 1 && s.actions[0] === "ecs:RunTask";
  return !isBadStatement;
});
(role as any).defaultPolicy.document.statements = withoutBadStatements;

role.addToPrincipalPolicy(
  new PolicyStatement({
    actions: ["ecs:RunTask"],
    resources: [importedTaskDefArn + ":*"], // this is the fix for the bug
    conditions: {
      ArnEquals: {
        "ecs:cluster": ecsCluster.clusterArn,
      },
    },
  })
);
ashishdhingra commented 4 weeks ago

Appears to be an issue based on below references:

Task Definition ARN in IAM policy resource should include task definition Revision Number.