@occam-scaly/scaly-cli 0.2.5 → 0.2.7
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/bin/scaly.js +2 -2
- package/lib/scaly-api.js +31 -0
- package/lib/scaly-artifacts.js +21 -0
- package/lib/scaly-plan.js +70 -0
- package/package.json +1 -1
package/bin/scaly.js
CHANGED
|
@@ -2992,9 +2992,9 @@ async function runDiagnoseApp(rest) {
|
|
|
2992
2992
|
}
|
|
2993
2993
|
if (/User pool .* does not exist/i.test(text)) {
|
|
2994
2994
|
findings.push({
|
|
2995
|
-
code: '
|
|
2995
|
+
code: 'USER_GROUPS_POOL_MISSING',
|
|
2996
2996
|
severity: 'medium',
|
|
2997
|
-
hint:
|
|
2997
|
+
hint: "User Groups is misconfigured for this account; contact support to repair the account's identity resources."
|
|
2998
2998
|
});
|
|
2999
2999
|
}
|
|
3000
3000
|
if (/ResourceAlreadyExistsException/i.test(text)) {
|
package/lib/scaly-api.js
CHANGED
|
@@ -254,6 +254,27 @@ const QUERIES = {
|
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
`,
|
|
257
|
+
listAddOnsByType: `
|
|
258
|
+
query ListAddOnsByType($accountId: String!, $type: AddOnTypeEnum!, $take: Int) {
|
|
259
|
+
listAddOns(
|
|
260
|
+
where: {
|
|
261
|
+
AND: [
|
|
262
|
+
{ accountId: { equals: $accountId } }
|
|
263
|
+
{ type: { equals: $type } }
|
|
264
|
+
{ isDeleted: { equals: false } }
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
take: $take
|
|
268
|
+
) {
|
|
269
|
+
id
|
|
270
|
+
name
|
|
271
|
+
type
|
|
272
|
+
accountId
|
|
273
|
+
status
|
|
274
|
+
addOnCognito { userPoolName }
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
`,
|
|
257
278
|
listStacks: `
|
|
258
279
|
query ListStacks($take: Int) {
|
|
259
280
|
listStacks(
|
|
@@ -351,6 +372,15 @@ async function findAddOnByName({ name, accountId }) {
|
|
|
351
372
|
return data?.listAddOns || [];
|
|
352
373
|
}
|
|
353
374
|
|
|
375
|
+
async function listAddOnsByType({ accountId, type, take = 10 }) {
|
|
376
|
+
const data = await graphqlRequest(QUERIES.listAddOnsByType, {
|
|
377
|
+
accountId,
|
|
378
|
+
type,
|
|
379
|
+
take
|
|
380
|
+
});
|
|
381
|
+
return data?.listAddOns || [];
|
|
382
|
+
}
|
|
383
|
+
|
|
354
384
|
async function createStack({ accountId, name, size, minIdle }) {
|
|
355
385
|
const data = await graphqlRequest(MUTATIONS.createStack, {
|
|
356
386
|
data: {
|
|
@@ -419,6 +449,7 @@ module.exports = {
|
|
|
419
449
|
findStackByName,
|
|
420
450
|
findAppByName,
|
|
421
451
|
findAddOnByName,
|
|
452
|
+
listAddOnsByType,
|
|
422
453
|
listStacks,
|
|
423
454
|
listApps,
|
|
424
455
|
createStack,
|
package/lib/scaly-artifacts.js
CHANGED
|
@@ -34,6 +34,27 @@ const SENSITIVE_PATH_PATTERNS = [
|
|
|
34
34
|
// ssh / aws creds
|
|
35
35
|
/(^|\/)\.ssh(\/|$)/i,
|
|
36
36
|
/(^|\/)\.aws(\/|$)/i,
|
|
37
|
+
// common package registry credentials
|
|
38
|
+
/(^|\/)\.npmrc$/i,
|
|
39
|
+
/(^|\/)\.pypirc$/i,
|
|
40
|
+
// docker registry auth
|
|
41
|
+
/(^|\/)\.docker\/config\.json$/i,
|
|
42
|
+
// kubernetes creds
|
|
43
|
+
/(^|\/)\.kube(\/|$)/i,
|
|
44
|
+
/(^|\/)(kubeconfig|.*\.kubeconfig)$/i,
|
|
45
|
+
// terraform state (often contains secrets)
|
|
46
|
+
/(^|\/).*\.tfstate(\.backup)?$/i,
|
|
47
|
+
/(^|\/)terraform\.tfstate(\.backup)?$/i,
|
|
48
|
+
// GCP service account keys / generic credentials files
|
|
49
|
+
/(^|\/)credentials\.json$/i,
|
|
50
|
+
/(^|\/)service-account.*\.json$/i,
|
|
51
|
+
// keystores / pkcs
|
|
52
|
+
/\.p12$/i,
|
|
53
|
+
/\.pfx$/i,
|
|
54
|
+
/\.keystore$/i,
|
|
55
|
+
/\.jks$/i,
|
|
56
|
+
// generic "secrets" files (defense-in-depth; can be overridden)
|
|
57
|
+
/(^|\/).*secrets?.*\.(ya?ml|json)$/i,
|
|
37
58
|
// private keys
|
|
38
59
|
/\.pem$/i,
|
|
39
60
|
/\.key$/i,
|
package/lib/scaly-plan.js
CHANGED
|
@@ -122,6 +122,17 @@ async function buildPlan({ config, env, appName }) {
|
|
|
122
122
|
accountId = await resolveAccountId({ config });
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
const requestedAuthPools = new Set();
|
|
126
|
+
for (const a of filteredApps) {
|
|
127
|
+
const pool = a && a.auth && a.auth.userPool;
|
|
128
|
+
if (typeof pool === 'string' && pool.trim()) requestedAuthPools.add(pool);
|
|
129
|
+
}
|
|
130
|
+
if (requestedAuthPools.size) {
|
|
131
|
+
warnings.push(
|
|
132
|
+
"app.auth.userPool is deprecated and ignored. Scaly uses the account's User Groups automatically; configure only app.auth.groups."
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
125
136
|
let stackOpId = null;
|
|
126
137
|
if (!currentStack) {
|
|
127
138
|
stackOpId = 'op_stack_create';
|
|
@@ -258,6 +269,7 @@ async function buildPlan({ config, env, appName }) {
|
|
|
258
269
|
|
|
259
270
|
if (Array.isArray(config.addons) && config.addons.length) {
|
|
260
271
|
let addOnReadable = true;
|
|
272
|
+
let existingUserPools = null;
|
|
261
273
|
for (const addOn of config.addons) {
|
|
262
274
|
if (!addOn || typeof addOn !== 'object') continue;
|
|
263
275
|
const apiType = mapAddOnType(addOn.type);
|
|
@@ -274,6 +286,30 @@ async function buildPlan({ config, env, appName }) {
|
|
|
274
286
|
|
|
275
287
|
let currentAddOn = null;
|
|
276
288
|
try {
|
|
289
|
+
if (apiType === 'COGNITO') {
|
|
290
|
+
try {
|
|
291
|
+
existingUserPools =
|
|
292
|
+
existingUserPools ||
|
|
293
|
+
(await api.listAddOnsByType({
|
|
294
|
+
accountId,
|
|
295
|
+
type: 'COGNITO',
|
|
296
|
+
take: 10
|
|
297
|
+
}));
|
|
298
|
+
} catch {
|
|
299
|
+
existingUserPools = null;
|
|
300
|
+
}
|
|
301
|
+
if (
|
|
302
|
+
Array.isArray(existingUserPools) &&
|
|
303
|
+
existingUserPools.length > 1
|
|
304
|
+
) {
|
|
305
|
+
const e = new Error(
|
|
306
|
+
`Multiple User Groups pools exist in this account (${existingUserPools.length}). Scaly supports exactly one user pool per account; contact support to repair this account before continuing.`
|
|
307
|
+
);
|
|
308
|
+
e.code = 'USER_GROUPS_POOL_DUPLICATE';
|
|
309
|
+
throw e;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
277
313
|
const list = await api.findAddOnByName({ name: addOn.name, accountId });
|
|
278
314
|
currentAddOn = list?.[0] || null;
|
|
279
315
|
if (list.length > 1) {
|
|
@@ -291,6 +327,40 @@ async function buildPlan({ config, env, appName }) {
|
|
|
291
327
|
}
|
|
292
328
|
|
|
293
329
|
if (!currentAddOn) {
|
|
330
|
+
if (apiType === 'COGNITO') {
|
|
331
|
+
const existing =
|
|
332
|
+
Array.isArray(existingUserPools) && existingUserPools.length
|
|
333
|
+
? existingUserPools.find((p) => p && p.name === 'users') ||
|
|
334
|
+
existingUserPools[0]
|
|
335
|
+
: null;
|
|
336
|
+
|
|
337
|
+
if (existing) {
|
|
338
|
+
warnings.push(
|
|
339
|
+
`User Groups is provisioned automatically per account. Ignoring configured user_groups add-on '${addOn.name}' and using existing '${existing.name}'.`
|
|
340
|
+
);
|
|
341
|
+
ops.push({
|
|
342
|
+
id: `op_addon_noop_${existing.name}`,
|
|
343
|
+
kind: 'addon',
|
|
344
|
+
action: 'noop',
|
|
345
|
+
resource: {
|
|
346
|
+
type: 'addon',
|
|
347
|
+
name: existing.name,
|
|
348
|
+
id: existing.id
|
|
349
|
+
},
|
|
350
|
+
current: pick(existing, ['id', 'name', 'type', 'accountId']),
|
|
351
|
+
desired: { name: existing.name, type: apiType, accountId },
|
|
352
|
+
diff: []
|
|
353
|
+
});
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const e = new Error(
|
|
358
|
+
'User Groups pool is missing for this account. Scaly provisions it automatically at account creation; contact support to repair this account.'
|
|
359
|
+
);
|
|
360
|
+
e.code = 'USER_GROUPS_POOL_MISSING';
|
|
361
|
+
throw e;
|
|
362
|
+
}
|
|
363
|
+
|
|
294
364
|
const desired = { name: addOn.name, type: apiType, accountId };
|
|
295
365
|
if (apiType === 'DATABASE') {
|
|
296
366
|
const dbType = mapDbEngine(addOn.engine);
|