|
1 | 1 | /* eslint-env mocha */
|
2 | 2 | 'use strict'
|
3 | 3 |
|
4 |
| -process.env.IBM_CLOUD_SECRETS_MANAGER_API_AUTH_TYPE = 'noauth' |
5 |
| -process.env.IBM_CLOUD_SECRETS_MANAGER_API_APIKEY = 'iamkey' |
6 |
| - |
7 | 4 | const { expect } = require('chai')
|
8 | 5 | const sinon = require('sinon')
|
9 | 6 |
|
10 | 7 | const IbmCloudSecretsManagerBackend = require('./ibmcloud-secrets-manager-backend')
|
11 | 8 |
|
| 9 | +// In the unit test suite, these tests mock calls to IBM Secrets Manager, but mocking can be disabled during development to validate actual operation. |
| 10 | +// To diable mocking and enable real calls to an instance of Secrets Manager: |
| 11 | +// |
| 12 | +// 1. Set the three credential environment variables: |
| 13 | +// SECRETS_MANAGER_API_AUTH_TYPE=iam |
| 14 | +// SECRETS_MANAGER_API_ENDPOINT=https://{instance-id}.{region}.secrets-manager.appdomain.cloud |
| 15 | +// SECRETS_MANAGER_API_APIKEY={API key with Read+ReadSecrets access to the instance} |
| 16 | +// |
| 17 | +// 2. Add the three secrets described in the data object below to Secrets Manager. |
| 18 | +// When you add the IAM secret, be sure that "Reuse IAM credentials until lease expires" is checked. |
| 19 | +// |
| 20 | +// 3. Set the following three environment variables to the IDs of those secrets: |
| 21 | +// IBM_CLOUD_SECRETS_MANAGER_TEST_CREDS_ID |
| 22 | +// IBM_CLOUD_SECRETS_MANAGER_TEST_SECRET_ID |
| 23 | +// IBM_CLOUD_SECRETS_MANAGER_TEST_IAM_ID |
| 24 | +// |
| 25 | +// 4. Set the following environment variable to the API key generated as part of the IAM credential: |
| 26 | +// IBM_CLOUD_SECRETS_MANAGER_TEST_IAM_APIKEY |
| 27 | +// |
| 28 | +// Note: In the Secrets Manager UI, you can select "Show snippet" from the secret's overflow menu to show a curl command that will retrieve the value. |
| 29 | +// Or you can use the "ibmcloud sm secret" CLI command to handle authentication for you. |
| 30 | +// |
| 31 | +// You can switch back to mocking simply by unsetting SECRETS_MANAGER_API_AUTH_TYPE. |
| 32 | +// This makes it easy to switch back and forth between the two modes when writing new tests. |
| 33 | + |
| 34 | +const endpoint = process.env.IBM_CLOUD_SECRETS_MANAGER_API_ENDPOINT || 'https://fake.secrets-manager.appdomain.cloud' |
| 35 | + |
| 36 | +const data = { |
| 37 | + creds: { |
| 38 | + id: process.env.IBM_CLOUD_SECRETS_MANAGER_TEST_CREDS_ID || 'id1', |
| 39 | + name: 'test-creds', |
| 40 | + secretType: 'username_password', |
| 41 | + username: 'johndoe', |
| 42 | + password: 'p@ssw0rd' |
| 43 | + }, |
| 44 | + secret: { |
| 45 | + id: process.env.IBM_CLOUD_SECRETS_MANAGER_TEST_SECRET_ID || 'id2', |
| 46 | + name: 'test-secret', |
| 47 | + secretType: 'arbitrary', |
| 48 | + payload: 's3cr3t' |
| 49 | + }, |
| 50 | + iam: { |
| 51 | + id: process.env.IBM_CLOUD_SECRETS_MANAGER_TEST_IAM_ID || 'id3', |
| 52 | + name: 'test-iam', |
| 53 | + secretType: 'iam_credentials', |
| 54 | + apiKey: process.env.IBM_CLOUD_SECRETS_MANAGER_TEST_IAM_APIKEY || 'key' |
| 55 | + } |
| 56 | +} |
| 57 | + |
12 | 58 | describe('IbmCloudSecretsManagerBackend', () => {
|
| 59 | + const mock = !process.env.IBM_CLOUD_SECRETS_MANAGER_API_AUTH_TYPE |
13 | 60 | let loggerMock
|
14 |
| - let clientMock |
15 | 61 | let ibmCloudSecretsManagerBackend
|
16 | 62 |
|
17 |
| - const username = 'fakeUserName' |
18 |
| - const password = 'fakeSecretPropertyValue' |
19 |
| - const secret = { result: { resources: [{ secret_data: { password: password, username: username } }] } } |
20 |
| - const returnsecret = JSON.stringify({ password: password, username: username }) |
21 |
| - const key = 'username_password' |
22 |
| - |
23 | 63 | beforeEach(() => {
|
24 |
| - loggerMock = sinon.mock() |
25 |
| - loggerMock.info = sinon.stub() |
26 |
| - clientMock = sinon.mock() |
27 |
| - clientMock.getSecret = sinon.stub().returns(secret) |
| 64 | + if (mock) { |
| 65 | + process.env.IBM_CLOUD_SECRETS_MANAGER_API_AUTH_TYPE = 'noauth' |
| 66 | + } |
| 67 | + |
| 68 | + loggerMock = { |
| 69 | + info: sinon.stub() |
| 70 | + } |
28 | 71 |
|
29 | 72 | ibmCloudSecretsManagerBackend = new IbmCloudSecretsManagerBackend({
|
30 |
| - credential: { endpoint: 'https//sampleendpoint' }, |
| 73 | + credential: { endpoint }, |
31 | 74 | logger: loggerMock
|
32 | 75 | })
|
33 |
| - ibmCloudSecretsManagerBackend._secretsManagerClient = sinon.stub().returns(clientMock) |
34 | 76 | })
|
35 | 77 |
|
| 78 | + afterEach(() => { |
| 79 | + if (mock) { |
| 80 | + delete process.env.IBM_CLOUD_SECRETS_MANAGER_API_AUTH_TYPE |
| 81 | + ibmCloudSecretsManagerBackend._secretsManagerClient.restore() |
| 82 | + } |
| 83 | + }) |
| 84 | + |
| 85 | + function mockClient ({ list = [], get = {} }) { |
| 86 | + if (mock) { |
| 87 | + const client = { |
| 88 | + listAllSecrets: sinon.stub().resolves({ result: { resources: list } }), |
| 89 | + getSecret: sinon.stub().resolves({ result: { resources: [get] } }) |
| 90 | + } |
| 91 | + sinon.stub(ibmCloudSecretsManagerBackend, '_secretsManagerClient').returns(client) |
| 92 | + } |
| 93 | + } |
| 94 | + |
36 | 95 | describe('_get', () => {
|
37 |
| - it('returns secret property value', async () => { |
38 |
| - const specOptions = {} |
39 |
| - const keyOptions = { secretType: 'password' } |
40 |
| - const secretPropertyValue = await ibmCloudSecretsManagerBackend._get({ |
41 |
| - key: key, |
42 |
| - specOptions, |
43 |
| - keyOptions |
| 96 | + describe('with default spec options', () => { |
| 97 | + it('returns a username_password secret', async () => { |
| 98 | + const { id, secretType, username, password } = data.creds |
| 99 | + mockClient({ get: { secret_data: { password, username } } }) |
| 100 | + |
| 101 | + const secretPropertyValue = await ibmCloudSecretsManagerBackend._get({ |
| 102 | + key: id, |
| 103 | + specOptions: {}, |
| 104 | + keyOptions: { secretType } |
| 105 | + }) |
| 106 | + expect(secretPropertyValue).equals('{"password":"p@ssw0rd","username":"johndoe"}') |
| 107 | + }) |
| 108 | + |
| 109 | + it('returns an arbitrary secret', async () => { |
| 110 | + const { id, secretType, payload } = data.secret |
| 111 | + mockClient({ get: { secret_data: { payload } } }) |
| 112 | + |
| 113 | + const secretPropertyValue = await ibmCloudSecretsManagerBackend._get({ |
| 114 | + key: id, |
| 115 | + specOptions: {}, |
| 116 | + keyOptions: { secretType } |
| 117 | + }) |
| 118 | + expect(secretPropertyValue).equals('{"payload":"s3cr3t"}') |
44 | 119 | })
|
45 |
| - expect(secretPropertyValue).equals(returnsecret) |
| 120 | + |
| 121 | + it('returns an API key from an iam_credentials secret', async () => { |
| 122 | + const { id, secretType, apiKey } = data.iam |
| 123 | + mockClient({ get: { api_key: apiKey } }) |
| 124 | + |
| 125 | + const secretPropertyValue = await ibmCloudSecretsManagerBackend._get({ |
| 126 | + key: id, |
| 127 | + specOptions: {}, |
| 128 | + keyOptions: { secretType } |
| 129 | + }) |
| 130 | + expect(secretPropertyValue).equals(`"${apiKey}"`) |
| 131 | + }) |
| 132 | + }) |
| 133 | + |
| 134 | + describe('with key by name enabled', () => { |
| 135 | + it('returns a secret that matches the given name and type', async () => { |
| 136 | + const { name, secretType, username, password } = data.creds |
| 137 | + const list = [ |
| 138 | + { name, secret_type: 'arbitrary' }, |
| 139 | + { name, secret_type: secretType }, |
| 140 | + { name: 'test-creds2', secret_type: secretType } |
| 141 | + ] |
| 142 | + mockClient({ list, get: { secret_data: { password, username } } }) |
| 143 | + |
| 144 | + const secretPropertyValue = await ibmCloudSecretsManagerBackend._get({ |
| 145 | + key: name, |
| 146 | + specOptions: { keyByName: true }, |
| 147 | + keyOptions: { secretType } |
| 148 | + }) |
| 149 | + expect(secretPropertyValue).equals('{"password":"p@ssw0rd","username":"johndoe"}') |
| 150 | + }) |
| 151 | + |
| 152 | + it('throws if there is no secret with the given name and type', async () => { |
| 153 | + mockClient({ list: [] }) |
| 154 | + |
| 155 | + try { |
| 156 | + await ibmCloudSecretsManagerBackend._get({ |
| 157 | + key: 'test-missing', |
| 158 | + specOptions: { keyByName: true }, |
| 159 | + keyOptions: { secretType: 'username_password' } |
| 160 | + }) |
| 161 | + } catch (error) { |
| 162 | + expect(error).to.have.property('message').that.includes('No username_password secret') |
| 163 | + return |
| 164 | + } |
| 165 | + expect.fail('expected to throw an error') |
| 166 | + }) |
| 167 | + |
| 168 | + // Defensive test: this condition does not appear to be possible currently with a real Secrets Manager instance. |
| 169 | + if (mock) { |
| 170 | + it('throws if there are multiple secrets with the given name and type', async () => { |
| 171 | + const { name, secretType, username, password } = data.creds |
| 172 | + const list = [ |
| 173 | + { name, secret_type: secretType }, |
| 174 | + { name, secret_type: secretType } |
| 175 | + ] |
| 176 | + mockClient({ list, get: { secret_data: { password, username } } }) |
| 177 | + |
| 178 | + try { |
| 179 | + await ibmCloudSecretsManagerBackend._get({ |
| 180 | + key: name, |
| 181 | + specOptions: { keyByName: true }, |
| 182 | + keyOptions: { secretType } |
| 183 | + }) |
| 184 | + } catch (error) { |
| 185 | + expect(error).to.have.property('message').that.includes('Multiple username_password secrets') |
| 186 | + return |
| 187 | + } |
| 188 | + expect.fail('expected to throw an error') |
| 189 | + }) |
| 190 | + } |
46 | 191 | })
|
47 | 192 | })
|
48 | 193 | })
|
0 commit comments