Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ecr): autoDeleteImages fails when repository is renamed #26742

Merged
merged 6 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@
}
}
},
"32b456cdb8ff646c98c4580ff6d88bd51e84e33af74af927bb882158a53a0d21": {
"adb4722adf5406a51ec51892647c61e7ac61ba38b845cd1163397018822e542e": {
"source": {
"path": "aws-ecr-integ-stack.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "32b456cdb8ff646c98c4580ff6d88bd51e84e33af74af927bb882158a53a0d21.json",
"objectKey": "adb4722adf5406a51ec51892647c61e7ac61ba38b845cd1163397018822e542e.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,31 @@
],
"Resource": [
{
"Fn::GetAtt": [
"Repo02AC86CF",
"Arn"
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":ecr:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":repository/*"
]
]
}
]
],
"Condition": {
"StringEquals": {
"ecr:ResourceTag/aws-cdk:auto-delete-images": "true"
}
}
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"validateOnSynth": false,
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/32b456cdb8ff646c98c4580ff6d88bd51e84e33af74af927bb882158a53a0d21.json",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/adb4722adf5406a51ec51892647c61e7ac61ba38b845cd1163397018822e542e.json",
"requiresBootstrapStackVersion": 6,
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
"additionalDependencies": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
}
},
"constructInfo": {
"fqn": "aws-cdk-lib.aws_ecr.CfnRepository",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
},
"AutoDeleteImagesCustomResource": {
Expand All @@ -40,20 +40,20 @@
"id": "Default",
"path": "aws-ecr-integ-stack/Repo/AutoDeleteImagesCustomResource/Default",
"constructInfo": {
"fqn": "aws-cdk-lib.CfnResource",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
}
},
"constructInfo": {
"fqn": "aws-cdk-lib.CustomResource",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
}
},
"constructInfo": {
"fqn": "aws-cdk-lib.aws_ecr.Repository",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
},
"Custom::ECRAutoDeleteImagesCustomResourceProvider": {
Expand All @@ -64,60 +64,60 @@
"id": "Staging",
"path": "aws-ecr-integ-stack/Custom::ECRAutoDeleteImagesCustomResourceProvider/Staging",
"constructInfo": {
"fqn": "aws-cdk-lib.AssetStaging",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
},
"Role": {
"id": "Role",
"path": "aws-ecr-integ-stack/Custom::ECRAutoDeleteImagesCustomResourceProvider/Role",
"constructInfo": {
"fqn": "aws-cdk-lib.CfnResource",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
},
"Handler": {
"id": "Handler",
"path": "aws-ecr-integ-stack/Custom::ECRAutoDeleteImagesCustomResourceProvider/Handler",
"constructInfo": {
"fqn": "aws-cdk-lib.CfnResource",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
}
},
"constructInfo": {
"fqn": "aws-cdk-lib.CustomResourceProvider",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
},
"RepositoryURI": {
"id": "RepositoryURI",
"path": "aws-ecr-integ-stack/RepositoryURI",
"constructInfo": {
"fqn": "aws-cdk-lib.CfnOutput",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
},
"BootstrapVersion": {
"id": "BootstrapVersion",
"path": "aws-ecr-integ-stack/BootstrapVersion",
"constructInfo": {
"fqn": "aws-cdk-lib.CfnParameter",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
},
"CheckBootstrapVersion": {
"id": "CheckBootstrapVersion",
"path": "aws-ecr-integ-stack/CheckBootstrapVersion",
"constructInfo": {
"fqn": "aws-cdk-lib.CfnRule",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
}
},
"constructInfo": {
"fqn": "aws-cdk-lib.Stack",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
},
"cdk-integ-auto-delete-images": {
Expand All @@ -144,22 +144,22 @@
"id": "BootstrapVersion",
"path": "cdk-integ-auto-delete-images/DefaultTest/DeployAssert/BootstrapVersion",
"constructInfo": {
"fqn": "aws-cdk-lib.CfnParameter",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
},
"CheckBootstrapVersion": {
"id": "CheckBootstrapVersion",
"path": "cdk-integ-auto-delete-images/DefaultTest/DeployAssert/CheckBootstrapVersion",
"constructInfo": {
"fqn": "aws-cdk-lib.CfnRule",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
}
},
"constructInfo": {
"fqn": "aws-cdk-lib.Stack",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
}
},
Expand All @@ -184,8 +184,8 @@
}
},
"constructInfo": {
"fqn": "aws-cdk-lib.App",
"version": "0.0.0"
"fqn": "constructs.Construct",
"version": "10.2.69"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -636,18 +636,31 @@
],
"Resource": [
{
"Fn::GetAtt": [
"defaultresourcesmaxecrasset13112F7F9",
"Arn"
]
},
{
"Fn::GetAtt": [
"defaultresourcesmaxecrasset2904B88A7",
"Arn"
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":ecr:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":repository/*"
]
]
}
]
],
"Condition": {
"StringEquals": {
"ecr:ResourceTag/aws-cdk:auto-delete-images": "true"
}
}
}
]
}
Expand Down
15 changes: 7 additions & 8 deletions packages/aws-cdk-lib/aws-ecr/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import {
CustomResource,
CustomResourceProvider,
CustomResourceProviderRuntime,
Aws,
} from '../../core';

const AUTO_DELETE_IMAGES_RESOURCE_TYPE = 'Custom::ECRAutoDeleteImages';
const AUTO_DELETE_IMAGES_TAG = 'aws-cdk:auto-delete-images';
const REPO_ARN_SYMBOL = Symbol.for('@aws-cdk/aws-ecr.RepoArns');

/**
* Represents an ECR repository.
Expand Down Expand Up @@ -867,12 +867,8 @@ export class Repository extends RepositoryBase {
});

if (firstTime) {
const repoArns = [this._resource.attrArn];
(provider as any)[REPO_ARN_SYMBOL] = repoArns;

// Use a iam policy to allow the custom resource to list & delete
// images in the repository and the ability to get all repositories to find the arn needed on delete.
// We lazily produce a list of repositories associated with this custom resource provider.
provider.addToRolePolicy({
Effect: 'Allow',
Action: [
Expand All @@ -881,10 +877,13 @@ export class Repository extends RepositoryBase {
'ecr:ListImages',
'ecr:ListTagsForResource',
],
Resource: Lazy.list({ produce: () => repoArns }),
Resource: [`arn:${Aws.PARTITION}:ecr:${Stack.of(this).region}:${Stack.of(this).account}:repository/*`],
Condition: {
StringEquals: {
['ecr:ResourceTag/' + AUTO_DELETE_IMAGES_TAG]: 'true',
},
},
Comment on lines +881 to +885
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rix0rrr

I implemented tag-based access control with a Condition for now.

});
} else {
(provider as any)[REPO_ARN_SYMBOL].push(this._resource.attrArn);
}
Copy link
Contributor Author

@go-to-k go-to-k Aug 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rix0rrr

The repositoryName was Token. So following codes didn't work.

    const role = iam.Role.fromRoleArn(this, `${this.repositoryName}Role`, provider.roleArn);

    // Use a iam policy to allow the custom resource to list & delete
    // images in the repository and the ability to get all repositories to find the arn needed on delete.
    new iam.Policy(this, `${this.repositoryName}Policy`, {
      roles: [role],
      statements: [
        new iam.PolicyStatement({
          effect: iam.Effect.ALLOW,
          actions: [
            'ecr:BatchDeleteImage',
            'ecr:DescribeRepositories',
            'ecr:ListImages',
            'ecr:ListTagsForResource',
          ],
          resources: [this._resource.attrArn],
        }),
      ],
    });

If, because of the token-ness and unpredictability of this.repositoryName, this cannot work, a good compromise may be to put a tag on the ECR repository (cdk:allow-deleting-images=true; in fact there should already be a tag on there) and use tag-based access control with a Condition.

In that case, is this the way to go?

Copy link
Contributor Author

@go-to-k go-to-k Aug 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rix0rrr

I used physicalName instead of repositoryName and it worked. Because this.repositoryName is a Token (lazy value), but physicalName is props.repositoryName.

However, props.repositoryName may be unspecified, in which case the props.repositoryName will be undefined and the IAM policy's logical IDs will duplicate. So this is not a good practice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, you can't use physicalName in this case. We should probably go the tag route then, a naked * is too dangerous for my tastes.


const customResource = new CustomResource(this, 'AutoDeleteImagesCustomResource', {
Expand Down
24 changes: 14 additions & 10 deletions packages/aws-cdk-lib/aws-ecr/test/repository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1006,18 +1006,22 @@ describe('repository', () => {
],
Resource: [
{
'Fn::GetAtt': [
'Repo1DBD717D9',
'Arn',
],
},
{
'Fn::GetAtt': [
'Repo2730A8200',
'Arn',
],
'Fn::Join': ['', [
'arn:',
{ Ref: 'AWS::Partition' },
':ecr:',
{ Ref: 'AWS::Region' },
':',
{ Ref: 'AWS::AccountId' },
':repository/*',
]],
},
],
Condition: {
StringEquals: {
'ecr:ResourceTag/aws-cdk:auto-delete-images': 'true',
},
},
},
],
},
Expand Down