pepr 0.28.7 → 0.29.0

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.
@@ -0,0 +1,38 @@
1
+ import { PeprValidateRequest } from "../lib/validate-request";
2
+ import { PeprMutateRequest } from "../lib/mutate-request";
3
+ import { a } from "../lib";
4
+ import { V1OwnerReference } from "@kubernetes/client-node";
5
+ import { GenericKind } from "kubernetes-fluent-client";
6
+ import { kind } from "kubernetes-fluent-client";
7
+ /**
8
+ * Returns all containers in a pod
9
+ * @param request the request/pod to get the containers from
10
+ * @param containerType the type of container to get
11
+ * @returns the list of containers in the pod
12
+ */
13
+ export declare function containers(request: PeprValidateRequest<a.Pod> | PeprMutateRequest<a.Pod>, containerType?: "containers" | "initContainers" | "ephemeralContainers"): import("@kubernetes/client-node").V1Container[];
14
+ /**
15
+ * Write a K8s event for a CRD
16
+ *
17
+ * @param cr The custom resource to write the event for
18
+ * @param event The event to write, should contain a human-readable message for the event
19
+ * @param eventType The type of event to write, for example "Warning"
20
+ * @param eventReason The reason for the event, for example "ReconciliationFailed"
21
+ * @param reportingComponent The component that is reporting the event, for example "uds.dev/operator"
22
+ * @param reportingInstance The instance of the component that is reporting the event, for example process.env.HOSTNAME
23
+ */
24
+ export declare function writeEvent(cr: GenericKind, event: Partial<kind.CoreEvent>, eventType: string, eventReason: string, reportingComponent: string, reportingInstance: string): Promise<void>;
25
+ /**
26
+ * Get the owner reference for a custom resource
27
+ * @param cr the custom resource to get the owner reference for
28
+ * @returns the owner reference for the custom resource
29
+ */
30
+ export declare function getOwnerRefFrom(cr: GenericKind): V1OwnerReference[];
31
+ /**
32
+ * Sanitize a resource name to make it a valid Kubernetes resource name.
33
+ *
34
+ * @param name the name of the resource to sanitize
35
+ * @returns the sanitized resource name
36
+ */
37
+ export declare function sanitizeResourceName(name: string): string;
38
+ //# sourceMappingURL=sdk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/sdk/sdk.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAO,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAGrD;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,EAC9D,aAAa,CAAC,EAAE,YAAY,GAAG,gBAAgB,GAAG,qBAAqB,mDAgBxE;AAED;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,WAAW,EACf,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,kBAAkB,EAAE,MAAM,EAC1B,iBAAiB,EAAE,MAAM,iBAwB1B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,WAAW,GAAG,gBAAgB,EAAE,CAWnE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,UAYhD"}
package/package.json CHANGED
@@ -9,13 +9,14 @@
9
9
  "engines": {
10
10
  "node": ">=18.0.0"
11
11
  },
12
- "version": "0.28.7",
12
+ "version": "0.29.0",
13
13
  "main": "dist/lib.js",
14
14
  "types": "dist/lib.d.ts",
15
15
  "scripts": {
16
16
  "gen-data-json": "node hack/build-template-data.js",
17
17
  "prebuild": "rm -fr dist/* && npm run gen-data-json",
18
18
  "build": "tsc && node build.mjs",
19
+ "build:image": "npm run build && docker buildx build --tag pepr:dev .",
19
20
  "test": "npm run test:unit && npm run test:journey",
20
21
  "test:unit": "npm run gen-data-json && jest src --coverage --detectOpenHandles --coverageDirectory=./coverage",
21
22
  "test:journey": "npm run test:journey:k3d && npm run test:journey:build && npm run test:journey:image && npm run test:journey:run",
@@ -31,7 +32,7 @@
31
32
  "format:fix": "eslint src --fix && prettier src --write"
32
33
  },
