pepr 0.35.0 → 0.37.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.
- package/dist/cli/init/index.d.ts.map +1 -1
- package/dist/cli/init/templates.d.ts +3 -1
- package/dist/cli/init/templates.d.ts.map +1 -1
- package/dist/cli/init/utils.d.ts.map +1 -1
- package/dist/cli/init/walkthrough.d.ts +10 -3
- package/dist/cli/init/walkthrough.d.ts.map +1 -1
- package/dist/cli.js +253 -31
- package/dist/controller.js +138 -1
- package/dist/lib/adjudicators.d.ts +63 -0
- package/dist/lib/adjudicators.d.ts.map +1 -0
- package/dist/lib/adjudicators.test.d.ts +2 -0
- package/dist/lib/adjudicators.test.d.ts.map +1 -0
- package/dist/lib/assets/loader.d.ts.map +1 -1
- package/dist/lib/assets/pods.d.ts +1 -0
- package/dist/lib/assets/pods.d.ts.map +1 -1
- package/dist/lib/capability.d.ts +1 -0
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/capability.test.d.ts +2 -0
- package/dist/lib/capability.test.d.ts.map +1 -0
- package/dist/lib/controller/index.d.ts.map +1 -1
- package/dist/lib/controller/store.d.ts +4 -0
- package/dist/lib/controller/store.d.ts.map +1 -1
- package/dist/lib/controller/store.test.d.ts +2 -0
- package/dist/lib/controller/store.test.d.ts.map +1 -0
- package/dist/lib/filter.d.ts +2 -3
- package/dist/lib/filter.d.ts.map +1 -1
- package/dist/lib/filter.test.d.ts +2 -1
- package/dist/lib/filter.test.d.ts.map +1 -1
- package/dist/lib/finalizer.d.ts +6 -0
- package/dist/lib/finalizer.d.ts.map +1 -0
- package/dist/lib/finalizer.test.d.ts +2 -0
- package/dist/lib/finalizer.test.d.ts.map +1 -0
- package/dist/lib/helpers.d.ts +2 -2
- package/dist/lib/helpers.d.ts.map +1 -1
- package/dist/lib/helpers.test.d.ts +1 -1
- package/dist/lib/helpers.test.d.ts.map +1 -1
- package/dist/lib/k8s.d.ts.map +1 -1
- package/dist/lib/logger.d.ts +1 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/module.d.ts +2 -1
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/mutate-processor.d.ts +2 -1
- package/dist/lib/mutate-processor.d.ts.map +1 -1
- package/dist/lib/mutate-request.d.ts +1 -2
- package/dist/lib/mutate-request.d.ts.map +1 -1
- package/dist/lib/queue.d.ts +19 -3
- package/dist/lib/queue.d.ts.map +1 -1
- package/dist/lib/schedule.d.ts +1 -2
- package/dist/lib/schedule.d.ts.map +1 -1
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/types.d.ts +118 -6
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/validate-processor.d.ts +4 -2
- package/dist/lib/validate-processor.d.ts.map +1 -1
- package/dist/lib/validate-request.d.ts +1 -1
- package/dist/lib/validate-request.d.ts.map +1 -1
- package/dist/lib/watch-processor.d.ts +8 -6
- package/dist/lib/watch-processor.d.ts.map +1 -1
- package/dist/lib.js +467 -233
- package/dist/lib.js.map +4 -4
- package/dist/sdk/sdk.d.ts +5 -3
- package/dist/sdk/sdk.d.ts.map +1 -1
- package/package.json +13 -11
- package/src/cli/build.ts +3 -3
- package/src/cli/init/index.ts +20 -11
- package/src/cli/init/templates.ts +1 -1
- package/src/cli/init/utils.test.ts +11 -20
- package/src/cli/init/utils.ts +5 -0
- package/src/cli/init/walkthrough.test.ts +92 -11
- package/src/cli/init/walkthrough.ts +71 -16
- package/src/cli/monitor.ts +1 -1
- package/src/cli.ts +4 -2
- package/src/fixtures/data/create-pod.json +1 -1
- package/src/fixtures/data/delete-pod.json +1 -1
- package/src/lib/adjudicators.test.ts +1232 -0
- package/src/lib/adjudicators.ts +235 -0
- package/src/lib/assets/index.ts +1 -1
- package/src/lib/assets/loader.ts +1 -0
- package/src/lib/assets/webhooks.ts +1 -1
- package/src/lib/capability.test.ts +655 -0
- package/src/lib/capability.ts +112 -11
- package/src/lib/controller/index.ts +7 -4
- package/src/lib/controller/store.test.ts +131 -0
- package/src/lib/controller/store.ts +43 -5
- package/src/lib/filter.test.ts +279 -9
- package/src/lib/filter.ts +46 -98
- package/src/lib/finalizer.test.ts +236 -0
- package/src/lib/finalizer.ts +63 -0
- package/src/lib/helpers.test.ts +359 -65
- package/src/lib/helpers.ts +141 -95
- package/src/lib/k8s.ts +4 -0
- package/src/lib/module.ts +3 -3
- package/src/lib/mutate-processor.ts +5 -4
- package/src/lib/mutate-request.test.ts +1 -2
- package/src/lib/mutate-request.ts +1 -3
- package/src/lib/queue.test.ts +138 -44
- package/src/lib/queue.ts +48 -13
- package/src/lib/schedule.ts +1 -1
- package/src/lib/storage.ts +5 -6
- package/src/lib/types.ts +154 -5
- package/src/lib/validate-processor.ts +5 -2
- package/src/lib/validate-request.test.ts +1 -4
- package/src/lib/validate-request.ts +1 -1
- package/src/lib/watch-processor.test.ts +89 -124
- package/src/lib/watch-processor.ts +52 -35
- package/src/sdk/sdk.test.ts +46 -13
- package/src/sdk/sdk.ts +15 -6
package/src/lib/capability.ts
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
4
|
import { GenericClass, GroupVersionKind, modelToGroupVersionKind } from "kubernetes-fluent-client";
|
|
5
|
-
import { WatchAction } from "kubernetes-fluent-client/dist/fluent/types";
|
|
6
5
|
import { pickBy } from "ramda";
|
|
7
|
-
|
|
8
6
|
import Log from "./logger";
|
|
9
7
|
import { isBuildMode, isDevMode, isWatchMode } from "./module";
|
|
10
8
|
import { PeprStore, Storage } from "./storage";
|
|
@@ -20,8 +18,12 @@ import {
|
|
|
20
18
|
MutateActionChain,
|
|
21
19
|
ValidateAction,
|
|
22
20
|
ValidateActionChain,
|
|
21
|
+
WatchLogAction,
|
|
22
|
+
FinalizeAction,
|
|
23
|
+
FinalizeActionChain,
|
|
23
24
|
WhenSelector,
|
|
24
25
|
} from "./types";
|
|
26
|
+
import { addFinalizer } from "./finalizer";
|
|
25
27
|
|
|
26
28
|
const registerAdmission = isBuildMode() || !isWatchMode();
|
|
27
29
|
const registerWatch = isBuildMode() || isWatchMode() || isDevMode();
|
|
@@ -69,6 +71,10 @@ export class Capability implements CapabilityExport {
|
|
|
69
71
|
}
|
|
70
72
|
};
|
|
71
73
|
|
|
74
|
+
public getScheduleStore() {
|
|
75
|
+
return this.#scheduleStore;
|
|
76
|
+
}
|
|
77
|
+
|
|
72
78
|
/**
|
|
73
79
|
* Store is a key-value data store that can be used to persist data that should be shared
|
|
74
80
|
* between requests. Each capability has its own store, and the data is persisted in Kubernetes
|
|
@@ -196,14 +202,17 @@ export class Capability implements CapabilityExport {
|
|
|
196
202
|
filters: {
|
|
197
203
|
name: "",
|
|
198
204
|
namespaces: [],
|
|
205
|
+
regexNamespaces: [],
|
|
206
|
+
regexName: "",
|
|
199
207
|
labels: {},
|
|
200
208
|
annotations: {},
|
|
209
|
+
deletionTimestamp: false,
|
|
201
210
|
},
|
|
202
211
|
};
|
|
203
212
|
|
|
204
213
|
const bindings = this.#bindings;
|
|
205
214
|
const prefix = `${this.#name}: ${model.name}`;
|
|
206
|
-
const commonChain = { WithLabel, WithAnnotation, Mutate, Validate, Watch, Reconcile };
|
|
215
|
+
const commonChain = { WithLabel, WithAnnotation, WithDeletionTimestamp, Mutate, Validate, Watch, Reconcile, Alias };
|
|
207
216
|
const isNotEmpty = (value: object) => Object.keys(value).length > 0;
|
|
208
217
|
const log = (message: string, cbString: string) => {
|
|
209
218
|
const filteredObj = pickBy(isNotEmpty, binding.filters);
|
|
@@ -217,12 +226,18 @@ export class Capability implements CapabilityExport {
|
|
|
217
226
|
if (registerAdmission) {
|
|
218
227
|
log("Validate Action", validateCallback.toString());
|
|
219
228
|
|
|
229
|
+
// Create the child logger
|
|
230
|
+
const aliasLogger = Log.child({ alias: binding.alias || "no alias provided" });
|
|
231
|
+
|
|
220
232
|
// Push the binding to the list of bindings for this capability as a new BindingAction
|
|
221
233
|
// with the callback function to preserve
|
|
222
234
|
bindings.push({
|
|
223
235
|
...binding,
|
|
224
236
|
isValidate: true,
|
|
225
|
-
validateCallback,
|
|
237
|
+
validateCallback: async (req, logger = aliasLogger) => {
|
|
238
|
+
Log.info(`Executing validate action with alias: ${binding.alias || "no alias provided"}`);
|
|
239
|
+
return await validateCallback(req, logger);
|
|
240
|
+
},
|
|
226
241
|
});
|
|
227
242
|
}
|
|
228
243
|
|
|
@@ -233,12 +248,18 @@ export class Capability implements CapabilityExport {
|
|
|
233
248
|
if (registerAdmission) {
|
|
234
249
|
log("Mutate Action", mutateCallback.toString());
|
|
235
250
|
|
|
251
|
+
// Create the child logger
|
|
252
|
+
const aliasLogger = Log.child({ alias: binding.alias || "no alias provided" });
|
|
253
|
+
|
|
236
254
|
// Push the binding to the list of bindings for this capability as a new BindingAction
|
|
237
255
|
// with the callback function to preserve
|
|
238
256
|
bindings.push({
|
|
239
257
|
...binding,
|
|
240
258
|
isMutate: true,
|
|
241
|
-
mutateCallback,
|
|
259
|
+
mutateCallback: async (req, logger = aliasLogger) => {
|
|
260
|
+
Log.info(`Executing mutation action with alias: ${binding.alias || "no alias provided"}`);
|
|
261
|
+
await mutateCallback(req, logger);
|
|
262
|
+
},
|
|
242
263
|
});
|
|
243
264
|
}
|
|
244
265
|
|
|
@@ -246,35 +267,105 @@ export class Capability implements CapabilityExport {
|
|
|
246
267
|
return { Watch, Validate, Reconcile };
|
|
247
268
|
}
|
|
248
269
|
|
|
249
|
-
function Watch(watchCallback:
|
|
270
|
+
function Watch(watchCallback: WatchLogAction<T>): FinalizeActionChain<T> {
|
|
250
271
|
if (registerWatch) {
|
|
251
272
|
log("Watch Action", watchCallback.toString());
|
|
252
273
|
|
|
274
|
+
// Create the child logger and cast it to the expected type
|
|
275
|
+
const aliasLogger = Log.child({ alias: binding.alias || "no alias provided" }) as typeof Log;
|
|
276
|
+
|
|
277
|
+
// Push the binding to the list of bindings for this capability as a new BindingAction
|
|
278
|
+
// with the callback function to preserve
|
|
253
279
|
bindings.push({
|
|
254
280
|
...binding,
|
|
255
281
|
isWatch: true,
|
|
256
|
-
watchCallback,
|
|
282
|
+
watchCallback: async (update, phase, logger = aliasLogger) => {
|
|
283
|
+
Log.info(`Executing watch action with alias: ${binding.alias || "no alias provided"}`);
|
|
284
|
+
await watchCallback(update, phase, logger);
|
|
285
|
+
},
|
|
257
286
|
});
|
|
258
287
|
}
|
|
288
|
+
return { Finalize };
|
|
259
289
|
}
|
|
260
290
|
|
|
261
|
-
function Reconcile(
|
|
291
|
+
function Reconcile(reconcileCallback: WatchLogAction<T>): FinalizeActionChain<T> {
|
|
262
292
|
if (registerWatch) {
|
|
263
|
-
log("Reconcile Action",
|
|
293
|
+
log("Reconcile Action", reconcileCallback.toString());
|
|
294
|
+
|
|
295
|
+
// Create the child logger and cast it to the expected type
|
|
296
|
+
const aliasLogger = Log.child({ alias: binding.alias || "no alias provided" }) as typeof Log;
|
|
264
297
|
|
|
298
|
+
// Push the binding to the list of bindings for this capability as a new BindingAction
|
|
299
|
+
// with the callback function to preserve
|
|
265
300
|
bindings.push({
|
|
266
301
|
...binding,
|
|
267
302
|
isWatch: true,
|
|
268
303
|
isQueue: true,
|
|
269
|
-
watchCallback,
|
|
304
|
+
watchCallback: async (update, phase, logger = aliasLogger) => {
|
|
305
|
+
Log.info(`Executing reconcile action with alias: ${binding.alias || "no alias provided"}`);
|
|
306
|
+
await reconcileCallback(update, phase, logger);
|
|
307
|
+
},
|
|
270
308
|
});
|
|
271
309
|
}
|
|
310
|
+
return { Finalize };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function Finalize(finalizeCallback: FinalizeAction<T>): void {
|
|
314
|
+
log("Finalize Action", finalizeCallback.toString());
|
|
315
|
+
|
|
316
|
+
// Create the child logger and cast it to the expected type
|
|
317
|
+
const aliasLogger = Log.child({ alias: binding.alias || "no alias provided" }) as typeof Log;
|
|
318
|
+
|
|
319
|
+
// Add binding to inject Pepr finalizer during admission (Mutate)
|
|
320
|
+
if (registerAdmission) {
|
|
321
|
+
const mutateBinding = {
|
|
322
|
+
...binding,
|
|
323
|
+
isMutate: true,
|
|
324
|
+
isFinalize: true,
|
|
325
|
+
event: Event.Any,
|
|
326
|
+
mutateCallback: addFinalizer,
|
|
327
|
+
};
|
|
328
|
+
bindings.push(mutateBinding);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Add binding to process finalizer callback / remove Pepr finalizer (Watch)
|
|
332
|
+
if (registerWatch) {
|
|
333
|
+
const watchBinding = {
|
|
334
|
+
...binding,
|
|
335
|
+
isWatch: true,
|
|
336
|
+
isFinalize: true,
|
|
337
|
+
event: Event.Update,
|
|
338
|
+
finalizeCallback: async (update: InstanceType<T>, logger = aliasLogger) => {
|
|
339
|
+
Log.info(`Executing finalize action with alias: ${binding.alias || "no alias provided"}`);
|
|
340
|
+
await finalizeCallback(update, logger);
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
bindings.push(watchBinding);
|
|
344
|
+
}
|
|
272
345
|
}
|
|
273
346
|
|
|
274
347
|
function InNamespace(...namespaces: string[]): BindingWithName<T> {
|
|
275
348
|
Log.debug(`Add namespaces filter ${namespaces}`, prefix);
|
|
276
349
|
binding.filters.namespaces.push(...namespaces);
|
|
277
|
-
return { ...commonChain, WithName };
|
|
350
|
+
return { ...commonChain, WithName, WithNameRegex };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function InNamespaceRegex(...namespaces: RegExp[]): BindingWithName<T> {
|
|
354
|
+
Log.debug(`Add regex namespaces filter ${namespaces}`, prefix);
|
|
355
|
+
binding.filters.regexNamespaces.push(...namespaces.map(regex => regex.source));
|
|
356
|
+
return { ...commonChain, WithName, WithNameRegex };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function WithDeletionTimestamp(): BindingFilter<T> {
|
|
360
|
+
Log.debug("Add deletionTimestamp filter");
|
|
361
|
+
binding.filters.deletionTimestamp = true;
|
|
362
|
+
return commonChain;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function WithNameRegex(regexName: RegExp): BindingFilter<T> {
|
|
366
|
+
Log.debug(`Add regex name filter ${regexName}`, prefix);
|
|
367
|
+
binding.filters.regexName = regexName.source;
|
|
368
|
+
return commonChain;
|
|
278
369
|
}
|
|
279
370
|
|
|
280
371
|
function WithName(name: string): BindingFilter<T> {
|
|
@@ -295,12 +386,22 @@ export class Capability implements CapabilityExport {
|
|
|
295
386
|
return commonChain;
|
|
296
387
|
}
|
|
297
388
|
|
|
389
|
+
function Alias(alias: string) {
|
|
390
|
+
Log.debug(`Adding prefix alias ${alias}`, prefix);
|
|
391
|
+
binding.alias = alias;
|
|
392
|
+
return commonChain;
|
|
393
|
+
}
|
|
394
|
+
|
|
298
395
|
function bindEvent(event: Event) {
|
|
299
396
|
binding.event = event;
|
|
300
397
|
return {
|
|
301
398
|
...commonChain,
|
|
302
399
|
InNamespace,
|
|
400
|
+
InNamespaceRegex,
|
|
303
401
|
WithName,
|
|
402
|
+
WithNameRegex,
|
|
403
|
+
WithDeletionTimestamp,
|
|
404
|
+
Alias,
|
|
304
405
|
};
|
|
305
406
|
}
|
|
306
407
|
|
|
@@ -15,6 +15,9 @@ import { validateProcessor } from "../validate-processor";
|
|
|
15
15
|
import { PeprControllerStore } from "./store";
|
|
16
16
|
import { ResponseItem } from "../types";
|
|
17
17
|
|
|
18
|
+
if (!process.env.PEPR_NODE_WARNINGS) {
|
|
19
|
+
process.removeAllListeners("warning");
|
|
20
|
+
}
|
|
18
21
|
export class Controller {
|
|
19
22
|
// Track whether the server is running
|
|
20
23
|
#running = false;
|
|
@@ -108,7 +111,7 @@ export class Controller {
|
|
|
108
111
|
// Handle EADDRINUSE errors
|
|
109
112
|
server.on("error", (e: { code: string }) => {
|
|
110
113
|
if (e.code === "EADDRINUSE") {
|
|
111
|
-
Log.
|
|
114
|
+
Log.info(
|
|
112
115
|
`Address in use, retrying in 2 seconds. If this persists, ensure ${port} is not in use, e.g. "lsof -i :${port}"`,
|
|
113
116
|
);
|
|
114
117
|
setTimeout(() => {
|
|
@@ -162,7 +165,7 @@ export class Controller {
|
|
|
162
165
|
const { token } = req.params;
|
|
163
166
|
if (token !== this.#token) {
|
|
164
167
|
const err = `Unauthorized: invalid token '${token.replace(/[^\w]/g, "_")}'`;
|
|
165
|
-
Log.
|
|
168
|
+
Log.info(err);
|
|
166
169
|
res.status(401).send(err);
|
|
167
170
|
this.#metricsCollector.alert();
|
|
168
171
|
return;
|
|
@@ -227,7 +230,7 @@ export class Controller {
|
|
|
227
230
|
if (admissionKind === "Mutate") {
|
|
228
231
|
response = await mutateProcessor(this.#config, this.#capabilities, request, reqMetadata);
|
|
229
232
|
} else {
|
|
230
|
-
response = await validateProcessor(this.#capabilities, request, reqMetadata);
|
|
233
|
+
response = await validateProcessor(this.#config, this.#capabilities, request, reqMetadata);
|
|
231
234
|
}
|
|
232
235
|
|
|
233
236
|
// Run the after hook if it exists
|
|
@@ -304,7 +307,7 @@ export class Controller {
|
|
|
304
307
|
duration: `${elapsedTime} ms`,
|
|
305
308
|
};
|
|
306
309
|
|
|
307
|
-
|
|
310
|
+
Log.info(message);
|
|
308
311
|
});
|
|
309
312
|
|
|
310
313
|
next();
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { expect, test, describe, afterEach } from "@jest/globals";
|
|
5
|
+
import * as fc from "fast-check";
|
|
6
|
+
import { redactedStore, redactedPatch } from "./store";
|
|
7
|
+
import { AddOperation } from "fast-json-patch";
|
|
8
|
+
|
|
9
|
+
const redactedValue = "**redacted**";
|
|
10
|
+
const peprStoreFuzz = fc.record({
|
|
11
|
+
kind: fc.constant("PeprStore"),
|
|
12
|
+
apiVersion: fc.constant("v1"),
|
|
13
|
+
metadata: fc.record({
|
|
14
|
+
name: fc.string(),
|
|
15
|
+
namespace: fc.string(),
|
|
16
|
+
}),
|
|
17
|
+
data: fc.dictionary(
|
|
18
|
+
fc.string().filter(str => str !== "__proto__"),
|
|
19
|
+
fc.string().filter(str => str !== "__proto__"),
|
|
20
|
+
),
|
|
21
|
+
});
|
|
22
|
+
describe("Fuzzing redactedStore", () => {
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
delete process.env.PEPR_STORE_REDACT_VALUES;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("should redact values if PEPR_STORE_REDACT_VALUES is true", () => {
|
|
28
|
+
fc.assert(
|
|
29
|
+
fc.property(peprStoreFuzz, store => {
|
|
30
|
+
process.env.PEPR_STORE_REDACT_VALUES = "true";
|
|
31
|
+
const result = redactedStore(store);
|
|
32
|
+
|
|
33
|
+
Object.values(result.data).forEach(value => {
|
|
34
|
+
expect(value).toBe(redactedValue);
|
|
35
|
+
});
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("should not redact values if PEPR_STORE_REDACT_VALUES is not true", () => {
|
|
41
|
+
fc.assert(
|
|
42
|
+
fc.property(peprStoreFuzz, store => {
|
|
43
|
+
process.env.PEPR_STORE_REDACT_VALUES = "false";
|
|
44
|
+
const result = redactedStore(store);
|
|
45
|
+
expect(result.data).toEqual(store.data);
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("should maintain other properties of the store", () => {
|
|
51
|
+
fc.assert(
|
|
52
|
+
fc.property(peprStoreFuzz, store => {
|
|
53
|
+
const redactionEnabled = fc.boolean();
|
|
54
|
+
process.env.PEPR_STORE_REDACT_VALUES = redactionEnabled ? "true" : "false";
|
|
55
|
+
|
|
56
|
+
const result = redactedStore(store);
|
|
57
|
+
|
|
58
|
+
expect(result.kind).toBe(store.kind);
|
|
59
|
+
expect(result.apiVersion).toBe(store.apiVersion);
|
|
60
|
+
expect(result.metadata).toEqual(store.metadata);
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const addOperationKeys = [
|
|
67
|
+
"add:/data/hello-pepr-a:secret",
|
|
68
|
+
"add:/data/hello-pepr-v2-b:secret",
|
|
69
|
+
"add:/data/hello-pepr-v2-c:secret",
|
|
70
|
+
"add:/data/hello-pepr-v2-d:secret",
|
|
71
|
+
"add:/data/hello-pepr-v2-e:secret",
|
|
72
|
+
"add:/data/hello-pepr-v2-f:secret",
|
|
73
|
+
];
|
|
74
|
+
const addOperationValues: AddOperation<string>[] = [
|
|
75
|
+
{
|
|
76
|
+
op: "add",
|
|
77
|
+
path: "add:/data/hello-pepr-a",
|
|
78
|
+
value: "secret",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
op: "add",
|
|
82
|
+
path: "add:/data/hello-pepr-v2-b",
|
|
83
|
+
value: "secret",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
op: "add",
|
|
87
|
+
path: "add:/data/hello-pepr-v2-c",
|
|
88
|
+
value: "secret",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
op: "add",
|
|
92
|
+
path: "add:/data/hello-pepr-v2-d",
|
|
93
|
+
value: "secret",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
op: "add",
|
|
97
|
+
path: "add:/data/hello-pepr-v2-e",
|
|
98
|
+
value: "secret",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
op: "add",
|
|
102
|
+
path: "add:/data/hello-pepr-v2-f",
|
|
103
|
+
value: "secret",
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
describe("redactedPatch", () => {
|
|
108
|
+
afterEach(() => {
|
|
109
|
+
delete process.env.PEPR_STORE_REDACT_VALUES;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("should redact keys and values if PEPR_STORE_REDACT_VALUES is true", () => {
|
|
113
|
+
process.env.PEPR_STORE_REDACT_VALUES = "true";
|
|
114
|
+
addOperationKeys.forEach((key, i) => {
|
|
115
|
+
const redactedResult = redactedPatch({ [key]: addOperationValues[i] });
|
|
116
|
+
for (const [k, v] of Object.entries(redactedResult)) {
|
|
117
|
+
expect(k).toContain(`:${redactedValue}`);
|
|
118
|
+
expect(v).toEqual(expect.objectContaining({ value: redactedValue }));
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
test("should not redact keys and values if PEPR_STORE_REDACT_VALUES is not true", () => {
|
|
123
|
+
addOperationKeys.forEach((key, i) => {
|
|
124
|
+
const redactedResult = redactedPatch({ [key]: addOperationValues[i] });
|
|
125
|
+
for (const [k, v] of Object.entries(redactedResult)) {
|
|
126
|
+
expect(k).not.toContain(`:${redactedValue}`);
|
|
127
|
+
expect(v).not.toEqual(expect.objectContaining({ value: redactedValue }));
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -10,6 +10,7 @@ import { PeprStore } from "../k8s";
|
|
|
10
10
|
import Log from "../logger";
|
|
11
11
|
import { DataOp, DataSender, DataStore, Storage } from "../storage";
|
|
12
12
|
|
|
13
|
+
const redactedValue = "**redacted**";
|
|
13
14
|
const namespace = "pepr-system";
|
|
14
15
|
export const debounceBackoff = 5000;
|
|
15
16
|
|
|
@@ -75,7 +76,7 @@ export class PeprControllerStore {
|
|
|
75
76
|
};
|
|
76
77
|
|
|
77
78
|
#migrateAndSetupWatch = async (store: PeprStore) => {
|
|
78
|
-
Log.debug(store, "Pepr Store migration");
|
|
79
|
+
Log.debug(redactedStore(store), "Pepr Store migration");
|
|
79
80
|
const data: DataStore = store.data || {};
|
|
80
81
|
const migrateCache: Record<string, Operation> = {};
|
|
81
82
|
|
|
@@ -91,7 +92,9 @@ export class PeprControllerStore {
|
|
|
91
92
|
|
|
92
93
|
try {
|
|
93
94
|
// Send the patch to the cluster
|
|
94
|
-
|
|
95
|
+
if (payload.length > 0) {
|
|
96
|
+
await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);
|
|
97
|
+
}
|
|
95
98
|
} catch (err) {
|
|
96
99
|
Log.error(err, "Pepr store update failure");
|
|
97
100
|
|
|
@@ -158,7 +161,7 @@ export class PeprControllerStore {
|
|
|
158
161
|
};
|
|
159
162
|
|
|
160
163
|
#receive = (store: PeprStore) => {
|
|
161
|
-
Log.debug(store, "Pepr Store update");
|
|
164
|
+
Log.debug(redactedStore(store), "Pepr Store update");
|
|
162
165
|
|
|
163
166
|
// Wrap the update in a debounced function
|
|
164
167
|
const debounced = () => {
|
|
@@ -247,7 +250,9 @@ export class PeprControllerStore {
|
|
|
247
250
|
|
|
248
251
|
try {
|
|
249
252
|
// Send the patch to the cluster
|
|
250
|
-
|
|
253
|
+
if (payload.length > 0) {
|
|
254
|
+
await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);
|
|
255
|
+
}
|
|
251
256
|
} catch (err) {
|
|
252
257
|
Log.error(err, "Pepr store update failure");
|
|
253
258
|
|
|
@@ -270,7 +275,7 @@ export class PeprControllerStore {
|
|
|
270
275
|
// Send any cached updates every debounceBackoff milliseconds
|
|
271
276
|
setInterval(() => {
|
|
272
277
|
if (Object.keys(sendCache).length > 0) {
|
|
273
|
-
Log.debug(sendCache, "Sending updates to Pepr store");
|
|
278
|
+
Log.debug(redactedPatch(sendCache), "Sending updates to Pepr store");
|
|
274
279
|
void flushCache();
|
|
275
280
|
}
|
|
276
281
|
}, debounceBackoff);
|
|
@@ -301,3 +306,36 @@ export class PeprControllerStore {
|
|
|
301
306
|
}
|
|
302
307
|
};
|
|
303
308
|
}
|
|
309
|
+
|
|
310
|
+
export function redactedStore(store: PeprStore): PeprStore {
|
|
311
|
+
const redacted = process.env.PEPR_STORE_REDACT_VALUES === "true";
|
|
312
|
+
return {
|
|
313
|
+
...store,
|
|
314
|
+
data: Object.keys(store.data).reduce((acc: Record<string, string>, key: string) => {
|
|
315
|
+
acc[key] = redacted ? redactedValue : store.data[key];
|
|
316
|
+
return acc;
|
|
317
|
+
}, {}),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function redactedPatch(patch: Record<string, Operation> = {}): Record<string, Operation> {
|
|
322
|
+
const redacted = process.env.PEPR_STORE_REDACT_VALUES === "true";
|
|
323
|
+
|
|
324
|
+
if (!redacted) {
|
|
325
|
+
return patch;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const redactedCache: Record<string, Operation> = {};
|
|
329
|
+
|
|
330
|
+
Object.keys(patch).forEach(key => {
|
|
331
|
+
const operation = patch[key];
|
|
332
|
+
const redactedKey = key.includes(":") ? key.substring(0, key.lastIndexOf(":")) + ":**redacted**" : key;
|
|
333
|
+
const redactedOperation: Operation = {
|
|
334
|
+
...operation,
|
|
335
|
+
...("value" in operation ? { value: "**redacted**" } : {}),
|
|
336
|
+
};
|
|
337
|
+
redactedCache[redactedKey] = redactedOperation;
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
return redactedCache;
|
|
341
|
+
}
|