Skip to content
This repository was archived by the owner on Jul 26, 2022. It is now read-only.

Commit 24421b9

Browse files
authored
fix: add dataFrom support to vault backend (refactor kv-backend) (#206)
* refactor: make kv backend more generic * refactor: vault to make use of kv backend, and support dataFrom
1 parent d2ebaeb commit 24421b9

8 files changed

+156
-142
lines changed

lib/backends/kv-backend.js

+25-22
Original file line numberDiff line numberDiff line change
@@ -20,62 +20,65 @@ class KVBackend extends AbstractBackend {
2020
* @param {string} data[].name - Kubernetes Secret property name.
2121
* @param {string} data[].property - If the backend secret is an
2222
* object, this is the property name of the value to use.
23-
* @param {string} roleArn - If the client should assume a role before fetching the secret
23+
* @param {Object} specOptions - Options set on spec level.
2424
* @returns {Promise} Promise object representing secret property values.
2525
*/
26-
_fetchDataValues ({ data, roleArn }) {
27-
return Promise.all(data.map(async secretProperty => {
28-
this._logger.info(`fetching secret property ${secretProperty.name} with role: ${roleArn || 'no role set'}`)
29-
const plainOrObjValue = await this._get({ secretKey: secretProperty.key, roleArn })
30-
const shouldParseValue = 'property' in secretProperty
26+
_fetchDataValues ({ data, specOptions }) {
27+
return Promise.all(data.map(async dataItem => {
28+
const { name, property = null, key, ...keyOptions } = dataItem
29+
const plainOrObjValue = await this._get({ key, keyOptions, specOptions })
30+
const shouldParseValue = 'property' in dataItem
3131

3232
let value = plainOrObjValue
3333
if (shouldParseValue) {
3434
let parsedValue
3535
try {
3636
parsedValue = JSON.parse(value)
3737
} catch (err) {
38-
this._logger.warn(`Failed to JSON.parse value for '${secretProperty.key}',` +
38+
this._logger.warn(`Failed to JSON.parse value for '${key}',` +
3939
` please verify that your secret value is correctly formatted as JSON.` +
40-
` To use plain text secret remove the 'property: ${secretProperty.property}'`)
40+
` To use plain text secret remove the 'property: ${property}'`)
4141
return
4242
}
4343

44-
if (!(secretProperty.property in parsedValue)) {
45-
throw new Error(`Could not find property ${secretProperty.property} in ${secretProperty.key}`)
44+
if (!(property in parsedValue)) {
45+
throw new Error(`Could not find property ${property} in ${key}`)
4646
}
4747

48-
value = parsedValue[secretProperty.property]
48+
value = parsedValue[property]
4949
}
5050

51-
return { [secretProperty.name]: value }
51+
return { [name]: value }
5252
}))
5353
}
5454

5555
/**
5656
* Fetch Kubernetes secret property values.
5757
* @param {string[]} dataFrom - Array of secret keys in the backend
58-
* @param {string} roleArn - If the client should assume a role before fetching the secret
58+
* @param {string} specOptions - Options set on spec level that might be interesting for the backend
5959
* @returns {Promise} Promise object representing secret property values.
6060
*/
61-
_fetchDataFromValues ({ dataFrom, roleArn }) {
62-
return Promise.all(dataFrom.map(async secretKey => {
63-
this._logger.info(`fetching secret ${secretKey} with role: ${roleArn || 'no role set'}`)
64-
const value = await this._get({ secretKey, roleArn })
61+
_fetchDataFromValues ({ dataFrom, specOptions }) {
62+
return Promise.all(dataFrom.map(async key => {
63+
const value = await this._get({ key, specOptions })
6564

6665
try {
6766
return JSON.parse(value)
6867
} catch (err) {
69-
this._logger.warn(`Failed to JSON.parse value for '${secretKey}',` +
68+
this._logger.warn(`Failed to JSON.parse value for '${key}',` +
7069
` please verify that your secret value is correctly formatted as JSON.`)
7170
}
7271
}))
7372
}
7473

7574
/**
7675
* Get a secret property value from Key Value backend.
76+
* @param {string} key - Secret key in the backend.
77+
* @param {string} keyOptions - Options for this specific key, eg version etc.
78+
* @param {string} specOptions - Options for this external secret, eg role
79+
* @returns {Promise} Promise object representing secret property values.
7780
*/
78-
_get () {
81+
_get ({ key, keyOptions, specOptions }) {
7982
throw new Error('_get not implemented')
8083
}
8184

@@ -90,12 +93,12 @@ class KVBackend extends AbstractBackend {
9093
properties = [],
9194
data = properties,
9295
dataFrom = [],
93-
roleArn
96+
...specOptions
9497
}
9598
}) {
9699
const [dataFromValues, dataValues] = await Promise.all([
97-
this._fetchDataFromValues({ dataFrom, roleArn }),
98-
this._fetchDataValues({ data, roleArn })
100+
this._fetchDataFromValues({ dataFrom, specOptions }),
101+
this._fetchDataValues({ data, specOptions })
99102
])
100103

101104
const plainValues = dataFromValues.concat(dataValues)

lib/backends/kv-backend.test.js

+71-52
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,19 @@ describe('kv-backend', () => {
3434
key: 'mocked-key',
3535
name: 'mocked-name',
3636
property: 'oops'
37-
}]
37+
}],
38+
specOptions: {}
3839
})
3940
expect.fail('Should not reach')
4041
} catch (err) {
4142
expect(err).to.be.an('error')
4243
}
44+
expect(kvBackend._get.calledOnce).to.equal(true)
45+
expect(kvBackend._get.getCall(0).args[0]).to.deep.equal({
46+
key: 'mocked-key',
47+
keyOptions: {},
48+
specOptions: {}
49+
})
4350
})
4451

4552
it('handles secrets values that are objects', async () => {
@@ -77,19 +84,22 @@ describe('kv-backend', () => {
7784
}, {
7885
key: 'fakePropertyKey2',
7986
name: 'fakePropertyName2'
80-
}]
87+
}],
88+
specOptions: {
89+
passMeAlong: true
90+
}
8191
})
8292

83-
expect(loggerMock.info.calledWith('fetching secret property fakePropertyName1 with role: no role set')).to.equal(true)
84-
expect(loggerMock.info.calledWith('fetching secret property fakePropertyName2 with role: no role set')).to.equal(true)
85-
expect(kvBackend._get.calledWith({
86-
secretKey: 'fakePropertyKey1',
87-
roleArn: undefined
88-
})).to.equal(true)
89-
expect(kvBackend._get.calledWith({
90-
secretKey: 'fakePropertyKey2',
91-
roleArn: undefined
92-
})).to.equal(true)
93+
expect(kvBackend._get.getCall(0).args[0]).to.deep.equal({
94+
key: 'fakePropertyKey1',
95+
keyOptions: {},
96+
specOptions: { passMeAlong: true }
97+
})
98+
expect(kvBackend._get.getCall(1).args[0]).to.deep.equal({
99+
key: 'fakePropertyKey2',
100+
keyOptions: {},
101+
specOptions: { passMeAlong: true }
102+
})
93103
expect(secretPropertyValues).deep.equals([{ fakePropertyName1: 'fakePropertyValue1' }, { fakePropertyName2: 'fakePropertyValue2' }])
94104
})
95105

@@ -105,19 +115,19 @@ describe('kv-backend', () => {
105115
key: 'fakePropertyKey2',
106116
name: 'fakePropertyName2'
107117
}],
108-
roleArn: 'secretDescriptiorRole'
118+
specOptions: { roleArn: 'secretDescriptiorRole' }
109119
})
110120

