@wavo-cloud/aws-secrets-manager-helper 0.1.9 → 0.1.12

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.
@@ -1,7 +1,7 @@
1
- confiversion: 2
1
+ version: 2.1
2
+ orbs:
3
+ codecov: codecov/codecov@1.1.3
2
4
  jobs:
3
-
4
-
5
5
  test:
6
6
  docker:
7
7
  - image: circleci/node:12.13
@@ -10,12 +10,22 @@ jobs:
10
10
  - checkout
11
11
  - setup_remote_docker
12
12
 
13
+ - run:
14
+ name: Adding NPM credentials
15
+ command: |
16
+ if [ ! -z "${WAVO_CLOUD_READ_NPM_TOKEN}" ]; then
17
+ echo "//registry.npmjs.org/:_authToken=$WAVO_CLOUD_READ_NPM_TOKEN" > ~/.npmrc
18
+ else
19
+ echo "Error: WAVO_CLOUD_READ_NPM_TOKEN not found."
20
+ false
21
+ fi
22
+
13
23
  # Download and cache dependencies
14
24
  - restore_cache: &restore_cache
15
25
  keys:
16
- - v1-dependencies-{{ checksum "package.json" }}
17
- # fallback to using the latest cache if no exact match is found
18
- - v1-dependencies-
26
+ - v1-dependencies-{{ checksum "package.json" }}
27
+ # fallback to using the latest cache if no exact match is found
28
+ - v1-dependencies-
19
29
 
20
30
  - run: yarn install
21
31
 
@@ -23,7 +33,7 @@ jobs:
23
33
  paths:
24
34
  - node_modules
25
35
  key: v1-dependencies-{{ checksum "package.json" }}
26
-
36
+
27
37
  # run tests!
28
38
  - run:
29
39
  name: Run tests (if possible)
@@ -31,6 +41,9 @@ jobs:
31
41
  if ./node_modules/@wavo-cloud/generator-microservice/shared/util/test-checks.sh; then
32
42
  yarn ci-test
33
43
  fi
44
+ - codecov/upload:
45
+ file: '/home/circleci/project/coverage/coverage-final.json'
46
+ token: 91eab9ce-01c7-4429-b973-470e75b73de7
34
47
 
35
48
  module-push:
36
49
  docker:
@@ -38,7 +51,7 @@ jobs:
38
51
 
39
52
  steps:
40
53
  - checkout
41
-
54
+
42
55
  - restore_cache: *restore_cache
43
56
 
44
57
  - run:
@@ -52,18 +65,17 @@ jobs:
52
65
  echo "WAVO_CLOUD_WRITE_NPM_TOKEN not found. Skipping generator module push."
53
66
  fi
54
67
 
55
-
56
68
  workflows:
57
69
  version: 2
58
70
  test-and-deploy:
59
71
  jobs:
60
- - test:
72
+ - test:
61
73
  context: org-global
62
- - module-push:
74
+ - module-push:
63
75
  context: org-global
64
76
  requires:
65
77
  - test
66
-
78
+
67
79
  filters:
68
80
  branches:
69
81
  only: master
package/CODEOWNERS ADDED
@@ -0,0 +1 @@
1
+ * @Wavo/wavo-cloud-core
package/Dockerfile CHANGED
@@ -5,7 +5,8 @@ RUN mkdir -p /usr/local/src/cloud-app
5
5
  WORKDIR /usr/local/src/cloud-app
6
6
 
7
7
  # Add .npmrc, package.json & yarn.lock
8
- COPY .npmrc /usr/local/src/cloud-app/.npmrc
8
+ ARG WAVO_CLOUD_READ_NPM_TOKEN
9
+ RUN echo "//registry.npmjs.org/:_authToken=$WAVO_CLOUD_READ_NPM_TOKEN" > ~/.npmrc
9
10
  COPY package.json yarn.lock /usr/local/src/cloud-app/
10
11
 
11
12
  # Install modules with yarn
