pepr 0.0.0-development
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/.prettierignore +1 -0
- package/CODE_OF_CONDUCT.md +133 -0
- package/LICENSE +201 -0
- package/README.md +151 -0
- package/SECURITY.md +18 -0
- package/SUPPORT.md +16 -0
- package/codecov.yaml +19 -0
- package/commitlint.config.js +1 -0
- package/package.json +70 -0
- package/src/cli.ts +48 -0
- package/src/lib/assets/deploy.ts +122 -0
- package/src/lib/assets/destroy.ts +33 -0
- package/src/lib/assets/helm.ts +219 -0
- package/src/lib/assets/index.ts +175 -0
- package/src/lib/assets/loader.ts +41 -0
- package/src/lib/assets/networking.ts +89 -0
- package/src/lib/assets/pods.ts +353 -0
- package/src/lib/assets/rbac.ts +111 -0
- package/src/lib/assets/store.ts +49 -0
- package/src/lib/assets/webhooks.ts +147 -0
- package/src/lib/assets/yaml.ts +234 -0
- package/src/lib/capability.ts +314 -0
- package/src/lib/controller/index.ts +326 -0
- package/src/lib/controller/store.ts +219 -0
- package/src/lib/errors.ts +20 -0
- package/src/lib/filter.ts +110 -0
- package/src/lib/helpers.ts +342 -0
- package/src/lib/included-files.ts +19 -0
- package/src/lib/k8s.ts +169 -0
- package/src/lib/logger.ts +27 -0
- package/src/lib/metrics.ts +120 -0
- package/src/lib/module.ts +136 -0
- package/src/lib/mutate-processor.ts +160 -0
- package/src/lib/mutate-request.ts +153 -0
- package/src/lib/queue.ts +89 -0
- package/src/lib/schedule.ts +175 -0
- package/src/lib/storage.ts +192 -0
- package/src/lib/tls.ts +90 -0
- package/src/lib/types.ts +215 -0
- package/src/lib/utils.ts +57 -0
- package/src/lib/validate-processor.ts +80 -0
- package/src/lib/validate-request.ts +102 -0
- package/src/lib/watch-processor.ts +124 -0
- package/src/lib.ts +27 -0
- package/src/runtime/controller.ts +75 -0
- package/src/sdk/sdk.ts +116 -0
- package/src/templates/.eslintrc.template.json +18 -0
- package/src/templates/.prettierrc.json +13 -0
- package/src/templates/README.md +21 -0
- package/src/templates/capabilities/hello-pepr.samples.json +160 -0
- package/src/templates/capabilities/hello-pepr.ts +426 -0
- package/src/templates/gitignore +4 -0
- package/src/templates/package.json +20 -0
- package/src/templates/pepr.code-snippets.json +21 -0
- package/src/templates/pepr.ts +17 -0
- package/src/templates/settings.json +10 -0
- package/src/templates/tsconfig.json +9 -0
- package/src/templates/tsconfig.module.json +19 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
import { promises as fs } from "fs";
|
|
6
|
+
import { K8s, kind } from "kubernetes-fluent-client";
|
|
7
|
+
|
|
8
|
+
import { Assets } from ".";
|
|
9
|
+
import Log from "../logger";
|
|
10
|
+
import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
|
|
11
|
+
import { deployment, moduleSecret, namespace, watcher } from "./pods";
|
|
12
|
+
import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
|
|
13
|
+
import { peprStoreCRD } from "./store";
|
|
14
|
+
import { webhookConfig } from "./webhooks";
|
|
15
|
+
import { CapabilityExport } from "../types";
|
|
16
|
+
|
|
17
|
+
export async function deploy(assets: Assets, force: boolean, webhookTimeout?: number) {
|
|
18
|
+
Log.info("Establishing connection to Kubernetes");
|
|
19
|
+
|
|
20
|
+
const { name, host, path } = assets;
|
|
21
|
+
|
|
22
|
+
Log.info("Applying pepr-system namespace");
|
|
23
|
+
await K8s(kind.Namespace).Apply(namespace(assets.config.customLabels?.namespace));
|
|
24
|
+
|
|
25
|
+
// Create the mutating webhook configuration if it is needed
|
|
26
|
+
const mutateWebhook = await webhookConfig(assets, "mutate", webhookTimeout);
|
|
27
|
+
if (mutateWebhook) {
|
|
28
|
+
Log.info("Applying mutating webhook");
|
|
29
|
+
await K8s(kind.MutatingWebhookConfiguration).Apply(mutateWebhook, { force });
|
|
30
|
+
} else {
|
|
31
|
+
Log.info("Mutating webhook not needed, removing if it exists");
|
|
32
|
+
await K8s(kind.MutatingWebhookConfiguration).Delete(name);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Create the validating webhook configuration if it is needed
|
|
36
|
+
const validateWebhook = await webhookConfig(assets, "validate", webhookTimeout);
|
|
37
|
+
if (validateWebhook) {
|
|
38
|
+
Log.info("Applying validating webhook");
|
|
39
|
+
await K8s(kind.ValidatingWebhookConfiguration).Apply(validateWebhook, { force });
|
|
40
|
+
} else {
|
|
41
|
+
Log.info("Validating webhook not needed, removing if it exists");
|
|
42
|
+
await K8s(kind.ValidatingWebhookConfiguration).Delete(name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Log.info("Applying the Pepr Store CRD if it doesn't exist");
|
|
46
|
+
await K8s(kind.CustomResourceDefinition).Apply(peprStoreCRD, { force });
|
|
47
|
+
|
|
48
|
+
// If a host is specified, we don't need to deploy the rest of the resources
|
|
49
|
+
if (host) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const code = await fs.readFile(path);
|
|
54
|
+
const hash = crypto.createHash("sha256").update(code).digest("hex");
|
|
55
|
+
|
|
56
|
+
if (code.length < 1) {
|
|
57
|
+
throw new Error("No code provided");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await setupRBAC(name, assets.capabilities, force);
|
|
61
|
+
await setupController(assets, code, hash, force);
|
|
62
|
+
await setupWatcher(assets, hash, force);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function setupRBAC(name: string, capabilities: CapabilityExport[], force: boolean) {
|
|
66
|
+
Log.info("Applying cluster role binding");
|
|
67
|
+
const crb = clusterRoleBinding(name);
|
|
68
|
+
await K8s(kind.ClusterRoleBinding).Apply(crb, { force });
|
|
69
|
+
|
|
70
|
+
Log.info("Applying cluster role");
|
|
71
|
+
const cr = clusterRole(name, capabilities);
|
|
72
|
+
await K8s(kind.ClusterRole).Apply(cr, { force });
|
|
73
|
+
|
|
74
|
+
Log.info("Applying service account");
|
|
75
|
+
const sa = serviceAccount(name);
|
|
76
|
+
await K8s(kind.ServiceAccount).Apply(sa, { force });
|
|
77
|
+
|
|
78
|
+
Log.info("Applying store role");
|
|
79
|
+
const role = storeRole(name);
|
|
80
|
+
await K8s(kind.Role).Apply(role, { force });
|
|
81
|
+
|
|
82
|
+
Log.info("Applying store role binding");
|
|
83
|
+
const roleBinding = storeRoleBinding(name);
|
|
84
|
+
await K8s(kind.RoleBinding).Apply(roleBinding, { force });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function setupController(assets: Assets, code: Buffer, hash: string, force: boolean) {
|
|
88
|
+
const { name } = assets;
|
|
89
|
+
|
|
90
|
+
Log.info("Applying module secret");
|
|
91
|
+
const mod = moduleSecret(name, code, hash);
|
|
92
|
+
await K8s(kind.Secret).Apply(mod, { force });
|
|
93
|
+
|
|
94
|
+
Log.info("Applying controller service");
|
|
95
|
+
const svc = service(name);
|
|
96
|
+
await K8s(kind.Service).Apply(svc, { force });
|
|
97
|
+
|
|
98
|
+
Log.info("Applying TLS secret");
|
|
99
|
+
const tls = tlsSecret(name, assets.tls);
|
|
100
|
+
await K8s(kind.Secret).Apply(tls, { force });
|
|
101
|
+
|
|
102
|
+
Log.info("Applying API token secret");
|
|
103
|
+
const apiToken = apiTokenSecret(name, assets.apiToken);
|
|
104
|
+
await K8s(kind.Secret).Apply(apiToken, { force });
|
|
105
|
+
|
|
106
|
+
Log.info("Applying deployment");
|
|
107
|
+
const dep = deployment(assets, hash, assets.buildTimestamp);
|
|
108
|
+
await K8s(kind.Deployment).Apply(dep, { force });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function setupWatcher(assets: Assets, hash: string, force: boolean) {
|
|
112
|
+
// If the module has a watcher, deploy it
|
|
113
|
+
const watchDeployment = watcher(assets, hash, assets.buildTimestamp);
|
|
114
|
+
if (watchDeployment) {
|
|
115
|
+
Log.info("Applying watcher deployment");
|
|
116
|
+
await K8s(kind.Deployment).Apply(watchDeployment, { force });
|
|
117
|
+
|
|
118
|
+
Log.info("Applying watcher service");
|
|
119
|
+
const watchSvc = watcherService(assets.name);
|
|
120
|
+
await K8s(kind.Service).Apply(watchSvc, { force });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { K8s, kind } from "kubernetes-fluent-client";
|
|
5
|
+
|
|
6
|
+
import Log from "../logger";
|
|
7
|
+
import { peprStoreCRD } from "./store";
|
|
8
|
+
|
|
9
|
+
export async function destroyModule(name: string) {
|
|
10
|
+
const namespace = "pepr-system";
|
|
11
|
+
|
|
12
|
+
Log.info("Destroying Pepr module");
|
|
13
|
+
|
|
14
|
+
await Promise.all([
|
|
15
|
+
K8s(kind.MutatingWebhookConfiguration).Delete(name),
|
|
16
|
+
K8s(kind.ValidatingWebhookConfiguration).Delete(name),
|
|
17
|
+
|
|
18
|
+
K8s(kind.CustomResourceDefinition).Delete(peprStoreCRD),
|
|
19
|
+
K8s(kind.ClusterRoleBinding).Delete(name),
|
|
20
|
+
K8s(kind.ClusterRole).Delete(name),
|
|
21
|
+
K8s(kind.ServiceAccount, { namespace }).Delete(name),
|
|
22
|
+
K8s(kind.Role, { namespace }).Delete(name),
|
|
23
|
+
K8s(kind.RoleBinding, { namespace }).Delete(`${name}-store`),
|
|
24
|
+
|
|
25
|
+
K8s(kind.Secret, { namespace }).Delete(`${name}-module`),
|
|
26
|
+
K8s(kind.Service, { namespace }).Delete(name),
|
|
27
|
+
K8s(kind.Secret, { namespace }).Delete(`${name}-tls`),
|
|
28
|
+
K8s(kind.Secret, { namespace }).Delete(`${name}-api-token`),
|
|
29
|
+
K8s(kind.Deployment, { namespace }).Delete(name),
|
|
30
|
+
K8s(kind.Deployment, { namespace }).Delete(`${name}-watcher`),
|
|
31
|
+
K8s(kind.Service, { namespace }).Delete(`${name}-watcher`),
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
export function nsTemplate() {
|
|
5
|
+
return `
|
|
6
|
+
apiVersion: v1
|
|
7
|
+
kind: Namespace
|
|
8
|
+
metadata:
|
|
9
|
+
name: pepr-system
|
|
10
|
+
{{- if .Values.namespace.annotations }}
|
|
11
|
+
annotations:
|
|
12
|
+
{{- toYaml .Values.namespace.annotations | nindent 6 }}
|
|
13
|
+
{{- end }}
|
|
14
|
+
{{- if .Values.namespace.labels }}
|
|
15
|
+
labels:
|
|
16
|
+
{{- toYaml .Values.namespace.labels | nindent 6 }}
|
|
17
|
+
{{- end }}
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function chartYaml(name: string, description?: string) {
|
|
22
|
+
return `
|
|
23
|
+
apiVersion: v2
|
|
24
|
+
name: ${name}
|
|
25
|
+
description: ${description || ""}
|
|
26
|
+
|
|
27
|
+
# A chart can be either an 'application' or a 'library' chart.
|
|
28
|
+
#
|
|
29
|
+
# Application charts are a collection of templates that can be packaged into versioned archives
|
|
30
|
+
# to be deployed.
|
|
31
|
+
#
|
|
32
|
+
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
|
33
|
+
# a dependency of application charts to inject those utilities and functions into the rendering
|
|
34
|
+
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
|
35
|
+
type: application
|
|
36
|
+
|
|
37
|
+
# This is the chart version. This version number should be incremented each time you make changes
|
|
38
|
+
# to the chart and its templates, including the app version.
|
|
39
|
+
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
|
40
|
+
version: 0.1.0
|
|
41
|
+
|
|
42
|
+
# This is the version number of the application being deployed. This version number should be
|
|
43
|
+
# incremented each time you make changes to the application. Versions are not expected to
|
|
44
|
+
# follow Semantic Versioning. They should reflect the version the application is using.
|
|
45
|
+
# It is recommended to use it with quotes.
|
|
46
|
+
appVersion: "1.16.0"
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function watcherDeployTemplate(buildTimestamp: string) {
|
|
51
|
+
return `
|
|
52
|
+
apiVersion: apps/v1
|
|
53
|
+
kind: Deployment
|
|
54
|
+
metadata:
|
|
55
|
+
name: {{ .Values.uuid }}-watcher
|
|
56
|
+
namespace: pepr-system
|
|
57
|
+
annotations:
|
|
58
|
+
{{- toYaml .Values.watcher.annotations | nindent 4 }}
|
|
59
|
+
labels:
|
|
60
|
+
{{- toYaml .Values.watcher.labels | nindent 4 }}
|
|
61
|
+
spec:
|
|
62
|
+
replicas: 1
|
|
63
|
+
strategy:
|
|
64
|
+
type: Recreate
|
|
65
|
+
selector:
|
|
66
|
+
matchLabels:
|
|
67
|
+
app: {{ .Values.uuid }}-watcher
|
|
68
|
+
pepr.dev/controller: watcher
|
|
69
|
+
template:
|
|
70
|
+
metadata:
|
|
71
|
+
annotations:
|
|
72
|
+
buildTimestamp: "${buildTimestamp}"
|
|
73
|
+
{{- if .Values.watcher.podAnnotations }}
|
|
74
|
+
{{- toYaml .Values.watcher.podAnnotations | nindent 8 }}
|
|
75
|
+
{{- end }}
|
|
76
|
+
labels:
|
|
77
|
+
app: {{ .Values.uuid }}-watcher
|
|
78
|
+
pepr.dev/controller: watcher
|
|
79
|
+
spec:
|
|
80
|
+
terminationGracePeriodSeconds: {{ .Values.watcher.terminationGracePeriodSeconds }}
|
|
81
|
+
serviceAccountName: {{ .Values.uuid }}
|
|
82
|
+
securityContext:
|
|
83
|
+
{{- toYaml .Values.admission.securityContext | nindent 8 }}
|
|
84
|
+
containers:
|
|
85
|
+
- name: watcher
|
|
86
|
+
image: {{ .Values.watcher.image }}
|
|
87
|
+
imagePullPolicy: IfNotPresent
|
|
88
|
+
command:
|
|
89
|
+
- node
|
|
90
|
+
- /app/node_modules/pepr/dist/controller.js
|
|
91
|
+
- {{ .Values.hash }}
|
|
92
|
+
readinessProbe:
|
|
93
|
+
httpGet:
|
|
94
|
+
path: /healthz
|
|
95
|
+
port: 3000
|
|
96
|
+
scheme: HTTPS
|
|
97
|
+
livenessProbe:
|
|
98
|
+
httpGet:
|
|
99
|
+
path: /healthz
|
|
100
|
+
port: 3000
|
|
101
|
+
scheme: HTTPS
|
|
102
|
+
ports:
|
|
103
|
+
- containerPort: 3000
|
|
104
|
+
resources:
|
|
105
|
+
{{- toYaml .Values.watcher.resources | nindent 12 }}
|
|
106
|
+
env:
|
|
107
|
+
{{- toYaml .Values.watcher.env | nindent 12 }}
|
|
108
|
+
securityContext:
|
|
109
|
+
{{- toYaml .Values.watcher.containerSecurityContext | nindent 12 }}
|
|
110
|
+
volumeMounts:
|
|
111
|
+
- name: tls-certs
|
|
112
|
+
mountPath: /etc/certs
|
|
113
|
+
readOnly: true
|
|
114
|
+
- name: module
|
|
115
|
+
mountPath: /app/load
|
|
116
|
+
readOnly: true
|
|
117
|
+
{{- if .Values.watcher.extraVolumeMounts }}
|
|
118
|
+
{{- toYaml .Values.watcher.extraVolumeMounts | nindent 12 }}
|
|
119
|
+
{{- end }}
|
|
120
|
+
volumes:
|
|
121
|
+
- name: tls-certs
|
|
122
|
+
secret:
|
|
123
|
+
secretName: {{ .Values.uuid }}-tls
|
|
124
|
+
- name: module
|
|
125
|
+
secret:
|
|
126
|
+
secretName: {{ .Values.uuid }}-module
|
|
127
|
+
{{- if .Values.watcher.extraVolumes }}
|
|
128
|
+
{{- toYaml .Values.watcher.extraVolumes | nindent 8 }}
|
|
129
|
+
{{- end }}
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function admissionDeployTemplate(buildTimestamp: string) {
|
|
134
|
+
return `
|
|
135
|
+
apiVersion: apps/v1
|
|
136
|
+
kind: Deployment
|
|
137
|
+
metadata:
|
|
138
|
+
name: {{ .Values.uuid }}
|
|
139
|
+
namespace: pepr-system
|
|
140
|
+
annotations:
|
|
141
|
+
{{- toYaml .Values.admission.annotations | nindent 4 }}
|
|
142
|
+
labels:
|
|
143
|
+
{{- toYaml .Values.admission.labels | nindent 4 }}
|
|
144
|
+
spec:
|
|
145
|
+
replicas: 2
|
|
146
|
+
selector:
|
|
147
|
+
matchLabels:
|
|
148
|
+
app: {{ .Values.uuid }}
|
|
149
|
+
pepr.dev/controller: admission
|
|
150
|
+
template:
|
|
151
|
+
metadata:
|
|
152
|
+
annotations:
|
|
153
|
+
buildTimestamp: "${buildTimestamp}"
|
|
154
|
+
{{- if .Values.admission.podAnnotations }}
|
|
155
|
+
{{- toYaml .Values.admission.podAnnotations | nindent 8 }}
|
|
156
|
+
{{- end }}
|
|
157
|
+
labels:
|
|
158
|
+
app: {{ .Values.uuid }}
|
|
159
|
+
pepr.dev/controller: admission
|
|
160
|
+
spec:
|
|
161
|
+
terminationGracePeriodSeconds: {{ .Values.admission.terminationGracePeriodSeconds }}
|
|
162
|
+
priorityClassName: system-node-critical
|
|
163
|
+
serviceAccountName: {{ .Values.uuid }}
|
|
164
|
+
securityContext:
|
|
165
|
+
{{- toYaml .Values.admission.securityContext | nindent 8 }}
|
|
166
|
+
containers:
|
|
167
|
+
- name: server
|
|
168
|
+
image: {{ .Values.admission.image }}
|
|
169
|
+
imagePullPolicy: IfNotPresent
|
|
170
|
+
command:
|
|
171
|
+
- node
|
|
172
|
+
- /app/node_modules/pepr/dist/controller.js
|
|
173
|
+
- {{ .Values.hash }}
|
|
174
|
+
readinessProbe:
|
|
175
|
+
httpGet:
|
|
176
|
+
path: /healthz
|
|
177
|
+
port: 3000
|
|
178
|
+
scheme: HTTPS
|
|
179
|
+
livenessProbe:
|
|
180
|
+
httpGet:
|
|
181
|
+
path: /healthz
|
|
182
|
+
port: 3000
|
|
183
|
+
scheme: HTTPS
|
|
184
|
+
ports:
|
|
185
|
+
- containerPort: 3000
|
|
186
|
+
resources:
|
|
187
|
+
{{- toYaml .Values.admission.resources | nindent 12 }}
|
|
188
|
+
env:
|
|
189
|
+
{{- toYaml .Values.admission.env | nindent 12 }}
|
|
190
|
+
securityContext:
|
|
191
|
+
{{- toYaml .Values.admission.containerSecurityContext | nindent 12 }}
|
|
192
|
+
volumeMounts:
|
|
193
|
+
- name: tls-certs
|
|
194
|
+
mountPath: /etc/certs
|
|
195
|
+
readOnly: true
|
|
196
|
+
- name: api-token
|
|
197
|
+
mountPath: /app/api-token
|
|
198
|
+
readOnly: true
|
|
199
|
+
- name: module
|
|
200
|
+
mountPath: /app/load
|
|
201
|
+
readOnly: true
|
|
202
|
+
{{- if .Values.admission.extraVolumeMounts }}
|
|
203
|
+
{{- toYaml .Values.admission.extraVolumeMounts | nindent 12 }}
|
|
204
|
+
{{- end }}
|
|
205
|
+
volumes:
|
|
206
|
+
- name: tls-certs
|
|
207
|
+
secret:
|
|
208
|
+
secretName: {{ .Values.uuid }}-tls
|
|
209
|
+
- name: api-token
|
|
210
|
+
secret:
|
|
211
|
+
secretName: {{ .Values.uuid }}-api-token
|
|
212
|
+
- name: module
|
|
213
|
+
secret:
|
|
214
|
+
secretName: {{ .Values.uuid }}-module
|
|
215
|
+
{{- if .Values.admission.extraVolumes }}
|
|
216
|
+
{{- toYaml .Values.admission.extraVolumes | nindent 8 }}
|
|
217
|
+
{{- end }}
|
|
218
|
+
`;
|
|
219
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
import { dumpYaml } from "@kubernetes/client-node";
|
|
6
|
+
import { ModuleConfig } from "../module";
|
|
7
|
+
import { TLSOut, genTLS } from "../tls";
|
|
8
|
+
import { CapabilityExport } from "../types";
|
|
9
|
+
import { WebhookIgnore } from "../k8s";
|
|
10
|
+
import { deploy } from "./deploy";
|
|
11
|
+
import { loadCapabilities } from "./loader";
|
|
12
|
+
import { allYaml, zarfYaml, overridesFile, zarfYamlChart } from "./yaml";
|
|
13
|
+
import { namespaceComplianceValidator, replaceString } from "../helpers";
|
|
14
|
+
import { createDirectoryIfNotExists, dedent } from "../helpers";
|
|
15
|
+
import { resolve } from "path";
|
|
16
|
+
import { chartYaml, nsTemplate, admissionDeployTemplate, watcherDeployTemplate } from "./helm";
|
|
17
|
+
import { promises as fs } from "fs";
|
|
18
|
+
import { webhookConfig } from "./webhooks";
|
|
19
|
+
import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
|
|
20
|
+
import { watcher, moduleSecret } from "./pods";
|
|
21
|
+
|
|
22
|
+
import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
|
|
23
|
+
export class Assets {
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly tls: TLSOut;
|
|
26
|
+
readonly apiToken: string;
|
|
27
|
+
readonly alwaysIgnore!: WebhookIgnore;
|
|
28
|
+
capabilities!: CapabilityExport[];
|
|
29
|
+
|
|
30
|
+
image: string;
|
|
31
|
+
buildTimestamp: string;
|
|
32
|
+
hash: string;
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
readonly config: ModuleConfig,
|
|
36
|
+
readonly path: string,
|
|
37
|
+
readonly host?: string,
|
|
38
|
+
) {
|
|
39
|
+
this.name = `pepr-${config.uuid}`;
|
|
40
|
+
this.buildTimestamp = `${Date.now()}`;
|
|
41
|
+
this.alwaysIgnore = config.alwaysIgnore;
|
|
42
|
+
this.image = `ghcr.io/defenseunicorns/pepr/controller:v${config.peprVersion}`;
|
|
43
|
+
this.hash = "";
|
|
44
|
+
// Generate the ephemeral tls things
|
|
45
|
+
this.tls = genTLS(this.host || `${this.name}.pepr-system.svc`);
|
|
46
|
+
|
|
47
|
+
// Generate the api token for the controller / webhook
|
|
48
|
+
this.apiToken = crypto.randomBytes(32).toString("hex");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setHash = (hash: string) => {
|
|
52
|
+
this.hash = hash;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
deploy = async (force: boolean, webhookTimeout?: number) => {
|
|
56
|
+
this.capabilities = await loadCapabilities(this.path);
|
|
57
|
+
await deploy(this, force, webhookTimeout);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
zarfYaml = (path: string) => zarfYaml(this, path);
|
|
61
|
+
|
|
62
|
+
zarfYamlChart = (path: string) => zarfYamlChart(this, path);
|
|
63
|
+
|
|
64
|
+
allYaml = async (rbacMode: string) => {
|
|
65
|
+
this.capabilities = await loadCapabilities(this.path);
|
|
66
|
+
// give error if namespaces are not respected
|
|
67
|
+
for (const capability of this.capabilities) {
|
|
68
|
+
namespaceComplianceValidator(capability, this.alwaysIgnore.namespaces);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return allYaml(this, rbacMode);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
generateHelmChart = async (basePath: string) => {
|
|
75
|
+
const CHART_DIR = `${basePath}/${this.config.uuid}-chart`;
|
|
76
|
+
const CHAR_TEMPLATES_DIR = `${CHART_DIR}/templates`;
|
|
77
|
+
const valuesPath = resolve(CHART_DIR, `values.yaml`);
|
|
78
|
+
const chartPath = resolve(CHART_DIR, `Chart.yaml`);
|
|
79
|
+
const nsPath = resolve(CHAR_TEMPLATES_DIR, `namespace.yaml`);
|
|
80
|
+
const watcherSVCPath = resolve(CHAR_TEMPLATES_DIR, `watcher-service.yaml`);
|
|
81
|
+
const admissionSVCPath = resolve(CHAR_TEMPLATES_DIR, `admission-service.yaml`);
|
|
82
|
+
const mutationWebhookPath = resolve(CHAR_TEMPLATES_DIR, `mutation-webhook.yaml`);
|
|
83
|
+
const validationWebhookPath = resolve(CHAR_TEMPLATES_DIR, `validation-webhook.yaml`);
|
|
84
|
+
const admissionDeployPath = resolve(CHAR_TEMPLATES_DIR, `admission-deployment.yaml`);
|
|
85
|
+
const watcherDeployPath = resolve(CHAR_TEMPLATES_DIR, `watcher-deployment.yaml`);
|
|
86
|
+
const tlsSecretPath = resolve(CHAR_TEMPLATES_DIR, `tls-secret.yaml`);
|
|
87
|
+
const apiTokenSecretPath = resolve(CHAR_TEMPLATES_DIR, `api-token-secret.yaml`);
|
|
88
|
+
const moduleSecretPath = resolve(CHAR_TEMPLATES_DIR, `module-secret.yaml`);
|
|
89
|
+
const storeRolePath = resolve(CHAR_TEMPLATES_DIR, `store-role.yaml`);
|
|
90
|
+
const storeRoleBindingPath = resolve(CHAR_TEMPLATES_DIR, `store-role-binding.yaml`);
|
|
91
|
+
const clusterRolePath = resolve(CHAR_TEMPLATES_DIR, `cluster-role.yaml`);
|
|
92
|
+
const clusterRoleBindingPath = resolve(CHAR_TEMPLATES_DIR, `cluster-role-binding.yaml`);
|
|
93
|
+
const serviceAccountPath = resolve(CHAR_TEMPLATES_DIR, `service-account.yaml`);
|
|
94
|
+
|
|
95
|
+
// create helm chart
|
|
96
|
+
try {
|
|
97
|
+
// create chart dir
|
|
98
|
+
await createDirectoryIfNotExists(CHART_DIR);
|
|
99
|
+
|
|
100
|
+
// create charts dir
|
|
101
|
+
await createDirectoryIfNotExists(`${CHART_DIR}/charts`);
|
|
102
|
+
|
|
103
|
+
// create templates dir
|
|
104
|
+
await createDirectoryIfNotExists(`${CHAR_TEMPLATES_DIR}`);
|
|
105
|
+
|
|
106
|
+
// create values file
|
|
107
|
+
await overridesFile(this, valuesPath);
|
|
108
|
+
|
|
109
|
+
// create the chart.yaml
|
|
110
|
+
await fs.writeFile(chartPath, dedent(chartYaml(this.config.uuid, this.config.description || "")));
|
|
111
|
+
|
|
112
|
+
// create the namespace.yaml in templates
|
|
113
|
+
await fs.writeFile(nsPath, dedent(nsTemplate()));
|
|
114
|
+
|
|
115
|
+
const code = await fs.readFile(this.path);
|
|
116
|
+
|
|
117
|
+
await fs.writeFile(watcherSVCPath, dumpYaml(watcherService(this.name), { noRefs: true }));
|
|
118
|
+
await fs.writeFile(admissionSVCPath, dumpYaml(service(this.name), { noRefs: true }));
|
|
119
|
+
await fs.writeFile(tlsSecretPath, dumpYaml(tlsSecret(this.name, this.tls), { noRefs: true }));
|
|
120
|
+
await fs.writeFile(apiTokenSecretPath, dumpYaml(apiTokenSecret(this.name, this.apiToken), { noRefs: true }));
|
|
121
|
+
await fs.writeFile(moduleSecretPath, dumpYaml(moduleSecret(this.name, code, this.hash), { noRefs: true }));
|
|
122
|
+
await fs.writeFile(storeRolePath, dumpYaml(storeRole(this.name), { noRefs: true }));
|
|
123
|
+
await fs.writeFile(storeRoleBindingPath, dumpYaml(storeRoleBinding(this.name), { noRefs: true }));
|
|
124
|
+
await fs.writeFile(
|
|
125
|
+
clusterRolePath,
|
|
126
|
+
dumpYaml(clusterRole(this.name, this.capabilities, "rbac"), { noRefs: true }),
|
|
127
|
+
);
|
|
128
|
+
await fs.writeFile(clusterRoleBindingPath, dumpYaml(clusterRoleBinding(this.name), { noRefs: true }));
|
|
129
|
+
await fs.writeFile(serviceAccountPath, dumpYaml(serviceAccount(this.name), { noRefs: true }));
|
|
130
|
+
|
|
131
|
+
const mutateWebhook = await webhookConfig(this, "mutate", this.config.webhookTimeout);
|
|
132
|
+
const validateWebhook = await webhookConfig(this, "validate", this.config.webhookTimeout);
|
|
133
|
+
const watchDeployment = watcher(this, this.hash, this.buildTimestamp);
|
|
134
|
+
|
|
135
|
+
if (validateWebhook || mutateWebhook) {
|
|
136
|
+
await fs.writeFile(admissionDeployPath, dedent(admissionDeployTemplate(this.buildTimestamp)));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (mutateWebhook) {
|
|
140
|
+
const yamlMutateWebhook = dumpYaml(mutateWebhook, { noRefs: true });
|
|
141
|
+
const mutateWebhookTemplate = replaceString(
|
|
142
|
+
replaceString(
|
|
143
|
+
replaceString(yamlMutateWebhook, this.name, "{{ .Values.uuid }}"),
|
|
144
|
+
this.config.onError === "reject" ? "Fail" : "Ignore",
|
|
145
|
+
"{{ .Values.admission.failurePolicy }}",
|
|
146
|
+
),
|
|
147
|
+
`${this.config.webhookTimeout}` || "10",
|
|
148
|
+
"{{ .Values.admission.webhookTimeout }}",
|
|
149
|
+
);
|
|
150
|
+
await fs.writeFile(mutationWebhookPath, mutateWebhookTemplate);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (validateWebhook) {
|
|
154
|
+
const yamlValidateWebhook = dumpYaml(validateWebhook, { noRefs: true });
|
|
155
|
+
const validateWebhookTemplate = replaceString(
|
|
156
|
+
replaceString(
|
|
157
|
+
replaceString(yamlValidateWebhook, this.name, "{{ .Values.uuid }}"),
|
|
158
|
+
this.config.onError === "reject" ? "Fail" : "Ignore",
|
|
159
|
+
"{{ .Values.admission.failurePolicy }}",
|
|
160
|
+
),
|
|
161
|
+
`${this.config.webhookTimeout}` || "10",
|
|
162
|
+
"{{ .Values.admission.webhookTimeout }}",
|
|
163
|
+
);
|
|
164
|
+
await fs.writeFile(validationWebhookPath, validateWebhookTemplate);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (watchDeployment) {
|
|
168
|
+
await fs.writeFile(watcherDeployPath, dedent(watcherDeployTemplate(this.buildTimestamp)));
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error(`Error generating helm chart: ${err.message}`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { fork } from "child_process";
|
|
5
|
+
|
|
6
|
+
import { CapabilityExport } from "../types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Read the capabilities from the module by running it in build mode
|
|
10
|
+
* @param path
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
export function loadCapabilities(path: string): Promise<CapabilityExport[]> {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
// Fork is needed with the PEPR_MODE env var to ensure the module is loaded in build mode and will send back the capabilities
|
|
16
|
+
const program = fork(path, {
|
|
17
|
+
env: {
|
|
18
|
+
...process.env,
|
|
19
|
+
LOG_LEVEL: "warn",
|
|
20
|
+
PEPR_MODE: "build",
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Wait for the module to send back the capabilities
|
|
25
|
+
program.on("message", message => {
|
|
26
|
+
// Cast the message to the ModuleCapabilities type
|
|
27
|
+
const capabilities = message.valueOf() as CapabilityExport[];
|
|
28
|
+
|
|
29
|
+
// Iterate through the capabilities and generate the rules
|
|
30
|
+
for (const capability of capabilities) {
|
|
31
|
+
console.info(`Registered Pepr Capability "${capability.name}"`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
resolve(capabilities);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
program.on("error", error => {
|
|
38
|
+
reject(error);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { kind } from "kubernetes-fluent-client";
|
|
5
|
+
|
|
6
|
+
import { TLSOut } from "../tls";
|
|
7
|
+
|
|
8
|
+
export function apiTokenSecret(name: string, apiToken: string): kind.Secret {
|
|
9
|
+
return {
|
|
10
|
+
apiVersion: "v1",
|
|
11
|
+
kind: "Secret",
|
|
12
|
+
metadata: {
|
|
13
|
+
name: `${name}-api-token`,
|
|
14
|
+
namespace: "pepr-system",
|
|
15
|
+
},
|
|
16
|
+
type: "Opaque",
|
|
17
|
+
data: {
|
|
18
|
+
value: Buffer.from(apiToken).toString("base64"),
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function tlsSecret(name: string, tls: TLSOut): kind.Secret {
|
|
24
|
+
return {
|
|
25
|
+
apiVersion: "v1",
|
|
26
|
+
kind: "Secret",
|
|
27
|
+
metadata: {
|
|
28
|
+
name: `${name}-tls`,
|
|
29
|
+
namespace: "pepr-system",
|
|
30
|
+
},
|
|
31
|
+
type: "kubernetes.io/tls",
|
|
32
|
+
data: {
|
|
33
|
+
"tls.crt": tls.crt,
|
|
34
|
+
"tls.key": tls.key,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function service(name: string): kind.Service {
|
|
40
|
+
return {
|
|
41
|
+
apiVersion: "v1",
|
|
42
|
+
kind: "Service",
|
|
43
|
+
metadata: {
|
|
44
|
+
name,
|
|
45
|
+
namespace: "pepr-system",
|
|
46
|
+
labels: {
|
|
47
|
+
"pepr.dev/controller": "admission",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
spec: {
|
|
51
|
+
selector: {
|
|
52
|
+
app: name,
|
|
53
|
+
"pepr.dev/controller": "admission",
|
|
54
|
+
},
|
|
55
|
+
ports: [
|
|
56
|
+
{
|
|
57
|
+
port: 443,
|
|
58
|
+
targetPort: 3000,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function watcherService(name: string): kind.Service {
|
|
66
|
+
return {
|
|
67
|
+
apiVersion: "v1",
|
|
68
|
+
kind: "Service",
|
|
69
|
+
metadata: {
|
|
70
|
+
name: `${name}-watcher`,
|
|
71
|
+
namespace: "pepr-system",
|
|
72
|
+
labels: {
|
|
73
|
+
"pepr.dev/controller": "watcher",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
spec: {
|
|
77
|
+
selector: {
|
|
78
|
+
app: `${name}-watcher`,
|
|
79
|
+
"pepr.dev/controller": "watcher",
|
|
80
|
+
},
|
|
81
|
+
ports: [
|
|
82
|
+
{
|
|
83
|
+
port: 443,
|
|
84
|
+
targetPort: 3000,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|