111-
expect(loggerMock.info.calledWith('fetching secret property fakePropertyName1 with role: secretDescriptiorRole')).to.equal(true)
112-
expect(loggerMock.info.calledWith('fetching secret property fakePropertyName2 with role: secretDescriptiorRole')).to.equal(true)
113-
expect(kvBackend._get.calledWith({
114-
secretKey: 'fakePropertyKey1',
115-
roleArn: 'secretDescriptiorRole'
116-
})).to.equal(true)
117-
expect(kvBackend._get.calledWith({
118-
secretKey: 'fakePropertyKey2',
119-
roleArn: 'secretDescriptiorRole'
120-
})).to.equal(true)
121+
expect(kvBackend._get.getCall(0).args[0]).to.deep.equal({
122+
key: 'fakePropertyKey1',
123+
keyOptions: {},
124+
specOptions: { roleArn: 'secretDescriptiorRole' }
125+
})
126+
expect(kvBackend._get.getCall(1).args[0]).to.deep.equal({
127+
key: 'fakePropertyKey2',
128+
keyOptions: {},
129+
specOptions: { roleArn: 'secretDescriptiorRole' }
130+
})
121131
expect(secretPropertyValues).deep.equals([{ fakePropertyName1: 'fakePropertyValue1' }, { fakePropertyName2: 'fakePropertyValue2' }])
122132
})
123133
})
@@ -147,33 +157,34 @@ describe('kv-backend', () => {
147157
kvBackend._get.onFirstCall().resolves('fakePropertyValue1')
148158

149159
const dataFromValues = await kvBackend._fetchDataFromValues({
150-
dataFrom: ['fakePropertyKey1']
160+
dataFrom: ['fakePropertyKey1'],
161+
specOptions: { passMeAlong: true }
151162
})
152163

153-
expect(kvBackend._get.calledWith({
154-
secretKey: 'fakePropertyKey1',
155-
roleArn: undefined
156-
})).to.equal(true)
164+
expect(kvBackend._get.getCall(0).args[0]).to.deep.equal({
165+
key: 'fakePropertyKey1',
166+
specOptions: { passMeAlong: true }
167+
})
157168
expect(dataFromValues).deep.equals([undefined])
158169
})
159170

