pepr 0.36.0 → 0.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/init/index.d.ts.map +1 -1
- package/dist/cli/init/templates.d.ts +4 -3
- 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/types.d.ts +3 -0
- package/dist/cli/types.d.ts.map +1 -0
- 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/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/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 +113 -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 +1 -1
- package/dist/lib/watch-processor.d.ts.map +1 -1
- package/dist/lib.js +383 -204
- package/dist/lib.js.map +4 -4
- package/package.json +13 -12
- 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/types.ts +3 -0
- 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 +104 -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 +194 -8
- package/src/lib/filter.ts +46 -107
- package/src/lib/finalizer.test.ts +236 -0
- package/src/lib/finalizer.ts +63 -0
- package/src/lib/helpers.test.ts +329 -69
- package/src/lib/helpers.ts +141 -100
- 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/schedule.ts +1 -1
- package/src/lib/storage.ts +5 -6
- package/src/lib/types.ts +148 -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.ts +19 -5
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,6 +202,8 @@ export class Capability implements CapabilityExport {
|
|
|
196
202
|
filters: {
|
|
197
203
|
name: "",
|
|
198
204
|
namespaces: [],
|
|
205
|
+
regexNamespaces: [],
|
|
206
|
+
regexName: "",
|
|
199
207
|
labels: {},
|
|
200
208
|
annotations: {},
|
|
201
209
|
deletionTimestamp: false,
|
|
@@ -204,7 +212,7 @@ export class Capability implements CapabilityExport {
|
|
|
204
212
|
|
|
205
213
|
const bindings = this.#bindings;
|
|
206
214
|
const prefix = `${this.#name}: ${model.name}`;
|
|
207
|
-
const commonChain = { WithLabel, WithAnnotation, WithDeletionTimestamp, Mutate, Validate, Watch, Reconcile };
|
|
215
|
+
const commonChain = { WithLabel, WithAnnotation, WithDeletionTimestamp, Mutate, Validate, Watch, Reconcile, Alias };
|
|
208
216
|
const isNotEmpty = (value: object) => Object.keys(value).length > 0;
|
|
209
217
|
const log = (message: string, cbString: string) => {
|
|
210
218
|
const filteredObj = pickBy(isNotEmpty, binding.filters);
|
|
@@ -218,12 +226,18 @@ export class Capability implements CapabilityExport {
|
|
|
218
226
|
if (registerAdmission) {
|
|
219
227
|
log("Validate Action", validateCallback.toString());
|
|
220
228
|
|
|
229
|
+
// Create the child logger
|
|
230
|
+
const aliasLogger = Log.child({ alias: binding.alias || "no alias provided" });
|
|
231
|
+
|
|
221
232
|
// Push the binding to the list of bindings for this capability as a new BindingAction
|
|
222
233
|
// with the callback function to preserve
|
|
223
234
|
bindings.push({
|
|
224
235
|
...binding,
|
|
225
236
|
isValidate: true,
|
|
226
|
-
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
|
+
},
|
|
227
241
|
});
|
|
228
242
|
}
|
|
229
243
|
|
|
@@ -234,12 +248,18 @@ export class Capability implements CapabilityExport {
|
|
|
234
248
|
if (registerAdmission) {
|
|
235
249
|
log("Mutate Action", mutateCallback.toString());
|
|
236
250
|
|
|
251
|
+
// Create the child logger
|
|
252
|
+
const aliasLogger = Log.child({ alias: binding.alias || "no alias provided" });
|
|
253
|
+
|
|
237
254
|
// Push the binding to the list of bindings for this capability as a new BindingAction
|
|
238
255
|
// with the callback function to preserve
|
|
239
256
|
bindings.push({
|
|
240
257
|
...binding,
|
|
241
258
|
isMutate: true,
|
|
242
|
-
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
|
+
},
|
|
243
263
|
});
|
|
244
264
|
}
|
|
245
265
|
|
|
@@ -247,35 +267,93 @@ export class Capability implements CapabilityExport {
|
|
|
247
267
|
return { Watch, Validate, Reconcile };
|
|
248
268
|
}
|
|
249
269
|
|
|
250
|
-
function Watch(watchCallback:
|
|
270
|
+
function Watch(watchCallback: WatchLogAction<T>): FinalizeActionChain<T> {
|
|
251
271
|
if (registerWatch) {
|
|
252
272
|
log("Watch Action", watchCallback.toString());
|
|
253
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
|
|
254
279
|
bindings.push({
|
|
255
280
|
...binding,
|
|
256
281
|
isWatch: true,
|
|
257
|
-
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
|
+
},
|
|
258
286
|
});
|
|
259
287
|
}
|
|
288
|
+
return { Finalize };
|
|
260
289
|
}
|
|
261
290
|
|
|
262
|
-
function Reconcile(
|
|
291
|
+
function Reconcile(reconcileCallback: WatchLogAction<T>): FinalizeActionChain<T> {
|
|
263
292
|
if (registerWatch) {
|
|
264
|
-
log("Reconcile Action",
|
|
293
|
+
log("Reconcile Action", reconcileCallback.toString());
|
|
265
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;
|
|
297
|
+
|
|
298
|
+
// Push the binding to the list of bindings for this capability as a new BindingAction
|
|
299
|
+
// with the callback function to preserve
|
|
266
300
|
bindings.push({
|
|
267
301
|
...binding,
|
|
268
302
|
isWatch: true,
|
|
269
303
|
isQueue: true,
|
|
270
|
-
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
|
+
},
|
|
271
308
|
});
|
|
272
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
|
+
}
|
|
273
345
|
}
|
|
274
346
|
|
|
275
347
|
function InNamespace(...namespaces: string[]): BindingWithName<T> {
|
|
276
348
|
Log.debug(`Add namespaces filter ${namespaces}`, prefix);
|
|
277
349
|
binding.filters.namespaces.push(...namespaces);
|
|
278
|
-
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 };
|
|
279
357
|
}
|
|
280
358
|
|
|
281
359
|
function WithDeletionTimestamp(): BindingFilter<T> {
|
|
@@ -284,6 +362,12 @@ export class Capability implements CapabilityExport {
|
|
|
284
362
|
return commonChain;
|
|
285
363
|
}
|
|
286
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;
|
|
369
|
+
}
|
|
370
|
+
|
|
287
371
|
function WithName(name: string): BindingFilter<T> {
|
|
288
372
|
Log.debug(`Add name filter ${name}`, prefix);
|
|
289
373
|
binding.filters.name = name;
|
|
@@ -302,13 +386,22 @@ export class Capability implements CapabilityExport {
|
|
|
302
386
|
return commonChain;
|
|
303
387
|
}
|
|
304
388
|
|
|
389
|
+
function Alias(alias: string) {
|
|
390
|
+
Log.debug(`Adding prefix alias ${alias}`, prefix);
|
|
391
|
+
binding.alias = alias;
|
|
392
|
+
return commonChain;
|
|
393
|
+
}
|
|
394
|
+
|
|
305
395
|
function bindEvent(event: Event) {
|
|
306
396
|
binding.event = event;
|
|
307
397
|
return {
|
|
308
398
|
...commonChain,
|
|
309
399
|
InNamespace,
|
|
400
|
+
InNamespaceRegex,
|
|
310
401
|
WithName,
|
|
402
|
+
WithNameRegex,
|
|
311
403
|
WithDeletionTimestamp,
|
|
404
|
+
Alias,
|
|
312
405
|
};
|
|
313
406
|
}
|
|
314
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
|
+
}
|