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

Commit 4193050

Browse files
authored
feat: add support for using either Vault k/v 1 or k/v 2 (#426)
1 parent 1b18c55 commit 4193050

File tree

7 files changed

+105
-15
lines changed

7 files changed

+105
-15
lines changed

charts/kubernetes-external-secrets/crds/kubernetes-client.io_externalsecrets_crd.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ spec:
5555
type: string
5656
vaultMountPoint:
5757
type: string
58+
kvVersion:
59+
description: Vault K/V version either 1 or 2, default = 2
60+
type: integer
61+
minimum: 1
62+
maximum: 2
5863
keyVaultName:
5964
type: string
6065
key:

examples/hello-service-external-secret-vault.yml

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ spec:
66
backendType: vault
77
vaultMountPoint: my-kubernetes-vault-mount-point
88
vaultRole: my-vault-role
9+
kvVersion: 2 # defaults to 2
910
data:
1011
- name: password
1112
key: secret/data/hello-service/password

examples/vault-kv1.yaml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: 'kubernetes-client.io/v1'
2+
kind: ExternalSecret
3+
metadata:
4+
name: database-credentials-from-kv1
5+
spec:
6+
backendType: vault
7+
vaultMountPoint: my-kubernetes-vault-mount-point
8+
vaultRole: my-vault-role
9+
kvVersion: 1
10+
data:
11+
- name: username
12+
key: kv/database
13+
property: db-username
14+
- name: password
15+
key: kv/database
16+
property: db-password

lib/backends/vault-backend.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ class VaultBackend extends KVBackend {
3333
* @param {object} specOptions - Options for this external secret, eg role
3434
* @param {string} specOptions.vaultMountPoint - mount point
3535
* @param {string} specOptions.vaultRole - role
36+
* @param {number} specOptions.kvVersion - K/V Version 1 or 2
3637
* @returns {Promise} Promise object representing secret property values.
3738
*/
38-
async _get ({ key, specOptions: { vaultMountPoint, vaultRole } }) {
39+
async _get ({ key, specOptions: { vaultMountPoint, vaultRole, kvVersion = 2 } }) {
3940
if (!this._client.token) {
4041
const jwt = this._fetchServiceAccountToken()
4142
this._logger.debug('fetching new token from vault')
@@ -53,7 +54,15 @@ class VaultBackend extends KVBackend {
5354
this._logger.debug(`reading secret key ${key} from vault`)
5455
const secretResponse = await this._client.read(key)
5556

56-
return JSON.stringify(secretResponse.data.data)
57+
if (kvVersion === 1) {
58+
return JSON.stringify(secretResponse.data)
59+
}
60+
61+
if (kvVersion === 2) {
62+
return JSON.stringify(secretResponse.data.data)
63+
}
64+
65+
throw new Error('Unknown "kvVersion" specified')
5766
}
5867
}
5968

lib/backends/vault-backend.test.js

+67-8
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,21 @@ describe('VaultBackend', () => {
1919
const role = 'fakeRole'
2020
const secretKey = 'fakeSecretKey'
2121
const secretValue = 'open, sesame'
22-
const quotedSecretValue = '"' + secretValue + '"'
22+
const secretData = { [secretKey]: secretValue }
23+
const quotedSecretValue = JSON.stringify(secretData)
2324
const quotedSecretValueAsBase64 = Buffer.from(quotedSecretValue).toString('base64')
2425
const jwt = 'this-is-a-jwt-token'
2526

27+
const kv1Secret = {
28+
data: secretData
29+
}
30+
31+
const kv2Secret = {
32+
data: {
33+
data: secretData
34+
}
35+
}
36+
2637
beforeEach(() => {
2738
clientMock = sinon.mock()
2839

@@ -34,11 +45,7 @@ describe('VaultBackend', () => {
3445

3546
describe('_get', () => {
3647
beforeEach(() => {
37-
clientMock.read = sinon.stub().returns({
38-
data: {
39-
data: secretValue
40-
}
41-
})
48+
clientMock.read = sinon.stub().returns(kv2Secret)
4249
clientMock.tokenRenewSelf = sinon.stub().returns(true)
4350
clientMock.kubernetesLogin = sinon.stub().returns({
4451
auth: {
@@ -51,7 +58,7 @@ describe('VaultBackend', () => {
5158
clientMock.token = undefined
5259
})
5360

54-
it('logs in and returns secret property value', async () => {
61+
it('logs in and returns secret property value - default', async () => {
5562
const secretPropertyValue = await vaultBackend._get({
5663
specOptions: {
5764
vaultMountPoint: mountPoint,
@@ -74,6 +81,58 @@ describe('VaultBackend', () => {
7481
expect(secretPropertyValue).equals(quotedSecretValue)
7582
})
7683

84+
it('logs in and returns secret property value - kv version 1', async () => {
85+
clientMock.read = sinon.stub().returns(kv1Secret)
86+
87+
const secretPropertyValue = await vaultBackend._get({
88+
specOptions: {
89+
vaultMountPoint: mountPoint,
90+
vaultRole: role,
91+
kvVersion: 1
92+
},
93+
key: secretKey
94+
})
95+
96+
// First, we log into Vault...
97+
sinon.assert.calledWith(clientMock.kubernetesLogin, {
98+
mount_point: mountPoint,
99+
role: role,
100+
jwt: jwt
101+
})
102+
103+
// ... then we fetch the secret ...
104+
sinon.assert.calledWith(clientMock.read, secretKey)
105+
106+
// ... and expect to get its proper value
107+
expect(secretPropertyValue).equals(quotedSecretValue)
108+
})
109+
110+
it('logs in and returns secret property value - kv version 2', async () => {
111+
clientMock.read = sinon.stub().returns(kv2Secret)
112+
113+
const secretPropertyValue = await vaultBackend._get({
114+
specOptions: {
115+
vaultMountPoint: mountPoint,
116+
vaultRole: role,
117+
kvVersion: 2
118+
},
119+
key: secretKey
120+
})
121+
122+
// First, we log into Vault...
123+
sinon.assert.calledWith(clientMock.kubernetesLogin, {
124+
mount_point: mountPoint,
125+
role: role,
126+
jwt: jwt
127+
})
128+
129+
// ... then we fetch the secret ...
130+
sinon.assert.calledWith(clientMock.read, secretKey)
131+
132+
// ... and expect to get its proper value
133+
expect(secretPropertyValue).equals(quotedSecretValue)
134+
})
135+
77136
it('returns secret property value after renewing token if a token exists', async () => {
78137
clientMock.token = 'an-existing-token'
79138

@@ -101,7 +160,7 @@ describe('VaultBackend', () => {
101160

102161
describe('getSecretManifestData', () => {
103162
beforeEach(() => {
104-
clientMock.read = sinon.stub().returns({ data: { data: secretValue } })
163+
clientMock.read = sinon.stub().returns(kv2Secret)
105164
clientMock.tokenRenewSelf = sinon.stub().returns(true)
106165
clientMock.kubernetesLogin = sinon.stub().returns({ auth: { client_token: '1234' } })
107166

package-lock.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,16 @@
3434
"@alicloud/kms20160120": "^1.1.0",
3535
"@azure/identity": "^1.0.2",
3636
"@azure/keyvault-secrets": "^4.0.2",
37-
"aws-sdk": "^2.628.0",
3837
"@google-cloud/secret-manager": "^1.2.1",
38+
"aws-sdk": "^2.628.0",
3939
"express": "^4.17.1",
4040
"js-yaml": "^3.13.1",
4141
"json-stream": "^1.0.0",
4242
"kubernetes-client": "^9.0.0",
4343
"lodash.clonedeep": "^4.5.0",
4444
"lodash.merge": "^4.6.2",
4545
"make-promises-safe": "^5.1.0",
46-
"node-vault": "^0.9.8",
46+
"node-vault": "^0.9.18",
4747
"pino": "^6.0.0",
4848
"prom-client": "^12.0.0"
4949
},

0 commit comments

Comments
 (0)