Repository: godaddy/kubernetes-external-secrets
Pull request #431 (2020-07-13 15:28:44)

Improve documentation
This is a contribution to improve the Readme for the GCP backend.


Patch:
diff --git a/README.md b/README.md
index e2df364..8b64b7b 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:
@@ -451,7 +456,19 @@ The external secret will poll for changes to the secret according to the value s
 
 A service account is required to grant the controller access to pull secrets. 
 
-#### Workload Identity
+
+#### Add a secret
+
+Add your secret data to your backend using GCP SDK :
+```
+echo -n '{"value": "my-secret-value"}' |     gcloud secrets create my-gsm-secret-name     --replication-policy="automatic"     --data-file=-
+```
+If the secret needs to be updated :
+```
+echo -n '{"value": "my-secret-value-with-update"}' |     gcloud secrets versions add my-gsm-secret-name --data-file=-
+```
+
+##### Deploy kubernetes-external-secrets using Workload Identity
 
 Instructions are here: [Enable Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#enable_workload_identity_on_a_new_cluster).  To enable workload identity on an existing cluster (which is not covered in that document), first enable it on the cluster like so:
 
@@ -493,7 +510,7 @@ Create the policy binding:
 
     gcloud iam service-accounts add-iam-policy-binding --role roles/iam.workloadIdentityUser --member "serviceAccount:$CLUSTER_PROJECT.svc.id.goog[$SECRETS_NAMESPACE/kubernetes-external-secrets]" my-secrets-sa@$PROJECT.iam.gserviceaccount.com
 
-#### Loading from a Service Account Key
+##### Deploy kubernetes-external-secrets using a service account key
 
 Alternatively you can create and mount a kubernetes secret containing google service account credentials and set the GOOGLE_APPLICATION_CREDENTIALS env variable.
 
@@ -525,21 +542,22 @@ Uncomment GOOGLE_APPLICATION_CREDENTIALS in the values file as well as the follo
          
 This will mount the secret at /app/gcp-creds/gcp-creds.json and make it available via the GOOGLE_APPLICATION_CREDENTIALS environment variable.
 
-Once you have this installed, you can create an external secret with YAML like the following:
+#### Usage 
+Once you have kubernetes-external-secrets installed, you can create an external secret with YAML like the following:
 
 ```yml
 apiVersion: kubernetes-client.io/v1
 kind: ExternalSecret
 metadata:
-  name: gcp-secrets-manager-example
+  name: gcp-secrets-manager-example    # name of the k8s external secret and the k8s secret
 spec:
   backendType: gcpSecretsManager
   projectId: my-gsm-secret-project
   data:
-    - key: my-gsm-secret-name
-      name: my-kubernetes-secret-name
-      version: latest
-      property: value
+    - key: my-gsm-secret-name     # name of the GCP secret
+      name: my-kubernetes-secret-name   # key name in the k8s secret
+      version: latest    # version of the GCP secret
+      property: value      # name of the field in the GCP secret
 ```
 
 The field "key" is the name of the secret in Google Secret Manager.  The field "name" is the name of the Kubernetes secret this external secret will generate.  The metadata "name" field is the name of the external secret in Kubernetes.
@@ -606,4 +624,4 @@ Add secrets using the AWS cli (example)
 
 ```sh
 AWS_ACCESS_KEY_ID=foobar AWS_SECRET_ACCESS_KEY=foobar aws --region=us-west-2 --endpoint-url=http://localhost:4584 secretsmanager create-secret --name hello-service/password --secret-string "1234"
-```
\ No newline at end of file
+```
diff --git a/charts/kubernetes-external-secrets/values.yaml b/charts/kubernetes-external-secrets/values.yaml
index c30239b..7231551 100644
--- a/charts/kubernetes-external-secrets/values.yaml
+++ b/charts/kubernetes-external-secrets/values.yaml
@@ -16,6 +16,8 @@ env:
   AWS_DEFAULT_REGION: us-west-2
   POLLER_INTERVAL_MILLISECONDS: 10000  # Caution, setting this frequency may incur additional charges on some platforms
   LOG_LEVEL: info
+  # Print logs level as string ("info") rather than integer (30)
+  # USE_HUMAN_READABLE_LOG_LEVELS: true
   METRICS_PORT: 3001
   VAULT_ADDR: http://127.0.0.1:8200
 #  GOOGLE_APPLICATION_CREDENTIALS: /app/gcp-creds/gcp-creds.json
diff --git a/config/environment.js b/config/environment.js
index 6375c09..7379832 100644
--- a/config/environment.js
+++ b/config/environment.js
@@ -24,6 +24,8 @@ const pollerIntervalMilliseconds = process.env.POLLER_INTERVAL_MILLISECONDS
   ? Number(process.env.POLLER_INTERVAL_MILLISECONDS) : 10000
 
 const logLevel = process.env.LOG_LEVEL || 'info'
+const useHumanReadableLogLevels = 'USE_HUMAN_READABLE_LOG_LEVELS' in process.env
+
 const pollingDisabled = 'DISABLE_POLLING' in process.env
 
 const rolePermittedAnnotation = process.env.ROLE_PERMITTED_ANNOTATION || 'iam.amazonaws.com/permitted'
@@ -43,5 +45,6 @@ module.exports = {
   namingPermittedAnnotation,
   pollingDisabled,
   logLevel,
-  customResourceManagerDisabled
+  customResourceManagerDisabled,
+  useHumanReadableLogLevels
 }
diff --git a/config/index.js b/config/index.js
index 7843dd4..de5f5c1 100644
--- a/config/index.js
+++ b/config/index.js
@@ -35,7 +35,8 @@ const logger = pino({
   serializers: {
     err: pino.stdSerializers.err
   },
-  level: envConfig.logLevel
+  level: envConfig.logLevel,
+  useLevelLabels: envConfig.useHumanReadableLogLevels
 })
 
 const customResourceManager = new CustomResourceManager({
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')
+    })
   })
 })