Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: expose rbacMode from moduleConfig #1347

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
5 changes: 3 additions & 2 deletions docs/030_user-guide/120_customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,11 @@ Below are the available configurations through `package.json`.
| `onError` | Behavior of the webhook failure policy | `reject`, `ignore` |
| `webhookTimeout` | Webhook timeout in seconds | `1` - `30` |
| `customLabels` | Custom labels for namespaces | `{namespace: {}}` |
| `alwaysIgnore` | Conditions to always ignore | `{namespaces: []}` |
| `alwaysIgnore` | Conditions to always ignore | `{namespaces: []}` |
| `includedFiles` | For working with WebAssembly | ["main.wasm", "wasm_exec.js"] |
| `env` | Environment variables for the container| `{LOG_LEVEL: "warn"}` |
| `rbac` | Custom RBAC rules | `{"rbac": [{"apiGroups": ["<apiGroups>"], "resources": ["<resources>"], "verbs": ["<verbs>"]}]}` |
| `rbac` | Custom RBAC rules (requires building with `rbacMode: scoped`) | `{"rbac": [{"apiGroups": ["<apiGroups>"], "resources": ["<resources>"], "verbs": ["<verbs>"]}]}` |
| `rbacMode` | Configures module to build binding RBAC with principal of least privilege | `scoped`, `admin` |

These tables provide a comprehensive overview of the fields available for customization within the Helm overrides and the `package.json` file. Modify these according to your deployment requirements.

Expand Down
29 changes: 24 additions & 5 deletions journey/entrypoint-wasm.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors

import { describe, jest } from "@jest/globals";

import { beforeAll, describe, jest } from "@jest/globals";
import { promises as fs } from "fs";
import { peprBuild } from "./pepr-build-wasm";

import { resolve } from "path";
import { cwd } from "./entrypoint.test";
import { execSync } from "child_process";

// Unmock unit test things
jest.deepUnmock("pino");


// Allow 5 minutes for the tests to run
jest.setTimeout(1000 * 60 * 5);
export const outputDir = "dist/pepr-test-module/child/folder";
beforeAll(async () => {
const dir = resolve(cwd);
await fs.mkdir(outputDir, { recursive: true });
await addScopedRbacMode();
});
describe(
"Journey: `npx pepr build -r gchr.io/defenseunicorns -o dist/pepr-test-module/child/folder`",
peprBuild,
);

describe("Journey: `npx pepr build -r gchr.io/defenseunicorns --rbac-mode scoped -o dist/pepr-test-module/child/folder`", peprBuild);
// Set rbacMode in the Pepr Module Config and write it back to disk
async function addScopedRbacMode() {
const dir = execSync("ls -la", { cwd, stdio: "inherit" }).toString().trim();
console.log("DIR", dir);
const packageJson = await fs.readFile(resolve(cwd, "package.json"), "utf8");
const packageJsonObj = JSON.parse(packageJson);
packageJsonObj.pepr.rbacMode = "scoped";
await fs.writeFile(resolve(cwd, "package.json"), JSON.stringify(packageJsonObj, null, 2));
}
51 changes: 29 additions & 22 deletions journey/pepr-build-wasm.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the relationship between rbacMode and pepr-build-wasm.ts..? I would have never thought to look for our RBAC mode tests in a -wasm file. 🤔 Are they bound together in some way?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't (apparently) comment on lines of code that weren't changed, but... just saw that line 15 says "dst folder" -- should probably say something like "/dist folder" instead (since that's what'll actually be there).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason the rbac tests were in the -wasm file when I started working on the rbac mode stuff. I have no idea why they were put there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line 15 only makes a folder. This probably should be live in an it() block. I will move this below the where the module is being built.

rbacMode is being set through the CLI in pepr-build.ts, we need a module to build so this is the other option.

Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,17 @@
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors

