Repository: godaddy/kubernetes-external-secrets
Pull request #436 (2020-07-14 14:18:03)

AWS Secrets Manager: Support versionId
This PR resolves https://github.com/godaddy/kubernetes-external-secrets/issues/435.

Added supporting versionId of AWS Secrets Manager.


Patch:
diff --git a/README.md b/README.md
index e2df364..09cef63 100644
--- a/README.md
+++ b/README.md
@@ -279,6 +279,11 @@ spec:
       # Version Stage in Secrets Manager
       versionStage: AWSPREVIOUS
       property: password_previous
+    - key: hello-service/credentials
+      name: password
+      # Version ID in Secrets Manager
+      versionId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+      property: password_versioned
 ```
 
 alternatively you can use `dataFrom` and get all the values from hello-service/credentials:
diff --git a/lib/backends/secrets-manager-backend.js b/lib/backends/secrets-manager-backend.js
index ecdae80..f7df15a 100644
--- a/lib/backends/secrets-manager-backend.js
+++ b/lib/backends/secrets-manager-backend.js
@@ -21,11 +21,12 @@ class SecretsManagerBackend extends KVBackend {
    * @param {string} key - Key used to store secret property value in Secrets Manager.
    * @param {object} keyOptions - Options for this specific key, eg version etc.
    * @param {string} keyOptions.versionStage - Version stage
+   * @param {string} keyOptions.versionId - Version ID
    * @param {object} specOptions - Options for this external secret, eg role
    * @param {string} specOptions.roleArn - IAM role arn to assume
    * @returns {Promise} Promise object representing secret property value.
    */
-  async _get ({ key, specOptions: { roleArn }, keyOptions: { versionStage = 'AWSCURRENT' } }) {
+  async _get ({ key, specOptions: { roleArn }, keyOptions: { versionStage = 'AWSCURRENT', versionId = null } }) {
     this._logger.info(`fetching secret property ${key} with role: ${roleArn || 'pods role'}`)
 
     let client = this._client
@@ -41,8 +42,15 @@ class SecretsManagerBackend extends KVBackend {
       })
     }
 
+    let params
+    if (versionId) {
+      params = { SecretId: key, VersionId: versionId }
+    } else {
+      params = { SecretId: key, VersionStage: versionStage }
+    }
+
     const data = await client
-      .getSecretValue({ SecretId: key, VersionStage: versionStage })
+      .getSecretValue(params)
       .promise()
 
     if ('SecretBinary' in data) {
diff --git a/lib/poller.js b/lib/poller.js
index f440d69..1e4e760 100644
--- a/lib/poller.js
+++ b/lib/poller.js
@@ -14,6 +14,7 @@ const merge = require('lodash.merge')
  * @param {string} properties[].property - If the backend secret is an
  *   object, this is the property name of the value to use.
  * @param {string} properties[].versionStage - If the backend supports versioning, eg secretsManager backend
+ * @param {string} properties[].versionId - If the backend supports versioning, eg secretsManager backend
  */
 
 /** Poller class. */


Test patch:
diff --git a/lib/backends/kv-backend.test.js b/lib/backends/kv-backend.test.js
index 62fb302..39b01ed 100644
--- a/lib/backends/kv-backend.test.js
+++ b/lib/backends/kv-backend.test.js
@@ -134,6 +134,7 @@ describe('kv-backend', () => {
     it('fetches secret property with version', async () => {
       kvBackend._get.onFirstCall().resolves('fakePropertyValue1')
       kvBackend._get.onSecondCall().resolves('fakePropertyValue2')
+      kvBackend._get.onThirdCall().resolves('fakePropertyValue3')
 
       const secretPropertyValues = await kvBackend._fetchDataValues({
         data: [{
@@ -143,6 +144,10 @@ describe('kv-backend', () => {
           key: 'fakePropertyKey2',
           versionStage: 'AWSPREVIOUS',
           name: 'fakePropertyName2'
+        }, {
+          key: 'fakePropertyKey3',
+          versionId: 'ea9ef8d7-ea54-4a3b-b24b-99510e8d7a3d',
+          name: 'fakePropertyName3'
         }],
         specOptions: {}
       })
@@ -157,7 +162,12 @@ describe('kv-backend', () => {
         keyOptions: { versionStage: 'AWSPREVIOUS' },
         specOptions: {}
       })
-      expect(secretPropertyValues).deep.equals([{ fakePropertyName1: 'fakePropertyValue1' }, { fakePropertyName2: 'fakePropertyValue2' }])
+      expect(kvBackend._get.getCall(2).args[0]).to.deep.equal({
+        key: 'fakePropertyKey3',
+        keyOptions: { versionId: 'ea9ef8d7-ea54-4a3b-b24b-99510e8d7a3d' },
+        specOptions: {}
+      })
+      expect(secretPropertyValues).deep.equals([{ fakePropertyName1: 'fakePropertyValue1' }, { fakePropertyName2: 'fakePropertyValue2' }, { fakePropertyName3: 'fakePropertyValue3' }])
     })
   })
 
diff --git a/lib/backends/secrets-manager-backend.test.js b/lib/backends/secrets-manager-backend.test.js
index 18fdd87..feae9f5 100644
--- a/lib/backends/secrets-manager-backend.test.js
+++ b/lib/backends/secrets-manager-backend.test.js
@@ -136,5 +136,27 @@ describe('SecretsManagerBackend', () => {
       expect(assumeRoleMock.callCount).equals(0)
       expect(secretPropertyValue).equals('fakeSecretPropertyValuePreviousVersion')
     })
+
+    it('returns secret property value with versionId', async () => {
+      getSecretValuePromise.promise.resolves({
+        SecretString: 'fakeSecretPropertyValueVersionId'
+      })
+
+      const secretPropertyValue = await secretsManagerBackend._get({
+        key: 'fakeSecretKey',
+        specOptions,
+        keyOptions: {
+          versionId: 'ea9ef8d7-ea54-4a3b-b24b-99510e8d7a3d'
+        }
+      })
+
+      expect(clientMock.getSecretValue.calledWith({
+        SecretId: 'fakeSecretKey',
+        VersionId: 'ea9ef8d7-ea54-4a3b-b24b-99510e8d7a3d'
+      })).to.equal(true)
+      expect(clientFactoryMock.getCall(0).args).deep.equals([])
+      expect(assumeRoleMock.callCount).equals(0)
+      expect(secretPropertyValue).equals('fakeSecretPropertyValueVersionId')
+    })
   })
 })