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