@jcoreio/aws-ecr-utils 1.1.1 → 1.3.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.ts +58 -0
- package/ImageManifestSchema.js +22 -0
- package/ImageManifestSchema.mjs +15 -0
- package/checkECRImageAccess.d.ts +18 -0
- package/checkECRImageAccess.js +180 -0
- package/checkECRImageAccess.mjs +133 -0
- package/checkECRRepositoryPolicy.d.ts +20 -0
- package/checkECRRepositoryPolicy.js +132 -0
- package/checkECRRepositoryPolicy.mjs +100 -0
- package/copyECRImage.js +79 -104
- package/copyECRImage.mjs +67 -0
- package/ecrImageExists.js +51 -74
- package/ecrImageExists.mjs +31 -0
- package/formatECRImageUri.d.ts +6 -0
- package/formatECRImageUri.js +20 -0
- package/formatECRImageUri.mjs +13 -0
- package/formatECRRepositoryHostname.d.ts +5 -0
- package/formatECRRepositoryHostname.js +13 -0
- package/formatECRRepositoryHostname.mjs +7 -0
- package/index.d.ts +5 -0
- package/index.js +41 -13
- package/index.mjs +11 -0
- package/loginToECR.js +44 -65
- package/loginToECR.mjs +38 -0
- package/package.json +27 -105
- package/parseECRImageUri.d.ts +3 -2
- package/parseECRImageUri.js +6 -10
- package/parseECRImageUri.mjs +11 -0
- package/parseECRRepositoryHostname.d.ts +6 -0
- package/parseECRRepositoryHostname.js +22 -0
- package/parseECRRepositoryHostname.mjs +10 -0
- package/tagECRImage.js +81 -115
- package/tagECRImage.mjs +50 -0
- package/upsertECRRepository.js +45 -64
- package/upsertECRRepository.mjs +33 -0
- package/es/copyECRImage.d.ts +0 -13
- package/es/copyECRImage.js +0 -76
- package/es/ecrImageExists.d.ts +0 -9
- package/es/ecrImageExists.js +0 -45
- package/es/index.d.ts +0 -6
- package/es/index.js +0 -56
- package/es/loginToECR.d.ts +0 -8
- package/es/loginToECR.js +0 -56
- package/es/parseECRImageUri.d.ts +0 -6
- package/es/parseECRImageUri.js +0 -19
- package/es/tagECRImage.d.ts +0 -10
- package/es/tagECRImage.js +0 -65
- package/es/upsertECRRepository.d.ts +0 -6
- package/es/upsertECRRepository.js +0 -45
- /package/{es/copyECRImage.js.flow → copyECRImage.mjs.flow} +0 -0
- /package/{es/ecrImageExists.js.flow → ecrImageExists.mjs.flow} +0 -0
- /package/{es/index.js.flow → index.mjs.flow} +0 -0
- /package/{es/loginToECR.js.flow → loginToECR.mjs.flow} +0 -0
- /package/{es/parseECRImageUri.js.flow → parseECRImageUri.mjs.flow} +0 -0
- /package/{es/tagECRImage.js.flow → tagECRImage.mjs.flow} +0 -0
- /package/{es/upsertECRRepository.js.flow → upsertECRRepository.mjs.flow} +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
export declare const ImageManifestSchema: z.ZodObject<{
|
|
3
|
+
schemaVersion: z.ZodLiteral<2>;
|
|
4
|
+
mediaType: z.ZodString;
|
|
5
|
+
config: z.ZodObject<{
|
|
6
|
+
mediaType: z.ZodString;
|
|
7
|
+
size: z.ZodNumber;
|
|
8
|
+
digest: z.ZodString;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
mediaType: string;
|
|
11
|
+
size: number;
|
|
12
|
+
digest: string;
|
|
13
|
+
}, {
|
|
14
|
+
mediaType: string;
|
|
15
|
+
size: number;
|
|
16
|
+
digest: string;
|
|
17
|
+
}>;
|
|
18
|
+
layers: z.ZodArray<z.ZodObject<{
|
|
19
|
+
mediaType: z.ZodString;
|
|
20
|
+
size: z.ZodNumber;
|
|
21
|
+
digest: z.ZodString;
|
|
22
|
+
}, "strip", z.ZodTypeAny, {
|
|
23
|
+
mediaType: string;
|
|
24
|
+
size: number;
|
|
25
|
+
digest: string;
|
|
26
|
+
}, {
|
|
27
|
+
mediaType: string;
|
|
28
|
+
size: number;
|
|
29
|
+
digest: string;
|
|
30
|
+
}>, "many">;
|
|
31
|
+
}, "strip", z.ZodTypeAny, {
|
|
32
|
+
mediaType: string;
|
|
33
|
+
schemaVersion: 2;
|
|
34
|
+
config: {
|
|
35
|
+
mediaType: string;
|
|
36
|
+
size: number;
|
|
37
|
+
digest: string;
|
|
38
|
+
};
|
|
39
|
+
layers: {
|
|
40
|
+
mediaType: string;
|
|
41
|
+
size: number;
|
|
42
|
+
digest: string;
|
|
43
|
+
}[];
|
|
44
|
+
}, {
|
|
45
|
+
mediaType: string;
|
|
46
|
+
schemaVersion: 2;
|
|
47
|
+
config: {
|
|
48
|
+
mediaType: string;
|
|
49
|
+
size: number;
|
|
50
|
+
digest: string;
|
|
51
|
+
};
|
|
52
|
+
layers: {
|
|
53
|
+
mediaType: string;
|
|
54
|
+
size: number;
|
|
55
|
+
digest: string;
|
|
56
|
+
}[];
|
|
57
|
+
}>;
|
|
58
|
+
export type ImageManifestSchema = z.infer<typeof ImageManifestSchema>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.ImageManifestSchema = void 0;
|
|
8
|
+
var _zod = _interopRequireDefault(require("zod"));
|
|
9
|
+
var MediaType = _zod["default"].string().min(1);
|
|
10
|
+
var Size = _zod["default"].number()["int"]().nonnegative();
|
|
11
|
+
var Digest = _zod["default"].string().min(32);
|
|
12
|
+
var LayerSchema = _zod["default"].object({
|
|
13
|
+
mediaType: MediaType,
|
|
14
|
+
size: Size,
|
|
15
|
+
digest: Digest
|
|
16
|
+
});
|
|
17
|
+
var ImageManifestSchema = exports.ImageManifestSchema = _zod["default"].object({
|
|
18
|
+
schemaVersion: _zod["default"].literal(2),
|
|
19
|
+
mediaType: _zod["default"].string(),
|
|
20
|
+
config: LayerSchema,
|
|
21
|
+
layers: _zod["default"].array(LayerSchema)
|
|
22
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
const MediaType = z.string().min(1);
|
|
3
|
+
const Size = z.number().int().nonnegative();
|
|
4
|
+
const Digest = z.string().min(32);
|
|
5
|
+
const LayerSchema = z.object({
|
|
6
|
+
mediaType: MediaType,
|
|
7
|
+
size: Size,
|
|
8
|
+
digest: Digest
|
|
9
|
+
});
|
|
10
|
+
export const ImageManifestSchema = z.object({
|
|
11
|
+
schemaVersion: z.literal(2),
|
|
12
|
+
mediaType: z.string(),
|
|
13
|
+
config: LayerSchema,
|
|
14
|
+
layers: z.array(LayerSchema)
|
|
15
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import AWS from 'aws-sdk';
|
|
2
|
+
export default function checkECRImageAccess({ ecr, awsConfig, repoAccountAwsConfig, imageUri, log, }: {
|
|
3
|
+
ecr?: AWS.ECR;
|
|
4
|
+
awsConfig?: AWS.ConfigurationOptions;
|
|
5
|
+
/**
|
|
6
|
+
* Config for the AWS account containing the ECR repository.
|
|
7
|
+
* Optional; if given, will prompt to add/update the policy on the
|
|
8
|
+
* ECR repository, if access checks failed and the terminal is
|
|
9
|
+
* interactive.
|
|
10
|
+
*/
|
|
11
|
+
repoAccountAwsConfig?: AWS.ConfigurationOptions;
|
|
12
|
+
imageUri: string;
|
|
13
|
+
log?: {
|
|
14
|
+
info: (...args: any[]) => void;
|
|
15
|
+
warn: (...args: any[]) => void;
|
|
16
|
+
error: (...args: any[]) => void;
|
|
17
|
+
};
|
|
18
|
+
}): Promise<boolean>;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports["default"] = checkECRImageAccess;
|
|
8
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
9
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
10
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
11
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
12
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
13
|
+
var _awsSdk = _interopRequireDefault(require("aws-sdk"));
|
|
14
|
+
var _parseECRImageUri2 = _interopRequireDefault(require("./parseECRImageUri.js"));
|
|
15
|
+
var _ImageManifestSchema = require("./ImageManifestSchema.js");
|
|
16
|
+
var _isInteractive = _interopRequireDefault(require("is-interactive"));
|
|
17
|
+
var _inquirer = _interopRequireDefault(require("inquirer"));
|
|
18
|
+
var _formatECRRepositoryHostname = _interopRequireDefault(require("./formatECRRepositoryHostname.js"));
|
|
19
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
20
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
21
|
+
function checkECRImageAccess(_x) {
|
|
22
|
+
return _checkECRImageAccess.apply(this, arguments);
|
|
23
|
+
}
|
|
24
|
+
function _checkECRImageAccess() {
|
|
25
|
+
_checkECRImageAccess = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(_ref) {
|
|
26
|
+
var ecr, awsConfig, repoAccountAwsConfig, imageUri, _ref$log, log, _parseECRImageUri, registryId, region, repositoryName, imageTag, _yield$ecr$batchGetIm, _yield$ecr$batchGetIm2, _yield$ecr$batchGetIm3, _yield$ecr$batchGetIm4, image, imageManifest, _ImageManifestSchema$, config, layers, Action, _yield$AWS$STS$getCal, Account, _yield$inquirer$promp, update, srcEcr, _yield$srcEcr$getRepo, policyText, policy;
|
|
27
|
+
return _regenerator["default"].wrap(function _callee$(_context) {
|
|
28
|
+
while (1) switch (_context.prev = _context.next) {
|
|
29
|
+
case 0:
|
|
30
|
+
ecr = _ref.ecr, awsConfig = _ref.awsConfig, repoAccountAwsConfig = _ref.repoAccountAwsConfig, imageUri = _ref.imageUri, _ref$log = _ref.log, log = _ref$log === void 0 ? console : _ref$log;
|
|
31
|
+
log.error('checking access to ECR image:', imageUri, '...');
|
|
32
|
+
_parseECRImageUri = (0, _parseECRImageUri2["default"])(imageUri), registryId = _parseECRImageUri.registryId, region = _parseECRImageUri.region, repositoryName = _parseECRImageUri.repositoryName, imageTag = _parseECRImageUri.imageTag;
|
|
33
|
+
if (!ecr) ecr = new _awsSdk["default"].ECR(_objectSpread(_objectSpread({}, awsConfig), {}, {
|
|
34
|
+
region: region
|
|
35
|
+
}));
|
|
36
|
+
_context.prev = 4;
|
|
37
|
+
_context.next = 7;
|
|
38
|
+
return ecr.batchGetImage({
|
|
39
|
+
registryId: registryId,
|
|
40
|
+
repositoryName: repositoryName,
|
|
41
|
+
imageIds: [{
|
|
42
|
+
imageTag: imageTag
|
|
43
|
+
}]
|
|
44
|
+
}).promise();
|
|
45
|
+
case 7:
|
|
46
|
+
_yield$ecr$batchGetIm = _context.sent;
|
|
47
|
+
_yield$ecr$batchGetIm2 = _yield$ecr$batchGetIm.images;
|
|
48
|
+
_yield$ecr$batchGetIm3 = _yield$ecr$batchGetIm2 === void 0 ? [] : _yield$ecr$batchGetIm2;
|
|
49
|
+
_yield$ecr$batchGetIm4 = (0, _slicedToArray2["default"])(_yield$ecr$batchGetIm3, 1);
|
|
50
|
+
image = _yield$ecr$batchGetIm4[0];
|
|
51
|
+
imageManifest = image === null || image === void 0 ? void 0 : image.imageManifest;
|
|
52
|
+
if (imageManifest) {
|
|
53
|
+
_context.next = 15;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
throw new Error("imageManifest not found for: ".concat(imageUri));
|
|
57
|
+
case 15:
|
|
58
|
+
_ImageManifestSchema$ = _ImageManifestSchema.ImageManifestSchema.parse(JSON.parse(imageManifest)), config = _ImageManifestSchema$.config, layers = _ImageManifestSchema$.layers;
|
|
59
|
+
_context.next = 18;
|
|
60
|
+
return ecr.batchCheckLayerAvailability({
|
|
61
|
+
registryId: registryId,
|
|
62
|
+
repositoryName: repositoryName,
|
|
63
|
+
layerDigests: [config.digest].concat((0, _toConsumableArray2["default"])(layers.map(function (l) {
|
|
64
|
+
return l.digest;
|
|
65
|
+
})))
|
|
66
|
+
}).promise();
|
|
67
|
+
case 18:
|
|
68
|
+
_context.next = 20;
|
|
69
|
+
return ecr.getDownloadUrlForLayer({
|
|
70
|
+
registryId: registryId,
|
|
71
|
+
repositoryName: repositoryName,
|
|
72
|
+
layerDigest: layers[0].digest
|
|
73
|
+
}).promise();
|
|
74
|
+
case 20:
|
|
75
|
+
log.error("ECR image is accessible: ".concat(imageUri));
|
|
76
|
+
return _context.abrupt("return", true);
|
|
77
|
+
case 24:
|
|
78
|
+
_context.prev = 24;
|
|
79
|
+
_context.t0 = _context["catch"](4);
|
|
80
|
+
if (!(!(_context.t0 instanceof Error) || _context.t0.name !== 'AccessDeniedException')) {
|
|
81
|
+
_context.next = 28;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
throw _context.t0;
|
|
85
|
+
case 28:
|
|
86
|
+
log.error("Unable to access ECR image: ".concat(imageUri));
|
|
87
|
+
Action = ['ecr:GetDownloadUrlForLayer', 'ecr:BatchCheckLayerAvailability', 'ecr:BatchGetImage'];
|
|
88
|
+
log.error("You may need to add a policy to the ECR repository to allow this account.\n\nThe policy should include:\n\n ".concat(JSON.stringify({
|
|
89
|
+
Version: '2012-10-17',
|
|
90
|
+
Statement: [{
|
|
91
|
+
Effect: 'Allow',
|
|
92
|
+
Principal: {
|
|
93
|
+
AWS: ['XXXXXXXXXXXX']
|
|
94
|
+
},
|
|
95
|
+
Action: Action
|
|
96
|
+
}]
|
|
97
|
+
}, null, 2).replace(/\n/gm, '\n '), "\n"));
|
|
98
|
+
if (!(repoAccountAwsConfig && (0, _isInteractive["default"])())) {
|
|
99
|
+
_context.next = 55;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
_context.next = 34;
|
|
103
|
+
return new _awsSdk["default"].STS({
|
|
104
|
+
credentials: ecr.config.credentials,
|
|
105
|
+
region: region
|
|
106
|
+
}).getCallerIdentity().promise();
|
|
107
|
+
case 34:
|
|
108
|
+
_yield$AWS$STS$getCal = _context.sent;
|
|
109
|
+
Account = _yield$AWS$STS$getCal.Account;
|
|
110
|
+
if (Account) {
|
|
111
|
+
_context.next = 39;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
log.error("failed to determine AWS account");
|
|
115
|
+
return _context.abrupt("return", false);
|
|
116
|
+
case 39:
|
|
117
|
+
_context.next = 41;
|
|
118
|
+
return _inquirer["default"].prompt([{
|
|
119
|
+
name: 'update',
|
|
120
|
+
message: 'Do you want to add/update the policy?',
|
|
121
|
+
type: 'confirm',
|
|
122
|
+
"default": false
|
|
123
|
+
}]);
|
|
124
|
+
case 41:
|
|
125
|
+
_yield$inquirer$promp = _context.sent;
|
|
126
|
+
update = _yield$inquirer$promp.update;
|
|
127
|
+
if (update) {
|
|
128
|
+
_context.next = 45;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
return _context.abrupt("return", false);
|
|
132
|
+
case 45:
|
|
133
|
+
srcEcr = new _awsSdk["default"].ECR(_objectSpread(_objectSpread({}, repoAccountAwsConfig), {}, {
|
|
134
|
+
region: region
|
|
135
|
+
}));
|
|
136
|
+
_context.next = 48;
|
|
137
|
+
return srcEcr.getRepositoryPolicy({
|
|
138
|
+
registryId: registryId,
|
|
139
|
+
repositoryName: repositoryName
|
|
140
|
+
}).promise()["catch"](function (error) {
|
|
141
|
+
if (error.name === 'RepositoryPolicyNotFoundException') return {};
|
|
142
|
+
throw error;
|
|
143
|
+
});
|
|
144
|
+
case 48:
|
|
145
|
+
_yield$srcEcr$getRepo = _context.sent;
|
|
146
|
+
policyText = _yield$srcEcr$getRepo.policyText;
|
|
147
|
+
policy = JSON.parse(policyText || '{}');
|
|
148
|
+
_context.next = 53;
|
|
149
|
+
return srcEcr.setRepositoryPolicy({
|
|
150
|
+
repositoryName: repositoryName,
|
|
151
|
+
policyText: JSON.stringify(_objectSpread(_objectSpread({
|
|
152
|
+
Version: '2012-10-17'
|
|
153
|
+
}, policy), {}, {
|
|
154
|
+
Statement: [].concat((0, _toConsumableArray2["default"])(policy.Statement || []), [{
|
|
155
|
+
Effect: 'Allow',
|
|
156
|
+
Principal: {
|
|
157
|
+
AWS: [Account]
|
|
158
|
+
},
|
|
159
|
+
Action: Action
|
|
160
|
+
}])
|
|
161
|
+
}), null, 2)
|
|
162
|
+
}).promise();
|
|
163
|
+
case 53:
|
|
164
|
+
log.info("updated policy on ECR repository ".concat((0, _formatECRRepositoryHostname["default"])({
|
|
165
|
+
registryId: registryId,
|
|
166
|
+
region: region,
|
|
167
|
+
repositoryName: repositoryName
|
|
168
|
+
})));
|
|
169
|
+
return _context.abrupt("return", true);
|
|
170
|
+
case 55:
|
|
171
|
+
return _context.abrupt("return", false);
|
|
172
|
+
case 56:
|
|
173
|
+
case "end":
|
|
174
|
+
return _context.stop();
|
|
175
|
+
}
|
|
176
|
+
}, _callee, null, [[4, 24]]);
|
|
177
|
+
}));
|
|
178
|
+
return _checkECRImageAccess.apply(this, arguments);
|
|
179
|
+
}
|
|
180
|
+
module.exports = exports.default;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import AWS from 'aws-sdk';
|
|
2
|
+
import parseECRImageUri from "./parseECRImageUri.mjs";
|
|
3
|
+
import { ImageManifestSchema } from "./ImageManifestSchema.mjs";
|
|
4
|
+
import isInteractive from 'is-interactive';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import formatECRRepositoryHostname from "./formatECRRepositoryHostname.mjs";
|
|
7
|
+
export default async function checkECRImageAccess({
|
|
8
|
+
ecr,
|
|
9
|
+
awsConfig,
|
|
10
|
+
repoAccountAwsConfig,
|
|
11
|
+
imageUri,
|
|
12
|
+
log = console
|
|
13
|
+
}) {
|
|
14
|
+
log.error('checking access to ECR image:', imageUri, '...');
|
|
15
|
+
const {
|
|
16
|
+
registryId,
|
|
17
|
+
region,
|
|
18
|
+
repositoryName,
|
|
19
|
+
imageTag
|
|
20
|
+
} = parseECRImageUri(imageUri);
|
|
21
|
+
if (!ecr) ecr = new AWS.ECR({
|
|
22
|
+
...awsConfig,
|
|
23
|
+
region
|
|
24
|
+
});
|
|
25
|
+
try {
|
|
26
|
+
const {
|
|
27
|
+
images: [image] = []
|
|
28
|
+
} = await ecr.batchGetImage({
|
|
29
|
+
registryId,
|
|
30
|
+
repositoryName,
|
|
31
|
+
imageIds: [{
|
|
32
|
+
imageTag
|
|
33
|
+
}]
|
|
34
|
+
}).promise();
|
|
35
|
+
const imageManifest = image === null || image === void 0 ? void 0 : image.imageManifest;
|
|
36
|
+
if (!imageManifest) {
|
|
37
|
+
throw new Error(`imageManifest not found for: ${imageUri}`);
|
|
38
|
+
}
|
|
39
|
+
const {
|
|
40
|
+
config,
|
|
41
|
+
layers
|
|
42
|
+
} = ImageManifestSchema.parse(JSON.parse(imageManifest));
|
|
43
|
+
await ecr.batchCheckLayerAvailability({
|
|
44
|
+
registryId,
|
|
45
|
+
repositoryName,
|
|
46
|
+
layerDigests: [config.digest, ...layers.map(l => l.digest)]
|
|
47
|
+
}).promise();
|
|
48
|
+
await ecr.getDownloadUrlForLayer({
|
|
49
|
+
registryId,
|
|
50
|
+
repositoryName,
|
|
51
|
+
layerDigest: layers[0].digest
|
|
52
|
+
}).promise();
|
|
53
|
+
log.error(`ECR image is accessible: ${imageUri}`);
|
|
54
|
+
return true;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (!(error instanceof Error) || error.name !== 'AccessDeniedException') {
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
log.error(`Unable to access ECR image: ${imageUri}`);
|
|
61
|
+
const Action = ['ecr:GetDownloadUrlForLayer', 'ecr:BatchCheckLayerAvailability', 'ecr:BatchGetImage'];
|
|
62
|
+
log.error(`You may need to add a policy to the ECR repository to allow this account.
|
|
63
|
+
|
|
64
|
+
The policy should include:
|
|
65
|
+
|
|
66
|
+
${JSON.stringify({
|
|
67
|
+
Version: '2012-10-17',
|
|
68
|
+
Statement: [{
|
|
69
|
+
Effect: 'Allow',
|
|
70
|
+
Principal: {
|
|
71
|
+
AWS: ['XXXXXXXXXXXX']
|
|
72
|
+
},
|
|
73
|
+
Action
|
|
74
|
+
}]
|
|
75
|
+
}, null, 2).replace(/\n/gm, '\n ')}
|
|
76
|
+
`);
|
|
77
|
+
if (repoAccountAwsConfig && isInteractive()) {
|
|
78
|
+
const {
|
|
79
|
+
Account
|
|
80
|
+
} = await new AWS.STS({
|
|
81
|
+
credentials: ecr.config.credentials,
|
|
82
|
+
region
|
|
83
|
+
}).getCallerIdentity().promise();
|
|
84
|
+
if (!Account) {
|
|
85
|
+
log.error(`failed to determine AWS account`);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const {
|
|
89
|
+
update
|
|
90
|
+
} = await inquirer.prompt([{
|
|
91
|
+
name: 'update',
|
|
92
|
+
message: 'Do you want to add/update the policy?',
|
|
93
|
+
type: 'confirm',
|
|
94
|
+
default: false
|
|
95
|
+
}]);
|
|
96
|
+
if (!update) return false;
|
|
97
|
+
const srcEcr = new AWS.ECR({
|
|
98
|
+
...repoAccountAwsConfig,
|
|
99
|
+
region
|
|
100
|
+
});
|
|
101
|
+
const {
|
|
102
|
+
policyText
|
|
103
|
+
} = await srcEcr.getRepositoryPolicy({
|
|
104
|
+
registryId,
|
|
105
|
+
repositoryName
|
|
106
|
+
}).promise().catch(error => {
|
|
107
|
+
if (error.name === 'RepositoryPolicyNotFoundException') return {};
|
|
108
|
+
throw error;
|
|
109
|
+
});
|
|
110
|
+
const policy = JSON.parse(policyText || '{}');
|
|
111
|
+
await srcEcr.setRepositoryPolicy({
|
|
112
|
+
repositoryName,
|
|
113
|
+
policyText: JSON.stringify({
|
|
114
|
+
Version: '2012-10-17',
|
|
115
|
+
...policy,
|
|
116
|
+
Statement: [...(policy.Statement || []), {
|
|
117
|
+
Effect: 'Allow',
|
|
118
|
+
Principal: {
|
|
119
|
+
AWS: [Account]
|
|
120
|
+
},
|
|
121
|
+
Action
|
|
122
|
+
}]
|
|
123
|
+
}, null, 2)
|
|
124
|
+
}).promise();
|
|
125
|
+
log.info(`updated policy on ECR repository ${formatECRRepositoryHostname({
|
|
126
|
+
registryId,
|
|
127
|
+
region,
|
|
128
|
+
repositoryName
|
|
129
|
+
})}`);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import AWS from 'aws-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Checks if the given ECR repository has a sufficient repository
|
|
4
|
+
* policy to allow the given AWS principal to access docker images.
|
|
5
|
+
*
|
|
6
|
+
* If not, prints a warning, and if the terminal is interactive, asks the user if they
|
|
7
|
+
* would like to add/update the repository policy.
|
|
8
|
+
*/
|
|
9
|
+
export default function checkECRRepositoryPolicy({ ecr, awsConfig, repositoryName, awsPrincipal, Action, log, }: {
|
|
10
|
+
ecr?: AWS.ECR;
|
|
11
|
+
awsConfig?: AWS.ConfigurationOptions;
|
|
12
|
+
repositoryName: string;
|
|
13
|
+
awsPrincipal: string;
|
|
14
|
+
Action?: string[];
|
|
15
|
+
log?: {
|
|
16
|
+
info: (...args: any[]) => void;
|
|
17
|
+
warn: (...args: any[]) => void;
|
|
18
|
+
error: (...args: any[]) => void;
|
|
19
|
+
};
|
|
20
|
+
}): Promise<boolean>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports["default"] = checkECRRepositoryPolicy;
|
|
8
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
9
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
10
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
11
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
12
|
+
var _awsSdk = _interopRequireDefault(require("aws-sdk"));
|
|
13
|
+
var _inquirer = _interopRequireDefault(require("inquirer"));
|
|
14
|
+
var _isInteractive = _interopRequireDefault(require("is-interactive"));
|
|
15
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
16
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
17
|
+
/**
|
|
18
|
+
* Checks if the given ECR repository has a sufficient repository
|
|
19
|
+
* policy to allow the given AWS principal to access docker images.
|
|
20
|
+
*
|
|
21
|
+
* If not, prints a warning, and if the terminal is interactive, asks the user if they
|
|
22
|
+
* would like to add/update the repository policy.
|
|
23
|
+
*/
|
|
24
|
+
function checkECRRepositoryPolicy(_x) {
|
|
25
|
+
return _checkECRRepositoryPolicy.apply(this, arguments);
|
|
26
|
+
}
|
|
27
|
+
function _checkECRRepositoryPolicy() {
|
|
28
|
+
_checkECRRepositoryPolicy = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(_ref) {
|
|
29
|
+
var _policy$Statement, _statementForAction$P;
|
|
30
|
+
var ecr, awsConfig, repositoryName, awsPrincipal, _ref$Action, Action, _ref$log, log, rootUserMatch, principalAliases, _yield$ecr$getReposit, policyText, policy, statementForAction, statementPrincipal, _yield$inquirer$promp, update, _statementForAction$A, finalPolicy;
|
|
31
|
+
return _regenerator["default"].wrap(function _callee$(_context) {
|
|
32
|
+
while (1) switch (_context.prev = _context.next) {
|
|
33
|
+
case 0:
|
|
34
|
+
ecr = _ref.ecr, awsConfig = _ref.awsConfig, repositoryName = _ref.repositoryName, awsPrincipal = _ref.awsPrincipal, _ref$Action = _ref.Action, Action = _ref$Action === void 0 ? ['ecr:GetDownloadUrlForLayer', 'ecr:BatchCheckLayerAvailability', 'ecr:BatchGetImage'] : _ref$Action, _ref$log = _ref.log, log = _ref$log === void 0 ? console : _ref$log;
|
|
35
|
+
rootUserMatch = /^arn:aws:iam::(\d+):root$/.exec(awsPrincipal);
|
|
36
|
+
if (rootUserMatch) awsPrincipal = rootUserMatch[1];
|
|
37
|
+
principalAliases = /^(\d+)$/.test(awsPrincipal) ? [awsPrincipal, "arn:aws:iam::".concat(awsPrincipal, ":root")] : [awsPrincipal];
|
|
38
|
+
if (!ecr) ecr = new _awsSdk["default"].ECR(awsConfig);
|
|
39
|
+
_context.next = 7;
|
|
40
|
+
return ecr.getRepositoryPolicy({
|
|
41
|
+
repositoryName: repositoryName
|
|
42
|
+
}).promise()["catch"](function (error) {
|
|
43
|
+
if (error.name === 'RepositoryPolicyNotFoundException') return {};
|
|
44
|
+
throw error;
|
|
45
|
+
});
|
|
46
|
+
case 7:
|
|
47
|
+
_yield$ecr$getReposit = _context.sent;
|
|
48
|
+
policyText = _yield$ecr$getReposit.policyText;
|
|
49
|
+
policy = JSON.parse(policyText || '{}');
|
|
50
|
+
statementForAction = (_policy$Statement = policy.Statement) === null || _policy$Statement === void 0 ? void 0 : _policy$Statement.find(function (s) {
|
|
51
|
+
return s.Effect === 'Allow' && Array.isArray(Action) && Action.every(function (a) {
|
|
52
|
+
var _s$Action;
|
|
53
|
+
return (_s$Action = s.Action) === null || _s$Action === void 0 ? void 0 : _s$Action.includes(a);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
statementPrincipal = statementForAction === null || statementForAction === void 0 || (_statementForAction$P = statementForAction.Principal) === null || _statementForAction$P === void 0 ? void 0 : _statementForAction$P.AWS;
|
|
57
|
+
if (!(statementPrincipal && typeof statementPrincipal === 'string' && principalAliases.includes(statementPrincipal) || Array.isArray(statementPrincipal) && statementPrincipal.some(function (s) {
|
|
58
|
+
return principalAliases.includes(s);
|
|
59
|
+
}))) {
|
|
60
|
+
_context.next = 15;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
// eslint-disable-next-line no-console
|
|
64
|
+
log.info("Found policy on ECR repository ".concat(repositoryName, " to allow access for AWS Principal ").concat(awsPrincipal, "."));
|
|
65
|
+
return _context.abrupt("return", true);
|
|
66
|
+
case 15:
|
|
67
|
+
// eslint-disable-next-line no-console
|
|
68
|
+
console.warn("Missing policy on ECR repository ".concat(repositoryName, " to allow access for AWS Principal ").concat(awsPrincipal, ".\n\nThe policy should include:\n\n ").concat(JSON.stringify({
|
|
69
|
+
Version: '2012-10-17',
|
|
70
|
+
Statement: [{
|
|
71
|
+
Effect: 'Allow',
|
|
72
|
+
Principal: {
|
|
73
|
+
AWS: [awsPrincipal]
|
|
74
|
+
},
|
|
75
|
+
Action: Action
|
|
76
|
+
}]
|
|
77
|
+
}, null, 2).replace(/\n/gm, '\n '), "\n"));
|
|
78
|
+
if (!(0, _isInteractive["default"])()) {
|
|
79
|
+
_context.next = 28;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
_context.next = 19;
|
|
83
|
+
return _inquirer["default"].prompt([{
|
|
84
|
+
name: 'update',
|
|
85
|
+
message: 'Do you want to add/update the policy?',
|
|
86
|
+
type: 'confirm',
|
|
87
|
+
"default": false
|
|
88
|
+
}]);
|
|
89
|
+
case 19:
|
|
90
|
+
_yield$inquirer$promp = _context.sent;
|
|
91
|
+
update = _yield$inquirer$promp.update;
|
|
92
|
+
if (!update) {
|
|
93
|
+
_context.next = 28;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
finalPolicy = policy;
|
|
97
|
+
if ((statementForAction === null || statementForAction === void 0 || (_statementForAction$A = statementForAction.Action) === null || _statementForAction$A === void 0 ? void 0 : _statementForAction$A.length) === Action.length) {
|
|
98
|
+
statementForAction.Principal = _objectSpread(_objectSpread({}, statementForAction.Principal), {}, {
|
|
99
|
+
AWS: [].concat((0, _toConsumableArray2["default"])(typeof statementPrincipal === 'string' ? [statementPrincipal] : Array.isArray(statementPrincipal) ? statementPrincipal : []), [awsPrincipal])
|
|
100
|
+
});
|
|
101
|
+
} else {
|
|
102
|
+
finalPolicy = _objectSpread(_objectSpread({
|
|
103
|
+
Version: '2012-10-17'
|
|
104
|
+
}, policy), {}, {
|
|
105
|
+
Statement: [].concat((0, _toConsumableArray2["default"])(policy.Statement || []), [{
|
|
106
|
+
Effect: 'Allow',
|
|
107
|
+
Principal: {
|
|
108
|
+
AWS: [awsPrincipal]
|
|
109
|
+
},
|
|
110
|
+
Action: Action
|
|
111
|
+
}])
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
_context.next = 26;
|
|
115
|
+
return ecr.setRepositoryPolicy({
|
|
116
|
+
repositoryName: repositoryName,
|
|
117
|
+
policyText: JSON.stringify(finalPolicy, null, 2)
|
|
118
|
+
}).promise();
|
|
119
|
+
case 26:
|
|
120
|
+
log.info("updated policy on ECR repository ".concat(repositoryName));
|
|
121
|
+
return _context.abrupt("return", true);
|
|
122
|
+
case 28:
|
|
123
|
+
return _context.abrupt("return", false);
|
|
124
|
+
case 29:
|
|
125
|
+
case "end":
|
|
126
|
+
return _context.stop();
|
|
127
|
+
}
|
|
128
|
+
}, _callee);
|
|
129
|
+
}));
|
|
130
|
+
return _checkECRRepositoryPolicy.apply(this, arguments);
|
|
131
|
+
}
|
|
132
|
+
module.exports = exports.default;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import AWS from 'aws-sdk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import isInteractive from 'is-interactive';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Checks if the given ECR repository has a sufficient repository
|
|
7
|
+
* policy to allow the given AWS principal to access docker images.
|
|
8
|
+
*
|
|
9
|
+
* If not, prints a warning, and if the terminal is interactive, asks the user if they
|
|
10
|
+
* would like to add/update the repository policy.
|
|
11
|
+
*/
|
|
12
|
+
export default async function checkECRRepositoryPolicy({
|
|
13
|
+
ecr,
|
|
14
|
+
awsConfig,
|
|
15
|
+
repositoryName,
|
|
16
|
+
awsPrincipal,
|
|
17
|
+
Action = ['ecr:GetDownloadUrlForLayer', 'ecr:BatchCheckLayerAvailability', 'ecr:BatchGetImage'],
|
|
18
|
+
log = console
|
|
19
|
+
}) {
|
|
20
|
+
var _policy$Statement, _statementForAction$P;
|
|
21
|
+
const rootUserMatch = /^arn:aws:iam::(\d+):root$/.exec(awsPrincipal);
|
|
22
|
+
if (rootUserMatch) awsPrincipal = rootUserMatch[1];
|
|
23
|
+
const principalAliases = /^(\d+)$/.test(awsPrincipal) ? [awsPrincipal, `arn:aws:iam::${awsPrincipal}:root`] : [awsPrincipal];
|
|
24
|
+
if (!ecr) ecr = new AWS.ECR(awsConfig);
|
|
25
|
+
const {
|
|
26
|
+
policyText
|
|
27
|
+
} = await ecr.getRepositoryPolicy({
|
|
28
|
+
repositoryName
|
|
29
|
+
}).promise().catch(error => {
|
|
30
|
+
if (error.name === 'RepositoryPolicyNotFoundException') return {};
|
|
31
|
+
throw error;
|
|
32
|
+
});
|
|
33
|
+
const policy = JSON.parse(policyText || '{}');
|
|
34
|
+
const statementForAction = (_policy$Statement = policy.Statement) === null || _policy$Statement === void 0 ? void 0 : _policy$Statement.find(s => s.Effect === 'Allow' && Array.isArray(Action) && Action.every(a => {
|
|
35
|
+
var _s$Action;
|
|
36
|
+
return (_s$Action = s.Action) === null || _s$Action === void 0 ? void 0 : _s$Action.includes(a);
|
|
37
|
+
}));
|
|
38
|
+
const statementPrincipal = statementForAction === null || statementForAction === void 0 || (_statementForAction$P = statementForAction.Principal) === null || _statementForAction$P === void 0 ? void 0 : _statementForAction$P.AWS;
|
|
39
|
+
if (statementPrincipal && typeof statementPrincipal === 'string' && principalAliases.includes(statementPrincipal) || Array.isArray(statementPrincipal) && statementPrincipal.some(s => principalAliases.includes(s))) {
|
|
40
|
+
// eslint-disable-next-line no-console
|
|
41
|
+
log.info(`Found policy on ECR repository ${repositoryName} to allow access for AWS Principal ${awsPrincipal}.`);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// eslint-disable-next-line no-console
|
|
46
|
+
console.warn(`Missing policy on ECR repository ${repositoryName} to allow access for AWS Principal ${awsPrincipal}.
|
|
47
|
+
|
|
48
|
+
The policy should include:
|
|
49
|
+
|
|
50
|
+
${JSON.stringify({
|
|
51
|
+
Version: '2012-10-17',
|
|
52
|
+
Statement: [{
|
|
53
|
+
Effect: 'Allow',
|
|
54
|
+
Principal: {
|
|
55
|
+
AWS: [awsPrincipal]
|
|
56
|
+
},
|
|
57
|
+
Action
|
|
58
|
+
}]
|
|
59
|
+
}, null, 2).replace(/\n/gm, '\n ')}
|
|
60
|
+
`);
|
|
61
|
+
if (isInteractive()) {
|
|
62
|
+
const {
|
|
63
|
+
update
|
|
64
|
+
} = await inquirer.prompt([{
|
|
65
|
+
name: 'update',
|
|
66
|
+
message: 'Do you want to add/update the policy?',
|
|
67
|
+
type: 'confirm',
|
|
68
|
+
default: false
|
|
69
|
+
}]);
|
|
70
|
+
if (update) {
|
|
71
|
+
var _statementForAction$A;
|
|
72
|
+
let finalPolicy = policy;
|
|
73
|
+
if ((statementForAction === null || statementForAction === void 0 || (_statementForAction$A = statementForAction.Action) === null || _statementForAction$A === void 0 ? void 0 : _statementForAction$A.length) === Action.length) {
|
|
74
|
+
statementForAction.Principal = {
|
|
75
|
+
...statementForAction.Principal,
|
|
76
|
+
AWS: [...(typeof statementPrincipal === 'string' ? [statementPrincipal] : Array.isArray(statementPrincipal) ? statementPrincipal : []), awsPrincipal]
|
|
77
|
+
};
|
|
78
|
+
} else {
|
|
79
|
+
finalPolicy = {
|
|
80
|
+
Version: '2012-10-17',
|
|
81
|
+
...policy,
|
|
82
|
+
Statement: [...(policy.Statement || []), {
|
|
83
|
+
Effect: 'Allow',
|
|
84
|
+
Principal: {
|
|
85
|
+
AWS: [awsPrincipal]
|
|
86
|
+
},
|
|
87
|
+
Action
|
|
88
|
+
}]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
await ecr.setRepositoryPolicy({
|
|
92
|
+
repositoryName,
|
|
93
|
+
policyText: JSON.stringify(finalPolicy, null, 2)
|
|
94
|
+
}).promise();
|
|
95
|
+
log.info(`updated policy on ECR repository ${repositoryName}`);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|