@cloud-copilot/iam-shrink 0.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/LICENSE.txt +674 -0
- package/README.md +187 -0
- package/dist/cjs/cli.d.ts +2 -0
- package/dist/cjs/cli.d.ts.map +1 -0
- package/dist/cjs/cli.js +78 -0
- package/dist/cjs/cli.js.map +1 -0
- package/dist/cjs/cli_utils.d.ts +30 -0
- package/dist/cjs/cli_utils.d.ts.map +1 -0
- package/dist/cjs/cli_utils.js +75 -0
- package/dist/cjs/cli_utils.js.map +1 -0
- package/dist/cjs/errors.d.ts +13 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +56 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +8 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/shrink.d.ts +131 -0
- package/dist/cjs/shrink.d.ts.map +1 -0
- package/dist/cjs/shrink.js +358 -0
- package/dist/cjs/shrink.js.map +1 -0
- package/dist/cjs/shrink_file.d.ts +12 -0
- package/dist/cjs/shrink_file.d.ts.map +1 -0
- package/dist/cjs/shrink_file.js +38 -0
- package/dist/cjs/shrink_file.js.map +1 -0
- package/dist/cjs/stdin.d.ts +7 -0
- package/dist/cjs/stdin.d.ts.map +1 -0
- package/dist/cjs/stdin.js +34 -0
- package/dist/cjs/stdin.js.map +1 -0
- package/dist/cjs/validate.d.ts +11 -0
- package/dist/cjs/validate.d.ts.map +1 -0
- package/dist/cjs/validate.js +30 -0
- package/dist/cjs/validate.js.map +1 -0
- package/dist/esm/cli.d.ts +2 -0
- package/dist/esm/cli.d.ts.map +1 -0
- package/dist/esm/cli.js +76 -0
- package/dist/esm/cli.js.map +1 -0
- package/dist/esm/cli_utils.d.ts +30 -0
- package/dist/esm/cli_utils.d.ts.map +1 -0
- package/dist/esm/cli_utils.js +69 -0
- package/dist/esm/cli_utils.js.map +1 -0
- package/dist/esm/errors.d.ts +13 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +50 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/shrink.d.ts +131 -0
- package/dist/esm/shrink.d.ts.map +1 -0
- package/dist/esm/shrink.js +343 -0
- package/dist/esm/shrink.js.map +1 -0
- package/dist/esm/shrink_file.d.ts +12 -0
- package/dist/esm/shrink_file.d.ts.map +1 -0
- package/dist/esm/shrink_file.js +35 -0
- package/dist/esm/shrink_file.js.map +1 -0
- package/dist/esm/stdin.d.ts +7 -0
- package/dist/esm/stdin.d.ts.map +1 -0
- package/dist/esm/stdin.js +31 -0
- package/dist/esm/stdin.js.map +1 -0
- package/dist/esm/validate.d.ts +11 -0
- package/dist/esm/validate.d.ts.map +1 -0
- package/dist/esm/validate.js +27 -0
- package/dist/esm/validate.js.map +1 -0
- package/package.json +43 -0
- package/postbuild.sh +13 -0
- package/src/cli.ts +83 -0
- package/src/cli_utils.test.ts +117 -0
- package/src/cli_utils.ts +82 -0
- package/src/errors.ts +52 -0
- package/src/index.ts +3 -0
- package/src/shrink.test.ts +594 -0
- package/src/shrink.ts +385 -0
- package/src/shrink_file.test.ts +72 -0
- package/src/shrink_file.ts +38 -0
- package/src/stdin.ts +34 -0
- package/src/validate.test.ts +55 -0
- package/src/validate.ts +29 -0
- package/tsconfig.cjs.json +12 -0
- package/tsconfig.esm.json +15 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
import { expandIamActions } from '@cloud-copilot/iam-expand';
|
|
2
|
+
import { beforeEach } from 'node:test';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { consolidateWildcardPatterns, countSubstrings, findCommonSequences, groupActionsByService, mapActions, reduceAction, regexForWildcardAction, shrink, shrinkIteration, shrinkResolvedList, splitActionIntoParts, wildcardActionMatchesAnyString } from './shrink.js';
|
|
5
|
+
import { validateShrinkResults } from './validate.js';
|
|
6
|
+
|
|
7
|
+
vi.mock('@cloud-copilot/iam-expand')
|
|
8
|
+
vi.mock('./validate.js')
|
|
9
|
+
|
|
10
|
+
const mockExpandIamActions = vi.mocked(expandIamActions);
|
|
11
|
+
const mockValidateShrinkResults = vi.mocked(validateShrinkResults);
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.resetAllMocks()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
describe('shrink.ts', () => {
|
|
18
|
+
describe('splitActionIntoParts', () => {
|
|
19
|
+
it('should split by capital letters', () => {
|
|
20
|
+
//Given an action with capital letters
|
|
21
|
+
const input = "CreateAccessPointForObjectLambda";
|
|
22
|
+
|
|
23
|
+
//When we split the action into parts
|
|
24
|
+
const result = splitActionIntoParts(input);
|
|
25
|
+
|
|
26
|
+
//Then we should get an array of parts
|
|
27
|
+
expect(result).toEqual([
|
|
28
|
+
"Create",
|
|
29
|
+
"Access",
|
|
30
|
+
"Point",
|
|
31
|
+
"For",
|
|
32
|
+
"Object",
|
|
33
|
+
"Lambda"
|
|
34
|
+
]);
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should split by asterisks', () => {
|
|
38
|
+
//Given an action with asterisks
|
|
39
|
+
const input = "*ObjectTagging*";
|
|
40
|
+
|
|
41
|
+
//When we split the action into parts
|
|
42
|
+
const result = splitActionIntoParts(input);
|
|
43
|
+
|
|
44
|
+
//Then we should get an array of parts
|
|
45
|
+
expect(result).toEqual([
|
|
46
|
+
"*",
|
|
47
|
+
"Object",
|
|
48
|
+
"Tagging",
|
|
49
|
+
"*"
|
|
50
|
+
]);
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should not split on consecutive capital letters', () => {
|
|
54
|
+
//Given an action with only capital letters
|
|
55
|
+
const input = "GET";
|
|
56
|
+
|
|
57
|
+
//When we split the action into parts
|
|
58
|
+
const result = splitActionIntoParts(input);
|
|
59
|
+
|
|
60
|
+
//Then we should back the original string
|
|
61
|
+
expect(result).toEqual([input]);
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe('consolidateWildcardPatterns', () => {
|
|
66
|
+
it('should consolidate wildcard patterns', () => {
|
|
67
|
+
//Given wildcard actions
|
|
68
|
+
const actions = ['*Object', 'Object*', '*Object*']
|
|
69
|
+
|
|
70
|
+
//When we consolidate the actions
|
|
71
|
+
const result = consolidateWildcardPatterns(actions);
|
|
72
|
+
|
|
73
|
+
//Then we should get an array of consolidated actions
|
|
74
|
+
expect(result).toEqual(['*Object*']);
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should not overconsolidate in the middle', () => {
|
|
78
|
+
//Given the wildcard actions
|
|
79
|
+
const actions = [
|
|
80
|
+
'Delete*Tagging',
|
|
81
|
+
'GetJobTagging',
|
|
82
|
+
'GetObjectTagging',
|
|
83
|
+
'GetObject*Tagging',
|
|
84
|
+
'GetStorage*Tagging',
|
|
85
|
+
'Put*Tagging'
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
//When we consolidate the actions
|
|
89
|
+
const result = consolidateWildcardPatterns(actions);
|
|
90
|
+
|
|
91
|
+
//Then we should get an array of consolidated actions
|
|
92
|
+
expect(result.sort()).toEqual([
|
|
93
|
+
'Delete*Tagging',
|
|
94
|
+
'GetJobTagging',
|
|
95
|
+
'GetObject*Tagging',
|
|
96
|
+
'GetStorage*Tagging',
|
|
97
|
+
'Put*Tagging'
|
|
98
|
+
].sort()
|
|
99
|
+
);
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('countSubstrings', () => {
|
|
104
|
+
it('will count strings that appear in the substrings', () => {
|
|
105
|
+
//Given a set of substrings
|
|
106
|
+
const substrings = [
|
|
107
|
+
"Get",
|
|
108
|
+
"Object",
|
|
109
|
+
"Tagging",
|
|
110
|
+
"Put"
|
|
111
|
+
]
|
|
112
|
+
const actionStrings = [
|
|
113
|
+
"GetObjectTagging",
|
|
114
|
+
"PutObjectTagging"
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
//When we count the substrings
|
|
119
|
+
const result = countSubstrings(substrings, actionStrings);
|
|
120
|
+
|
|
121
|
+
//Then we should get the count of substrings
|
|
122
|
+
expect(result.size).toEqual(4);
|
|
123
|
+
expect(result.get("Get")).toBe(1);
|
|
124
|
+
expect(result.get("Object")).toBe(2);
|
|
125
|
+
expect(result.get("Tagging")).toBe(2);
|
|
126
|
+
expect(result.get("Put")).toBe(1);
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('does not count substrings that are not in the action strings', () => {
|
|
130
|
+
//Given a set of substrings
|
|
131
|
+
const substrings = [
|
|
132
|
+
"Silliness",
|
|
133
|
+
"Get",
|
|
134
|
+
]
|
|
135
|
+
const actionStrings = [
|
|
136
|
+
"GetObjectTagging",
|
|
137
|
+
"PutObjectTagging"
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
//When we count the substrings
|
|
141
|
+
const result = countSubstrings(substrings, actionStrings);
|
|
142
|
+
|
|
143
|
+
//Then we should get the count of substrings
|
|
144
|
+
expect(result.size).toEqual(1);
|
|
145
|
+
expect(result.get("Get")).toBe(1);
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe('findCommonSequences', () => {
|
|
150
|
+
it('counts up the common sequences', () => {
|
|
151
|
+
//Given a set of actions
|
|
152
|
+
const actions = [
|
|
153
|
+
"GetObjectTagging",
|
|
154
|
+
"PutObjectTagging",
|
|
155
|
+
"GetBucketTagging",
|
|
156
|
+
"GetObjectVersionAcl",
|
|
157
|
+
"Get*"
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
//When we find the common sequences
|
|
161
|
+
const result = findCommonSequences(actions);
|
|
162
|
+
|
|
163
|
+
//And sort them
|
|
164
|
+
result.sort((a, b) => {
|
|
165
|
+
return a.sequence.localeCompare(b.sequence);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
//Then we should get the common sequences
|
|
169
|
+
expect(result).toEqual([
|
|
170
|
+
{sequence: "*", frequency: 1, length: 1},
|
|
171
|
+
{sequence: "Acl", frequency: 1, length: 3},
|
|
172
|
+
{sequence: "Bucket", frequency: 1, length: 6},
|
|
173
|
+
{sequence: "Get", frequency: 4, length: 3},
|
|
174
|
+
{sequence: "Object", frequency: 3, length: 6},
|
|
175
|
+
{sequence: "Put", frequency: 1, length: 3},
|
|
176
|
+
{sequence: "Tagging", frequency: 3, length: 7},
|
|
177
|
+
{sequence: "Version", frequency: 1, length: 7},
|
|
178
|
+
])
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
describe('regexForWildcardAction', () => {
|
|
183
|
+
it('should create a regex for a wildcard action', () => {
|
|
184
|
+
//Given a wildcard action
|
|
185
|
+
const action = "*ObjectTagging*";
|
|
186
|
+
|
|
187
|
+
//When we create a regex for the action
|
|
188
|
+
const result = regexForWildcardAction(action);
|
|
189
|
+
|
|
190
|
+
//Then we should get a regex that matches the action
|
|
191
|
+
expect(result.source).toBe("^.*?ObjectTagging.*?$");
|
|
192
|
+
expect(result.flags).toBe("i");
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('should collapse consecutive asterisks', () => {
|
|
196
|
+
//Given a wildcard action with consecutive asterisks
|
|
197
|
+
const action = "*Object****Tagging*";
|
|
198
|
+
|
|
199
|
+
//When we create a regex for the action
|
|
200
|
+
const result = regexForWildcardAction(action);
|
|
201
|
+
|
|
202
|
+
//Then we should get a regex that matches the action
|
|
203
|
+
expect(result.source).toBe("^.*?Object.*?Tagging.*?$");
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
describe('wildcardActionMatchesAnyString', () => {
|
|
208
|
+
it('should match a string that matches the wildcard action', () => {
|
|
209
|
+
//Given a wildcard action
|
|
210
|
+
const wildcardAction = "*ObjectTagging*";
|
|
211
|
+
//And a list of strings with one that matches the wildcard action
|
|
212
|
+
const strings = [
|
|
213
|
+
"GetObjectTagging",
|
|
214
|
+
"PutObjectTagging",
|
|
215
|
+
"GetObjectVersionAcl"
|
|
216
|
+
]
|
|
217
|
+
|
|
218
|
+
//When we match the strings against the action
|
|
219
|
+
const result = wildcardActionMatchesAnyString(wildcardAction, strings);
|
|
220
|
+
|
|
221
|
+
//Then we should get a match
|
|
222
|
+
expect(result).toBe(true);
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('should return false if there are no matches', () => {
|
|
226
|
+
//Given a wildcard action
|
|
227
|
+
const wildcardAction = "*ObjectTagging*";
|
|
228
|
+
//And a list of strings with none that match the wildcard action
|
|
229
|
+
const strings = [
|
|
230
|
+
"GetObjectVersionAcl",
|
|
231
|
+
"PutBucketTagging",
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
//When we match the strings against the action
|
|
235
|
+
const result = wildcardActionMatchesAnyString(wildcardAction, strings);
|
|
236
|
+
|
|
237
|
+
//Then we should not get a match
|
|
238
|
+
expect(result).toBe(false);
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('should return false if for an empty list of strings', () => {
|
|
242
|
+
//Given a wildcard action
|
|
243
|
+
const wildcardAction = "*ObjectTagging*";
|
|
244
|
+
//And an empty list of strings
|
|
245
|
+
const strings: string[] = []
|
|
246
|
+
|
|
247
|
+
//When we match the strings against the action
|
|
248
|
+
const result = wildcardActionMatchesAnyString(wildcardAction, strings);
|
|
249
|
+
|
|
250
|
+
//Then the result should be false
|
|
251
|
+
expect(result).toBe(false);
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
const reduceActionCases: {action: string, sequence: string, undesiredActions: string[], expected: string, name?: string}[] = [
|
|
256
|
+
//Sequence at the beginning
|
|
257
|
+
{action: "GetObjectTagging", sequence: "Get", undesiredActions: ["GetObjectAcl"], expected: "Get*Tagging"},
|
|
258
|
+
{action: "GetObjectTagging", sequence: "Get", undesiredActions: ["PutObjectTagging"], expected: "Get*"},
|
|
259
|
+
|
|
260
|
+
//Sequence in the middle, remove beginning
|
|
261
|
+
{action: "GetObjectTagging", sequence: "Object", undesiredActions: ["PutObjectVersion", "GetObjectVersion"], expected: "*ObjectTagging"},
|
|
262
|
+
{action: "GetIntelligentTieringConfiguration", sequence: "Tiering", undesiredActions: ["GetIntelligentTieringStructure"], expected: "*TieringConfiguration"},
|
|
263
|
+
|
|
264
|
+
//Sequence in the middle, remove end
|
|
265
|
+
{action: "GetObjectTagging", sequence: "Object", undesiredActions: ["PutObjectTagging"], expected: "GetObject*"},
|
|
266
|
+
{action: "GetObjectTaggingVersion", sequence: "Object", undesiredActions: ["PutObjectTagging"], expected: "GetObject*"},
|
|
267
|
+
|
|
268
|
+
//Sequence in the middle, remove beginning and end
|
|
269
|
+
{action: "GetObjectTagging", sequence: "Object", undesiredActions: ["ListBucketVersions"], expected: "*Object*"},
|
|
270
|
+
|
|
271
|
+
//Sequence at the end
|
|
272
|
+
{action: "GetObjectTagging", sequence: "Tagging", undesiredActions: ["PutObjectTagging"], expected: "Get*Tagging"},
|
|
273
|
+
{action: "GetObjectTagging", sequence: "Tagging", undesiredActions: ["PutObjectTagging"], expected: "Get*Tagging"},
|
|
274
|
+
{action: "GetObjectTagging", sequence: "Tagging", undesiredActions: ["GetObjectAcl"], expected: "*Tagging"},
|
|
275
|
+
|
|
276
|
+
//Action only Has One Part
|
|
277
|
+
{action: "GET", sequence: "GET", undesiredActions: ["PUT"], expected: "GET"},
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
describe('reduceAction', () => {
|
|
281
|
+
for(const {action, sequence, undesiredActions, expected, name} of reduceActionCases) {
|
|
282
|
+
it(name ?? `should reduce ${action} to ${expected}`, () => {
|
|
283
|
+
//When we reduce the action
|
|
284
|
+
const result = reduceAction(action, sequence, undesiredActions);
|
|
285
|
+
|
|
286
|
+
//Then we should get the reduced action
|
|
287
|
+
expect(result).toBe(expected);
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
it('should replace parts after the sequence if it occurs at the beginning', () => {
|
|
292
|
+
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
describe('shrinkIteration', () => {
|
|
297
|
+
it('should shrink the actions, shallow', () => {
|
|
298
|
+
//Given a list of actions
|
|
299
|
+
const actions = [
|
|
300
|
+
"GetObjectTagging",
|
|
301
|
+
"PutObjectTagging",
|
|
302
|
+
"GetBucketTagging",
|
|
303
|
+
"GetObjectVersionAcl",
|
|
304
|
+
]
|
|
305
|
+
//And a set of undesired actions
|
|
306
|
+
const undesiredActions = [
|
|
307
|
+
"GetObjectAcl",
|
|
308
|
+
"PutObjectTagging"
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
//When we shrink the actions
|
|
312
|
+
const result = shrinkIteration(actions, undesiredActions, false);
|
|
313
|
+
|
|
314
|
+
//Then we should get the reduced actions
|
|
315
|
+
expect(result).toEqual([
|
|
316
|
+
// "*ObjectTagging",
|
|
317
|
+
"PutObjectTagging",
|
|
318
|
+
"Get*VersionAcl",
|
|
319
|
+
"Get*Tagging",
|
|
320
|
+
])
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('should shrink the actions, deep', () => {
|
|
324
|
+
//Given a list of actions
|
|
325
|
+
const actions = [
|
|
326
|
+
"GetObjectTagging",
|
|
327
|
+
"PutObjectTagging",
|
|
328
|
+
"GetBucketTagging",
|
|
329
|
+
"GetObjectVersionAcl",
|
|
330
|
+
]
|
|
331
|
+
//And a set of undesired actions
|
|
332
|
+
const undesiredActions = [
|
|
333
|
+
"GetObjectAcl",
|
|
334
|
+
"PutObjectTagging"
|
|
335
|
+
]
|
|
336
|
+
|
|
337
|
+
//When we shrink the actions
|
|
338
|
+
const result = shrinkIteration(actions, undesiredActions, true);
|
|
339
|
+
|
|
340
|
+
//Then we should get the reduced actions
|
|
341
|
+
expect(result).toEqual([
|
|
342
|
+
"PutObjectTagging",
|
|
343
|
+
"Get*Tagging",
|
|
344
|
+
"*Version*",
|
|
345
|
+
])
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
describe('shrinkResolvedList', () => {
|
|
350
|
+
it('should return a wildcard if all actions are desired', () => {
|
|
351
|
+
//Given a list of actions
|
|
352
|
+
const actions = [
|
|
353
|
+
"GetObjectTagging",
|
|
354
|
+
"PutObjectTagging",
|
|
355
|
+
"GetBucketTagging",
|
|
356
|
+
"GetObjectVersionAcl",
|
|
357
|
+
]
|
|
358
|
+
//And an exact same list of possible actions
|
|
359
|
+
const possibleActions = actions.slice(0);
|
|
360
|
+
|
|
361
|
+
//When we shrink the actions
|
|
362
|
+
const result = shrinkResolvedList(actions, possibleActions, Infinity);
|
|
363
|
+
|
|
364
|
+
//Then we should get a wildcard
|
|
365
|
+
expect(result).toEqual(["*"]);
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
it('should shrink the list with two iterations', () => {
|
|
369
|
+
//Given a list of actions
|
|
370
|
+
const actions = [
|
|
371
|
+
"GetObjectTagging",
|
|
372
|
+
"PutObjectTagging",
|
|
373
|
+
"GetBucketTagging",
|
|
374
|
+
"GetObjectVersionAcl",
|
|
375
|
+
]
|
|
376
|
+
//And a set of undesired actions
|
|
377
|
+
const possibleActions = [
|
|
378
|
+
"GetObjectTagging",
|
|
379
|
+
"PutObjectTagging",
|
|
380
|
+
"GetBucketTagging",
|
|
381
|
+
"GetObjectVersionAcl",
|
|
382
|
+
"GetObjectAcl",
|
|
383
|
+
"GetObjectVersion",
|
|
384
|
+
]
|
|
385
|
+
|
|
386
|
+
//When we shrink the actions
|
|
387
|
+
const result = shrinkResolvedList(actions, possibleActions, 2);
|
|
388
|
+
|
|
389
|
+
//Then we should get the reduced actions
|
|
390
|
+
expect(result.sort()).toEqual([
|
|
391
|
+
"*Tagging",
|
|
392
|
+
"Get*VersionAcl"
|
|
393
|
+
])
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
it('should shrink the list of actions aggressively', () => {
|
|
397
|
+
//Given a list of actions
|
|
398
|
+
const actions = [
|
|
399
|
+
"GetObjectTagging",
|
|
400
|
+
"PutObjectTagging",
|
|
401
|
+
"GetBucketTagging",
|
|
402
|
+
"GetObjectVersionAcl",
|
|
403
|
+
]
|
|
404
|
+
//And a set of undesired actions
|
|
405
|
+
const possibleActions = [
|
|
406
|
+
"GetObjectTagging",
|
|
407
|
+
"PutObjectTagging",
|
|
408
|
+
"GetBucketTagging",
|
|
409
|
+
"GetObjectVersionAcl",
|
|
410
|
+
"GetObjectAcl",
|
|
411
|
+
"GetObjectVersion",
|
|
412
|
+
]
|
|
413
|
+
|
|
414
|
+
//When we shrink the actions
|
|
415
|
+
const result = shrinkResolvedList(actions, possibleActions, Infinity);
|
|
416
|
+
|
|
417
|
+
//Then we should get the reduced actions
|
|
418
|
+
expect(result.sort()).toEqual([
|
|
419
|
+
"*Tagging",
|
|
420
|
+
"*VersionAcl"
|
|
421
|
+
])
|
|
422
|
+
})
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
describe('groupActionsByService', () => {
|
|
426
|
+
it('should group the actions by service', () => {
|
|
427
|
+
//Given a list of actions
|
|
428
|
+
const actions = [
|
|
429
|
+
"s3:GetObjectTagging",
|
|
430
|
+
"s3:PutObjectTagging",
|
|
431
|
+
"s3:GetBucketTagging",
|
|
432
|
+
"s3:GetObjectVersionAcl",
|
|
433
|
+
"ec2:GetObjectTagging",
|
|
434
|
+
"ec2:PutObjectTagging",
|
|
435
|
+
"ec2:GetBucketTagging"
|
|
436
|
+
]
|
|
437
|
+
|
|
438
|
+
//When we group the actions by service
|
|
439
|
+
const result = groupActionsByService(actions);
|
|
440
|
+
|
|
441
|
+
//Then we should get the actions grouped by service
|
|
442
|
+
expect([...result.keys()]).toEqual(["s3", "ec2"]);
|
|
443
|
+
|
|
444
|
+
expect(result.get("s3")).toEqual({
|
|
445
|
+
withService: [
|
|
446
|
+
"s3:GetObjectTagging",
|
|
447
|
+
"s3:PutObjectTagging",
|
|
448
|
+
"s3:GetBucketTagging",
|
|
449
|
+
"s3:GetObjectVersionAcl"
|
|
450
|
+
],
|
|
451
|
+
withoutService: [
|
|
452
|
+
"GetObjectTagging",
|
|
453
|
+
"PutObjectTagging",
|
|
454
|
+
"GetBucketTagging",
|
|
455
|
+
"GetObjectVersionAcl"
|
|
456
|
+
]
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
expect(result.get("ec2")).toEqual({
|
|
460
|
+
withService: [
|
|
461
|
+
"ec2:GetObjectTagging",
|
|
462
|
+
"ec2:PutObjectTagging",
|
|
463
|
+
"ec2:GetBucketTagging"
|
|
464
|
+
],
|
|
465
|
+
withoutService: [
|
|
466
|
+
"GetObjectTagging",
|
|
467
|
+
"PutObjectTagging",
|
|
468
|
+
"GetBucketTagging"
|
|
469
|
+
]
|
|
470
|
+
})
|
|
471
|
+
})
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
describe('mapActions', () => {
|
|
475
|
+
it('should map the actions with the service', () => {
|
|
476
|
+
//Given a list of actions
|
|
477
|
+
const actions = [
|
|
478
|
+
"s3:GetObjectTagging",
|
|
479
|
+
"s3:PutObjectTagging",
|
|
480
|
+
"s3:GetBucketTagging",
|
|
481
|
+
"s3:GetObjectVersionAcl",
|
|
482
|
+
"ec2:GetObjectTagging",
|
|
483
|
+
"ec2:PutObjectTagging",
|
|
484
|
+
"ec2:GetBucketTagging"
|
|
485
|
+
]
|
|
486
|
+
|
|
487
|
+
//When we map the actions to services
|
|
488
|
+
const result = mapActions(actions);
|
|
489
|
+
|
|
490
|
+
//Then we should get the actions grouped by service
|
|
491
|
+
expect(result).toEqual([
|
|
492
|
+
"GetObjectTagging",
|
|
493
|
+
"PutObjectTagging",
|
|
494
|
+
"GetBucketTagging",
|
|
495
|
+
"GetObjectVersionAcl",
|
|
496
|
+
"GetObjectTagging",
|
|
497
|
+
"PutObjectTagging",
|
|
498
|
+
"GetBucketTagging"
|
|
499
|
+
])
|
|
500
|
+
})
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
describe('shrink', () => {
|
|
504
|
+
it('should shrink the actions', async () => {
|
|
505
|
+
//Given a list of actions
|
|
506
|
+
const actions = [
|
|
507
|
+
"s3:GetObjectTagging",
|
|
508
|
+
"s3:PutObjectTagging",
|
|
509
|
+
"s3:GetBucketTagging",
|
|
510
|
+
"s3:GetObjectVersionAcl"
|
|
511
|
+
]
|
|
512
|
+
|
|
513
|
+
//And a set of available actions
|
|
514
|
+
mockExpandIamActions.mockImplementation(async (actions: string | string[]) => {
|
|
515
|
+
if(actions == "s3:*") {
|
|
516
|
+
return [
|
|
517
|
+
"s3:GetObjectTagging",
|
|
518
|
+
"s3:PutObjectTagging",
|
|
519
|
+
"s3:GetBucketTagging",
|
|
520
|
+
"s3:GetObjectVersionAcl",
|
|
521
|
+
"s3:GetObjectAcl",
|
|
522
|
+
"s3:GetObjectVersion"
|
|
523
|
+
]
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return [
|
|
527
|
+
"s3:GetObjectTagging",
|
|
528
|
+
"s3:PutObjectTagging",
|
|
529
|
+
"s3:GetBucketTagging",
|
|
530
|
+
"s3:GetObjectVersionAcl"
|
|
531
|
+
];
|
|
532
|
+
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
//When shrink is called
|
|
536
|
+
const result = await shrink(actions, {});
|
|
537
|
+
|
|
538
|
+
//Then we should get the reduced actions
|
|
539
|
+
expect(result).toEqual([
|
|
540
|
+
"s3:Get*VersionAcl",
|
|
541
|
+
"s3:*Tagging"
|
|
542
|
+
])
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
it('should throw an error if the shrink does not validate', async () => {
|
|
546
|
+
//Given a list of actions
|
|
547
|
+
const actions = [
|
|
548
|
+
"s3:GetObjectTagging",
|
|
549
|
+
"s3:PutObjectTagging",
|
|
550
|
+
"s3:GetBucketTagging",
|
|
551
|
+
"s3:GetObjectVersionAcl"
|
|
552
|
+
]
|
|
553
|
+
|
|
554
|
+
//And the validation fails
|
|
555
|
+
mockValidateShrinkResults.mockResolvedValueOnce("Undesired action: s3:DeleteObject")
|
|
556
|
+
|
|
557
|
+
//When shrink is called
|
|
558
|
+
const result = shrink(actions, {});
|
|
559
|
+
|
|
560
|
+
//Then we should get the reduced actions
|
|
561
|
+
await expect(result).rejects.toThrow(/@cloud-copilot\/iam-shrink has failed validation and this is a bug\./)
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
it('should return an all actions wildcard if one is provided', async () => {
|
|
565
|
+
//Given a list of actions that includes a global wildcard
|
|
566
|
+
const actions = [
|
|
567
|
+
"*",
|
|
568
|
+
"s3:GetObjectTagging",
|
|
569
|
+
"s3:PutObjectTagging",
|
|
570
|
+
]
|
|
571
|
+
|
|
572
|
+
//When shrink is called
|
|
573
|
+
const result = await shrink(actions, {});
|
|
574
|
+
|
|
575
|
+
//Then we should get back the single wildcard
|
|
576
|
+
expect(result).toEqual(["*"])
|
|
577
|
+
})
|
|
578
|
+
|
|
579
|
+
it('should return an all actions wildcard if a string of multiple asterisks is included', async () => {
|
|
580
|
+
//Given a list of actions that includes a global wildcard
|
|
581
|
+
const actions = [
|
|
582
|
+
"***",
|
|
583
|
+
"s3:GetObjectTagging",
|
|
584
|
+
"s3:PutObjectTagging",
|
|
585
|
+
]
|
|
586
|
+
|
|
587
|
+
//When shrink is called
|
|
588
|
+
const result = await shrink(actions);
|
|
589
|
+
|
|
590
|
+
//Then we should get back the single wildcard
|
|
591
|
+
expect(result).toEqual(["*"])
|
|
592
|
+
})
|
|
593
|
+
})
|
|
594
|
+
})
|