pepr 0.1.27 → 0.1.28

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.
Files changed (59) hide show
  1. package/dist/index.d.ts +5 -0
  2. package/dist/index.js +5 -0
  3. package/dist/package.json +76 -0
  4. package/dist/src/cli/banner.d.ts +1 -0
  5. package/dist/{pepr-cli.js → src/cli/banner.js} +1 -1251
  6. package/dist/src/cli/build.d.ts +7 -0
  7. package/dist/src/cli/build.js +95 -0
  8. package/dist/src/cli/capability.d.ts +2 -0
  9. package/dist/src/cli/capability.js +12 -0
  10. package/dist/src/cli/deploy.d.ts +2 -0
  11. package/dist/src/cli/deploy.js +49 -0
  12. package/dist/src/cli/dev.d.ts +2 -0
  13. package/dist/src/cli/dev.js +90 -0
  14. package/dist/src/cli/index.d.ts +1 -0
  15. package/dist/src/cli/index.js +28 -0
  16. package/dist/src/cli/init/index.d.ts +2 -0
  17. package/dist/src/cli/init/index.js +48 -0
  18. package/dist/src/cli/init/templates.d.ts +82 -0
  19. package/dist/src/cli/init/templates.js +224 -0
  20. package/dist/src/cli/init/utils.d.ts +20 -0
  21. package/dist/src/cli/init/utils.js +50 -0
  22. package/dist/src/cli/init/walkthrough.d.ts +7 -0
  23. package/dist/src/cli/init/walkthrough.js +76 -0
  24. package/dist/src/cli/root.d.ts +4 -0
  25. package/dist/src/cli/root.js +14 -0
  26. package/dist/src/cli/test.d.ts +2 -0
  27. package/dist/src/cli/test.js +45 -0
  28. package/dist/src/lib/capability.d.ts +26 -0
  29. package/dist/src/lib/capability.js +112 -0
  30. package/dist/src/lib/controller.d.ts +13 -0
  31. package/dist/src/lib/controller.js +77 -0
  32. package/dist/src/lib/filter.d.ts +10 -0
  33. package/dist/src/lib/filter.js +41 -0
  34. package/dist/src/lib/k8s/index.d.ts +4 -0
  35. package/dist/src/lib/k8s/index.js +7 -0
  36. package/dist/src/lib/k8s/kinds.d.ts +3 -0
  37. package/dist/src/lib/k8s/kinds.js +427 -0
  38. package/dist/src/lib/k8s/tls.d.ts +17 -0
  39. package/dist/src/lib/k8s/tls.js +67 -0
  40. package/dist/src/lib/k8s/types.d.ts +136 -0
  41. package/dist/src/lib/k8s/types.js +9 -0
  42. package/dist/src/lib/k8s/upstream.d.ts +1 -0
  43. package/dist/src/lib/k8s/upstream.js +3 -0
  44. package/dist/src/lib/k8s/webhook.d.ts +33 -0
  45. package/dist/src/lib/k8s/webhook.js +490 -0
  46. package/dist/src/lib/logger.d.ts +54 -0
  47. package/dist/{types-1709b44f.js → src/lib/logger.js} +3 -40
  48. package/dist/src/lib/module.d.ts +22 -0
  49. package/dist/src/lib/module.js +32 -0
  50. package/dist/src/lib/processor.d.ts +4 -0
  51. package/dist/src/lib/processor.js +66 -0
  52. package/dist/src/lib/request.d.ts +77 -0
  53. package/dist/src/lib/request.js +117 -0
  54. package/dist/src/lib/types.d.ts +187 -0
  55. package/dist/src/lib/types.js +31 -0
  56. package/package.json +8 -11
  57. package/tsconfig.build.json +4 -0
  58. package/dist/pepr-core.js +0 -949
  59. package/tsconfig.json +0 -17
@@ -1,28 +1,6 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- var json = require('@rollup/plugin-json');
5
- var nodeResolve = require('@rollup/plugin-node-resolve');
6
- var typescript = require('@rollup/plugin-typescript');
7
- var fs = require('fs');
8
- var path = require('path');
9
- var rollup = require('rollup');
10
- var types = require('./types-1709b44f.js');
11
- var clientNode = require('@kubernetes/client-node');
12
- var zlib = require('zlib');
13
- var forge = require('node-forge');
14
- var prompt = require('prompts');
15
- var child_process = require('child_process');
16
- var chokidar = require('chokidar');
17
- var util = require('util');
18
- var uuid = require('uuid');
19
- var commander = require('commander');
20
-
21
- var version = "0.1.27";
22
-
23
1
  // SPDX-License-Identifier: Apache-2.0
24
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
25
- const banner = `                                                                                                                                           
3
+ export const banner = `                                                                                                                                           
26
4
                                                                                                                                             
27
5
                                                                                                                                             
28
6
                                                                                                                                             
@@ -87,1231 +65,3 @@ const banner = `    [38;5;
87
65
                                                                                                                                             
88
66
                                                                                                                                             
89
67
  `;