160-
it('fetches secret property values using the specified role', async () => {
171+
it('fetches secret property values using the specified options', async () => {
161172
kvBackend._get.onFirstCall().resolves('{"fakePropertyName1":"fakePropertyValue1"}')
162173
kvBackend._get.onSecondCall().resolves('{"fakePropertyName2":"fakePropertyValue2"}')
163174

164175
const dataFromValues = await kvBackend._fetchDataFromValues({
165176
dataFrom: ['fakePropertyKey1', 'fakePropertyKey2'],
166-
roleArn: 'secretDescriptiorRole'
177+
specOptions: { roleArn: 'secretDescriptiorRole' }
167178
})
168179

169-
expect(kvBackend._get.calledWith({
170-
secretKey: 'fakePropertyKey1',
171-
roleArn: 'secretDescriptiorRole'
172-
})).to.equal(true)
173-
expect(kvBackend._get.calledWith({
174-
secretKey: 'fakePropertyKey2',
175-
roleArn: 'secretDescriptiorRole'
176-
})).to.equal(true)
180+
expect(kvBackend._get.getCall(0).args[0]).to.deep.equal({
181+
key: 'fakePropertyKey1',
182+
specOptions: { roleArn: 'secretDescriptiorRole' }
183+
})
184+
expect(kvBackend._get.getCall(1).args[0]).to.deep.equal({
185+
key: 'fakePropertyKey2',
186+
specOptions: { roleArn: 'secretDescriptiorRole' }
187+
})
177188
expect(dataFromValues).deep.equals([{ fakePropertyName1: 'fakePropertyValue1' }, { fakePropertyName2: 'fakePropertyValue2' }])
178189
})
179190
})
@@ -183,7 +194,7 @@ describe('kv-backend', () => {
183194
let error
184195

185196
try {
186-
kvBackend._get()
197+
kvBackend._get({})
187198
} catch (err) {
188199
error = err
189200
}
@@ -267,12 +278,12 @@ describe('kv-backend', () => {
267278
key: 'fakePropertyKey2',
268279
name: 'fakePropertyName2'
269280
}],
270-
roleArn: 'my-role'
281+
specOptions: { roleArn: 'my-role' }
271282
})).to.equal(true)
272283

