Skip to content

Commit

Permalink
feat: additionalIgnoredNamespaces at runtime through helm (#1641)
Browse files Browse the repository at this point in the history
## Description

This feature:
- [x] Adds additionalIgnoredNamespaces to values.yaml
- [x] Sets additionalIgnoredNamespaces in controller container envs as
`PEPR_ADDITIONAL_IGNORED_NAMESPACES`
- [x] reads `PEPR_ADDITIONAL_IGNORED_NAMESPACES` env and adds namespaces
to ignoredNamespaces
- [x] Does **not** document PEPR_ADDITIONAL_IGNORED_NAMESPACES as
feature is meant to be set through helm chart and not by hand, By hand
you should set them in `package.json`

In action:
`package.json`

```json
    "alwaysIgnore": {
      "namespaces": ["something"]
    },
```


`values.yaml`
```yaml
additionalIgnoredNamespaces: 
  - 'kube-system'
  - 'kube-public'
  - 'kube-node-lease'
  - 'default'
  - 'pepr'
  - 'pepr-system'
  - 'pepr-test-module'
```

`> helm template .`

`controllers`
```yaml
            - name: PEPR__ADDITIONAL_IGNORED_NAMESPACES
              value: "kube-system, kube-public, kube-node-lease, default, pepr, pepr-system, pepr-test-module"
```

`webhook configs`

```yaml
    namespaceSelector:
      matchExpressions:
        - key: kubernetes.io/metadata.name
          operator: NotIn
          values:
            - kube-system
            - pepr-system
            - kube-system
            - kube-public
            - kube-node-lease
            - default
            - pepr
            - pepr-system
            - pepr-test-module
            - something
```

## Related Issue

Fixes #1610 #1617 
<!-- or -->
Relates to #

## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Other (security config, docs update, etc)

## Checklist before merging
- [x] Unit,
[Journey](https://github.com/defenseunicorns/pepr/tree/main/journey),
[E2E Tests](https://github.com/defenseunicorns/pepr-excellent-examples),
[docs](https://github.com/defenseunicorns/pepr/tree/main/docs),
[adr](https://github.com/defenseunicorns/pepr/tree/main/adr) added or
updated as needed
- [x] [Contributor Guide
Steps](https://docs.pepr.dev/main/contribute/#submitting-a-pull-request)
followed

---------

Signed-off-by: Case Wylie <[email protected]>
  • Loading branch information
cmwylie19 authored Jan 15, 2025
1 parent e51d093 commit 4e45cf5
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 14 deletions.
1 change: 1 addition & 0 deletions docs/030_user-guide/120_customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Below are the available Helm override configurations after you have built your P

| Parameter | Description | Example Values |
|---------------------------------|-------------------------------------------|------------------------------------------------|
| `additionalIgnoredNamespaces` | Namespaces to ignore in addition to alwaysIgnore.namespaces from Pepr config in `package.json`. | `- pepr-playground` |
| `secrets.apiToken` | Kube API-Server Token. | `Buffer.from(apiToken).toString("base64")` |
| `hash` | Unique hash for deployment. Do not change.| `<your_hash>` |
| `namespace.annotations` | Namespace annotations | `{}` |
Expand Down
8 changes: 8 additions & 0 deletions src/lib/assets/helm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ export function watcherDeployTemplate(buildTimestamp: string): string {
{{- toYaml .Values.watcher.env | nindent 12 }}
- name: PEPR_WATCH_MODE
value: "true"
{{- if .Values.additionalIgnoredNamespaces }}
- name: PEPR_ADDITIONAL_IGNORED_NAMESPACES
value: "{{ join ", " .Values.additionalIgnoredNamespaces }}"
{{- end }}
envFrom:
{{- toYaml .Values.watcher.envFrom | nindent 12 }}
securityContext:
Expand Down Expand Up @@ -195,6 +199,10 @@ export function admissionDeployTemplate(buildTimestamp: string): string {
{{- toYaml .Values.admission.env | nindent 12 }}
- name: PEPR_WATCH_MODE
value: "false"
{{- if .Values.additionalIgnoredNamespaces }}
- name: PEPR_ADDITIONAL_IGNORED_NAMESPACES
value: "{{ join ", " .Values.additionalIgnoredNamespaces }}"
{{- end }}
envFrom:
{{- toYaml .Values.admission.envFrom | nindent 12 }}
securityContext:
Expand Down
61 changes: 61 additions & 0 deletions src/lib/assets/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
import { it, describe, expect } from "@jest/globals";
import { createWebhookYaml } from "./index";
import { kind } from "kubernetes-fluent-client";

describe("createWebhookYaml", () => {
const webhookConfiguration = new kind.MutatingWebhookConfiguration();
webhookConfiguration.apiVersion = "admissionregistration.k8s.io/v1";
webhookConfiguration.kind = "MutatingWebhookConfiguration";
webhookConfiguration.metadata = { name: "pepr-static-test" };
webhookConfiguration.webhooks = [
{
name: "pepr-static-test.pepr.dev",
admissionReviewVersions: ["v1", "v1beta1"],
clientConfig: {
caBundle: "",
service: {
name: "pepr-static-test",
namespace: "pepr-system",
path: "",
},
},
failurePolicy: "Fail",
matchPolicy: "Equivalent",
timeoutSeconds: 15,
namespaceSelector: {
matchExpressions: [
{
key: "kubernetes.io/metadata.name",
operator: "NotIn",
values: ["kube-system", "pepr-system", "something"],
},
],
},
sideEffects: "None",
},
];

const moduleConfig = {
onError: "reject",
webhookTimeout: 15,
uuid: "some-uuid",
alwaysIgnore: {
namespaces: ["kube-system", "pepr-system"],
},
};

it("replaces placeholders in the YAML correctly", () => {
const result = createWebhookYaml("pepr-static-test", moduleConfig, webhookConfiguration);
console.log(result);
expect(result).toContain("{{ .Values.uuid }}");
expect(result).toContain("{{ .Values.admission.failurePolicy }}");
expect(result).toContain("{{ .Values.admission.webhookTimeout }}");
expect(result).toContain("- pepr-system");
expect(result).toContain("- kube-system");
expect(result).toContain("{{- range .Values.additionalIgnoredNamespaces }}");
expect(result).toContain("{{ . }}");
expect(result).toContain("{{- end }}");
});
});
42 changes: 33 additions & 9 deletions src/lib/assets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,45 @@ export function toYaml(obj: any): string {
return dumpYaml(obj, { noRefs: true });
}

// Unit Test Me!!
export function createWebhookYaml(
name: string,
config: ModuleConfig,
webhookConfiguration: kind.MutatingWebhookConfiguration | kind.ValidatingWebhookConfiguration,
): string {
const yaml = toYaml(webhookConfiguration);
return replaceString(
replaceString(
replaceString(yaml, name, "{{ .Values.uuid }}"),
config.onError === "reject" ? "Fail" : "Ignore",
"{{ .Values.admission.failurePolicy }}",
),
`${config.webhookTimeout}` || "10",
"{{ .Values.admission.webhookTimeout }}",
);
const replacements = [
{ search: name, replace: "{{ .Values.uuid }}" },
{
search: config.onError === "reject" ? "Fail" : "Ignore",
replace: "{{ .Values.admission.failurePolicy }}",
},
{
search: `${config.webhookTimeout}` || "10",
replace: "{{ .Values.admission.webhookTimeout }}",
},
{
search: `
- key: kubernetes.io/metadata.name
operator: NotIn
values:
- kube-system
- pepr-system
`,
replace: `
- key: kubernetes.io/metadata.name
operator: NotIn
values:
- kube-system
- pepr-system
{{- range .Values.additionalIgnoredNamespaces }}
- {{ . }}
{{- end }}
`,
},
];

return replacements.reduce((updatedYaml, { search, replace }) => replaceString(updatedYaml, search, replace), yaml);
}

export function helmLayout(basePath: string, unique: string): Record<string, Record<string, string>> {
Expand Down
30 changes: 30 additions & 0 deletions src/lib/assets/webhooks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
import { it, describe, expect } from "@jest/globals";
import { resolveIgnoreNamespaces, peprIgnoreNamespaces } from "./webhooks";

describe("peprIgnoreNamespaces", () => {
it("should have order of kube-system, then pepr-system for the helm templating", () => {
expect(peprIgnoreNamespaces).toEqual(["kube-system", "pepr-system"]);
expect(peprIgnoreNamespaces[0]).toEqual("kube-system");
expect(peprIgnoreNamespaces[1]).toEqual("pepr-system");
});
});

describe("resolveIgnoreNamespaces", () => {
it("should default to empty array ig config is empty", () => {
const result = resolveIgnoreNamespaces();
expect(result).toEqual([]);
});

it("should return the config ignore namespaces if not provided PEPR_ADDITIONAL_IGNORED_NAMESPACES is not provided", () => {
const result = resolveIgnoreNamespaces(["payments", "istio-system"]);
expect(result).toEqual(["payments", "istio-system"]);
});

it("should include additionalIgnoredNamespaces when PEPR_ADDITIONAL_IGNORED_NAMESPACES is provided", () => {
process.env.PEPR_ADDITIONAL_IGNORED_NAMESPACES = "uds, project-fox";
const result = resolveIgnoreNamespaces(["zarf", "lula"]);
expect(result).toEqual(["uds", "project-fox", "zarf", "lula"]);
});
});
19 changes: 17 additions & 2 deletions src/lib/assets/webhooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Assets } from "./assets";
import { Event } from "../enums";
import { Binding } from "../types";

const peprIgnoreNamespaces: string[] = ["kube-system", "pepr-system"];
export const peprIgnoreNamespaces: string[] = ["kube-system", "pepr-system"];

const validateRule = (binding: Binding, isMutateWebhook: boolean): V1RuleWithOperations | undefined => {
const { event, kind, isMutate, isValidate } = binding;
Expand All @@ -39,6 +39,21 @@ const validateRule = (binding: Binding, isMutateWebhook: boolean): V1RuleWithOpe
return ruleObject;
};

export function resolveIgnoreNamespaces(ignoredNSConfig: string[] = []): string[] {
const ignoredNSEnv = process.env.PEPR_ADDITIONAL_IGNORED_NAMESPACES;
if (!ignoredNSEnv) {
return ignoredNSConfig;
}

const namespaces = ignoredNSEnv.split(",").map(ns => ns.trim());

// add alwaysIgnore.namespaces to the list
if (ignoredNSConfig) {
namespaces.push(...ignoredNSConfig);
}
return namespaces.filter(ns => ns.length > 0);
}

export async function generateWebhookRules(assets: Assets, isMutateWebhook: boolean): Promise<V1RuleWithOperations[]> {
const { config, capabilities } = assets;

Expand All @@ -61,7 +76,7 @@ export async function webhookConfig(
const ignore: V1LabelSelectorRequirement[] = [];

const { name, tls, config, apiToken, host } = assets;
const ignoreNS = concat(peprIgnoreNamespaces, config?.alwaysIgnore?.namespaces || []);
const ignoreNS = concat(peprIgnoreNamespaces, resolveIgnoreNamespaces(config?.alwaysIgnore?.namespaces));

// Add any namespaces to ignore
if (ignoreNS) {
Expand Down
1 change: 1 addition & 0 deletions src/lib/assets/yaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export async function overridesFile(
const rbacOverrides = clusterRole(name, capabilities, config.rbacMode, config.rbac).rules;

const overrides = {
additionalIgnoredNamespaces: [],
rbac: rbacOverrides,
secrets: {
apiToken: Buffer.from(apiToken).toString("base64"),
Expand Down
3 changes: 2 additions & 1 deletion src/lib/core/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { CapabilityExport, AdmissionRequest } from "../types";
import { setupWatch } from "../processors/watch-processor";
import { Log } from "../../lib";
import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
import { resolveIgnoreNamespaces } from "../assets/webhooks";

/** Custom Labels Type for package.json */
export interface CustomLabels {
Expand Down Expand Up @@ -113,7 +114,7 @@ export class PeprModule {
// Wait for the controller to be ready before setting up watches
if (isWatchMode() || isDevMode()) {
try {
setupWatch(capabilities, pepr?.alwaysIgnore?.namespaces);
setupWatch(capabilities, resolveIgnoreNamespaces(pepr?.alwaysIgnore?.namespaces));
} catch (e) {
Log.error(e, "Error setting up watch");
process.exit(1);
Expand Down
3 changes: 2 additions & 1 deletion src/lib/processors/mutate-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ModuleConfig } from "../core/module";
import { PeprMutateRequest } from "../mutate-request";
import { base64Encode, convertFromBase64Map, convertToBase64Map } from "../utils";
import { OnError } from "../../cli/init/enums";
import { resolveIgnoreNamespaces } from "../assets/webhooks";

export interface Bindable {
req: AdmissionRequest;
Expand Down Expand Up @@ -169,7 +170,7 @@ export async function mutateProcessor(
bind.binding,
bind.req,
bind.namespaces,
bind.config?.alwaysIgnore?.namespaces,
resolveIgnoreNamespaces(bind.config?.alwaysIgnore?.namespaces),
);
if (shouldSkip !== "") {
Log.debug(shouldSkip);
Expand Down
8 changes: 7 additions & 1 deletion src/lib/processors/validate-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Log from "../telemetry/logger";
import { convertFromBase64Map } from "../utils";
import { PeprValidateRequest } from "../validate-request";
import { ModuleConfig } from "../core/module";
import { resolveIgnoreNamespaces } from "../assets/webhooks";

export async function processRequest(
binding: Binding,
Expand Down Expand Up @@ -78,7 +79,12 @@ export async function validateProcessor(
}

// Continue to the next action without doing anything if this one should be skipped
const shouldSkip = shouldSkipRequest(binding, req, namespaces, config?.alwaysIgnore?.namespaces);
const shouldSkip = shouldSkipRequest(
binding,
req,
namespaces,
resolveIgnoreNamespaces(config?.alwaysIgnore?.namespaces),
);
if (shouldSkip !== "") {
Log.debug(shouldSkip);
continue;
Expand Down

0 comments on commit 4e45cf5

Please sign in to comment.