33
34
  "dependencies": {
34
- "@types/ramda": "0.29.11",
35
+ "@types/ramda": "0.29.12",
35
36
  "express": "4.19.2",
36
37
  "fast-json-patch": "3.1.1",
37
38
  "kubernetes-fluent-client": "2.3.0",
@@ -44,7 +45,7 @@
44
45
  "@commitlint/cli": "19.2.1",
45
46
  "@commitlint/config-conventional": "19.1.0",
46
47
  "@jest/globals": "29.7.0",
47
- "@types/eslint": "8.56.6",
48
+ "@types/eslint": "8.56.7",
48
49
  "@types/express": "4.17.21",
49
50
  "@types/node": "18.x.x",
50
51
  "@types/node-forge": "1.3.11",
@@ -50,7 +50,7 @@ export class Capability implements CapabilityExport {
50
50
  const { name, every, unit, run, startTime, completions } = schedule;
51
51
  this.hasSchedule = true;
52
52
 
53
- if (process.env.PEPR_WATCH_MODE === "true") {
53
+ if (process.env.PEPR_WATCH_MODE === "true" || process.env.PEPR_MODE === "dev") {
54
54
  // Only create/watch schedule store if necessary
55
55
 
56
56
  // Create a new schedule
@@ -6,6 +6,17 @@ import { K8s, KubernetesObject, kind } from "kubernetes-fluent-client";
6
6
  import Log from "./logger";
7
7
  import { Binding, CapabilityExport } from "./types";
8
8
 
9
+ export class ValidationError extends Error {}
10
+
11
+ export function validateHash(expectedHash: string): void {
12
+ // Require the hash to be a valid SHA-256 hash (64 characters, hexadecimal)
13
+ const sha256Regex = /^[a-f0-9]{64}$/i;
14
+ if (!expectedHash || !sha256Regex.test(expectedHash)) {
15
+ Log.error(`Invalid hash. Expected a valid SHA-256 hash, got ${expectedHash}`);
16
+ throw new ValidationError("Invalid hash");
17
+ }
18
+ }
19
+
9
20
  type RBACMap = {
10
21
  [key: string]: {
11
22
  verbs: string[];
package/src/lib.ts CHANGED
@@ -7,7 +7,7 @@ import { PeprModule } from "./lib/module";
7
7
  import { PeprMutateRequest } from "./lib/mutate-request";
8
8
  import * as PeprUtils from "./lib/utils";
9
9
  import { PeprValidateRequest } from "./lib/validate-request";
10
- import { containers } from "./lib/module-helpers";
10
+ import * as sdk from "./sdk/sdk";
11
11
 
12
12
  export {
13
13
  Capability,
@@ -23,5 +23,5 @@ export {
23
23
  fetch,
24
24
  fetchStatus,
25
25
  kind,
26
- containers,
26
+ sdk,
27
27
  };
@@ -11,17 +11,9 @@ import { K8s, kind } from "kubernetes-fluent-client";
11
11
  import Log from "../lib/logger";
12
12
  import { packageJSON } from "../templates/data.json";
13
13
  import { peprStoreCRD } from "../lib/assets/store";
14
-
14
+ import { validateHash } from "../lib/helpers";
15
15
  const { version } = packageJSON;
16
16
 
17
- function validateHash(expectedHash: string) {
18
- // Require the hash to be 64 characters long
19
- if (!expectedHash || expectedHash.length !== 64) {
20
- Log.error("Invalid hash");
21
- process.exit(1);
22
- }
23
- }
24
-
25
17
  function runModule(expectedHash: string) {
26
18
  const gzPath = `/app/load/module-${expectedHash}.js.gz`;
27
19
  const jsPath = `/app/module-${expectedHash}.js`;
@@ -31,8 +23,7 @@ function runModule(expectedHash: string) {
31
23
 
32
24
  // Check if the path is a valid file
33
25
  if (!fs.existsSync(gzPath)) {
34
- Log.error(`File not found: ${gzPath}`);
35
- process.exit(1);
26
+ throw new Error(`File not found: ${gzPath}`);
36
27
  }
37
28
 
38
29
  try {
@@ -46,9 +37,10 @@ function runModule(expectedHash: string) {
46
37
  const actualHash = crypto.createHash("sha256").update(code).digest("hex");
47
38
 
48
39
  // If the hash doesn't match, exit
49
- if (expectedHash !== actualHash) {
50
- Log.error(`File hash does not match, expected ${expectedHash} but got ${actualHash}`);
51
- process.exit(1);
40
+ // This is a timing safe comparison to prevent timing attacks
41
+ // https://en.wikipedia.org/wiki/Timing_attack
42
+ if (!crypto.timingSafeEqual(Buffer.from(expectedHash, "hex"), Buffer.from(actualHash, "hex"))) {
43
+ throw new Error(`File hash does not match, expected ${expectedHash} but got ${actualHash}`);
52
44
  }
53
45
 
54
46
  Log.info(`File hash matches, running module`);
@@ -59,8 +51,7 @@ function runModule(expectedHash: string) {
59
51
  // Run the module
60
52
  fork(jsPath);
61
53
  } catch (e) {
62
- Log.error(`Failed to decompress module: ${e}`);
63
- process.exit(1);
54
+ throw new Error(`Failed to decompress module: ${e}`);
64
55
  }
65
56
  }
66
57
 
@@ -69,11 +60,16 @@ Log.info(`Pepr Controller (v${version})`);
69
60
  const hash = process.argv[2];
70
61
 
71
62
  const startup = async () => {
72
- Log.info("Applying the Pepr Store CRD if it doesn't exist");
73
- await K8s(kind.CustomResourceDefinition).Apply(peprStoreCRD, { force: true });
63
+ try {
64
+ Log.info("Applying the Pepr Store CRD if it doesn't exist");
65
+ await K8s(kind.CustomResourceDefinition).Apply(peprStoreCRD, { force: true });
74
66
 
75
- validateHash(hash);
76
- runModule(hash);
67
+ validateHash(hash);
68
+ runModule(hash);
69
+ } catch (err) {
70
+ Log.error(err);
71
+ process.exit(1);
72
+ }
77
73
  };
78
74
 
79
75
  startup().catch(err => Log.error(err));
package/src/sdk/sdk.ts ADDED
@@ -0,0 +1,116 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import { PeprValidateRequest } from "../lib/validate-request";
5
+ import { PeprMutateRequest } from "../lib/mutate-request";
6
+ import { a } from "../lib";
7
+ import { V1OwnerReference } from "@kubernetes/client-node";
8
+ import { GenericKind } from "kubernetes-fluent-client";
9
+ import { K8s, kind } from "kubernetes-fluent-client";
10
+ import Log from "../lib/logger";
11
+
12
+ /**
13
+ * Returns all containers in a pod
14
+ * @param request the request/pod to get the containers from
15
+ * @param containerType the type of container to get
16
+ * @returns the list of containers in the pod
17
+ */
18
+ export function containers(
19
+ request: PeprValidateRequest<a.Pod> | PeprMutateRequest<a.Pod>,
20
+ containerType?: "containers" | "initContainers" | "ephemeralContainers",
21
+ ) {
22
+ const containers = request.Raw.spec?.containers || [];
23
+ const initContainers = request.Raw.spec?.initContainers || [];
24
+ const ephemeralContainers = request.Raw.spec?.ephemeralContainers || [];
25
+
26
+ if (containerType === "containers") {
27
+ return containers;
28
+ }
29
+ if (containerType === "initContainers") {
30
+ return initContainers;
31
+ }
32
+ if (containerType === "ephemeralContainers") {
33
+ return ephemeralContainers;
34
+ }
35
+ return [...containers, ...initContainers, ...ephemeralContainers];
36
+ }
37
+
38
+ /**
39
+ * Write a K8s event for a CRD
40
+ *
41
+ * @param cr The custom resource to write the event for
42
+ * @param event The event to write, should contain a human-readable message for the event
43
+ * @param eventType The type of event to write, for example "Warning"
44
+ * @param eventReason The reason for the event, for example "ReconciliationFailed"
45
+ * @param reportingComponent The component that is reporting the event, for example "uds.dev/operator"
46
+ * @param reportingInstance The instance of the component that is reporting the event, for example process.env.HOSTNAME
47
+ */
48
+ export async function writeEvent(
49
+ cr: GenericKind,
50
+ event: Partial<kind.CoreEvent>,
51
+ eventType: string,
52
+ eventReason: string,
53
+ reportingComponent: string,
54
+ reportingInstance: string,
55
+ ) {
56
+ Log.debug(cr.metadata, `Writing event: ${event.message}`);
57
+
58
+ await K8s(kind.CoreEvent).Create({
59
+ type: eventType,
60
+ reason: eventReason,
61
+ ...event,
62
+ // Fixed values
63
+ metadata: {
64
+ namespace: cr.metadata!.namespace,
65
+ generateName: cr.metadata!.name,
66
+ },
67
+ involvedObject: {
68
+ apiVersion: cr.apiVersion,
69
+ kind: cr.kind,
70
+ name: cr.metadata!.name,
71
+ namespace: cr.metadata!.namespace,
72
+ uid: cr.metadata!.uid,
73
+ },
74
+ firstTimestamp: new Date(),
75
+ reportingComponent: reportingComponent,
76
+ reportingInstance: reportingInstance,
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Get the owner reference for a custom resource
82
+ * @param cr the custom resource to get the owner reference for
83
+ * @returns the owner reference for the custom resource
84
+ */
85
+ export function getOwnerRefFrom(cr: GenericKind): V1OwnerReference[] {
86
+ const { name, uid } = cr.metadata!;
87
+
88
+ return [
89
+ {
90
+ apiVersion: cr.apiVersion!,
91
+ kind: cr.kind!,
92
+ uid: uid!,
93
+ name: name!,
94
+ },
95
+ ];
96
+ }
97
+
98
+ /**
99
+ * Sanitize a resource name to make it a valid Kubernetes resource name.
100
+ *
101
+ * @param name the name of the resource to sanitize
102
+ * @returns the sanitized resource name
103
+ */
104
+ export function sanitizeResourceName(name: string) {
105
+ return (
106
+ name
107
+ // The name must be lowercase
108
+ .toLowerCase()
109
+ // Replace sequences of non-alphanumeric characters with a single '-'
110
+ .replace(/[^a-z0-9]+/g, "-")
111
+ // Truncate the name to 250 characters
112
+ .slice(0, 250)
113
+ // Remove leading and trailing non-letter characters
114
+ .replace(/^[^a-z]+|[^a-z]+$/g, "")
115
+ );
116
+ }
@@ -1,5 +0,0 @@
1
- import { PeprValidateRequest } from "./validate-request";
2
- import { PeprMutateRequest } from "./mutate-request";
3
- import { a } from "../lib";
4
- export declare function containers(request: PeprValidateRequest<a.Pod> | PeprMutateRequest<a.Pod>, containerType?: "containers" | "initContainers" | "ephemeralContainers"): import("@kubernetes/client-node").V1Container[];
5
- //# sourceMappingURL=module-helpers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"module-helpers.d.ts","sourceRoot":"","sources":["../../src/lib/module-helpers.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAG3B,wBAAgB,UAAU,CACxB,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,EAC9D,aAAa,CAAC,EAAE,YAAY,GAAG,gBAAgB,GAAG,qBAAqB,mDAgBxE"}
@@ -1,27 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
-
4
- import { PeprValidateRequest } from "./validate-request";
5
- import { PeprMutateRequest } from "./mutate-request";
6
- import { a } from "../lib";
7
-
8
- // Returns all containers in the pod
9
- export function containers(
10
- request: PeprValidateRequest<a.Pod> | PeprMutateRequest<a.Pod>,
11
- containerType?: "containers" | "initContainers" | "ephemeralContainers",
12
- ) {
13
- const containers = request.Raw.spec?.containers || [];
14
- const initContainers = request.Raw.spec?.initContainers || [];
15
- const ephemeralContainers = request.Raw.spec?.ephemeralContainers || [];
16
-
17
- if (containerType === "containers") {
18
- return containers;
19
- }
20
- if (containerType === "initContainers") {
21
- return initContainers;
22
- }
23
- if (containerType === "ephemeralContainers") {
24
- return ephemeralContainers;
25
- }
26
- return [...containers, ...initContainers, ...ephemeralContainers];
27
- }