pepr 0.1.20 → 0.1.22

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/dist/pepr-cli.js CHANGED
@@ -21,7 +21,7 @@ var uuid = require('uuid');
21
21
  var commander = require('commander');
22
22
  var chokidar = require('chokidar');
23
23
 
24
- var version = "0.1.20";
24
+ var version = "0.1.22";
25
25
 
26
26
  // SPDX-License-Identifier: Apache-2.0
27
27
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
@@ -91,47 +91,71 @@ const banner = `    [38;5;
91
91
                                                                                                                                             
92
92
  `;
93
93
 
94
- // SPDX-License-Identifier: Apache-2.0
94
+ const caName = "Pepr Ephemeral CA";
95
+ /**
96
+ * Generates a self-signed CA and server certificate with Subject Alternative Names (SANs) for the K8s webhook.
97
+ *
98
+ * @param {string} name - The name to use for the server certificate's Common Name and SAN DNS entry.
99
+ * @returns {TLSOut} - An object containing the Base64-encoded CA, server certificate, and server private key.
100
+ */
95
101
  function genTLS(name) {
96
- // Generate a new CA key pair
102
+ // Generate a new CA key pair and create a self-signed CA certificate
97
103
  const caKeys = forge.pki.rsa.generateKeyPair(2048);
98
- const caCert = forge.pki.createCertificate();
99
- caCert.publicKey = caKeys.publicKey;
100
- caCert.serialNumber = "01";
101
- caCert.validity.notBefore = new Date();
102
- caCert.validity.notAfter = new Date();
103
- caCert.validity.notAfter.setFullYear(caCert.validity.notBefore.getFullYear() + 1);
104
- const caAttrs = [
104
+ const caCert = genCert(caKeys, caName, [{ name: "commonName", value: caName }]);
105
+ caCert.setExtensions([
105
106
  {
106
- name: "commonName",
107
- value: "Pepr Ephemeral CA",
107
+ name: "basicConstraints",
108
+ cA: true,
108
109
  },
109
- ];
110
- caCert.setSubject(caAttrs);
111
- caCert.setIssuer(caAttrs);
112
- caCert.sign(caKeys.privateKey, forge.md.sha256.create());
113
- // Generate a new key pair
114
- const keys = forge.pki.rsa.generateKeyPair(2048);
115
- const cert = forge.pki.createCertificate();
116
- cert.publicKey = keys.publicKey;
117
- cert.serialNumber = "01";
118
- cert.validity.notBefore = new Date();
119
- cert.validity.notAfter = new Date();
120
- cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
121
- const attrs = [
122
110
  {
123
- name: "commonName",
124
- value: `${name}.pepr-system.svc`,
111
+ name: "keyUsage",
112
+ keyCertSign: true,
113
+ digitalSignature: true,
114
+ nonRepudiation: true,
115
+ keyEncipherment: true,
116
+ dataEncipherment: true,
125
117
  },
126
- ];
127
- cert.setSubject(attrs);
128
- cert.setIssuer(caCert.subject.attributes);
129
- cert.sign(caKeys.privateKey, forge.md.sha256.create());
118
+ ]);
119
+ // Generate a new server key pair and create a server certificate signed by the CA
120
+ const serverKeys = forge.pki.rsa.generateKeyPair(2048);
121
+ const serverCert = genCert(serverKeys, name, caCert.subject.attributes);
122
+ // Sign both certificates with the CA private key
123
+ caCert.sign(caKeys.privateKey, forge.md.sha256.create());
124
+ serverCert.sign(caKeys.privateKey, forge.md.sha256.create());
130
125
  // Convert the keys and certificates to PEM format
131
- const ca = Buffer.from(forge.pki.certificateToPem(caCert)).toString("base64");
132
- const key = Buffer.from(forge.pki.privateKeyToPem(keys.privateKey)).toString("base64");
133
- const crt = Buffer.from(forge.pki.certificateToPem(cert)).toString("base64");
134
- return { ca, key, crt };
126
+ const pem = {
127
+ ca: forge.pki.certificateToPem(caCert),
128
+ crt: forge.pki.certificateToPem(serverCert),
129
+ key: forge.pki.privateKeyToPem(serverKeys.privateKey),
130
+ };
131
+ // Base64-encode the PEM strings
132
+ const ca = Buffer.from(pem.ca).toString("base64");
133
+ const key = Buffer.from(pem.key).toString("base64");
134
+ const crt = Buffer.from(pem.crt).toString("base64");
135
+ return { ca, key, crt, pem };
136
+ }
137
+ function genCert(key, name, issuer) {
138
+ const crt = forge.pki.createCertificate();
139
+ crt.publicKey = key.publicKey;
140
+ crt.serialNumber = "01";
141
+ crt.validity.notBefore = new Date();
142
+ crt.validity.notAfter = new Date();
143
+ crt.validity.notAfter.setFullYear(crt.validity.notBefore.getFullYear() + 1);
144
+ // Add SANs to the server certificate
145
+ crt.setExtensions([
146
+ {
147
+ name: "subjectAltName",
148
+ altNames: [
149
+ {
150
+ type: 2,
151
+ value: name,
152
+ },
153
+ ],
154
+ },
155
+ ]);
156
+ // Set the server certificate's issuer to the CA
157
+ crt.setIssuer(issuer);
158
+ return crt;
135
159
  }