package/README.md CHANGED
@@ -1,3 +1,6 @@
1
+ [![CircleCI](https://circleci.com/gh/Wavo/wavo-cloud.aws-secrets-manager-helper.svg?style=svg&circle-token=96efda30ada01190ff50d3461793d0500ea88dab)](https://circleci.com/gh/Wavo/wavo-cloud.aws-secrets-manager-helper)
2
+ [![codecov](https://codecov.io/gh/Wavo/wavo-cloud.aws-secrets-manager-helper/branch/master/graph/badge.svg?token=JXYSD5035D)](https://codecov.io/gh/Wavo/wavo-cloud.aws-secrets-manager-helper)
3
+
1
4
  # wavo-cloud.aws-secrets-manager-helper
2
5
 
3
6
  > Wavo Cloud Infallible AWS Secrets Manager Helper
@@ -24,14 +27,15 @@ To get all client secrets call `getAllClientSecrets()`
24
27
  ## Important Commands
25
28
 
26
29
  ### Building the Dockerfile image
27
- $ yarn image-build
28
30
 
31
+ $ yarn image-build
29
32
 
30
33
  ### Prettify the Source
31
- $ yarn prettify
32
34
 
35
+ $ yarn prettify
33
36
 
34
37
  ### Start the Microservice
38
+
35
39
  When using this option, node is passed the `--inspect` flag so you can inspect your code with the debugger of your choice. If your microservice is a worker or if you need it to block and wait for the debugger to connect use the "debug mode" instead.
36
40
 
37
41
  $ yarn start
@@ -40,10 +44,9 @@ Dockerized version (using `docker-compose`):
40
44
 
41
45
  $ yarn docker-start
42
46
 
43
-
44
47
  ### Start the Microservice in Debug Mode
45
48
 
46
- Important: when you run the microservice in debug mode, it is going to be started with node's `--inspect-brk` flag.
49
+ Important: when you run the microservice in debug mode, it is going to be started with node's `--inspect-brk` flag.
47
50
  **This implies that the microservice will block and wait for a debugger to connect to port 5858 and resume code execution.**
48
51
 
49
52
  $ yarn debug
@@ -52,14 +55,13 @@ Dockerized version (using `docker-compose`):
52
55
 
53
56
  $ yarn docker-debug
54
57
 
55
-
56
58
  ### Running the Tests
57
59
 
58
- Important: when you run the microservice in debug mode, it is going to be started with node's `--inspect-brk` flag.
60
+ Important: when you run the microservice in debug mode, it is going to be started with node's `--inspect-brk` flag.
59
61
  **This implies that the microservice will block and wait for a debugger to connect to port 5858 and resume code execution.**
60
62
 
61
63
  $ yarn test
62
-
64
+
63
65
  Dockerized version (using `docker-compose`):
64
66
 
65
67
  $ yarn docker-test
@@ -161,7 +161,7 @@ async function getAllClientSecrets(
161
161
  * @param {String} secretDescription - The description of the secret
162
162
  * @param {String} secretValue - The JSON string secret value
163
163
  */
164
- async function createSecret(secretName, secretDescription, secretValue) {
164
+ async function createSecret(secretName, secretDescription, secretValue, tags) {
165
165
  let promiseError
166
166
  if (typeof secretValue !== 'string')
167
167
  throw new Error('Secret value must be a JSON string.')
@@ -175,6 +175,7 @@ async function createSecret(secretName, secretDescription, secretValue) {
175
175
  Name: secretName,
176
176
  Description: secretDescription,
177
177
  SecretString: secretValue,
178
+ Tags: tags,
178
179
  })
179
180
  .promise()
180
181
  .catch(error => {
@@ -208,6 +209,50 @@ async function editSecret(secretName, secretValue) {
208
209
  else return data
209
210
  }
210
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
+
211
256
  /**
212
257
  * Adds a client to the client list and does data validation
213
258
  * @param {String} clientId - The ID of the client
@@ -216,14 +261,16 @@ async function editSecret(secretName, secretValue) {
216
261
  * @param {String} region - The sub location of the organization's data in S3
217
262
  * @param {Object} keyValuePairs - The secret to store for the client
218
263
  */
219
- async function createClient(
264
+ async function createClient({
220
265
  clientId,
221
266
  clientName,
222
267
  organization,
223
268
  region,
224
269
  keyValuePairs,
225
- clientIdsSecretIdOverride = null
226
- ) {
270
+ clientIdsSecretIdOverride = null,
271
+ isSelfServe = false,
272
+ isActiveOrganization = false,
273
+ }) {
227
274
  if (!clientId || !clientName || !organization || !region)
228
275
  throw new Error('Client ID, name, organization, and region are required.')
229
276
  const regex = new RegExp('^[a-z0-9-_]*$')
@@ -247,7 +294,17 @@ async function createClient(
247
294
  organization,
248
295
  region,
249
296
  ...keyValuePairs,
250
- })
297
+ }),
298
+ [
299
+ {
300
+ Key: 'self_serve',
301
+ Value: isSelfServe.toString(),
302
+ },
303
+ {
304
+ Key: 'is_active_organization',
305
+ Value: isActiveOrganization.toString(),
306
+ },
307
+ ]
251
308
  )
252
309
  // add to client list
253
310
  const editClientListResults = await editSecret(
@@ -269,14 +326,14 @@ async function createClient(
269
326
  * @param {String} region - The sub location of the organization's data in S3
270
327
  * @param {Object} keyValuePairs - The secret to store for the client
271
328
  */
272
- async function setClient(
329
+ async function setClient({
273
330
  clientId,
274
331
  clientName,
275
332
  organization,
276
333
  region,
277
334
  keyValuePairs,
278
- clientIdsSecretIdOverride = null
279
- ) {
335
+ clientIdsSecretIdOverride = null,
336
+ }) {
280
337
  if (!clientId || !clientName || !organization || !region)
281
338
  throw new Error('Client ID, name, organization, and region are required.')
282
339
  const regex = new RegExp('^[a-z0-9-_]*$')
@@ -315,12 +372,12 @@ async function setClient(
315
372
  * @param {Object} keyValuePairsToAdd
316
373
  * @param {Array} keysToDelete
317
374
  */
318
- async function editClient(
375
+ async function editClient({
319
376
  clientId,
320
377
  keyValuePairsToAdd,
321
378
  keysToDelete,
322
- clientIdsSecretIdOverride
323
- ) {
379
+ clientIdsSecretIdOverride,
380
+ }) {
324
381
  if (!clientId) throw new Error('Client ID is required.')
325
382
  if (
326
383
  keysToDelete.some(key =>
@@ -357,6 +414,28 @@ async function editClient(
357
414
  return { editClientResults }
358
415
  }
359
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
+
360
439
  module.exports = {
361
440
  getClientSecretIds,
362
441
  getClientSecret,
@@ -366,4 +445,5 @@ module.exports = {
366
445
  createClient,
367
446
  setClient,
368
447
  editClient,
448
+ setClientActiveStatus,
369
449
  }
package/codecov.yml ADDED
@@ -0,0 +1,6 @@
1
+ comment:
2
+ layout: "reach, diff"
3
+ behavior: default
4
+ require_changes: true # if true: only post the comment if coverage changes
5
+ github_checks:
6
+ annotations: false
@@ -2,7 +2,10 @@ version: "3.3"
2
2
 
3
3
  services:
4
4
  app:
5
- build: .
5
+ build:
6
+ context: .
7
+ args:
8
+ WAVO_CLOUD_READ_NPM_TOKEN: ${WAVO_CLOUD_READ_NPM_TOKEN}
6
9
  ports:
7
10
  - "3000:3000"
8
11
  entrypoint: yarn test
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wavo-cloud/aws-secrets-manager-helper",
3
- "version": "0.1.9",
3
+ "version": "0.1.12",
4
4
  "description": "Wavo Cloud Infallible AWS Secrets Manager Helper",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -17,15 +17,15 @@
17
17
  "image-push": "./node_modules/@wavo-cloud/generator-microservice/shared/util/image-push.sh",
18
18
  "k8s-deploy": "./node_modules/@wavo-cloud/generator-microservice/shared/util/k8s-deploy.sh",
19
19
  "prettier": "node ./node_modules/prettier/bin-prettier.js --write --single-quote --print-width 120 --no-semi '**/*.js'",
20
- "test": "NODE_ENV=${NODE_ENV-test} node ./node_modules/mocha/bin/mocha --recursive --require mock-local-storage",
20
+ "test": "NODE_ENV=${NODE_ENV-test} node ./node_modules/nyc/bin/nyc --reporter=json ./node_modules/mocha/bin/mocha --recursive --require mock-local-storage",
21
21
  "docker-dev-up": "docker-compose -f ./docker-compose.dev.yml up",
22
22
  "docker-dev-down": "docker-compose -f ./docker-compose.dev.yml down",
23
23
  "docker-test": "docker-compose -f ./docker-compose.test.yml down; docker-compose -f ./docker-compose.test.yml build && docker-compose -f ./docker-compose.test.yml run --rm app; R=$?; docker-compose -f ./docker-compose.test.yml down; [ $R -eq 0 ]",
24
- "ci-test": "yarn docker-test"
24
+ "ci-test": "yarn test"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@wavo-cloud/eslint-config": "^0.0.9",
28
- "@wavo-cloud/generator-microservice": "^2.5.0",
28
+ "@wavo-cloud/generator-microservice": "^6.0.0",
29
29
  "chai": "^4.2.0",
30
30
  "eslint": "^7.6.0",
31
31
  "eslint-config-prettier": "^3.1.0",
@@ -35,7 +35,8 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "aws-sdk": "^2.713.0",
38
- "lodash": "^4.17.19"
38
+ "lodash": "^4.17.19",
39
+ "nyc": "^15.1.0"
39
40
  },
40
41
  "bugs": {
41
42
  "url": "https://github.com/Wavo/wavo-cloud.aws-secrets-manager-helper/issues"
@@ -0,0 +1,11 @@
1
+ ## JIRA Ticket
2
+
3
+ ## Description of change / fix
4
+
5
+ _Did you update the wiki/\*.md files for your change?_
6
+
7
+ ## How to test (optional)
8
+
9
+ ## Screenshots (optional)
10
+
11
+ ## Migration Guide (optional)
package/test/aws.test.js CHANGED
@@ -3,16 +3,39 @@ 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
12
14
 
13
15
  const clientIdsSecretId = 'wavo/self_serve/client_list_test'
14
16
 
17
+ /**
18
+ * In this test file, we sleep between calls
19
+ * This is because I presume that AWS is returning early optimistic API responses,
20
+ * and doing additional work behind our backs.
21
+ *
22
+ * Reasoning: if you run `yarn test; yarn test;` back to back tests, you will see
23
+ * an error from AWS complaining that we're running calls against a deleted resource.
24
+ * This implies that even after we get the "delete successful" response from AWS, they're
25
+ * still in the process of deleting the resource.
26
+ *
27
+ * Since we sleep for 2 seconds, I set the test timeouts to be 4s.
28
+ */
29
+ function sleep(ms) {
30
+ return new Promise(resolve => setTimeout(resolve, ms));
31
+ }
32
+
15
33
  describe('Test Secrets Manager Helper', async () => {
34
+ const randomString = Math.random().toString(36).substring(7);
35
+ const clientId = 'test_client_id_' + randomString
36
+ const organization = 'test_organization_' + randomString
37
+ const newOrganization = organization + '_new'
38
+
16
39
  /**
17
40
  * This function call tests all of the helper functions
18
41
  */
@@ -22,76 +45,104 @@ describe('Test Secrets Manager Helper', async () => {
22
45
  expect(clientSecrets.length).to.be.above(0)
23
46
  expect(clientSecrets[0].clientId).to.not.be.null
24
47
  expect(clientSecrets[0].secretId).to.not.be.null
25
- })
48
+ await sleep(2000)
49
+ }).timeout(4000)
26
50
 
27
51
  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
- )
52
+ const createClientResult = await createClient({
53
+ clientId,
54
+ clientName: 'test_client_name',
55
+ organization,
56
+ region: 'test_region',
57
+ keyValuePairs: { test_secret_key: 'test_secret_value' },
58
+ clientIdsSecretIdOverride: clientIdsSecretId,
59
+ isSelfServe: true,
60
+ isActiveOrganization: true,
61
+ })
36
62
  expect(createClientResult.createSecretResults.Name).to.equal(
37
- 'test_organization/ad_platforms/api'
63
+ `${organization}/ad_platforms/api`
38
64
  )
39
65
 
66
+ const newClientTags = await getSecretTags(`${organization}/ad_platforms/api`)
67
+ const isActiveOrganizationTag = newClientTags.find(
68
+ tag => tag.Key === 'is_active_organization'
69
+ )
70
+ expect(isActiveOrganizationTag.Value === 'true')
71
+
40
72
  const clientList = await getClientSecretIds(clientIdsSecretId)
41
- expect(clientList['test_client_id']).to.equal(
42
- 'test_organization/ad_platforms/api'
73
+ expect(clientList[clientId]).to.equal(
74
+ `${organization}/ad_platforms/api`
43
75
  )
44
- })
76
+ await sleep(2000)
77
+ }).timeout(4000)
45
78
 
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
79
+ it('should change the clients active status to false', async () => {
80
+ await setClientActiveStatus({
81
+ clientId,
82
+ newActiveStatus: false,
83
+ clientIdsSecretIdOverride: clientIdsSecretId,
84
+ })
85
+
86
+ const clientTags = await getSecretTags(`${organization}/ad_platforms/api`)
87
+ const isActiveOrganizationTag = clientTags.find(
88
+ tag => tag.Key === 'is_active_organization'
54
89
  )
90
+ expect(isActiveOrganizationTag.Value === 'falsey')
91
+
92
+ await sleep(2000)
93
+ }).timeout(4000)
94
+
95
+ it('should set a client', async () => {
96
+ await setClient({
97
+ clientId,
98
+ clientName: 'test_client_name_2',
99
+ organization,
100
+ region: 'test_region_new',
101
+ keyValuePairs: { test_secret_key_new: 'test_secret_value_new', to_delete: 'to_delete' },
102
+ clientIdsSecretIdOverride: clientIdsSecretId
103
+ })
55
104
 
56
105
  const testSecret = await getSecretValue(
57
- 'test_organization/ad_platforms/api'
106
+ `${organization}/ad_platforms/api`
58
107
  )
59
108
  expect(testSecret.client_name).to.equal('test_client_name_2')
60
- expect(testSecret.organization).to.equal('test_organization')
109
+ expect(testSecret.organization).to.equal(organization)
61
110
  expect(testSecret.region).to.equal('test_region_new')
62
111
  expect(testSecret.test_secret_key).to.equal(undefined)
63
112
  expect(testSecret.test_secret_key_new).to.equal('test_secret_value_new')
64
113
  expect(testSecret.to_delete).to.equal('to_delete')
65
- })
114
+ await sleep(2000)
115
+ }).timeout(4000)
66
116
 
67
117
  it('should edit, add, and delete a client key/value secret', async () => {
68
- await editClient(
69
- 'test_client_id',
70
- {
118
+ await editClient({
119
+ clientId,
120
+ keyValuePairsToAdd: {
71
121
  test_secret_key_new: 'edit',
72
122
  new_key: 'add',
73
- organization: 'test_organization_new'
123
+ organization: newOrganization
74
124
  },
75
- ['to_delete'],
76
- clientIdsSecretId
77
- )
125
+ keysToDelete: ['to_delete'],
126
+ clientIdsSecretIdOverride: clientIdsSecretId
127
+ })
78
128
 
79
129
  const testSecret = await getSecretValue(
80
- 'test_organization/ad_platforms/api'
130
+ `${organization}/ad_platforms/api`
81
131
  )
82
132
 
83
133
  expect(testSecret.client_name).to.equal('test_client_name_2')
84
- expect(testSecret.organization).to.equal('test_organization_new')
134
+ expect(testSecret.organization).to.equal(newOrganization)
85
135
  expect(testSecret.region).to.equal('test_region_new')
86
136
  expect(testSecret.test_secret_key_new).to.equal('edit')
87
137
  expect(testSecret.new_key).to.equal('add')
88
138
  expect(testSecret.to_delete).to.equal(undefined)
89
- })
139
+ await sleep(2000)
140
+ }).timeout(4000)
90
141
 
91
142
  it('should delete a client', async () => {
92
143
  //let clientList
93
144
  await deleteClient(
94
- 'test_client_id',
145
+ clientId,
95
146
  true,
96
147
  30,
97
148
  clientIdsSecretId,
@@ -101,11 +152,12 @@ describe('Test Secrets Manager Helper', async () => {
101
152
 
102
153
  try {
103
154
  await getSecretValue(
104
- 'test_organization/ad_platforms/api'
155
+ `${organization}/ad_platforms/api`
105
156
  )
106
- expect.fail("'test_organization/ad_platforms/api' should not be a valid secret value")
157
+ expect.fail(`'${organization}/ad_platforms/api' should not be a valid secret value`)
107
158
  } catch (error) {
108
- expect(clientList['test_client_id']).to.equal(undefined)
159
+ expect(clientList[clientId]).to.equal(undefined)
109
160
  }
110
- })
161
+ await sleep(2000)
162
+ }).timeout(4000)
111
163
  })