pepr 0.12.2 → 0.13.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/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +70 -0
- package/README.md +28 -30
- package/dist/cli.js +666 -692
- package/dist/controller.js +13 -81
- package/dist/lib/assets/deploy.d.ts +3 -0
- package/dist/lib/assets/deploy.d.ts.map +1 -0
- package/dist/lib/assets/index.d.ts +17 -0
- package/dist/lib/assets/index.d.ts.map +1 -0
- package/dist/lib/assets/loader.d.ts +8 -0
- package/dist/lib/assets/loader.d.ts.map +1 -0
- package/dist/lib/assets/networking.d.ts +6 -0
- package/dist/lib/assets/networking.d.ts.map +1 -0
- package/dist/lib/assets/pods.d.ts +8 -0
- package/dist/lib/assets/pods.d.ts.map +1 -0
- package/dist/lib/assets/rbac.d.ts +11 -0
- package/dist/lib/assets/rbac.d.ts.map +1 -0
- package/dist/lib/assets/webhooks.d.ts +6 -0
- package/dist/lib/assets/webhooks.d.ts.map +1 -0
- package/dist/lib/assets/yaml.d.ts +4 -0
- package/dist/lib/assets/yaml.d.ts.map +1 -0
- package/dist/lib/capability.d.ts +4 -9
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/controller.d.ts +4 -15
- package/dist/lib/controller.d.ts.map +1 -1
- package/dist/lib/errors.d.ts +12 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/filter.d.ts +1 -1
- package/dist/lib/filter.d.ts.map +1 -1
- package/dist/lib/k8s/index.d.ts +2 -1
- package/dist/lib/k8s/index.d.ts.map +1 -1
- package/dist/lib/k8s/kinds.d.ts.map +1 -1
- package/dist/lib/k8s/types.d.ts +18 -14
- package/dist/lib/k8s/types.d.ts.map +1 -1
- package/dist/lib/k8s/upstream.d.ts +2 -2
- package/dist/lib/k8s/upstream.d.ts.map +1 -1
- package/dist/lib/logger.d.ts +8 -54
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/metrics.d.ts +10 -9
- package/dist/lib/metrics.d.ts.map +1 -1
- package/dist/lib/module.d.ts +4 -4
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/mutate-processor.d.ts +5 -0
- package/dist/lib/mutate-processor.d.ts.map +1 -0
- package/dist/lib/{request.d.ts → mutate-request.d.ts} +7 -7
- package/dist/lib/mutate-request.d.ts.map +1 -0
- package/dist/lib/types.d.ts +48 -55
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/validate-processor.d.ts +4 -0
- package/dist/lib/validate-processor.d.ts.map +1 -0
- package/dist/lib/validate-request.d.ts +54 -0
- package/dist/lib/validate-request.d.ts.map +1 -0
- package/dist/lib.d.ts +3 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +610 -354
- package/dist/lib.js.map +4 -4
- package/jest.config.json +4 -0
- package/journey/before.ts +21 -0
- package/journey/k8s.ts +81 -0
- package/journey/pepr-build.ts +69 -0
- package/journey/pepr-deploy.ts +133 -0
- package/journey/pepr-dev.ts +155 -0
- package/journey/pepr-format.ts +13 -0
- package/journey/pepr-init.ts +12 -0
- package/package.json +29 -27
- package/src/cli.ts +2 -11
- package/src/lib/assets/deploy.ts +179 -0
- package/src/lib/assets/index.ts +53 -0
- package/src/lib/assets/loader.ts +41 -0
- package/src/lib/assets/networking.ts +58 -0
- package/src/lib/assets/pods.ts +148 -0
- package/src/lib/assets/rbac.ts +57 -0
- package/src/lib/assets/webhooks.ts +139 -0
- package/src/lib/assets/yaml.ts +75 -0
- package/src/lib/capability.ts +80 -68
- package/src/lib/controller.ts +199 -99
- package/src/lib/errors.ts +20 -0
- package/src/lib/fetch.ts +1 -1
- package/src/lib/filter.ts +1 -3
- package/src/lib/k8s/index.ts +4 -1
- package/src/lib/k8s/kinds.ts +40 -0
- package/src/lib/k8s/types.ts +21 -15
- package/src/lib/k8s/upstream.ts +5 -1
- package/src/lib/logger.ts +14 -125
- package/src/lib/metrics.ts +86 -29
- package/src/lib/module.ts +32 -16
- package/src/lib/{processor.ts → mutate-processor.ts} +39 -28
- package/src/lib/{request.ts → mutate-request.ts} +26 -13
- package/src/lib/types.ts +54 -60
- package/src/lib/validate-processor.ts +76 -0
- package/src/lib/validate-request.ts +106 -0
- package/src/lib.ts +4 -2
- package/src/runtime/controller.ts +1 -1
- package/dist/lib/k8s/webhook.d.ts +0 -37
- package/dist/lib/k8s/webhook.d.ts.map +0 -1
- package/dist/lib/processor.d.ts +0 -5
- package/dist/lib/processor.d.ts.map +0 -1
- package/dist/lib/request.d.ts.map +0 -1
- package/src/lib/k8s/webhook.ts +0 -643
|
@@ -10,11 +10,13 @@ import { DeepPartial } from "./types";
|
|
|
10
10
|
* The RequestWrapper class provides methods to modify Kubernetes objects in the context
|
|
11
11
|
* of a mutating webhook request.
|
|
12
12
|
*/
|
|
13
|
-
export class
|
|
14
|
-
|
|
13
|
+
export class PeprMutateRequest<T extends KubernetesObject> {
|
|
14
|
+
Raw: T;
|
|
15
|
+
|
|
16
|
+
#input: Request<T>;
|
|
15
17
|
|
|
16
18
|
get PermitSideEffects() {
|
|
17
|
-
return !this.
|
|
19
|
+
return !this.#input.dryRun;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -22,7 +24,7 @@ export class PeprRequest<T extends KubernetesObject> {
|
|
|
22
24
|
* @returns true if the request is a dry run, false otherwise.
|
|
23
25
|
*/
|
|
24
26
|
get IsDryRun() {
|
|
25
|
-
return this.
|
|
27
|
+
return this.#input.dryRun;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -30,7 +32,7 @@ export class PeprRequest<T extends KubernetesObject> {
|
|
|
30
32
|
* @returns The old Kubernetes resource object or null if not available.
|
|
31
33
|
*/
|
|
32
34
|
get OldResource() {
|
|
33
|
-
return this.
|
|
35
|
+
return this.#input.oldObject;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
/**
|
|
@@ -38,20 +40,31 @@ export class PeprRequest<T extends KubernetesObject> {
|
|
|
38
40
|
* @returns The request object containing the Kubernetes resource.
|
|
39
41
|
*/
|
|
40
42
|
get Request() {
|
|
41
|
-
return this
|
|
43
|
+
return this.#input;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
/**
|
|
45
|
-
* Creates a new instance of the
|
|
47
|
+
* Creates a new instance of the action class.
|
|
46
48
|
* @param input - The request object containing the Kubernetes resource to modify.
|
|
47
49
|
*/
|
|
48
|
-
constructor(
|
|
50
|
+
constructor(input: Request<T>) {
|
|
51
|
+
this.#input = input;
|
|
52
|
+
|
|
53
|
+
// Bind public methods
|
|
54
|
+
this.Merge = this.Merge.bind(this);
|
|
55
|
+
this.SetLabel = this.SetLabel.bind(this);
|
|
56
|
+
this.SetAnnotation = this.SetAnnotation.bind(this);
|
|
57
|
+
this.RemoveLabel = this.RemoveLabel.bind(this);
|
|
58
|
+
this.RemoveAnnotation = this.RemoveAnnotation.bind(this);
|
|
59
|
+
this.HasLabel = this.HasLabel.bind(this);
|
|
60
|
+
this.HasAnnotation = this.HasAnnotation.bind(this);
|
|
61
|
+
|
|
49
62
|
// If this is a DELETE operation, use the oldObject instead
|
|
50
|
-
if (
|
|
51
|
-
this.Raw = clone(
|
|
63
|
+
if (input.operation.toUpperCase() === Operation.DELETE) {
|
|
64
|
+
this.Raw = clone(input.oldObject as T);
|
|
52
65
|
} else {
|
|
53
66
|
// Otherwise, use the incoming object
|
|
54
|
-
this.Raw = clone(
|
|
67
|
+
this.Raw = clone(input.object);
|
|
55
68
|
}
|
|
56
69
|
|
|
57
70
|
if (!this.Raw) {
|
|
@@ -72,7 +85,7 @@ export class PeprRequest<T extends KubernetesObject> {
|
|
|
72
85
|
* Updates a label on the Kubernetes resource.
|
|
73
86
|
* @param key - The key of the label to update.
|
|
74
87
|
* @param value - The value of the label.
|
|
75
|
-
* @returns The current
|
|
88
|
+
* @returns The current action instance for method chaining.
|
|
76
89
|
*/
|
|
77
90
|
SetLabel(key: string, value: string) {
|
|
78
91
|
const ref = this.Raw;
|
|
@@ -88,7 +101,7 @@ export class PeprRequest<T extends KubernetesObject> {
|
|
|
88
101
|
* Updates an annotation on the Kubernetes resource.
|
|
89
102
|
* @param key - The key of the annotation to update.
|
|
90
103
|
* @param value - The value of the annotation.
|
|
91
|
-
* @returns The current
|
|
104
|
+
* @returns The current action instance for method chaining.
|
|
92
105
|
*/
|
|
93
106
|
SetAnnotation(key: string, value: string) {
|
|
94
107
|
const ref = this.Raw;
|
package/src/lib/types.ts
CHANGED
|
@@ -2,32 +2,14 @@
|
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
4
|
import { GroupVersionKind, KubernetesObject, WebhookIgnore } from "./k8s/types";
|
|
5
|
-
import {
|
|
5
|
+
import { PeprMutateRequest } from "./mutate-request";
|
|
6
|
+
import { PeprValidateRequest } from "./validate-request";
|
|
6
7
|
|
|
7
8
|
export type PackageJSON = {
|
|
8
9
|
description: string;
|
|
9
10
|
pepr: ModuleConfig;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
|
-
/**
|
|
13
|
-
* The behavior of this module when an error occurs.
|
|
14
|
-
*/
|
|
15
|
-
export enum ErrorBehavior {
|
|
16
|
-
ignore = "ignore",
|
|
17
|
-
audit = "audit",
|
|
18
|
-
reject = "reject",
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* The phase of the Kubernetes admission webhook that the capability is registered for.
|
|
23
|
-
*
|
|
24
|
-
* Currently only `mutate` is supported.
|
|
25
|
-
*/
|
|
26
|
-
export enum HookPhase {
|
|
27
|
-
mutate = "mutate",
|
|
28
|
-
validate = "validate",
|
|
29
|
-
}
|
|
30
|
-
|
|
31
13
|
/**
|
|
32
14
|
* Recursively make all properties in T optional.
|
|
33
15
|
*/
|
|
@@ -36,9 +18,8 @@ export type DeepPartial<T> = {
|
|
|
36
18
|
};
|
|
37
19
|
|
|
38
20
|
/**
|
|
39
|
-
* The type of Kubernetes mutating webhook event that the
|
|
21
|
+
* The type of Kubernetes mutating webhook event that the action is registered for.
|
|
40
22
|
*/
|
|
41
|
-
|
|
42
23
|
export enum Event {
|
|
43
24
|
Create = "CREATE",
|
|
44
25
|
Update = "UPDATE",
|
|
@@ -61,14 +42,10 @@ export interface CapabilityCfg {
|
|
|
61
42
|
* This does not supersede the `alwaysIgnore` global configuration.
|
|
62
43
|
*/
|
|
63
44
|
namespaces?: string[];
|
|
45
|
+
}
|
|
64
46
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
*
|
|
68
|
-
* Declare if this capability should be used for mutation or validation. Currently this is not used
|
|
69
|
-
* and everything is considered a mutation.
|
|
70
|
-
*/
|
|
71
|
-
mutateOrValidate?: HookPhase;
|
|
47
|
+
export interface CapabilityExport extends CapabilityCfg {
|
|
48
|
+
bindings: Binding[];
|
|
72
49
|
}
|
|
73
50
|
|
|
74
51
|
export type ModuleSigning = {
|
|
@@ -99,7 +76,7 @@ export type ModuleConfig = {
|
|
|
99
76
|
/** A description of the Pepr module and what it does. */
|
|
100
77
|
description?: string;
|
|
101
78
|
/** Reject K8s resource AdmissionRequests on error. */
|
|
102
|
-
onError
|
|
79
|
+
onError?: string;
|
|
103
80
|
/** Configure global exclusions that will never be processed by Pepr. */
|
|
104
81
|
alwaysIgnore: WebhookIgnore;
|
|
105
82
|
/**
|
|
@@ -115,18 +92,20 @@ export type ModuleConfig = {
|
|
|
115
92
|
export type GenericClass = abstract new () => any;
|
|
116
93
|
|
|
117
94
|
export type WhenSelector<T extends GenericClass> = {
|
|
118
|
-
/** Register
|
|
95
|
+
/** Register an action to be executed when a Kubernetes resource is created or updated. */
|
|
119
96
|
IsCreatedOrUpdated: () => BindingAll<T>;
|
|
120
|
-
/** Register
|
|
97
|
+
/** Register an action to be executed when a Kubernetes resource is created. */
|
|
121
98
|
IsCreated: () => BindingAll<T>;
|
|
122
|
-
/** Register
|
|
99
|
+
/** Register ann action to be executed when a Kubernetes resource is updated. */
|
|
123
100
|
IsUpdated: () => BindingAll<T>;
|
|
124
|
-
/** Register
|
|
101
|
+
/** Register an action to be executed when a Kubernetes resource is deleted. */
|
|
125
102
|
IsDeleted: () => BindingAll<T>;
|
|
126
103
|
};
|
|
127
104
|
|
|
128
105
|
export type Binding = {
|
|
129
106
|
event: Event;
|
|
107
|
+
isMutate?: boolean;
|
|
108
|
+
isValidate?: boolean;
|
|
130
109
|
readonly kind: GroupVersionKind;
|
|
131
110
|
readonly filters: {
|
|
132
111
|
name: string;
|
|
@@ -134,12 +113,13 @@ export type Binding = {
|
|
|
134
113
|
labels: Record<string, string>;
|
|
135
114
|
annotations: Record<string, string>;
|
|
136
115
|
};
|
|
137
|
-
readonly
|
|
116
|
+
readonly mutateCallback?: MutateAction<GenericClass, InstanceType<GenericClass>>;
|
|
117
|
+
readonly validateCallback?: ValidateAction<GenericClass, InstanceType<GenericClass>>;
|
|
138
118
|
};
|
|
139
119
|
|
|
140
|
-
export type BindingFilter<T extends GenericClass> =
|
|
120
|
+
export type BindingFilter<T extends GenericClass> = CommonActionChain<T> & {
|
|
141
121
|
/**
|
|
142
|
-
* Only apply the
|
|
122
|
+
* Only apply the action if the resource has the specified label. If no value is specified, the label must exist.
|
|
143
123
|
* Note multiple calls to this method will result in an AND condition. e.g.
|
|
144
124
|
*
|
|
145
125
|
* ```ts
|
|
@@ -147,17 +127,17 @@ export type BindingFilter<T extends GenericClass> = BindToActionOrSet<T> & {
|
|
|
147
127
|
* .IsCreated()
|
|
148
128
|
* .WithLabel("foo", "bar")
|
|
149
129
|
* .WithLabel("baz", "qux")
|
|
150
|
-
* .
|
|
130
|
+
* .Mutate(...)
|
|
151
131
|
* ```
|
|
152
132
|
*
|
|
153
|
-
* Will only apply the
|
|
133
|
+
* Will only apply the action if the resource has both the `foo=bar` and `baz=qux` labels.
|
|
154
134
|
*
|
|
155
135
|
* @param key
|
|
156
136
|
* @param value
|
|
157
137
|
*/
|
|
158
138
|
WithLabel: (key: string, value?: string) => BindingFilter<T>;
|
|
159
139
|
/**
|
|
160
|
-
* Only apply the
|
|
140
|
+
* Only apply the action if the resource has the specified annotation. If no value is specified, the annotation must exist.
|
|
161
141
|
* Note multiple calls to this method will result in an AND condition. e.g.
|
|
162
142
|
*
|
|
163
143
|
* ```ts
|
|
@@ -165,10 +145,10 @@ export type BindingFilter<T extends GenericClass> = BindToActionOrSet<T> & {
|
|
|
165
145
|
* .IsCreated()
|
|
166
146
|
* .WithAnnotation("foo", "bar")
|
|
167
147
|
* .WithAnnotation("baz", "qux")
|
|
168
|
-
* .
|
|
148
|
+
* .Mutate(...)
|
|
169
149
|
* ```
|
|
170
150
|
*
|
|
171
|
-
* Will only apply the
|
|
151
|
+
* Will only apply the action if the resource has both the `foo=bar` and `baz=qux` annotations.
|
|
172
152
|
*
|
|
173
153
|
* @param key
|
|
174
154
|
* @param value
|
|
@@ -177,43 +157,57 @@ export type BindingFilter<T extends GenericClass> = BindToActionOrSet<T> & {
|
|
|
177
157
|
};
|
|
178
158
|
|
|
179
159
|
export type BindingWithName<T extends GenericClass> = BindingFilter<T> & {
|
|
180
|
-
/** Only apply the
|
|
160
|
+
/** Only apply the action if the resource name matches the specified name. */
|
|
181
161
|
WithName: (name: string) => BindingFilter<T>;
|
|
182
162
|
};
|
|
183
163
|
|
|
184
164
|
export type BindingAll<T extends GenericClass> = BindingWithName<T> & {
|
|
185
|
-
/** Only apply the
|
|
165
|
+
/** Only apply the action if the resource is in one of the specified namespaces.*/
|
|
186
166
|
InNamespace: (...namespaces: string[]) => BindingWithName<T>;
|
|
187
167
|
};
|
|
188
168
|
|
|
189
|
-
export type
|
|
169
|
+
export type CommonActionChain<T extends GenericClass> = MutateActionChain<T> & {
|
|
190
170
|
/**
|
|
191
|
-
* Create a new
|
|
171
|
+
* Create a new MUTATE action with the specified callback function and previously specified
|
|
192
172
|
* filters.
|
|
193
|
-
* @param action The
|
|
173
|
+
* @param action The action to be executed when the Kubernetes resource is processed by the AdmissionController.
|
|
194
174
|
*/
|
|
195
|
-
|
|
175
|
+
Mutate: (action: MutateAction<T, InstanceType<T>>) => MutateActionChain<T>;
|
|
196
176
|
};
|
|
197
177
|
|
|
198
|
-
export type
|
|
178
|
+
export type MutateActionChain<T extends GenericClass> = {
|
|
199
179
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* Example change the `minReadySeconds` to 3 of a deployment when it is created:
|
|
180
|
+
* Create a new VALIDATE action with the specified callback function and previously specified
|
|
181
|
+
* filters. Return the `request.Approve()` or `Request.Deny()` methods to approve or deny the request:
|
|
204
182
|
*
|
|
183
|
+
* @example
|
|
205
184
|
* ```ts
|
|
206
185
|
* When(a.Deployment)
|
|
207
186
|
* .IsCreated()
|
|
208
|
-
* .
|
|
187
|
+
* .Validate(request => {
|
|
188
|
+
* if (request.HasLabel("foo")) {
|
|
189
|
+
* return request.Approve();
|
|
190
|
+
* }
|
|
191
|
+
*
|
|
192
|
+
* return request.Deny("Deployment must have label foo");
|
|
193
|
+
* });
|
|
209
194
|
* ```
|
|
210
195
|
*
|
|
211
|
-
* @param
|
|
212
|
-
* @returns
|
|
196
|
+
* @param action The action to be executed when the Kubernetes resource is processed by the AdmissionController.
|
|
213
197
|
*/
|
|
214
|
-
|
|
198
|
+
Validate: (action: ValidateAction<T, InstanceType<T>>) => void;
|
|
215
199
|
};
|
|
216
200
|
|
|
217
|
-
export type
|
|
218
|
-
req:
|
|
219
|
-
) => Promise<void> | void | Promise<
|
|
201
|
+
export type MutateAction<T extends GenericClass, K extends KubernetesObject = InstanceType<T>> = (
|
|
202
|
+
req: PeprMutateRequest<K>,
|
|
203
|
+
) => Promise<void> | void | Promise<PeprMutateRequest<K>> | PeprMutateRequest<K>;
|
|
204
|
+
|
|
205
|
+
export type ValidateAction<T extends GenericClass, K extends KubernetesObject = InstanceType<T>> = (
|
|
206
|
+
req: PeprValidateRequest<K>,
|
|
207
|
+
) => Promise<ValidateResponse> | ValidateResponse;
|
|
208
|
+
|
|
209
|
+
export type ValidateResponse = {
|
|
210
|
+
allowed: boolean;
|
|
211
|
+
statusCode?: number;
|
|
212
|
+
statusMessage?: string;
|
|
213
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { Capability } from "./capability";
|
|
5
|
+
import { shouldSkipRequest } from "./filter";
|
|
6
|
+
import { Request, ValidateResponse } from "./k8s/types";
|
|
7
|
+
import { Secret } from "./k8s/upstream";
|
|
8
|
+
import Log from "./logger";
|
|
9
|
+
import { convertFromBase64Map } from "./utils";
|
|
10
|
+
import { PeprValidateRequest } from "./validate-request";
|
|
11
|
+
|
|
12
|
+
export async function validateProcessor(
|
|
13
|
+
capabilities: Capability[],
|
|
14
|
+
req: Request,
|
|
15
|
+
reqMetadata: Record<string, string>,
|
|
16
|
+
): Promise<ValidateResponse> {
|
|
17
|
+
const wrapped = new PeprValidateRequest(req);
|
|
18
|
+
const response: ValidateResponse = {
|
|
19
|
+
uid: req.uid,
|
|
20
|
+
allowed: true, // Assume it's allowed until a validation check fails
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// If the resource is a secret, decode the data
|
|
24
|
+
const isSecret = req.kind.version == "v1" && req.kind.kind == "Secret";
|
|
25
|
+
if (isSecret) {
|
|
26
|
+
convertFromBase64Map(wrapped.Raw as unknown as Secret);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Log.info(reqMetadata, `Processing validation request`);
|
|
30
|
+
|
|
31
|
+
for (const { name, bindings } of capabilities) {
|
|
32
|
+
const actionMetadata = { ...reqMetadata, name };
|
|
33
|
+
|
|
34
|
+
for (const action of bindings) {
|
|
35
|
+
// Skip this action if it's not a validation action
|
|
36
|
+
if (!action.validateCallback) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Continue to the next action without doing anything if this one should be skipped
|
|
41
|
+
if (shouldSkipRequest(action, req)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const label = action.validateCallback.name;
|
|
46
|
+
Log.info(actionMetadata, `Processing matched action ${label}`);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Run the validation callback, if it fails set allowed to false
|
|
50
|
+
const resp = await action.validateCallback(wrapped);
|
|
51
|
+
response.allowed = resp.allowed;
|
|
52
|
+
|
|
53
|
+
// If the validation callback returned a status code or message, set it in the Response
|
|
54
|
+
if (resp.statusCode || resp.statusMessage) {
|
|
55
|
+
response.status = {
|
|
56
|
+
code: resp.statusCode || 400,
|
|
57
|
+
message: resp.statusMessage || `Validation failed for ${name}`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
Log.info(actionMetadata, `Validation Action completed: ${resp.allowed ? "allowed" : "denied"}`);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
// If any validation throws an error, note the failure in the Response
|
|
64
|
+
Log.error(actionMetadata, `Action failed: ${e}`);
|
|
65
|
+
response.allowed = false;
|
|
66
|
+
response.status = {
|
|
67
|
+
code: 500,
|
|
68
|
+
message: `Action failed with error: ${e}`,
|
|
69
|
+
};
|
|
70
|
+
return response;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return response;
|
|
76
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
/* eslint-disable class-methods-use-this */
|
|
5
|
+
|
|
6
|
+
import { clone } from "ramda";
|
|
7
|
+
import { KubernetesObject, Operation, Request } from "./k8s/types";
|
|
8
|
+
import { ValidateResponse } from "./types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The RequestWrapper class provides methods to modify Kubernetes objects in the context
|
|
12
|
+
* of a mutating webhook request.
|
|
13
|
+
*/
|
|
14
|
+
export class PeprValidateRequest<T extends KubernetesObject> {
|
|
15
|
+
Raw: T;
|
|
16
|
+
|
|
17
|
+
#input: Request<T>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Provides access to the old resource in the request if available.
|
|
21
|
+
* @returns The old Kubernetes resource object or null if not available.
|
|
22
|
+
*/
|
|
23
|
+
get OldResource() {
|
|
24
|
+
return this.#input.oldObject;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Provides access to the request object.
|
|
29
|
+
* @returns The request object containing the Kubernetes resource.
|
|
30
|
+
*/
|
|
31
|
+
get Request() {
|
|
32
|
+
return this.#input;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates a new instance of the Action class.
|
|
37
|
+
* @param input - The request object containing the Kubernetes resource to modify.
|
|
38
|
+
*/
|
|
39
|
+
constructor(input: Request<T>) {
|
|
40
|
+
this.#input = input;
|
|
41
|
+
|
|
42
|
+
// Bind public methods to this instance
|
|
43
|
+
this.HasLabel = this.HasLabel.bind(this);
|
|
44
|
+
this.HasAnnotation = this.HasAnnotation.bind(this);
|
|
45
|
+
this.Approve = this.Approve.bind(this);
|
|
46
|
+
this.Deny = this.Deny.bind(this);
|
|
47
|
+
|
|
48
|
+
// If this is a DELETE operation, use the oldObject instead
|
|
49
|
+
if (input.operation.toUpperCase() === Operation.DELETE) {
|
|
50
|
+
this.Raw = clone(input.oldObject as T);
|
|
51
|
+
} else {
|
|
52
|
+
// Otherwise, use the incoming object
|
|
53
|
+
this.Raw = clone(input.object);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!this.Raw) {
|
|
57
|
+
throw new Error("unable to load the request object into PeprRequest.RawP");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if a label exists on the Kubernetes resource.
|
|
63
|
+
*
|
|
64
|
+
* @param key the label key to check
|
|
65
|
+
* @returns
|
|
66
|
+
*/
|
|
67
|
+
HasLabel(key: string) {
|
|
68
|
+
return this.Raw.metadata?.labels?.[key] !== undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if an annotation exists on the Kubernetes resource.
|
|
73
|
+
*
|
|
74
|
+
* @param key the annotation key to check
|
|
75
|
+
* @returns
|
|
76
|
+
*/
|
|
77
|
+
HasAnnotation(key: string) {
|
|
78
|
+
return this.Raw.metadata?.annotations?.[key] !== undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a validation response that allows the request.
|
|
83
|
+
*
|
|
84
|
+
* @returns The validation response.
|
|
85
|
+
*/
|
|
86
|
+
Approve(): ValidateResponse {
|
|
87
|
+
return {
|
|
88
|
+
allowed: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Create a validation response that denies the request.
|
|
94
|
+
*
|
|
95
|
+
* @param statusMessage Optional status message to return to the user.
|
|
96
|
+
* @param statusCode Optional status code to return to the user.
|
|
97
|
+
* @returns The validation response.
|
|
98
|
+
*/
|
|
99
|
+
Deny(statusMessage?: string, statusCode?: number): ValidateResponse {
|
|
100
|
+
return {
|
|
101
|
+
allowed: false,
|
|
102
|
+
statusCode,
|
|
103
|
+
statusMessage,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
package/src/lib.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { fetch, fetchRaw } from "./lib/fetch";
|
|
|
6
6
|
import { RegisterKind, a } from "./lib/k8s/index";
|
|
7
7
|
import Log from "./lib/logger";
|
|
8
8
|
import { PeprModule } from "./lib/module";
|
|
9
|
-
import {
|
|
9
|
+
import { PeprMutateRequest } from "./lib/mutate-request";
|
|
10
|
+
import { PeprValidateRequest } from "./lib/validate-request";
|
|
10
11
|
import * as PeprUtils from "./lib/utils";
|
|
11
12
|
|
|
12
13
|
// Import type information for external packages
|
|
@@ -17,7 +18,8 @@ export {
|
|
|
17
18
|
a,
|
|
18
19
|
/** PeprModule is used to setup a complete Pepr Module: `new PeprModule(cfg, {...capabilities})` */
|
|
19
20
|
PeprModule,
|
|
20
|
-
|
|
21
|
+
PeprMutateRequest,
|
|
22
|
+
PeprValidateRequest,
|
|
21
23
|
PeprUtils,
|
|
22
24
|
RegisterKind,
|
|
23
25
|
Capability,
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { V1ClusterRole, V1ClusterRoleBinding, V1Deployment, V1MutatingWebhookConfiguration, V1Namespace, V1RuleWithOperations, V1Secret, V1Service, V1ServiceAccount } from "@kubernetes/client-node";
|
|
3
|
-
import { ModuleConfig } from "../types";
|
|
4
|
-
import { TLSOut } from "./tls";
|
|
5
|
-
export declare class Webhook {
|
|
6
|
-
private readonly config;
|
|
7
|
-
private readonly host?;
|
|
8
|
-
private name;
|
|
9
|
-
private _tls;
|
|
10
|
-
private _apiToken;
|
|
11
|
-
image: string;
|
|
12
|
-
get tls(): TLSOut;
|
|
13
|
-
get apiToken(): string;
|
|
14
|
-
constructor(config: ModuleConfig, host?: string | undefined);
|
|
15
|
-
/** Generate the pepr-system namespace */
|
|
16
|
-
namespace(): V1Namespace;
|
|
17
|
-
/**
|
|
18
|
-
* Grants the controller access to cluster resources beyond the mutating webhook.
|
|
19
|
-
*
|
|
20
|
-
* @todo: should dynamically generate this based on resources used by the module. will also need to explore how this should work for multiple modules.
|
|
21
|
-
* @returns
|
|
22
|
-
*/
|
|
23
|
-
clusterRole(): V1ClusterRole;
|
|
24
|
-
clusterRoleBinding(): V1ClusterRoleBinding;
|
|
25
|
-
serviceAccount(): V1ServiceAccount;
|
|
26
|
-
apiTokenSecret(): V1Secret;
|
|
27
|
-
tlsSecret(): V1Secret;
|
|
28
|
-
generateWebhookRules(path: string): Promise<V1RuleWithOperations[]>;
|
|
29
|
-
mutatingWebhook(path: string, timeoutSeconds?: number): Promise<V1MutatingWebhookConfiguration>;
|
|
30
|
-
deployment(hash: string): V1Deployment;
|
|
31
|
-
service(): V1Service;
|
|
32
|
-
moduleSecret(data: Buffer, hash: string): V1Secret;
|
|
33
|
-
zarfYaml(path: string): string;
|
|
34
|
-
allYaml(path: string): Promise<string>;
|
|
35
|
-
deploy(path: string, webhookTimeout?: number): Promise<void>;
|
|
36
|
-
}
|
|
37
|
-
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../../src/lib/k8s/webhook.ts"],"names":[],"mappings":";AAGA,OAAO,EAQL,aAAa,EACb,oBAAoB,EACpB,YAAY,EAEZ,8BAA8B,EAC9B,WAAW,EACX,oBAAoB,EACpB,QAAQ,EACR,SAAS,EACT,gBAAgB,EAEjB,MAAM,yBAAyB,CAAC;AAQjC,OAAO,EAA6B,YAAY,EAAE,MAAM,UAAU,CAAC;AACnE,OAAO,EAAE,MAAM,EAAU,MAAM,OAAO,CAAC;AAQvC,qBAAa,OAAO;IAeN,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAgB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IAdzE,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,SAAS,CAAS;IAEnB,KAAK,EAAE,MAAM,CAAC;IAErB,IAAW,GAAG,IAAI,MAAM,CAEvB;IAED,IAAW,QAAQ,IAAI,MAAM,CAE5B;gBAE4B,MAAM,EAAE,YAAY,EAAmB,IAAI,CAAC,oBAAQ;IAYjF,yCAAyC;IACzC,SAAS,IAAI,WAAW;IAQxB;;;;;OAKG;IACH,WAAW,IAAI,aAAa;IAgB5B,kBAAkB,IAAI,oBAAoB;IAqB1C,cAAc,IAAI,gBAAgB;IAWlC,cAAc,IAAI,QAAQ;IAe1B,SAAS,IAAI,QAAQ;IAgBrB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAwF7D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,SAAK,GAAG,OAAO,CAAC,8BAA8B,CAAC;IA4DjG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY;IAoGtC,OAAO,IAAI,SAAS;IAsBpB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ;IAkBlD,QAAQ,CAAC,IAAI,EAAE,MAAM;IA4Bf,OAAO,CAAC,IAAI,EAAE,MAAM;IAyBpB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM;CA6InD"}
|
package/dist/lib/processor.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { Capability } from "./capability";
|
|
2
|
-
import { Request, Response } from "./k8s/types";
|
|
3
|
-
import { ModuleConfig } from "./types";
|
|
4
|
-
export declare function processor(config: ModuleConfig, capabilities: Capability[], req: Request, parentPrefix: string): Promise<Response>;
|
|
5
|
-
//# sourceMappingURL=processor.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../../src/lib/processor.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAIhD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGvC,wBAAsB,SAAS,CAC7B,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,UAAU,EAAE,EAC1B,GAAG,EAAE,OAAO,EACZ,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,QAAQ,CAAC,CAsHnB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/lib/request.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,gBAAgB,EAAa,OAAO,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC;;;GAGG;AACH,qBAAa,WAAW,CAAC,CAAC,SAAS,gBAAgB;IAmCrC,OAAO,CAAC,MAAM;IAlCnB,GAAG,EAAE,CAAC,CAAC;IAEd,IAAI,iBAAiB,YAEpB;IAED;;;OAGG;IACH,IAAI,QAAQ,wBAEX;IAED;;;OAGG;IACH,IAAI,WAAW,kBAEd;IAED;;;OAGG;IACH,IAAI,OAAO,eAEV;IAED;;;OAGG;gBACiB,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IActC;;;;OAIG;IACH,KAAK,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC;IAIzB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAUnC;;;;;OAKG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAUxC;;;;OAIG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM;IAQvB;;;;OAIG;IACH,gBAAgB,CAAC,GAAG,EAAE,MAAM;IAQ5B;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM;IAIpB;;;;;OAKG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM;CAG1B"}
|