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

Commit 90f01c5

Browse files
authored
feat: add support for dataFrom & fix: encoding of non-string values (#196)
* feat: add support for using dataFrom Adds support for using `dataFrom` to get all values from a secret backend ```yaml apiVersion: kubernetes-client.io/v1 kind: ExternalSecret metadata: name: postgres-credentials secretDescriptor: backendType: secretsManager dataFrom: - /development/postgres/credentials - /development/api/credentials data: - key: /development/auth/secret name: authSecret ```
1 parent 8c17819 commit 90f01c5

File tree

6 files changed

+281
-55
lines changed

6 files changed

+281
-55
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ language: node_js
33
matrix:
44
fast_finish: true
55
include:
6-
- node_js: '10'
6+
- node_js: '12'
77
# https://github.com/greenkeeperio/greenkeeper-lockfile#npm
88
before_install:
99
# package-lock.json was introduced in npm@5

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM node:10.15.1-alpine
1+
FROM node:12.13.0-alpine
22

33
ENV NODE_ENV production
44
ENV NPM_CONFIG_LOGLEVEL info

README.md

+34
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,40 @@ secretDescriptor:
243243
property: username
244244
```
245245

246+
alternatively you can use `dataFrom` and get all the values from hello-service/credentials:
247+
248+
```yml
249+
apiVersion: 'kubernetes-client.io/v1'
250+
kind: ExternalSecret
251+
metadata:
252+
name: hello-service
253+
secretDescriptor:
254+
backendType: secretsManager
255+
# optional: specify role to assume when retrieving the data
256+
roleArn: arn:aws:iam::123456789012:role/test-role
257+
dataFrom:
258+
- hello-service/credentials
259+
```
260+
261+
`data` and `dataFrom` can of course be combined, any naming conflicts will use the last defined, with `data` overriding `dataFrom`
262+
263+
```yml
264+
apiVersion: 'kubernetes-client.io/v1'
265+
kind: ExternalSecret
266+
metadata:
267+
name: hello-service
268+
secretDescriptor:
269+
backendType: secretsManager
270+
# optional: specify role to assume when retrieving the data
271+
roleArn: arn:aws:iam::123456789012:role/test-role
272+
dataFrom:
273+
- hello-service/credentials
274+
data:
275+
- key: hello-service/migration-credentials
276+
name: password
277+
property: password
278+
```
279+
246280
## Metrics
247281

248282
kubernetes-external-secrets exposes the following metrics over a prometheus endpoint:

lib/backends/kv-backend.js

+60-23
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,22 @@ class KVBackend extends AbstractBackend {
1515

1616
/**
1717
* Fetch Kubernetes secret property values.
18-
* @param {Object[]} secretProperties - Kubernetes secret properties.
19-
* @param {string} secretProperties[].key - Secret key in the backend.
20-
* @param {string} secretProperties[].name - Kubernetes Secret property name.
21-
* @param {string} secretProperties[].property - If the backend secret is an
18+
* @param {Object[]} data - Kubernetes secret properties.
19+
* @param {string} data[].key - Secret key in the backend.
20+
* @param {string} data[].name - Kubernetes Secret property name.
21+
* @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} secretProperties[].roleArn - If the client should assume a role before fetching the secret
23+
* @param {string} roleArn - If the client should assume a role before fetching the secret
2424
* @returns {Promise} Promise object representing secret property values.
2525
*/
26-
_fetchSecretPropertyValues ({ externalData, roleArn }) {
27-
return Promise.all(externalData.map(async secretProperty => {
26+
_fetchDataValues ({ data, roleArn }) {
27+
return Promise.all(data.map(async secretProperty => {
2828
this._logger.info(`fetching secret property ${secretProperty.name} with role: ${roleArn || 'no role set'}`)
29-
const value = await this._get({ secretKey: secretProperty.key, roleArn })
29+
const plainOrObjValue = await this._get({ secretKey: secretProperty.key, roleArn })
30+
const shouldParseValue = 'property' in secretProperty
3031

31-
if ('property' in secretProperty) {
32+
let value = plainOrObjValue
33+
if (shouldParseValue) {
3234
let parsedValue
3335
try {
3436
parsedValue = JSON.parse(value)
@@ -43,10 +45,30 @@ class KVBackend extends AbstractBackend {
4345
throw new Error(`Could not find property ${secretProperty.property} in ${secretProperty.key}`)
4446
}
4547

46-
return parsedValue[secretProperty.property]
48+
value = parsedValue[secretProperty.property]
4749
}
4850

49-
return value
51+
return { [secretProperty.name]: value }
52+
}))
53+
}
54+
55+
/**
56+
* Fetch Kubernetes secret property values.
57+
* @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
59+
* @returns {Promise} Promise object representing secret property values.
60+
*/
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 })
65+
66+
try {
67+
return JSON.parse(value)
68+
} catch (err) {
69+
this._logger.warn(`Failed to JSON.parse value for '${secretKey}',` +
70+
` please verify that your secret value is correctly formatted as JSON.`)
71+
}
5072
}))
5173
}
5274

@@ -62,18 +84,33 @@ class KVBackend extends AbstractBackend {
6284
* @param {SecretDescriptor} secretDescriptor - Kubernetes secret descriptor.
6385
* @returns {Promise} Promise object representing Kubernetes secret manifest data.
6486
*/
65-
async getSecretManifestData ({ secretDescriptor }) {
66-
const data = {}
67-
// Use secretDescriptor.properties to be backwards compatible.
68-
const externalData = secretDescriptor.data || secretDescriptor.properties
69-
const secretPropertyValues = await this._fetchSecretPropertyValues({
70-
externalData,
71-
roleArn: secretDescriptor.roleArn
72-
})
73-
externalData.forEach((secretProperty, index) => {
74-
data[secretProperty.name] = (Buffer.from(secretPropertyValues[index], 'utf8')).toString('base64')
75-
})
76-
return data
87+
async getSecretManifestData ({
88+
secretDescriptor: {
89+
// Use secretDescriptor.properties to be backwards compatible.
90+
properties = [],
91+
data = properties,
92+
dataFrom = [],
93+
roleArn
94+
}
95+
}) {
96+
const [dataFromValues, dataValues] = await Promise.all([
97+
this._fetchDataFromValues({ dataFrom, roleArn }),
98+
this._fetchDataValues({ data, roleArn })
99+
])
100+
101+
const plainValues = dataFromValues.concat(dataValues)
102+
.reduce((acc, parsedValue) => ({
103+
...acc,
104+
...parsedValue
105+
}), {})
106+
107+
const encodedEntries = Object.entries(plainValues)
108+
.map(([name, plainValue]) => [
109+
name,
110+
(Buffer.from(`${plainValue}`, 'utf8')).toString('base64')
111+
])
112+
113+
return Object.fromEntries(encodedEntries)
77114
}
78115
}
79116

0 commit comments

Comments
 (0)