Skip to content

Commit

Permalink
feat: added the option to enforce namespace annotations (external-sec…
Browse files Browse the repository at this point in the history
…rets#448)

* added the option to enforce namespace annotations

* Update lib/poller.js

Co-authored-by: Markus Maga <[email protected]>

* Update config/environment.js

Co-authored-by: Markus Maga <[email protected]>

Co-authored-by: Markus Maga <[email protected]>
  • Loading branch information
omerlh and Flydiverny authored Jul 26, 2020
1 parent 605a815 commit 1517333
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 2 deletions.
4 changes: 3 additions & 1 deletion bin/daemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ const {
pollerIntervalMilliseconds,
pollingDisabled,
rolePermittedAnnotation,
namingPermittedAnnotation
namingPermittedAnnotation,
enforceNamespaceAnnotation
} = require('../config')

async function main () {
Expand All @@ -49,6 +50,7 @@ async function main () {
pollerIntervalMilliseconds,
rolePermittedAnnotation,
namingPermittedAnnotation,
enforceNamespaceAnnotation,
customResourceManifest,
pollingDisabled,
logger
Expand Down
2 changes: 2 additions & 0 deletions config/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const pollingDisabled = 'DISABLE_POLLING' in process.env

const rolePermittedAnnotation = process.env.ROLE_PERMITTED_ANNOTATION || 'iam.amazonaws.com/permitted'
const namingPermittedAnnotation = process.env.NAMING_PERMITTED_ANNOTATION || 'externalsecrets.kubernetes-client.io/permitted-key-name'
const enforceNamespaceAnnotation = 'ENFORCE_NAMESPACE_ANNOTATIONS' in process.env || false

const metricsPort = process.env.METRICS_PORT || 3001

Expand All @@ -44,6 +45,7 @@ module.exports = {
metricsPort,
rolePermittedAnnotation,
namingPermittedAnnotation,
enforceNamespaceAnnotation,
pollingDisabled,
logLevel,
customResourceManagerDisabled,
Expand Down
3 changes: 3 additions & 0 deletions lib/poller-factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class PollerFactory {
rolePermittedAnnotation,
namingPermittedAnnotation,
customResourceManifest,
enforceNamespaceAnnotation,
pollingDisabled,
logger
}) {
Expand All @@ -32,6 +33,7 @@ class PollerFactory {
this._customResourceManifest = customResourceManifest
this._rolePermittedAnnotation = rolePermittedAnnotation
this._namingPermittedAnnotation = namingPermittedAnnotation
this._enforceNamespaceAnnotation = enforceNamespaceAnnotation
this._pollingDisabled = pollingDisabled
}

Expand All @@ -49,6 +51,7 @@ class PollerFactory {
customResourceManifest: this._customResourceManifest,
rolePermittedAnnotation: this._rolePermittedAnnotation,
namingPermittedAnnotation: this._namingPermittedAnnotation,
enforceNamespaceAnnotation: this._enforceNamespaceAnnotation,
pollingDisabled: this._pollingDisabled,
externalSecret
})
Expand Down
23 changes: 22 additions & 1 deletion lib/poller.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class Poller {
* @param {Object} customResourceManifest - CRD manifest
* @param {Object} externalSecret - ExternalSecret manifest.
* @param {string} rolePermittedAnnotation - namespace annotation that defines which roles can be assumed within this namespace
* @param {string} namingPermittedAnnotation - namespace annotation that defines which keys can be assumed within this namespace
* @param {string} enforceNamespaceAnnotation - should enforce namespace annotations
* @param {Object} metrics - Metrics client.
*/
constructor ({
Expand All @@ -40,6 +42,7 @@ class Poller {
customResourceManifest,
rolePermittedAnnotation,
namingPermittedAnnotation,
enforceNamespaceAnnotation,
pollingDisabled,
externalSecret
}) {
Expand All @@ -53,6 +56,7 @@ class Poller {
this._rolePermittedAnnotation = rolePermittedAnnotation
this._namingPermittedAnnotation = namingPermittedAnnotation
this._customResourceManifest = customResourceManifest
this._enforceNamespaceAnnotation = enforceNamespaceAnnotation

this._externalSecret = externalSecret
this._spec = externalSecret.spec || externalSecret.secretDescriptor
Expand Down Expand Up @@ -197,6 +201,8 @@ class Poller {
let reason = ''

if (!namespace.metadata.annotations) {
allowed = !this._enforceNamespaceAnnotation
reason = this._enforceNamespaceAnnotation ? 'Namespace annotation is required' : ''
return {
allowed, reason
}
Expand All @@ -213,6 +219,14 @@ class Poller {
reNaming = new RegExp(namingConvention)
}

if (!namingConvention && this._enforceNamespaceAnnotation) {
allowed = false
reason = `Missing required annotation ${this._namingPermittedAnnotation} on namespace ${namespace.metadata.name}`
return {
allowed, reason
}
}

// Testing data property
if (namingConvention && externalData) {
externalData.forEach((secretProperty, index) => {
Expand Down Expand Up @@ -242,14 +256,21 @@ class Poller {

// 2. testing assume role if configured

const role = descriptor.roleArn
const role = descriptor.roleArn || descriptor.vaultRole

if (!role) {
return {
allowed, reason
}
}

if (!namespace.metadata.annotations[this._rolePermittedAnnotation] && this._enforceNamespaceAnnotation) {
allowed = false
reason = `Missing required annotation ${this._rolePermittedAnnotation} on namespace ${namespace.metadata.name}`
return {
allowed, reason
}
}
// an empty annotation value allows access to all roles
const re = new RegExp(namespace.metadata.annotations[this._rolePermittedAnnotation])

Expand Down
122 changes: 122 additions & 0 deletions lib/poller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,128 @@ describe('Poller', () => {
]
},
permitted: true
},
{
// test regex
ns: { metadata: { annotations: { [namingPermittedAnnotation]: '.*', [rolePermittedAnnotation]: 'a' } } },
descriptor: {
data: [
{ key: 'whatever', name: 'somethingelse' }
],
roleArn: 'b'
},
permitted: false
},
{
// test regex
ns: { metadata: { annotations: { [namingPermittedAnnotation]: '.*', [rolePermittedAnnotation]: 'a' } } },
descriptor: {
data: [
{ key: 'whatever', name: 'somethingelse' }
],
vaultRole: 'b'
},
permitted: false
},
{
// test regex
ns: { metadata: { annotations: { [namingPermittedAnnotation]: '.*', [rolePermittedAnnotation]: 'a' } } },
descriptor: {
data: [
{ key: 'whatever', name: 'somethingelse' }
],
roleArn: 'a'
},
permitted: true
},
{
// test regex
ns: { metadata: { annotations: { [namingPermittedAnnotation]: '.*', [rolePermittedAnnotation]: 'a' } } },
descriptor: {
data: [
{ key: 'whatever', name: 'somethingelse' }
],
vaultRole: 'a'
},
permitted: true
}
]

for (let i = 0; i < testcases.length; i++) {
const testcase = testcases[i]
const verdict = poller._isPermitted(testcase.ns, testcase.descriptor)
expect(verdict.allowed, `test case ${i + 1}`).to.equal(testcase.permitted)
}
})
})
describe('namespace annotation enforcement', () => {
let poller
beforeEach(() => {
poller = new Poller({
backends: {
fakeBackendType: backendMock
},
metrics: metricsMock,
intervalMilliseconds: 5000,
kubeClient: kubeClientMock,
logger: loggerMock,
externalSecret: fakeExternalSecret,
rolePermittedAnnotation,
namingPermittedAnnotation,
enforceNamespaceAnnotation: true,
customResourceManifest: fakeCustomResourceManifest
})
})

it('should enforce namespace annotations`', () => {
const testcases = [
{
// no annotations at all
ns: { metadata: {} },
descriptor: {},
permitted: false
},
{
// empty name annotation
ns: { metadata: { annotations: { [namingPermittedAnnotation]: '' } } },
descriptor: {
dataFrom: ['test']
},
permitted: false
},
{
// empty role annotation
ns: { metadata: { annotations: { [rolePermittedAnnotation]: '' } } },
descriptor: {
dataFrom: ['test']
},
permitted: false
},
{
// missing role annotation
ns: { metadata: { annotations: { [namingPermittedAnnotation]: 'a' } } },
descriptor: {
dataFrom: ['a'],
roleArn: 'a'
},
permitted: false
},
{
// empty role annotation
ns: { metadata: { annotations: { [namingPermittedAnnotation]: 'a', [rolePermittedAnnotation]: '' } } },
descriptor: {
dataFrom: ['a'],
roleArn: 'a'
},
permitted: false
},
{
// all required annotations
ns: { metadata: { annotations: { [namingPermittedAnnotation]: 'a', [rolePermittedAnnotation]: 'b' } } },
descriptor: {
dataFrom: ['a']
},
permitted: true
}
]

Expand Down

0 comments on commit 1517333

Please sign in to comment.