Skip to content

Commit 423d23d

Browse files
committed
feat: load user-defined Rego rules for workload auto-import
The Rego rules are loaded by snyk-monitor at the start of the application. We want the rules to be provided client-side but any choice whether workloads should be imported or deleted happens in the upstream. This allows us to swap implementations, fix bugs, etc. in the back end without requiring any new versions to be deployed by users. Test coverage provided in system tests to ensure the policy is being transmitted first thing on start up of the application.
1 parent 89b7f72 commit 423d23d

File tree

11 files changed

+137
-3
lines changed

11 files changed

+137
-3
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"prepare": "npm run build",
2121
"build": "tsc",
2222
"dev": "tsc-watch --project tsconfig.json --onSuccess 'node --inspect .'",
23-
"debug": "tsc-watch --project tsconfig.json --onSuccess 'node --inspect --debug-brk .'",
23+
"debug": "tsc-watch --project tsconfig.json --onSuccess 'node --inspect-brk .'",
2424
"lint": "eslint \"src/**/*.ts\" && (cd test && eslint \"**/*.ts\")"
2525
},
2626
"author": "snyk.io",

snyk-monitor-deployment.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ spec:
3434
mountPath: "/srv/app/certs"
3535
- name: registries-conf
3636
mountPath: "/srv/app/.config/containers"
37+
- name: workload-policies
38+
mountPath: "/var/tmp/policies"
3739
env:
3840
- name: SNYK_INTEGRATION_ID
3941
valueFrom:
@@ -115,4 +117,8 @@ spec:
115117
configMap:
116118
name: snyk-monitor-registries-conf
117119
optional: true
120+
- name: workload-policies
121+
configMap:
122+
name: snyk-monitor-workload-policies
123+
optional: true
118124
serviceAccountName: snyk-monitor

snyk-monitor/templates/deployment.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ spec:
4949
mountPath: "/var/tmp"
5050
- name: ssl-certs
5151
mountPath: "/srv/app/certs"
52+
- name: workload-policies
53+
mountPath: "/var/tmp/policies"
54+
readOnly: true
5255
- name: registries-conf
5356
mountPath: "/srv/app/.config/containers"
5457
env:
@@ -114,6 +117,10 @@ spec:
114117
configMap:
115118
name: {{ .Values.certsConfigMap }}
116119
optional: true
120+
- name: workload-policies
121+
configMap:
122+
name: {{ .Values.workloadPoliciesMap }}
123+
optional: true
117124
- name: registries-conf
118125
configMap:
119126
name: {{ .Values.registriesConfConfigMap }}

snyk-monitor/values.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
monitorSecrets: snyk-monitor
88
certsConfigMap: snyk-monitor-certs
99
registriesConfConfigMap: snyk-monitor-registries-conf
10+
workloadPoliciesMap: snyk-monitor-workload-policies
1011

1112
# One of: Cluster, Namespaced
1213
# Cluster - creates a ClusterRole and ClusterRoleBinding with the ServiceAccount

src/common/policy.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { existsSync, readFile } from 'fs';
2+
import { resolve as resolvePath } from 'path';
3+
import { promisify } from 'util';
4+
5+
import { logger } from './logger';
6+
import { constructWorkloadAutoImportPolicy } from '../transmitter/payload';
7+
import { sendWorkloadAutoImportPolicy } from '../transmitter';
8+
import { config } from './config';
9+
10+
const readFileAsync = promisify(readFile);
11+
12+
export async function loadAndSendWorkloadAutoImportPolicy(): Promise<void> {
13+
try {
14+
/** This path is set in snyk-monitor during installation/deployment and is defined in the Helm chart. */
15+
const userProvidedRegoPolicyPath = resolvePath(
16+
config.IMAGE_STORAGE_ROOT,
17+
'policies',
18+
'workload-auto-import.rego',
19+
);
20+
if (!existsSync(userProvidedRegoPolicyPath)) {
21+
logger.info({}, 'Rego policy file does not exist, skipping loading');
22+
return;
23+
}
24+
25+
const regoPolicy = await readFileAsync(userProvidedRegoPolicyPath, 'utf8');
26+
const payload = constructWorkloadAutoImportPolicy(regoPolicy);
27+
await sendWorkloadAutoImportPolicy(payload);
28+
} catch (err) {
29+
logger.error({ err }, 'Unexpected error occurred while loading workload auto-import policy');
30+
}
31+
}

