@wavo-cloud/aws-secrets-manager-helper 0.1.4 → 0.1.6
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/.circleci/config.yml +2 -2
- package/.eslintrc +6 -0
- package/Dockerfile +1 -1
- package/app/index.js +0 -1
- package/app/utils/awsSecretsManager.js +259 -39
- package/app/utils/awsSecretsManagerDeleteHelper.js +108 -0
- package/package.json +9 -4
- package/test/aws.test.js +95 -1
- package/yarn.lock +0 -4001
package/.circleci/config.yml
CHANGED
|
@@ -4,7 +4,7 @@ jobs:
|
|
|
4
4
|
|
|
5
5
|
test:
|
|
6
6
|
docker:
|
|
7
|
-
- image: circleci/node:
|
|
7
|
+
- image: circleci/node:12.13
|
|
8
8
|
|
|
9
9
|
steps:
|
|
10
10
|
- checkout
|
|
@@ -34,7 +34,7 @@ jobs:
|
|
|
34
34
|
|
|
35
35
|
module-push:
|
|
36
36
|
docker:
|
|
37
|
-
- image: circleci/node:
|
|
37
|
+
- image: circleci/node:12.13
|
|
38
38
|
|
|
39
39
|
steps:
|
|
40
40
|
- checkout
|
package/.eslintrc
ADDED
package/Dockerfile
CHANGED
package/app/index.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
const AWS = require('aws-sdk')
|
|
2
|
+
const { merge, omit } = require('lodash')
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
+
const secretsManagerRegion = 'us-east-1'
|
|
4
5
|
const clientIdsSecretId = 'wavo/self_serve/client_list'
|
|
5
6
|
const client = new AWS.SecretsManager({
|
|
6
|
-
region,
|
|
7
|
+
region: secretsManagerRegion,
|
|
7
8
|
})
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Attempts to parse secret
|
|
12
|
+
* @param {String} secretString - The stringified secret
|
|
13
|
+
*/
|
|
10
14
|
async function parseSecret(secretString) {
|
|
11
15
|
let parsedSecret = {}
|
|
12
16
|
try {
|
|
@@ -18,8 +22,35 @@ async function parseSecret(secretString) {
|
|
|
18
22
|
}
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
function handleAWSPromiseError(promiseError) {
|
|
26
|
+
if (promiseError.code === 'DecryptionFailureException') {
|
|
27
|
+
promiseError.message =
|
|
28
|
+
"Secrets Manager can't decrypt the protected secret text using the provided KMS key."
|
|
29
|
+
} else if (promiseError.code === 'InternalServiceErrorException') {
|
|
30
|
+
promiseError.message = 'An error occurred on the server side.'
|
|
31
|
+
} else if (promiseError.code === 'InvalidParameterException') {
|
|
32
|
+
promiseError.message = 'You provided an invalid value for a parameter.'
|
|
33
|
+
} else if (promiseError.code === 'InvalidRequestException') {
|
|
34
|
+
promiseError.message =
|
|
35
|
+
'You provided a parameter value that is not valid for the current state of the resource.'
|
|
36
|
+
} else if (promiseError.code === 'ResourceNotFoundException') {
|
|
37
|
+
promiseError.message = "We can't find the resource that you asked for."
|
|
38
|
+
} else if (promiseError.code === 'EncryptionFailureException') {
|
|
39
|
+
promiseError.message =
|
|
40
|
+
'We could not encrypt your secret. Your KMS/CMK key may be invalid.'
|
|
41
|
+
} else if (promiseError.code === 'LimitExceededException') {
|
|
42
|
+
promiseError.message = 'Your request would exceed our AWSSM internal limit.'
|
|
43
|
+
} else if (promiseError.code === 'ResourceExistsException') {
|
|
44
|
+
promiseError.message = 'A resource with this identifier already exists.'
|
|
45
|
+
}
|
|
46
|
+
throw promiseError
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Gets a defined secret value from aws secrets manager
|
|
51
|
+
* Returns JSON where JSON is the secret
|
|
52
|
+
* @param {String} secretId - The ID of an existing secret
|
|
53
|
+
*/
|
|
23
54
|
async function getSecretValue(secretId) {
|
|
24
55
|
let promiseError
|
|
25
56
|
const data = await client
|
|
@@ -30,24 +61,7 @@ async function getSecretValue(secretId) {
|
|
|
30
61
|
})
|
|
31
62
|
|
|
32
63
|
if (promiseError) {
|
|
33
|
-
|
|
34
|
-
promiseError.message =
|
|
35
|
-
"Secrets Manager can't decrypt the protected secret text using the provided KMS key."
|
|
36
|
-
throw promiseError
|
|
37
|
-
} else if (promiseError.code === 'InternalServiceErrorException') {
|
|
38
|
-
promiseError.message = 'An error occurred on the server side.'
|
|
39
|
-
throw promiseError
|
|
40
|
-
} else if (promiseError.code === 'InvalidParameterException') {
|
|
41
|
-
promiseError.message = 'You provided an invalid value for a parameter.'
|
|
42
|
-
throw promiseError
|
|
43
|
-
} else if (promiseError.code === 'InvalidRequestException') {
|
|
44
|
-
promiseError.message =
|
|
45
|
-
'You provided a parameter value that is not valid for the current state of the resource.'
|
|
46
|
-
throw promiseError
|
|
47
|
-
} else if (promiseError.code === 'ResourceNotFoundException') {
|
|
48
|
-
promiseError.message = "We can't find the resource that you asked for."
|
|
49
|
-
throw promiseError
|
|
50
|
-
}
|
|
64
|
+
handleAWSPromiseError(promiseError)
|
|
51
65
|
} else if ('SecretString' in data) {
|
|
52
66
|
return parseSecret(data.SecretString)
|
|
53
67
|
} else {
|
|
@@ -56,28 +70,32 @@ async function getSecretValue(secretId) {
|
|
|
56
70
|
}
|
|
57
71
|
}
|
|
58
72
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Gets the secret keying where client secrets are stored
|
|
75
|
+
* Returns JSON where JSON is the secret
|
|
76
|
+
*/
|
|
77
|
+
async function getClientSecretIds(clientIdsSecretIdOverride = null) {
|
|
78
|
+
return getSecretValue(clientIdsSecretIdOverride || clientIdsSecretId)
|
|
63
79
|
}
|
|
64
80
|
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Gets a specific client secret holding their API keys
|
|
83
|
+
* Returns JSON where JSON is the secret
|
|
84
|
+
* @param {String} clientId - The client ID
|
|
85
|
+
*/
|
|
67
86
|
async function getClientSecret(clientId) {
|
|
68
87
|
const clientSecretIds = await getClientSecretIds()
|
|
69
88
|
return getSecretValue(clientSecretIds[clientId])
|
|
70
89
|
}
|
|
71
90
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Gets all client secrets holding their API keys
|
|
93
|
+
* Returns [{ clientId: String, secretId: String, ...secret }]
|
|
94
|
+
* Skips all that are missing a region or organization
|
|
95
|
+
*/
|
|
75
96
|
async function getAllClientSecrets() {
|
|
76
97
|
const clientSecretIds = await getClientSecretIds()
|
|
77
|
-
if (!clientSecretIds)
|
|
78
|
-
console.log('No clients found')
|
|
79
|
-
return []
|
|
80
|
-
}
|
|
98
|
+
if (!clientSecretIds) return []
|
|
81
99
|
const parsedClientSecrets = await Promise.all(
|
|
82
100
|
Object.keys(clientSecretIds).map(async clientId => {
|
|
83
101
|
const result = await getSecretValue(clientSecretIds[clientId])
|
|
@@ -89,16 +107,218 @@ async function getAllClientSecrets() {
|
|
|
89
107
|
return parsedClientSecrets.filter(clientSecret => {
|
|
90
108
|
if (clientSecret.organization && clientSecret.region && !clientSecret.error)
|
|
91
109
|
return true
|
|
92
|
-
console.log(
|
|
93
|
-
`Client secret at ${clientSecret.secretId} is missing organization or region`
|
|
94
|
-
)
|
|
95
110
|
return false
|
|
96
111
|
})
|
|
97
112
|
}
|
|
98
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Creates a new secret
|
|
116
|
+
* @param {String} secretName - The friendly name of the secret
|
|
117
|
+
* @param {String} secretDescription - The description of the secret
|
|
118
|
+
* @param {String} secretValue - The JSON string secret value
|
|
119
|
+
*/
|
|
120
|
+
async function createSecret(secretName, secretDescription, secretValue) {
|
|
121
|
+
let promiseError
|
|
122
|
+
if (typeof secretValue !== 'string')
|
|
123
|
+
throw new Error('Secret value must be a JSON string.')
|
|
124
|
+
const regex = new RegExp('^[a-z0-9-_/]*$')
|
|
125
|
+
if (!regex.test(secretName))
|
|
126
|
+
throw new Error(
|
|
127
|
+
'The secret name can only include lowercase alphanumerics, underscores, and dashes.'
|
|
128
|
+
)
|
|
129
|
+
const data = await client
|
|
130
|
+
.createSecret({
|
|
131
|
+
Name: secretName,
|
|
132
|
+
Description: secretDescription,
|
|
133
|
+
SecretString: secretValue,
|
|
134
|
+
})
|
|
135
|
+
.promise()
|
|
136
|
+
.catch(error => {
|
|
137
|
+
promiseError = error
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
if (promiseError) handleAWSPromiseError(promiseError)
|
|
141
|
+
else return data
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Edits an existing secret
|
|
146
|
+
* @param {String} secretName - The friendly name of the secret
|
|
147
|
+
* @param {Object} secretValue - The new JSON string secret value
|
|
148
|
+
*/
|
|
149
|
+
async function editSecret(secretName, secretValue) {
|
|
150
|
+
let promiseError
|
|
151
|
+
if (typeof secretValue !== 'string')
|
|
152
|
+
throw new Error('Secret value must be a JSON string.')
|
|
153
|
+
const data = await client
|
|
154
|
+
.updateSecret({
|
|
155
|
+
SecretId: secretName,
|
|
156
|
+
SecretString: secretValue,
|
|
157
|
+
})
|
|
158
|
+
.promise()
|
|
159
|
+
.catch(error => {
|
|
160
|
+
promiseError = error
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
if (promiseError) handleAWSPromiseError(promiseError)
|
|
164
|
+
else return data
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Adds a client to the client list and does data validation
|
|
169
|
+
* @param {String} clientId - The ID of the client
|
|
170
|
+
* @param {String} clientName - The display name of the client
|
|
171
|
+
* @param {String} organization - The location of the organization's data in S3
|
|
172
|
+
* @param {String} region - The sub location of the organization's data in S3
|
|
173
|
+
* @param {Object} keyValuePairs - The secret to store for the client
|
|
174
|
+
*/
|
|
175
|
+
async function createClient(
|
|
176
|
+
clientId,
|
|
177
|
+
clientName,
|
|
178
|
+
organization,
|
|
179
|
+
region,
|
|
180
|
+
keyValuePairs,
|
|
181
|
+
clientIdsSecretIdOverride = null
|
|
182
|
+
) {
|
|
183
|
+
if (!clientId || !clientName || !organization || !region)
|
|
184
|
+
throw new Error('Client ID, name, organization, and region are required.')
|
|
185
|
+
const regex = new RegExp('^[a-z0-9-_]*$')
|
|
186
|
+
if (!regex.test(organization) || !regex.test(region))
|
|
187
|
+
throw new Error(
|
|
188
|
+
'Organization and region can only include lowercase alphanumerics, underscores, and dashes.'
|
|
189
|
+
)
|
|
190
|
+
// get client list secret
|
|
191
|
+
const currentClientList = await getClientSecretIds(
|
|
192
|
+
clientIdsSecretIdOverride || clientIdsSecretId
|
|
193
|
+
)
|
|
194
|
+
// if client is already in list, throw error
|
|
195
|
+
if (currentClientList[clientId])
|
|
196
|
+
throw new Error('A client with this ID already exists.')
|
|
197
|
+
// create client secret
|
|
198
|
+
const createSecretResults = await createSecret(
|
|
199
|
+
`${organization}/ad_platforms/api`,
|
|
200
|
+
`${clientName} Ad Platform API Keys`,
|
|
201
|
+
JSON.stringify({
|
|
202
|
+
client_name: clientName,
|
|
203
|
+
organization,
|
|
204
|
+
region,
|
|
205
|
+
...keyValuePairs,
|
|
206
|
+
})
|
|
207
|
+
)
|
|
208
|
+
// add to client list
|
|
209
|
+
const editClientListResults = await editSecret(
|
|
210
|
+
clientIdsSecretIdOverride || clientIdsSecretId,
|
|
211
|
+
JSON.stringify({
|
|
212
|
+
[clientId]: `${organization}/ad_platforms/api`,
|
|
213
|
+
...currentClientList,
|
|
214
|
+
})
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
return { createSecretResults, editClientListResults }
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Directly sets the secret value of an existing secret
|
|
222
|
+
* @param {String} clientId - The ID of the client
|
|
223
|
+
* @param {String} clientName - The display name of the client
|
|
224
|
+
* @param {String} organization - The location of the organization's data in S3
|
|
225
|
+
* @param {String} region - The sub location of the organization's data in S3
|
|
226
|
+
* @param {Object} keyValuePairs - The secret to store for the client
|
|
227
|
+
*/
|
|
228
|
+
async function setClient(
|
|
229
|
+
clientId,
|
|
230
|
+
clientName,
|
|
231
|
+
organization,
|
|
232
|
+
region,
|
|
233
|
+
keyValuePairs,
|
|
234
|
+
clientIdsSecretIdOverride = null
|
|
235
|
+
) {
|
|
236
|
+
if (!clientId || !clientName || !organization || !region)
|
|
237
|
+
throw new Error('Client ID, name, organization, and region are required.')
|
|
238
|
+
const regex = new RegExp('^[a-z0-9-_]*$')
|
|
239
|
+
if (
|
|
240
|
+
(organization && !regex.test(organization)) ||
|
|
241
|
+
(region && !regex.test(region))
|
|
242
|
+
)
|
|
243
|
+
throw new Error(
|
|
244
|
+
'Organization and region can only include lowercase alphanumerics, underscores, and dashes.'
|
|
245
|
+
)
|
|
246
|
+
// get client secret location
|
|
247
|
+
const currentClientList = await getClientSecretIds(clientIdsSecretIdOverride)
|
|
248
|
+
const clientSecretId = currentClientList[clientId]
|
|
249
|
+
// set client
|
|
250
|
+
const setClientResults = await editSecret(
|
|
251
|
+
clientSecretId,
|
|
252
|
+
JSON.stringify({
|
|
253
|
+
client_name: clientName,
|
|
254
|
+
organization,
|
|
255
|
+
region,
|
|
256
|
+
...keyValuePairs,
|
|
257
|
+
})
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
return { setClientResults }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Edits a secret value of an existing secret
|
|
265
|
+
* It merges keyValuePairsToAdd
|
|
266
|
+
* It omits keysToDelete
|
|
267
|
+
* @param {String} clientId - The ID of the client
|
|
268
|
+
* @param {String} clientName - The display name of the client
|
|
269
|
+
* @param {String} organization - The location of the organization's data in S3
|
|
270
|
+
* @param {String} region - The sub location of the organization's data in S3
|
|
271
|
+
* @param {Object} keyValuePairsToAdd
|
|
272
|
+
* @param {Array} keysToDelete
|
|
273
|
+
*/
|
|
274
|
+
async function editClient(
|
|
275
|
+
clientId,
|
|
276
|
+
keyValuePairsToAdd,
|
|
277
|
+
keysToDelete,
|
|
278
|
+
clientIdsSecretIdOverride
|
|
279
|
+
) {
|
|
280
|
+
if (!clientId) throw new Error('Client ID is required.')
|
|
281
|
+
if (
|
|
282
|
+
keysToDelete.some(key =>
|
|
283
|
+
['organization', 'region', 'client_name'].includes(key)
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
throw new Error('You cannot unset the organization, region, or client_name')
|
|
287
|
+
const regex = new RegExp('^[a-z0-9-_]*$')
|
|
288
|
+
const keysToAdd = Object.keys(keyValuePairsToAdd)
|
|
289
|
+
if (
|
|
290
|
+
(keysToAdd.includes('organization') &&
|
|
291
|
+
(!regex.test(keyValuePairsToAdd.organization) ||
|
|
292
|
+
!keyValuePairsToAdd.organization)) ||
|
|
293
|
+
(keysToAdd.includes('region') &&
|
|
294
|
+
(!regex.test(keyValuePairsToAdd.region) || !keyValuePairsToAdd.region)) ||
|
|
295
|
+
(keysToAdd.includes('client_name') && !keyValuePairsToAdd.client_name)
|
|
296
|
+
)
|
|
297
|
+
throw new Error(
|
|
298
|
+
'Organization, region, and client_name can only include lowercase alphanumerics, underscores, and dashes and must be non-null if included.'
|
|
299
|
+
)
|
|
300
|
+
// get client secret location
|
|
301
|
+
const currentClientList = await getClientSecretIds(clientIdsSecretIdOverride)
|
|
302
|
+
const clientSecretId = currentClientList[clientId]
|
|
303
|
+
const keyValuePairs = await getSecretValue(clientSecretId)
|
|
304
|
+
merge(keyValuePairs, keyValuePairsToAdd)
|
|
305
|
+
// edit client
|
|
306
|
+
const editClientResults = await editSecret(
|
|
307
|
+
clientSecretId,
|
|
308
|
+
JSON.stringify({
|
|
309
|
+
...omit(keyValuePairs, keysToDelete),
|
|
310
|
+
})
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
return { editClientResults }
|
|
314
|
+
}
|
|
315
|
+
|
|
99
316
|
module.exports = {
|
|
100
317
|
getClientSecretIds,
|
|
101
318
|
getClientSecret,
|
|
102
319
|
getAllClientSecrets,
|
|
103
320
|
getSecretValue,
|
|
321
|
+
createClient,
|
|
322
|
+
setClient,
|
|
323
|
+
editClient,
|
|
104
324
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const AWS = require('aws-sdk')
|
|
2
|
+
const { omit } = require('lodash')
|
|
3
|
+
const { getClientSecretIds } = require('./awsSecretsManager')
|
|
4
|
+
|
|
5
|
+
const secretsManagerRegion = 'us-east-1'
|
|
6
|
+
const clientIdsSecretId = 'wavo/self_serve/client_list'
|
|
7
|
+
const client = new AWS.SecretsManager({
|
|
8
|
+
region: secretsManagerRegion,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
function handleAWSPromiseError(promiseError) {
|
|
12
|
+
if (promiseError.code === 'DecryptionFailureException') {
|
|
13
|
+
promiseError.message =
|
|
14
|
+
"Secrets Manager can't decrypt the protected secret text using the provided KMS key."
|
|
15
|
+
} else if (promiseError.code === 'InternalServiceErrorException') {
|
|
16
|
+
promiseError.message = 'An error occurred on the server side.'
|
|
17
|
+
} else if (promiseError.code === 'InvalidParameterException') {
|
|
18
|
+
promiseError.message = 'You provided an invalid value for a parameter.'
|
|
19
|
+
} else if (promiseError.code === 'InvalidRequestException') {
|
|
20
|
+
promiseError.message =
|
|
21
|
+
'You provided a parameter value that is not valid for the current state of the resource.'
|
|
22
|
+
} else if (promiseError.code === 'ResourceNotFoundException') {
|
|
23
|
+
promiseError.message = "We can't find the resource that you asked for."
|
|
24
|
+
} else if (promiseError.code === 'EncryptionFailureException') {
|
|
25
|
+
promiseError.message =
|
|
26
|
+
'We could not encrypt your secret. Your KMS/CMK key may be invalid.'
|
|
27
|
+
} else if (promiseError.code === 'LimitExceededException') {
|
|
28
|
+
promiseError.message = 'Your request would exceed our AWSSM internal limit.'
|
|
29
|
+
} else if (promiseError.code === 'ResourceExistsException') {
|
|
30
|
+
promiseError.message = 'A resource with this identifier already exists.'
|
|
31
|
+
}
|
|
32
|
+
throw promiseError
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Edits an existing secret
|
|
37
|
+
* @param {String} secretName - The friendly name of the secret
|
|
38
|
+
* @param {Object} secretValue - The new JSON string secret value
|
|
39
|
+
*/
|
|
40
|
+
async function editSecret(secretName, secretValue) {
|
|
41
|
+
let promiseError
|
|
42
|
+
if (typeof secretValue !== 'string')
|
|
43
|
+
throw new Error('Secret value must be a JSON string.')
|
|
44
|
+
const data = await client
|
|
45
|
+
.updateSecret({
|
|
46
|
+
SecretId: secretName,
|
|
47
|
+
SecretString: secretValue,
|
|
48
|
+
})
|
|
49
|
+
.promise()
|
|
50
|
+
.catch(error => {
|
|
51
|
+
promiseError = error
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
if (promiseError) handleAWSPromiseError(promiseError)
|
|
55
|
+
else return data
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Delete a secret
|
|
60
|
+
* @param {String} secretId - The ID of the secret
|
|
61
|
+
* @param {Boolean} force - If true, the secret cannot be recovered
|
|
62
|
+
* @param {Number} recoveryWindow - The recovery window
|
|
63
|
+
*/
|
|
64
|
+
async function deleteSecret(secretId, force = false, recoveryWindow = 30) {
|
|
65
|
+
let promiseError
|
|
66
|
+
if (!secretId) throw new Error('Secret value must be a JSON string.')
|
|
67
|
+
|
|
68
|
+
const data = await client
|
|
69
|
+
.deleteSecret({
|
|
70
|
+
ForceDeleteWithoutRecovery: force,
|
|
71
|
+
RecoveryWindowInDays: force ? undefined : recoveryWindow,
|
|
72
|
+
SecretId: secretId,
|
|
73
|
+
})
|
|
74
|
+
.promise()
|
|
75
|
+
.catch(error => {
|
|
76
|
+
promiseError = error
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
if (promiseError) handleAWSPromiseError(promiseError)
|
|
80
|
+
else return data
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function deleteClient(
|
|
84
|
+
clientId,
|
|
85
|
+
force,
|
|
86
|
+
recoveryWindow,
|
|
87
|
+
clientIdsSecretIdOverride = null,
|
|
88
|
+
) {
|
|
89
|
+
if (!clientId) throw new Error('Client ID is required')
|
|
90
|
+
// get current client list
|
|
91
|
+
const currentClientList = await getClientSecretIds(clientIdsSecretIdOverride)
|
|
92
|
+
const secretId = currentClientList[clientId]
|
|
93
|
+
if (!secretId)
|
|
94
|
+
throw new Error('The client ID could not be found or is invalid')
|
|
95
|
+
// delete the client secret
|
|
96
|
+
const deleteSecretResult = await deleteSecret(secretId, force, recoveryWindow)
|
|
97
|
+
// remove them from client list
|
|
98
|
+
const editClientListResult = await editSecret(
|
|
99
|
+
clientIdsSecretIdOverride || clientIdsSecretId,
|
|
100
|
+
JSON.stringify(omit(currentClientList, [clientId]))
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return { deleteSecretResult, editClientListResult }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
deleteClient,
|
|
108
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wavo-cloud/aws-secrets-manager-helper",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Wavo Cloud Infallible AWS Secrets Manager Helper",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -24,16 +24,18 @@
|
|
|
24
24
|
"ci-test": "yarn docker-test"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
+
"@wavo-cloud/eslint-config": "^0.0.9",
|
|
27
28
|
"@wavo-cloud/generator-microservice": "^2.5.0",
|
|
28
29
|
"chai": "^4.2.0",
|
|
29
|
-
"eslint": "^
|
|
30
|
+
"eslint": "^7.6.0",
|
|
30
31
|
"eslint-config-prettier": "^3.1.0",
|
|
31
32
|
"mocha": "^5.2.0",
|
|
32
33
|
"mock-local-storage": "^1.1.12",
|
|
33
34
|
"prettier": "^1.14.3"
|
|
34
35
|
},
|
|
35
36
|
"dependencies": {
|
|
36
|
-
"aws-sdk": "^2.713.0"
|
|
37
|
+
"aws-sdk": "^2.713.0",
|
|
38
|
+
"lodash": "^4.17.19"
|
|
37
39
|
},
|
|
38
40
|
"bugs": {
|
|
39
41
|
"url": "https://github.com/Wavo/wavo-cloud.aws-secrets-manager-helper/issues"
|
|
@@ -47,5 +49,8 @@
|
|
|
47
49
|
"secrets",
|
|
48
50
|
"manager"
|
|
49
51
|
],
|
|
50
|
-
"author": "Zain Virani"
|
|
52
|
+
"author": "Zain Virani",
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=12.13"
|
|
55
|
+
}
|
|
51
56
|
}
|
package/test/aws.test.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
1
2
|
const {
|
|
2
3
|
getAllClientSecrets,
|
|
4
|
+
getClientSecretIds,
|
|
5
|
+
getSecretValue,
|
|
6
|
+
createClient,
|
|
7
|
+
setClient,
|
|
8
|
+
editClient,
|
|
3
9
|
} = require('../app/utils/awsSecretsManager')
|
|
10
|
+
const { deleteClient } = require('../app/utils/awsSecretsManagerDeleteHelper')
|
|
4
11
|
const expect = require('chai').expect
|
|
5
12
|
|
|
13
|
+
const clientIdsSecretId = 'wavo/self_serve/client_list_test'
|
|
14
|
+
|
|
6
15
|
describe('Test Secrets Manager Helper', async () => {
|
|
7
16
|
/**
|
|
8
17
|
* This function call tests all of the helper functions
|
|
@@ -14,4 +23,89 @@ describe('Test Secrets Manager Helper', async () => {
|
|
|
14
23
|
expect(clientSecrets[0].clientId).to.not.be.null
|
|
15
24
|
expect(clientSecrets[0].secretId).to.not.be.null
|
|
16
25
|
})
|
|
17
|
-
|
|
26
|
+
|
|
27
|
+
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
|
+
)
|
|
36
|
+
expect(createClientResult.createSecretResults.Name).to.equal(
|
|
37
|
+
'test_organization/ad_platforms/api'
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const clientList = await getClientSecretIds(clientIdsSecretId)
|
|
41
|
+
expect(clientList['test_client_id']).to.equal(
|
|
42
|
+
'test_organization/ad_platforms/api'
|
|
43
|
+
)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should set a client', async () => {
|
|
47
|
+
await setClient(
|
|
48
|
+
'test_client_id',
|
|
49
|
+
'test_client_name_2',
|
|
50
|
+
'test_organization',
|
|
51
|
+
'test_region_new',
|
|
52
|
+
{ test_secret_key_new: 'test_secret_value_new', to_delete: 'to_delete' },
|
|
53
|
+
clientIdsSecretId
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const testSecret = await getSecretValue(
|
|
57
|
+
'test_organization/ad_platforms/api'
|
|
58
|
+
)
|
|
59
|
+
expect(testSecret.client_name).to.equal('test_client_name_2')
|
|
60
|
+
expect(testSecret.organization).to.equal('test_organization')
|
|
61
|
+
expect(testSecret.region).to.equal('test_region_new')
|
|
62
|
+
expect(testSecret.test_secret_key).to.equal(undefined)
|
|
63
|
+
expect(testSecret.test_secret_key_new).to.equal('test_secret_value_new')
|
|
64
|
+
expect(testSecret.to_delete).to.equal('to_delete')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should edit, add, and delete a client key/value secret', async () => {
|
|
68
|
+
await editClient(
|
|
69
|
+
'test_client_id',
|
|
70
|
+
{
|
|
71
|
+
test_secret_key_new: 'edit',
|
|
72
|
+
new_key: 'add',
|
|
73
|
+
organization: 'test_organization_new'
|
|
74
|
+
},
|
|
75
|
+
['to_delete'],
|
|
76
|
+
clientIdsSecretId
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
const testSecret = await getSecretValue(
|
|
80
|
+
'test_organization/ad_platforms/api'
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
expect(testSecret.client_name).to.equal('test_client_name_2')
|
|
84
|
+
expect(testSecret.organization).to.equal('test_organization_new')
|
|
85
|
+
expect(testSecret.region).to.equal('test_region_new')
|
|
86
|
+
expect(testSecret.test_secret_key_new).to.equal('edit')
|
|
87
|
+
expect(testSecret.new_key).to.equal('add')
|
|
88
|
+
expect(testSecret.to_delete).to.equal(undefined)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should delete a client', async () => {
|
|
92
|
+
//let clientList
|
|
93
|
+
await deleteClient(
|
|
94
|
+
'test_client_id',
|
|
95
|
+
true,
|
|
96
|
+
30,
|
|
97
|
+
clientIdsSecretId,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
const clientList = await getClientSecretIds(clientIdsSecretId)
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
await getSecretValue(
|
|
104
|
+
'test_organization/ad_platforms/api'
|
|
105
|
+
)
|
|
106
|
+
expect.fail("'test_organization/ad_platforms/api' should not be a valid secret value")
|
|
107
|
+
} catch (error) {
|
|
108
|
+
expect(clientList['test_client_id']).to.equal(undefined)
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
})
|