pepr 0.12.1 → 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/utils.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 +510 -306
- 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/utils.ts +9 -7
- 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;
|
|
@@ -982,11 +1058,15 @@ function convertFromBase64Map(obj) {
|
|
|
982
1058
|
const skip = [];
|
|
983
1059
|
obj.data = obj.data ?? {};
|
|
984
1060
|
for (const key in obj.data) {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
obj.data[key] = decoded;
|
|
1061
|
+
if (obj.data[key] == void 0) {
|
|
1062
|
+
obj.data[key] = "";
|
|
988
1063
|
} else {
|
|
989
|
-
|
|
1064
|
+
const decoded = base64Decode(obj.data[key]);
|
|
1065
|
+
if (isAscii.test(decoded)) {
|
|
1066
|
+
obj.data[key] = decoded;
|
|
1067
|
+
} else {
|
|
1068
|
+
skip.push(key);
|
|
1069
|
+
}
|
|
990
1070
|
}
|
|
991
1071
|
}
|
|
992
1072
|
logger_default.debug(`Non-ascii data detected in keys: ${skip}, skipping automatic base64 decoding`);
|
|
@@ -999,30 +1079,33 @@ function base64Encode(data) {
|
|
|
999
1079
|
return Buffer.from(data).toString("base64");
|
|
1000
1080
|
}
|
|
1001
1081
|
|
|
1002
|
-
// src/lib/processor.ts
|
|
1003
|
-
async function
|
|
1004
|
-
const wrapped = new
|
|
1082
|
+
// src/lib/mutate-processor.ts
|
|
1083
|
+
async function mutateProcessor(config, capabilities, req, reqMetadata) {
|
|
1084
|
+
const wrapped = new PeprMutateRequest(req);
|
|
1005
1085
|
const response = {
|
|
1006
1086
|
uid: req.uid,
|
|
1007
1087
|
warnings: [],
|
|
1008
1088
|
allowed: false
|
|
1009
1089
|
};
|
|
1010
|
-
let
|
|
1090
|
+
let matchedAction = false;
|
|
1011
1091
|
let skipDecode = [];
|
|
1012
1092
|
const isSecret = req.kind.version == "v1" && req.kind.kind == "Secret";
|
|
1013
1093
|
if (isSecret) {
|
|
1014
1094
|
skipDecode = convertFromBase64Map(wrapped.Raw);
|
|
1015
1095
|
}
|
|
1016
|
-
logger_default.info(`Processing request
|
|
1096
|
+
logger_default.info(reqMetadata, `Processing request`);
|
|
1017
1097
|
for (const { name, bindings } of capabilities) {
|
|
1018
|
-
const
|
|
1098
|
+
const actionMetadata = { ...reqMetadata, name };
|
|
1019
1099
|
for (const action of bindings) {
|
|
1100
|
+
if (!action.mutateCallback) {
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1020
1103
|
if (shouldSkipRequest(action, req)) {
|
|
1021
1104
|
continue;
|
|
1022
1105
|
}
|
|
1023
|
-
const label = action.
|
|
1024
|
-
logger_default.info(`Processing matched action ${label}
|
|
1025
|
-
|
|
1106
|
+
const label = action.mutateCallback.name;
|
|
1107
|
+
logger_default.info(actionMetadata, `Processing matched action ${label}`);
|
|
1108
|
+
matchedAction = true;
|
|
1026
1109
|
const updateStatus = (status) => {
|
|
1027
1110
|
if (req.operation == "DELETE") {
|
|
1028
1111
|
return;
|
|
@@ -1034,26 +1117,28 @@ async function processor(config, capabilities, req, parentPrefix) {
|
|
|
1034
1117
|
};
|
|
1035
1118
|
updateStatus("started");
|
|
1036
1119
|
try {
|
|
1037
|
-
await action.
|
|
1038
|
-
logger_default.info(`Action succeeded
|
|
1120
|
+
await action.mutateCallback(wrapped);
|
|
1121
|
+
logger_default.info(actionMetadata, `Action succeeded`);
|
|
1039
1122
|
updateStatus("succeeded");
|
|
1040
1123
|
} catch (e) {
|
|
1124
|
+
logger_default.warn(actionMetadata, `Action failed: ${e}`);
|
|
1125
|
+
updateStatus("warning");
|
|
1041
1126
|
response.warnings = response.warnings || [];
|
|
1042
1127
|
response.warnings.push(`Action failed: ${e}`);
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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;
|
|
1050
1135
|
}
|
|
1051
1136
|
}
|
|
1052
1137
|
}
|
|
1053
1138
|
}
|
|
1054
1139
|
response.allowed = true;
|
|
1055
|
-
if (!
|
|
1056
|
-
logger_default.info(`No matching
|
|
1140
|
+
if (!matchedAction) {
|
|
1141
|
+
logger_default.info(reqMetadata, `No matching actions found`);
|
|
1057
1142
|
return response;
|
|
1058
1143
|
}
|
|
1059
1144
|
if (req.operation == "DELETE") {
|
|
@@ -1066,126 +1151,192 @@ async function processor(config, capabilities, req, parentPrefix) {
|
|
|
1066
1151
|
const patches = import_fast_json_patch.default.compare(req.object, transformed);
|
|
1067
1152
|
if (patches.length > 0) {
|
|
1068
1153
|
response.patchType = "JSONPatch";
|
|
1069
|
-
response.patch =
|
|
1154
|
+
response.patch = base64Encode(JSON.stringify(patches));
|
|
1070
1155
|
}
|
|
1071
1156
|
if (response.warnings && response.warnings.length < 1) {
|
|
1072
1157
|
delete response.warnings;
|
|
1073
1158
|
}
|
|
1074
|
-
logger_default.debug(patches,
|
|
1159
|
+
logger_default.debug({ ...reqMetadata, patches }, `Patches generated`);
|
|
1075
1160
|
return response;
|
|
1076
1161
|
}
|
|
1077
1162
|
|
|
1078
|
-
// src/lib/
|
|
1079
|
-
var
|
|
1080
|
-
var
|
|
1081
|
-
var MetricsCollector = class {
|
|
1082
|
-
_registry;
|
|
1083
|
-
_errors;
|
|
1084
|
-
_alerts;
|
|
1085
|
-
_summary;
|
|
1163
|
+
// src/lib/validate-request.ts
|
|
1164
|
+
var import_ramda3 = require("ramda");
|
|
1165
|
+
var PeprValidateRequest = class {
|
|
1086
1166
|
/**
|
|
1087
|
-
* Creates a
|
|
1088
|
-
* @param
|
|
1167
|
+
* Creates a new instance of the Action class.
|
|
1168
|
+
* @param input - The request object containing the Kubernetes resource to modify.
|
|
1089
1169
|
*/
|
|
1090
|
-
constructor(
|
|
1091
|
-
this.
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
}
|
|
1097
|
-
this.
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
registers: [this._registry]
|
|
1101
|
-
});
|
|
1102
|
-
this._summary = new import_prom_client.default.Summary({
|
|
1103
|
-
name: `${prefix}_summary`,
|
|
1104
|
-
help: "summary",
|
|
1105
|
-
registers: [this._registry]
|
|
1106
|
-
});
|
|
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
|
+
}
|
|
1107
1180
|
}
|
|
1181
|
+
Raw;
|
|
1108
1182
|
/**
|
|
1109
|
-
*
|
|
1183
|
+
* Provides access to the old resource in the request if available.
|
|
1184
|
+
* @returns The old Kubernetes resource object or null if not available.
|
|
1110
1185
|
*/
|
|
1111
|
-
|
|
1112
|
-
this.
|
|
1186
|
+
get OldResource() {
|
|
1187
|
+
return this._input.oldObject;
|
|
1113
1188
|
}
|
|
1114
1189
|
/**
|
|
1115
|
-
*
|
|
1190
|
+
* Provides access to the request object.
|
|
1191
|
+
* @returns The request object containing the Kubernetes resource.
|
|
1116
1192
|
*/
|
|
1117
|
-
|
|
1118
|
-
this.
|
|
1193
|
+
get Request() {
|
|
1194
|
+
return this._input;
|
|
1119
1195
|
}
|
|
1120
1196
|
/**
|
|
1121
|
-
*
|
|
1122
|
-
*
|
|
1197
|
+
* Check if a label exists on the Kubernetes resource.
|
|
1198
|
+
*
|
|
1199
|
+
* @param key the label key to check
|
|
1200
|
+
* @returns
|
|
1123
1201
|
*/
|
|
1124
|
-
|
|
1125
|
-
return
|
|
1202
|
+
HasLabel(key) {
|
|
1203
|
+
return this.Raw.metadata?.labels?.[key] !== void 0;
|
|
1126
1204
|
}
|
|
1127
1205
|
/**
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1206
|
+
* Check if an annotation exists on the Kubernetes resource.
|
|
1207
|
+
*
|
|
1208
|
+
* @param key the annotation key to check
|
|
1209
|
+
* @returns
|
|
1130
1210
|
*/
|
|
1131
|
-
|
|
1132
|
-
this.
|
|
1211
|
+
HasAnnotation(key) {
|
|
1212
|
+
return this.Raw.metadata?.annotations?.[key] !== void 0;
|
|
1133
1213
|
}
|
|
1134
1214
|
/**
|
|
1135
|
-
*
|
|
1136
|
-
*
|
|
1215
|
+
* Create a validation response that allows the request.
|
|
1216
|
+
*
|
|
1217
|
+
* @returns The validation response.
|
|
1137
1218
|
*/
|
|
1138
|
-
|
|
1139
|
-
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
|
+
};
|
|
1140
1237
|
}
|
|
1141
1238
|
};
|
|
1142
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
|
+
|
|
1143
1284
|
// src/lib/controller.ts
|
|
1144
1285
|
var Controller = class {
|
|
1145
|
-
constructor(
|
|
1146
|
-
this.
|
|
1147
|
-
this.
|
|
1148
|
-
this.
|
|
1149
|
-
this.
|
|
1150
|
-
this.
|
|
1151
|
-
this.
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
this.app.post("/mutate/:token", this.mutate);
|
|
1155
|
-
if (beforeHook) {
|
|
1156
|
-
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}`);
|
|
1157
1295
|
}
|
|
1158
|
-
if (
|
|
1159
|
-
|
|
1296
|
+
if (_afterHook) {
|
|
1297
|
+
logger_default.info(`Using afterHook: ${_afterHook}`);
|
|
1160
1298
|
}
|
|
1299
|
+
this.bindEndpoints();
|
|
1161
1300
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1301
|
+
_app = (0, import_express.default)();
|
|
1302
|
+
_running = false;
|
|
1164
1303
|
metricsCollector = new MetricsCollector("pepr");
|
|
1165
1304
|
// The token used to authenticate requests
|
|
1166
|
-
|
|
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
|
+
};
|
|
1167
1316
|
/** Start the webhook server */
|
|
1168
1317
|
startServer = (port) => {
|
|
1169
|
-
if (this.
|
|
1318
|
+
if (this._running) {
|
|
1170
1319
|
throw new Error("Cannot start Pepr module: Pepr module was not instantiated with deferStart=true");
|
|
1171
1320
|
}
|
|
1172
1321
|
const options = {
|
|
1173
1322
|
key: import_fs.default.readFileSync(process.env.SSL_KEY_PATH || "/etc/certs/tls.key"),
|
|
1174
1323
|
cert: import_fs.default.readFileSync(process.env.SSL_CERT_PATH || "/etc/certs/tls.crt")
|
|
1175
1324
|
};
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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
|
+
}
|
|
1180
1331
|
}
|
|
1181
|
-
const server = import_https.default.createServer(options, this.
|
|
1332
|
+
const server = import_https.default.createServer(options, this._app).listen(port);
|
|
1182
1333
|
server.on("listening", () => {
|
|
1183
|
-
|
|
1184
|
-
this.
|
|
1334
|
+
logger_default.info(`Server listening on port ${port}`);
|
|
1335
|
+
this._running = true;
|
|
1185
1336
|
});
|
|
1186
1337
|
server.on("error", (e) => {
|
|
1187
1338
|
if (e.code === "EADDRINUSE") {
|
|
1188
|
-
|
|
1339
|
+
logger_default.warn(
|
|
1189
1340
|
`Address in use, retrying in 2 seconds. If this persists, ensure ${port} is not in use, e.g. "lsof -i :${port}"`
|
|
1190
1341
|
);
|
|
1191
1342
|
setTimeout(() => {
|
|
@@ -1195,80 +1346,128 @@ var Controller = class {
|
|
|
1195
1346
|
}
|
|
1196
1347
|
});
|
|
1197
1348
|
process.on("SIGTERM", () => {
|
|
1198
|
-
|
|
1349
|
+
logger_default.info("Received SIGTERM, closing server");
|
|
1199
1350
|
server.close(() => {
|
|
1200
|
-
|
|
1351
|
+
logger_default.info("Server closed");
|
|
1201
1352
|
process.exit(0);
|
|
1202
1353
|
});
|
|
1203
1354
|
});
|
|
1204
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
|
+
*/
|
|
1205
1363
|
logger = (req, res, next) => {
|
|
1206
1364
|
const startTime = Date.now();
|
|
1207
1365
|
res.on("finish", () => {
|
|
1208
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1209
1366
|
const elapsedTime = Date.now() - startTime;
|
|
1210
|
-
const message =
|
|
1211
|
-
|
|
1212
|
-
|
|
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);
|
|
1213
1375
|
});
|
|
1214
1376
|
next();
|
|
1215
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
|
+
*/
|
|
1216
1403
|
healthz = (req, res) => {
|
|
1217
1404
|
try {
|
|
1218
1405
|
res.send("OK");
|
|
1219
1406
|
} catch (err) {
|
|
1220
|
-
|
|
1407
|
+
logger_default.error(err);
|
|
1221
1408
|
res.status(500).send("Internal Server Error");
|
|
1222
1409
|
}
|
|
1223
1410
|
};
|
|
1411
|
+
/**
|
|
1412
|
+
* Metrics endpoint handler
|
|
1413
|
+
*
|
|
1414
|
+
* @param req the incoming request
|
|
1415
|
+
* @param res the outgoing response
|
|
1416
|
+
*/
|
|
1224
1417
|
metrics = async (req, res) => {
|
|
1225
1418
|
try {
|
|
1226
1419
|
res.send(await this.metricsCollector.getMetrics());
|
|
1227
1420
|
} catch (err) {
|
|
1228
|
-
|
|
1421
|
+
logger_default.error(err);
|
|
1229
1422
|
res.status(500).send("Internal Server Error");
|
|
1230
1423
|
}
|
|
1231
1424
|
};
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
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();
|
|
1242
1465
|
}
|
|
1243
|
-
|
|
1244
|
-
this.beforeHook && this.beforeHook(request || {});
|
|
1245
|
-
const name = request?.name ? `/${request.name}` : "";
|
|
1246
|
-
const namespace = request?.namespace || "";
|
|
1247
|
-
const gvk = request?.kind || { group: "", version: "", kind: "" };
|
|
1248
|
-
const prefix = `${request.uid} ${namespace}${name}`;
|
|
1249
|
-
logger_default.info(`Mutate [${request.operation}] ${gvk.group}/${gvk.version}/${gvk.kind}`, prefix);
|
|
1250
|
-
const response = await processor(this.config, this.capabilities, request, prefix);
|
|
1251
|
-
this.afterHook && this.afterHook(response);
|
|
1252
|
-
logger_default.debug(response, prefix);
|
|
1253
|
-
res.send({
|
|
1254
|
-
apiVersion: "admission.k8s.io/v1",
|
|
1255
|
-
kind: "AdmissionReview",
|
|
1256
|
-
response
|
|
1257
|
-
});
|
|
1258
|
-
this.metricsCollector.observeEnd(startTime);
|
|
1259
|
-
} catch (err) {
|
|
1260
|
-
console.error(err);
|
|
1261
|
-
res.status(500).send("Internal Server Error");
|
|
1262
|
-
this.metricsCollector.error();
|
|
1263
|
-
}
|
|
1466
|
+
};
|
|
1264
1467
|
};
|
|
1265
1468
|
};
|
|
1266
1469
|
|
|
1267
1470
|
// src/lib/module.ts
|
|
1268
|
-
var alwaysIgnore = {
|
|
1269
|
-
namespaces: ["kube-system", "pepr-system"],
|
|
1270
|
-
labels: [{ "pepr.dev": "ignore" }]
|
|
1271
|
-
};
|
|
1272
1471
|
var PeprModule = class {
|
|
1273
1472
|
_controller;
|
|
1274
1473
|
/**
|
|
@@ -1279,10 +1478,14 @@ var PeprModule = class {
|
|
|
1279
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()`.
|
|
1280
1479
|
*/
|
|
1281
1480
|
constructor({ description, pepr }, capabilities = [], opts = {}) {
|
|
1282
|
-
const config = (0,
|
|
1481
|
+
const config = (0, import_ramda4.clone)(pepr);
|
|
1283
1482
|
config.description = description;
|
|
1284
|
-
|
|
1285
|
-
|
|
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);
|
|
1286
1489
|
return;
|
|
1287
1490
|
}
|
|
1288
1491
|
this._controller = new Controller(config, capabilities, opts.beforeHook, opts.afterHook);
|
|
@@ -1306,8 +1509,9 @@ var PeprModule = class {
|
|
|
1306
1509
|
Capability,
|
|
1307
1510
|
Log,
|
|
1308
1511
|
PeprModule,
|
|
1309
|
-
|
|
1512
|
+
PeprMutateRequest,
|
|
1310
1513
|
PeprUtils,
|
|
1514
|
+
PeprValidateRequest,
|
|
1311
1515
|
R,
|
|
1312
1516
|
RegisterKind,
|
|
1313
1517
|
a,
|