273284
expect(kvBackend._fetchDataFromValues.calledWith({
274285
dataFrom: [],
275-
roleArn: 'my-role'
286+
specOptions: { roleArn: 'my-role' }
276287
})).to.equal(true)
277288
})
278289

@@ -290,24 +301,32 @@ describe('kv-backend', () => {
290301
],
291302
dataFrom: [
292303
'fakeDataFromKey1'
293-
]
304+
],
305+
roleArn: 'this-should-be-passed-along',
306+
magicSetting: 'this as well'
294307
}
295308
})
296309

297-
expect(kvBackend._fetchDataValues.calledWith({
310+
expect(kvBackend._fetchDataValues.getCall(0).args[0]).to.deep.equal({
298311
data: [{
299312
key: 'fakePropertyKey1',
300313
name: 'fakePropertyName1'
301314
}, {
302315
key: 'fakePropertyKey2',
303316
name: 'fakePropertyName2'
304317
}],
305-
roleArn: undefined
306-
})).to.equal(true)
318+
specOptions: {
319+
roleArn: 'this-should-be-passed-along',
320+
magicSetting: 'this as well'
321+
}
322+
})
307323

308324
expect(kvBackend._fetchDataFromValues.calledWith({
309325
dataFrom: ['fakeDataFromKey1'],
310-
roleArn: undefined
326+
specOptions: {
327+
roleArn: 'this-should-be-passed-along',
328+
magicSetting: 'this as well'
329+
}
311330
})).to.equal(true)
312331
})
313332

@@ -321,15 +340,15 @@ describe('kv-backend', () => {
321340
}
322341
})
323342

324-
expect(kvBackend._fetchDataValues.calledWith({
343+
expect(kvBackend._fetchDataValues.getCall(0).args[0]).to.deep.equal({
325344
data: [],
326-
roleArn: undefined
327-
})).to.equal(true)
345+
specOptions: { }
346+
})
328347

329-
expect(kvBackend._fetchDataFromValues.calledWith({
348+
expect(kvBackend._fetchDataFromValues.getCall(0).args[0]).to.deep.equal({
330349
dataFrom: ['fakeDataFromKey1', 'fakeDataFromKey2'],
331-
roleArn: undefined
332-
})).to.equal(true)
350+
specOptions: { }
351+
})
333352
})
334353
})
335354
})

lib/backends/secrets-manager-backend.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@ class SecretsManagerBackend extends KVBackend {
1818

1919
/**
2020
* Get secret property value from Secrets Manager.
21-
* @param {string} secretKey - Key used to store secret property value in Secrets Manager.
21+
* @param {string} key - Key used to store secret property value in Secrets Manager.
22+
* @param {object} keyOptions - Options for this specific key, eg version etc.
23+
* @param {object} specOptions - Options for this external secret, eg role
24+
* @param {string} specOptions.roleArn - IAM role arn to assume
2225
* @returns {Promise} Promise object representing secret property value.
2326
*/
24-
async _get ({ secretKey, roleArn }) {
27+
async _get ({ key, specOptions: { roleArn } }) {
28+
this._logger.info(`fetching secret property ${key} with role: ${roleArn || 'pods role'}`)
29+
2530
let client = this._client
2631
if (roleArn) {
2732
const res = await this._assumeRole({
@@ -36,7 +41,7 @@ class SecretsManagerBackend extends KVBackend {
3641
}
3742

3843
const data = await client
39-
.getSecretValue({ SecretId: secretKey })
44+
.getSecretValue({ SecretId: key })
4045
.promise()
4146

4247
if ('SecretBinary' in data) {
@@ -45,7 +50,7 @@ class SecretsManagerBackend extends KVBackend {
4550
return data.SecretString
4651
}
4752

48-
this._logger.error(`Unexpected data from Secrets Manager secret ${secretKey}`)
53+
this._logger.error(`Unexpected data from Secrets Manager secret ${key}`)
4954
return null
5055
}
5156
}

0 commit comments

Comments
 (0)