Skip to content

Commit 383cccb

Browse files
feat(ecr): validate repository arn in fromRepositoryArn (#25302)
This PR is adding a regex to check the `arn` structure of an ECR repository. Closes #16223 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent fe5ed10 commit 383cccb

File tree

4 files changed

+41
-18
lines changed

4 files changed

+41
-18
lines changed

packages/aws-cdk-lib/aws-ecr/lib/repository.ts

+10
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,8 @@ export class Repository extends RepositoryBase {
560560
throw new Error('"repositoryArn" is a late-bound value, and therefore "repositoryName" is required. Use `fromRepositoryAttributes` instead');
561561
}
562562

563+
validateRepositoryArn();
564+
563565
const repositoryName = repositoryArn.split('/').slice(1).join('/');
564566

565567
class Import extends RepositoryBase {
@@ -575,6 +577,14 @@ export class Repository extends RepositoryBase {
575577
return new Import(scope, id, {
576578
environmentFromArn: repositoryArn,
577579
});
580+
581+
function validateRepositoryArn() {
582+
const splitArn = repositoryArn.split(':');
583+
584+
if (!splitArn[splitArn.length - 1].startsWith('repository/')) {
585+
throw new Error(`Repository arn should be in the format 'arn:<PARTITION>:ecr:<REGION>:<ACCOUNT>:repository/<NAME>', got ${repositoryArn}.`);
586+
}
587+
}
578588
}
579589

580590
public static fromRepositoryName(scope: Construct, id: string, repositoryName: string): IRepository {

packages/aws-cdk-lib/aws-ecr/test/repository.test.ts

+13
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,19 @@ describe('repository', () => {
209209
expect(stack.resolve(repo2.repositoryName)).toBe('foo/bar/foo/fooo');
210210
});
211211

212+
test('import with arn without /repository', () => {
213+
// GIVEN
214+
const stack = new cdk.Stack();
215+
216+
// WHEN
217+
const invalidArn = 'arn:aws:ecr:us-east-1:123456789012:foo-ecr-repo-name';
218+
219+
// THEN
220+
expect(() => {
221+
ecr.Repository.fromRepositoryArn(stack, 'repo', invalidArn);
222+
}).toThrowError(`Repository arn should be in the format 'arn:<PARTITION>:ecr:<REGION>:<ACCOUNT>:repository/<NAME>', got ${invalidArn}.`);
223+
});
224+
212225
test('fails if importing with token arn and no name', () => {
213226
// GIVEN
214227
const stack = new cdk.Stack();

packages/aws-cdk-lib/aws-lambda/test/lambda-insights.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ describe('lambda-insights', () => {
142142
const stack = new cdk.Stack();
143143
new lambda.DockerImageFunction(stack, 'MyFunction', {
144144
code: lambda.DockerImageCode.fromEcr(ecr.Repository.fromRepositoryArn(stack, 'MyRepo',
145-
'arn:aws:ecr:us-east-1:0123456789:repository/MyRepo')),
145+
'arn:aws:ecr:us-east-1:123456789012:repository/MyRepo')),
146146
insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_98_0,
147147
});
148148

packages/aws-cdk-lib/pipelines/test/docker-credentials.test.ts

+17-17
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ let stack: cdk.Stack;
1414
beforeEach(() => {
1515
app = new TestApp();
1616
stack = new cdk.Stack(app, 'Stack', {
17-
env: { account: '0123456789012', region: 'eu-west-1' },
17+
env: { account: '123456789012', region: 'eu-west-1' },
1818
});
1919
});
2020

@@ -44,7 +44,7 @@ describe('ExternalDockerCredential', () => {
4444
});
4545

4646
test('maximum example includes all expected properties', () => {
47-
const roleArn = 'arn:aws:iam::0123456789012:role/MyRole';
47+
const roleArn = 'arn:aws:iam::123456789012:role/MyRole';
4848
const creds = cdkp.DockerCredential.customRegistry('example.com', secret, {
4949
secretUsernameField: 'login',
5050
secretPasswordField: 'pass',
@@ -86,7 +86,7 @@ describe('ExternalDockerCredential', () => {
8686
});
8787

8888
test('grants read access to the secret to the assumeRole if provided', () => {
89-
const assumeRole = iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::0123456789012:role/MyRole');
89+
const assumeRole = iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::123456789012:role/MyRole');
9090
const creds = cdkp.DockerCredential.customRegistry('example.com', secret, { assumeRole });
9191

9292
const user = new iam.User(stack, 'User');
@@ -108,7 +108,7 @@ describe('ExternalDockerCredential', () => {
108108
Statement: [{
109109
Action: 'sts:AssumeRole',
110110
Effect: 'Allow',
111-
Resource: 'arn:aws:iam::0123456789012:role/MyRole',
111+
Resource: 'arn:aws:iam::123456789012:role/MyRole',
112112
}],
113113
Version: '2012-10-17',
114114
},
@@ -117,7 +117,7 @@ describe('ExternalDockerCredential', () => {
117117
});
118118

119119
test('does not grant any access if the usage does not match', () => {
120-
const assumeRole = iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::0123456789012:role/MyRole');
120+
const assumeRole = iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::123456789012:role/MyRole');
121121
const creds = cdkp.DockerCredential.customRegistry('example.com', secret, {
122122
assumeRole,
123123
usages: [cdkp.DockerCredentialUsage.ASSET_PUBLISHING],
@@ -138,7 +138,7 @@ describe('EcrDockerCredential', () => {
138138
let repo: ecr.IRepository;
139139

140140
beforeEach(() => {
141-
repo = ecr.Repository.fromRepositoryArn(stack, 'Repo', 'arn:aws:ecr:eu-west-1:0123456789012:repository/Repo');
141+
repo = ecr.Repository.fromRepositoryArn(stack, 'Repo', 'arn:aws:ecr:eu-west-1:123456789012:repository/Repo');
142142
});
143143

144144
test('minimal example only renders ecrRepository', () => {
@@ -150,7 +150,7 @@ describe('EcrDockerCredential', () => {
150150
'Fn::Select': [
151151
0, {
152152
'Fn::Split': ['/', {
153-
'Fn::Join': ['', ['0123456789012.dkr.ecr.eu-west-1.', { Ref: 'AWS::URLSuffix' }, '/Repo']],
153+
'Fn::Join': ['', ['123456789012.dkr.ecr.eu-west-1.', { Ref: 'AWS::URLSuffix' }, '/Repo']],
154154
}],
155155
},
156156
],
@@ -161,7 +161,7 @@ describe('EcrDockerCredential', () => {
161161
});
162162

163163
test('maximum example renders all fields', () => {
164-
const roleArn = 'arn:aws:iam::0123456789012:role/MyRole';
164+
const roleArn = 'arn:aws:iam::123456789012:role/MyRole';
165165
const creds = cdkp.DockerCredential.ecr([repo], {
166166
assumeRole: iam.Role.fromRoleArn(stack, 'Role', roleArn),
167167
usages: [cdkp.DockerCredentialUsage.SYNTH],
@@ -173,7 +173,7 @@ describe('EcrDockerCredential', () => {
173173
'Fn::Select': [
174174
0, {
175175
'Fn::Split': ['/', {
176-
'Fn::Join': ['', ['0123456789012.dkr.ecr.eu-west-1.', { Ref: 'AWS::URLSuffix' }, '/Repo']],
176+
'Fn::Join': ['', ['123456789012.dkr.ecr.eu-west-1.', { Ref: 'AWS::URLSuffix' }, '/Repo']],
177177
}],
178178
},
179179
],
@@ -201,7 +201,7 @@ describe('EcrDockerCredential', () => {
201201
'ecr:BatchGetImage',
202202
],
203203
Effect: 'Allow',
204-
Resource: 'arn:aws:ecr:eu-west-1:0123456789012:repository/Repo',
204+
Resource: 'arn:aws:ecr:eu-west-1:123456789012:repository/Repo',
205205
},
206206
{
207207
Action: 'ecr:GetAuthorizationToken',
@@ -215,7 +215,7 @@ describe('EcrDockerCredential', () => {
215215
});
216216

217217
test('grants pull access to the assumed role', () => {
218-
const assumeRole = iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::0123456789012:role/MyRole');
218+
const assumeRole = iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::123456789012:role/MyRole');
219219
const creds = cdkp.DockerCredential.ecr([repo], { assumeRole });
220220

221221
const user = new iam.User(stack, 'User');
@@ -230,7 +230,7 @@ describe('EcrDockerCredential', () => {
230230
'ecr:BatchGetImage',
231231
],
232232
Effect: 'Allow',
233-
Resource: 'arn:aws:ecr:eu-west-1:0123456789012:repository/Repo',
233+
Resource: 'arn:aws:ecr:eu-west-1:123456789012:repository/Repo',
234234
},
235235
{
236236
Action: 'ecr:GetAuthorizationToken',
@@ -246,7 +246,7 @@ describe('EcrDockerCredential', () => {
246246
Statement: [{
247247
Action: 'sts:AssumeRole',
248248
Effect: 'Allow',
249-
Resource: 'arn:aws:iam::0123456789012:role/MyRole',
249+
Resource: 'arn:aws:iam::123456789012:role/MyRole',
250250
}],
251251
Version: '2012-10-17',
252252
},
@@ -255,7 +255,7 @@ describe('EcrDockerCredential', () => {
255255
});
256256

257257
test('grants pull access to multiple repos if provided', () => {
258-
const repo2 = ecr.Repository.fromRepositoryArn(stack, 'Repo2', 'arn:aws:ecr:eu-west-1:0123456789012:repository/Repo2');
258+
const repo2 = ecr.Repository.fromRepositoryArn(stack, 'Repo2', 'arn:aws:ecr:eu-west-1:123456789012:repository/Repo2');
259259
const creds = cdkp.DockerCredential.ecr([repo, repo2]);
260260

261261
const user = new iam.User(stack, 'User');
@@ -270,7 +270,7 @@ describe('EcrDockerCredential', () => {
270270
'ecr:BatchGetImage',
271271
],
272272
Effect: 'Allow',
273-
Resource: 'arn:aws:ecr:eu-west-1:0123456789012:repository/Repo',
273+
Resource: 'arn:aws:ecr:eu-west-1:123456789012:repository/Repo',
274274
},
275275
{
276276
Action: 'ecr:GetAuthorizationToken',
@@ -284,7 +284,7 @@ describe('EcrDockerCredential', () => {
284284
'ecr:BatchGetImage',
285285
],
286286
Effect: 'Allow',
287-
Resource: 'arn:aws:ecr:eu-west-1:0123456789012:repository/Repo2',
287+
Resource: 'arn:aws:ecr:eu-west-1:123456789012:repository/Repo2',
288288
}]),
289289
Version: '2012-10-17',
290290
},
@@ -324,7 +324,7 @@ describe('EcrDockerCredential', () => {
324324
});
325325

326326
describe('dockerCredentialsInstallCommands', () => {
327-
const secretArn = 'arn:aws:secretsmanager:eu-west-1:0123456789012:secret:mySecret-012345';
327+
const secretArn = 'arn:aws:secretsmanager:eu-west-1:123456789012:secret:mySecret-012345';
328328
let secret: secretsmanager.ISecret;
329329

330330
beforeEach(() => {

0 commit comments

Comments
 (0)