90
-
91
- const caName = "Pepr Ephemeral CA";
92
- /**
93
- * Generates a self-signed CA and server certificate with Subject Alternative Names (SANs) for the K8s webhook.
94
- *
95
- * @param {string} name - The name to use for the server certificate's Common Name and SAN DNS entry.
96
- * @returns {TLSOut} - An object containing the Base64-encoded CA, server certificate, and server private key.
97
- */
98
- function genTLS(name) {
99
- // Generate a new CA key pair and create a self-signed CA certificate
100
- const caKeys = forge.pki.rsa.generateKeyPair(2048);
101
- const caCert = genCert(caKeys, caName, [{ name: "commonName", value: caName }]);
102
- caCert.setExtensions([
103
- {
104
- name: "basicConstraints",
105
- cA: true,
106
- },
107
- {
108
- name: "keyUsage",
109
- keyCertSign: true,
110
- digitalSignature: true,
111
- nonRepudiation: true,
112
- keyEncipherment: true,
113
- dataEncipherment: true,
114
- },
115
- ]);
116
- // Generate a new server key pair and create a server certificate signed by the CA
117
- const serverKeys = forge.pki.rsa.generateKeyPair(2048);
118
- const serverCert = genCert(serverKeys, name, caCert.subject.attributes);
119
- // Sign both certificates with the CA private key
120
- caCert.sign(caKeys.privateKey, forge.md.sha256.create());
121
- serverCert.sign(caKeys.privateKey, forge.md.sha256.create());
122
- // Convert the keys and certificates to PEM format
123
- const pem = {
124
- ca: forge.pki.certificateToPem(caCert),
125
- crt: forge.pki.certificateToPem(serverCert),
126
- key: forge.pki.privateKeyToPem(serverKeys.privateKey),
127
- };
128
- // Base64-encode the PEM strings
129
- const ca = Buffer.from(pem.ca).toString("base64");
130
- const key = Buffer.from(pem.key).toString("base64");
131
- const crt = Buffer.from(pem.crt).toString("base64");
132
- return { ca, key, crt, pem };
133
- }
134
- function genCert(key, name, issuer) {
135
- const crt = forge.pki.createCertificate();
136
- crt.publicKey = key.publicKey;
137
- crt.serialNumber = "01";
138
- crt.validity.notBefore = new Date();
139
- crt.validity.notAfter = new Date();
140
- crt.validity.notAfter.setFullYear(crt.validity.notBefore.getFullYear() + 1);
141
- // Add SANs to the server certificate
142
- crt.setExtensions([
143
- {
144
- name: "subjectAltName",
145
- altNames: [
146
- {
147
- type: 2,
148
- value: name,
149
- },
150
- ],
151
- },
152
- ]);
153
- // Set the server certificate's issuer to the CA
154
- crt.setIssuer(issuer);
155
- return crt;
156
- }
157
-
158
- // SPDX-License-Identifier: Apache-2.0
159
- const peprIgnore = {
160
- key: "pepr.dev",
161
- operator: "NotIn",
162
- values: ["ignore"],
163
- };
164
- class Webhook {
165
- get tls() {
166
- return this._tls;
167
- }
168
- constructor(config, host) {
169
- this.config = config;
170
- this.host = host;
171
- this.name = `pepr-${config.uuid}`;
172
- this.image = `ghcr.io/defenseunicorns/pepr/controller:${config.version}`;
173
- // Generate the ephemeral tls things
174
- this._tls = genTLS(this.host || `${this.name}.pepr-system.svc`);
175
- }
176
- /** Generate the pepr-system namespace */
177
- namespace() {
178
- return {
179
- apiVersion: "v1",
180
- kind: "Namespace",
181
- metadata: { name: "pepr-system" },
182
- };
183
- }
184
- /**
185
- * Grants the controller access to cluster resources beyond the mutating webhook.
186
- *
187
- * @todo: should dynamically generate this based on resources used by the module. will also need to explore how this should work for multiple modules.
188
- * @returns
189
- */
190
- clusterRole() {
191
- return {
192
- apiVersion: "rbac.authorization.k8s.io/v1",
193
- kind: "ClusterRole",
194
- metadata: { name: this.name },
195
- rules: [
196
- {
197
- // @todo: make this configurable
198
- apiGroups: ["*"],
199
- resources: ["*"],
200
- verbs: ["create", "delete", "get", "list", "patch", "update", "watch"],
201
- },
202
- ],
203
- };
204
- }
205
- clusterRoleBinding() {
206
- const name = this.name;
207
- return {
208
- apiVersion: "rbac.authorization.k8s.io/v1",
209
- kind: "ClusterRoleBinding",
210
- metadata: { name },
211
- roleRef: {
212
- apiGroup: "rbac.authorization.k8s.io",
213
- kind: "ClusterRole",
214
- name,
215
- },
216
- subjects: [
217
- {
218
- kind: "ServiceAccount",
219
- name,
220
- namespace: "pepr-system",
221
- },
222
- ],
223
- };
224
- }
225
- serviceAccount() {
226
- return {
227
- apiVersion: "v1",
228
- kind: "ServiceAccount",
229
- metadata: {
230
- name: this.name,
231
- namespace: "pepr-system",
232
- },
233
- };
234
- }
235
- tlsSecret() {
236
- return {
237
- apiVersion: "v1",
238
- kind: "Secret",
239
- metadata: {
240
- name: `${this.name}-tls`,
241
- namespace: "pepr-system",
242
- },
243
- type: "kubernetes.io/tls",
244
- data: {
245
- "tls.crt": this._tls.crt,
246
- "tls.key": this._tls.key,
247
- },
248
- };
249
- }
250
- mutatingWebhook() {
251
- const { name } = this;
252
- const ignore = [peprIgnore];
253
- // Add any namespaces to ignore
254
- if (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
- const clientConfig = {
262
- caBundle: this._tls.ca,
263
- };
264
- // If a host is specified, use that with a port of 3000
265
- if (this.host) {
266
- clientConfig.url = `https://${this.host}:3000/mutate`;
267
- }
268
- else {
269
- // Otherwise, use the service
270
- clientConfig.service = {
271
- name: this.name,
272
- namespace: "pepr-system",
273
- path: "/mutate",
274
- };
275
- }
276
- return {
277
- apiVersion: "admissionregistration.k8s.io/v1",
278
- kind: "MutatingWebhookConfiguration",
279
- metadata: { name },
280
- webhooks: [
281
- {
282
- name: `${name}.pepr.dev`,
283
- admissionReviewVersions: ["v1", "v1beta1"],
284
- clientConfig,
285
- failurePolicy: "Ignore",
286
- matchPolicy: "Equivalent",
287
- timeoutSeconds: 15,
288
- namespaceSelector: {
289
- matchExpressions: ignore,
290
- },
291
- objectSelector: {
292
- matchExpressions: ignore,
293
- },
294
- // @todo: make this configurable
295
- rules: [
296
- {
297
- apiGroups: ["*"],
298
- apiVersions: ["*"],
299
- operations: ["CREATE", "UPDATE", "DELETE"],
300
- resources: ["*/*"],
301
- },
302
- ],
303
- // @todo: track side effects state
304
- sideEffects: "None",
305
- },
306
- ],
307
- };
308
- }
309
- deployment() {
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
- livenessProbe: {
342
- httpGet: {
343
- path: "/healthz",
344
- port: 3000,
345
- scheme: "HTTPS",
346
- },
347
- },
348
- ports: [
349
- {
350
- containerPort: 3000,
351
- },
352
- ],
353
- resources: {
354
- requests: {
355
- memory: "64Mi",
356
- cpu: "100m",
357
- },
358
- limits: {
359
- memory: "256Mi",
360
- cpu: "500m",
361
- },
362
- },
363
- volumeMounts: [
364
- {
365
- name: "tls-certs",
366
- mountPath: "/etc/certs",
367
- readOnly: true,
368
- },
369
- {
370
- name: "module",
371
- mountPath: "/app/module.js.gz",
372
- readOnly: true,
373
- },
374
- ],
375
- },
376
- ],
377
- volumes: [
378
- {
379
- name: "tls-certs",
380
- secret: {
381
- secretName: `${this.name}-tls`,
382
- },
383
- },
384
- {
385
- name: "module",
386
- secret: {
387
- secretName: `${this.name}-module`,
388
- },
389
- },
390
- ],
391
- },
392
- },
393
- },
394
- };
395
- }
396
- /** Only permit the */
397
- networkPolicy() {
398
- return {
399
- apiVersion: "networking.k8s.io/v1",
400
- kind: "NetworkPolicy",
401
- metadata: {
402
- name: this.name,
403
- namespace: "pepr-system",
404
- },
405
- spec: {
406
- podSelector: {
407
- matchLabels: {
408
- app: this.name,
409
- },
410
- },
411
- policyTypes: ["Ingress"],
412
- ingress: [
413
- {
414
- from: [
415
- {
416
- namespaceSelector: {
417
- matchLabels: {
418
- "kubernetes.io/metadata.name": "kube-system",
419
- },
420
- },
421
- },
422
- ],
423
- ports: [
424
- {
425
- protocol: "TCP",
426
- port: 443,
427
- },
428
- ],
429
- },
430
- ],
431
- },
432
- };
433
- }
434
- service() {
435
- return {
436
- apiVersion: "v1",
437
- kind: "Service",
438
- metadata: {
439
- name: this.name,
440
- namespace: "pepr-system",
441
- },
442
- spec: {
443
- selector: {
444
- app: this.name,
445
- },
446
- ports: [
447
- {
448
- port: 443,
449
- targetPort: 3000,
450
- },
451
- ],
452
- },
453
- };
454
- }
455
- moduleSecret(data) {
456
- // Compress the data
457
- const compressed = zlib.gzipSync(data);
458
- return {
459
- apiVersion: "v1",
460
- kind: "Secret",
461
- metadata: {
462
- name: `${this.name}-module`,
463
- namespace: "pepr-system",
464
- },
465
- type: "Opaque",
466
- data: {
467
- module: compressed.toString("base64"),
468
- },
469
- };
470
- }
471
- zarfYaml(path) {
472
- const zarfCfg = {
473
- kind: "ZarfPackageConfig",
474
- metadata: {
475
- name: this.name,
476
- description: `Pepr Module: ${this.config.description}`,
477
- url: "https://github.com/defenseunicorns/pepr",
478
- version: this.config.version,
479
- },
480
- components: [
481
- {
482
- name: "module",
483
- required: true,
484
- manifests: [
485
- {
486
- name: "module",
487
- namespace: "pepr-system",
488
- files: [path],
489
- },
490
- ],
491
- images: [this.image],
492
- },
493
- ],
494
- };
495
- return clientNode.dumpYaml(zarfCfg, { noRefs: true });
496
- }
497
- allYaml(code) {
498
- const resources = [
499
- this.namespace(),
500
- this.networkPolicy(),
501
- this.clusterRole(),
502
- this.clusterRoleBinding(),
503
- this.serviceAccount(),
504
- this.tlsSecret(),
505
- this.mutatingWebhook(),
506
- this.deployment(),
507
- this.service(),
508
- this.moduleSecret(code),
509
- ];
510
- // Convert the resources to a single YAML string
511
- return resources.map(r => clientNode.dumpYaml(r, { noRefs: true })).join("---\n");
512
- }
513
- async deploy(code) {
514
- types.logger.info("Establishing connection to Kubernetes");
515
- const namespace = "pepr-system";
516
- // Deploy the resources using the k8s API
517
- const kubeConfig = new clientNode.KubeConfig();
518
- kubeConfig.loadFromDefault();
519
- const coreV1Api = kubeConfig.makeApiClient(clientNode.CoreV1Api);
520
- const rbacApi = kubeConfig.makeApiClient(clientNode.RbacAuthorizationV1Api);
521
- const appsApi = kubeConfig.makeApiClient(clientNode.AppsV1Api);
522
- const admissionApi = kubeConfig.makeApiClient(clientNode.AdmissionregistrationV1Api);
523
- const networkApi = kubeConfig.makeApiClient(clientNode.NetworkingV1Api);
524
- const ns = this.namespace();
525
- try {
526
- types.logger.info("Checking for namespace");
527
- await coreV1Api.readNamespace(namespace);
528
- }
529
- catch (e) {
530
- types.logger.debug(e.body);
531
- types.logger.info("Creating namespace");
532
- await coreV1Api.createNamespace(ns);
533
- }
534
- const netpol = this.networkPolicy();
535
- try {
536
- types.logger.info("Checking for network policy");
537
- await networkApi.readNamespacedNetworkPolicy(netpol.metadata.name, namespace);
538
- }
539
- catch (e) {
540
- types.logger.debug(e.body);
541
- types.logger.info("Creating network policy");
542
- await networkApi.createNamespacedNetworkPolicy(namespace, netpol);
543
- }
544
- const wh = this.mutatingWebhook();
545
- try {
546
- types.logger.info("Creating mutating webhook");
547
- await admissionApi.createMutatingWebhookConfiguration(wh);
548
- }
549
- catch (e) {
550
- types.logger.debug(e.body);
551
- types.logger.info("Removing and re-creating mutating webhook");
552
- await admissionApi.deleteMutatingWebhookConfiguration(wh.metadata.name);
553
- await admissionApi.createMutatingWebhookConfiguration(wh);
554
- }
555
- const crb = this.clusterRoleBinding();
556
- try {
557
- types.logger.info("Creating cluster role binding");
558
- await rbacApi.createClusterRoleBinding(crb);
559
- }
560
- catch (e) {
561
- types.logger.debug(e.body);
562
- types.logger.info("Removing and re-creating cluster role binding");
563
- await rbacApi.deleteClusterRoleBinding(crb.metadata.name);
564
- await rbacApi.createClusterRoleBinding(crb);
565
- }
566
- const cr = this.clusterRole();
567
- try {
568
- types.logger.info("Creating cluster role");
569
- await rbacApi.createClusterRole(cr);
570
- }
571
- catch (e) {
572
- types.logger.debug(e.body);
573
- types.logger.info("Removing and re-creating the cluster role");
574
- try {
575
- await rbacApi.deleteClusterRole(cr.metadata.name);
576
- await rbacApi.createClusterRole(cr);
577
- }
578
- catch (e) {
579
- types.logger.debug(e.body);
580
- }
581
- }
582
- const sa = this.serviceAccount();
583
- try {
584
- types.logger.info("Creating service account");
585
- await coreV1Api.createNamespacedServiceAccount(namespace, sa);
586
- }
587
- catch (e) {
588
- types.logger.debug(e.body);
589
- types.logger.info("Removing and re-creating service account");
590
- await coreV1Api.deleteNamespacedServiceAccount(sa.metadata.name, namespace);
591
- await coreV1Api.createNamespacedServiceAccount(namespace, sa);
592
- }
593
- // If a host is specified, we don't need to deploy the rest of the resources
594
- if (this.host) {
595
- return;
596
- }
597
- const mod = this.moduleSecret(code);
598
- try {
599
- types.logger.info("Creating module secret");
600
- await coreV1Api.createNamespacedSecret(namespace, mod);
601
- }
602
- catch (e) {
603
- types.logger.debug(e.body);
604
- types.logger.info("Removing and re-creating module secret");
605
- await coreV1Api.deleteNamespacedSecret(mod.metadata.name, namespace);
606
- await coreV1Api.createNamespacedSecret(namespace, mod);
607
- }
608
- const svc = this.service();
609
- try {
610
- types.logger.info("Creating service");
611
- await coreV1Api.createNamespacedService(namespace, svc);
612
- }
613
- catch (e) {
614
- types.logger.debug(e.body);
615
- types.logger.info("Removing and re-creating service");
616
- await coreV1Api.deleteNamespacedService(svc.metadata.name, namespace);
617
- await coreV1Api.createNamespacedService(namespace, svc);
618
- }
619
- const tls = this.tlsSecret();
620
- try {
621
- types.logger.info("Creating TLS secret");
622
- await coreV1Api.createNamespacedSecret(namespace, tls);
623
- }
624
- catch (e) {
625
- types.logger.debug(e.body);
626
- types.logger.info("Removing and re-creating TLS secret");
627
- await coreV1Api.deleteNamespacedSecret(tls.metadata.name, namespace);
628
- await coreV1Api.createNamespacedSecret(namespace, tls);
629
- }
630
- const dep = this.deployment();
631
- try {
632
- types.logger.info("Creating deployment");
633
- await appsApi.createNamespacedDeployment(namespace, dep);
634
- }
635
- catch (e) {
636
- types.logger.debug(e.body);
637
- types.logger.info("Removing and re-creating deployment");
638
- await appsApi.deleteNamespacedDeployment(dep.metadata.name, namespace);
639
- await appsApi.createNamespacedDeployment(namespace, dep);
640
- }
641
- }
642
- }
643
-
644
- // SPDX-License-Identifier: Apache-2.0
645
- function build (program) {
646
- program
647
- .command("build")
648
- .description("Build a Pepr Module for deployment")
649
- .option("-d, --dir [directory]", "Pepr module directory", ".")
650
- .action(async (opts) => {
651
- // Build the module
652
- const { cfg, path: path$1, uuid } = await buildModule(opts.dir);
653
- // Read the compiled module code
654
- const code = await fs.promises.readFile(path$1, { encoding: "utf-8" });
655
- // Generate a secret for the module
656
- const webhook = new Webhook({
657
- ...cfg.pepr,
658
- description: cfg.description,
659
- });
660
- const yamlFile = `pepr-module-${uuid}.yaml`;
661
- const yamlPath = path.resolve("dist", yamlFile);
662
- const yaml = webhook.allYaml(code);
663
- const zarfPath = path.resolve("dist", "zarf.yaml");
664
- const zarf = webhook.zarfYaml(yamlFile);
665
- await fs.promises.writeFile(yamlPath, yaml);
666
- await fs.promises.writeFile(zarfPath, zarf);
667
- types.logger.debug(`Module compiled successfully at ${path$1}`);
668
- types.logger.info(`K8s resource for the module saved to ${yamlPath}`);
669
- });
670
- }
671
- const externalLibs = [
672
- /@kubernetes\/client-node(\/.*)?/,
673
- "commander",
674
- "express",
675
- "fast-json-patch",
676
- "pepr",
677
- "ramda",
678
- ];
679
- async function buildModule(moduleDir) {
680
- try {
681
- // Resolve the path to the module's package.json file
682
- const cfgPath = path.resolve(moduleDir, "package.json");
683
- const input = path.resolve(moduleDir, "pepr.ts");
684
- // Read the module's UUID from the package.json filel
685
- const moduleText = await fs.promises.readFile(cfgPath, { encoding: "utf-8" });
686
- const cfg = JSON.parse(moduleText);
687
- const { uuid } = cfg.pepr;
688
- const name = `pepr-${uuid}.js`;
689
- // Exit if the module's UUID could not be found
690
- if (!uuid) {
691
- throw new Error("Could not load the uuid in package.json");
692
- }
693
- const plugins = [
694
- nodeResolve({
695
- preferBuiltins: true,
696
- }),
697
- json(),
698
- typescript({
699
- tsconfig: "./tsconfig.json",
700
- declaration: false,
701
- removeComments: true,
702
- sourceMap: false,
703
- include: ["**/*.ts", "node_modules/pepr/**/*.ts"],
704
- }),
705
- ];
706
- // Build the module using Rollup
707
- const bundle = await rollup.rollup({
708
- plugins,
709
- external: externalLibs,
710
- treeshake: true,
711
- input,
712
- });
713
- // Write the module to the dist directory
714
- await bundle.write({
715
- dir: "dist",
716
- format: "cjs",
717
- entryFileNames: name,
718
- });
719
- return {
720
- path: path.resolve("dist", name),
721
- cfg,
722
- uuid,
723
- };
724
- }
725
- catch (e) {
726
- // On any other error, exit with a non-zero exit code
727
- types.logger.debug(e);
728
- types.logger.error(e.message);
729
- process.exit(1);
730
- }
731
- }
732
-
733
- // SPDX-License-Identifier: Apache-2.0
734
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
735
- function capability (program) {
736
- program
737
- .command("new")
738
- .description("Create a new Pepr Capability")
739
- .option("-d, --dir [directory]", "Pepr module directory", ".")
740
- .action(() => {
741
- // TODO: Create a new capability
742
- console.log("new");
743
- });
744
- }
745
-
746
- // SPDX-License-Identifier: Apache-2.0
747
- function deploy (program) {
748
- program
749
- .command("deploy")
750
- .description("Deploy a Pepr Module")
751
- .option("-d, --dir [directory]", "Pepr module directory", ".")
752
- .option("-i, --image [image]", "Override the image tag")
753
- .option("-f, --force", "Force redeployment")
754
- .action(async (opts) => {
755
- if (!opts.force) {
756
- // Prompt the user to confirm
757
- const confirm = await prompt.prompt({
758
- type: "confirm",
759
- name: "confirm",
760
- message: "This will remove and redeploy the module. Continue?",
761
- });
762
- // Exit if the user doesn't confirm
763
- if (!confirm.confirm) {
764
- process.exit(0);
765
- }
766
- }
767
- // Build the module
768
- const { cfg, path } = await buildModule(opts.dir);
769
- // Read the compiled module code
770
- const code = await fs.promises.readFile(path, { encoding: "utf-8" });
771
- // Generate a secret for the module
772
- const webhook = new Webhook({
773
- ...cfg.pepr,
774
- description: cfg.description,
775
- });
776
- if (opts.image) {
777
- webhook.image = opts.image;
778
- }
779
- try {
780
- await webhook.deploy(code);
781
- types.logger.info(`Module deployed successfully`);
782
- }
783
- catch (e) {
784
- types.logger.error(`Error deploying module: ${e}`);
785
- process.exit(1);
786
- }
787
- });
788
- }
789
-
790
- // SPDX-License-Identifier: Apache-2.0
791
- function dev (program) {
792
- program
793
- .command("dev")
794
- .description("Setup a local webhook development environment")
795
- .option("-d, --dir [directory]", "Pepr module directory", ".")
796
- .option("-h, --host [host]", "Host to listen on", "host.docker.internal")
797
- .action(async (opts) => {
798
- // Prompt the user to confirm
799
- const confirm = await prompt.prompt({
800
- type: "confirm",
801
- name: "confirm",
802
- message: "This will remove and redeploy the module. Continue?",
803
- });
804
- // Exit if the user doesn't confirm
805
- if (!confirm.confirm) {
806
- process.exit(0);
807
- }
808
- // Build the module
809
- const { cfg, path: path$1 } = await buildModule(opts.dir);
810
- // Read the compiled module code
811
- const code = await fs.promises.readFile(path$1, { encoding: "utf-8" });
812
- // Generate a secret for the module
813
- const webhook = new Webhook({
814
- ...cfg.pepr,
815
- description: cfg.description,
816
- }, opts.host);
817
- // Write the TLS cert and key to disk
818
- await fs.promises.writeFile("insecure-tls.crt", webhook.tls.pem.crt);
819
- await fs.promises.writeFile("insecure-tls.key", webhook.tls.pem.key);
820
- try {
821
- await webhook.deploy(code);
822
- types.logger.info(`Module deployed successfully`);
823
- const moduleFiles = path.resolve(opts.dir, "**", "*.ts");
824
- const watcher = chokidar.watch(moduleFiles);
825
- const peprTS = path.resolve(opts.dir, "pepr.ts");
826
- let program;
827
- // Run the module once to start the server
828
- runDev(peprTS);
829
- // Watch for changes
830
- watcher.on("ready", () => {
831
- types.logger.info(`Watching for changes in ${moduleFiles}`);
832
- watcher.on("all", async (event, path) => {
833
- types.logger.debug({ event, path }, "File changed");
834
- // Kill the running process
835
- if (program) {
836
- program.kill("SIGKILL");
837
- }
838
- // Start the process again
839
- program = runDev(peprTS);
840
- });
841
- });
842
- }
843
- catch (e) {
844
- types.logger.error(`Error deploying module: ${e}`);
845
- process.exit(1);
846
- }
847
- });
848
- }
849
- function runDev(path) {
850
- try {
851
- const program = child_process.spawn("./node_modules/.bin/ts-node", [path], {
852
- env: {
853
- ...process.env,
854
- SSL_KEY_PATH: "insecure-tls.key",
855
- SSL_CERT_PATH: "insecure-tls.crt",
856
- },
857
- });
858
- program.stdout.on("data", data => console.log(data.toString()));
859
- program.stderr.on("data", data => console.error(data.toString()));
860
- program.on("close", code => {
861
- types.logger.info(`Process exited with code ${code}`);
862
- });
863
- return program;
864
- }
865
- catch (e) {
866
- types.logger.debug(e);
867
- types.logger.error(`Error running module: ${e}`);
868
- process.exit(1);
869
- }
870
- }
871
-
872
- // SPDX-License-Identifier: Apache-2.0
873
- /**
874
- * Sanitize a user input name to be used as a pepr module directory name
875
- *
876
- * @param name the user input name
877
- * @returns the sanitized name
878
- */
879
- function sanitizeName(name) {
880
- // Replace any characters outside of [^a-z0-9-] with "-"
881
- let sanitized = name.toLowerCase().replace(/[^a-z0-9-]+/gi, "-");
882
- // Remove any leading or trailing hyphens
883
- sanitized = sanitized.replace(/^-+|-+$/g, "");
884
- // Replace multiple hyphens with a single hyphen
885
- sanitized = sanitized.replace(/--+/g, "-");
886
- return sanitized;
887
- }
888
- /**
889
- * Creates a directory and throws an error if it already exists
890
- *
891
- * @param dir - The directory to create
892
- */
893
- async function createDir(dir) {
894
- try {
895
- await fs.promises.mkdir(dir);
896
- }
897
- catch (err) {
898
- // The directory already exists
899
- if (err.code === "EEXIST") {
900
- throw new Error(`Directory ${dir} already exists`);
901
- }
902
- else {
903
- throw err;
904
- }
905
- }
906
- }
907
- /**
908
- * Write data to a file on disk
909
- * @param path - The path to the file
910
- * @param data - The data to write
911
- * @returns A promise that resolves when the file has been written
912
- */
913
- function write(path, data) {
914
- // If the data is not a string, stringify it
915
- if (typeof data !== "string") {
916
- data = JSON.stringify(data, null, 2);
917
- }
918
- return fs.promises.writeFile(path, data);
919
- }
920
-
921
- // SPDX-License-Identifier: Apache-2.0
922
- function genPeprTS() {
923
- return {
924
- path: "pepr.ts",
925
- data: `import { PeprModule } from "pepr";
926
- import cfg from "./package.json";
927
- import { HelloPepr } from "./capabilities/hello-pepr";
928
-
929
- /**
930
- * This is the main entrypoint for the Pepr module. It is the file that is run when the module is started.
931
- * This is where you register your configurations and capabilities with the module.
932
- */
933
- new PeprModule(cfg, [
934
- // "HelloPepr" is a demo capability that is included with Pepr. You can remove it if you want.
935
- HelloPepr,
936
-
937
- // Your additional capabilities go here
938
- ]);
939
- `,
940
- };
941
- }
942
- function genPkgJSON(opts) {
943
- // Generate a random UUID for the module based on the module name
944
- const uuid$1 = uuid.v5(opts.name, uuid.v4());
945
- // Generate a name for the module based on the module name
946
- const name = sanitizeName(opts.name);
947
- const data = {
948
- name,
949
- version: "0.0.1",
950
- description: opts.description,
951
- keywords: ["pepr", "k8s", "policy-engine", "pepr-module", "security"],
952
- pepr: {
953
- name: opts.name.trim(),
954
- version,
955
- uuid: uuid$1,
956
- onError: opts.errorBehavior,
957
- alwaysIgnore: {
958
- namespaces: [],
959
- labels: [],
960
- },
961
- },
962
- scripts: {
963
- build: "pepr build",
964
- start: "pepr test",
965
- },
966
- dependencies: {
967
- pepr: `^${version}`,
968
- },
969
- };
970
- return {
971
- data,
972
- path: "package.json",
973
- print: util.inspect(data, false, 5, true),
974
- };
975
- }
976
- const tsConfig = {
977
- path: "tsconfig.json",
978
- data: {
979
- compilerOptions: {
980
- esModuleInterop: true,
981
- lib: ["ES2020"],
982
- moduleResolution: "node",
983
- resolveJsonModule: true,
984
- rootDir: ".",
985
- strict: false,
986
- target: "ES2020",
987
- },
988
- include: ["**/*.ts", "node_modules/pepr/**/*.ts"],
989
- },
990
- };
991
- const gitIgnore = {
992
- path: ".gitignore",
993
- data: `# Ignore node_modules
994
- node_modules
995
- dist
996
- `,
997
- };
998
- const prettierRC = {
999
- path: ".prettierrc",
1000
- data: {
1001
- arrowParens: "avoid",
1002
- bracketSameLine: false,
1003
- bracketSpacing: true,
1004
- embeddedLanguageFormatting: "auto",
1005
- insertPragma: false,
1006
- printWidth: 80,
1007
- quoteProps: "as-needed",
1008
- requirePragma: false,
1009
- semi: true,
1010
- tabWidth: 2,
1011
- useTabs: false,
1012
- },
1013
- };
1014
- const readme = {
1015
- path: "README.md",
1016
- data: `# Pepr Module
1017
-
1018
- This is a Pepr module. It is a module that can be used with the [Pepr]() framework.
1019
-
1020
- The \`capabilities\` directory contains all the capabilities for this module. By default,
1021
- a capability is a single typescript file in the format of \`capability-name.ts\` that is
1022
- imported in the root \`pepr.ts\` file as \`import { HelloPepr } from "./capabilities/hello-pepr";\`.
1023
- Because this is typescript, you can organize this however you choose, e.g. creating a sub-folder
1024
- per-capability or common logic in shared files or folders.
1025
-
1026
- Example Structure:
1027
-
1028
- \`\`\`
1029
- Module Root
1030
- ├── package.json
1031
- ├── pepr.ts
1032
- └── capabilities
1033
- ├── example-one.ts
1034
- ├── example-three.ts
1035
- └── example-two.ts
1036
- \`\`\`
1037
- `,
1038
- };
1039
- const capabilityHelloPeprTS = {
1040
- path: "hello-pepr.ts",
1041
- data: `import { Capability, a } from "pepr";
1042
-
1043
- export const HelloPepr = new Capability({
1044
- name: "hello-pepr",
1045
- description: "A simple example capability to show how things work.",
1046
- namespaces: ["pepr-demo"],
1047
- });
1048
-
1049
- // Use the 'When' function to create a new Capability Action
1050
- const { When } = HelloPepr;
1051
-
1052
- /**
1053
- * This is a single Capability Action. They can be in the same file or put imported from other files.
1054
- * In this exmaple, when a ConfigMap is created with the name \`example-1\`, then add a label and annotation.
1055
- *
1056
- * Equivelant to manually running:
1057
- * \`kubectl label configmap example-1 pepr=was-here\`
1058
- * \`kubectl annotate configmap example-1 pepr.dev=annotations-work-too\`
1059
- */
1060
- When(a.ConfigMap)
1061
- .IsCreated()
1062
- .WithName("example-1")
1063
- .Then(request =>
1064
- request
1065
- .SetLabel("pepr", "was-here")
1066
- .SetAnnotation("pepr.dev", "annotations-work-too")
1067
- );
1068
-
1069
- /**
1070
- * This Capabiility Action does the exact same changes for example-2, except this time it uses the \`.ThenSet()\` feature.
1071
- * You can stack multiple \`.Then()\` calls, but only a single \`.ThenSet()\`
1072
- */
1073
- When(a.ConfigMap)
1074
- .IsCreated()
1075
- .WithName("example-2")
1076
- .ThenSet({
1077
- metadata: {
1078
- labels: {
1079
- pepr: "was-here",
1080
- },
1081
- annotations: {
1082
- "pepr.dev": "annotations-work-too",
1083
- },
1084
- },
1085
- });
1086
-
1087
- /**
1088
- * This Capability Action combines different styles. Unlike the previous actions, this one will look for any ConfigMap
1089
- * in the \`pepr-demo\` namespace that has the label \`change=by-label\`. Note that all conditions added such as \`WithName()\`,
1090
- * \`WithLabel()\`, \`InNamespace()\`, are ANDs so all conditions must be true for the request to be procssed.
1091
- */
1092
- When(a.ConfigMap)
1093
- .IsCreated()
1094
- .WithLabel("change", "by-label")
1095
- .Then(request => {
1096
- // The K8s object e are going to mutate
1097
- const cm = request.Raw;
1098
-
1099
- // Get the username and uid of the K8s reuest
1100
- const { username, uid } = request.Request.userInfo;
1101
-
1102
- // Store some data about the request in the configmap
1103
- cm.data["username"] = username;
1104
- cm.data["uid"] = uid;
1105
-
1106
- // You can still mix other ways of making changes too
1107
- request.SetAnnotation("pepr.dev", "making-waves");
1108
- });
1109
- `,
1110
- };
1111
- const capabilitySnippet = {
1112
- path: "pepr.code-snippets",
1113
- data: `{
1114
- "Create a new Pepr capability": {
1115
- "prefix": "create pepr capability",
1116
- "body": [
1117
- "import { Capability, a } from 'pepr';",
1118
- "",
1119
- "export const $\{TM_FILENAME_BASE/(.*)/$\{1:/pascalcase}/} = new Capability({",
1120
- "\\tname: '$\{TM_FILENAME_BASE}',",
1121
- "\\tdescription: '$\{1:A brief description of this capability.}',",
1122
- "\\tnamespaces: [$\{2:}],",
1123
- "});",
1124
- "",
1125
- "// Use the 'When' function to create a new Capability Action",
1126
- "const { When } = $\{TM_FILENAME_BASE/(.*)/$\{1:/pascalcase}/};",
1127
- "",
1128
- "// When(a.<Kind>).Is<Event>().Then(change => change.<changes>",
1129
- "When($\{3:})"
1130
- ],
1131
- "description": "Creates a new Pepr capability with a specified description, and optional namespaces, and adds a When statement for the specified value."
1132
- }
1133
- }`,
1134
- };
1135
-
1136
- // SPDX-License-Identifier: Apache-2.0
1137
- function walkthrough() {
1138
- const askName = {
1139
- type: "text",
1140
- name: "name",
1141
- message: "Enter a name for the new Pepr module. This will create a new directory based on the name.\n",
1142
- validate: async (val) => {
1143
- try {
1144
- const name = sanitizeName(val);
1145
- await fs.promises.access(name, fs.promises.constants.F_OK);
1146
- return "A directory with this name already exists";
1147
- }
1148
- catch (e) {
1149
- return val.length > 2 || "The name must be at least 3 characters long";
1150
- }
1151
- },
1152
- };
1153
- const askDescription = {
1154
- type: "text",
1155
- name: "description",
1156
- message: "(Recommended) Enter a description for the new Pepr module.\n",
1157
- };
1158
- const askErrorBehavior = {
1159
- type: "select",
1160
- name: "errorBehavior",
1161
- validate: val => types.ErrorBehavior[val],
1162
- message: "How do you want Pepr to handle errors encountered during K8s operations?",
1163
- choices: [
1164
- {
1165
- title: "Ignore",
1166
- value: types.ErrorBehavior.ignore,
1167
- description: "Pepr will continue processing and generate an entry in the Pepr Controller log.",
1168
- selected: true,
1169
- },
1170
- {
1171
- title: "Log an audit event",
1172
- value: types.ErrorBehavior.audit,
1173
- description: "Pepr will continue processing and generate an entry in the Pepr Controller log as well as an audit event in the cluster.",
1174
- },
1175
- {
1176
- title: "Reject the operation",
1177
- value: types.ErrorBehavior.reject,
1178
- description: "Pepr will reject the operation and return an error to the client.",
1179
- },
1180
- ],
1181
- };
1182
- return prompt([askName, askDescription, askErrorBehavior]);
1183
- }
1184
- async function confirm(dirName, packageJSON, peprTSPath) {
1185
- console.log(`
1186
- To be generated:
1187
-
1188
- \x1b[1m${dirName}\x1b[0m
1189
- ├── \x1b[1m${gitIgnore.path}\x1b[0m
1190
- ├── \x1b[1m${prettierRC.path}\x1b[0m
1191
- ├── \x1b[1mcapabilties\x1b[0m
1192
- | └── \x1b[1mhello-pepr.ts\x1b[0m
1193
- ├── \x1b[1m${packageJSON.path}\x1b[0m
1194
- ${packageJSON.print.replace(/^/gm, " │ ")}
1195
- ├── \x1b[1m${peprTSPath}\x1b[0m
1196
- ├── \x1b[1m${readme.path}\x1b[0m
1197
- └── \x1b[1m${tsConfig.path}\x1b[0m
1198
- `);
1199
- const confirm = await prompt({
1200
- type: "confirm",
1201
- name: "confirm",
1202
- message: "Create the new Pepr module?",
1203
- });
1204
- return confirm.confirm;
1205
- }
1206
-
1207
- // SPDX-License-Identifier: Apache-2.0
1208
- function init (program) {
1209
- program
1210
- .command("init")
1211
- .description("Initialize a new Pepr Module")
1212
- .action(async () => {
1213
- const response = await walkthrough();
1214
- const dirName = sanitizeName(response.name);
1215
- const packageJSON = genPkgJSON(response);
1216
- const peprTS = genPeprTS();
1217
- const confirmed = await confirm(dirName, packageJSON, peprTS.path);
1218
- if (confirmed) {
1219
- console.log("Creating new Pepr module...");
1220
- try {
1221
- await createDir(dirName);
1222
- await createDir(path.resolve(dirName, ".vscode"));
1223
- await createDir(path.resolve(dirName, "capabilities"));
1224
- await write(path.resolve(dirName, gitIgnore.path), gitIgnore.data);
1225
- await write(path.resolve(dirName, prettierRC.path), prettierRC.data);
1226
- await write(path.resolve(dirName, packageJSON.path), packageJSON.data);
1227
- await write(path.resolve(dirName, readme.path), readme.data);
1228
- await write(path.resolve(dirName, tsConfig.path), tsConfig.data);
1229
- await write(path.resolve(dirName, peprTS.path), peprTS.data);
1230
- await write(path.resolve(dirName, ".vscode", capabilitySnippet.path), capabilitySnippet.data);
1231
- await write(path.resolve(dirName, "capabilities", capabilityHelloPeprTS.path), capabilityHelloPeprTS.data);
1232
- // run npm install from the new directory
1233
- process.chdir(dirName);
1234
- child_process.execSync("npm install", {
1235
- stdio: "inherit",
1236
- });
1237
- console.log(`New Pepr module created at ${dirName}`);
1238
- console.log(`Open VSCode or your editor of choice in ${dirName} to get started!`);
1239
- }
1240
- catch (e) {
1241
- types.logger.debug(e);
1242
- types.logger.error(e.message);
1243
- process.exit(1);
1244
- }
1245
- }
1246
- });
1247
- }
1248
-
1249
- // SPDX-License-Identifier: Apache-2.0
1250
- class RootCmd extends commander.Command {
1251
- createCommand(name) {
1252
- const cmd = new commander.Command(name);
1253
- cmd.option("-l, --log-level [level]", "Log level: debug, info, warn, error", "info");
1254
- cmd.hook("preAction", run => {
1255
- types.logger.SetLogLevel(run.opts().logLevel);
1256
- });
1257
- return cmd;
1258
- }
1259
- }
1260
-
1261
- // SPDX-License-Identifier: Apache-2.0
1262
- const exec = util.promisify(child_process.exec);
1263
- function test (program) {
1264
- program
1265
- .command("test")
1266
- .description("Test a Pepr Module locally")
1267
- .option("-d, --dir [directory]", "Pepr module directory", ".")
1268
- .option("-w, --watch", "Watch for changes and re-run the test")
1269
- .action(async (opts) => {
1270
- types.logger.info("Test Module");
1271
- await buildAndTest(opts.dir);
1272
- if (opts.watch) {
1273
- const moduleFiles = path.resolve(opts.dir, "**", "*.ts");
1274
- const watcher = chokidar.watch(moduleFiles);
1275
- watcher.on("ready", () => {
1276
- types.logger.info(`Watching for changes in ${moduleFiles}`);
1277
- watcher.on("all", async (event, path) => {
1278
- types.logger.debug({ event, path }, "File changed");
1279
- await buildAndTest(opts.dir);
1280
- });
1281
- });
1282
- }
1283
- });
1284
- }
1285
- async function buildAndTest(dir) {
1286
- const { path } = await buildModule(dir);
1287
- types.logger.info(`Module built successfully at ${path}`);
1288
- try {
1289
- const { stdout, stderr } = await exec(`node ${path}`);
1290
- console.log(stdout);
1291
- console.log(stderr);
1292
- }
1293
- catch (e) {
1294
- types.logger.debug(e);
1295
- types.logger.error(`Error running module: ${e}`);
1296
- process.exit(1);
1297
- }
1298
- }
1299
-
1300
- // SPDX-License-Identifier: Apache-2.0
1301
- const program = new RootCmd();
1302
- program
1303
- .version(version)
1304
- .description(`Pepr Kubernetes Thingy (v${version})`)
1305
- .action(() => {
1306
- if (program.args.length < 1) {
1307
- console.log(banner);
1308
- program.help();
1309
- }
1310
- });
1311
- init(program);
1312
- build(program);
1313
- capability(program);
1314
- test(program);
1315
- deploy(program);
1316
- dev(program);
1317
- program.parse();