pepr 0.42.1 → 0.42.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/build.helpers.d.ts +1 -1
- package/dist/cli/build.helpers.d.ts.map +1 -1
- package/dist/cli/deploy.d.ts +15 -0
- package/dist/cli/deploy.d.ts.map +1 -1
- package/dist/cli/dev.d.ts.map +1 -1
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.helpers.d.ts +3 -0
- package/dist/cli/format.helpers.d.ts.map +1 -0
- package/dist/cli/init/enums.d.ts +10 -0
- package/dist/cli/init/enums.d.ts.map +1 -0
- package/dist/cli/init/index.d.ts.map +1 -1
- package/dist/cli/init/templates.d.ts +15 -11
- package/dist/cli/init/templates.d.ts.map +1 -1
- package/dist/cli/init/utils.d.ts.map +1 -1
- package/dist/cli/init/walkthrough.d.ts +3 -2
- package/dist/cli/init/walkthrough.d.ts.map +1 -1
- package/dist/cli/kfc.d.ts.map +1 -1
- package/dist/cli/root.d.ts.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/uuid.d.ts.map +1 -1
- package/dist/cli.js +1073 -1220
- package/dist/controller.js +1 -195
- package/dist/fixtures/loader.d.ts.map +1 -1
- package/dist/lib/assets/assets.d.ts +27 -0
- package/dist/lib/assets/assets.d.ts.map +1 -0
- package/dist/lib/assets/deploy.d.ts +1 -1
- package/dist/lib/assets/deploy.d.ts.map +1 -1
- package/dist/lib/assets/index.d.ts +5 -24
- package/dist/lib/assets/index.d.ts.map +1 -1
- package/dist/lib/assets/pods.d.ts +2 -4
- package/dist/lib/assets/pods.d.ts.map +1 -1
- package/dist/lib/assets/rbac.d.ts.map +1 -1
- package/dist/lib/assets/webhooks.d.ts +1 -1
- package/dist/lib/assets/webhooks.d.ts.map +1 -1
- package/dist/lib/assets/yaml.d.ts +31 -5
- package/dist/lib/assets/yaml.d.ts.map +1 -1
- package/dist/lib/controller/index.d.ts +2 -2
- package/dist/lib/controller/index.d.ts.map +1 -1
- package/dist/lib/controller/store.d.ts +1 -1
- package/dist/lib/controller/store.d.ts.map +1 -1
- package/dist/lib/controller/storeCache.d.ts +1 -1
- package/dist/lib/controller/storeCache.d.ts.map +1 -1
- package/dist/lib/{capability.d.ts → core/capability.d.ts} +1 -1
- package/dist/lib/core/capability.d.ts.map +1 -0
- package/dist/lib/{module.d.ts → core/module.d.ts} +2 -2
- package/dist/lib/core/module.d.ts.map +1 -0
- package/dist/lib/core/queue.d.ts.map +1 -0
- package/dist/lib/{schedule.d.ts → core/schedule.d.ts} +0 -1
- package/dist/lib/core/schedule.d.ts.map +1 -0
- package/dist/lib/core/storage.d.ts.map +1 -0
- package/dist/lib/deploymentChecks.d.ts.map +1 -1
- package/dist/lib/errors.d.ts +0 -5
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/filesystemService.d.ts.map +1 -1
- package/dist/lib/filter/adjudicators/adjudicators.d.ts +5 -4
- package/dist/lib/filter/adjudicators/adjudicators.d.ts.map +1 -1
- package/dist/lib/filter/filter.d.ts +33 -1
- package/dist/lib/filter/filter.d.ts.map +1 -1
- package/dist/lib/finalizer.d.ts.map +1 -1
- package/dist/lib/helpers.d.ts +4 -9
- package/dist/lib/helpers.d.ts.map +1 -1
- package/dist/lib/included-files.d.ts.map +1 -1
- package/dist/lib/mutate-request.d.ts.map +1 -1
- package/dist/lib/processors/mutate-processor.d.ts +28 -0
- package/dist/lib/processors/mutate-processor.d.ts.map +1 -0
- package/dist/lib/{validate-processor.d.ts → processors/validate-processor.d.ts} +5 -5
- package/dist/lib/processors/validate-processor.d.ts.map +1 -0
- package/dist/lib/{watch-processor.d.ts → processors/watch-processor.d.ts} +2 -2
- package/dist/lib/processors/watch-processor.d.ts.map +1 -0
- package/dist/lib/telemetry/logger.d.ts.map +1 -1
- package/dist/lib/telemetry/metrics.d.ts.map +1 -1
- package/dist/lib/validate-request.d.ts +2 -2
- package/dist/lib/validate-request.d.ts.map +1 -1
- package/dist/lib.d.ts +2 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +383 -243
- package/dist/lib.js.map +4 -4
- package/dist/sdk/heredoc.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/cli/build.helpers.ts +1 -1
- package/src/cli/build.ts +1 -1
- package/src/cli/deploy.ts +114 -75
- package/src/cli/dev.ts +3 -3
- package/src/cli/format.helpers.ts +27 -0
- package/src/cli/format.ts +4 -18
- package/src/cli/init/enums.ts +9 -0
- package/src/cli/init/index.ts +4 -3
- package/src/cli/init/templates.ts +30 -2
- package/src/cli/init/utils.ts +3 -3
- package/src/cli/init/walkthrough.ts +7 -8
- package/src/cli/kfc.ts +1 -1
- package/src/cli/root.ts +1 -1
- package/src/cli/update.ts +1 -1
- package/src/cli/uuid.ts +1 -1
- package/src/fixtures/loader.ts +2 -2
- package/src/lib/assets/assets.ts +176 -0
- package/src/lib/assets/deploy.ts +6 -6
- package/src/lib/assets/index.ts +10 -144
- package/src/lib/assets/pods.ts +2 -2
- package/src/lib/assets/webhooks.ts +32 -56
- package/src/lib/assets/yaml.ts +47 -25
- package/src/lib/controller/index.ts +4 -4
- package/src/lib/controller/store.ts +2 -2
- package/src/lib/controller/storeCache.ts +6 -2
- package/src/lib/{capability.ts → core/capability.ts} +4 -4
- package/src/lib/{module.ts → core/module.ts} +10 -10
- package/src/lib/{queue.ts → core/queue.ts} +1 -1
- package/src/lib/deploymentChecks.ts +2 -2
- package/src/lib/errors.ts +3 -8
- package/src/lib/filesystemService.ts +1 -1
- package/src/lib/filter/adjudicators/adjudicators.ts +40 -9
- package/src/lib/filter/filter.ts +204 -111
- package/src/lib/finalizer.ts +2 -2
- package/src/lib/helpers.ts +20 -133
- package/src/lib/included-files.ts +1 -1
- package/src/lib/processors/mutate-processor.ts +225 -0
- package/src/lib/{validate-processor.ts → processors/validate-processor.ts} +8 -8
- package/src/lib/{watch-processor.ts → processors/watch-processor.ts} +8 -8
- package/src/lib/telemetry/logger.ts +3 -1
- package/src/lib/tls.ts +5 -1
- package/src/lib/validate-request.ts +4 -4
- package/src/lib.ts +2 -2
- package/src/runtime/controller.ts +2 -2
- package/src/sdk/heredoc.ts +1 -1
- package/dist/lib/capability.d.ts.map +0 -1
- package/dist/lib/module.d.ts.map +0 -1
- package/dist/lib/mutate-processor.d.ts +0 -6
- package/dist/lib/mutate-processor.d.ts.map +0 -1
- package/dist/lib/queue.d.ts.map +0 -1
- package/dist/lib/schedule.d.ts.map +0 -1
- package/dist/lib/storage.d.ts.map +0 -1
- package/dist/lib/validate-processor.d.ts.map +0 -1
- package/dist/lib/watch-processor.d.ts.map +0 -1
- package/src/lib/mutate-processor.ts +0 -165
- /package/dist/lib/{queue.d.ts → core/queue.d.ts} +0 -0
- /package/dist/lib/{storage.d.ts → core/storage.d.ts} +0 -0
- /package/src/lib/{schedule.ts → core/schedule.ts} +0 -0
- /package/src/lib/{storage.ts → core/storage.ts} +0 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import { CapabilityExport } from "../types";
|
|
3
|
+
import { ModuleConfig } from "../core/module";
|
|
4
|
+
import { TLSOut, genTLS } from "../tls";
|
|
5
|
+
import { WebhookIgnore } from "../k8s";
|
|
6
|
+
import {
|
|
7
|
+
chartYaml,
|
|
8
|
+
namespaceTemplate,
|
|
9
|
+
clusterRoleTemplate,
|
|
10
|
+
admissionDeployTemplate,
|
|
11
|
+
serviceMonitorTemplate,
|
|
12
|
+
watcherDeployTemplate,
|
|
13
|
+
} from "./helm";
|
|
14
|
+
import { createDirectoryIfNotExists } from "../filesystemService";
|
|
15
|
+
import { deploy } from "./deploy";
|
|
16
|
+
import { getDeployment, getModuleSecret, getWatcher } from "./pods";
|
|
17
|
+
import { helmLayout, createWebhookYaml, toYaml } from "./index";
|
|
18
|
+
import { loadCapabilities } from "./loader";
|
|
19
|
+
import { namespaceComplianceValidator, dedent } from "../helpers";
|
|
20
|
+
import { storeRole, storeRoleBinding, clusterRoleBinding, serviceAccount } from "./rbac";
|
|
21
|
+
import { watcherService, service, tlsSecret, apiTokenSecret } from "./networking";
|
|
22
|
+
import { webhookConfig } from "./webhooks";
|
|
23
|
+
import { generateZarfYaml, generateZarfYamlChart, generateAllYaml, overridesFile } from "./yaml";
|
|
24
|
+
import { promises as fs } from "fs";
|
|
25
|
+
import { V1MutatingWebhookConfiguration, V1ValidatingWebhookConfiguration } from "@kubernetes/client-node/dist/gen";
|
|
26
|
+
|
|
27
|
+
export class Assets {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly tls: TLSOut;
|
|
30
|
+
readonly apiToken: string;
|
|
31
|
+
readonly alwaysIgnore!: WebhookIgnore;
|
|
32
|
+
capabilities!: CapabilityExport[];
|
|
33
|
+
|
|
34
|
+
image: string;
|
|
35
|
+
buildTimestamp: string;
|
|
36
|
+
hash: string;
|
|
37
|
+
|
|
38
|
+
constructor(
|
|
39
|
+
readonly config: ModuleConfig,
|
|
40
|
+
readonly path: string,
|
|
41
|
+
readonly host?: string,
|
|
42
|
+
) {
|
|
43
|
+
this.name = `pepr-${config.uuid}`;
|
|
44
|
+
this.buildTimestamp = `${Date.now()}`;
|
|
45
|
+
this.alwaysIgnore = config.alwaysIgnore;
|
|
46
|
+
this.image = `ghcr.io/defenseunicorns/pepr/controller:v${config.peprVersion}`;
|
|
47
|
+
this.hash = "";
|
|
48
|
+
// Generate the ephemeral tls things
|
|
49
|
+
this.tls = genTLS(this.host || `${this.name}.pepr-system.svc`);
|
|
50
|
+
|
|
51
|
+
// Generate the api token for the controller / webhook
|
|
52
|
+
this.apiToken = crypto.randomBytes(32).toString("hex");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
setHash = (hash: string): void => {
|
|
56
|
+
this.hash = hash;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
deploy = async (force: boolean, webhookTimeout?: number): Promise<void> => {
|
|
60
|
+
this.capabilities = await loadCapabilities(this.path);
|
|
61
|
+
await deploy(this, force, webhookTimeout);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
zarfYaml = (path: string): string => generateZarfYaml(this.name, this.image, this.config, path);
|
|
65
|
+
|
|
66
|
+
zarfYamlChart = (path: string): string => generateZarfYamlChart(this.name, this.image, this.config, path);
|
|
67
|
+
|
|
68
|
+
allYaml = async (imagePullSecret?: string): Promise<string> => {
|
|
69
|
+
this.capabilities = await loadCapabilities(this.path);
|
|
70
|
+
// give error if namespaces are not respected
|
|
71
|
+
for (const capability of this.capabilities) {
|
|
72
|
+
namespaceComplianceValidator(capability, this.alwaysIgnore?.namespaces);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const webhooks = {
|
|
76
|
+
mutate: await webhookConfig(this, "mutate", this.config.webhookTimeout),
|
|
77
|
+
validate: await webhookConfig(this, "validate", this.config.webhookTimeout),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const code = await fs.readFile(this.path);
|
|
81
|
+
|
|
82
|
+
// Generate a hash of the code
|
|
83
|
+
this.hash = crypto.createHash("sha256").update(code).digest("hex");
|
|
84
|
+
|
|
85
|
+
const deployments = {
|
|
86
|
+
default: getDeployment(this, this.hash, this.buildTimestamp, imagePullSecret),
|
|
87
|
+
watch: getWatcher(this, this.hash, this.buildTimestamp, imagePullSecret),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const assetsInputs = {
|
|
91
|
+
apiToken: this.apiToken,
|
|
92
|
+
capabilities: this.capabilities,
|
|
93
|
+
config: this.config,
|
|
94
|
+
hash: this.hash,
|
|
95
|
+
name: this.name,
|
|
96
|
+
path: this.path,
|
|
97
|
+
tls: this.tls,
|
|
98
|
+
};
|
|
99
|
+
return generateAllYaml(webhooks, deployments, assetsInputs);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
writeWebhookFiles = async (
|
|
103
|
+
validateWebhook: V1MutatingWebhookConfiguration | V1ValidatingWebhookConfiguration | null,
|
|
104
|
+
mutateWebhook: V1MutatingWebhookConfiguration | V1ValidatingWebhookConfiguration | null,
|
|
105
|
+
helm: Record<string, Record<string, string>>,
|
|
106
|
+
): Promise<void> => {
|
|
107
|
+
if (validateWebhook || mutateWebhook) {
|
|
108
|
+
await fs.writeFile(helm.files.admissionDeploymentYaml, dedent(admissionDeployTemplate(this.buildTimestamp)));
|
|
109
|
+
await fs.writeFile(helm.files.admissionServiceMonitorYaml, dedent(serviceMonitorTemplate("admission")));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (mutateWebhook) {
|
|
113
|
+
await fs.writeFile(helm.files.mutationWebhookYaml, createWebhookYaml(this.name, this.config, mutateWebhook));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (validateWebhook) {
|
|
117
|
+
await fs.writeFile(helm.files.validationWebhookYaml, createWebhookYaml(this.name, this.config, validateWebhook));
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
generateHelmChart = async (basePath: string): Promise<void> => {
|
|
122
|
+
const helm = helmLayout(basePath, this.config.uuid);
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await Promise.all(
|
|
126
|
+
Object.values(helm.dirs)
|
|
127
|
+
.sort((l, r) => l.split("/").length - r.split("/").length)
|
|
128
|
+
.map(async dir => await createDirectoryIfNotExists(dir)),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const code = await fs.readFile(this.path);
|
|
132
|
+
|
|
133
|
+
const pairs: [string, () => string][] = [
|
|
134
|
+
[helm.files.chartYaml, (): string => dedent(chartYaml(this.config.uuid, this.config.description || ""))],
|
|
135
|
+
[helm.files.namespaceYaml, (): string => dedent(namespaceTemplate())],
|
|
136
|
+
[helm.files.watcherServiceYaml, (): string => toYaml(watcherService(this.name))],
|
|
137
|
+
[helm.files.admissionServiceYaml, (): string => toYaml(service(this.name))],
|
|
138
|
+
[helm.files.tlsSecretYaml, (): string => toYaml(tlsSecret(this.name, this.tls))],
|
|
139
|
+
[helm.files.apiTokenSecretYaml, (): string => toYaml(apiTokenSecret(this.name, this.apiToken))],
|
|
140
|
+
[helm.files.storeRoleYaml, (): string => toYaml(storeRole(this.name))],
|
|
141
|
+
[helm.files.storeRoleBindingYaml, (): string => toYaml(storeRoleBinding(this.name))],
|
|
142
|
+
[helm.files.clusterRoleYaml, (): string => dedent(clusterRoleTemplate())],
|
|
143
|
+
[helm.files.clusterRoleBindingYaml, (): string => toYaml(clusterRoleBinding(this.name))],
|
|
144
|
+
[helm.files.serviceAccountYaml, (): string => toYaml(serviceAccount(this.name))],
|
|
145
|
+
[helm.files.moduleSecretYaml, (): string => toYaml(getModuleSecret(this.name, code, this.hash))],
|
|
146
|
+
];
|
|
147
|
+
await Promise.all(pairs.map(async ([file, content]) => await fs.writeFile(file, content())));
|
|
148
|
+
|
|
149
|
+
const overrideData = {
|
|
150
|
+
hash: this.hash,
|
|
151
|
+
name: this.name,
|
|
152
|
+
image: this.image,
|
|
153
|
+
config: this.config,
|
|
154
|
+
apiToken: this.apiToken,
|
|
155
|
+
capabilities: this.capabilities,
|
|
156
|
+
};
|
|
157
|
+
await overridesFile(overrideData, helm.files.valuesYaml);
|
|
158
|
+
|
|
159
|
+
const [mutateWebhook, validateWebhook] = await Promise.all([
|
|
160
|
+
webhookConfig(this, "mutate", this.config.webhookTimeout),
|
|
161
|
+
webhookConfig(this, "validate", this.config.webhookTimeout),
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
await this.writeWebhookFiles(validateWebhook, mutateWebhook, helm);
|
|
165
|
+
|
|
166
|
+
const watchDeployment = getWatcher(this, this.hash, this.buildTimestamp);
|
|
167
|
+
if (watchDeployment) {
|
|
168
|
+
await fs.writeFile(helm.files.watcherDeploymentYaml, dedent(watcherDeployTemplate(this.buildTimestamp)));
|
|
169
|
+
await fs.writeFile(helm.files.watcherServiceMonitorYaml, dedent(serviceMonitorTemplate("watcher")));
|
|
170
|
+
}
|
|
171
|
+
} catch (err) {
|
|
172
|
+
console.error(`Error generating helm chart: ${err.message}`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
package/src/lib/assets/deploy.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { promises as fs } from "fs";
|
|
|
6
6
|
import { K8s, kind } from "kubernetes-fluent-client";
|
|
7
7
|
import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
|
|
8
8
|
|
|
9
|
-
import { Assets } from "
|
|
9
|
+
import { Assets } from "./assets";
|
|
10
10
|
import Log from "../telemetry/logger";
|
|
11
11
|
import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
|
|
12
12
|
import { getDeployment, getModuleSecret, getNamespace, getWatcher } from "./pods";
|
|
@@ -15,7 +15,7 @@ import { peprStoreCRD } from "./store";
|
|
|
15
15
|
import { webhookConfig } from "./webhooks";
|
|
16
16
|
import { CapabilityExport, ImagePullSecret } from "../types";
|
|
17
17
|
|
|
18
|
-
export async function deployImagePullSecret(imagePullSecret: ImagePullSecret, name: string) {
|
|
18
|
+
export async function deployImagePullSecret(imagePullSecret: ImagePullSecret, name: string): Promise<void> {
|
|
19
19
|
try {
|
|
20
20
|
await K8s(kind.Namespace).Get("pepr-system");
|
|
21
21
|
} catch {
|
|
@@ -42,7 +42,7 @@ export async function deployImagePullSecret(imagePullSecret: ImagePullSecret, na
|
|
|
42
42
|
Log.error(e);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
export async function deploy(assets: Assets, force: boolean, webhookTimeout?: number) {
|
|
45
|
+
export async function deploy(assets: Assets, force: boolean, webhookTimeout?: number): Promise<void> {
|
|
46
46
|
Log.info("Establishing connection to Kubernetes");
|
|
47
47
|
|
|
48
48
|
const { name, host, path } = assets;
|
|
@@ -95,7 +95,7 @@ async function setupRBAC(
|
|
|
95
95
|
capabilities: CapabilityExport[],
|
|
96
96
|
force: boolean,
|
|
97
97
|
config: { rbacMode?: string; rbac?: PolicyRule[] },
|
|
98
|
-
) {
|
|
98
|
+
): Promise<void> {
|
|
99
99
|
const { rbacMode, rbac } = config;
|
|
100
100
|
|
|
101
101
|
Log.info("Applying cluster role binding");
|
|
@@ -119,7 +119,7 @@ async function setupRBAC(
|
|
|
119
119
|
await K8s(kind.RoleBinding).Apply(roleBinding, { force });
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
async function setupController(assets: Assets, code: Buffer, hash: string, force: boolean) {
|
|
122
|
+
async function setupController(assets: Assets, code: Buffer, hash: string, force: boolean): Promise<void> {
|
|
123
123
|
const { name } = assets;
|
|
124
124
|
|
|
125
125
|
Log.info("Applying module secret");
|
|
@@ -144,7 +144,7 @@ async function setupController(assets: Assets, code: Buffer, hash: string, force
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// Setup the watcher deployment and service
|
|
147
|
-
async function setupWatcher(assets: Assets, hash: string, force: boolean) {
|
|
147
|
+
async function setupWatcher(assets: Assets, hash: string, force: boolean): Promise<void> {
|
|
148
148
|
// If the module has a watcher, deploy it
|
|
149
149
|
const watchDeployment = getWatcher(assets, hash, assets.buildTimestamp);
|
|
150
150
|
if (watchDeployment) {
|
package/src/lib/assets/index.ts
CHANGED
|
@@ -1,57 +1,35 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
import crypto from "crypto";
|
|
5
4
|
import { dumpYaml } from "@kubernetes/client-node";
|
|
6
5
|
import { kind } from "kubernetes-fluent-client";
|
|
7
|
-
import {
|
|
8
|
-
import { TLSOut, genTLS } from "../tls";
|
|
9
|
-
import { CapabilityExport } from "../types";
|
|
10
|
-
import { WebhookIgnore } from "../k8s";
|
|
11
|
-
import { deploy } from "./deploy";
|
|
12
|
-
import { loadCapabilities } from "./loader";
|
|
13
|
-
import { allYaml, zarfYaml, overridesFile, zarfYamlChart } from "./yaml";
|
|
14
|
-
import { namespaceComplianceValidator, replaceString } from "../helpers";
|
|
15
|
-
import { dedent } from "../helpers";
|
|
6
|
+
import { replaceString } from "../helpers";
|
|
16
7
|
import { resolve } from "path";
|
|
17
|
-
import {
|
|
18
|
-
chartYaml,
|
|
19
|
-
namespaceTemplate,
|
|
20
|
-
admissionDeployTemplate,
|
|
21
|
-
watcherDeployTemplate,
|
|
22
|
-
clusterRoleTemplate,
|
|
23
|
-
serviceMonitorTemplate,
|
|
24
|
-
} from "./helm";
|
|
25
|
-
import { promises as fs } from "fs";
|
|
26
|
-
import { webhookConfig } from "./webhooks";
|
|
27
|
-
import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
|
|
28
|
-
import { getWatcher, getModuleSecret } from "./pods";
|
|
29
|
-
|
|
30
|
-
import { clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
|
|
31
|
-
import { createDirectoryIfNotExists } from "../filesystemService";
|
|
8
|
+
import { ModuleConfig } from "../core/module";
|
|
32
9
|
|
|
33
10
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
-
function toYaml(obj: any): string {
|
|
11
|
+
export function toYaml(obj: any): string {
|
|
35
12
|
return dumpYaml(obj, { noRefs: true });
|
|
36
13
|
}
|
|
37
14
|
|
|
38
|
-
function createWebhookYaml(
|
|
39
|
-
|
|
15
|
+
export function createWebhookYaml(
|
|
16
|
+
name: string,
|
|
17
|
+
config: ModuleConfig,
|
|
40
18
|
webhookConfiguration: kind.MutatingWebhookConfiguration | kind.ValidatingWebhookConfiguration,
|
|
41
19
|
): string {
|
|
42
20
|
const yaml = toYaml(webhookConfiguration);
|
|
43
21
|
return replaceString(
|
|
44
22
|
replaceString(
|
|
45
|
-
replaceString(yaml,
|
|
46
|
-
|
|
23
|
+
replaceString(yaml, name, "{{ .Values.uuid }}"),
|
|
24
|
+
config.onError === "reject" ? "Fail" : "Ignore",
|
|
47
25
|
"{{ .Values.admission.failurePolicy }}",
|
|
48
26
|
),
|
|
49
|
-
`${
|
|
27
|
+
`${config.webhookTimeout}` || "10",
|
|
50
28
|
"{{ .Values.admission.webhookTimeout }}",
|
|
51
29
|
);
|
|
52
30
|
}
|
|
53
31
|
|
|
54
|
-
function helmLayout(basePath: string, unique: string): Record<string, Record<string, string>> {
|
|
32
|
+
export function helmLayout(basePath: string, unique: string): Record<string, Record<string, string>> {
|
|
55
33
|
const helm: Record<string, Record<string, string>> = {
|
|
56
34
|
dirs: {
|
|
57
35
|
chart: resolve(`${basePath}/${unique}-chart`),
|
|
@@ -90,115 +68,3 @@ function helmLayout(basePath: string, unique: string): Record<string, Record<str
|
|
|
90
68
|
|
|
91
69
|
return helm;
|
|
92
70
|
}
|
|
93
|
-
|
|
94
|
-
export class Assets {
|
|
95
|
-
readonly name: string;
|
|
96
|
-
readonly tls: TLSOut;
|
|
97
|
-
readonly apiToken: string;
|
|
98
|
-
readonly alwaysIgnore!: WebhookIgnore;
|
|
99
|
-
capabilities!: CapabilityExport[];
|
|
100
|
-
|
|
101
|
-
image: string;
|
|
102
|
-
buildTimestamp: string;
|
|
103
|
-
hash: string;
|
|
104
|
-
|
|
105
|
-
constructor(
|
|
106
|
-
readonly config: ModuleConfig,
|
|
107
|
-
readonly path: string,
|
|
108
|
-
readonly host?: string,
|
|
109
|
-
) {
|
|
110
|
-
this.name = `pepr-${config.uuid}`;
|
|
111
|
-
this.buildTimestamp = `${Date.now()}`;
|
|
112
|
-
this.alwaysIgnore = config.alwaysIgnore;
|
|
113
|
-
this.image = `ghcr.io/defenseunicorns/pepr/controller:v${config.peprVersion}`;
|
|
114
|
-
this.hash = "";
|
|
115
|
-
// Generate the ephemeral tls things
|
|
116
|
-
this.tls = genTLS(this.host || `${this.name}.pepr-system.svc`);
|
|
117
|
-
|
|
118
|
-
// Generate the api token for the controller / webhook
|
|
119
|
-
this.apiToken = crypto.randomBytes(32).toString("hex");
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
setHash = (hash: string): void => {
|
|
123
|
-
this.hash = hash;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
deploy = async (force: boolean, webhookTimeout?: number): Promise<void> => {
|
|
127
|
-
this.capabilities = await loadCapabilities(this.path);
|
|
128
|
-
await deploy(this, force, webhookTimeout);
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
zarfYaml = (path: string): string => zarfYaml(this, path);
|
|
132
|
-
|
|
133
|
-
zarfYamlChart = (path: string): string => zarfYamlChart(this, path);
|
|
134
|
-
|
|
135
|
-
allYaml = async (imagePullSecret?: string): Promise<string> => {
|
|
136
|
-
this.capabilities = await loadCapabilities(this.path);
|
|
137
|
-
// give error if namespaces are not respected
|
|
138
|
-
for (const capability of this.capabilities) {
|
|
139
|
-
namespaceComplianceValidator(capability, this.alwaysIgnore?.namespaces);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return allYaml(this, imagePullSecret);
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
/* eslint max-statements: ["warn", 21] */
|
|
146
|
-
generateHelmChart = async (basePath: string): Promise<void> => {
|
|
147
|
-
const helm = helmLayout(basePath, this.config.uuid);
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
await Promise.all(
|
|
151
|
-
Object.values(helm.dirs)
|
|
152
|
-
.sort((l, r) => l.split("/").length - r.split("/").length)
|
|
153
|
-
.map(async dir => await createDirectoryIfNotExists(dir)),
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
const code = await fs.readFile(this.path);
|
|
157
|
-
|
|
158
|
-
const pairs: [string, () => string][] = [
|
|
159
|
-
[helm.files.chartYaml, (): string => dedent(chartYaml(this.config.uuid, this.config.description || ""))],
|
|
160
|
-
[helm.files.namespaceYaml, (): string => dedent(namespaceTemplate())],
|
|
161
|
-
[helm.files.watcherServiceYaml, (): string => toYaml(watcherService(this.name))],
|
|
162
|
-
[helm.files.admissionServiceYaml, (): string => toYaml(service(this.name))],
|
|
163
|
-
[helm.files.tlsSecretYaml, (): string => toYaml(tlsSecret(this.name, this.tls))],
|
|
164
|
-
[helm.files.apiTokenSecretYaml, (): string => toYaml(apiTokenSecret(this.name, this.apiToken))],
|
|
165
|
-
[helm.files.storeRoleYaml, (): string => toYaml(storeRole(this.name))],
|
|
166
|
-
[helm.files.storeRoleBindingYaml, (): string => toYaml(storeRoleBinding(this.name))],
|
|
167
|
-
[helm.files.clusterRoleYaml, (): string => dedent(clusterRoleTemplate())],
|
|
168
|
-
[helm.files.clusterRoleBindingYaml, (): string => toYaml(clusterRoleBinding(this.name))],
|
|
169
|
-
[helm.files.serviceAccountYaml, (): string => toYaml(serviceAccount(this.name))],
|
|
170
|
-
[helm.files.moduleSecretYaml, (): string => toYaml(getModuleSecret(this.name, code, this.hash))],
|
|
171
|
-
];
|
|
172
|
-
await Promise.all(pairs.map(async ([file, content]) => await fs.writeFile(file, content())));
|
|
173
|
-
|
|
174
|
-
await overridesFile(this, helm.files.valuesYaml);
|
|
175
|
-
|
|
176
|
-
const [mutateWebhook, validateWebhook] = await Promise.all([
|
|
177
|
-
webhookConfig(this, "mutate", this.config.webhookTimeout),
|
|
178
|
-
webhookConfig(this, "validate", this.config.webhookTimeout),
|
|
179
|
-
]);
|
|
180
|
-
|
|
181
|
-
if (validateWebhook || mutateWebhook) {
|
|
182
|
-
await fs.writeFile(helm.files.admissionDeploymentYaml, dedent(admissionDeployTemplate(this.buildTimestamp)));
|
|
183
|
-
await fs.writeFile(helm.files.admissionServiceMonitorYaml, dedent(serviceMonitorTemplate("admission")));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (mutateWebhook) {
|
|
187
|
-
await fs.writeFile(helm.files.mutationWebhookYaml, createWebhookYaml(this, mutateWebhook));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (validateWebhook) {
|
|
191
|
-
await fs.writeFile(helm.files.validationWebhookYaml, createWebhookYaml(this, validateWebhook));
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const watchDeployment = getWatcher(this, this.hash, this.buildTimestamp);
|
|
195
|
-
if (watchDeployment) {
|
|
196
|
-
await fs.writeFile(helm.files.watcherDeploymentYaml, dedent(watcherDeployTemplate(this.buildTimestamp)));
|
|
197
|
-
await fs.writeFile(helm.files.watcherServiceMonitorYaml, dedent(serviceMonitorTemplate("watcher")));
|
|
198
|
-
}
|
|
199
|
-
} catch (err) {
|
|
200
|
-
console.error(`Error generating helm chart: ${err.message}`);
|
|
201
|
-
process.exit(1);
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
}
|
package/src/lib/assets/pods.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { KubernetesObject, V1EnvVar } from "@kubernetes/client-node";
|
|
|
5
5
|
import { kind } from "kubernetes-fluent-client";
|
|
6
6
|
import { gzipSync } from "zlib";
|
|
7
7
|
import { secretOverLimit } from "../helpers";
|
|
8
|
-
import { Assets } from "
|
|
9
|
-
import { ModuleConfig } from "../module";
|
|
8
|
+
import { Assets } from "./assets";
|
|
9
|
+
import { ModuleConfig } from "../core/module";
|
|
10
10
|
import { Binding } from "../types";
|
|
11
11
|
|
|
12
12
|
/** Generate the pepr-system namespace */
|
|
@@ -9,68 +9,47 @@ import {
|
|
|
9
9
|
import { kind } from "kubernetes-fluent-client";
|
|
10
10
|
import { concat, equals, uniqWith } from "ramda";
|
|
11
11
|
|
|
12
|
-
import { Assets } from "
|
|
12
|
+
import { Assets } from "./assets";
|
|
13
13
|
import { Event } from "../enums";
|
|
14
|
-
|
|
15
|
-
const peprIgnoreLabel: V1LabelSelectorRequirement = {
|
|
16
|
-
key: "pepr.dev",
|
|
17
|
-
operator: "NotIn",
|
|
18
|
-
values: ["ignore"],
|
|
19
|
-
};
|
|
14
|
+
import { Binding } from "../types";
|
|
20
15
|
|
|
21
16
|
const peprIgnoreNamespaces: string[] = ["kube-system", "pepr-system"];
|
|
22
17
|
|
|
18
|
+
const validateRule = (binding: Binding, isMutateWebhook: boolean): V1RuleWithOperations | undefined => {
|
|
19
|
+
const { event, kind, isMutate, isValidate } = binding;
|
|
20
|
+
|
|
21
|
+
// Skip invalid bindings based on webhook type
|
|
22
|
+
if ((isMutateWebhook && !isMutate) || (!isMutateWebhook && !isValidate)) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Translate event to operations
|
|
27
|
+
const operations = event === Event.CREATE_OR_UPDATE ? [Event.CREATE, Event.UPDATE] : [event];
|
|
28
|
+
|
|
29
|
+
// Use the plural property if it exists, otherwise use lowercase kind + s
|
|
30
|
+
const resource = kind.plural || `${kind.kind.toLowerCase()}s`;
|
|
31
|
+
|
|
32
|
+
const ruleObject: V1RuleWithOperations = {
|
|
33
|
+
apiGroups: [kind.group],
|
|
34
|
+
apiVersions: [kind.version || "*"],
|
|
35
|
+
operations,
|
|
36
|
+
resources: [resource, ...(resource === "pods" ? ["pods/ephemeralcontainers"] : [])],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return ruleObject;
|
|
40
|
+
};
|
|
41
|
+
|
|
23
42
|
export async function generateWebhookRules(assets: Assets, isMutateWebhook: boolean): Promise<V1RuleWithOperations[]> {
|
|
24
43
|
const { config, capabilities } = assets;
|
|
25
|
-
const rules: V1RuleWithOperations[] = [];
|
|
26
44
|
|
|
27
|
-
|
|
28
|
-
for (const capability of capabilities) {
|
|
45
|
+
const rules = capabilities.flatMap(capability => {
|
|
29
46
|
console.info(`Module ${config.uuid} has capability: ${capability.name}`);
|
|
30
47
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// If the module doesn't have a callback for the event, skip it
|
|
36
|
-
if (isMutateWebhook && !isMutate) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (!isMutateWebhook && !isValidate) {
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const operations: string[] = [];
|
|
45
|
-
|
|
46
|
-
// CreateOrUpdate is a Pepr-specific event that is translated to Create and Update
|
|
47
|
-
if (event === Event.CREATE_OR_UPDATE) {
|
|
48
|
-
operations.push(Event.CREATE, Event.UPDATE);
|
|
49
|
-
} else {
|
|
50
|
-
operations.push(event);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Use the plural property if it exists, otherwise use lowercase kind + s
|
|
54
|
-
const resource = kind.plural || `${kind.kind.toLowerCase()}s`;
|
|
55
|
-
|
|
56
|
-
const ruleObject = {
|
|
57
|
-
apiGroups: [kind.group],
|
|
58
|
-
apiVersions: [kind.version || "*"],
|
|
59
|
-
operations,
|
|
60
|
-
resources: [resource],
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// If the resource is pods, add ephemeralcontainers as well
|
|
64
|
-
if (resource === "pods") {
|
|
65
|
-
ruleObject.resources.push("pods/ephemeralcontainers");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Add the rule to the rules array
|
|
69
|
-
rules.push(ruleObject);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
48
|
+
return capability.bindings
|
|
49
|
+
.map(binding => validateRule(binding, isMutateWebhook))
|
|
50
|
+
.filter((rule): rule is V1RuleWithOperations => !!rule);
|
|
51
|
+
});
|
|
72
52
|
|
|
73
|
-
// Return the rules with duplicates removed
|
|
74
53
|
return uniqWith(equals, rules);
|
|
75
54
|
}
|
|
76
55
|
|
|
@@ -79,7 +58,7 @@ export async function webhookConfig(
|
|
|
79
58
|
mutateOrValidate: "mutate" | "validate",
|
|
80
59
|
timeoutSeconds = 10,
|
|
81
60
|
): Promise<kind.MutatingWebhookConfiguration | kind.ValidatingWebhookConfiguration | null> {
|
|
82
|
-
const ignore = [
|
|
61
|
+
const ignore: V1LabelSelectorRequirement[] = [];
|
|
83
62
|
|
|
84
63
|
const { name, tls, config, apiToken, host } = assets;
|
|
85
64
|
const ignoreNS = concat(peprIgnoreNamespaces, config?.alwaysIgnore?.namespaces || []);
|
|
@@ -135,9 +114,6 @@ export async function webhookConfig(
|
|
|
135
114
|
namespaceSelector: {
|
|
136
115
|
matchExpressions: ignore,
|
|
137
116
|
},
|
|
138
|
-
objectSelector: {
|
|
139
|
-
matchExpressions: ignore,
|
|
140
|
-
},
|
|
141
117
|
rules,
|
|
142
118
|
// @todo: track side effects state
|
|
143
119
|
sideEffects: "None",
|
package/src/lib/assets/yaml.ts
CHANGED
|
@@ -1,19 +1,41 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
dumpYaml,
|
|
6
|
+
V1Deployment,
|
|
7
|
+
V1MutatingWebhookConfiguration,
|
|
8
|
+
V1ValidatingWebhookConfiguration,
|
|
9
|
+
} from "@kubernetes/client-node";
|
|
6
10
|
import { promises as fs } from "fs";
|
|
7
|
-
import { Assets } from ".";
|
|
8
11
|
import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
|
|
9
|
-
import {
|
|
12
|
+
import { getModuleSecret, getNamespace } from "./pods";
|
|
10
13
|
import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
|
|
11
|
-
import { webhookConfig } from "./webhooks";
|
|
12
14
|
import { genEnv } from "./pods";
|
|
15
|
+
import { ModuleConfig } from "../core/module";
|
|
16
|
+
import { CapabilityExport } from "../types";
|
|
17
|
+
import { TLSOut } from "../tls";
|
|
18
|
+
|
|
19
|
+
type CommonOverrideValues = {
|
|
20
|
+
apiToken: string;
|
|
21
|
+
capabilities: CapabilityExport[];
|
|
22
|
+
config: ModuleConfig;
|
|
23
|
+
hash: string;
|
|
24
|
+
name: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type ChartOverrides = CommonOverrideValues & {
|
|
28
|
+
image: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type ResourceOverrides = CommonOverrideValues & {
|
|
32
|
+
path: string;
|
|
33
|
+
tls: TLSOut;
|
|
34
|
+
};
|
|
13
35
|
|
|
14
36
|
// Helm Chart overrides file (values.yaml) generated from assets
|
|
15
37
|
export async function overridesFile(
|
|
16
|
-
{ hash, name, image, config, apiToken, capabilities }:
|
|
38
|
+
{ hash, name, image, config, apiToken, capabilities }: ChartOverrides,
|
|
17
39
|
path: string,
|
|
18
40
|
): Promise<void> {
|
|
19
41
|
const rbacOverrides = clusterRole(name, capabilities, config.rbacMode, config.rbac).rules;
|
|
@@ -169,7 +191,7 @@ export async function overridesFile(
|
|
|
169
191
|
|
|
170
192
|
await fs.writeFile(path, dumpYaml(overrides, { noRefs: true, forceQuotes: true }));
|
|
171
193
|
}
|
|
172
|
-
export function
|
|
194
|
+
export function generateZarfYaml(name: string, image: string, config: ModuleConfig, path: string): string {
|
|
173
195
|
const zarfCfg = {
|
|
174
196
|
kind: "ZarfPackageConfig",
|
|
175
197
|
metadata: {
|
|
@@ -197,7 +219,7 @@ export function zarfYaml({ name, image, config }: Assets, path: string): string
|
|
|
197
219
|
return dumpYaml(zarfCfg, { noRefs: true });
|
|
198
220
|
}
|
|
199
221
|
|
|
200
|
-
export function
|
|
222
|
+
export function generateZarfYamlChart(name: string, image: string, config: ModuleConfig, path: string): string {
|
|
201
223
|
const zarfCfg = {
|
|
202
224
|
kind: "ZarfPackageConfig",
|
|
203
225
|
metadata: {
|
|
@@ -226,16 +248,16 @@ export function zarfYamlChart({ name, image, config }: Assets, path: string): st
|
|
|
226
248
|
return dumpYaml(zarfCfg, { noRefs: true });
|
|
227
249
|
}
|
|
228
250
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const code = await fs.readFile(path);
|
|
232
|
-
|
|
233
|
-
// Generate a hash of the code
|
|
234
|
-
assets.hash = crypto.createHash("sha256").update(code).digest("hex");
|
|
251
|
+
type webhooks = { validate: V1ValidatingWebhookConfiguration | null; mutate: V1MutatingWebhookConfiguration | null };
|
|
252
|
+
type deployments = { default: V1Deployment; watch: V1Deployment | null };
|
|
235
253
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
254
|
+
export async function generateAllYaml(
|
|
255
|
+
webhooks: webhooks,
|
|
256
|
+
deployments: deployments,
|
|
257
|
+
assets: ResourceOverrides,
|
|
258
|
+
): Promise<string> {
|
|
259
|
+
const { name, tls, hash, apiToken, path, config } = assets;
|
|
260
|
+
const code = await fs.readFile(path);
|
|
239
261
|
|
|
240
262
|
const resources = [
|
|
241
263
|
getNamespace(assets.config.customLabels?.namespace),
|
|
@@ -244,24 +266,24 @@ export async function allYaml(assets: Assets, imagePullSecret?: string): Promise
|
|
|
244
266
|
serviceAccount(name),
|
|
245
267
|
apiTokenSecret(name, apiToken),
|
|
246
268
|
tlsSecret(name, tls),
|
|
247
|
-
|
|
269
|
+
deployments.default,
|
|
248
270
|
service(name),
|
|
249
271
|
watcherService(name),
|
|
250
|
-
getModuleSecret(name, code,
|
|
272
|
+
getModuleSecret(name, code, hash),
|
|
251
273
|
storeRole(name),
|
|
252
274
|
storeRoleBinding(name),
|
|
253
275
|
];
|
|
254
276
|
|
|
255
|
-
if (
|
|
256
|
-
resources.push(
|
|
277
|
+
if (webhooks.mutate) {
|
|
278
|
+
resources.push(webhooks.mutate);
|
|
257
279
|
}
|
|
258
280
|
|
|
259
|
-
if (
|
|
260
|
-
resources.push(
|
|
281
|
+
if (webhooks.validate) {
|
|
282
|
+
resources.push(webhooks.validate);
|
|
261
283
|
}
|
|
262
284
|
|
|
263
|
-
if (
|
|
264
|
-
resources.push(
|
|
285
|
+
if (deployments.watch) {
|
|
286
|
+
resources.push(deployments.watch);
|
|
265
287
|
}
|
|
266
288
|
|
|
267
289
|
// Convert the resources to a single YAML string
|