pepr 0.12.2 → 0.13.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/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +70 -0
- package/README.md +28 -30
- package/dist/cli.js +644 -679
- 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 +18 -0
- package/dist/lib/assets/index.d.ts.map +1 -0
- package/dist/lib/assets/loader.d.ts +14 -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 +1 -3
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/controller.d.ts +45 -10
- package/dist/lib/controller.d.ts.map +1 -1
- 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 +13 -13
- 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 +11 -4
- package/dist/lib/metrics.d.ts.map +1 -1
- package/dist/lib/module.d.ts +2 -2
- 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} +5 -5
- package/dist/lib/mutate-request.d.ts.map +1 -0
- package/dist/lib/types.d.ts +45 -46
- 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 +502 -302
- package/dist/lib.js.map +4 -4
- package/package.json +15 -12
- package/src/cli.ts +2 -11
- package/src/lib/assets/deploy.ts +179 -0
- package/src/lib/assets/index.ts +46 -0
- package/src/lib/assets/loader.ts +49 -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 +54 -44
- package/src/lib/controller.ts +171 -89
- 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 +16 -14
- package/src/lib/k8s/upstream.ts +5 -1
- package/src/lib/logger.ts +14 -125
- package/src/lib/metrics.ts +67 -23
- package/src/lib/module.ts +13 -11
- package/src/lib/{processor.ts → mutate-processor.ts} +37 -28
- package/src/lib/{request.ts → mutate-request.ts} +4 -4
- package/src/lib/types.ts +51 -51
- package/src/lib/validate-processor.ts +68 -0
- package/src/lib/validate-request.ts +94 -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
package/dist/lib.js
CHANGED
|
@@ -33,8 +33,9 @@ __export(lib_exports, {
|
|
|
33
33
|
Capability: () => Capability,
|
|
34
34
|
Log: () => logger_default,
|
|
35
35
|
PeprModule: () => PeprModule,
|
|
36
|
-
|
|
36
|
+
PeprMutateRequest: () => PeprMutateRequest,
|
|
37
37
|
PeprUtils: () => utils_exports,
|
|
38
|
+
PeprValidateRequest: () => PeprValidateRequest,
|
|
38
39
|
R: () => R,
|
|
39
40
|
RegisterKind: () => RegisterKind,
|
|
40
41
|
a: () => upstream_exports,
|
|
@@ -48,55 +49,63 @@ var k8s = __toESM(require("@kubernetes/client-node"));
|
|
|
48
49
|
var import_http_status_codes2 = require("http-status-codes");
|
|
49
50
|
var R = __toESM(require("ramda"));
|
|
50
51
|
|
|
52
|
+
// src/lib/capability.ts
|
|
53
|
+
var import_ramda = require("ramda");
|
|
54
|
+
|
|
51
55
|
// src/lib/k8s/upstream.ts
|
|
52
56
|
var upstream_exports = {};
|
|
53
57
|
__export(upstream_exports, {
|
|
54
|
-
APIService: () =>
|
|
55
|
-
CSIDriver: () =>
|
|
56
|
-
CSIStorageCapacity: () =>
|
|
57
|
-
CertificateSigningRequest: () =>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
APIService: () => import_client_node2.V1APIService,
|
|
59
|
+
CSIDriver: () => import_client_node2.V1CSIDriver,
|
|
60
|
+
CSIStorageCapacity: () => import_client_node2.V1CSIStorageCapacity,
|
|
61
|
+
CertificateSigningRequest: () => import_client_node2.V1CertificateSigningRequest,
|
|
62
|
+
ClusterRole: () => import_client_node2.V1ClusterRole,
|
|
63
|
+
ClusterRoleBinding: () => import_client_node2.V1ClusterRoleBinding,
|
|
64
|
+
ConfigMap: () => import_client_node2.V1ConfigMap,
|
|
65
|
+
ControllerRevision: () => import_client_node2.V1ControllerRevision,
|
|
66
|
+
CronJob: () => import_client_node2.V1CronJob,
|
|
67
|
+
CustomResourceDefinition: () => import_client_node2.V1CustomResourceDefinition,
|
|
68
|
+
DaemonSet: () => import_client_node2.V1DaemonSet,
|
|
69
|
+
Deployment: () => import_client_node2.V1Deployment,
|
|
70
|
+
EndpointSlice: () => import_client_node2.V1EndpointSlice,
|
|
65
71
|
GenericKind: () => GenericKind,
|
|
66
|
-
HorizontalPodAutoscaler: () =>
|
|
67
|
-
Ingress: () =>
|
|
68
|
-
IngressClass: () =>
|
|
69
|
-
Job: () =>
|
|
70
|
-
LimitRange: () =>
|
|
71
|
-
LocalSubjectAccessReview: () =>
|
|
72
|
-
MutatingWebhookConfiguration: () =>
|
|
73
|
-
Namespace: () =>
|
|
74
|
-
NetworkPolicy: () =>
|
|
75
|
-
Node: () =>
|
|
76
|
-
PersistentVolume: () =>
|
|
77
|
-
PersistentVolumeClaim: () =>
|
|
78
|
-
Pod: () =>
|
|
79
|
-
PodDisruptionBudget: () =>
|
|
80
|
-
PodTemplate: () =>
|
|
81
|
-
ReplicaSet: () =>
|
|
82
|
-
ReplicationController: () =>
|
|
83
|
-
ResourceQuota: () =>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
72
|
+
HorizontalPodAutoscaler: () => import_client_node2.V1HorizontalPodAutoscaler,
|
|
73
|
+
Ingress: () => import_client_node2.V1Ingress,
|
|
74
|
+
IngressClass: () => import_client_node2.V1IngressClass,
|
|
75
|
+
Job: () => import_client_node2.V1Job,
|
|
76
|
+
LimitRange: () => import_client_node2.V1LimitRange,
|
|
77
|
+
LocalSubjectAccessReview: () => import_client_node2.V1LocalSubjectAccessReview,
|
|
78
|
+
MutatingWebhookConfiguration: () => import_client_node2.V1MutatingWebhookConfiguration,
|
|
79
|
+
Namespace: () => import_client_node2.V1Namespace,
|
|
80
|
+
NetworkPolicy: () => import_client_node2.V1NetworkPolicy,
|
|
81
|
+
Node: () => import_client_node2.V1Node,
|
|
82
|
+
PersistentVolume: () => import_client_node2.V1PersistentVolume,
|
|
83
|
+
PersistentVolumeClaim: () => import_client_node2.V1PersistentVolumeClaim,
|
|
84
|
+
Pod: () => import_client_node2.V1Pod,
|
|
85
|
+
PodDisruptionBudget: () => import_client_node2.V1PodDisruptionBudget,
|
|
86
|
+
PodTemplate: () => import_client_node2.V1PodTemplate,
|
|
87
|
+
ReplicaSet: () => import_client_node2.V1ReplicaSet,
|
|
88
|
+
ReplicationController: () => import_client_node2.V1ReplicationController,
|
|
89
|
+
ResourceQuota: () => import_client_node2.V1ResourceQuota,
|
|
90
|
+
Role: () => import_client_node2.V1Role,
|
|
91
|
+
RoleBinding: () => import_client_node2.V1RoleBinding,
|
|
92
|
+
RuntimeClass: () => import_client_node2.V1RuntimeClass,
|
|
93
|
+
Secret: () => import_client_node2.V1Secret,
|
|
94
|
+
SelfSubjectAccessReview: () => import_client_node2.V1SelfSubjectAccessReview,
|
|
95
|
+
SelfSubjectRulesReview: () => import_client_node2.V1SelfSubjectRulesReview,
|
|
96
|
+
Service: () => import_client_node2.V1Service,
|
|
97
|
+
ServiceAccount: () => import_client_node2.V1ServiceAccount,
|
|
98
|
+
StatefulSet: () => import_client_node2.V1StatefulSet,
|
|
99
|
+
StorageClass: () => import_client_node2.V1StorageClass,
|
|
100
|
+
SubjectAccessReview: () => import_client_node2.V1SubjectAccessReview,
|
|
101
|
+
TokenReview: () => import_client_node2.V1TokenReview,
|
|
102
|
+
ValidatingWebhookConfiguration: () => import_client_node2.V1ValidatingWebhookConfiguration,
|
|
103
|
+
VolumeAttachment: () => import_client_node2.V1VolumeAttachment
|
|
96
104
|
});
|
|
97
|
-
var
|
|
105
|
+
var import_client_node2 = require("@kubernetes/client-node");
|
|
98
106
|
|
|
99
107
|
// src/lib/k8s/types.ts
|
|
108
|
+
var import_client_node = require("@kubernetes/client-node");
|
|
100
109
|
var GenericKind = class {
|
|
101
110
|
apiVersion;
|
|
102
111
|
kind;
|
|
@@ -105,6 +114,46 @@ var GenericKind = class {
|
|
|
105
114
|
|
|
106
115
|
// src/lib/k8s/kinds.ts
|
|
107
116
|
var gvkMap = {
|
|
117
|
+
/**
|
|
118
|
+
* Represents a K8s ClusterRole resource.
|
|
119
|
+
* ClusterRole is a set of permissions that can be bound to a user or group in a cluster-wide scope.
|
|
120
|
+
* @see {@link https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole}
|
|
121
|
+
*/
|
|
122
|
+
V1ClusterRole: {
|
|
123
|
+
kind: "ClusterRole",
|
|
124
|
+
version: "v1",
|
|
125
|
+
group: "rbac.authorization.k8s.io"
|
|
126
|
+
},
|
|
127
|
+
/**
|
|
128
|
+
* Represents a K8s ClusterRoleBinding resource.
|
|
129
|
+
* ClusterRoleBinding binds a ClusterRole to a user or group in a cluster-wide scope.
|
|
130
|
+
* @see {@link https://kubernetes.io/docs/reference/access-authn-authz/rbac/#rolebinding-and-clusterrolebinding}
|
|
131
|
+
*/
|
|
132
|
+
V1ClusterRoleBinding: {
|
|
133
|
+
kind: "ClusterRoleBinding",
|
|
134
|
+
version: "v1",
|
|
135
|
+
group: "rbac.authorization.k8s.io"
|
|
136
|
+
},
|
|
137
|
+
/**
|
|
138
|
+
* Represents a K8s Role resource.
|
|
139
|
+
* Role is a set of permissions that can be bound to a user or group in a namespace scope.
|
|
140
|
+
* @see {@link https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole}
|
|
141
|
+
*/
|
|
142
|
+
V1Role: {
|
|
143
|
+
kind: "Role",
|
|
144
|
+
version: "v1",
|
|
145
|
+
group: "rbac.authorization.k8s.io"
|
|
146
|
+
},
|
|
147
|
+
/**
|
|
148
|
+
* Represents a K8s RoleBinding resource.
|
|
149
|
+
* RoleBinding binds a Role to a user or group in a namespace scope.
|
|
150
|
+
* @see {@link https://kubernetes.io/docs/reference/access-authn-authz/rbac/#rolebinding-and-clusterrolebinding}
|
|
151
|
+
*/
|
|
152
|
+
V1RoleBinding: {
|
|
153
|
+
kind: "RoleBinding",
|
|
154
|
+
version: "v1",
|
|
155
|
+
group: "rbac.authorization.k8s.io"
|
|
156
|
+
},
|
|
108
157
|
/**
|
|
109
158
|
* Represents a K8s ConfigMap resource.
|
|
110
159
|
* ConfigMap holds configuration data for pods to consume.
|
|
@@ -539,89 +588,24 @@ var RegisterKind = (model, groupVersionKind) => {
|
|
|
539
588
|
gvkMap[name] = groupVersionKind;
|
|
540
589
|
};
|
|
541
590
|
|
|
591
|
+
// src/lib/k8s/index.ts
|
|
592
|
+
var isWatchMode = process.env.PEPR_WATCH_MODE === "true";
|
|
593
|
+
|
|
542
594
|
// src/lib/logger.ts
|
|
543
|
-
var
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
})(LogLevel || {});
|
|
550
|
-
var Logger = class {
|
|
551
|
-
_logLevel;
|
|
552
|
-
/**
|
|
553
|
-
* Create a new logger instance.
|
|
554
|
-
* @param logLevel - The minimum log level to log messages for.
|
|
555
|
-
*/
|
|
556
|
-
constructor(logLevel) {
|
|
557
|
-
this._logLevel = logLevel;
|
|
558
|
-
}
|
|
559
|
-
/**
|
|
560
|
-
* Change the log level of the logger.
|
|
561
|
-
* @param logLevel - The log level to log the message at.
|
|
562
|
-
*/
|
|
563
|
-
SetLogLevel(logLevel) {
|
|
564
|
-
this._logLevel = LogLevel[logLevel];
|
|
565
|
-
this.debug(`Log level set to ${logLevel}`);
|
|
566
|
-
}
|
|
567
|
-
/**
|
|
568
|
-
* Log a debug message.
|
|
569
|
-
* @param message - The message to log.
|
|
570
|
-
*/
|
|
571
|
-
debug(message, prefix) {
|
|
572
|
-
this.log(0 /* debug */, message, prefix);
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* Log an info message.
|
|
576
|
-
* @param message - The message to log.
|
|
577
|
-
*/
|
|
578
|
-
info(message, prefix) {
|
|
579
|
-
this.log(1 /* info */, message, prefix);
|
|
580
|
-
}
|
|
581
|
-
/**
|
|
582
|
-
* Log a warning message.
|
|
583
|
-
* @param message - The message to log.
|
|
584
|
-
*/
|
|
585
|
-
warn(message, prefix) {
|
|
586
|
-
this.log(2 /* warn */, message, prefix);
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* Log an error message.
|
|
590
|
-
* @param message - The message to log.
|
|
591
|
-
*/
|
|
592
|
-
error(message, prefix) {
|
|
593
|
-
this.log(3 /* error */, message, prefix);
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Log a message at the specified log level.
|
|
597
|
-
* @param logLevel - The log level of the message.
|
|
598
|
-
* @param message - The message to log.
|
|
599
|
-
*/
|
|
600
|
-
log(logLevel, message, callerPrefix = "") {
|
|
601
|
-
const color = {
|
|
602
|
-
[0 /* debug */]: "\x1B[30m" /* FgBlack */,
|
|
603
|
-
[1 /* info */]: "\x1B[36m" /* FgCyan */,
|
|
604
|
-
[2 /* warn */]: "\x1B[33m" /* FgYellow */,
|
|
605
|
-
[3 /* error */]: "\x1B[31m" /* FgRed */
|
|
606
|
-
};
|
|
607
|
-
if (logLevel >= this._logLevel) {
|
|
608
|
-
let prefix = "[" + LogLevel[logLevel] + "] " + callerPrefix;
|
|
609
|
-
prefix = this.colorize(prefix, color[logLevel]);
|
|
610
|
-
if (typeof message !== "string") {
|
|
611
|
-
console.log(prefix);
|
|
612
|
-
console.debug("%o", message);
|
|
613
|
-
} else {
|
|
614
|
-
console.log(prefix + " " + message);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
colorize(text, color) {
|
|
619
|
-
return color + text + "\x1B[0m" /* Reset */;
|
|
595
|
+
var import_pino = require("pino");
|
|
596
|
+
var isPrettyLog = process.env.PEPR_PRETTY_LOGS === "true";
|
|
597
|
+
var pretty = {
|
|
598
|
+
target: "pino-pretty",
|
|
599
|
+
options: {
|
|
600
|
+
colorize: true
|
|
620
601
|
}
|
|
621
602
|
};
|
|
622
|
-
var
|
|
603
|
+
var transport = isPrettyLog ? pretty : void 0;
|
|
604
|
+
var Log = (0, import_pino.pino)({
|
|
605
|
+
transport
|
|
606
|
+
});
|
|
623
607
|
if (process.env.LOG_LEVEL) {
|
|
624
|
-
Log.
|
|
608
|
+
Log.level = process.env.LOG_LEVEL;
|
|
625
609
|
}
|
|
626
610
|
var logger_default = Log;
|
|
627
611
|
|
|
@@ -630,8 +614,6 @@ var Capability = class {
|
|
|
630
614
|
_name;
|
|
631
615
|
_description;
|
|
632
616
|
_namespaces;
|
|
633
|
-
// Currently everything is considered a mutation
|
|
634
|
-
_mutateOrValidate = "mutate" /* mutate */;
|
|
635
617
|
_bindings = [];
|
|
636
618
|
get bindings() {
|
|
637
619
|
return this._bindings;
|
|
@@ -645,9 +627,6 @@ var Capability = class {
|
|
|
645
627
|
get namespaces() {
|
|
646
628
|
return this._namespaces || [];
|
|
647
629
|
}
|
|
648
|
-
get mutateOrValidate() {
|
|
649
|
-
return this._mutateOrValidate;
|
|
650
|
-
}
|
|
651
630
|
constructor(cfg) {
|
|
652
631
|
this._name = cfg.name;
|
|
653
632
|
this._description = cfg.description;
|
|
@@ -678,52 +657,63 @@ var Capability = class {
|
|
|
678
657
|
namespaces: [],
|
|
679
658
|
labels: {},
|
|
680
659
|
annotations: {}
|
|
681
|
-
}
|
|
682
|
-
callback: () => void 0
|
|
660
|
+
}
|
|
683
661
|
};
|
|
684
662
|
const prefix = `${this._name}: ${model.name}`;
|
|
685
|
-
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
logger_default.
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
callback: cb
|
|
692
|
-
});
|
|
693
|
-
return { Then };
|
|
663
|
+
const isNotEmpty = (value) => Object.keys(value).length > 0;
|
|
664
|
+
const log = (message, cbString) => {
|
|
665
|
+
const filteredObj = (0, import_ramda.pickBy)(isNotEmpty, binding.filters);
|
|
666
|
+
logger_default.info(`${message} configured for ${binding.event}`, prefix);
|
|
667
|
+
logger_default.info(filteredObj, prefix);
|
|
668
|
+
logger_default.debug(cbString, prefix);
|
|
694
669
|
};
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
670
|
+
const Validate = (validateCallback) => {
|
|
671
|
+
if (!isWatchMode) {
|
|
672
|
+
log("Validate Action", validateCallback.toString());
|
|
673
|
+
this._bindings.push({
|
|
674
|
+
...binding,
|
|
675
|
+
isValidate: true,
|
|
676
|
+
validateCallback
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
const Mutate = (mutateCallback) => {
|
|
681
|
+
if (!isWatchMode) {
|
|
682
|
+
log("Mutate Action", mutateCallback.toString());
|
|
683
|
+
this._bindings.push({
|
|
684
|
+
...binding,
|
|
685
|
+
isMutate: true,
|
|
686
|
+
mutateCallback
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
return { Validate };
|
|
698
690
|
};
|
|
699
691
|
function InNamespace(...namespaces) {
|
|
700
692
|
logger_default.debug(`Add namespaces filter ${namespaces}`, prefix);
|
|
701
693
|
binding.filters.namespaces.push(...namespaces);
|
|
702
|
-
return {
|
|
694
|
+
return { ...commonChain, WithName };
|
|
703
695
|
}
|
|
704
696
|
function WithName(name) {
|
|
705
697
|
logger_default.debug(`Add name filter ${name}`, prefix);
|
|
706
698
|
binding.filters.name = name;
|
|
707
|
-
return
|
|
699
|
+
return commonChain;
|
|
708
700
|
}
|
|
709
701
|
function WithLabel(key, value = "") {
|
|
710
702
|
logger_default.debug(`Add label filter ${key}=${value}`, prefix);
|
|
711
703
|
binding.filters.labels[key] = value;
|
|
712
|
-
return
|
|
704
|
+
return commonChain;
|
|
713
705
|
}
|
|
714
706
|
const WithAnnotation = (key, value = "") => {
|
|
715
707
|
logger_default.debug(`Add annotation filter ${key}=${value}`, prefix);
|
|
716
708
|
binding.filters.annotations[key] = value;
|
|
717
|
-
return
|
|
709
|
+
return commonChain;
|
|
718
710
|
};
|
|
711
|
+
const commonChain = { WithLabel, WithAnnotation, Mutate, Validate };
|
|
719
712
|
const bindEvent = (event) => {
|
|
720
713
|
binding.event = event;
|
|
721
714
|
return {
|
|
715
|
+
...commonChain,
|
|
722
716
|
InNamespace,
|
|
723
|
-
Then,
|
|
724
|
-
ThenSet,
|
|
725
|
-
WithAnnotation,
|
|
726
|
-
WithLabel,
|
|
727
717
|
WithName
|
|
728
718
|
};
|
|
729
719
|
};
|
|
@@ -743,7 +733,7 @@ var fetchRaw = import_node_fetch.default;
|
|
|
743
733
|
async function fetch(url, init) {
|
|
744
734
|
let data = void 0;
|
|
745
735
|
try {
|
|
746
|
-
logger_default.debug(`Fetching ${url}`);
|
|
736
|
+
logger_default.debug(init, `Fetching ${url}`);
|
|
747
737
|
const resp = await fetchRaw(url, init);
|
|
748
738
|
const contentType = resp.headers.get("content-type") || "";
|
|
749
739
|
if (resp.ok) {
|
|
@@ -780,14 +770,101 @@ async function fetch(url, init) {
|
|
|
780
770
|
}
|
|
781
771
|
|
|
782
772
|
// src/lib/module.ts
|
|
783
|
-
var
|
|
773
|
+
var import_ramda4 = require("ramda");
|
|
784
774
|
|
|
785
775
|
// src/lib/controller.ts
|
|
786
776
|
var import_express = __toESM(require("express"));
|
|
787
777
|
var import_fs = __toESM(require("fs"));
|
|
788
778
|
var import_https = __toESM(require("https"));
|
|
789
779
|
|
|
790
|
-
// src/lib/
|
|
780
|
+
// src/lib/metrics.ts
|
|
781
|
+
var import_prom_client = __toESM(require("prom-client"));
|
|
782
|
+
var import_perf_hooks = require("perf_hooks");
|
|
783
|
+
var loggingPrefix = "MetricsCollector";
|
|
784
|
+
var MetricsCollector = class {
|
|
785
|
+
_registry;
|
|
786
|
+
_counters = /* @__PURE__ */ new Map();
|
|
787
|
+
_summaries = /* @__PURE__ */ new Map();
|
|
788
|
+
_prefix;
|
|
789
|
+
_metricNames = {
|
|
790
|
+
errors: "errors",
|
|
791
|
+
alerts: "alerts",
|
|
792
|
+
mutate: "Mutate",
|
|
793
|
+
validate: "Validate"
|
|
794
|
+
};
|
|
795
|
+
/**
|
|
796
|
+
* Creates a MetricsCollector instance with prefixed metrics.
|
|
797
|
+
* @param {string} [prefix='pepr'] - The prefix for the metric names.
|
|
798
|
+
*/
|
|
799
|
+
constructor(prefix = "pepr") {
|
|
800
|
+
this._registry = new import_prom_client.Registry();
|
|
801
|
+
this._prefix = prefix;
|
|
802
|
+
this.addCounter(this._metricNames.errors, "Mutation/Validate errors encountered");
|
|
803
|
+
this.addCounter(this._metricNames.alerts, "Mutation/Validate bad api token received");
|
|
804
|
+
this.addSummary(this._metricNames.mutate, "Mutation operation summary");
|
|
805
|
+
this.addSummary(this._metricNames.validate, "Validation operation summary");
|
|
806
|
+
}
|
|
807
|
+
getMetricName(name) {
|
|
808
|
+
return `${this._prefix}_${name}`;
|
|
809
|
+
}
|
|
810
|
+
addMetric(collection, MetricType, name, help) {
|
|
811
|
+
if (collection.has(this.getMetricName(name))) {
|
|
812
|
+
logger_default.debug(`Metric for ${name} already exists`, loggingPrefix);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
const metric = new MetricType({
|
|
816
|
+
name: this.getMetricName(name),
|
|
817
|
+
help,
|
|
818
|
+
registers: [this._registry]
|
|
819
|
+
});
|
|
820
|
+
collection.set(this.getMetricName(name), metric);
|
|
821
|
+
}
|
|
822
|
+
addCounter(name, help) {
|
|
823
|
+
this.addMetric(this._counters, import_prom_client.default.Counter, name, help);
|
|
824
|
+
}
|
|
825
|
+
addSummary(name, help) {
|
|
826
|
+
this.addMetric(this._summaries, import_prom_client.default.Summary, name, help);
|
|
827
|
+
}
|
|
828
|
+
incCounter(name) {
|
|
829
|
+
this._counters.get(this.getMetricName(name))?.inc();
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Increments the error counter.
|
|
833
|
+
*/
|
|
834
|
+
error() {
|
|
835
|
+
this.incCounter(this._metricNames.errors);
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Increments the alerts counter.
|
|
839
|
+
*/
|
|
840
|
+
alert() {
|
|
841
|
+
this.incCounter(this._metricNames.alerts);
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Returns the current timestamp from performance.now() method. Useful for start timing an operation.
|
|
845
|
+
* @returns {number} The timestamp.
|
|
846
|
+
*/
|
|
847
|
+
observeStart() {
|
|
848
|
+
return import_perf_hooks.performance.now();
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Observes the duration since the provided start time and updates the summary.
|
|
852
|
+
* @param {number} startTime - The start time.
|
|
853
|
+
* @param {string} name - The metrics summary to increment.
|
|
854
|
+
*/
|
|
855
|
+
observeEnd(startTime, name = this._metricNames.mutate) {
|
|
856
|
+
this._summaries.get(this.getMetricName(name))?.observe(import_perf_hooks.performance.now() - startTime);
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Fetches the current metrics from the registry.
|
|
860
|
+
* @returns {Promise<string>} The metrics.
|
|
861
|
+
*/
|
|
862
|
+
async getMetrics() {
|
|
863
|
+
return this._registry.metrics();
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
// src/lib/mutate-processor.ts
|
|
791
868
|
var import_fast_json_patch = __toESM(require("fast-json-patch"));
|
|
792
869
|
|
|
793
870
|
// src/lib/filter.ts
|
|
@@ -797,7 +874,6 @@ function shouldSkipRequest(binding, req) {
|
|
|
797
874
|
const operation = req.operation.toUpperCase();
|
|
798
875
|
const srcObject = operation === "DELETE" /* DELETE */ ? req.oldObject : req.object;
|
|
799
876
|
const { metadata } = srcObject || {};
|
|
800
|
-
console.log(metadata);
|
|
801
877
|
if (!binding.event.includes(operation) && !binding.event.includes("*" /* Any */)) {
|
|
802
878
|
return true;
|
|
803
879
|
}
|
|
@@ -842,19 +918,19 @@ function shouldSkipRequest(binding, req) {
|
|
|
842
918
|
return false;
|
|
843
919
|
}
|
|
844
920
|
|
|
845
|
-
// src/lib/request.ts
|
|
846
|
-
var
|
|
847
|
-
var
|
|
921
|
+
// src/lib/mutate-request.ts
|
|
922
|
+
var import_ramda2 = require("ramda");
|
|
923
|
+
var PeprMutateRequest = class {
|
|
848
924
|
/**
|
|
849
|
-
* Creates a new instance of the
|
|
925
|
+
* Creates a new instance of the action class.
|
|
850
926
|
* @param input - The request object containing the Kubernetes resource to modify.
|
|
851
927
|
*/
|
|
852
928
|
constructor(_input) {
|
|
853
929
|
this._input = _input;
|
|
854
930
|
if (_input.operation.toUpperCase() === "DELETE" /* DELETE */) {
|
|
855
|
-
this.Raw = (0,
|
|
931
|
+
this.Raw = (0, import_ramda2.clone)(_input.oldObject);
|
|
856
932
|
} else {
|
|
857
|
-
this.Raw = (0,
|
|
933
|
+
this.Raw = (0, import_ramda2.clone)(_input.object);
|
|
858
934
|
}
|
|
859
935
|
if (!this.Raw) {
|
|
860
936
|
throw new Error("unable to load the request object into PeprRequest.RawP");
|
|
@@ -891,13 +967,13 @@ var PeprRequest = class {
|
|
|
891
967
|
* @param obj - The object to merge with the current resource.
|
|
892
968
|
*/
|
|
893
969
|
Merge(obj) {
|
|
894
|
-
this.Raw = (0,
|
|
970
|
+
this.Raw = (0, import_ramda2.mergeDeepRight)(this.Raw, obj);
|
|
895
971
|
}
|
|
896
972
|
/**
|
|
897
973
|
* Updates a label on the Kubernetes resource.
|
|
898
974
|
* @param key - The key of the label to update.
|
|
899
975
|
* @param value - The value of the label.
|
|
900
|
-
* @returns The current
|
|
976
|
+
* @returns The current action instance for method chaining.
|
|
901
977
|
*/
|
|
902
978
|
SetLabel(key, value) {
|
|
903
979
|
const ref = this.Raw;
|
|
@@ -910,7 +986,7 @@ var PeprRequest = class {
|
|
|
910
986
|
* Updates an annotation on the Kubernetes resource.
|
|
911
987
|
* @param key - The key of the annotation to update.
|
|
912
988
|
* @param value - The value of the annotation.
|
|
913
|
-
* @returns The current
|
|
989
|
+
* @returns The current action instance for method chaining.
|
|
914
990
|
*/
|
|
915
991
|
SetAnnotation(key, value) {
|
|
916
992
|
const ref = this.Raw;
|
|
@@ -1003,30 +1079,33 @@ function base64Encode(data) {
|
|
|
1003
1079
|
return Buffer.from(data).toString("base64");
|
|
1004
1080
|
}
|
|
1005
1081
|
|
|
1006
|
-
// src/lib/processor.ts
|
|
1007
|
-
async function
|
|
1008
|
-
const wrapped = new
|
|
1082
|
+
// src/lib/mutate-processor.ts
|
|
1083
|
+
async function mutateProcessor(config, capabilities, req, reqMetadata) {
|
|
1084
|
+
const wrapped = new PeprMutateRequest(req);
|
|
1009
1085
|
const response = {
|
|
1010
1086
|
uid: req.uid,
|
|
1011
1087
|
warnings: [],
|
|
1012
1088
|
allowed: false
|
|
1013
1089
|
};
|
|
1014
|
-
let
|
|
1090
|
+
let matchedAction = false;
|
|
1015
1091
|
let skipDecode = [];
|
|
1016
1092
|
const isSecret = req.kind.version == "v1" && req.kind.kind == "Secret";
|
|
1017
1093
|
if (isSecret) {
|
|
1018
1094
|
skipDecode = convertFromBase64Map(wrapped.Raw);
|
|
1019
1095
|
}
|
|
1020
|
-
logger_default.info(`Processing request
|
|
1096
|
+
logger_default.info(reqMetadata, `Processing request`);
|
|
1021
1097
|
for (const { name, bindings } of capabilities) {
|
|
1022
|
-
const
|
|
1098
|
+
const actionMetadata = { ...reqMetadata, name };
|
|
1023
1099
|
for (const action of bindings) {
|
|
1100
|
+
if (!action.mutateCallback) {
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1024
1103
|
if (shouldSkipRequest(action, req)) {
|
|
1025
1104
|
continue;
|
|
1026
1105
|
}
|
|
1027
|
-
const label = action.
|
|
1028
|
-
logger_default.info(`Processing matched action ${label}
|
|
1029
|
-
|
|
1106
|
+
const label = action.mutateCallback.name;
|
|
1107
|
+
logger_default.info(actionMetadata, `Processing matched action ${label}`);
|
|
1108
|
+
matchedAction = true;
|
|
1030
1109
|
const updateStatus = (status) => {
|
|
1031
1110
|
if (req.operation == "DELETE") {
|
|
1032
1111
|
return;
|
|
@@ -1038,26 +1117,28 @@ async function processor(config, capabilities, req, parentPrefix) {
|
|
|
1038
1117
|
};
|
|
1039
1118
|
updateStatus("started");
|
|
1040
1119
|
try {
|
|
1041
|
-
await action.
|
|
1042
|
-
logger_default.info(`Action succeeded
|
|
1120
|
+
await action.mutateCallback(wrapped);
|
|
1121
|
+
logger_default.info(actionMetadata, `Action succeeded`);
|
|
1043
1122
|
updateStatus("succeeded");
|
|
1044
1123
|
} catch (e) {
|
|
1124
|
+
logger_default.warn(actionMetadata, `Action failed: ${e}`);
|
|
1125
|
+
updateStatus("warning");
|
|
1045
1126
|
response.warnings = response.warnings || [];
|
|
1046
1127
|
response.warnings.push(`Action failed: ${e}`);
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1128
|
+
switch (config.onError) {
|
|
1129
|
+
case "reject":
|
|
1130
|
+
logger_default.error(actionMetadata, `Action failed: ${e}`);
|
|
1131
|
+
response.result = "Pepr module configured to reject on error";
|
|
1132
|
+
return response;
|
|
1133
|
+
case "audit":
|
|
1134
|
+
break;
|
|
1054
1135
|
}
|
|
1055
1136
|
}
|
|
1056
1137
|
}
|
|
1057
1138
|
}
|
|
1058
1139
|
response.allowed = true;
|
|
1059
|
-
if (!
|
|
1060
|
-
logger_default.info(`No matching
|
|
1140
|
+
if (!matchedAction) {
|
|
1141
|
+
logger_default.info(reqMetadata, `No matching actions found`);
|
|
1061
1142
|
return response;
|
|
1062
1143
|
}
|
|
1063
1144
|
if (req.operation == "DELETE") {
|
|
@@ -1070,126 +1151,192 @@ async function processor(config, capabilities, req, parentPrefix) {
|
|
|
1070
1151
|
const patches = import_fast_json_patch.default.compare(req.object, transformed);
|
|
1071
1152
|
if (patches.length > 0) {
|
|
1072
1153
|
response.patchType = "JSONPatch";
|
|
1073
|
-
response.patch =
|
|
1154
|
+
response.patch = base64Encode(JSON.stringify(patches));
|
|
1074
1155
|
}
|
|
1075
1156
|
if (response.warnings && response.warnings.length < 1) {
|
|
1076
1157
|
delete response.warnings;
|
|
1077
1158
|
}
|
|
1078
|
-
logger_default.debug(patches,
|
|
1159
|
+
logger_default.debug({ ...reqMetadata, patches }, `Patches generated`);
|
|
1079
1160
|
return response;
|
|
1080
1161
|
}
|
|
1081
1162
|
|
|
1082
|
-
// src/lib/
|
|
1083
|
-
var
|
|
1084
|
-
var
|
|
1085
|
-
var MetricsCollector = class {
|
|
1086
|
-
_registry;
|
|
1087
|
-
_errors;
|
|
1088
|
-
_alerts;
|
|
1089
|
-
_summary;
|
|
1163
|
+
// src/lib/validate-request.ts
|
|
1164
|
+
var import_ramda3 = require("ramda");
|
|
1165
|
+
var PeprValidateRequest = class {
|
|
1090
1166
|
/**
|
|
1091
|
-
* Creates a
|
|
1092
|
-
* @param
|
|
1167
|
+
* Creates a new instance of the Action class.
|
|
1168
|
+
* @param input - The request object containing the Kubernetes resource to modify.
|
|
1093
1169
|
*/
|
|
1094
|
-
constructor(
|
|
1095
|
-
this.
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
}
|
|
1101
|
-
this.
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
registers: [this._registry]
|
|
1105
|
-
});
|
|
1106
|
-
this._summary = new import_prom_client.default.Summary({
|
|
1107
|
-
name: `${prefix}_summary`,
|
|
1108
|
-
help: "summary",
|
|
1109
|
-
registers: [this._registry]
|
|
1110
|
-
});
|
|
1170
|
+
constructor(_input) {
|
|
1171
|
+
this._input = _input;
|
|
1172
|
+
if (_input.operation.toUpperCase() === "DELETE" /* DELETE */) {
|
|
1173
|
+
this.Raw = (0, import_ramda3.clone)(_input.oldObject);
|
|
1174
|
+
} else {
|
|
1175
|
+
this.Raw = (0, import_ramda3.clone)(_input.object);
|
|
1176
|
+
}
|
|
1177
|
+
if (!this.Raw) {
|
|
1178
|
+
throw new Error("unable to load the request object into PeprRequest.RawP");
|
|
1179
|
+
}
|
|
1111
1180
|
}
|
|
1181
|
+
Raw;
|
|
1112
1182
|
/**
|
|
1113
|
-
*
|
|
1183
|
+
* Provides access to the old resource in the request if available.
|
|
1184
|
+
* @returns The old Kubernetes resource object or null if not available.
|
|
1114
1185
|
*/
|
|
1115
|
-
|
|
1116
|
-
this.
|
|
1186
|
+
get OldResource() {
|
|
1187
|
+
return this._input.oldObject;
|
|
1117
1188
|
}
|
|
1118
1189
|
/**
|
|
1119
|
-
*
|
|
1190
|
+
* Provides access to the request object.
|
|
1191
|
+
* @returns The request object containing the Kubernetes resource.
|
|
1120
1192
|
*/
|
|
1121
|
-
|
|
1122
|
-
this.
|
|
1193
|
+
get Request() {
|
|
1194
|
+
return this._input;
|
|
1123
1195
|
}
|
|
1124
1196
|
/**
|
|
1125
|
-
*
|
|
1126
|
-
*
|
|
1197
|
+
* Check if a label exists on the Kubernetes resource.
|
|
1198
|
+
*
|
|
1199
|
+
* @param key the label key to check
|
|
1200
|
+
* @returns
|
|
1127
1201
|
*/
|
|
1128
|
-
|
|
1129
|
-
return
|
|
1202
|
+
HasLabel(key) {
|
|
1203
|
+
return this.Raw.metadata?.labels?.[key] !== void 0;
|
|
1130
1204
|
}
|
|
1131
1205
|
/**
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
1206
|
+
* Check if an annotation exists on the Kubernetes resource.
|
|
1207
|
+
*
|
|
1208
|
+
* @param key the annotation key to check
|
|
1209
|
+
* @returns
|
|
1134
1210
|
*/
|
|
1135
|
-
|
|
1136
|
-
this.
|
|
1211
|
+
HasAnnotation(key) {
|
|
1212
|
+
return this.Raw.metadata?.annotations?.[key] !== void 0;
|
|
1137
1213
|
}
|
|
1138
1214
|
/**
|
|
1139
|
-
*
|
|
1140
|
-
*
|
|
1215
|
+
* Create a validation response that allows the request.
|
|
1216
|
+
*
|
|
1217
|
+
* @returns The validation response.
|
|
1141
1218
|
*/
|
|
1142
|
-
|
|
1143
|
-
return
|
|
1219
|
+
Approve() {
|
|
1220
|
+
return {
|
|
1221
|
+
allowed: true
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Create a validation response that denies the request.
|
|
1226
|
+
*
|
|
1227
|
+
* @param statusMessage Optional status message to return to the user.
|
|
1228
|
+
* @param statusCode Optional status code to return to the user.
|
|
1229
|
+
* @returns The validation response.
|
|
1230
|
+
*/
|
|
1231
|
+
Deny(statusMessage, statusCode) {
|
|
1232
|
+
return {
|
|
1233
|
+
allowed: false,
|
|
1234
|
+
statusCode,
|
|
1235
|
+
statusMessage
|
|
1236
|
+
};
|
|
1144
1237
|
}
|
|
1145
1238
|
};
|
|
1146
1239
|
|
|
1240
|
+
// src/lib/validate-processor.ts
|
|
1241
|
+
async function validateProcessor(capabilities, req, reqMetadata) {
|
|
1242
|
+
const wrapped = new PeprValidateRequest(req);
|
|
1243
|
+
const response = {
|
|
1244
|
+
uid: req.uid,
|
|
1245
|
+
allowed: true
|
|
1246
|
+
// Assume it's allowed until a validation check fails
|
|
1247
|
+
};
|
|
1248
|
+
logger_default.info(reqMetadata, `Processing validation request`);
|
|
1249
|
+
for (const { name, bindings } of capabilities) {
|
|
1250
|
+
const actionMetadata = { ...reqMetadata, name };
|
|
1251
|
+
for (const action of bindings) {
|
|
1252
|
+
if (!action.validateCallback) {
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
if (shouldSkipRequest(action, req)) {
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
const label = action.validateCallback.name;
|
|
1259
|
+
logger_default.info(actionMetadata, `Processing matched action ${label}`);
|
|
1260
|
+
try {
|
|
1261
|
+
const resp = await action.validateCallback(wrapped);
|
|
1262
|
+
response.allowed = resp.allowed;
|
|
1263
|
+
if (resp.statusCode || resp.statusMessage) {
|
|
1264
|
+
response.status = {
|
|
1265
|
+
code: resp.statusCode || 400,
|
|
1266
|
+
message: resp.statusMessage || `Validation failed for ${name}`
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
logger_default.info(actionMetadata, `Validation Action completed: ${resp.allowed ? "allowed" : "denied"}`);
|
|
1270
|
+
} catch (e) {
|
|
1271
|
+
logger_default.error(actionMetadata, `Action failed: ${e}`);
|
|
1272
|
+
response.allowed = false;
|
|
1273
|
+
response.status = {
|
|
1274
|
+
code: 500,
|
|
1275
|
+
message: `Action failed with error: ${e}`
|
|
1276
|
+
};
|
|
1277
|
+
return response;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
return response;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1147
1284
|
// src/lib/controller.ts
|
|
1148
1285
|
var Controller = class {
|
|
1149
|
-
constructor(
|
|
1150
|
-
this.
|
|
1151
|
-
this.
|
|
1152
|
-
this.
|
|
1153
|
-
this.
|
|
1154
|
-
this.
|
|
1155
|
-
this.
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
this.app.post("/mutate/:token", this.mutate);
|
|
1159
|
-
if (beforeHook) {
|
|
1160
|
-
console.info(`Using beforeHook: ${beforeHook}`);
|
|
1286
|
+
constructor(_config, _capabilities, _beforeHook, _afterHook) {
|
|
1287
|
+
this._config = _config;
|
|
1288
|
+
this._capabilities = _capabilities;
|
|
1289
|
+
this._beforeHook = _beforeHook;
|
|
1290
|
+
this._afterHook = _afterHook;
|
|
1291
|
+
this._app.use(this.logger);
|
|
1292
|
+
this._app.use(import_express.default.json({ limit: "2mb" }));
|
|
1293
|
+
if (_beforeHook) {
|
|
1294
|
+
logger_default.info(`Using beforeHook: ${_beforeHook}`);
|
|
1161
1295
|
}
|
|
1162
|
-
if (
|
|
1163
|
-
|
|
1296
|
+
if (_afterHook) {
|
|
1297
|
+
logger_default.info(`Using afterHook: ${_afterHook}`);
|
|
1164
1298
|
}
|
|
1299
|
+
this.bindEndpoints();
|
|
1165
1300
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1301
|
+
_app = (0, import_express.default)();
|
|
1302
|
+
_running = false;
|
|
1168
1303
|
metricsCollector = new MetricsCollector("pepr");
|
|
1169
1304
|
// The token used to authenticate requests
|
|
1170
|
-
|
|
1305
|
+
_token = "";
|
|
1306
|
+
bindEndpoints = () => {
|
|
1307
|
+
this._app.get("/healthz", this.healthz);
|
|
1308
|
+
this._app.get("/metrics", this.metrics);
|
|
1309
|
+
if (isWatchMode) {
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
this._app.use(["/mutate/:token", "/validate/:token"], this.validateToken);
|
|
1313
|
+
this._app.post("/mutate/:token", this.admissionReq("Mutate"));
|
|
1314
|
+
this._app.post("/validate/:token", this.admissionReq("Validate"));
|
|
1315
|
+
};
|
|
1171
1316
|
/** Start the webhook server */
|
|
1172
1317
|
startServer = (port) => {
|
|
1173
|
-
if (this.
|
|
1318
|
+
if (this._running) {
|
|
1174
1319
|
throw new Error("Cannot start Pepr module: Pepr module was not instantiated with deferStart=true");
|
|
1175
1320
|
}
|
|
1176
1321
|
const options = {
|
|
1177
1322
|
key: import_fs.default.readFileSync(process.env.SSL_KEY_PATH || "/etc/certs/tls.key"),
|
|
1178
1323
|
cert: import_fs.default.readFileSync(process.env.SSL_CERT_PATH || "/etc/certs/tls.crt")
|
|
1179
1324
|
};
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1325
|
+
if (!isWatchMode) {
|
|
1326
|
+
this._token = process.env.PEPR_API_TOKEN || import_fs.default.readFileSync("/app/api-token/value").toString().trim();
|
|
1327
|
+
logger_default.info(`Using API token: ${this._token}`);
|
|
1328
|
+
if (!this._token) {
|
|
1329
|
+
throw new Error("API token not found");
|
|
1330
|
+
}
|
|
1184
1331
|
}
|
|
1185
|
-
const server = import_https.default.createServer(options, this.
|
|
1332
|
+
const server = import_https.default.createServer(options, this._app).listen(port);
|
|
1186
1333
|
server.on("listening", () => {
|
|
1187
|
-
|
|
1188
|
-
this.
|
|
1334
|
+
logger_default.info(`Server listening on port ${port}`);
|
|
1335
|
+
this._running = true;
|
|
1189
1336
|
});
|
|
1190
1337
|
server.on("error", (e) => {
|
|
1191
1338
|
if (e.code === "EADDRINUSE") {
|
|
1192
|
-
|
|
1339
|
+
logger_default.warn(
|
|
1193
1340
|
`Address in use, retrying in 2 seconds. If this persists, ensure ${port} is not in use, e.g. "lsof -i :${port}"`
|
|
1194
1341
|
);
|
|
1195
1342
|
setTimeout(() => {
|
|
@@ -1199,80 +1346,128 @@ var Controller = class {
|
|
|
1199
1346
|
}
|
|
1200
1347
|
});
|
|
1201
1348
|
process.on("SIGTERM", () => {
|
|
1202
|
-
|
|
1349
|
+
logger_default.info("Received SIGTERM, closing server");
|
|
1203
1350
|
server.close(() => {
|
|
1204
|
-
|
|
1351
|
+
logger_default.info("Server closed");
|
|
1205
1352
|
process.exit(0);
|
|
1206
1353
|
});
|
|
1207
1354
|
});
|
|
1208
1355
|
};
|
|
1356
|
+
/**
|
|
1357
|
+
* Middleware for logging requests
|
|
1358
|
+
*
|
|
1359
|
+
* @param req the incoming request
|
|
1360
|
+
* @param res the outgoing response
|
|
1361
|
+
* @param next the next middleware function
|
|
1362
|
+
*/
|
|
1209
1363
|
logger = (req, res, next) => {
|
|
1210
1364
|
const startTime = Date.now();
|
|
1211
1365
|
res.on("finish", () => {
|
|
1212
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1213
1366
|
const elapsedTime = Date.now() - startTime;
|
|
1214
|
-
const message =
|
|
1215
|
-
|
|
1216
|
-
|
|
1367
|
+
const message = {
|
|
1368
|
+
uid: req.body?.request?.uid,
|
|
1369
|
+
method: req.method,
|
|
1370
|
+
url: req.originalUrl,
|
|
1371
|
+
status: res.statusCode,
|
|
1372
|
+
duration: `${elapsedTime} ms`
|
|
1373
|
+
};
|
|
1374
|
+
res.statusCode >= 300 ? logger_default.warn(message) : logger_default.info(message);
|
|
1217
1375
|
});
|
|
1218
1376
|
next();
|
|
1219
1377
|
};
|
|
1378
|
+
/**
|
|
1379
|
+
* Validate the token in the request path
|
|
1380
|
+
*
|
|
1381
|
+
* @param req The incoming request
|
|
1382
|
+
* @param res The outgoing response
|
|
1383
|
+
* @param next The next middleware function
|
|
1384
|
+
* @returns
|
|
1385
|
+
*/
|
|
1386
|
+
validateToken = (req, res, next) => {
|
|
1387
|
+
const { token } = req.params;
|
|
1388
|
+
if (token !== this._token) {
|
|
1389
|
+
const err = `Unauthorized: invalid token '${token.replace(/[^\w]/g, "_")}'`;
|
|
1390
|
+
logger_default.warn(err);
|
|
1391
|
+
res.status(401).send(err);
|
|
1392
|
+
this.metricsCollector.alert();
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
next();
|
|
1396
|
+
};
|
|
1397
|
+
/**
|
|
1398
|
+
* Health check endpoint handler
|
|
1399
|
+
*
|
|
1400
|
+
* @param req the incoming request
|
|
1401
|
+
* @param res the outgoing response
|
|
1402
|
+
*/
|
|
1220
1403
|
healthz = (req, res) => {
|
|
1221
1404
|
try {
|
|
1222
1405
|
res.send("OK");
|
|
1223
1406
|
} catch (err) {
|
|
1224
|
-
|
|
1407
|
+
logger_default.error(err);
|
|
1225
1408
|
res.status(500).send("Internal Server Error");
|
|
1226
1409
|
}
|
|
1227
1410
|
};
|
|
1411
|
+
/**
|
|
1412
|
+
* Metrics endpoint handler
|
|
1413
|
+
*
|
|
1414
|
+
* @param req the incoming request
|
|
1415
|
+
* @param res the outgoing response
|
|
1416
|
+
*/
|
|
1228
1417
|
metrics = async (req, res) => {
|
|
1229
1418
|
try {
|
|
1230
1419
|
res.send(await this.metricsCollector.getMetrics());
|
|
1231
1420
|
} catch (err) {
|
|
1232
|
-
|
|
1421
|
+
logger_default.error(err);
|
|
1233
1422
|
res.status(500).send("Internal Server Error");
|
|
1234
1423
|
}
|
|
1235
1424
|
};
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1425
|
+
/**
|
|
1426
|
+
* Admission request handler for both mutate and validate requests
|
|
1427
|
+
*
|
|
1428
|
+
* @param admissionKind the type of admission request
|
|
1429
|
+
* @returns the request handler
|
|
1430
|
+
*/
|
|
1431
|
+
admissionReq = (admissionKind) => {
|
|
1432
|
+
return async (req, res) => {
|
|
1433
|
+
const startTime = this.metricsCollector.observeStart();
|
|
1434
|
+
try {
|
|
1435
|
+
const request = req.body?.request || {};
|
|
1436
|
+
this._beforeHook && this._beforeHook(request || {});
|
|
1437
|
+
const name = request?.name ? `/${request.name}` : "";
|
|
1438
|
+
const namespace = request?.namespace || "";
|
|
1439
|
+
const gvk = request?.kind || { group: "", version: "", kind: "" };
|
|
1440
|
+
const reqMetadata = {
|
|
1441
|
+
uid: request.uid,
|
|
1442
|
+
namespace,
|
|
1443
|
+
name
|
|
1444
|
+
};
|
|
1445
|
+
logger_default.info({ ...reqMetadata, gvk, operation: request.operation, admissionKind }, "Incoming request");
|
|
1446
|
+
logger_default.debug({ ...reqMetadata, request }, "Incoming request body");
|
|
1447
|
+
let response;
|
|
1448
|
+
if (admissionKind === "Mutate") {
|
|
1449
|
+
response = await mutateProcessor(this._config, this._capabilities, request, reqMetadata);
|
|
1450
|
+
} else {
|
|
1451
|
+
response = await validateProcessor(this._capabilities, request, reqMetadata);
|
|
1452
|
+
}
|
|
1453
|
+
this._afterHook && this._afterHook(response);
|
|
1454
|
+
logger_default.debug({ ...reqMetadata, response }, "Outgoing response");
|
|
1455
|
+
res.send({
|
|
1456
|
+
apiVersion: "admission.k8s.io/v1",
|
|
1457
|
+
kind: "AdmissionReview",
|
|
1458
|
+
response
|
|
1459
|
+
});
|
|
1460
|
+
this.metricsCollector.observeEnd(startTime, admissionKind);
|
|
1461
|
+
} catch (err) {
|
|
1462
|
+
logger_default.error(err);
|
|
1463
|
+
res.status(500).send("Internal Server Error");
|
|
1464
|
+
this.metricsCollector.error();
|
|
1246
1465
|
}
|
|
1247
|
-
|
|
1248
|
-
this.beforeHook && this.beforeHook(request || {});
|
|
1249
|
-
const name = request?.name ? `/${request.name}` : "";
|
|
1250
|
-
const namespace = request?.namespace || "";
|
|
1251
|
-
const gvk = request?.kind || { group: "", version: "", kind: "" };
|
|
1252
|
-
const prefix = `${request.uid} ${namespace}${name}`;
|
|
1253
|
-
logger_default.info(`Mutate [${request.operation}] ${gvk.group}/${gvk.version}/${gvk.kind}`, prefix);
|
|
1254
|
-
const response = await processor(this.config, this.capabilities, request, prefix);
|
|
1255
|
-
this.afterHook && this.afterHook(response);
|
|
1256
|
-
logger_default.debug(response, prefix);
|
|
1257
|
-
res.send({
|
|
1258
|
-
apiVersion: "admission.k8s.io/v1",
|
|
1259
|
-
kind: "AdmissionReview",
|
|
1260
|
-
response
|
|
1261
|
-
});
|
|
1262
|
-
this.metricsCollector.observeEnd(startTime);
|
|
1263
|
-
} catch (err) {
|
|
1264
|
-
console.error(err);
|
|
1265
|
-
res.status(500).send("Internal Server Error");
|
|
1266
|
-
this.metricsCollector.error();
|
|
1267
|
-
}
|
|
1466
|
+
};
|
|
1268
1467
|
};
|
|
1269
1468
|
};
|
|
1270
1469
|
|
|
1271
1470
|
// src/lib/module.ts
|
|
1272
|
-
var alwaysIgnore = {
|
|
1273
|
-
namespaces: ["kube-system", "pepr-system"],
|
|
1274
|
-
labels: [{ "pepr.dev": "ignore" }]
|
|
1275
|
-
};
|
|
1276
1471
|
var PeprModule = class {
|
|
1277
1472
|
_controller;
|
|
1278
1473
|
/**
|
|
@@ -1283,10 +1478,14 @@ var PeprModule = class {
|
|
|
1283
1478
|
* @param _deferStart (optional) If set to `true`, the Pepr runtime will not be started automatically. This can be used to start the Pepr runtime manually with `start()`.
|
|
1284
1479
|
*/
|
|
1285
1480
|
constructor({ description, pepr }, capabilities = [], opts = {}) {
|
|
1286
|
-
const config = (0,
|
|
1481
|
+
const config = (0, import_ramda4.clone)(pepr);
|
|
1287
1482
|
config.description = description;
|
|
1288
|
-
|
|
1289
|
-
|
|
1483
|
+
const validOnErrors = ["ignore", "warn", "fail"];
|
|
1484
|
+
if (!validOnErrors.includes(config.onError || "")) {
|
|
1485
|
+
throw new Error(`Invalid onErrors value: ${config.onError}`);
|
|
1486
|
+
}
|
|
1487
|
+
if (process.env.PEPR_MODE === "build" && process.send) {
|
|
1488
|
+
process.send(capabilities);
|
|
1290
1489
|
return;
|
|
1291
1490
|
}
|
|
1292
1491
|
this._controller = new Controller(config, capabilities, opts.beforeHook, opts.afterHook);
|
|
@@ -1310,8 +1509,9 @@ var PeprModule = class {
|
|
|
1310
1509
|
Capability,
|
|
1311
1510
|
Log,
|
|
1312
1511
|
PeprModule,
|
|
1313
|
-
|
|
1512
|
+
PeprMutateRequest,
|
|
1314
1513
|
PeprUtils,
|
|
1514
|
+
PeprValidateRequest,
|
|
1315
1515
|
R,
|
|
1316
1516
|
RegisterKind,
|
|
1317
1517
|
a,
|