@jcoreio/aws-ecr-utils 1.3.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ImageManifestSchema.d.mts +59 -0
- package/ImageManifestSchema.d.mts.map +1 -0
- package/ImageManifestSchema.d.ts +50 -49
- package/ImageManifestSchema.d.ts.map +1 -0
- package/ImageManifestSchema.js +2 -1
- package/ImageManifestSchema.js.map +1 -0
- package/ImageManifestSchema.mjs +2 -1
- package/ImageManifestSchema.mjs.map +1 -0
- package/checkECRImageAccess.d.mts +25 -0
- package/checkECRImageAccess.d.mts.map +1 -0
- package/checkECRImageAccess.d.ts +25 -17
- package/checkECRImageAccess.d.ts.map +1 -0
- package/checkECRImageAccess.js +65 -65
- package/checkECRImageAccess.js.map +1 -0
- package/checkECRImageAccess.mjs +22 -19
- package/checkECRImageAccess.mjs.map +1 -0
- package/checkECRRepositoryPolicy.d.mts +28 -0
- package/checkECRRepositoryPolicy.d.mts.map +1 -0
- package/checkECRRepositoryPolicy.d.ts +21 -12
- package/checkECRRepositoryPolicy.d.ts.map +1 -0
- package/checkECRRepositoryPolicy.js +27 -26
- package/checkECRRepositoryPolicy.js.map +1 -0
- package/checkECRRepositoryPolicy.mjs +11 -9
- package/checkECRRepositoryPolicy.mjs.map +1 -0
- package/copyECRImage.d.mts +17 -0
- package/copyECRImage.d.mts.map +1 -0
- package/copyECRImage.d.ts +17 -12
- package/copyECRImage.d.ts.map +1 -0
- package/copyECRImage.js +21 -20
- package/copyECRImage.js.map +1 -0
- package/copyECRImage.mjs +2 -1
- package/copyECRImage.mjs.map +1 -0
- package/ecrImageExists.d.mts +17 -0
- package/ecrImageExists.d.mts.map +1 -0
- package/ecrImageExists.d.ts +17 -8
- package/ecrImageExists.d.ts.map +1 -0
- package/ecrImageExists.js +17 -16
- package/ecrImageExists.js.map +1 -0
- package/ecrImageExists.mjs +6 -5
- package/ecrImageExists.mjs.map +1 -0
- package/formatECRImageUri.d.mts +12 -0
- package/formatECRImageUri.d.mts.map +1 -0
- package/formatECRImageUri.d.ts +12 -5
- package/formatECRImageUri.d.ts.map +1 -0
- package/formatECRImageUri.js +2 -1
- package/formatECRImageUri.js.map +1 -0
- package/formatECRImageUri.mjs +2 -1
- package/formatECRImageUri.mjs.map +1 -0
- package/formatECRRepositoryHostname.d.mts +10 -0
- package/formatECRRepositoryHostname.d.mts.map +1 -0
- package/formatECRRepositoryHostname.d.ts +10 -4
- package/formatECRRepositoryHostname.d.ts.map +1 -0
- package/formatECRRepositoryHostname.js +2 -1
- package/formatECRRepositoryHostname.js.map +1 -0
- package/formatECRRepositoryHostname.mjs +2 -1
- package/formatECRRepositoryHostname.mjs.map +1 -0
- package/index.d.mts +12 -0
- package/index.d.mts.map +1 -0
- package/index.d.ts +12 -11
- package/index.d.ts.map +1 -0
- package/index.js +2 -1
- package/index.js.map +1 -0
- package/index.mjs +2 -1
- package/index.mjs.map +1 -0
- package/loginToECR.d.mts +11 -0
- package/loginToECR.d.mts.map +1 -0
- package/loginToECR.d.ts +11 -7
- package/loginToECR.d.ts.map +1 -0
- package/loginToECR.js +96 -28
- package/loginToECR.js.map +1 -0
- package/loginToECR.mjs +46 -8
- package/loginToECR.mjs.map +1 -0
- package/package.json +17 -6
- package/parseECRImageUri.d.mts +7 -0
- package/parseECRImageUri.d.mts.map +1 -0
- package/parseECRImageUri.d.ts +7 -6
- package/parseECRImageUri.d.ts.map +1 -0
- package/parseECRImageUri.js +2 -1
- package/parseECRImageUri.js.map +1 -0
- package/parseECRImageUri.mjs +2 -1
- package/parseECRImageUri.mjs.map +1 -0
- package/parseECRRepositoryHostname.d.mts +6 -0
- package/parseECRRepositoryHostname.d.mts.map +1 -0
- package/parseECRRepositoryHostname.d.ts +6 -5
- package/parseECRRepositoryHostname.d.ts.map +1 -0
- package/parseECRRepositoryHostname.js +2 -1
- package/parseECRRepositoryHostname.js.map +1 -0
- package/parseECRRepositoryHostname.mjs +2 -1
- package/parseECRRepositoryHostname.mjs.map +1 -0
- package/src/ImageManifestSchema.ts +19 -0
- package/src/checkECRImageAccess.ts +193 -0
- package/src/checkECRRepositoryPolicy.ts +153 -0
- package/src/copyECRImage.ts +76 -0
- package/src/ecrImageExists.ts +48 -0
- package/src/formatECRImageUri.ts +19 -0
- package/src/formatECRRepositoryHostname.ts +11 -0
- package/src/index.ts +11 -0
- package/src/loginToECR.ts +96 -0
- package/src/parseECRImageUri.ts +13 -0
- package/src/parseECRRepositoryHostname.ts +12 -0
- package/src/tagECRImage.ts +57 -0
- package/src/upsertECRRepository.ts +40 -0
- package/tagECRImage.d.mts +16 -0
- package/tagECRImage.d.mts.map +1 -0
- package/tagECRImage.d.ts +13 -6
- package/tagECRImage.d.ts.map +1 -0
- package/tagECRImage.js +46 -45
- package/tagECRImage.js.map +1 -0
- package/tagECRImage.mjs +9 -9
- package/tagECRImage.mjs.map +1 -0
- package/upsertECRRepository.d.mts +11 -0
- package/upsertECRRepository.d.mts.map +1 -0
- package/upsertECRRepository.d.ts +12 -6
- package/upsertECRRepository.d.ts.map +1 -0
- package/upsertECRRepository.js +34 -31
- package/upsertECRRepository.js.map +1 -0
- package/upsertECRRepository.mjs +11 -8
- package/upsertECRRepository.mjs.map +1 -0
- package/copyECRImage.js.flow +0 -14
- package/copyECRImage.mjs.flow +0 -14
- package/ecrImageExists.js.flow +0 -10
- package/ecrImageExists.mjs.flow +0 -10
- package/index.js.flow +0 -8
- package/index.mjs.flow +0 -8
- package/loginToECR.js.flow +0 -6
- package/loginToECR.mjs.flow +0 -6
- package/parseECRImageUri.js.flow +0 -8
- package/parseECRImageUri.mjs.flow +0 -8
- package/tagECRImage.js.flow +0 -8
- package/tagECRImage.mjs.flow +0 -8
- package/upsertECRRepository.js.flow +0 -7
- package/upsertECRRepository.mjs.flow +0 -7
package/loginToECR.mjs
CHANGED
|
@@ -1,21 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @prettier
|
|
3
3
|
*/
|
|
4
|
-
import AWS from 'aws-sdk';
|
|
5
4
|
import { spawn } from 'promisify-child-process';
|
|
6
5
|
import base64 from 'base64-js';
|
|
6
|
+
import { ECRClient, GetAuthorizationTokenCommand } from '@aws-sdk/client-ecr';
|
|
7
|
+
import parseECRImageUri from "./parseECRImageUri.mjs";
|
|
8
|
+
import * as log4jcore from 'log4jcore';
|
|
9
|
+
const log = log4jcore.logger('@jcoreio/aws-ecr-utils/loginToECR');
|
|
7
10
|
export default async function loginToECR({
|
|
8
11
|
ecr,
|
|
12
|
+
forImages,
|
|
9
13
|
awsConfig
|
|
10
14
|
}) {
|
|
11
|
-
if (
|
|
15
|
+
if (forImages) {
|
|
16
|
+
log.debug('logging into AWS ECR for images:', forImages);
|
|
17
|
+
const regions = new Set(forImages.flatMap(image => {
|
|
18
|
+
try {
|
|
19
|
+
return [parseECRImageUri(image).region];
|
|
20
|
+
} catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
}));
|
|
24
|
+
log.debug('parsed AWS regions from images:', regions);
|
|
25
|
+
await Promise.all([...regions].map(async region => loginToECR({
|
|
26
|
+
awsConfig: {
|
|
27
|
+
...awsConfig,
|
|
28
|
+
region
|
|
29
|
+
}
|
|
30
|
+
})));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (!ecr) ecr = new ECRClient({
|
|
34
|
+
...awsConfig
|
|
35
|
+
});
|
|
36
|
+
log.debug('logging into ECR for region:', await ecr.config.region());
|
|
12
37
|
const {
|
|
13
38
|
authorizationData
|
|
14
|
-
} = await ecr.
|
|
39
|
+
} = await ecr.send(new GetAuthorizationTokenCommand());
|
|
15
40
|
const {
|
|
16
41
|
authorizationToken,
|
|
17
42
|
proxyEndpoint
|
|
18
43
|
} = (authorizationData === null || authorizationData === void 0 ? void 0 : authorizationData[0]) || {};
|
|
44
|
+
log.debug('GetAuthorizationToken data:', {
|
|
45
|
+
authorizationToken,
|
|
46
|
+
proxyEndpoint
|
|
47
|
+
});
|
|
19
48
|
if (!authorizationToken) {
|
|
20
49
|
throw new Error('failed to get authorizationToken from ECR');
|
|
21
50
|
}
|
|
@@ -23,10 +52,12 @@ export default async function loginToECR({
|
|
|
23
52
|
throw new Error('failed to get proxyEndpoint from ECR');
|
|
24
53
|
}
|
|
25
54
|
// this is silly...
|
|
26
|
-
const decoded =
|
|
55
|
+
const decoded = Buffer.from(base64.toByteArray(authorizationToken)).toString('utf8');
|
|
27
56
|
const [user, password] = decoded.split(/:/);
|
|
28
|
-
const
|
|
29
|
-
|
|
57
|
+
const dockerArgs = ['login', '-u', user, '--password-stdin', proxyEndpoint];
|
|
58
|
+
log.debug('running: docker', ...dockerArgs.map(arg => /^[-_a-z0-9]+$/i.test(arg) ? arg : `'${arg.replace(/'/g, `'\\'')}'`)}'`));
|
|
59
|
+
const child = spawn('docker', dockerArgs, {
|
|
60
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
30
61
|
encoding: 'utf8'
|
|
31
62
|
});
|
|
32
63
|
if (!child.stdin) {
|
|
@@ -34,5 +65,12 @@ export default async function loginToECR({
|
|
|
34
65
|
}
|
|
35
66
|
child.stdin.write(password);
|
|
36
67
|
child.stdin.end();
|
|
37
|
-
await child
|
|
38
|
-
|
|
68
|
+
await child.then(() => log.debug('ECR login succeeded'), error => {
|
|
69
|
+
if (error && typeof error === 'object') {
|
|
70
|
+
if ('code' in error) log.debug('docker login exited with code', error.code);
|
|
71
|
+
if ('signal' in error) log.debug('docker login was killed with signal', error.signal);
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=loginToECR.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loginToECR.mjs","names":["spawn","base64","ECRClient","GetAuthorizationTokenCommand","parseECRImageUri","log4jcore","log","logger","loginToECR","ecr","forImages","awsConfig","debug","regions","Set","flatMap","image","region","Promise","all","map","config","authorizationData","send","authorizationToken","proxyEndpoint","Error","decoded","Buffer","from","toByteArray","toString","user","password","split","dockerArgs","arg","test","replace","child","stdio","encoding","stdin","write","end","then","error","code","signal"],"sources":["src/loginToECR.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jcoreio/aws-ecr-utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "utilities for working with AWS Elastic Container Registry",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"repository": {
|
|
@@ -19,11 +19,13 @@
|
|
|
19
19
|
},
|
|
20
20
|
"homepage": "https://github.com/jcoreio/aws-ecr-utils#readme",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@
|
|
23
|
-
"aws-sdk": "^
|
|
22
|
+
"@aws-sdk/client-ecr": "^3.577.0",
|
|
23
|
+
"@aws-sdk/client-sts": "^3.577.0",
|
|
24
|
+
"@babel/runtime": "^7.26.0",
|
|
24
25
|
"base64-js": "^1.5.1",
|
|
25
26
|
"inquirer": "^8.2.6",
|
|
26
27
|
"is-interactive": "^1.0.0",
|
|
28
|
+
"log4jcore": "^4.8.0",
|
|
27
29
|
"promisify-child-process": "^4.1.1",
|
|
28
30
|
"zod": "^3.22.4"
|
|
29
31
|
},
|
|
@@ -32,13 +34,19 @@
|
|
|
32
34
|
"types": "index.d.ts",
|
|
33
35
|
"exports": {
|
|
34
36
|
".": {
|
|
35
|
-
"types":
|
|
37
|
+
"types": {
|
|
38
|
+
"import": "./index.d.mts",
|
|
39
|
+
"default": "./index.d.ts"
|
|
40
|
+
},
|
|
36
41
|
"require": "./index.js",
|
|
37
42
|
"default": "./index.mjs"
|
|
38
43
|
},
|
|
39
44
|
"./package.json": "./package.json",
|
|
40
45
|
"./*": {
|
|
41
|
-
"types":
|
|
46
|
+
"types": {
|
|
47
|
+
"import": "./*.d.mts",
|
|
48
|
+
"default": "./*.d.ts"
|
|
49
|
+
},
|
|
42
50
|
"require": "./*.js",
|
|
43
51
|
"default": "./*.mjs"
|
|
44
52
|
}
|
|
@@ -46,5 +54,8 @@
|
|
|
46
54
|
"engines": {
|
|
47
55
|
"node": ">=16"
|
|
48
56
|
},
|
|
49
|
-
"packageManager": "pnpm@
|
|
57
|
+
"packageManager": "pnpm@10.6.5",
|
|
58
|
+
"@jcoreio/toolchain": {
|
|
59
|
+
"migratedVersion": "5.8.6"
|
|
60
|
+
}
|
|
50
61
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseECRImageUri.d.mts","names":["parseECRImageUri","imageUri","registryId","region","repositoryName","imageTag"],"sources":["src/parseECRImageUri.ts"],"sourcesContent":[null],"mappings":"AAAA,eAAc,SAAUA,gBAAgBA,CAACC,QAAQ,EAAE,MAAM,GAAG;EAC1DC,UAAU,EAAE,MAAM;EAClBC,MAAM,EAAE,MAAM;EACdC,cAAc,EAAE,MAAM;EACtBC,QAAQ,EAAE,MAAM;CACjB","ignoreList":[]}
|
package/parseECRImageUri.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
imageTag: string;
|
|
1
|
+
declare function parseECRImageUri(imageUri: string): {
|
|
2
|
+
registryId: string;
|
|
3
|
+
region: string;
|
|
4
|
+
repositoryName: string;
|
|
5
|
+
imageTag: string;
|
|
7
6
|
};
|
|
7
|
+
export = parseECRImageUri;
|
|
8
|
+
//# sourceMappingURL=parseECRImageUri.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseECRImageUri.d.ts","names":["parseECRImageUri","imageUri","registryId","region","repositoryName","imageTag"],"sources":["src/parseECRImageUri.ts"],"sourcesContent":[null],"mappings":"AAAc,iBAAUA,gBAAgBA,CAACC,QAAQ,EAAE,MAAM,GAAG;EAC1DC,UAAU,EAAE,MAAM;EAClBC,MAAM,EAAE,MAAM;EACdC,cAAc,EAAE,MAAM;EACtBC,QAAQ,EAAE,MAAM;CACjB;AAOA,SAAAL,gBAAA","ignoreList":[]}
|
package/parseECRImageUri.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseECRImageUri.js","names":["parseECRImageUri","imageUri","match","exec","Error","concat","_match","_slicedToArray2","registryId","region","repositoryName","imageTag","module","exports","default"],"sources":["src/parseECRImageUri.ts"],"sourcesContent":[null],"mappings":";;;;;;;;AAAe,SAASA,gBAAgBA,CAACC,QAAgB,EAKvD;EACA,IAAMC,KAAK,GAAG,wDAAwD,CAACC,IAAI,CACzEF,QACF,CAAC;EACD,IAAI,CAACC,KAAK,EAAE,MAAM,IAAIE,KAAK,sBAAAC,MAAA,CAAsBJ,QAAQ,CAAE,CAAC;EAC5D,IAAAK,MAAA,OAAAC,eAAA,aAAyDL,KAAK;IAArDM,UAAU,GAAAF,MAAA;IAAEG,MAAM,GAAAH,MAAA;IAAEI,cAAc,GAAAJ,MAAA;IAAEK,QAAQ,GAAAL,MAAA;EACrD,OAAO;IAAEE,UAAU,EAAVA,UAAU;IAAEC,MAAM,EAANA,MAAM;IAAEC,cAAc,EAAdA,cAAc;IAAEC,QAAQ,EAARA;EAAS,CAAC;AACzD;AAACC,MAAA,CAAAC,OAAA,GAAAA,OAAA,CAAAC,OAAA","ignoreList":[]}
|
package/parseECRImageUri.mjs
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseECRImageUri.mjs","names":["parseECRImageUri","imageUri","match","exec","Error","registryId","region","repositoryName","imageTag"],"sources":["src/parseECRImageUri.ts"],"sourcesContent":[null],"mappings":"AAAA,eAAe,SAASA,gBAAgBA,CAACC,QAAgB,EAKvD;EACA,MAAMC,KAAK,GAAG,wDAAwD,CAACC,IAAI,CACzEF,QACF,CAAC;EACD,IAAI,CAACC,KAAK,EAAE,MAAM,IAAIE,KAAK,CAAC,qBAAqBH,QAAQ,EAAE,CAAC;EAC5D,MAAM,GAAGI,UAAU,EAAEC,MAAM,EAAEC,cAAc,EAAEC,QAAQ,CAAC,GAAGN,KAAK;EAC9D,OAAO;IAAEG,UAAU;IAAEC,MAAM;IAAEC,cAAc;IAAEC;EAAS,CAAC;AACzD","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseECRRepositoryHostname.d.mts","names":["parseECRRepositoryHostname","hostname","registryId","region","repositoryName"],"sources":["src/parseECRRepositoryHostname.ts"],"sourcesContent":[null],"mappings":"AAAA,eAAc,SAAUA,0BAA0BA,CAACC,QAAQ,EAAE,MAAM,GAAG;EACpEC,UAAU,EAAE,MAAM;EAClBC,MAAM,EAAE,MAAM;EACdC,cAAc,EAAE,MAAM;CACvB","ignoreList":[]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
repositoryName: AWS.ECR.RepositoryName;
|
|
1
|
+
declare function parseECRRepositoryHostname(hostname: string): {
|
|
2
|
+
registryId: string;
|
|
3
|
+
region: string;
|
|
4
|
+
repositoryName: string;
|
|
6
5
|
};
|
|
6
|
+
export = parseECRRepositoryHostname;
|
|
7
|
+
//# sourceMappingURL=parseECRRepositoryHostname.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseECRRepositoryHostname.d.ts","names":["parseECRRepositoryHostname","hostname","registryId","region","repositoryName"],"sources":["src/parseECRRepositoryHostname.ts"],"sourcesContent":[null],"mappings":"AAAc,iBAAUA,0BAA0BA,CAACC,QAAQ,EAAE,MAAM,GAAG;EACpEC,UAAU,EAAE,MAAM;EAClBC,MAAM,EAAE,MAAM;EACdC,cAAc,EAAE,MAAM;CACvB;AAOA,SAAAJ,0BAAA","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseECRRepositoryHostname.js","names":["parseECRRepositoryHostname","hostname","match","exec","Error","concat","_match","_slicedToArray2","registryId","region","repositoryName","module","exports","default"],"sources":["src/parseECRRepositoryHostname.ts"],"sourcesContent":[null],"mappings":";;;;;;;;AAAe,SAASA,0BAA0BA,CAACC,QAAgB,EAIjE;EACA,IAAMC,KAAK,GAAG,sDAAsD,CAACC,IAAI,CACvEF,QACF,CAAC;EACD,IAAI,CAACC,KAAK,EAAE,MAAM,IAAIE,KAAK,qCAAAC,MAAA,CAAqCJ,QAAQ,CAAE,CAAC;EAC3E,IAAAK,MAAA,OAAAC,eAAA,aAA+CL,KAAK;IAA3CM,UAAU,GAAAF,MAAA;IAAEG,MAAM,GAAAH,MAAA;IAAEI,cAAc,GAAAJ,MAAA;EAC3C,OAAO;IAAEE,UAAU,EAAVA,UAAU;IAAEC,MAAM,EAANA,MAAM;IAAEC,cAAc,EAAdA;EAAe,CAAC;AAC/C;AAACC,MAAA,CAAAC,OAAA,GAAAA,OAAA,CAAAC,OAAA","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseECRRepositoryHostname.mjs","names":["parseECRRepositoryHostname","hostname","match","exec","Error","registryId","region","repositoryName"],"sources":["src/parseECRRepositoryHostname.ts"],"sourcesContent":[null],"mappings":"AAAA,eAAe,SAASA,0BAA0BA,CAACC,QAAgB,EAIjE;EACA,MAAMC,KAAK,GAAG,sDAAsD,CAACC,IAAI,CACvEF,QACF,CAAC;EACD,IAAI,CAACC,KAAK,EAAE,MAAM,IAAIE,KAAK,CAAC,oCAAoCH,QAAQ,EAAE,CAAC;EAC3E,MAAM,GAAGI,UAAU,EAAEC,MAAM,EAAEC,cAAc,CAAC,GAAGL,KAAK;EACpD,OAAO;IAAEG,UAAU;IAAEC,MAAM;IAAEC;EAAe,CAAC;AAC/C","ignoreList":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import z from 'zod'
|
|
2
|
+
|
|
3
|
+
const MediaType = z.string().min(1)
|
|
4
|
+
const Size = z.number().int().nonnegative()
|
|
5
|
+
const Digest = z.string().min(32)
|
|
6
|
+
|
|
7
|
+
const LayerSchema = z.object({
|
|
8
|
+
mediaType: MediaType,
|
|
9
|
+
size: Size,
|
|
10
|
+
digest: Digest,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export const ImageManifestSchema = z.object({
|
|
14
|
+
schemaVersion: z.literal(2),
|
|
15
|
+
mediaType: z.string(),
|
|
16
|
+
config: LayerSchema,
|
|
17
|
+
layers: z.array(LayerSchema),
|
|
18
|
+
})
|
|
19
|
+
export type ImageManifestSchema = z.infer<typeof ImageManifestSchema>
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BatchCheckLayerAvailabilityCommand,
|
|
3
|
+
BatchGetImageCommand,
|
|
4
|
+
ECRClient,
|
|
5
|
+
ECRClientConfig,
|
|
6
|
+
GetDownloadUrlForLayerCommand,
|
|
7
|
+
GetRepositoryPolicyCommand,
|
|
8
|
+
SetRepositoryPolicyCommand,
|
|
9
|
+
} from '@aws-sdk/client-ecr'
|
|
10
|
+
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts'
|
|
11
|
+
import parseECRImageUri from './parseECRImageUri'
|
|
12
|
+
import { ImageManifestSchema } from './ImageManifestSchema'
|
|
13
|
+
import isInteractive from 'is-interactive'
|
|
14
|
+
import inquirer from 'inquirer'
|
|
15
|
+
import formatECRRepositoryHostname from './formatECRRepositoryHostname'
|
|
16
|
+
|
|
17
|
+
export default async function checkECRImageAccess({
|
|
18
|
+
ecr,
|
|
19
|
+
awsConfig,
|
|
20
|
+
repoAccountAwsConfig,
|
|
21
|
+
imageUri,
|
|
22
|
+
log = console,
|
|
23
|
+
}: {
|
|
24
|
+
ecr?: ECRClient
|
|
25
|
+
awsConfig?: ECRClientConfig
|
|
26
|
+
/**
|
|
27
|
+
* Config for the AWS account containing the ECR repository.
|
|
28
|
+
* Optional; if given, will prompt to add/update the policy on the
|
|
29
|
+
* ECR repository, if access checks failed and the terminal is
|
|
30
|
+
* interactive.
|
|
31
|
+
*/
|
|
32
|
+
repoAccountAwsConfig?: ECRClientConfig
|
|
33
|
+
imageUri: string
|
|
34
|
+
log?: {
|
|
35
|
+
info: (...args: any[]) => void
|
|
36
|
+
warn: (...args: any[]) => void
|
|
37
|
+
error: (...args: any[]) => void
|
|
38
|
+
}
|
|
39
|
+
}): Promise<boolean> {
|
|
40
|
+
log.error('checking access to ECR image:', imageUri, '...')
|
|
41
|
+
|
|
42
|
+
const { registryId, region, repositoryName, imageTag } =
|
|
43
|
+
parseECRImageUri(imageUri)
|
|
44
|
+
if (!ecr) ecr = new ECRClient({ ...awsConfig, region })
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const { images = [] } = await ecr.send(
|
|
48
|
+
new BatchGetImageCommand({
|
|
49
|
+
registryId,
|
|
50
|
+
repositoryName,
|
|
51
|
+
imageIds: [{ imageTag }],
|
|
52
|
+
})
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
const imageManifest = images[0]?.imageManifest
|
|
56
|
+
|
|
57
|
+
if (!imageManifest) {
|
|
58
|
+
throw new Error(`imageManifest not found for: ${imageUri}`)
|
|
59
|
+
}
|
|
60
|
+
const { config, layers } = ImageManifestSchema.parse(
|
|
61
|
+
JSON.parse(imageManifest)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
await ecr.send(
|
|
65
|
+
new BatchCheckLayerAvailabilityCommand({
|
|
66
|
+
registryId,
|
|
67
|
+
repositoryName,
|
|
68
|
+
layerDigests: [config.digest, ...layers.map((l) => l.digest)],
|
|
69
|
+
})
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
await ecr.send(
|
|
73
|
+
new GetDownloadUrlForLayerCommand({
|
|
74
|
+
registryId,
|
|
75
|
+
repositoryName,
|
|
76
|
+
layerDigest: layers[0].digest,
|
|
77
|
+
})
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
log.error(`ECR image is accessible: ${imageUri}`)
|
|
81
|
+
return true
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (!(error instanceof Error) || error.name !== 'AccessDeniedException') {
|
|
84
|
+
throw error
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
log.error(`Unable to access ECR image: ${imageUri}`)
|
|
88
|
+
|
|
89
|
+
const Action = [
|
|
90
|
+
'ecr:GetDownloadUrlForLayer',
|
|
91
|
+
'ecr:BatchCheckLayerAvailability',
|
|
92
|
+
'ecr:BatchGetImage',
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
log.error(`You may need to add a policy to the ECR repository to allow this account.
|
|
96
|
+
|
|
97
|
+
The policy should include:
|
|
98
|
+
|
|
99
|
+
${JSON.stringify(
|
|
100
|
+
{
|
|
101
|
+
Version: '2012-10-17',
|
|
102
|
+
Statement: [
|
|
103
|
+
{
|
|
104
|
+
Effect: 'Allow',
|
|
105
|
+
Principal: {
|
|
106
|
+
AWS: ['XXXXXXXXXXXX'],
|
|
107
|
+
},
|
|
108
|
+
Action,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
null,
|
|
113
|
+
2
|
|
114
|
+
).replace(/\n/gm, '\n ')}
|
|
115
|
+
`)
|
|
116
|
+
|
|
117
|
+
if (repoAccountAwsConfig && isInteractive()) {
|
|
118
|
+
const { Account } = await new STSClient({
|
|
119
|
+
credentials: ecr.config.credentials,
|
|
120
|
+
region,
|
|
121
|
+
}).send(new GetCallerIdentityCommand())
|
|
122
|
+
if (!Account) {
|
|
123
|
+
log.error(`failed to determine AWS account`)
|
|
124
|
+
return false
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const { update } = await inquirer.prompt([
|
|
128
|
+
{
|
|
129
|
+
name: 'update',
|
|
130
|
+
message: 'Do you want to add/update the policy?',
|
|
131
|
+
type: 'confirm',
|
|
132
|
+
default: false,
|
|
133
|
+
},
|
|
134
|
+
])
|
|
135
|
+
if (!update) return false
|
|
136
|
+
|
|
137
|
+
const srcEcr = new ECRClient({
|
|
138
|
+
...repoAccountAwsConfig,
|
|
139
|
+
region,
|
|
140
|
+
})
|
|
141
|
+
const { policyText } = await srcEcr
|
|
142
|
+
.send(
|
|
143
|
+
new GetRepositoryPolicyCommand({
|
|
144
|
+
registryId,
|
|
145
|
+
repositoryName,
|
|
146
|
+
})
|
|
147
|
+
)
|
|
148
|
+
.catch((error: unknown): { policyText?: string } => {
|
|
149
|
+
if (
|
|
150
|
+
error &&
|
|
151
|
+
typeof error === 'object' &&
|
|
152
|
+
'name' in error &&
|
|
153
|
+
error.name === 'RepositoryPolicyNotFoundException'
|
|
154
|
+
)
|
|
155
|
+
return {}
|
|
156
|
+
throw error
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const policy: any = JSON.parse(policyText || '{}')
|
|
160
|
+
await srcEcr.send(
|
|
161
|
+
new SetRepositoryPolicyCommand({
|
|
162
|
+
repositoryName,
|
|
163
|
+
policyText: JSON.stringify(
|
|
164
|
+
{
|
|
165
|
+
Version: '2012-10-17',
|
|
166
|
+
...policy,
|
|
167
|
+
Statement: [
|
|
168
|
+
...(policy.Statement || []),
|
|
169
|
+
{
|
|
170
|
+
Effect: 'Allow',
|
|
171
|
+
Principal: {
|
|
172
|
+
AWS: [Account],
|
|
173
|
+
},
|
|
174
|
+
Action,
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
null,
|
|
179
|
+
2
|
|
180
|
+
),
|
|
181
|
+
})
|
|
182
|
+
)
|
|
183
|
+
log.info(
|
|
184
|
+
`updated policy on ECR repository ${formatECRRepositoryHostname({
|
|
185
|
+
registryId,
|
|
186
|
+
region,
|
|
187
|
+
repositoryName,
|
|
188
|
+
})}`
|
|
189
|
+
)
|
|
190
|
+
return await checkECRImageAccess({ awsConfig, imageUri, log, ecr })
|
|
191
|
+
}
|
|
192
|
+
return false
|
|
193
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ECRClient,
|
|
3
|
+
ECRClientConfig,
|
|
4
|
+
GetRepositoryPolicyCommand,
|
|
5
|
+
SetRepositoryPolicyCommand,
|
|
6
|
+
} from '@aws-sdk/client-ecr'
|
|
7
|
+
import inquirer from 'inquirer'
|
|
8
|
+
import isInteractive from 'is-interactive'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Checks if the given ECR repository has a sufficient repository
|
|
12
|
+
* policy to allow the given AWS principal to access docker images.
|
|
13
|
+
*
|
|
14
|
+
* If not, prints a warning, and if the terminal is interactive, asks the user if they
|
|
15
|
+
* would like to add/update the repository policy.
|
|
16
|
+
*/
|
|
17
|
+
export default async function checkECRRepositoryPolicy({
|
|
18
|
+
ecr,
|
|
19
|
+
awsConfig,
|
|
20
|
+
repositoryName,
|
|
21
|
+
awsPrincipal,
|
|
22
|
+
Action = [
|
|
23
|
+
'ecr:GetDownloadUrlForLayer',
|
|
24
|
+
'ecr:BatchCheckLayerAvailability',
|
|
25
|
+
'ecr:BatchGetImage',
|
|
26
|
+
],
|
|
27
|
+
log = console,
|
|
28
|
+
}: {
|
|
29
|
+
ecr?: ECRClient
|
|
30
|
+
awsConfig?: ECRClientConfig
|
|
31
|
+
repositoryName: string
|
|
32
|
+
awsPrincipal: string
|
|
33
|
+
Action?: string[]
|
|
34
|
+
log?: {
|
|
35
|
+
info: (...args: any[]) => void
|
|
36
|
+
warn: (...args: any[]) => void
|
|
37
|
+
error: (...args: any[]) => void
|
|
38
|
+
}
|
|
39
|
+
}): Promise<boolean> {
|
|
40
|
+
const rootUserMatch = /^arn:aws:iam::(\d+):root$/.exec(awsPrincipal)
|
|
41
|
+
if (rootUserMatch) awsPrincipal = rootUserMatch[1]
|
|
42
|
+
|
|
43
|
+
const principalAliases =
|
|
44
|
+
/^(\d+)$/.test(awsPrincipal) ?
|
|
45
|
+
[awsPrincipal, `arn:aws:iam::${awsPrincipal}:root`]
|
|
46
|
+
: [awsPrincipal]
|
|
47
|
+
|
|
48
|
+
if (!ecr) ecr = new ECRClient({ ...awsConfig })
|
|
49
|
+
const { policyText } = await ecr
|
|
50
|
+
.send(new GetRepositoryPolicyCommand({ repositoryName }))
|
|
51
|
+
.catch((error: unknown): { policyText?: string } => {
|
|
52
|
+
if (
|
|
53
|
+
error &&
|
|
54
|
+
typeof error === 'object' &&
|
|
55
|
+
'name' in error &&
|
|
56
|
+
error.name === 'RepositoryPolicyNotFoundException'
|
|
57
|
+
)
|
|
58
|
+
return {}
|
|
59
|
+
throw error
|
|
60
|
+
})
|
|
61
|
+
const policy: any = JSON.parse(policyText || '{}')
|
|
62
|
+
const statementForAction = policy.Statement?.find(
|
|
63
|
+
(s: any) =>
|
|
64
|
+
s.Effect === 'Allow' &&
|
|
65
|
+
Array.isArray(Action) &&
|
|
66
|
+
Action.every((a) => s.Action?.includes(a))
|
|
67
|
+
)
|
|
68
|
+
const statementPrincipal = statementForAction?.Principal?.AWS
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
(statementPrincipal &&
|
|
72
|
+
typeof statementPrincipal === 'string' &&
|
|
73
|
+
principalAliases.includes(statementPrincipal)) ||
|
|
74
|
+
(Array.isArray(statementPrincipal) &&
|
|
75
|
+
statementPrincipal.some((s) => principalAliases.includes(s)))
|
|
76
|
+
) {
|
|
77
|
+
log.info(
|
|
78
|
+
`Found policy on ECR repository ${repositoryName} to allow access for AWS Principal ${awsPrincipal}.`
|
|
79
|
+
)
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// eslint-disable-next-line no-console
|
|
84
|
+
console.warn(`Missing policy on ECR repository ${repositoryName} to allow access for AWS Principal ${awsPrincipal}.
|
|
85
|
+
|
|
86
|
+
The policy should include:
|
|
87
|
+
|
|
88
|
+
${JSON.stringify(
|
|
89
|
+
{
|
|
90
|
+
Version: '2012-10-17',
|
|
91
|
+
Statement: [
|
|
92
|
+
{
|
|
93
|
+
Effect: 'Allow',
|
|
94
|
+
Principal: {
|
|
95
|
+
AWS: [awsPrincipal],
|
|
96
|
+
},
|
|
97
|
+
Action,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
null,
|
|
102
|
+
2
|
|
103
|
+
).replace(/\n/gm, '\n ')}
|
|
104
|
+
`)
|
|
105
|
+
if (isInteractive()) {
|
|
106
|
+
const { update } = await inquirer.prompt([
|
|
107
|
+
{
|
|
108
|
+
name: 'update',
|
|
109
|
+
message: 'Do you want to add/update the policy?',
|
|
110
|
+
type: 'confirm',
|
|
111
|
+
default: false,
|
|
112
|
+
},
|
|
113
|
+
])
|
|
114
|
+
if (update) {
|
|
115
|
+
let finalPolicy = policy
|
|
116
|
+
if (statementForAction?.Action?.length === Action.length) {
|
|
117
|
+
statementForAction.Principal = {
|
|
118
|
+
...statementForAction.Principal,
|
|
119
|
+
AWS: [
|
|
120
|
+
...(typeof statementPrincipal === 'string' ? [statementPrincipal]
|
|
121
|
+
: Array.isArray(statementPrincipal) ? statementPrincipal
|
|
122
|
+
: []),
|
|
123
|
+
awsPrincipal,
|
|
124
|
+
],
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
finalPolicy = {
|
|
128
|
+
Version: '2012-10-17',
|
|
129
|
+
...policy,
|
|
130
|
+
Statement: [
|
|
131
|
+
...(policy.Statement || []),
|
|
132
|
+
{
|
|
133
|
+
Effect: 'Allow',
|
|
134
|
+
Principal: {
|
|
135
|
+
AWS: [awsPrincipal],
|
|
136
|
+
},
|
|
137
|
+
Action,
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
await ecr.send(
|
|
143
|
+
new SetRepositoryPolicyCommand({
|
|
144
|
+
repositoryName,
|
|
145
|
+
policyText: JSON.stringify(finalPolicy, null, 2),
|
|
146
|
+
})
|
|
147
|
+
)
|
|
148
|
+
log.info(`updated policy on ECR repository ${repositoryName}`)
|
|
149
|
+
return true
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return false
|
|
153
|
+
}
|