Repository: godaddy/kubernetes-external-secrets
Pull request #409 (2020-06-19 12:10:32)

feat: allow permitted-key-name to be provided as list
While documentation says that `permitted-key-name` can be provided as regular expression
```
kind: Namespace
metadata:
  name: core-namespace
  annotations:
    # annotation key is configurable
    externalsecrets.kubernetes-client.io/permitted-key-name: "/dev/cluster1/core-namespace/.*"
```
it would be nice (clearer ?) if it can support _list_ of regular expressions
```
kind: Namespace
metadata:
  name: core-namespace
  annotations:
    # annotation key is configurable
    externalsecrets.kubernetes-client.io/permitted-key-name:
      - "/dev/cluster1/core-namespace/.*"
      - "/common/.*"
```

nbendafi-yseop: This PR needs #411 for tests to pass

Patch:
diff --git a/config/index.js b/config/index.js
index 978a812..7843dd4 100644
--- a/config/index.js
+++ b/config/index.js
@@ -66,7 +66,7 @@ const vaultOptions = {
 // Include the Vault Namespace header if we have provided it as an env var.
 // See: https://github.com/kr1sp1n/node-vault/pull/137#issuecomment-585309687
 if (envConfig.vaultNamespace) {
-  vaultOptions.headers = {
+  vaultOptions.requestOptions.headers = {
     'X-VAULT-NAMESPACE': envConfig.vaultNamespace
   }
 }
diff --git a/lib/poller.js b/lib/poller.js
index ac6a336..f440d69 100644
--- a/lib/poller.js
+++ b/lib/poller.js
@@ -205,11 +205,16 @@ class Poller {
 
     const externalData = descriptor.data || descriptor.properties
     const namingConvention = namespace.metadata.annotations[this._namingPermittedAnnotation]
+    let reNaming = new RegExp()
+    if (Array.isArray(namingConvention)) {
+      reNaming = new RegExp(namingConvention.join('|'))
+    } else {
+      reNaming = new RegExp(namingConvention)
+    }
 
     // Testing data property
     if (namingConvention && externalData) {
       externalData.forEach((secretProperty, index) => {
-        const reNaming = new RegExp(namingConvention)
         if (!reNaming.test(secretProperty.key)) {
           allowed = false
           reason = `key name ${secretProperty.key} does not match naming convention ${namingConvention}`
@@ -224,7 +229,6 @@ class Poller {
     const externalDataFrom = descriptor.dataFrom
     if (namingConvention && externalDataFrom) {
       externalDataFrom.forEach((secretProperty, index) => {
-        const reNaming = new RegExp(namingConvention)
         if (!reNaming.test(secretProperty)) {
           allowed = false
           reason = `key name ${secretProperty} does not match naming convention ${namingConvention}`


Test patch:
diff --git a/e2e/tests/secrets-manager.test.js b/e2e/tests/secrets-manager.test.js
index 24c978c..043ee63 100644
--- a/e2e/tests/secrets-manager.test.js
+++ b/e2e/tests/secrets-manager.test.js
@@ -142,65 +142,132 @@ describe('secretsmanager', async () => {
         body: {
           metadata: {
             annotations: {
-              'iam.amazonaws.com/permitted': '.*'
+              'iam.amazonaws.com/permitted': '.*',
+              'externalsecrets.kubernetes-client.io/permitted-key-name': '.*'
             }
           }
         }
       })
     })
 
-    it('should not pull from secretsmanager', async () => {
-      let result = await createSecret({
-        Name: `e2e/${uuid}/tls/permitted`,
-        SecretString: '{"crt":"foo","key":"bar"}'
-      }).catch(err => {
-        expect(err).to.equal(null)
+    describe('assuming role', async () => {
+      it('should not pull from secretsmanager', async () => {
+        let result = await createSecret({
+          Name: `e2e/${uuid}/tls/permitted`,
+          SecretString: '{"crt":"foo","key":"bar"}'
+        }).catch(err => {
+          expect(err).to.equal(null)
+        })
+
+        result = await kubeClient
+          .apis[customResourceManifest.spec.group]
+          .v1.namespaces('default')[customResourceManifest.spec.names.plural]
+          .post({
+            body: {
+              apiVersion: 'kubernetes-client.io/v1',
+              kind: 'ExternalSecret',
+              metadata: {
+                name: `e2e-secretmanager-permitted-tls-${uuid}`
+              },
+              spec: {
+                backendType: 'secretsManager',
+                type: 'kubernetes.io/tls',
+                // this should not be allowed
+                roleArn: 'let-me-be-root',
+                data: [
+                  {
+                    key: `e2e/${uuid}/tls/permitted`,
+                    property: 'crt',
+                    name: 'tls.crt'
+                  },
+                  {
+                    key: `e2e/${uuid}/tls/permitted`,
+                    property: 'key',
+                    name: 'tls.key'
+                  }
+                ]
+              }
+            }
+          })
+
+        expect(result).to.not.equal(undefined)
+        expect(result.statusCode).to.equal(201)
+
+        const secret = await waitForSecret('default', `e2e-secretmanager-permitted-tls-${uuid}`)
+        expect(secret).to.equal(undefined)
+
+        result = await kubeClient
+          .apis[customResourceManifest.spec.group]
+          .v1.namespaces('default')
+          .externalsecrets(`e2e-secretmanager-permitted-tls-${uuid}`)
+          .get()
+        expect(result).to.not.equal(undefined)
+        expect(result.body.status.status).to.contain('namespace does not allow to assume role let-me-be-root')
       })
+    })
 
-      result = await kubeClient
-        .apis[customResourceManifest.spec.group]
-        .v1.namespaces('default')[customResourceManifest.spec.names.plural]
-        .post({
+    describe('enforcing naming convention', async () => {
+      it('should not pull from secretsmanager', async () => {
+        await kubeClient.api.v1.namespaces('default').patch({
           body: {
-            apiVersion: 'kubernetes-client.io/v1',
-            kind: 'ExternalSecret',
             metadata: {
-              name: `e2e-secretmanager-permitted-tls-${uuid}`
-            },
-            spec: {
-              backendType: 'secretsManager',
-              type: 'kubernetes.io/tls',
-              // this should not be allowed
-              roleArn: 'let-me-be-root',
-              data: [
-                {
-                  key: `e2e/${uuid}/tls/permitted`,
-                  property: 'crt',
-                  name: 'tls.crt'
-                },
-                {
-                  key: `e2e/${uuid}/tls/permitted`,
-                  property: 'key',
-                  name: 'tls.key'
-                }
-              ]
+              annotations: {
+                'iam.amazonaws.com/permitted': '.*',
+                'externalsecrets.kubernetes-client.io/permitted-key-name': '/permitted/path/.*'
+              }
             }
           }
         })
 
-      expect(result).to.not.equal(undefined)
-      expect(result.statusCode).to.equal(201)
+        let result = await createSecret({
+          Name: `e2e/${uuid}/another_credentials`,
+          SecretString: '{"username":"foo","password":"bar"}'
+        }).catch(err => {
+          expect(err).to.equal(null)
+        })
+
+        result = await kubeClient
+          .apis[customResourceManifest.spec.group]
+          .v1.namespaces('default')[customResourceManifest.spec.names.plural]
+          .post({
+            body: {
+              apiVersion: 'kubernetes-client.io/v1',
+              kind: 'ExternalSecret',
+              metadata: {
+                name: `e2e-secretmanager-permitted-key-${uuid}`
+              },
+              spec: {
+                backendType: 'secretsManager',
+                data: [
+                  {
+                    key: `e2e/${uuid}/another_credentials`,
+                    property: 'password',
+                    name: 'password'
+                  },
+                  {
+                    key: `e2e/${uuid}/another_credentials`,
+                    property: 'username',
+                    name: 'username'
+                  }
+                ]
+              }
+            }
+          })
+
+        expect(result).to.not.equal(undefined)
+        expect(result.statusCode).to.equal(201)
 
-      const secret = await waitForSecret('default', `e2e-secretmanager-permitted-tls-${uuid}`)
-      expect(secret).to.equal(undefined)
+        const secret = await waitForSecret('default', `e2e-secretmanager-permitted-key-${uuid}`)
+        expect(secret).to.equal(undefined)
 
-      result = await kubeClient
-        .apis[customResourceManifest.spec.group]
-        .v1.namespaces('default')
-        .externalsecrets(`e2e-secretmanager-permitted-tls-${uuid}`)
-        .get()
-      expect(result).to.not.equal(undefined)
-      expect(result.body.status.status).to.contain('namespace does not allow to assume role let-me-be-root')
+        result = await kubeClient
+          .apis[customResourceManifest.spec.group]
+          .v1.namespaces('default')
+          .externalsecrets(`e2e-secretmanager-permitted-key-${uuid}`)
+          .get()
+        expect(result).to.not.equal(undefined)
+        expect(result.body.status.status).to.contain(`key name e2e/${uuid}/another_credentials does not match naming convention /permitted/path/.*`)
+      })
     })
   })
 })
diff --git a/lib/poller.test.js b/lib/poller.test.js
index 47df018..d097dc0 100644
--- a/lib/poller.test.js
+++ b/lib/poller.test.js
@@ -876,6 +876,41 @@ describe('Poller', () => {
             ]
           },
           permitted: false
+        },
+        {
+          // test multiple regex data
+          ns: { metadata: { annotations: { [namingPermittedAnnotation]: ['dev/team-a/.*', 'common/.*'] } } },
+          descriptor: {
+            data: [
+              { key: 'dev/team-a/ok-secret', name: 'somethingelse' },
+              { key: 'common/generic-secret', name: 'genericsecret' }
+            ]
+          },
+          permitted: true
+        },
+        {
+          // test multiple regex data
+          ns: { metadata: { annotations: { [namingPermittedAnnotation]: ['dev/team-a/.*', 'common/.*'] } } },
+          descriptor: {
+            data: [
+              { key: 'dev/team-b/nok-secret', name: 'somethingelse' },
+              { key: 'common/generic-secret', name: 'genericsecret' }
+            ]
+          },
+          permitted: false
+        },
+        {
+          // test multiple regex data
+          ns: { metadata: { annotations: { [namingPermittedAnnotation]: ['dev/team-b/.*', 'common/.*'] } } },
+          descriptor: {
+            data: [
+              { key: 'common/generic-secret', name: 'genericsecret' }
+            ],
+            dataFrom: [
+              'common/generic-secret'
+            ]
+          },
+          permitted: true
         }
       ]