136
160
 
137
161
  // SPDX-License-Identifier: Apache-2.0
@@ -141,12 +165,16 @@ const peprIgnore = {
141
165
  values: ["ignore"],
142
166
  };
143
167
  class Webhook {
144
- constructor(config) {
168
+ get tls() {
169
+ return this._tls;
170
+ }
171
+ constructor(config, host) {
145
172
  this.config = config;
173
+ this.host = host;
146
174
  this.name = `pepr-${config.uuid}`;
147
175
  this.image = `ghcr.io/defenseunicorns/pepr/controller:${config.version}`;
148
176
  // Generate the ephemeral tls things
149
- this.tls = genTLS(this.name);
177
+ this._tls = genTLS(this.host || `${this.name}.pepr-system.svc`);
150
178
  }
151
179
  /** Generate the pepr-system namespace */
152
180
  namespace() {
@@ -217,8 +245,8 @@ class Webhook {
217
245
  },
218
246
  type: "kubernetes.io/tls",
219
247
  data: {
220
- "tls.crt": this.tls.crt,
221
- "tls.key": this.tls.key,
248
+ "tls.crt": this._tls.crt,
249
+ "tls.key": this._tls.key,
222
250
  },
223
251
  };
224
252
  }
@@ -233,6 +261,21 @@ class Webhook {
233
261
  values: this.config.alwaysIgnore.namespaces,
234
262
  });
235
263
  }
264
+ const clientConfig = {
265
+ caBundle: this._tls.ca,
266
+ };
267
+ // If a host is specified, use that with a port of 3000
268
+ if (this.host) {
269
+ clientConfig.url = `https://${this.host}:3000/mutate`;
270
+ }
271
+ else {
272
+ // Otherwise, use the service
273
+ clientConfig.service = {
274
+ name: this.name,
275
+ namespace: "pepr-system",
276
+ path: "/mutate",
277
+ };
278
+ }
236
279
  return {
237
280
  apiVersion: "admissionregistration.k8s.io/v1",
238
281
  kind: "MutatingWebhookConfiguration",
@@ -241,14 +284,7 @@ class Webhook {
241
284
  {
242
285
  name: `${name}.pepr.dev`,
243
286
  admissionReviewVersions: ["v1", "v1beta1"],
244
- clientConfig: {
245
- caBundle: this.tls.ca,
246
- service: {
247
- name: this.name,
248
- namespace: "pepr-system",
249
- path: "/mutate",
250
- },
251
- },
287
+ clientConfig,
252
288
  failurePolicy: "Ignore",
253
289
  matchPolicy: "Equivalent",
254
290
  timeoutSeconds: 15,
@@ -264,7 +300,7 @@ class Webhook {
264
300
  apiGroups: ["*"],
265
301
  apiVersions: ["*"],
266
302
  operations: ["CREATE", "UPDATE", "DELETE"],
267
- resources: ["*"],
303
+ resources: ["*/*"],
268
304
  },
269
305
  ],
270
306
  // @todo: track side effects state
@@ -285,7 +321,7 @@ class Webhook {
285
321
  },
286
322
  },
287
323
  spec: {
288
- replicas: 1,
324
+ replicas: 2,
289
325
  selector: {
290
326
  matchLabels: {
291
327
  app: this.name,
@@ -333,6 +369,11 @@ class Webhook {
333
369
  mountPath: "/etc/certs",
334
370
  readOnly: true,
335
371
  },
372
+ {
373
+ name: "module",
374
+ mountPath: "/app/module.js.gz",
375
+ readOnly: true,
376
+ },
336
377
  ],
337
378
  },
338
379
  ],
@@ -343,12 +384,56 @@ class Webhook {
343
384
  secretName: `${this.name}-tls`,
344
385
  },
345
386
  },
