@wavo-cloud/aws-secrets-manager-helper 0.1.8 → 0.1.11
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/app/utils/awsSecretsManager.js +138 -13
- package/package.json +1 -1
- package/test/aws.test.js +47 -22
|
@@ -7,6 +7,13 @@ const client = new AWS.SecretsManager({
|
|
|
7
7
|
region: secretsManagerRegion,
|
|
8
8
|
})
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Right now, we have client lists that tell our systems where a clients secrets are
|
|
12
|
+
* We should be using tags (ie all production client lists have a tag saying its a production client
|
|
13
|
+
* or test client etc)
|
|
14
|
+
* These tags will also hold the organization name so that client secrets can still be found by name
|
|
15
|
+
*/
|
|
16
|
+
|
|
10
17
|
/**
|
|
11
18
|
* Attempts to parse secret
|
|
12
19
|
* @param {String} secretString - The stringified secret
|
|
@@ -70,6 +77,28 @@ async function getSecretValue(secretId) {
|
|
|
70
77
|
}
|
|
71
78
|
}
|
|
72
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Gets secret tags from aws secrets manager
|
|
82
|
+
* Returns Array of tags
|
|
83
|
+
* @param {String} secretId - The ID of an existing secret
|
|
84
|
+
*/
|
|
85
|
+
async function getSecretTags(secretId) {
|
|
86
|
+
let promiseError
|
|
87
|
+
const data = await client
|
|
88
|
+
.describeSecret({ SecretId: secretId })
|
|
89
|
+
.promise()
|
|
90
|
+
.catch(error => {
|
|
91
|
+
promiseError = error
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
if (promiseError) {
|
|
95
|
+
handleAWSPromiseError(promiseError)
|
|
96
|
+
} else if ('Tags' in data) {
|
|
97
|
+
return data.Tags
|
|
98
|
+
}
|
|
99
|
+
return []
|
|
100
|
+
}
|
|
101
|
+
|
|
73
102
|
/**
|
|
74
103
|
* Gets the secret keying where client secrets are stored
|
|
75
104
|
* Returns JSON where JSON is the secret
|
|
@@ -92,8 +121,13 @@ async function getClientSecret(clientId, clientIdsSecretIdOverride = null) {
|
|
|
92
121
|
* Gets all client secrets holding their API keys
|
|
93
122
|
* Returns [{ clientId: String, secretId: String, ...secret }]
|
|
94
123
|
* Skips all that are missing a region or organization
|
|
124
|
+
* If activeOnly flag, it only returns active client secrets
|
|
125
|
+
* This is true by default
|
|
95
126
|
*/
|
|
96
|
-
async function getAllClientSecrets(
|
|
127
|
+
async function getAllClientSecrets(
|
|
128
|
+
clientIdsSecretIdOverride = null,
|
|
129
|
+
activeOnly = true
|
|
130
|
+
) {
|
|
97
131
|
const clientSecretIds = await getClientSecretIds(clientIdsSecretIdOverride)
|
|
98
132
|
if (!clientSecretIds) return []
|
|
99
133
|
const parsedClientSecrets = await Promise.all(
|
|
@@ -101,11 +135,21 @@ async function getAllClientSecrets(clientIdsSecretIdOverride = null) {
|
|
|
101
135
|
const result = await getSecretValue(clientSecretIds[clientId])
|
|
102
136
|
result.id = clientId
|
|
103
137
|
result.secretId = clientSecretIds[clientId]
|
|
138
|
+
const tags = await getSecretTags(clientSecretIds[clientId])
|
|
139
|
+
const isActiveOrganizationTag = tags.find(
|
|
140
|
+
tag => tag.Key === 'is_active_organization'
|
|
141
|
+
) || { Value: 'false' }
|
|
142
|
+
result.isActiveOrganization = isActiveOrganizationTag.Value
|
|
104
143
|
return result
|
|
105
144
|
})
|
|
106
145
|
)
|
|
107
146
|
return parsedClientSecrets.filter(clientSecret => {
|
|
108
|
-
if (
|
|
147
|
+
if (
|
|
148
|
+
clientSecret.organization &&
|
|
149
|
+
clientSecret.region &&
|
|
150
|
+
!clientSecret.error &&
|
|
151
|
+
(!activeOnly || clientSecret.isActiveOrganization)
|
|
152
|
+
)
|
|
109
153
|
return true
|
|
110
154
|
return false
|
|
111
155
|
})
|
|
@@ -117,7 +161,7 @@ async function getAllClientSecrets(clientIdsSecretIdOverride = null) {
|
|
|
117
161
|
* @param {String} secretDescription - The description of the secret
|
|
118
162
|
* @param {String} secretValue - The JSON string secret value
|
|
119
163
|
*/
|
|
120
|
-
async function createSecret(secretName, secretDescription, secretValue) {
|
|
164
|
+
async function createSecret(secretName, secretDescription, secretValue, tags) {
|
|
121
165
|
let promiseError
|
|
122
166
|
if (typeof secretValue !== 'string')
|
|
123
167
|
throw new Error('Secret value must be a JSON string.')
|
|
@@ -131,6 +175,7 @@ async function createSecret(secretName, secretDescription, secretValue) {
|
|
|
131
175
|
Name: secretName,
|
|
132
176
|
Description: secretDescription,
|
|
133
177
|
SecretString: secretValue,
|
|
178
|
+
Tags: tags,
|
|
134
179
|
})
|
|
135
180
|
.promise()
|
|
136
181
|
.catch(error => {
|
|
@@ -164,6 +209,50 @@ async function editSecret(secretName, secretValue) {
|
|
|
164
209
|
else return data
|
|
165
210
|
}
|
|
166
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Removes a set of tags from a secret
|
|
214
|
+
* @param {String} secretName - The friendly name of the secret
|
|
215
|
+
* @param {Object} tagNames - An array of strings representing tags to remove
|
|
216
|
+
*/
|
|
217
|
+
async function removeTags(secretName, tagNames) {
|
|
218
|
+
let promiseError
|
|
219
|
+
const data = await client
|
|
220
|
+
.untagResource({
|
|
221
|
+
SecretId: secretName,
|
|
222
|
+
TagKeys: tagNames,
|
|
223
|
+
})
|
|
224
|
+
.promise()
|
|
225
|
+
.catch(error => {
|
|
226
|
+
promiseError = error
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
if (promiseError) handleAWSPromiseError(promiseError)
|
|
230
|
+
else return data
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Appends a set of tags to a secret
|
|
235
|
+
* @param {String} secretName - The friendly name of the secret
|
|
236
|
+
* @param {Object} tags - An array of objects representing tags to add
|
|
237
|
+
* Object structure:
|
|
238
|
+
* { Key: 'key', Value: 'value' }
|
|
239
|
+
*/
|
|
240
|
+
async function appendTags(secretName, tags) {
|
|
241
|
+
let promiseError
|
|
242
|
+
const data = await client
|
|
243
|
+
.tagResource({
|
|
244
|
+
SecretId: secretName,
|
|
245
|
+
Tags: tags,
|
|
246
|
+
})
|
|
247
|
+
.promise()
|
|
248
|
+
.catch(error => {
|
|
249
|
+
promiseError = error
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
if (promiseError) handleAWSPromiseError(promiseError)
|
|
253
|
+
else return data
|
|
254
|
+
}
|
|
255
|
+
|
|
167
256
|
/**
|
|
168
257
|
* Adds a client to the client list and does data validation
|
|
169
258
|
* @param {String} clientId - The ID of the client
|
|
@@ -172,14 +261,16 @@ async function editSecret(secretName, secretValue) {
|
|
|
172
261
|
* @param {String} region - The sub location of the organization's data in S3
|
|
173
262
|
* @param {Object} keyValuePairs - The secret to store for the client
|
|
174
263
|
*/
|
|
175
|
-
async function createClient(
|
|
264
|
+
async function createClient({
|
|
176
265
|
clientId,
|
|
177
266
|
clientName,
|
|
178
267
|
organization,
|
|
179
268
|
region,
|
|
180
269
|
keyValuePairs,
|
|
181
|
-
clientIdsSecretIdOverride = null
|
|
182
|
-
|
|
270
|
+
clientIdsSecretIdOverride = null,
|
|
271
|
+
isSelfServe = false,
|
|
272
|
+
isActiveOrganization = false,
|
|
273
|
+
}) {
|
|
183
274
|
if (!clientId || !clientName || !organization || !region)
|
|
184
275
|
throw new Error('Client ID, name, organization, and region are required.')
|
|
185
276
|
const regex = new RegExp('^[a-z0-9-_]*$')
|
|
@@ -203,7 +294,17 @@ async function createClient(
|
|
|
203
294
|
organization,
|
|
204
295
|
region,
|
|
205
296
|
...keyValuePairs,
|
|
206
|
-
})
|
|
297
|
+
}),
|
|
298
|
+
[
|
|
299
|
+
{
|
|
300
|
+
Key: 'self_serve',
|
|
301
|
+
Value: isSelfServe.toString(),
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
Key: 'is_active_organization',
|
|
305
|
+
Value: isActiveOrganization.toString(),
|
|
306
|
+
},
|
|
307
|
+
]
|
|
207
308
|
)
|
|
208
309
|
// add to client list
|
|
209
310
|
const editClientListResults = await editSecret(
|
|
@@ -225,14 +326,14 @@ async function createClient(
|
|
|
225
326
|
* @param {String} region - The sub location of the organization's data in S3
|
|
226
327
|
* @param {Object} keyValuePairs - The secret to store for the client
|
|
227
328
|
*/
|
|
228
|
-
async function setClient(
|
|
329
|
+
async function setClient({
|
|
229
330
|
clientId,
|
|
230
331
|
clientName,
|
|
231
332
|
organization,
|
|
232
333
|
region,
|
|
233
334
|
keyValuePairs,
|
|
234
|
-
clientIdsSecretIdOverride = null
|
|
235
|
-
) {
|
|
335
|
+
clientIdsSecretIdOverride = null,
|
|
336
|
+
}) {
|
|
236
337
|
if (!clientId || !clientName || !organization || !region)
|
|
237
338
|
throw new Error('Client ID, name, organization, and region are required.')
|
|
238
339
|
const regex = new RegExp('^[a-z0-9-_]*$')
|
|
@@ -271,12 +372,12 @@ async function setClient(
|
|
|
271
372
|
* @param {Object} keyValuePairsToAdd
|
|
272
373
|
* @param {Array} keysToDelete
|
|
273
374
|
*/
|
|
274
|
-
async function editClient(
|
|
375
|
+
async function editClient({
|
|
275
376
|
clientId,
|
|
276
377
|
keyValuePairsToAdd,
|
|
277
378
|
keysToDelete,
|
|
278
|
-
clientIdsSecretIdOverride
|
|
279
|
-
) {
|
|
379
|
+
clientIdsSecretIdOverride,
|
|
380
|
+
}) {
|
|
280
381
|
if (!clientId) throw new Error('Client ID is required.')
|
|
281
382
|
if (
|
|
282
383
|
keysToDelete.some(key =>
|
|
@@ -313,12 +414,36 @@ async function editClient(
|
|
|
313
414
|
return { editClientResults }
|
|
314
415
|
}
|
|
315
416
|
|
|
417
|
+
async function setClientActiveStatus({
|
|
418
|
+
clientId,
|
|
419
|
+
newActiveStatus,
|
|
420
|
+
clientIdsSecretIdOverride,
|
|
421
|
+
}) {
|
|
422
|
+
if (!clientId) throw new Error('Client ID is required.')
|
|
423
|
+
if (newActiveStatus === null || newActiveStatus === undefined)
|
|
424
|
+
throw new Error('New active status is required.')
|
|
425
|
+
if (typeof newActiveStatus !== 'boolean')
|
|
426
|
+
throw new Error('New active status must be a boolean')
|
|
427
|
+
// get client secret location
|
|
428
|
+
const currentClientList = await getClientSecretIds(clientIdsSecretIdOverride)
|
|
429
|
+
const clientSecretId = currentClientList[clientId]
|
|
430
|
+
await removeTags(clientSecretId, ['is_active_organization'])
|
|
431
|
+
await appendTags(clientSecretId, [
|
|
432
|
+
{
|
|
433
|
+
Key: 'is_active_organization',
|
|
434
|
+
Value: newActiveStatus.toString(),
|
|
435
|
+
},
|
|
436
|
+
])
|
|
437
|
+
}
|
|
438
|
+
|
|
316
439
|
module.exports = {
|
|
317
440
|
getClientSecretIds,
|
|
318
441
|
getClientSecret,
|
|
319
442
|
getAllClientSecrets,
|
|
320
443
|
getSecretValue,
|
|
444
|
+
getSecretTags,
|
|
321
445
|
createClient,
|
|
322
446
|
setClient,
|
|
323
447
|
editClient,
|
|
448
|
+
setClientActiveStatus,
|
|
324
449
|
}
|
package/package.json
CHANGED
package/test/aws.test.js
CHANGED
|
@@ -3,9 +3,11 @@ const {
|
|
|
3
3
|
getAllClientSecrets,
|
|
4
4
|
getClientSecretIds,
|
|
5
5
|
getSecretValue,
|
|
6
|
+
getSecretTags,
|
|
6
7
|
createClient,
|
|
7
8
|
setClient,
|
|
8
9
|
editClient,
|
|
10
|
+
setClientActiveStatus,
|
|
9
11
|
} = require('../app/utils/awsSecretsManager')
|
|
10
12
|
const { deleteClient } = require('../app/utils/awsSecretsManagerDeleteHelper')
|
|
11
13
|
const expect = require('chai').expect
|
|
@@ -25,33 +27,56 @@ describe('Test Secrets Manager Helper', async () => {
|
|
|
25
27
|
})
|
|
26
28
|
|
|
27
29
|
it('should create a new client', async () => {
|
|
28
|
-
const createClientResult = await createClient(
|
|
29
|
-
'test_client_id',
|
|
30
|
-
'test_client_name',
|
|
31
|
-
'test_organization',
|
|
32
|
-
'test_region',
|
|
33
|
-
{ test_secret_key: 'test_secret_value' },
|
|
34
|
-
clientIdsSecretId
|
|
35
|
-
|
|
30
|
+
const createClientResult = await createClient({
|
|
31
|
+
clientId: 'test_client_id',
|
|
32
|
+
clientName: 'test_client_name',
|
|
33
|
+
organization: 'test_organization',
|
|
34
|
+
region: 'test_region',
|
|
35
|
+
keyValuePairs: { test_secret_key: 'test_secret_value' },
|
|
36
|
+
clientIdsSecretIdOverride: clientIdsSecretId,
|
|
37
|
+
isSelfServe: true,
|
|
38
|
+
isActiveOrganization: true,
|
|
39
|
+
})
|
|
36
40
|
expect(createClientResult.createSecretResults.Name).to.equal(
|
|
37
41
|
'test_organization/ad_platforms/api'
|
|
38
42
|
)
|
|
39
43
|
|
|
44
|
+
const newClientTags = await getSecretTags('test_organization/ad_platforms/api')
|
|
45
|
+
const isActiveOrganizationTag = newClientTags.find(
|
|
46
|
+
tag => tag.Key === 'is_active_organization'
|
|
47
|
+
)
|
|
48
|
+
expect(isActiveOrganizationTag.Value === 'true')
|
|
49
|
+
|
|
40
50
|
const clientList = await getClientSecretIds(clientIdsSecretId)
|
|
41
51
|
expect(clientList['test_client_id']).to.equal(
|
|
42
52
|
'test_organization/ad_platforms/api'
|
|
43
53
|
)
|
|
44
54
|
})
|
|
45
55
|
|
|
46
|
-
it('should
|
|
47
|
-
await
|
|
48
|
-
'test_client_id',
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
it('should change the clients active status to false', async () => {
|
|
57
|
+
await setClientActiveStatus({
|
|
58
|
+
clientId: 'test_client_id',
|
|
59
|
+
newActiveStatus: false,
|
|
60
|
+
clientIdsSecretIdOverride: clientIdsSecretId,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const clientTags = await getSecretTags('test_organization/ad_platforms/api')
|
|
64
|
+
const isActiveOrganizationTag = clientTags.find(
|
|
65
|
+
tag => tag.Key === 'is_active_organization'
|
|
54
66
|
)
|
|
67
|
+
expect(isActiveOrganizationTag.Value === 'falsey')
|
|
68
|
+
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should set a client', async () => {
|
|
72
|
+
await setClient({
|
|
73
|
+
clientId: 'test_client_id',
|
|
74
|
+
clientName: 'test_client_name_2',
|
|
75
|
+
organization: 'test_organization',
|
|
76
|
+
region: 'test_region_new',
|
|
77
|
+
keyValuePairs: { test_secret_key_new: 'test_secret_value_new', to_delete: 'to_delete' },
|
|
78
|
+
clientIdsSecretIdOverride: clientIdsSecretId
|
|
79
|
+
})
|
|
55
80
|
|
|
56
81
|
const testSecret = await getSecretValue(
|
|
57
82
|
'test_organization/ad_platforms/api'
|
|
@@ -65,16 +90,16 @@ describe('Test Secrets Manager Helper', async () => {
|
|
|
65
90
|
})
|
|
66
91
|
|
|
67
92
|
it('should edit, add, and delete a client key/value secret', async () => {
|
|
68
|
-
await editClient(
|
|
69
|
-
'test_client_id',
|
|
70
|
-
{
|
|
93
|
+
await editClient({
|
|
94
|
+
clientId: 'test_client_id',
|
|
95
|
+
keyValuePairsToAdd: {
|
|
71
96
|
test_secret_key_new: 'edit',
|
|
72
97
|
new_key: 'add',
|
|
73
98
|
organization: 'test_organization_new'
|
|
74
99
|
},
|
|
75
|
-
['to_delete'],
|
|
76
|
-
clientIdsSecretId
|
|
77
|
-
)
|
|
100
|
+
keysToDelete: ['to_delete'],
|
|
101
|
+
clientIdsSecretIdOverride: clientIdsSecretId
|
|
102
|
+
})
|
|
78
103
|
|
|
79
104
|
const testSecret = await getSecretValue(
|
|
80
105
|
'test_organization/ad_platforms/api'
|