Skip to content

Commit d0c28c0

Browse files
committed
feat: allows excluded namespaces to be configurable
Users can configure their own list of namespaces that kubernetes monitor will ignore. This will overwrite the list of Kubernetes internal namespaces which are excluded by default
1 parent 08ce126 commit d0c28c0

File tree

9 files changed

+147
-34
lines changed

9 files changed

+147
-34
lines changed

snyk-monitor/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,20 @@ helm upgrade --install snyk-monitor snyk-charts/snyk-monitor \
180180
--set psp.enabled=true
181181
```
182182

183+
## Configuring excluded namespaces ##
184+
185+
By default, `snyk-monitor` does not scan containers that are internal to Kubernetes, in the following namespaces:
186+
* `kube-node-lease`
187+
* `kube-public`
188+
* `kube-system`
189+
* `local-path-storage`
190+
191+
If you prefer to override this, you can add your own list of namespaces to exclude by setting the `excludedNamespaces` to your own list.
192+
For example:
193+
```yaml
194+
--set excludedNamespaces={kube-node-lease,kube-public,local-path-storage,some_namespace}
195+
```
196+
183197
## Terms and conditions ##
184198

185199
*The Snyk Container Kubernetes integration uses Red Hat UBI (Universal Base Image).*
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{{ if .Values.excludedNamespaces }}
2+
apiVersion: v1
3+
kind: ConfigMap
4+
metadata:
5+
name: {{ .Release.Name }}-excluded-namespaces
6+
data:
7+
excludedNamespaces: |-
8+
{{- range .Values.excludedNamespaces }}
9+
{{ . }}
10+
{{- end }}
11+
{{ end }}

snyk-monitor/templates/deployment.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ spec:
5858
readOnly: true
5959
- name: registries-conf
6060
mountPath: "/srv/app/.config/containers"
61+
{{- if .Values.excludedNamespaces }}
62+
- name: excluded-namespaces
63+
mountPath: "/etc/config"
64+
{{- end }}
6165
env:
6266
- name: SNYK_INTEGRATION_ID
6367
valueFrom:
@@ -129,6 +133,11 @@ spec:
129133
configMap:
130134
name: {{ .Values.registriesConfConfigMap }}
131135
optional: true
136+
{{- if .Values.excludedNamespaces }}
137+
- name: excluded-namespaces
138+
configMap:
139+
name: {{ .Release.Name }}-excluded-namespaces
140+
{{- end }}
132141
{{- with .Values.nodeSelector }}
133142
nodeSelector:
134143
{{- toYaml . | nindent 8 }}

snyk-monitor/values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,6 @@ nodeSelector: {}
8888
psp:
8989
enabled: false
9090
name: ""
91+
92+
# Override the excluded namespaces
93+
excludedNamespaces:

src/common/config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
import * as uuidv4 from 'uuid/v4';
22
import { loadConfig } from 'snyk-config';
3+
import * as fs from 'fs';
34

45
const config: Record<string, any> = loadConfig(__dirname + '/../..', {
56
secretConfig: process.env.CONFIG_SECRET_FILE,
67
});
78

9+
const namespacesFilePath = '/etc/config/excludedNamespaces';
10+
11+
function loadExcludedNamespaces(): string[] | null {
12+
try {
13+
const data = fs.readFileSync(namespacesFilePath, 'UTF-8');
14+
const namespaces: string[] = data.split(/\r?\n/);
15+
return namespaces;
16+
} catch (err) {
17+
return null;
18+
}
19+
}
20+
821
config.AGENT_ID = uuidv4();
922
config.INTEGRATION_ID = config.INTEGRATION_ID.trim();
1023
config.CLUSTER_NAME = config.CLUSTER_NAME || 'Default cluster';
1124
config.IMAGE_STORAGE_ROOT = '/var/tmp';
1225
config.POLICIES_STORAGE_ROOT = '/tmp/policies';
26+
config.EXCLUDED_NAMESPACES = loadExcludedNamespaces();
1327

1428
/**
1529
* Important: we delete the following env vars because we don't want to proxy requests to the Kubernetes API server.

src/supervisor/watchers/index.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import { ECONNRESET_ERROR_CODE } from './types';
88
import { setupInformer } from './handlers';
99
import { kubeConfig, k8sApi } from '../cluster';
1010
import * as kubernetesApiWrappers from '../kuberenetes-api-wrappers';
11-
import { kubernetesInternalNamespaces } from './internal-namespaces';
11+
import {
12+
kubernetesInternalNamespaces,
13+
openshiftInternalNamespaces,
14+
} from './internal-namespaces';
1215

1316
/**
1417
* This map keeps track of all currently watched namespaces.
@@ -52,8 +55,14 @@ export function extractNamespaceName(namespace: V1Namespace): string {
5255
throw new Error('Namespace missing metadata.name');
5356
}
5457

55-
export function isKubernetesInternalNamespace(namespace: string): boolean {
56-
return kubernetesInternalNamespaces.has(namespace);
58+
export function isExcludedNamespace(namespace: string): boolean {
59+
return (
60+
(config.EXCLUDED_NAMESPACES
61+
? config.EXCLUDED_NAMESPACES.includes(namespace)
62+
: kubernetesInternalNamespaces.has(namespace)) ||
63+
// always check openshift excluded namespaces
64+
openshiftInternalNamespaces.has(namespace)
65+
);
5766
}
5867

5968
async function setupWatchesForCluster(): Promise<void> {
@@ -90,8 +99,8 @@ async function setupWatchesForCluster(): Promise<void> {
9099
informer.on(ADD, async (namespace: V1Namespace) => {
91100
try {
92101
const namespaceName = extractNamespaceName(namespace);
93-
if (isKubernetesInternalNamespace(namespaceName)) {
94-
// disregard namespaces internal to kubernetes
102+
if (isExcludedNamespace(namespaceName)) {
103+
// disregard excluded namespaces
95104
logger.info({ namespaceName }, 'ignoring blacklisted namespace');
96105
return;
97106
}

src/supervisor/watchers/internal-namespaces.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ export const kubernetesInternalNamespaces = new Set([
33
'kube-public',
44
'kube-system',
55
'local-path-storage',
6+
]);
7+
8+
export const openshiftInternalNamespaces = new Set([
69
'openshift',
710
'openshift-apiserver',
811
'openshift-apiserver-operator',

test/unit/supervisor/__snapshots__/watchers.spec.ts.snap

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`internal Kubernetes namespaces tests internal namespaces list against snapshot 1`] = `
3+
exports[`isExcludedNamespace() internal Kubernetes namespaces list against snapshot 1`] = `
44
Set {
55
"kube-node-lease",
66
"kube-public",
77
"kube-system",
88
"local-path-storage",
9+
}
10+
`;
11+
12+
exports[`isExcludedNamespace() openshift internal namespaces list against snapshot 1`] = `
13+
Set {
914
"openshift",
1015
"openshift-apiserver",
1116
"openshift-apiserver-operator",
Lines changed: 73 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,25 @@
11
import { V1Namespace } from '@kubernetes/client-node';
2+
import { config } from '../../../src/common/config';
23

34
import * as watchers from '../../../src/supervisor/watchers';
4-
import { kubernetesInternalNamespaces } from '../../../src/supervisor/watchers/internal-namespaces';
5+
import {
6+
kubernetesInternalNamespaces,
7+
openshiftInternalNamespaces,
8+
} from '../../../src/supervisor/watchers/internal-namespaces';
59

6-
describe('extractNamespaceName() tests', () => {
10+
describe('extractNamespaceName()', () => {
711
test.each([
8-
['extractNamespaceName() throws on empty input', {} as V1Namespace],
9-
[
10-
'extractNamespaceName() throws on empty metadata',
11-
{ metadata: {} } as V1Namespace,
12-
],
13-
[
14-
'extractNamespaceName() throws on undefined name',
15-
{ metadata: { name: undefined } } as V1Namespace,
16-
],
17-
[
18-
'extractNamespaceName() throws on empty name',
19-
{ metadata: { name: '' } } as V1Namespace,
20-
],
21-
])('%s', (_testCaseName, input) => {
12+
['empty input', {} as V1Namespace],
13+
['empty metadata', { metadata: {} } as V1Namespace],
14+
['undefined name', { metadata: { name: undefined } } as V1Namespace],
15+
['empty name', { metadata: { name: '' } } as V1Namespace],
16+
])('throws on %s', (_testCaseName, input) => {
2217
expect(() => watchers.extractNamespaceName(input)).toThrowError(
2318
'Namespace missing metadata.name',
2419
);
2520
});
2621

27-
test('extractNamespaceName() returns namespace.metadata.name', () => {
22+
test('returns namespace.metadata.name', () => {
2823
expect(
2924
watchers.extractNamespaceName({
3025
metadata: { name: 'literally anything else' },
@@ -33,18 +28,16 @@ describe('extractNamespaceName() tests', () => {
3328
});
3429
});
3530

36-
describe('internal Kubernetes namespaces tests', () => {
37-
test('internal namespaces list against snapshot', () => {
31+
describe('isExcludedNamespace() internal Kubernetes namespaces', () => {
32+
test('list against snapshot', () => {
3833
expect(kubernetesInternalNamespaces).toMatchSnapshot();
3934
});
4035

41-
test('isKubernetesInternalNamespace(): internal Kubernetes namespaces are used', () => {
42-
for (const internalNamespace of kubernetesInternalNamespaces) {
43-
expect(watchers.isKubernetesInternalNamespace(internalNamespace)).toEqual(
44-
true,
45-
);
46-
}
47-
});
36+
for (const internalNamespace of kubernetesInternalNamespaces) {
37+
test(`isExcludedNamespace(${internalNamespace}) -> true`, () => {
38+
expect(watchers.isExcludedNamespace(internalNamespace)).toEqual(true);
39+
});
40+
}
4841

4942
test.each([
5043
['kube-node-lease-'],
@@ -53,7 +46,59 @@ describe('internal Kubernetes namespaces tests', () => {
5346
['egg'],
5447
[''],
5548
[(undefined as unknown) as string],
56-
])('isKubernetesInternalNamespace(%s) -> false', (input) => {
57-
expect(watchers.isKubernetesInternalNamespace(input)).toEqual(false);
49+
])('isExcludedNamespace(%s) -> false', (input) => {
50+
expect(watchers.isExcludedNamespace(input)).toEqual(false);
51+
});
52+
});
53+
54+
describe('isExcludedNamespace() openshift internal namespaces', () => {
55+
test('list against snapshot', () => {
56+
expect(openshiftInternalNamespaces).toMatchSnapshot();
57+
});
58+
59+
for (const internalNamespace of openshiftInternalNamespaces) {
60+
test(`isExcludedNamespace(${internalNamespace}) -> true`, () => {
61+
expect(watchers.isExcludedNamespace(internalNamespace)).toEqual(true);
62+
});
63+
}
64+
65+
test.each([
66+
['openshif'],
67+
['openshift-'],
68+
['egg'],
69+
[''],
70+
[(undefined as unknown) as string],
71+
])('isExcludedNamespace(%s) -> false', (input) => {
72+
expect(watchers.isExcludedNamespace(input)).toEqual(false);
73+
});
74+
});
75+
76+
describe('isExcludedNamespace() excluded namespaces from config', () => {
77+
const excludedNamespacesFromConfig = ['one', 'two', 'three'];
78+
beforeAll(() => {
79+
config.EXCLUDED_NAMESPACES = excludedNamespacesFromConfig;
80+
});
81+
82+
afterAll(() => {
83+
config.EXCLUDED_NAMESPACES = null;
84+
});
85+
86+
excludedNamespacesFromConfig.forEach((namespace) => {
87+
test(`[excluded namespaces from config] isExcludedNamespace(${namespace}) -> true`, () => {
88+
expect(watchers.isExcludedNamespace(namespace)).toEqual(true);
89+
});
5890
});
91+
92+
for (const internalNamespace of openshiftInternalNamespaces) {
93+
test(`[openshift internal namespaces] isExcludedNamespace(${internalNamespace}) -> true`, () => {
94+
expect(watchers.isExcludedNamespace(internalNamespace)).toEqual(true);
95+
});
96+
}
97+
98+
test.each([['kube-system']['egg'], [''], [(undefined as unknown) as string]])(
99+
'isExcludedNamespace(%s) -> false',
100+
(input) => {
101+
expect(watchers.isExcludedNamespace(input)).toEqual(false);
102+
},
103+
);
59104
});

0 commit comments

Comments
 (0)