387
+ {
388
+ name: "module",
389
+ secret: {
390
+ secretName: `${this.name}-module`,
391
+ },
392
+ },
346
393
  ],
347
394
  },
348
395
  },
349
396
  },
350
397
  };
351
398
  }
399
+ /** Only permit the */
400
+ networkPolicy() {
401
+ return {
402
+ apiVersion: "networking.k8s.io/v1",
403
+ kind: "NetworkPolicy",
404
+ metadata: {
405
+ name: this.name,
406
+ namespace: "pepr-system",
407
+ },
408
+ spec: {
409
+ podSelector: {
410
+ matchLabels: {
411
+ app: this.name,
412
+ },
413
+ },
414
+ policyTypes: ["Ingress"],
415
+ ingress: [
416
+ {
417
+ from: [
418
+ {
419
+ namespaceSelector: {
420
+ matchLabels: {
421
+ "kubernetes.io/metadata.name": "kube-system",
422
+ },
423
+ },
424
+ },
425
+ ],
426
+ ports: [
427
+ {
428
+ protocol: "TCP",
429
+ port: 443,
430
+ },
431
+ ],
432
+ },
433
+ ],
434
+ },
435
+ };
436
+ }
352
437
  service() {
353
438
  return {
354
439
  apiVersion: "v1",
@@ -415,6 +500,7 @@ class Webhook {
415
500
  allYaml(code) {
416
501
  const resources = [
417
502
  this.namespace(),
503
+ this.networkPolicy(),
418
504
  this.clusterRole(),
419
505
  this.clusterRoleBinding(),
420
506
  this.serviceAccount(),
@@ -437,6 +523,7 @@ class Webhook {
437
523
  const rbacApi = kubeConfig.makeApiClient(clientNode.RbacAuthorizationV1Api);
438
524
  const appsApi = kubeConfig.makeApiClient(clientNode.AppsV1Api);
439
525
  const admissionApi = kubeConfig.makeApiClient(clientNode.AdmissionregistrationV1Api);
526
+ const networkApi = kubeConfig.makeApiClient(clientNode.NetworkingV1Api);
440
527
  const ns = this.namespace();
441
528
  try {
442
529
  types.Log.info("Checking for namespace");
@@ -447,6 +534,16 @@ class Webhook {
447
534
  types.Log.info("Creating namespace");
448
535
  await coreV1Api.createNamespace(ns);
449
536
  }
537
+ const netpol = this.networkPolicy();
538
+ try {
539
+ types.Log.info("Checking for network policy");
540
+ await networkApi.readNamespacedNetworkPolicy(netpol.metadata.name, namespace);
541
+ }
542
+ catch (e) {
543
+ types.Log.debug(e.body);
544
+ types.Log.info("Creating network policy");
545
+ await networkApi.createNamespacedNetworkPolicy(namespace, netpol);
546
+ }
450
547
  const wh = this.mutatingWebhook();
451
548
  try {
452
549
  types.Log.info("Creating mutating webhook");
@@ -496,6 +593,10 @@ class Webhook {
496
593
  await coreV1Api.deleteNamespacedServiceAccount(sa.metadata.name, namespace);
497
594
  await coreV1Api.createNamespacedServiceAccount(namespace, sa);
498
595
  }
596
+ // If a host is specified, we don't need to deploy the rest of the resources
597
+ if (this.host) {
598
+ return;
599
+ }
499
600
  const mod = this.moduleSecret(code);
500
601
  try {
501
602
  types.Log.info("Creating module secret");
@@ -689,6 +790,47 @@ function deploy (program) {
689
790
  });
690
791
  }
691
792
 
793
+ // SPDX-License-Identifier: Apache-2.0
794
+ function dev (program) {
795
+ program
796
+ .command("dev")
797
+ .description("Setup a local webhook development environment")
798
+ .option("-d, --dir [directory]", "Pepr module directory", ".")
799
+ .option("-h, --host [host]", "Host to listen on", "host.docker.internal")
800
+ .action(async (opts) => {
801
+ // Prompt the user to confirm
802
+ const confirm = await prompt.prompt({
803
+ type: "confirm",
804
+ name: "confirm",
805
+ message: "This will remove and redeploy the module. Continue?",
806
+ });
807
+ // Exit if the user doesn't confirm
808
+ if (!confirm.confirm) {
809
+ process.exit(0);
810
+ }
811
+ // Build the module
812
+ const { cfg, path } = await buildModule(opts.dir);
813
+ // Read the compiled module code
814
+ const code = await fs.promises.readFile(path, { encoding: "utf-8" });
815
+ // Generate a secret for the module
816
+ const webhook = new Webhook({
817
+ ...cfg.pepr,
818
+ description: cfg.description,
819
+ }, opts.host);
820
+ // Write the TLS cert and key to disk
821
+ await fs.promises.writeFile("insecure-tls.crt", webhook.tls.pem.crt);
822
+ await fs.promises.writeFile("insecure-tls.key", webhook.tls.pem.key);
823
+ try {
824
+ await webhook.deploy(code);
825
+ types.Log.info(`Module deployed successfully`);
826
+ }
827
+ catch (e) {
828
+ types.Log.error(`Error deploying module: ${e}`);
829
+ process.exit(1);
830
+ }
831
+ });
832
+ }
833
+
692
834
  // SPDX-License-Identifier: Apache-2.0
693
835
  /**
694
836
  * Sanitize a user input name to be used as a pepr module directory name
@@ -1009,11 +1151,7 @@ function walkthrough() {
1009
1151
  },
1010
1152
  ],
1011
1153
  };
1012
- return prompt([
1013
- askName,
1014
- askDescription,
1015
- askErrorBehavior,
1016
- ]);
1154
+ return prompt([askName, askDescription, askErrorBehavior]);
1017
1155
  }
1018
1156
  async function confirm(dirName, packageJSON, peprTSPath) {
1019
1157
  console.log(`
@@ -1147,4 +1285,5 @@ build(program);
1147
1285
  capability(program);
1148
1286
  test(program);
1149
1287
  deploy(program);
1288
+ dev(program);
1150
1289
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pepr",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "Kubernetes application engine",
5
5
  "author": "Defense Unicorns",
6
6
  "homepage": "https://github.com/defenseunicorns/pepr",
@@ -29,7 +29,10 @@
29
29
  "prettier": "npx prettier src --check",
30
30
  "prettier:fix": "npm run prettier -- --write",
31
31
  "prepublishOnly": "rm -fr dist/* && npm run lint:fix && npm run prettier:fix && npm run test && npm run build",
32
- "e2e-dev": "npm run build && docker buildx build --tag pepr:dev . && k3d image import pepr:dev && node dist/pepr-cli.js deploy -f -i pepr:dev"
32
+ "e2e-dev-setup": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev",
33
+ "e2e-dev": "npm run build && docker buildx build --tag pepr:dev . && k3d image import pepr:dev -c pepr-dev && node dist/pepr-cli.js deploy -f -i pepr:dev",
34
+ "prestart": "ts-node src/cli dev",
35
+ "start": "chokidar 'src/**/*.ts' -c 'SSL_KEY_PATH=insecure-tls.key SSL_CERT_PATH=insecure-tls.crt ts-node src/controller/index.ts' --initial --silent"
33
36
  },
34
37
  "dependencies": {
35
38
  "@kubernetes/client-node": "^0.18.1",
@@ -59,6 +62,7 @@
59
62
  "@typescript-eslint/eslint-plugin": "^5.57.0",
60
63
  "@typescript-eslint/parser": "^5.57.0",
61
64
  "ava": "^5.2.0",
65
+ "chokidar-cli": "^3.0.0",
62
66
  "eslint": "^8.37.0",
63
67
  "ts-node": "^10.9.1",
64
68
  "tsconfig-paths": "^4.1.2"
@@ -1,57 +1,90 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
-
4
- // @todo: quick and dirty temp tls chain for testing, to be replaced at runtime
5
- // Don't freak out, this is a self-signed cert for testing purposes only.
6
1
  import forge from "node-forge";
7
2
 
3
+ const caName = "Pepr Ephemeral CA";
4
+
8
5
  export interface TLSOut {
9
6
  ca: string;
10
7
  crt: string;
11
8
  key: string;
9
+ pem: {
10
+ ca: string;
11
+ crt: string;
12
+ key: string;
13
+ };
12
14
  }
13
15
 
16
+ /**
17
+ * Generates a self-signed CA and server certificate with Subject Alternative Names (SANs) for the K8s webhook.
18
+ *
19
+ * @param {string} name - The name to use for the server certificate's Common Name and SAN DNS entry.
20
+ * @returns {TLSOut} - An object containing the Base64-encoded CA, server certificate, and server private key.
21
+ */
14
22
  export function genTLS(name: string): TLSOut {
15
- // Generate a new CA key pair
23
+ // Generate a new CA key pair and create a self-signed CA certificate
16
24
  const caKeys = forge.pki.rsa.generateKeyPair(2048);
17
- const caCert = forge.pki.createCertificate();
18
- caCert.publicKey = caKeys.publicKey;
19
- caCert.serialNumber = "01";
20
- caCert.validity.notBefore = new Date();
21
- caCert.validity.notAfter = new Date();
22
- caCert.validity.notAfter.setFullYear(caCert.validity.notBefore.getFullYear() + 1);
23
- const caAttrs = [
25
+ const caCert = genCert(caKeys, caName, [{ name: "commonName", value: caName }]);
26
+
27
+ caCert.setExtensions([
24
28
  {
25
- name: "commonName",
26
- value: "Pepr Ephemeral CA",
29
+ name: "basicConstraints",
30
+ cA: true,
27
31
  },
28
- ];
29
- caCert.setSubject(caAttrs);
30
- caCert.setIssuer(caAttrs);
32
+ {
33
+ name: "keyUsage",
34
+ keyCertSign: true,
35
+ digitalSignature: true,
36
+ nonRepudiation: true,
37
+ keyEncipherment: true,
38
+ dataEncipherment: true,
39
+ },
40
+ ]);
41
+
42
+ // Generate a new server key pair and create a server certificate signed by the CA
43
+ const serverKeys = forge.pki.rsa.generateKeyPair(2048);
44
+ const serverCert = genCert(serverKeys, name, caCert.subject.attributes);
45
+
46
+ // Sign both certificates with the CA private key
31
47
  caCert.sign(caKeys.privateKey, forge.md.sha256.create());
48
+ serverCert.sign(caKeys.privateKey, forge.md.sha256.create());
32
49
 
33
- // Generate a new key pair
34
- const keys = forge.pki.rsa.generateKeyPair(2048);
35
- const cert = forge.pki.createCertificate();
36
- cert.publicKey = keys.publicKey;
37
- cert.serialNumber = "01";
38
- cert.validity.notBefore = new Date();
39
- cert.validity.notAfter = new Date();
40
- cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
41
- const attrs = [
50
+ // Convert the keys and certificates to PEM format
51
+ const pem = {
52
+ ca: forge.pki.certificateToPem(caCert),
53
+ crt: forge.pki.certificateToPem(serverCert),
54
+ key: forge.pki.privateKeyToPem(serverKeys.privateKey),
55
+ };
56
+
57
+ // Base64-encode the PEM strings
58
+ const ca = Buffer.from(pem.ca).toString("base64");
59
+ const key = Buffer.from(pem.key).toString("base64");
60
+ const crt = Buffer.from(pem.crt).toString("base64");
61
+
62
+ return { ca, key, crt, pem };
63
+ }
64
+
65
+ function genCert(key: forge.pki.rsa.KeyPair, name: string, issuer: forge.pki.CertificateField[]) {
66
+ const crt = forge.pki.createCertificate();
67
+ crt.publicKey = key.publicKey;
68
+ crt.serialNumber = "01";
69
+ crt.validity.notBefore = new Date();
70
+ crt.validity.notAfter = new Date();
71
+ crt.validity.notAfter.setFullYear(crt.validity.notBefore.getFullYear() + 1);
72
+
73
+ // Add SANs to the server certificate
74
+ crt.setExtensions([
42
75
  {
43
- name: "commonName",
44
- value: `${name}.pepr-system.svc`,
76
+ name: "subjectAltName",
77
+ altNames: [
78
+ {
79
+ type: 2, // DNS
80
+ value: name,
81
+ },
82
+ ],
45
83
  },
46
- ];
47
- cert.setSubject(attrs);
48
- cert.setIssuer(caCert.subject.attributes);
49
- cert.sign(caKeys.privateKey, forge.md.sha256.create());
84
+ ]);
50
85
 
51
- // Convert the keys and certificates to PEM format
52
- const ca = Buffer.from(forge.pki.certificateToPem(caCert)).toString("base64");
53
- const key = Buffer.from(forge.pki.privateKeyToPem(keys.privateKey)).toString("base64");
54
- const crt = Buffer.from(forge.pki.certificateToPem(cert)).toString("base64");
86
+ // Set the server certificate's issuer to the CA
87
+ crt.setIssuer(issuer);
55
88
 
56
- return { ca, key, crt };
89
+ return crt;
57
90
  }
@@ -3,9 +3,11 @@
3
3
 
4
4
  import {
5
5
  AdmissionregistrationV1Api,
6
+ AdmissionregistrationV1WebhookClientConfig,
6
7
  AppsV1Api,
7
8
  CoreV1Api,
8
9
  KubeConfig,
10
+ NetworkingV1Api,
9
11
  RbacAuthorizationV1Api,
10
12
  V1ClusterRole,
11
13
  V1ClusterRoleBinding,
@@ -13,6 +15,7 @@ import {
13
15
  V1LabelSelectorRequirement,
14
16
  V1MutatingWebhookConfiguration,
15
17
  V1Namespace,
18
+ V1NetworkPolicy,
16
19
  V1Secret,
17
20
  V1Service,
18
21
  V1ServiceAccount,
@@ -31,17 +34,21 @@ const peprIgnore: V1LabelSelectorRequirement = {
31
34
 
32
35
  export class Webhook {
33
36
  private name: string;
34
- private tls: TLSOut;
37
+ private _tls: TLSOut;
35
38
 
36
39
  public image: string;
37
40
 
38
- constructor(private readonly config: ModuleConfig) {
41
+ public get tls(): TLSOut {
42
+ return this._tls;
43
+ }
44
+
45
+ constructor(private readonly config: ModuleConfig, private readonly host?: string) {
39
46
  this.name = `pepr-${config.uuid}`;
40
47
 
41
48
  this.image = `ghcr.io/defenseunicorns/pepr/controller:${config.version}`;
42
49
 
43
50
  // Generate the ephemeral tls things
44
- this.tls = genTLS(this.name);
51
+ this._tls = genTLS(this.host || `${this.name}.pepr-system.svc`);
45
52
  }
46
53
 
47
54
  /** Generate the pepr-system namespace */
@@ -117,8 +124,8 @@ export class Webhook {
117
124
  },
118
125
  type: "kubernetes.io/tls",
119
126
  data: {
120
- "tls.crt": this.tls.crt,
121
- "tls.key": this.tls.key,
127
+ "tls.crt": this._tls.crt,
128
+ "tls.key": this._tls.key,
122
129
  },
123
130
  };
124
131
  }
@@ -136,6 +143,22 @@ export class Webhook {
136
143
  });
137
144
  }
138
145
 
146
+ const clientConfig: AdmissionregistrationV1WebhookClientConfig = {
147
+ caBundle: this._tls.ca,
148
+ };
149
+
150
+ // If a host is specified, use that with a port of 3000
151
+ if (this.host) {
152
+ clientConfig.url = `https://${this.host}:3000/mutate`;
153
+ } else {
154
+ // Otherwise, use the service
155
+ clientConfig.service = {
156
+ name: this.name,
157
+ namespace: "pepr-system",
158
+ path: "/mutate",
159
+ };
160
+ }
161
+
139
162
  return {
140
163
  apiVersion: "admissionregistration.k8s.io/v1",
141
164
  kind: "MutatingWebhookConfiguration",
@@ -144,14 +167,7 @@ export class Webhook {
144
167
  {
145
168
  name: `${name}.pepr.dev`,
146
169
  admissionReviewVersions: ["v1", "v1beta1"],
147
- clientConfig: {
148
- caBundle: this.tls.ca,
149
- service: {
150
- name: this.name,
151
- namespace: "pepr-system",
152
- path: "/mutate",
153
- },
154
- },
170
+ clientConfig,
155
171
  failurePolicy: "Ignore",
156
172
  matchPolicy: "Equivalent",
157
173
  timeoutSeconds: 15,
@@ -167,7 +183,7 @@ export class Webhook {
167
183
  apiGroups: ["*"],
168
184
  apiVersions: ["*"],
169
185
  operations: ["CREATE", "UPDATE", "DELETE"],
170
- resources: ["*"],
186
+ resources: ["*/*"],
171
187
  },
172
188
  ],
173
189
  // @todo: track side effects state
@@ -189,7 +205,7 @@ export class Webhook {
189
205
  },
190
206
  },
191
207
  spec: {
192
- replicas: 1,
208
+ replicas: 2,
193
209
  selector: {
194
210
  matchLabels: {
195
211
  app: this.name,
@@ -237,6 +253,11 @@ export class Webhook {
237
253
  mountPath: "/etc/certs",
238
254
  readOnly: true,
239
255
  },
256
+ {
257
+ name: "module",
258
+ mountPath: "/app/module.js.gz",
259
+ readOnly: true,
260
+ },
240
261
  ],
241
262
  },
242
263
  ],
@@ -247,6 +268,12 @@ export class Webhook {
247
268
  secretName: `${this.name}-tls`,
248
269
  },
249
270
  },
271
+ {
272
+ name: "module",
273
+ secret: {
274
+ secretName: `${this.name}-module`,
275
+ },
276
+ },
250
277
  ],
251
278
  },
252
279
  },
@@ -254,6 +281,45 @@ export class Webhook {
254
281
  };
255
282
  }
256
283
 
284
+ /** Only permit the */
285
+ networkPolicy(): V1NetworkPolicy {
286
+ return {
287
+ apiVersion: "networking.k8s.io/v1",
288
+ kind: "NetworkPolicy",
289
+ metadata: {
290
+ name: this.name,
291
+ namespace: "pepr-system",
292
+ },
293
+ spec: {
294
+ podSelector: {
295
+ matchLabels: {
296
+ app: this.name,
297
+ },
298
+ },
299
+ policyTypes: ["Ingress"],
300
+ ingress: [
301
+ {
302
+ from: [
303
+ {
304
+ namespaceSelector: {
305
+ matchLabels: {
306
+ "kubernetes.io/metadata.name": "kube-system",
307
+ },
308
+ },
309
+ },
310
+ ],
311
+ ports: [
312
+ {
313
+ protocol: "TCP",
314
+ port: 443,
315
+ },
316
+ ],
317
+ },
318
+ ],
319
+ },
320
+ };
321
+ }
322
+
257
323
  service(): V1Service {
258
324
  return {
259
325
  apiVersion: "v1",
@@ -324,6 +390,7 @@ export class Webhook {
324
390
  allYaml(code: string) {
325
391
  const resources = [
326
392
  this.namespace(),
393
+ this.networkPolicy(),
327
394
  this.clusterRole(),
328
395
  this.clusterRoleBinding(),
329
396
  this.serviceAccount(),
@@ -351,6 +418,7 @@ export class Webhook {
351
418
  const rbacApi = kubeConfig.makeApiClient(RbacAuthorizationV1Api);
352
419
  const appsApi = kubeConfig.makeApiClient(AppsV1Api);
353
420
  const admissionApi = kubeConfig.makeApiClient(AdmissionregistrationV1Api);
421
+ const networkApi = kubeConfig.makeApiClient(NetworkingV1Api);
354
422
 
355
423
  const ns = this.namespace();
356
424
  try {
@@ -362,6 +430,16 @@ export class Webhook {
362
430
  await coreV1Api.createNamespace(ns);
363
431
  }
364
432
 
433
+ const netpol = this.networkPolicy();
434
+ try {
435
+ logger.info("Checking for network policy");
436
+ await networkApi.readNamespacedNetworkPolicy(netpol.metadata.name, namespace);
437
+ } catch (e) {
438
+ logger.debug(e.body);
439
+ logger.info("Creating network policy");
440
+ await networkApi.createNamespacedNetworkPolicy(namespace, netpol);
441
+ }
442
+
365
443
  const wh = this.mutatingWebhook();
366
444
  try {
367
445
  logger.info("Creating mutating webhook");
@@ -410,6 +488,11 @@ export class Webhook {
410
488
  await coreV1Api.createNamespacedServiceAccount(namespace, sa);
411
489
  }
412
490
 
491
+ // If a host is specified, we don't need to deploy the rest of the resources
492
+ if (this.host) {
493
+ return;
494
+ }
495
+
413
496
  const mod = this.moduleSecret(code);
414
497
  try {
415
498
  logger.info("Creating module secret");