pepr 0.12.2 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +70 -0
- package/README.md +28 -30
- package/dist/cli.js +666 -692
- package/dist/controller.js +13 -81
- package/dist/lib/assets/deploy.d.ts +3 -0
- package/dist/lib/assets/deploy.d.ts.map +1 -0
- package/dist/lib/assets/index.d.ts +17 -0
- package/dist/lib/assets/index.d.ts.map +1 -0
- package/dist/lib/assets/loader.d.ts +8 -0
- package/dist/lib/assets/loader.d.ts.map +1 -0
- package/dist/lib/assets/networking.d.ts +6 -0
- package/dist/lib/assets/networking.d.ts.map +1 -0
- package/dist/lib/assets/pods.d.ts +8 -0
- package/dist/lib/assets/pods.d.ts.map +1 -0
- package/dist/lib/assets/rbac.d.ts +11 -0
- package/dist/lib/assets/rbac.d.ts.map +1 -0
- package/dist/lib/assets/webhooks.d.ts +6 -0
- package/dist/lib/assets/webhooks.d.ts.map +1 -0
- package/dist/lib/assets/yaml.d.ts +4 -0
- package/dist/lib/assets/yaml.d.ts.map +1 -0
- package/dist/lib/capability.d.ts +4 -9
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/controller.d.ts +4 -15
- package/dist/lib/controller.d.ts.map +1 -1
- package/dist/lib/errors.d.ts +12 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/filter.d.ts +1 -1
- package/dist/lib/filter.d.ts.map +1 -1
- package/dist/lib/k8s/index.d.ts +2 -1
- package/dist/lib/k8s/index.d.ts.map +1 -1
- package/dist/lib/k8s/kinds.d.ts.map +1 -1
- package/dist/lib/k8s/types.d.ts +18 -14
- package/dist/lib/k8s/types.d.ts.map +1 -1
- package/dist/lib/k8s/upstream.d.ts +2 -2
- package/dist/lib/k8s/upstream.d.ts.map +1 -1
- package/dist/lib/logger.d.ts +8 -54
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/metrics.d.ts +10 -9
- package/dist/lib/metrics.d.ts.map +1 -1
- package/dist/lib/module.d.ts +4 -4
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/mutate-processor.d.ts +5 -0
- package/dist/lib/mutate-processor.d.ts.map +1 -0
- package/dist/lib/{request.d.ts → mutate-request.d.ts} +7 -7
- package/dist/lib/mutate-request.d.ts.map +1 -0
- package/dist/lib/types.d.ts +48 -55
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/validate-processor.d.ts +4 -0
- package/dist/lib/validate-processor.d.ts.map +1 -0
- package/dist/lib/validate-request.d.ts +54 -0
- package/dist/lib/validate-request.d.ts.map +1 -0
- package/dist/lib.d.ts +3 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +610 -354
- package/dist/lib.js.map +4 -4
- package/jest.config.json +4 -0
- package/journey/before.ts +21 -0
- package/journey/k8s.ts +81 -0
- package/journey/pepr-build.ts +69 -0
- package/journey/pepr-deploy.ts +133 -0
- package/journey/pepr-dev.ts +155 -0
- package/journey/pepr-format.ts +13 -0
- package/journey/pepr-init.ts +12 -0
- package/package.json +29 -27
- package/src/cli.ts +2 -11
- package/src/lib/assets/deploy.ts +179 -0
- package/src/lib/assets/index.ts +53 -0
- package/src/lib/assets/loader.ts +41 -0
- package/src/lib/assets/networking.ts +58 -0
- package/src/lib/assets/pods.ts +148 -0
- package/src/lib/assets/rbac.ts +57 -0
- package/src/lib/assets/webhooks.ts +139 -0
- package/src/lib/assets/yaml.ts +75 -0
- package/src/lib/capability.ts +80 -68
- package/src/lib/controller.ts +199 -99
- package/src/lib/errors.ts +20 -0
- package/src/lib/fetch.ts +1 -1
- package/src/lib/filter.ts +1 -3
- package/src/lib/k8s/index.ts +4 -1
- package/src/lib/k8s/kinds.ts +40 -0
- package/src/lib/k8s/types.ts +21 -15
- package/src/lib/k8s/upstream.ts +5 -1
- package/src/lib/logger.ts +14 -125
- package/src/lib/metrics.ts +86 -29
- package/src/lib/module.ts +32 -16
- package/src/lib/{processor.ts → mutate-processor.ts} +39 -28
- package/src/lib/{request.ts → mutate-request.ts} +26 -13
- package/src/lib/types.ts +54 -60
- package/src/lib/validate-processor.ts +76 -0
- package/src/lib/validate-request.ts +106 -0
- package/src/lib.ts +4 -2
- package/src/runtime/controller.ts +1 -1
- package/dist/lib/k8s/webhook.d.ts +0 -37
- package/dist/lib/k8s/webhook.d.ts.map +0 -1
- package/dist/lib/processor.d.ts +0 -5
- package/dist/lib/processor.d.ts.map +0 -1
- package/dist/lib/request.d.ts.map +0 -1
- package/src/lib/k8s/webhook.ts +0 -643
package/src/lib/k8s/webhook.ts
DELETED
|
@@ -1,643 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
AdmissionregistrationV1Api as AdmissionRegV1API,
|
|
6
|
-
AdmissionregistrationV1WebhookClientConfig as AdmissionRegnV1WebhookClientCfg,
|
|
7
|
-
AppsV1Api,
|
|
8
|
-
CoreV1Api,
|
|
9
|
-
HttpError,
|
|
10
|
-
KubeConfig,
|
|
11
|
-
RbacAuthorizationV1Api,
|
|
12
|
-
V1ClusterRole,
|
|
13
|
-
V1ClusterRoleBinding,
|
|
14
|
-
V1Deployment,
|
|
15
|
-
V1LabelSelectorRequirement,
|
|
16
|
-
V1MutatingWebhookConfiguration,
|
|
17
|
-
V1Namespace,
|
|
18
|
-
V1RuleWithOperations,
|
|
19
|
-
V1Secret,
|
|
20
|
-
V1Service,
|
|
21
|
-
V1ServiceAccount,
|
|
22
|
-
dumpYaml,
|
|
23
|
-
} from "@kubernetes/client-node";
|
|
24
|
-
import { fork } from "child_process";
|
|
25
|
-
import crypto from "crypto";
|
|
26
|
-
import { promises as fs } from "fs";
|
|
27
|
-
import { equals, uniqWith } from "ramda";
|
|
28
|
-
import { gzipSync } from "zlib";
|
|
29
|
-
|
|
30
|
-
import Log from "../logger";
|
|
31
|
-
import { Binding, Event, HookPhase, ModuleConfig } from "../types";
|
|
32
|
-
import { TLSOut, genTLS } from "./tls";
|
|
33
|
-
|
|
34
|
-
const peprIgnore: V1LabelSelectorRequirement = {
|
|
35
|
-
key: "pepr.dev",
|
|
36
|
-
operator: "NotIn",
|
|
37
|
-
values: ["ignore"],
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export class Webhook {
|
|
41
|
-
private name: string;
|
|
42
|
-
private _tls: TLSOut;
|
|
43
|
-
private _apiToken: string;
|
|
44
|
-
|
|
45
|
-
public image: string;
|
|
46
|
-
|
|
47
|
-
public get tls(): TLSOut {
|
|
48
|
-
return this._tls;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
public get apiToken(): string {
|
|
52
|
-
return this._apiToken;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
constructor(private readonly config: ModuleConfig, private readonly host?: string) {
|
|
56
|
-
this.name = `pepr-${config.uuid}`;
|
|
57
|
-
|
|
58
|
-
this.image = `ghcr.io/defenseunicorns/pepr/controller:v${config.peprVersion}`;
|
|
59
|
-
|
|
60
|
-
// Generate the ephemeral tls things
|
|
61
|
-
this._tls = genTLS(this.host || `${this.name}.pepr-system.svc`);
|
|
62
|
-
|
|
63
|
-
// Generate the api token for the controller / webhook
|
|
64
|
-
this._apiToken = crypto.randomBytes(32).toString("hex");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Generate the pepr-system namespace */
|
|
68
|
-
namespace(): V1Namespace {
|
|
69
|
-
return {
|
|
70
|
-
apiVersion: "v1",
|
|
71
|
-
kind: "Namespace",
|
|
72
|
-
metadata: { name: "pepr-system" },
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Grants the controller access to cluster resources beyond the mutating webhook.
|
|
78
|
-
*
|
|
79
|
-
* @todo: should dynamically generate this based on resources used by the module. will also need to explore how this should work for multiple modules.
|
|
80
|
-
* @returns
|
|
81
|
-
*/
|
|
82
|
-
clusterRole(): V1ClusterRole {
|
|
83
|
-
return {
|
|
84
|
-
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
85
|
-
kind: "ClusterRole",
|
|
86
|
-
metadata: { name: this.name },
|
|
87
|
-
rules: [
|
|
88
|
-
{
|
|
89
|
-
// @todo: make this configurable
|
|
90
|
-
apiGroups: ["*"],
|
|
91
|
-
resources: ["*"],
|
|
92
|
-
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"],
|
|
93
|
-
},
|
|
94
|
-
],
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
clusterRoleBinding(): V1ClusterRoleBinding {
|
|
99
|
-
const name = this.name;
|
|
100
|
-
return {
|
|
101
|
-
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
102
|
-
kind: "ClusterRoleBinding",
|
|
103
|
-
metadata: { name },
|
|
104
|
-
roleRef: {
|
|
105
|
-
apiGroup: "rbac.authorization.k8s.io",
|
|
106
|
-
kind: "ClusterRole",
|
|
107
|
-
name,
|
|
108
|
-
},
|
|
109
|
-
subjects: [
|
|
110
|
-
{
|
|
111
|
-
kind: "ServiceAccount",
|
|
112
|
-
name,
|
|
113
|
-
namespace: "pepr-system",
|
|
114
|
-
},
|
|
115
|
-
],
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
serviceAccount(): V1ServiceAccount {
|
|
120
|
-
return {
|
|
121
|
-
apiVersion: "v1",
|
|
122
|
-
kind: "ServiceAccount",
|
|
123
|
-
metadata: {
|
|
124
|
-
name: this.name,
|
|
125
|
-
namespace: "pepr-system",
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
apiTokenSecret(): V1Secret {
|
|
131
|
-
return {
|
|
132
|
-
apiVersion: "v1",
|
|
133
|
-
kind: "Secret",
|
|
134
|
-
metadata: {
|
|
135
|
-
name: `${this.name}-api-token`,
|
|
136
|
-
namespace: "pepr-system",
|
|
137
|
-
},
|
|
138
|
-
type: "Opaque",
|
|
139
|
-
data: {
|
|
140
|
-
value: Buffer.from(this._apiToken).toString("base64"),
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
tlsSecret(): V1Secret {
|
|
146
|
-
return {
|
|
147
|
-
apiVersion: "v1",
|
|
148
|
-
kind: "Secret",
|
|
149
|
-
metadata: {
|
|
150
|
-
name: `${this.name}-tls`,
|
|
151
|
-
namespace: "pepr-system",
|
|
152
|
-
},
|
|
153
|
-
type: "kubernetes.io/tls",
|
|
154
|
-
data: {
|
|
155
|
-
"tls.crt": this._tls.crt,
|
|
156
|
-
"tls.key": this._tls.key,
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
generateWebhookRules(path: string): Promise<V1RuleWithOperations[]> {
|
|
162
|
-
return new Promise((resolve, reject) => {
|
|
163
|
-
const rules: V1RuleWithOperations[] = [];
|
|
164
|
-
|
|
165
|
-
// Add a default rule that allows all resources as a fallback
|
|
166
|
-
const defaultRule = {
|
|
167
|
-
apiGroups: ["*"],
|
|
168
|
-
apiVersions: ["*"],
|
|
169
|
-
operations: ["CREATE", "UPDATE", "DELETE"],
|
|
170
|
-
resources: ["*/*"],
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
// Fork is needed with the PEPR_MODE env var to ensure the module is loaded in build mode and will send back the capabilities
|
|
174
|
-
const program = fork(path, {
|
|
175
|
-
env: {
|
|
176
|
-
...process.env,
|
|
177
|
-
LOG_LEVEL: "warn",
|
|
178
|
-
PEPR_MODE: "build",
|
|
179
|
-
},
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// We are receiving javascript so the private fields are now public
|
|
183
|
-
interface ModuleCapabilities {
|
|
184
|
-
capabilities: {
|
|
185
|
-
_name: string;
|
|
186
|
-
_description: string;
|
|
187
|
-
_namespaces: string[];
|
|
188
|
-
_mutateOrValidate: HookPhase;
|
|
189
|
-
_bindings: Binding[];
|
|
190
|
-
}[];
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Wait for the module to send back the capabilities
|
|
194
|
-
program.on("message", message => {
|
|
195
|
-
// Cast the message to the ModuleCapabilities type
|
|
196
|
-
const { capabilities } = message.valueOf() as ModuleCapabilities;
|
|
197
|
-
|
|
198
|
-
for (const capability of capabilities) {
|
|
199
|
-
Log.info(`Module ${this.config.uuid} has capability: ${capability._name}`);
|
|
200
|
-
|
|
201
|
-
const { _bindings } = capability;
|
|
202
|
-
|
|
203
|
-
// Read the bindings and generate the rules
|
|
204
|
-
for (const binding of _bindings) {
|
|
205
|
-
const { event, kind } = binding;
|
|
206
|
-
|
|
207
|
-
const operations: string[] = [];
|
|
208
|
-
|
|
209
|
-
// CreateOrUpdate is a Pepr-specific event that is translated to Create and Update
|
|
210
|
-
if (event === Event.CreateOrUpdate) {
|
|
211
|
-
operations.push(Event.Create, Event.Update);
|
|
212
|
-
} else {
|
|
213
|
-
operations.push(event);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Use the plural property if it exists, otherwise use lowercase kind + s
|
|
217
|
-
const resource = kind.plural || `${kind.kind.toLowerCase()}s`;
|
|
218
|
-
|
|
219
|
-
rules.push({
|
|
220
|
-
apiGroups: [kind.group],
|
|
221
|
-
apiVersions: [kind.version || "*"],
|
|
222
|
-
operations,
|
|
223
|
-
resources: [resource],
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
program.on("exit", code => {
|
|
230
|
-
if (code !== 0) {
|
|
231
|
-
reject(new Error(`Child process exited with code ${code}`));
|
|
232
|
-
} else {
|
|
233
|
-
// If there are no rules, add a catch-all
|
|
234
|
-
if (rules.length < 1) {
|
|
235
|
-
resolve([defaultRule]);
|
|
236
|
-
} else {
|
|
237
|
-
const reducedRules = uniqWith(equals, rules);
|
|
238
|
-
resolve(reducedRules);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
program.on("error", error => {
|
|
244
|
-
reject(error);
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async mutatingWebhook(path: string, timeoutSeconds = 10): Promise<V1MutatingWebhookConfiguration> {
|
|
250
|
-
const { name } = this;
|
|
251
|
-
const ignore = [peprIgnore];
|
|
252
|
-
|
|
253
|
-
// Add any namespaces to ignore
|
|
254
|
-
if (this.config.alwaysIgnore.namespaces && this.config.alwaysIgnore.namespaces.length > 0) {
|
|
255
|
-
ignore.push({
|
|
256
|
-
key: "kubernetes.io/metadata.name",
|
|
257
|
-
operator: "NotIn",
|
|
258
|
-
values: this.config.alwaysIgnore.namespaces,
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const clientConfig: AdmissionRegnV1WebhookClientCfg = {
|
|
263
|
-
caBundle: this._tls.ca,
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
// The URL must include the API Token
|
|
267
|
-
const mutatePath = `/mutate/${this._apiToken}`;
|
|
268
|
-
|
|
269
|
-
// If a host is specified, use that with a port of 3000
|
|
270
|
-
if (this.host) {
|
|
271
|
-
clientConfig.url = `https://${this.host}:3000${mutatePath}`;
|
|
272
|
-
} else {
|
|
273
|
-
// Otherwise, use the service
|
|
274
|
-
clientConfig.service = {
|
|
275
|
-
name: this.name,
|
|
276
|
-
namespace: "pepr-system",
|
|
277
|
-
path: mutatePath,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const rules = await this.generateWebhookRules(path);
|
|
282
|
-
|
|
283
|
-
return {
|
|
284
|
-
apiVersion: "admissionregistration.k8s.io/v1",
|
|
285
|
-
kind: "MutatingWebhookConfiguration",
|
|
286
|
-
metadata: { name },
|
|
287
|
-
webhooks: [
|
|
288
|
-
{
|
|
289
|
-
name: `${name}.pepr.dev`,
|
|
290
|
-
admissionReviewVersions: ["v1", "v1beta1"],
|
|
291
|
-
clientConfig,
|
|
292
|
-
failurePolicy: "Ignore",
|
|
293
|
-
matchPolicy: "Equivalent",
|
|
294
|
-
timeoutSeconds,
|
|
295
|
-
namespaceSelector: {
|
|
296
|
-
matchExpressions: ignore,
|
|
297
|
-
},
|
|
298
|
-
objectSelector: {
|
|
299
|
-
matchExpressions: ignore,
|
|
300
|
-
},
|
|
301
|
-
rules,
|
|
302
|
-
// @todo: track side effects state
|
|
303
|
-
sideEffects: "None",
|
|
304
|
-
},
|
|
305
|
-
],
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
deployment(hash: string): V1Deployment {
|
|
310
|
-
return {
|
|
311
|
-
apiVersion: "apps/v1",
|
|
312
|
-
kind: "Deployment",
|
|
313
|
-
metadata: {
|
|
314
|
-
name: this.name,
|
|
315
|
-
namespace: "pepr-system",
|
|
316
|
-
labels: {
|
|
317
|
-
app: this.name,
|
|
318
|
-
},
|
|
319
|
-
},
|
|
320
|
-
spec: {
|
|
321
|
-
replicas: 2,
|
|
322
|
-
selector: {
|
|
323
|
-
matchLabels: {
|
|
324
|
-
app: this.name,
|
|
325
|
-
},
|
|
326
|
-
},
|
|
327
|
-
template: {
|
|
328
|
-
metadata: {
|
|
329
|
-
labels: {
|
|
330
|
-
app: this.name,
|
|
331
|
-
},
|
|
332
|
-
},
|
|
333
|
-
spec: {
|
|
334
|
-
priorityClassName: "system-node-critical",
|
|
335
|
-
serviceAccountName: this.name,
|
|
336
|
-
containers: [
|
|
337
|
-
{
|
|
338
|
-
name: "server",
|
|
339
|
-
image: this.image,
|
|
340
|
-
imagePullPolicy: "IfNotPresent",
|
|
341
|
-
command: ["node", "/app/node_modules/pepr/dist/controller.js", hash],
|
|
342
|
-
livenessProbe: {
|
|
343
|
-
httpGet: {
|
|
344
|
-
path: "/healthz",
|
|
345
|
-
port: 3000,
|
|
346
|
-
scheme: "HTTPS",
|
|
347
|
-
},
|
|
348
|
-
},
|
|
349
|
-
ports: [
|
|
350
|
-
{
|
|
351
|
-
containerPort: 3000,
|
|
352
|
-
},
|
|
353
|
-
],
|
|
354
|
-
resources: {
|
|
355
|
-
requests: {
|
|
356
|
-
memory: "64Mi",
|
|
357
|
-
cpu: "100m",
|
|
358
|
-
},
|
|
359
|
-
limits: {
|
|
360
|
-
memory: "256Mi",
|
|
361
|
-
cpu: "500m",
|
|
362
|
-
},
|
|
363
|
-
},
|
|
364
|
-
volumeMounts: [
|
|
365
|
-
{
|
|
366
|
-
name: "tls-certs",
|
|
367
|
-
mountPath: "/etc/certs",
|
|
368
|
-
readOnly: true,
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
name: "api-token",
|
|
372
|
-
mountPath: "/app/api-token",
|
|
373
|
-
readOnly: true,
|
|
374
|
-
},
|
|
375
|
-
{
|
|
376
|
-
name: "module",
|
|
377
|
-
mountPath: `/app/load`,
|
|
378
|
-
readOnly: true,
|
|
379
|
-
},
|
|
380
|
-
],
|
|
381
|
-
},
|
|
382
|
-
],
|
|
383
|
-
volumes: [
|
|
384
|
-
{
|
|
385
|
-
name: "tls-certs",
|
|
386
|
-
secret: {
|
|
387
|
-
secretName: `${this.name}-tls`,
|
|
388
|
-
},
|
|
389
|
-
},
|
|
390
|
-
{
|
|
391
|
-
name: "api-token",
|
|
392
|
-
secret: {
|
|
393
|
-
secretName: `${this.name}-api-token`,
|
|
394
|
-
},
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
name: "module",
|
|
398
|
-
secret: {
|
|
399
|
-
secretName: `${this.name}-module`,
|
|
400
|
-
},
|
|
401
|
-
},
|
|
402
|
-
],
|
|
403
|
-
},
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
service(): V1Service {
|
|
410
|
-
return {
|
|
411
|
-
apiVersion: "v1",
|
|
412
|
-
kind: "Service",
|
|
413
|
-
metadata: {
|
|
414
|
-
name: this.name,
|
|
415
|
-
namespace: "pepr-system",
|
|
416
|
-
},
|
|
417
|
-
spec: {
|
|
418
|
-
selector: {
|
|
419
|
-
app: this.name,
|
|
420
|
-
},
|
|
421
|
-
ports: [
|
|
422
|
-
{
|
|
423
|
-
port: 443,
|
|
424
|
-
targetPort: 3000,
|
|
425
|
-
},
|
|
426
|
-
],
|
|
427
|
-
},
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
moduleSecret(data: Buffer, hash: string): V1Secret {
|
|
432
|
-
// Compress the data
|
|
433
|
-
const compressed = gzipSync(data);
|
|
434
|
-
const path = `module-${hash}.js.gz`;
|
|
435
|
-
return {
|
|
436
|
-
apiVersion: "v1",
|
|
437
|
-
kind: "Secret",
|
|
438
|
-
metadata: {
|
|
439
|
-
name: `${this.name}-module`,
|
|
440
|
-
namespace: "pepr-system",
|
|
441
|
-
},
|
|
442
|
-
type: "Opaque",
|
|
443
|
-
data: {
|
|
444
|
-
[path]: compressed.toString("base64"),
|
|
445
|
-
},
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
zarfYaml(path: string) {
|
|
450
|
-
const zarfCfg = {
|
|
451
|
-
kind: "ZarfPackageConfig",
|
|
452
|
-
metadata: {
|
|
453
|
-
name: this.name,
|
|
454
|
-
description: `Pepr Module: ${this.config.description}`,
|
|
455
|
-
url: "https://github.com/defenseunicorns/pepr",
|
|
456
|
-
version: `${this.config.appVersion || "0.0.1"}`,
|
|
457
|
-
},
|
|
458
|
-
components: [
|
|
459
|
-
{
|
|
460
|
-
name: "module",
|
|
461
|
-
required: true,
|
|
462
|
-
manifests: [
|
|
463
|
-
{
|
|
464
|
-
name: "module",
|
|
465
|
-
namespace: "pepr-system",
|
|
466
|
-
files: [path],
|
|
467
|
-
},
|
|
468
|
-
],
|
|
469
|
-
images: [this.image],
|
|
470
|
-
},
|
|
471
|
-
],
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
return dumpYaml(zarfCfg, { noRefs: true });
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
async allYaml(path: string) {
|
|
478
|
-
const code = await fs.readFile(path);
|
|
479
|
-
|
|
480
|
-
// Generate a hash of the code
|
|
481
|
-
const hash = crypto.createHash("sha256").update(code).digest("hex");
|
|
482
|
-
|
|
483
|
-
const webhook = await this.mutatingWebhook(path);
|
|
484
|
-
|
|
485
|
-
const resources = [
|
|
486
|
-
this.namespace(),
|
|
487
|
-
this.clusterRole(),
|
|
488
|
-
this.clusterRoleBinding(),
|
|
489
|
-
this.serviceAccount(),
|
|
490
|
-
this.apiTokenSecret(),
|
|
491
|
-
this.tlsSecret(),
|
|
492
|
-
webhook,
|
|
493
|
-
this.deployment(hash),
|
|
494
|
-
this.service(),
|
|
495
|
-
this.moduleSecret(code, hash),
|
|
496
|
-
];
|
|
497
|
-
|
|
498
|
-
// Convert the resources to a single YAML string
|
|
499
|
-
return resources.map(r => dumpYaml(r, { noRefs: true })).join("---\n");
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
async deploy(path: string, webhookTimeout?: number) {
|
|
503
|
-
Log.info("Establishing connection to Kubernetes");
|
|
504
|
-
|
|
505
|
-
const namespace = "pepr-system";
|
|
506
|
-
|
|
507
|
-
// Deploy the resources using the k8s API
|
|
508
|
-
const kubeConfig = new KubeConfig();
|
|
509
|
-
kubeConfig.loadFromDefault();
|
|
510
|
-
|
|
511
|
-
const coreV1Api = kubeConfig.makeApiClient(CoreV1Api);
|
|
512
|
-
const admissionApi = kubeConfig.makeApiClient(AdmissionRegV1API);
|
|
513
|
-
|
|
514
|
-
const ns = this.namespace();
|
|
515
|
-
try {
|
|
516
|
-
Log.info("Checking for namespace");
|
|
517
|
-
await coreV1Api.readNamespace(namespace);
|
|
518
|
-
} catch (e) {
|
|
519
|
-
Log.debug(e instanceof HttpError ? e.body : e);
|
|
520
|
-
Log.info("Creating namespace");
|
|
521
|
-
await coreV1Api.createNamespace(ns);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
const wh = await this.mutatingWebhook(path, webhookTimeout);
|
|
525
|
-
try {
|
|
526
|
-
Log.info("Creating mutating webhook");
|
|
527
|
-
await admissionApi.createMutatingWebhookConfiguration(wh);
|
|
528
|
-
} catch (e) {
|
|
529
|
-
Log.debug(e instanceof HttpError ? e.body : e);
|
|
530
|
-
Log.info("Removing and re-creating mutating webhook");
|
|
531
|
-
await admissionApi.deleteMutatingWebhookConfiguration(wh.metadata?.name ?? "");
|
|
532
|
-
await admissionApi.createMutatingWebhookConfiguration(wh);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// If a host is specified, we don't need to deploy the rest of the resources
|
|
536
|
-
if (this.host) {
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
if (!path) {
|
|
541
|
-
throw new Error("No code provided");
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
const code = await fs.readFile(path);
|
|
545
|
-
|
|
546
|
-
const hash = crypto.createHash("sha256").update(code).digest("hex");
|
|
547
|
-
|
|
548
|
-
const appsApi = kubeConfig.makeApiClient(AppsV1Api);
|
|
549
|
-
const rbacApi = kubeConfig.makeApiClient(RbacAuthorizationV1Api);
|
|
550
|
-
|
|
551
|
-
const crb = this.clusterRoleBinding();
|
|
552
|
-
try {
|
|
553
|
-
Log.info("Creating cluster role binding");
|
|
554
|
-
await rbacApi.createClusterRoleBinding(crb);
|
|
555
|
-
} catch (e) {
|
|
556
|
-
Log.debug(e instanceof HttpError ? e.body : e);
|
|
557
|
-
Log.info("Removing and re-creating cluster role binding");
|
|
558
|
-
await rbacApi.deleteClusterRoleBinding(crb.metadata?.name ?? "");
|
|
559
|
-
await rbacApi.createClusterRoleBinding(crb);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
const cr = this.clusterRole();
|
|
563
|
-
try {
|
|
564
|
-
Log.info("Creating cluster role");
|
|
565
|
-
await rbacApi.createClusterRole(cr);
|
|
566
|
-
} catch (e) {
|
|
567
|
-
Log.debug(e instanceof HttpError ? e.body : e);
|
|
568
|
-
Log.info("Removing and re-creating the cluster role");
|
|
569
|
-
try {
|
|
570
|
-
await rbacApi.deleteClusterRole(cr.metadata?.name ?? "");
|
|
571
|
-
await rbacApi.createClusterRole(cr);
|
|
572
|
-
} catch (e) {
|
|
573
|
-
Log.debug(e instanceof HttpError ? e.body : e);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
const sa = this.serviceAccount();
|
|
578
|
-
try {
|
|
579
|
-
Log.info("Creating service account");
|
|
580
|
-
await coreV1Api.createNamespacedServiceAccount(namespace, sa);
|
|
581
|
-
} catch (e) {
|
|
582
|
-
Log.debug(e instanceof HttpError ? e.body : e);
|
|
583
|
-
Log.info("Removing and re-creating service account");
|
|
584
|
-
await coreV1Api.deleteNamespacedServiceAccount(sa.metadata?.name ?? "", namespace);
|
|
585
|
-
await coreV1Api.createNamespacedServiceAccount(namespace, sa);
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
const mod = this.moduleSecret(code, hash);
|
|
589
|
-
try {
|
|
590
|
-
Log.info("Creating module secret");
|
|
591
|
-
await coreV1Api.createNamespacedSecret(namespace, mod);
|
|
592
|
-
} catch (e) {
|
|
593
|
-
Log.debug(e instanceof HttpError ? e.body : e);
|
|
594
|
-
Log.info("Removing and re-creating module secret");
|
|
595
|
-
await coreV1Api.deleteNamespacedSecret(mod.metadata?.name ?? "", namespace);
|
|
596
|
-
await coreV1Api.createNamespacedSecret(namespace, mod);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
const svc = this.service();
|
|
600
|
-
try {
|
|
601
|
-
Log.info("Creating service");
|
|
602
|
-
await coreV1Api.createNamespacedService(namespace, svc);
|
|
603
|
-
} catch (e) {
|
|
604
|
-
Log.debug(e instanceof HttpError ? e.body : e);
|
|
605
|
-
Log.info("Removing and re-creating service");
|
|
606
|
-
await coreV1Api.deleteNamespacedService(svc.metadata?.name ?? "", namespace);
|
|
607
|
-
await coreV1Api.createNamespacedService(namespace, svc);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
const tls = this.tlsSecret();
|
|
611
|
-
try {
|
|
612
|
-
Log.info("Creating TLS secret");
|
|
613
|
-
await coreV1Api.createNamespacedSecret(namespace, tls);
|
|
614
|
-
} catch (e) {
|
|
615
|
-
Log.debug(e instanceof HttpError ? e.body : e);
|
|
616
|
-
Log.info("Removing and re-creating TLS secret");
|
|
617
|
-
await coreV1Api.deleteNamespacedSecret(tls.metadata?.name ?? "", namespace);
|
|
618
|
-
await coreV1Api.createNamespacedSecret(namespace, tls);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const apiToken = this.apiTokenSecret();
|
|
622
|
-
try {
|
|
623
|
-
Log.info("Creating API token secret");
|
|
624
|
-
await coreV1Api.createNamespacedSecret(namespace, apiToken);
|
|
625
|
-
} catch (e) {
|
|
626
|
-
Log.debug(e instanceof HttpError ? e.body : e);
|
|
627
|
-
Log.info("Removing and re-creating API token secret");
|
|
628
|
-
await coreV1Api.deleteNamespacedSecret(apiToken.metadata?.name ?? "", namespace);
|
|
629
|
-
await coreV1Api.createNamespacedSecret(namespace, apiToken);
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
const dep = this.deployment(hash);
|
|
633
|
-
try {
|
|
634
|
-
Log.info("Creating deployment");
|
|
635
|
-
await appsApi.createNamespacedDeployment(namespace, dep);
|
|
636
|
-
} catch (e) {
|
|
637
|
-
Log.debug(e instanceof HttpError ? e.body : e);
|
|
638
|
-
Log.info("Removing and re-creating deployment");
|
|
639
|
-
await appsApi.deleteNamespacedDeployment(dep.metadata?.name ?? "", namespace);
|
|
640
|
-
await appsApi.createNamespacedDeployment(namespace, dep);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|