@cloud-copilot/iam-expand 0.1.8 → 0.1.10
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/README.md +6 -6
- package/dist/cjs/cli.js +2 -2
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/cli_utils.d.ts +1 -1
- package/dist/cjs/cli_utils.d.ts.map +1 -1
- package/dist/cjs/cli_utils.js +1 -1
- package/dist/cjs/cli_utils.js.map +1 -1
- package/dist/esm/cli.js +2 -2
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/cli_utils.d.ts +1 -1
- package/dist/esm/cli_utils.d.ts.map +1 -1
- package/dist/esm/cli_utils.js +1 -1
- package/dist/esm/cli_utils.js.map +1 -1
- package/package.json +4 -1
- package/examples/README.md +0 -3
- package/examples/download-and-expand-authorization-details.sh +0 -8
- package/examples/download-and-expand-policies.sh +0 -22
- package/postbuild.sh +0 -13
- package/src/cli.ts +0 -94
- package/src/cli_utils.test.ts +0 -147
- package/src/cli_utils.ts +0 -84
- package/src/expand.test.ts +0 -489
- package/src/expand.ts +0 -166
- package/src/expand_file.test.ts +0 -184
- package/src/expand_file.ts +0 -39
- package/src/index.ts +0 -1
- package/src/stdin.ts +0 -34
- package/tsconfig.cjs.json +0 -11
- package/tsconfig.esm.json +0 -14
- package/tsconfig.json +0 -22
package/src/expand.test.ts
DELETED
|
@@ -1,489 +0,0 @@
|
|
|
1
|
-
import { iamActionDetails, iamActionExists, iamActionsForService, iamServiceExists, iamServiceKeys } from '@cloud-copilot/iam-data'
|
|
2
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
-
import { expandIamActions, InvalidActionBehavior } from "./expand.js"
|
|
4
|
-
|
|
5
|
-
vi.mock('@cloud-copilot/iam-data')
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
vi.resetAllMocks()
|
|
9
|
-
// jest.resetAllMocks()
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
describe("expand", () => {
|
|
13
|
-
it("should return an empty array when actionString is null", async () => {
|
|
14
|
-
//Given actionString is null
|
|
15
|
-
const actionString = null
|
|
16
|
-
//When expand is called with actionString
|
|
17
|
-
const result = await expandIamActions(actionString as any)
|
|
18
|
-
//Then result should be an empty array
|
|
19
|
-
expect(result).toEqual([])
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it("should return '*' when actionString is '*' and expandAsterisk is false", async () => {
|
|
23
|
-
//Given actionString is '*'
|
|
24
|
-
const actionString = '*'
|
|
25
|
-
//When expand is called with actionString
|
|
26
|
-
const result = await expandIamActions(actionString)
|
|
27
|
-
//Then result should be '*'
|
|
28
|
-
expect(result).toEqual(['*'])
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('should return "*" when action string multiple asterisks and expandAsterisk is false', async () => {
|
|
32
|
-
//Given actionString is multiple asterisks
|
|
33
|
-
const actionString = '***'
|
|
34
|
-
|
|
35
|
-
//And expandAsterisk is false
|
|
36
|
-
const options = { expandAsterisk: false }
|
|
37
|
-
|
|
38
|
-
//When expand is called with actionString and options
|
|
39
|
-
const result = await expandIamActions(actionString, options)
|
|
40
|
-
|
|
41
|
-
//Then result should be '*'
|
|
42
|
-
expect(result).toEqual(['*'])
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it("should expand all actions for all services when actionString is '*' and expandAsterisk is true", async () => {
|
|
46
|
-
//Given actionString is '*'
|
|
47
|
-
const actionString = '*'
|
|
48
|
-
//And expandAsterisk is true
|
|
49
|
-
const options = { expandAsterisk: true }
|
|
50
|
-
//And there are services
|
|
51
|
-
vi.mocked(iamServiceKeys).mockResolvedValue(['s3', 'ec2'])
|
|
52
|
-
|
|
53
|
-
//And there are actions for the services
|
|
54
|
-
vi.mocked(iamActionsForService).mockImplementation(async (service) => {
|
|
55
|
-
if(service === 's3') {
|
|
56
|
-
return ['action1', 'action2']
|
|
57
|
-
}
|
|
58
|
-
if(service === 'ec2') {
|
|
59
|
-
return ['action3', 'action4']
|
|
60
|
-
}
|
|
61
|
-
return []
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
//When expand is called with actionString and options
|
|
65
|
-
const result = await expandIamActions(actionString, options)
|
|
66
|
-
//Then result should be an array of all actions for all services
|
|
67
|
-
expect(result.sort()).toEqual([
|
|
68
|
-
'ec2:action3',
|
|
69
|
-
'ec2:action4',
|
|
70
|
-
's3:action1',
|
|
71
|
-
's3:action2'
|
|
72
|
-
])
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it("should do a case insensitive match for the service in the action string", async () => {
|
|
76
|
-
//Given actionString is 'S3:GetObject'
|
|
77
|
-
const actionString = 'S3:get*'
|
|
78
|
-
//And s3 service exists
|
|
79
|
-
vi.mocked(iamServiceExists).mockImplementation(async (s) => s === 's3')
|
|
80
|
-
//And there are matching actions
|
|
81
|
-
vi.mocked(iamActionsForService).mockResolvedValue(['GetObject'])
|
|
82
|
-
|
|
83
|
-
//When expand is called with actionString
|
|
84
|
-
const result = await expandIamActions(actionString)
|
|
85
|
-
|
|
86
|
-
//Then result should be an array with the actionString
|
|
87
|
-
expect(result).toEqual(['s3:GetObject'])
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
describe("invalid action name", () => {
|
|
91
|
-
it('should return an action without wildcards if it is a valid action', async () => {
|
|
92
|
-
//Given actionString contains a valid action
|
|
93
|
-
const actionString = 's3:getobject'
|
|
94
|
-
//And s3 the service exists
|
|
95
|
-
vi.mocked(iamServiceExists).mockResolvedValue(true)
|
|
96
|
-
//And the action does not
|
|
97
|
-
vi.mocked(iamActionExists).mockResolvedValue(true)
|
|
98
|
-
vi.mocked(iamActionDetails).mockResolvedValue({name: 'GetObject'} as any)
|
|
99
|
-
|
|
100
|
-
//When expand is called with actionString
|
|
101
|
-
const result = await expandIamActions(actionString)
|
|
102
|
-
|
|
103
|
-
//Then result should be an array with the actionString
|
|
104
|
-
expect(result).toEqual(['s3:GetObject'])
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it("should remove an invalid action if invalidActionBehavior is Remove", async () => {
|
|
108
|
-
//Given actionString contains an invalid action
|
|
109
|
-
const actionString = 's3:DoSomethingDumb'
|
|
110
|
-
//And invalidActionBehavior is Remove
|
|
111
|
-
const options = { invalidActionBehavior: InvalidActionBehavior.Remove }
|
|
112
|
-
//And s3 the service exists
|
|
113
|
-
vi.mocked(iamServiceExists).mockResolvedValue(true)
|
|
114
|
-
//And the action does not
|
|
115
|
-
vi.mocked(iamActionExists).mockResolvedValue(false)
|
|
116
|
-
|
|
117
|
-
//When expand is called with actionString
|
|
118
|
-
const result = await expandIamActions(actionString, options)
|
|
119
|
-
|
|
120
|
-
//Then result should be an array with the valid action
|
|
121
|
-
expect(result).toEqual([])
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it("should include an invalid action if invalidActionBehavior is Include", async () => {
|
|
125
|
-
//Given actionString contains an invalid action
|
|
126
|
-
const actionString = 's3:DoSomethingSilly'
|
|
127
|
-
//And invalidActionBehavior is Include
|
|
128
|
-
const options = { invalidActionBehavior: InvalidActionBehavior.Include }
|
|
129
|
-
//And s3 the service exists
|
|
130
|
-
vi.mocked(iamServiceExists).mockResolvedValue(true)
|
|
131
|
-
//And the action does not
|
|
132
|
-
vi.mocked(iamActionExists).mockResolvedValue(false)
|
|
133
|
-
|
|
134
|
-
//When expand is called with actionString
|
|
135
|
-
const result = await expandIamActions(actionString, options)
|
|
136
|
-
|
|
137
|
-
//Then result should be an array with the invalid action
|
|
138
|
-
expect(result).toEqual([actionString])
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
it('should throw an error if the action is invalid and invalidActionBehavior is Error', async () => {
|
|
142
|
-
//Given actionString contains an invalid action
|
|
143
|
-
const actionString = 's3:AbsurdlyInvalidAction'
|
|
144
|
-
//And invalidActionBehavior is Error
|
|
145
|
-
const options = { invalidActionBehavior: InvalidActionBehavior.Error }
|
|
146
|
-
//And s3 the service exists
|
|
147
|
-
vi.mocked(iamServiceExists).mockResolvedValue(true)
|
|
148
|
-
//And the action does not
|
|
149
|
-
vi.mocked(iamActionExists).mockResolvedValue(false)
|
|
150
|
-
|
|
151
|
-
//When expand is called with actionString
|
|
152
|
-
//Then an error should be thrown
|
|
153
|
-
expect(
|
|
154
|
-
expandIamActions(actionString, options)
|
|
155
|
-
).rejects.toThrowError('Invalid action')
|
|
156
|
-
})
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
describe("when the actions string is in the wrong format", () => {
|
|
160
|
-
it("should return an empty array when there are too many parts and errorOnInvalidFormat is false", async () => {
|
|
161
|
-
//Given actionString is in the wrong format
|
|
162
|
-
const actionString = 's3:GetObject:Extra*'
|
|
163
|
-
//And errorOnInvalidFormat is false
|
|
164
|
-
const options = { errorOnInvalidFormat: false }
|
|
165
|
-
|
|
166
|
-
//When expand is called with actionString
|
|
167
|
-
const result = await expandIamActions(actionString, options)
|
|
168
|
-
|
|
169
|
-
//Then result should be an empty array
|
|
170
|
-
expect(result).toEqual([])
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
it("should return an empty array when there are too few parts and errorOnInvalidFormat is false", async () => {
|
|
174
|
-
//Given actionString has no :
|
|
175
|
-
const actionString = 's3GetObject*'
|
|
176
|
-
//And errorOnInvalidFormat is false
|
|
177
|
-
const options = { errorOnInvalidFormat: false }
|
|
178
|
-
|
|
179
|
-
//When expand is called with actionString
|
|
180
|
-
const result = await expandIamActions(actionString, options)
|
|
181
|
-
|
|
182
|
-
//Then result should be an empty array
|
|
183
|
-
expect(result).toEqual([])
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
it("should throw an error when there are too many parts and errorOnInvalidFormat is true", async () => {
|
|
187
|
-
//Given actionString is in the wrong format
|
|
188
|
-
const actionString = 's3:GetObject:Extra*'
|
|
189
|
-
//And errorOnInvalidFormat is true
|
|
190
|
-
const options = { errorOnInvalidFormat: true }
|
|
191
|
-
|
|
192
|
-
//When expand is called with actionString
|
|
193
|
-
//Then an error should be thrown
|
|
194
|
-
expect(
|
|
195
|
-
() => expandIamActions(actionString, options)
|
|
196
|
-
).rejects.toThrowError('Invalid action format')
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
it("should throw an error when there are too few parts and errorOnInvalidFormat is true", async () => {
|
|
200
|
-
//Given actionString has no :
|
|
201
|
-
const actionString = 's3GetObject*'
|
|
202
|
-
//And errorOnInvalidFormat is true
|
|
203
|
-
const options = { errorOnInvalidFormat: true }
|
|
204
|
-
|
|
205
|
-
//When expand is called with actionString
|
|
206
|
-
//Then an error should be thrown
|
|
207
|
-
expect(
|
|
208
|
-
() => expandIamActions(actionString, options)
|
|
209
|
-
).rejects.toThrowError('Invalid action format')
|
|
210
|
-
})
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
describe("when the service in the action string does not exist", () => {
|
|
214
|
-
it("should return an empty array when errorOnInvalidService is false", async () => {
|
|
215
|
-
//Given actionString contains a service that does not exist
|
|
216
|
-
const actionString = 'fake:GetObject*'
|
|
217
|
-
//And errorOnMissingService is false
|
|
218
|
-
const options = { errorOnInvalidService: false }
|
|
219
|
-
|
|
220
|
-
//When expand is called with actionString
|
|
221
|
-
const result = await expandIamActions(actionString, options)
|
|
222
|
-
|
|
223
|
-
//Then result should be an empty array
|
|
224
|
-
expect(result).toEqual([])
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
it("should throw an error when errorOnInvalidService is true", async () => {
|
|
228
|
-
//Given actionString contains a service that does not exist
|
|
229
|
-
const actionString = 'fake:GetObject*'
|
|
230
|
-
//And errorOnMissingService is true
|
|
231
|
-
const options = { errorOnInvalidService: true }
|
|
232
|
-
|
|
233
|
-
//When expand is called with actionString
|
|
234
|
-
//Then an error should be thrown
|
|
235
|
-
expect(
|
|
236
|
-
() => expandIamActions(actionString, options)
|
|
237
|
-
).rejects.toThrowError('Service not found')
|
|
238
|
-
})
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
describe("when the action string contains a wildcard for a service", () => {
|
|
242
|
-
it("should expand not expand the wildcard when expandServiceAsterisk is false", async () => {
|
|
243
|
-
//Given actionString is 's3:*'
|
|
244
|
-
const actionString = 's3:*'
|
|
245
|
-
//And expandServiceAsterisk is false
|
|
246
|
-
const options = { expandServiceAsterisk: false }
|
|
247
|
-
//And s3 service exists
|
|
248
|
-
vi.mocked(iamServiceExists).mockResolvedValue(true)
|
|
249
|
-
//And there are matching actions
|
|
250
|
-
vi.mocked(iamActionsForService).mockResolvedValue(['GetObject', 'PutObject'])
|
|
251
|
-
|
|
252
|
-
//When expand is called with actionString
|
|
253
|
-
const result = await expandIamActions(actionString, options)
|
|
254
|
-
|
|
255
|
-
//Then result should be an array with the original string
|
|
256
|
-
expect(result).toEqual([actionString])
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
it("should expand not expand the wildcard when there are multiple asterisks and expandServiceAsterisk is false", async () => {
|
|
260
|
-
//Given actionString has multiple asterisks for the actions
|
|
261
|
-
const actionString = 's3:****'
|
|
262
|
-
//And expandServiceAsterisk is false
|
|
263
|
-
const options = { expandServiceAsterisk: false }
|
|
264
|
-
//And s3 service exists
|
|
265
|
-
vi.mocked(iamServiceExists).mockResolvedValue(true)
|
|
266
|
-
//And there are matching actions
|
|
267
|
-
vi.mocked(iamActionsForService).mockResolvedValue(['GetObject', 'PutObject'])
|
|
268
|
-
|
|
269
|
-
//When expand is called with actionString
|
|
270
|
-
const result = await expandIamActions(actionString, options)
|
|
271
|
-
|
|
272
|
-
//Then result should be an array with the original string
|
|
273
|
-
expect(result).toEqual(['s3:*'])
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
it("should expand the wildcard when expandServiceAsterisk is true", async () => {
|
|
277
|
-
//Given actionString is 's3:*'
|
|
278
|
-
const actionString = 's3:*'
|
|
279
|
-
//And expandServiceAsterisk is true
|
|
280
|
-
const options = { expandServiceAsterisk: true }
|
|
281
|
-
//And s3 service exists
|
|
282
|
-
vi.mocked(iamServiceExists).mockResolvedValue(true)
|
|
283
|
-
//And there are matching actions
|
|
284
|
-
vi.mocked(iamActionsForService).mockResolvedValue(['GetObject', 'PutObject'])
|
|
285
|
-
|
|
286
|
-
//When expand is called with actionString
|
|
287
|
-
const result = await expandIamActions(actionString, options)
|
|
288
|
-
|
|
289
|
-
//Then result should be an array of actions
|
|
290
|
-
expect(result).toEqual([
|
|
291
|
-
's3:GetObject',
|
|
292
|
-
's3:PutObject'
|
|
293
|
-
])
|
|
294
|
-
})
|
|
295
|
-
})
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
describe("when the action string contains wildcards", () => {
|
|
299
|
-
it('should expand the wildcard actions at the end', async () => {
|
|
300
|
-
//Given actionString is 's3:Get*'
|
|
301
|
-
const actionString = 's3:Get*'
|
|
302
|
-
//And s3 service exists
|
|
303
|
-
vi.mocked(iamServiceExists).mockResolvedValue(true)
|
|
304
|
-
//And there are matching actions
|
|
305
|
-
vi.mocked(iamActionsForService).mockResolvedValue([
|
|
306
|
-
'GetObject',
|
|
307
|
-
'GetObjectAcl',
|
|
308
|
-
'GetObjectTagging',
|
|
309
|
-
'GetObjectTorrent',
|
|
310
|
-
'PutObject',
|
|
311
|
-
'PutObjectAcl',
|
|
312
|
-
'SomethingGetSomething'
|
|
313
|
-
])
|
|
314
|
-
|
|
315
|
-
//When expand is called with actionString
|
|
316
|
-
const result = await expandIamActions(actionString)
|
|
317
|
-
//Then result should be an array of actions
|
|
318
|
-
expect(result).toEqual([
|
|
319
|
-
's3:GetObject',
|
|
320
|
-
's3:GetObjectAcl',
|
|
321
|
-
's3:GetObjectTagging',
|
|
322
|
-
's3:GetObjectTorrent'
|
|
323
|
-
])
|
|
324
|
-
})
|
|
325
|
-
|
|
326
|
-
it('should expand the wildcard actions at the beginning', async () => {
|
|
327
|
-
//Given actionString is 's3:*Object'
|
|
328
|
-
const actionString = 's3:*Object'
|
|
329
|
-
//And s3 service exists
|
|
330
|
-
vi.mocked(iamServiceExists).mockResolvedValue(true)
|
|
331
|
-
//And there are matching actions
|
|
332
|
-
vi.mocked(iamActionsForService).mockResolvedValue([
|
|
333
|
-
'GetObject',
|
|
334
|
-
'GetObjectAcl',
|
|
335
|
-
'GetObjectTagging',
|
|
336
|
-
'GetObjectTorrent',
|
|
337
|
-
'PutObject',
|
|
338
|
-
'PutObjectAcl',
|
|
339
|
-
'SomethingGetSomething'
|
|
340
|
-
])
|
|
341
|
-
|
|
342
|
-
//When expand is called with actionString
|
|
343
|
-
const result = await expandIamActions(actionString)
|
|
344
|
-
//Then result should be an array of actions
|
|
345
|
-
expect(result).toEqual([
|
|
346
|
-
's3:GetObject',
|
|
347
|
-
's3:PutObject'
|
|
348
|
-
])
|
|
349
|
-
})
|
|
350
|
-
|
|
351
|
-
it('should expand the wildcard actions in the middle', async () => {
|
|
352
|
-
//Given actionString is 's3:Get*Tagging'
|
|
353
|
-
const actionString = 's3:Get*Tagging'
|
|
354
|
-
//And s3 service exists
|
|
355
|
-
vi.mocked(iamServiceExists).mockResolvedValue(true)
|
|
356
|
-
//And there are matching actions
|
|
357
|
-
vi.mocked(iamActionsForService).mockResolvedValue([
|
|
358
|
-
'GetObject',
|
|
359
|
-
'GetObjectAcl',
|
|
360
|
-
'GetObjectTagging',
|
|
361
|
-
'GetBanskyTagging',
|
|
362
|
-
'GetObjectTorrent',
|
|
363
|
-
'PutObject',
|
|
364
|
-
'PutObjectAcl',
|
|
365
|
-
'SomethingGetSomething'
|
|
366
|
-
])
|
|
367
|
-
|
|
368
|
-
//When expand is called with actionString
|
|
369
|
-
const result = await expandIamActions(actionString)
|
|
370
|
-
//Then result should be an array of actions
|
|
371
|
-
expect(result).toEqual([
|
|
372
|
-
's3:GetBanskyTagging',
|
|
373
|
-
's3:GetObjectTagging'
|
|
374
|
-
])
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
it('should expand multiple wildcards', async () => {
|
|
378
|
-
//Given actionString is 's3:Get*Tagging*'
|
|
379
|
-
const actionString = 's3:Get*Tagging*'
|
|
380
|
-
//And s3 service exists
|
|
381
|
-
vi.mocked(iamServiceExists).mockResolvedValue(true)
|
|
382
|
-
//And there are matching actions
|
|
383
|
-
vi.mocked(iamActionsForService).mockResolvedValue([
|
|
384
|
-
'GetObject',
|
|
385
|
-
'GetObjectAcl',
|
|
386
|
-
'GetObjectTagging',
|
|
387
|
-
'GetBanskyTagging',
|
|
388
|
-
'GetTagging',
|
|
389
|
-
'GetObjectTorrent',
|
|
390
|
-
'GetSomethingTaggingSomething',
|
|
391
|
-
'PutObject',
|
|
392
|
-
'PutObjectAcl',
|
|
393
|
-
'SomethingGetSomething'
|
|
394
|
-
])
|
|
395
|
-
|
|
396
|
-
//When expand is called with actionString
|
|
397
|
-
const result = await expandIamActions(actionString)
|
|
398
|
-
//Then result should be an array of actions
|
|
399
|
-
expect(result).toEqual([
|
|
400
|
-
's3:GetBanskyTagging',
|
|
401
|
-
's3:GetObjectTagging',
|
|
402
|
-
's3:GetSomethingTaggingSomething',
|
|
403
|
-
's3:GetTagging'
|
|
404
|
-
])
|
|
405
|
-
})
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
describe("when actionStrings is an array", () => {
|
|
409
|
-
it("should return an empty array when actionStrings is an empty array", async () => {
|
|
410
|
-
//Given actionStrings is an empty array
|
|
411
|
-
const actionStrings: string[] = []
|
|
412
|
-
|
|
413
|
-
//When expand is called with actionStrings
|
|
414
|
-
const result = await expandIamActions(actionStrings)
|
|
415
|
-
|
|
416
|
-
//Then result should be an empty array
|
|
417
|
-
expect(result).toEqual([])
|
|
418
|
-
})
|
|
419
|
-
|
|
420
|
-
it("should return an array of expanded actions when actionStrings is an array of action strings", async () => {
|
|
421
|
-
//Given actionStrings is an array of action strings
|
|
422
|
-
const actionStrings = [
|
|
423
|
-
's3:Get*',
|
|
424
|
-
'ec2:*Instances'
|
|
425
|
-
]
|
|
426
|
-
//And s3 and ec2 services exist
|
|
427
|
-
vi.mocked(iamServiceExists).mockResolvedValue(true)
|
|
428
|
-
//And there are actions for the services
|
|
429
|
-
vi.mocked(iamActionsForService).mockImplementation(async (service) => {
|
|
430
|
-
if(service === 's3') {
|
|
431
|
-
return ['GetObject', 'GetObjectTagging', 'PutObject', 'PutObjectTagging']
|
|
432
|
-
}
|
|
433
|
-
if(service === 'ec2') {
|
|
434
|
-
return ['RunInstances', 'TerminateInstances']
|
|
435
|
-
}
|
|
436
|
-
return []
|
|
437
|
-
})
|
|
438
|
-
|
|
439
|
-
//When expand is called with actionStrings
|
|
440
|
-
const result = await expandIamActions(actionStrings)
|
|
441
|
-
|
|
442
|
-
//Then result should be an array of expanded actions
|
|
443
|
-
expect(result.sort()).toEqual([
|
|
444
|
-
'ec2:RunInstances',
|
|
445
|
-
'ec2:TerminateInstances',
|
|
446
|
-
's3:GetObject',
|
|
447
|
-
's3:GetObjectTagging',
|
|
448
|
-
])
|
|
449
|
-
})
|
|
450
|
-
})
|
|
451
|
-
|
|
452
|
-
it('should return only unique values', async () => {
|
|
453
|
-
//Given two action strings
|
|
454
|
-
const actionString = ['s3:Get*','s3:*Object']
|
|
455
|
-
//And s3 service exists
|
|
456
|
-
vi.mocked(iamServiceExists).mockResolvedValue(true)
|
|
457
|
-
//And there are matching actions
|
|
458
|
-
vi.mocked(iamActionsForService).mockResolvedValue(['GetObject', 'PutObject', 'GetOtherObject'])
|
|
459
|
-
|
|
460
|
-
//When expand is called with actionStrings and distinct is true
|
|
461
|
-
const result = await expandIamActions(actionString)
|
|
462
|
-
//Then result should be an array of unique actions
|
|
463
|
-
expect(result).toEqual(['s3:GetObject', 's3:GetOtherObject', 's3:PutObject'])
|
|
464
|
-
})
|
|
465
|
-
|
|
466
|
-
it('should return values sorted', async () => {
|
|
467
|
-
//Given two action strings
|
|
468
|
-
const actionString = ['s3:Get*','ec2:Describe*']
|
|
469
|
-
//And s3 service exists
|
|
470
|
-
vi.mocked(iamServiceExists).mockResolvedValue(true)
|
|
471
|
-
//And there are matching actions
|
|
472
|
-
vi.mocked(iamActionsForService).mockImplementation(async (service) => {
|
|
473
|
-
if(service === 's3') {
|
|
474
|
-
return ['GetObject', 'GetBucket']
|
|
475
|
-
}
|
|
476
|
-
if(service === 'ec2') {
|
|
477
|
-
return ['DescribeInstances', 'DescribeVolumes']
|
|
478
|
-
}
|
|
479
|
-
return []
|
|
480
|
-
})
|
|
481
|
-
|
|
482
|
-
//When expand is called with actionStrings
|
|
483
|
-
const result = await expandIamActions(actionString)
|
|
484
|
-
|
|
485
|
-
//Then result should be an array of sorted actions
|
|
486
|
-
expect(result).toEqual(['ec2:DescribeInstances', 'ec2:DescribeVolumes', 's3:GetBucket', 's3:GetObject'])
|
|
487
|
-
})
|
|
488
|
-
|
|
489
|
-
})
|
package/src/expand.ts
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { iamActionDetails, iamActionExists, iamActionsForService, iamServiceExists, iamServiceKeys } from '@cloud-copilot/iam-data'
|
|
2
|
-
|
|
3
|
-
export enum InvalidActionBehavior {
|
|
4
|
-
Remove = "Remove",
|
|
5
|
-
Error = "Error",
|
|
6
|
-
Include = "Include",
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Options for the expand function
|
|
11
|
-
*
|
|
12
|
-
*/
|
|
13
|
-
export interface ExpandIamActionsOptions {
|
|
14
|
-
/**
|
|
15
|
-
* If true, a single `*` will be expanded to all actions for all services
|
|
16
|
-
* If false, a single `*` will be returned as is
|
|
17
|
-
* Default: false
|
|
18
|
-
*/
|
|
19
|
-
expandAsterisk: boolean
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* If true, `service:*` will be expanded to all actions for that service
|
|
23
|
-
* If false, `service:*` will be returned as is
|
|
24
|
-
* Default: false
|
|
25
|
-
*/
|
|
26
|
-
expandServiceAsterisk: boolean
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* If true, an error will be thrown if the action string is not in the correct format
|
|
30
|
-
* If false, an empty array will be returned
|
|
31
|
-
* Default: false
|
|
32
|
-
*/
|
|
33
|
-
errorOnInvalidFormat: boolean
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* If true, an error will be thrown if the service in the action string does not exist
|
|
37
|
-
* If false, an empty array will be returned
|
|
38
|
-
* Default: false
|
|
39
|
-
*/
|
|
40
|
-
errorOnInvalidService: boolean
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* The behavior to use when an invalid action is encountered without wildcards
|
|
44
|
-
* @{InvalidActionBehavior.Remove} will remove the invalid action from the output
|
|
45
|
-
* @{InvalidActionBehavior.Error} will throw an error if an invalid action is encountered
|
|
46
|
-
* @{InvalidActionBehavior.Include} will include the invalid action in the output
|
|
47
|
-
*
|
|
48
|
-
* Default: InvalidActionBehavior.Remove
|
|
49
|
-
*/
|
|
50
|
-
invalidActionBehavior: InvalidActionBehavior
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const defaultOptions: ExpandIamActionsOptions = {
|
|
54
|
-
expandAsterisk: false,
|
|
55
|
-
expandServiceAsterisk: false,
|
|
56
|
-
errorOnInvalidFormat: false,
|
|
57
|
-
errorOnInvalidService: false,
|
|
58
|
-
invalidActionBehavior: InvalidActionBehavior.Remove,
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const allAsterisksPattern = /^\*+$/i
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Expands an IAM action string that contains wildcards.
|
|
65
|
-
* If the action string contains no wildcards, it is returned as is.
|
|
66
|
-
* @see {@link ExpandIamActionsOptions} for options to customize behavior
|
|
67
|
-
*
|
|
68
|
-
* If any options are set to throw an error, the function will throw an error if validation fails for a single value.
|
|
69
|
-
*
|
|
70
|
-
* @param actionStringOrStrings An IAM action or array IAM action(s) that may contain wildcards
|
|
71
|
-
* @param overrideOptions Options to override the default behavior
|
|
72
|
-
* @returns An array of expanded action strings flattend to a single array
|
|
73
|
-
*/
|
|
74
|
-
export async function expandIamActions(actionStringOrStrings: string | string[], overrideOptions?: Partial<ExpandIamActionsOptions>): Promise<string[]> {
|
|
75
|
-
const options = {...defaultOptions, ...overrideOptions}
|
|
76
|
-
|
|
77
|
-
if(!actionStringOrStrings) {
|
|
78
|
-
//Just in case the user passes in null or undefined
|
|
79
|
-
return []
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if(Array.isArray(actionStringOrStrings)) {
|
|
83
|
-
const actionLists = await Promise.all(actionStringOrStrings.map(async (actionString) => {
|
|
84
|
-
return expandIamActions(actionString, options);
|
|
85
|
-
}))
|
|
86
|
-
|
|
87
|
-
const allMatches = Array.from(new Set(actionLists.flat()))
|
|
88
|
-
allMatches.sort()
|
|
89
|
-
|
|
90
|
-
return allMatches
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const actionString = actionStringOrStrings.trim()
|
|
94
|
-
|
|
95
|
-
if(actionString.match(allAsterisksPattern)) {
|
|
96
|
-
if(options.expandAsterisk) {
|
|
97
|
-
//If that's really what you want...
|
|
98
|
-
const allActions = []
|
|
99
|
-
const serviceKeys = await iamServiceKeys()
|
|
100
|
-
for await (const service of serviceKeys) {
|
|
101
|
-
const serviceActions = await iamActionsForService(service)
|
|
102
|
-
allActions.push(...serviceActions.map(action => `${service}:${action}`))
|
|
103
|
-
}
|
|
104
|
-
return allActions
|
|
105
|
-
}
|
|
106
|
-
return ['*']
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if(!actionString.includes(':')) {
|
|
110
|
-
if(options.errorOnInvalidFormat) {
|
|
111
|
-
throw new Error(`Invalid action format: ${actionString}`)
|
|
112
|
-
}
|
|
113
|
-
return []
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const parts = actionString.split(':')
|
|
117
|
-
if(parts.length !== 2) {
|
|
118
|
-
if(options.errorOnInvalidFormat) {
|
|
119
|
-
throw new Error(`Invalid action format: ${actionString}`)
|
|
120
|
-
}
|
|
121
|
-
return []
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const [service, wildcardActions] = parts.map(part => part.toLowerCase())
|
|
125
|
-
if(!await iamServiceExists(service)) {
|
|
126
|
-
if(options.errorOnInvalidService) {
|
|
127
|
-
throw new Error(`Service not found: ${service}`)
|
|
128
|
-
}
|
|
129
|
-
return []
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if(wildcardActions.match(allAsterisksPattern)) {
|
|
133
|
-
if(options.expandServiceAsterisk) {
|
|
134
|
-
const actionsForService = await iamActionsForService(service)
|
|
135
|
-
return actionsForService.map(action => `${service}:${action}`)
|
|
136
|
-
}
|
|
137
|
-
return [`${service}:*`]
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if(!actionString.includes('*')) {
|
|
141
|
-
const actionExists = await iamActionExists(service, wildcardActions)
|
|
142
|
-
if(actionExists) {
|
|
143
|
-
const details = await iamActionDetails(service, wildcardActions)
|
|
144
|
-
return [service + ":" + details.name]
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if(options.invalidActionBehavior === InvalidActionBehavior.Remove) {
|
|
148
|
-
return []
|
|
149
|
-
} else if(options.invalidActionBehavior === InvalidActionBehavior.Include) {
|
|
150
|
-
return [actionString]
|
|
151
|
-
} else if(options.invalidActionBehavior === InvalidActionBehavior.Error) {
|
|
152
|
-
throw new Error(`Invalid action: ${actionString}`)
|
|
153
|
-
} else {
|
|
154
|
-
//This should never happen
|
|
155
|
-
throw new Error(`Invalid invalidActionBehavior: ${options.invalidActionBehavior}`)
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const allActions = await iamActionsForService(service)
|
|
160
|
-
const pattern = "^" + wildcardActions.replace(/\*/g, '.*?') + "$"
|
|
161
|
-
const regex = new RegExp(pattern, 'i')
|
|
162
|
-
const matchingActions = allActions.filter(action => regex.test(action)).map(action => `${service}:${action}`)
|
|
163
|
-
matchingActions.sort()
|
|
164
|
-
|
|
165
|
-
return matchingActions
|
|
166
|
-
}
|