src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { config } from './common/config';
77
import { logger } from './common/logger';
88
import { currentClusterName } from './supervisor/cluster';
99
import { beginWatchingWorkloads } from './supervisor/watchers';
10+
import { loadAndSendWorkloadAutoImportPolicy } from './common/policy';
1011

1112
process.on('uncaughtException', (err) => {
1213
if (state.shutdownInProgress) {
@@ -58,4 +59,9 @@ function monitor(): void {
5859

5960
SourceMapSupport.install();
6061
cleanUpTempStorage();
61-
monitor();
62+
63+
// Allow running in an async context
64+
setImmediate(async function setUpAndMonitor(): Promise<void> {
65+
await loadAndSendWorkloadAutoImportPolicy();
66+
monitor();
67+
});

src/transmitter/index.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
IRequestError,
1111
ScanResultsPayload,
1212
IDependencyGraphPayload,
13+
WorkloadAutoImportPolicyPayload,
1314
} from './types';
1415
import { getProxyAgent } from './proxy';
1516

@@ -69,6 +70,30 @@ export async function sendWorkloadMetadata(payload: IWorkloadMetadataPayload): P
6970
}
7071
}
7172

73+
export async function sendWorkloadAutoImportPolicy(payload: WorkloadAutoImportPolicyPayload): Promise<void> {
74+
try {
75+
logger.info(
76+
{ userLocator: payload.userLocator, cluster: payload.cluster, agentId: payload.agentId },
77+
'attempting to send workload auto-import policy',
78+
);
79+
80+
const { response, attempt } = await retryRequest('post', `${upstreamUrl}/api/v1/policy`, payload);
81+
if (!isSuccessStatusCode(response.statusCode)) {
82+
throw new Error(`${response.statusCode} ${response.statusMessage}`);
83+
}
84+
85+
logger.info(
86+
{ userLocator: payload.userLocator, cluster: payload.cluster, agentId: payload.agentId, attempt },
87+
'workload auto-import policy sent upstream successfully',
88+
);
89+
} catch (error) {
90+
logger.error(
91+
{ error, userLocator: payload.userLocator, cluster: payload.cluster, agentId: payload.agentId },
92+
'could not send workload auto-import policy',
93+
);
94+
}
95+
}
96+
7297
export async function deleteWorkload(payload: IDeleteWorkloadPayload): Promise<void> {
7398
try {
7499
const {response, attempt} = await retryRequest('delete', `${upstreamUrl}/api/v1/workload`, payload);

src/transmitter/payload.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
IKubernetesMonitorMetadata,
1313
ScanResultsPayload,
1414
IDependencyGraphPayload,
15+
WorkloadAutoImportPolicyPayload,
1516
} from './types';
1617

1718
export function constructDepGraph(
@@ -125,3 +126,14 @@ export function constructDeleteWorkload(
125126
agentId: config.AGENT_ID,
126127
};
127128
}
129+
130+
export function constructWorkloadAutoImportPolicy(
131+
policy: string,
132+
): WorkloadAutoImportPolicyPayload {
133+
return {
134+
policy,
135+
userLocator: config.INTEGRATION_ID,
136+
cluster: currentClusterName,
137+
agentId: config.AGENT_ID,
138+
};
139+
}

src/transmitter/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ export interface IDeleteWorkloadPayload {
6161
agentId: string;
6262
}
6363

64+
export interface WorkloadAutoImportPolicyPayload {
65+
userLocator: string;
66+
cluster: string;
67+
agentId: string;
68+
policy: string;
69+
}
70+
6471
export interface IWorkload {
6572
type: string;
6673
name: string;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package snyk
2+
3+
default workload_auto_import = false

0 commit comments

Comments
 (0)