import { expect, it } from "@jest/globals";
import { loadYaml } from "@kubernetes/client-node";
import { loadYaml, V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
import { execSync } from "child_process";
import { promises as fs } from "fs";
import { resolve } from "path";

import yaml from "js-yaml";
import { cwd } from "./entrypoint.test";
import { outputDir } from "./entrypoint-wasm.test";

// test npx pepr build -o dst
const outputDir = "dist/pepr-test-module/child/folder";
export function peprBuild() {
it("should build artifacts in the dst folder", async () => {
await fs.mkdir(outputDir, { recursive: true });
});

it("should successfully build the Pepr project with arguments", async () => {
execSync(`npx pepr build -r gchr.io/defenseunicorns --rbac-mode scoped -o ${outputDir}`, {
it("should successfully build the Pepr project with arguments and rbacMode scoped", async () => {
execSync(`npx pepr build -r gchr.io/defenseunicorns -o ${outputDir}`, {
cwd: cwd,
stdio: "inherit",
});
Expand All @@ -33,21 +28,11 @@ export function peprBuild() {
});

it("should generate a scoped ClusterRole", async () => {
await validateClusterRoleYaml();
const validateHelmChart = true;
await validateClusterRoleYaml(validateHelmChart);
});
}

async function validateClusterRoleYaml() {
// Read the generated yaml files
const k8sYaml = await fs.readFile(
resolve(cwd, outputDir, "pepr-module-static-test.yaml"),
"utf8",
);
const cr = await fs.readFile(resolve("journey", "resources", "clusterrole.yaml"), "utf8");

expect(k8sYaml.includes(cr)).toEqual(true);
}

async function validateZarfYaml() {
// Get the version of the pepr binary
const peprVer = execSync("npx pepr --version", { cwd }).toString().trim();
Expand Down Expand Up @@ -91,3 +76,25 @@ async function validateZarfYaml() {
const actualZarfYaml = loadYaml(zarfYAML);
expect(actualZarfYaml).toEqual(expectedZarfYaml);
}

async function validateClusterRoleYaml(validateChart: boolean = false) {
// Read the generated yaml files
const k8sYaml = await fs.readFile(
resolve(cwd, outputDir, "pepr-module-static-test.yaml"),
"utf8",
);
const cr = await fs.readFile(resolve("journey", "resources", "clusterrole.yaml"), "utf8");
expect(k8sYaml.includes(cr)).toEqual(true);

if (validateChart) {
const yamlChartRBAC = await fs.readFile(resolve("journey", "resources", "values.yaml"), "utf8");
const expectedYamlChartRBAC = await fs.readFile(
resolve("journey", "resources", "values.yaml"),
"utf8",
);
const jsonChartRBAC = yaml.load(yamlChartRBAC) as Record<string, PolicyRule[]>;
const expectedJsonChartRBAC = yaml.load(expectedYamlChartRBAC) as Record<string, PolicyRule[]>;

expect(JSON.stringify(jsonChartRBAC)).toEqual(JSON.stringify(expectedJsonChartRBAC));
}
}
29 changes: 29 additions & 0 deletions journey/resources/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
rbac:
- apiGroups:
- 'pepr.dev'
resources:
- 'peprstores'
verbs:
- 'create'
- 'get'
- 'patch'
- 'watch'
- apiGroups:
- 'apiextensions.k8s.io'
resources:
- 'customresourcedefinitions'
verbs:
- 'patch'
- 'create'
- apiGroups:
- ''
resources:
- 'namespaces'
verbs:
- 'watch'
- apiGroups:
- ''
resources:
- 'configmaps'
verbs:
- 'watch'
11 changes: 6 additions & 5 deletions src/cli/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import { Option } from "commander";
import { createDirectoryIfNotExists, validateCapabilityNames, parseTimeout } from "../lib/helpers";
import { sanitizeResourceName } from "../sdk/sdk";

import { determineRbacMode } from "../lib/cli-helpers/build";
const peprTS = "pepr.ts";
let outputDir: string = "dist";
export type Reloader = (opts: BuildResult<BuildOptions>) => void | Promise<void>;
Expand Down Expand Up @@ -66,11 +66,11 @@
.default("manifest"),
)
.addOption(
new Option("--rbac-mode [admin|scoped]", "Rbac Mode: admin, scoped (default: admin)")
.choices(["admin", "scoped"])
.default("admin"),
new Option("--rbac-mode [admin|scoped]", "Rbac Mode: admin, scoped (default: admin)").choices(
["admin", "scoped"],
),
)
.action(async opts => {

Check warning on line 73 in src/cli/build.ts

View workflow job for this annotation

GitHub Actions / format

Async arrow function has too many statements (52). Maximum allowed is 20

Check warning on line 73 in src/cli/build.ts

View workflow job for this annotation

GitHub Actions / format

Async arrow function has a complexity of 15. Maximum allowed is 10
// assign custom output directory if provided
if (opts.outputDir) {
outputDir = opts.outputDir;
Expand Down Expand Up @@ -133,7 +133,8 @@
...cfg.pepr,
appVersion: cfg.version,
description: cfg.description,
rbacMode: opts.rbacMode,
// Can override the rbacMode with the CLI option
rbacMode: determineRbacMode(opts, cfg),
},
path,
);
Expand Down Expand Up @@ -242,7 +243,7 @@
};
}

export async function buildModule(reloader?: Reloader, entryPoint = peprTS, embed = true) {

Check warning on line 246 in src/cli/build.ts

View workflow job for this annotation

GitHub Actions / format

Async function 'buildModule' has too many statements (36). Maximum allowed is 20
try {
const { cfg, modulePath, path, uuid } = await loadModule(entryPoint);

Expand Down Expand Up @@ -348,7 +349,7 @@
const conflicts = [...out.matchAll(pgkErrMatch)];

// If the regex didn't match, leave a generic error
if (conflicts.length < 1) {

Check warning on line 352 in src/cli/build.ts

View workflow job for this annotation

GitHub Actions / format

Blocks are nested too deeply (4). Maximum allowed is 3
console.info(
`\n\tOne or more imported Pepr Capabilities seem to be using an incompatible version of Pepr.\n\tTry updating your Pepr Capabilities to their latest versions.`,
"Version Conflict",
Expand Down
36 changes: 36 additions & 0 deletions src/lib/cli-helpers/build.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors

import { determineRbacMode } from "./build";

import { expect, describe, test } from "@jest/globals";

describe("determineRbacMode", () => {
test("should allow CLI options to overwrite module config", () => {
const opts = { rbacMode: "admin" };
const cfg = { pepr: { rbacMode: "scoped" } };
const result = determineRbacMode(opts, cfg);
expect(result).toBe("admin");
});

test('should return "admin" when cfg.pepr.rbacMode is provided and not "scoped"', () => {
const opts = {};
const cfg = { pepr: { rbacMode: "admin" } };
const result = determineRbacMode(opts, cfg);
expect(result).toBe("admin");
});

test('should return "scoped" when cfg.pepr.rbacMode is "scoped"', () => {
const opts = {};
const cfg = { pepr: { rbacMode: "scoped" } };
const result = determineRbacMode(opts, cfg);
expect(result).toBe("scoped");
});

test("should default to admin when neither option is provided", () => {
const opts = {};
const cfg = { pepr: {} };
const result = determineRbacMode(opts, cfg);
expect(result).toBe("admin");
});
});
15 changes: 15 additions & 0 deletions src/lib/cli-helpers/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// determineRbacMode determines the RBAC mode to use based on the cli and the module's config
export function determineRbacMode(opts: { rbacMode?: string }, cfg: { pepr: { rbacMode?: string } }): string {
// CLI overrides the module's config
if (opts.rbacMode) {
return opts.rbacMode;
}

// if rbacMode is defined and not scoped, return admin
if (cfg.pepr.rbacMode && cfg.pepr.rbacMode !== "scoped") {
return "admin";
}

// if nothing is defined return admin, else return scoped
return cfg.pepr.rbacMode || "admin";
}
Loading