From c8b910da365bc5f07cf57c48f2222a891810c141 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Tue, 17 Oct 2023 18:12:32 -0400 Subject: [PATCH 01/21] feat: least priv RBAC creation Signed-off-by: Case Wylie --- src/lib/assets/deploy.ts | 7 +- src/lib/assets/rbac.ts | 39 ++++-- src/lib/assets/yaml.ts | 2 +- src/lib/helpers.test.ts | 274 +++++++++++++++++++++++++++++++++++++++ src/lib/helpers.ts | 45 +++++++ 5 files changed, 355 insertions(+), 12 deletions(-) create mode 100644 src/lib/helpers.test.ts create mode 100644 src/lib/helpers.ts diff --git a/src/lib/assets/deploy.ts b/src/lib/assets/deploy.ts index 208d07c78..23c16bac5 100644 --- a/src/lib/assets/deploy.ts +++ b/src/lib/assets/deploy.ts @@ -12,6 +12,7 @@ import { deployment, moduleSecret, namespace, watcher } from "./pods"; import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac"; import { peprStoreCRD } from "./store"; import { webhookConfig } from "./webhooks"; +import { CapabilityExport } from "../types"; export async function deploy(assets: Assets, webhookTimeout?: number) { Log.info("Establishing connection to Kubernetes"); @@ -56,18 +57,18 @@ export async function deploy(assets: Assets, webhookTimeout?: number) { throw new Error("No code provided"); } - await setupRBAC(name); + await setupRBAC(name, assets.capabilities); await setupController(assets, code, hash); await setupWatcher(assets, hash); } -async function setupRBAC(name: string) { +async function setupRBAC(name: string, capabilities: CapabilityExport[]) { Log.info("Applying cluster role binding"); const crb = clusterRoleBinding(name); await K8s(kind.ClusterRoleBinding).Apply(crb); Log.info("Applying cluster role"); - const cr = clusterRole(name); + const cr = clusterRole(name, capabilities); await K8s(kind.ClusterRole).Apply(cr); Log.info("Applying service account"); diff --git a/src/lib/assets/rbac.ts b/src/lib/assets/rbac.ts index 1c67dab52..7a9181721 100644 --- a/src/lib/assets/rbac.ts +++ b/src/lib/assets/rbac.ts @@ -2,25 +2,48 @@ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors import { kind } from "kubernetes-fluent-client"; - +import { CapabilityExport } from "../types"; +import { createRBACMap } from "../helpers"; /** * Grants the controller access to cluster resources beyond the mutating webhook. * * @todo: should dynamically generate this based on resources used by the module. will also need to explore how this should work for multiple modules. * @returns */ -export function clusterRole(name: string): kind.ClusterRole { +export function clusterRole(name: string, capabilities: CapabilityExport[]): kind.ClusterRole { + console.log(`Let's give this SA the least privileges possible.\n${JSON.stringify(capabilities, null, 2)}`); + const rbacMap = createRBACMap(capabilities); return { apiVersion: "rbac.authorization.k8s.io/v1", kind: "ClusterRole", metadata: { name }, rules: [ - { - // @todo: make this configurable - apiGroups: ["*"], - resources: ["*"], - verbs: ["create", "delete", "get", "list", "patch", "update", "watch"], - }, + ...Object.keys(rbacMap).map(key => { + // let group:string, version:string, kind:string; + let group: string; + + if (key.split("/").length < 3) { + group = ""; + // version = key.split("/")[0] + // kind = key.split("/")[1] + } else { + group = key.split("/")[0]; + // version = key.split("/")[1] + // kind = key.split("/")[2] + } + + return { + apiGroups: [group], + resources: [rbacMap[key].plural], + verbs: rbacMap[key].verbs, + }; + }), + // { + // // @todo: make this configurable + // apiGroups: ["*"], + // resources: ["*"], + // verbs: ["create", "delete", "get", "list", "patch", "update", "watch"], + // }, ], }; } diff --git a/src/lib/assets/yaml.ts b/src/lib/assets/yaml.ts index 029671d2e..ef42fdf26 100644 --- a/src/lib/assets/yaml.ts +++ b/src/lib/assets/yaml.ts @@ -54,7 +54,7 @@ export async function allYaml(assets: Assets) { const resources = [ namespace, - clusterRole(name), + clusterRole(name, assets.capabilities), clusterRoleBinding(name), serviceAccount(name), apiTokenSecret(name, apiToken), diff --git a/src/lib/helpers.test.ts b/src/lib/helpers.test.ts new file mode 100644 index 000000000..e00d003dd --- /dev/null +++ b/src/lib/helpers.test.ts @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023-Present The Pepr Authors + +import { CapabilityExport } from "./types"; +import { createRBACMap } from "./helpers"; +import { expect, describe, test } from "@jest/globals"; + +const capabilities: CapabilityExport[] = JSON.parse(`[ + { + "name": "hello-pepr", + "description": "A simple example capability to show how things work.", + "namespaces": [ + "pepr-demo", + "pepr-demo-2" + ], + "bindings": [ + { + "kind": { + "kind": "Namespace", + "version": "v1", + "group": "" + }, + "event": "CREATE", + "filters": { + "name": "", + "namespaces": [], + "labels": {}, + "annotations": {} + }, + "isMutate": true + }, + { + "kind": { + "kind": "Namespace", + "version": "v1", + "group": "" + }, + "event": "DELETE", + "filters": { + "name": "pepr-demo-2", + "namespaces": [], + "labels": {}, + "annotations": {} + }, + "isWatch": true + }, + { + "kind": { + "kind": "ConfigMap", + "version": "v1", + "group": "" + }, + "event": "CREATE", + "filters": { + "name": "example-1", + "namespaces": [], + "labels": {}, + "annotations": {} + }, + "isMutate": true + }, + { + "kind": { + "kind": "ConfigMap", + "version": "v1", + "group": "" + }, + "event": "UPDATE", + "filters": { + "name": "example-2", + "namespaces": [], + "labels": {}, + "annotations": {} + }, + "isMutate": true + }, + { + "kind": { + "kind": "ConfigMap", + "version": "v1", + "group": "" + }, + "event": "CREATE", + "filters": { + "name": "example-2", + "namespaces": [], + "labels": {}, + "annotations": {} + }, + "isValidate": true + }, + { + "kind": { + "kind": "ConfigMap", + "version": "v1", + "group": "" + }, + "event": "CREATE", + "filters": { + "name": "example-2", + "namespaces": [], + "labels": {}, + "annotations": {} + }, + "isWatch": true + }, + { + "kind": { + "kind": "ConfigMap", + "version": "v1", + "group": "" + }, + "event": "CREATE", + "filters": { + "name": "", + "namespaces": [], + "labels": {}, + "annotations": {} + }, + "isValidate": true + }, + { + "kind": { + "kind": "ConfigMap", + "version": "v1", + "group": "" + }, + "event": "CREATEORUPDATE", + "filters": { + "name": "", + "namespaces": [], + "labels": { + "change": "by-label" + }, + "annotations": {} + }, + "isMutate": true + }, + { + "kind": { + "kind": "ConfigMap", + "version": "v1", + "group": "" + }, + "event": "DELETE", + "filters": { + "name": "", + "namespaces": [], + "labels": { + "change": "by-label" + }, + "annotations": {} + }, + "isValidate": true + }, + { + "kind": { + "kind": "ConfigMap", + "version": "v1", + "group": "" + }, + "event": "CREATE", + "filters": { + "name": "example-4", + "namespaces": [], + "labels": {}, + "annotations": {} + }, + "isMutate": true + }, + { + "kind": { + "kind": "ConfigMap", + "version": "v1", + "group": "" + }, + "event": "CREATE", + "filters": { + "name": "example-4a", + "namespaces": [ + "pepr-demo-2" + ], + "labels": {}, + "annotations": {} + }, + "isMutate": true + }, + { + "kind": { + "kind": "ConfigMap", + "version": "v1", + "group": "" + }, + "event": "CREATE", + "filters": { + "name": "", + "namespaces": [], + "labels": { + "chuck-norris": "" + }, + "annotations": {} + }, + "isMutate": true + }, + { + "kind": { + "kind": "Secret", + "version": "v1", + "group": "" + }, + "event": "CREATE", + "filters": { + "name": "secret-1", + "namespaces": [], + "labels": {}, + "annotations": {} + }, + "isMutate": true + }, + { + "kind": { + "group": "pepr.dev", + "version": "v1", + "kind": "Unicorn" + }, + "event": "CREATE", + "filters": { + "name": "example-1", + "namespaces": [], + "labels": {}, + "annotations": {} + }, + "isMutate": true + }, + { + "kind": { + "group": "pepr.dev", + "version": "v1", + "kind": "Unicorn" + }, + "event": "CREATE", + "filters": { + "name": "example-2", + "namespaces": [], + "labels": {}, + "annotations": {} + }, + "isMutate": true + } + ] + } +]`); + +describe("createRBACMap", () => { + test("should return the correct RBACMap for given capabilities", () => { + const result = createRBACMap(capabilities); + console.log(result); + + const expected = { + "/v1/Namespace": { + verbs: ["get", "list", "create", "watch", "delete"], + plural: "namespaces", + }, + "/v1/ConfigMap": { + verbs: ["get", "list", "create", "update", "watch", "delete"], + plural: "configmaps", + }, + "/v1/Secret": { verbs: ["get", "list", "create"], plural: "secrets" }, + "pepr.dev/v1/Unicorn": { verbs: ["get", "list", "create"], plural: "unicorns" }, + }; + + expect(result).toEqual(expected); + }); +}); diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts new file mode 100644 index 000000000..4e15fb073 --- /dev/null +++ b/src/lib/helpers.ts @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023-Present The Pepr Authors + +import { CapabilityExport } from "./types"; + +type RBACMap = { + [key: string]: { + verbs: string[]; + plural: string; + }; +}; + +export const createRBACMap = (capabilities: CapabilityExport[]): RBACMap => { + return capabilities.reduce((acc: RBACMap, capability: CapabilityExport) => { + capability.bindings.forEach(binding => { + // Create a unique key for the rule + const key = `${binding.kind.group}/${binding.kind.version}/${binding.kind.kind}`; + + if (!acc[key]) { + acc[key] = { + verbs: ["get", "list"], + plural: binding.kind.plural || `${binding.kind.kind.toLowerCase()}s`, + }; + } + if (binding.isWatch === true) { + if (!acc[key].verbs.includes("watch")) { + acc[key].verbs.push("watch"); + } + } + if (binding.event === "CREATEORUPDATE") { + if (!acc[key].verbs.includes("create")) { + acc[key].verbs.push("create"); + } + if (!acc[key].verbs.includes("update")) { + acc[key].verbs.push("update"); + } + } else { + if (!acc[key].verbs.includes(binding.event.toLowerCase())) { + acc[key].verbs.push(binding.event.toLowerCase()); + } + } + }); + return acc; + }, {}); +}; From 5851a97e5db27903152b0bc38fe9c50cd3d16391 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Wed, 18 Oct 2023 09:50:29 -0400 Subject: [PATCH 02/21] chore: refactoring for efficiency Signed-off-by: Case Wylie --- src/lib/assets/rbac.ts | 11 +---------- src/lib/helpers.test.ts | 16 +++++++++++++++- src/lib/helpers.ts | 28 ++++++++++++++-------------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/lib/assets/rbac.ts b/src/lib/assets/rbac.ts index 7a9181721..230da10b2 100644 --- a/src/lib/assets/rbac.ts +++ b/src/lib/assets/rbac.ts @@ -21,16 +21,7 @@ export function clusterRole(name: string, capabilities: CapabilityExport[]): kin ...Object.keys(rbacMap).map(key => { // let group:string, version:string, kind:string; let group: string; - - if (key.split("/").length < 3) { - group = ""; - // version = key.split("/")[0] - // kind = key.split("/")[1] - } else { - group = key.split("/")[0]; - // version = key.split("/")[1] - // kind = key.split("/")[2] - } + key.split("/").length < 3 ? (group = "") : (group = key.split("/")[0]); return { apiGroups: [group], diff --git a/src/lib/helpers.test.ts b/src/lib/helpers.test.ts index e00d003dd..32e45cfc0 100644 --- a/src/lib/helpers.test.ts +++ b/src/lib/helpers.test.ts @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors import { CapabilityExport } from "./types"; -import { createRBACMap } from "./helpers"; +import { createRBACMap, addVerbIfNotExists } from "./helpers"; import { expect, describe, test } from "@jest/globals"; const capabilities: CapabilityExport[] = JSON.parse(`[ @@ -272,3 +272,17 @@ describe("createRBACMap", () => { expect(result).toEqual(expected); }); }); + +describe("addVerbIfNotExists", () => { + test("should add a verb if it does not exist in the array", () => { + const verbs = ["get", "list"]; + addVerbIfNotExists(verbs, "watch"); + expect(verbs).toEqual(["get", "list", "watch"]); + }); + + test("should not add a verb if it already exists in the array", () => { + const verbs = ["get", "list", "watch"]; + addVerbIfNotExists(verbs, "get"); + expect(verbs).toEqual(["get", "list", "watch"]); // The array remains unchanged + }); +}); diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index 4e15fb073..88da7154c 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -10,10 +10,15 @@ type RBACMap = { }; }; +export const addVerbIfNotExists = (verbs: string[], verb: string) => { + if (!verbs.includes(verb)) { + verbs.push(verb); + } +}; + export const createRBACMap = (capabilities: CapabilityExport[]): RBACMap => { return capabilities.reduce((acc: RBACMap, capability: CapabilityExport) => { capability.bindings.forEach(binding => { - // Create a unique key for the rule const key = `${binding.kind.group}/${binding.kind.version}/${binding.kind.kind}`; if (!acc[key]) { @@ -22,24 +27,19 @@ export const createRBACMap = (capabilities: CapabilityExport[]): RBACMap => { plural: binding.kind.plural || `${binding.kind.kind.toLowerCase()}s`, }; } - if (binding.isWatch === true) { - if (!acc[key].verbs.includes("watch")) { - acc[key].verbs.push("watch"); - } + + if (binding.isWatch) { + addVerbIfNotExists(acc[key].verbs, "watch"); } + if (binding.event === "CREATEORUPDATE") { - if (!acc[key].verbs.includes("create")) { - acc[key].verbs.push("create"); - } - if (!acc[key].verbs.includes("update")) { - acc[key].verbs.push("update"); - } + addVerbIfNotExists(acc[key].verbs, "create"); + addVerbIfNotExists(acc[key].verbs, "update"); } else { - if (!acc[key].verbs.includes(binding.event.toLowerCase())) { - acc[key].verbs.push(binding.event.toLowerCase()); - } + addVerbIfNotExists(acc[key].verbs, binding.event.toLowerCase()); } }); + return acc; }, {}); }; From d51579f3fbe324fe0cd845f1e93430a7f341e0cb Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Thu, 19 Oct 2023 19:18:02 -0400 Subject: [PATCH 03/21] docs: add updates to docs Signed-off-by: Case Wylie --- docs/cli.md | 2 ++ docs/rbac.md | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 docs/rbac.md diff --git a/docs/cli.md b/docs/cli.md index 439792e0a..a09e1526d 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -55,4 +55,6 @@ Create a [zarf.yaml](https://zarf.dev) and K8s manifest for the current module. **Options:** +- `-r, --registry-info [/]` - Registry Info: Image registry and username. Note: You must be signed into the registry +- `-rm, --rbac-mode [admin|scoped]` - Rbac Mode: admin, scoped (default: admin) - `-l, --log-level [level]` - Log level: debug, info, warn, error (default: "info") diff --git a/docs/rbac.md b/docs/rbac.md new file mode 100644 index 000000000..3af94fcf4 --- /dev/null +++ b/docs/rbac.md @@ -0,0 +1,21 @@ +# RBAC Modes + +During the build phase of Pepr (`npx pepr build --rbac-mode [admin|scoped]`), you have the option to specify the desired RBAC mode through specific flags. This allows you to fine-tune the level of access granted based on your requirements and preferences. + +## Modes + +**admin** + +```bash +npx pepr build --rbac-mode admin +``` + +**Description:** In this mode, the service account is given cluster-admin permissions, granting it full, unrestricted access across the entire cluster. This can be useful for administrative tasks where broad permissions are necessary. However, use this mode with caution, as it can pose security risks if misused. This is the default mode. + +**scoped** + +```bash +npx pepr build --rbac-mode scoped +``` + +**Description:** In the scoped mode, the principle of least privilege is applied. The service account is provided just enough permissions to perform its required tasks, and no more. This mode is recommended for most use cases as it limits potential attack vectors and aligns with best practices in security. _Should you initiate further interactions with the Kubernetes API involving resources, you'll need to ensure they are reflected in the ClusterRole._ From 2886a458de262c17258a7388989f3f615c240948 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Fri, 20 Oct 2023 11:29:55 -0400 Subject: [PATCH 04/21] chore: improve unit test & docs Signed-off-by: Case Wylie --- docs/rbac.md | 97 +++++++++++++++++++++++++++++++++++++++-- journey/pepr-build.ts | 44 +++++++++++++++++++ src/cli/build.ts | 7 ++- src/lib/assets/rbac.ts | 2 +- src/lib/helpers.test.ts | 17 +++----- src/lib/helpers.ts | 22 ++++------ 6 files changed, 158 insertions(+), 31 deletions(-) diff --git a/docs/rbac.md b/docs/rbac.md index 3af94fcf4..93f3d2a29 100644 --- a/docs/rbac.md +++ b/docs/rbac.md @@ -1,6 +1,6 @@ # RBAC Modes -During the build phase of Pepr (`npx pepr build --rbac-mode [admin|scoped]`), you have the option to specify the desired RBAC mode through specific flags. This allows you to fine-tune the level of access granted based on your requirements and preferences. +During the build phase of Pepr (`npx pepr build --rbac-mode [admin|scoped]`), you have the option to specify the desired RBAC mode through specific flags. This allows fine-tuning the level of access granted based on requirements and preferences. ## Modes @@ -10,7 +10,7 @@ During the build phase of Pepr (`npx pepr build --rbac-mode [admin|scoped]`), yo npx pepr build --rbac-mode admin ``` -**Description:** In this mode, the service account is given cluster-admin permissions, granting it full, unrestricted access across the entire cluster. This can be useful for administrative tasks where broad permissions are necessary. However, use this mode with caution, as it can pose security risks if misused. This is the default mode. +**Description:** The service account is given cluster-admin permissions, granting it full, unrestricted access across the entire cluster. This can be useful for administrative tasks where broad permissions are necessary. However, use this mode with caution, as it can pose security risks if misused. This is the default mode. **scoped** @@ -18,4 +18,95 @@ npx pepr build --rbac-mode admin npx pepr build --rbac-mode scoped ``` -**Description:** In the scoped mode, the principle of least privilege is applied. The service account is provided just enough permissions to perform its required tasks, and no more. This mode is recommended for most use cases as it limits potential attack vectors and aligns with best practices in security. _Should you initiate further interactions with the Kubernetes API involving resources, you'll need to ensure they are reflected in the ClusterRole._ +**Description:** The service account is provided just enough permissions to perform its required tasks, and no more. This mode is recommended for most use cases as it limits potential attack vectors and aligns with best practices in security. _The admission controller's primary mutating or validating action doesn't require a ClusterRole (as the request is not persisted or executed while passing through admission control), if you have a use case where the admission controller's logic involves reading other Kubernetes resources or taking additional actions beyond just validating, mutating, or watching the incoming request, appropriate RBAC settings should be reflected in the ClusterRole._ + +## Debugging RBAC Issues + +If encountering unexpected behaviors in Pepr while running in scoped mode, check to see if they are related to RBAC. + +1. Check Deployment logs for RBAC errors: + +```bash +kubectl logs -n pepr-system -l app | jq 'select(.data.reason == "Forbidden")' | grep -A9 -B9 "Forbidden" + +# example output +dden")' | grep -A9 -B9 "Forbidden" + "time": 1697814589833, + "pid": 16, + "hostname": "pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10-watcher-6f44f996pmhhb", + "data": { + "kind": "Status", + "apiVersion": "v1", + "metadata": {}, + "status": "Failure", + "message": "peprstores.pepr.dev \"pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10-store\" is forbidden: User \"system:serviceaccount:pepr-system:pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10\" cannot get resource \"peprstores\" in API group \"pepr.dev\" in the namespace \"pepr-system\": RBAC: clusterrole.rbac.authorization.k8s.io \"pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10\" not found", + "reason": "Forbidden", + "details": { + "name": "pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10-store", + "group": "pepr.dev", + "kind": "peprstores" + }, + "code": 403 + }, + "ok": false, + "status": 403, + "statusText": "Forbidden" +} +{ + "level": 50, + "time": 1697814589840, + "pid": 16, + "hostname": "pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10-watcher-6f44f996pmhhb", + "data": { + "kind": "Status", + "apiVersion": "v1", + "metadata": {}, + "status": "Failure", + "message": "peprstores.pepr.dev \"pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10-store\" is forbidden: User \"system:serviceaccount:pepr-system:pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10\" cannot patch resource \"peprstores\" in API group \"pepr.dev\" in the namespace \"pepr-system\": RBAC: clusterrole.rbac.authorization.k8s.io \"pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10\" not found", + "reason": "Forbidden", + "details": { + "name": "pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10-store", + "group": "pepr.dev", + "kind": "peprstores" + }, + "code": 403 + }, + "ok": false, + "status": 403, + "statusText": "Forbidden", + "msg": "Failed to create Pepr store" +} +``` + +2. Verify ServiceAccount Permissions with `kubectl auth can-i` + +```bash +SA=$(kubectl get deploy -n pepr-system -o=jsonpath='{range .items[0]}{.spec.template.spec.serviceAccountName}{"\n"}{end}') + +# Can i create configmaps as the service account? +kubectl auth can-i create cm --as=system:serviceaccount:pepr-system:$SA + +# example output: yes + +# Can i create configmaps as the service account in kube-system? +kubectl auth can-i create cm --as=system:serviceaccount:pepr-system:$SA -n kube-system + +# example output: no +``` + +3. Describe the ServiceAccount + +```bash +SA=$(kubectl get deploy -n pepr-system -o=jsonpath='{range .items[0]}{.spec.template.spec.serviceAccountName}{"\n"}{end}') + +kubectl describe clusterrole $SA + +# example output: +Name: pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10 +Labels: +Annotations: +PolicyRule: + Resources Non-Resource URLs Resource Names Verbs + --------- ----------------- -------------- ----- + *.* [] [] [create delete get list patch update watch] +``` diff --git a/journey/pepr-build.ts b/journey/pepr-build.ts index e37b107f0..d78266547 100644 --- a/journey/pepr-build.ts +++ b/journey/pepr-build.ts @@ -22,6 +22,50 @@ export function peprBuild() { await fs.access(resolve(cwd, "dist", "zarf.yaml")); await validateZarfYaml(); }); + + it('should generate correct ClusterRole yaml', async () => { + await validateClusterRoleYaml() + }); +} + +async function validateClusterRoleYaml() { + // Read the generated yaml files + const k8sYaml = await fs.readFile(resolve(cwd, "dist", "pepr-module-static-test.yaml"), "utf8"); + + // The expected ClusterRole + const expectedClusterRoleYaml = { + apiVersion: "rbac.authorization.k8s.io/v1", + kind: "ClusterRole", + metadata: { + name: "pepr-module-static-test", + }, + rules: [ + { + apiGroups: [""], + resources: ["namespaces"], + verbs: ["get", "list", "create","watch"], + }, + { + apiGroups: [""], + resources: ["configmaps"], + verbs: ["get", "list", "create","watch","update","delete"], + }, + { + apiGroups: [""], + resources: ["secrets"], + verbs: ["get", "list", "create"], + }, + { + apiGroups: ["pepr.dev"], + resources: ["unicorns"], + verbs: ["get", "list", "create"], + }, + ], + }; + + // Check the generated cluster role yaml + const actualClusterRoleYaml = loadYaml(k8sYaml); + expect(actualClusterRoleYaml).toEqual(expectedClusterRoleYaml); } async function validateZarfYaml() { diff --git a/src/cli/build.ts b/src/cli/build.ts index d396077a6..fffaf4aad 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -26,7 +26,12 @@ export default function (program: RootCmd) { ) .option( "-r, --registry-info [/]", - "Where to upload the image. Note: You must be signed into the registry", + "Registry Info: Image registry and username. Note: You must be signed into the registry", + ) + .option( + "-rm, --rbac-mode [admin|scoped]", + "Rbac Mode: admin, scoped (default: admin)", + ) .action(async opts => { // Build the module diff --git a/src/lib/assets/rbac.ts b/src/lib/assets/rbac.ts index 230da10b2..1ff5e9bec 100644 --- a/src/lib/assets/rbac.ts +++ b/src/lib/assets/rbac.ts @@ -11,7 +11,7 @@ import { createRBACMap } from "../helpers"; * @returns */ export function clusterRole(name: string, capabilities: CapabilityExport[]): kind.ClusterRole { - console.log(`Let's give this SA the least privileges possible.\n${JSON.stringify(capabilities, null, 2)}`); + const rbacMap = createRBACMap(capabilities); return { apiVersion: "rbac.authorization.k8s.io/v1", diff --git a/src/lib/helpers.test.ts b/src/lib/helpers.test.ts index 32e45cfc0..3e332b4af 100644 --- a/src/lib/helpers.test.ts +++ b/src/lib/helpers.test.ts @@ -256,18 +256,11 @@ describe("createRBACMap", () => { const result = createRBACMap(capabilities); console.log(result); - const expected = { - "/v1/Namespace": { - verbs: ["get", "list", "create", "watch", "delete"], - plural: "namespaces", - }, - "/v1/ConfigMap": { - verbs: ["get", "list", "create", "update", "watch", "delete"], - plural: "configmaps", - }, - "/v1/Secret": { verbs: ["get", "list", "create"], plural: "secrets" }, - "pepr.dev/v1/Unicorn": { verbs: ["get", "list", "create"], plural: "unicorns" }, - }; + const expected = { + 'pepr.dev/v1/peprstore': { verbs: [ '*' ], plural: 'peprstores' }, + '/v1/Namespace': { verbs: [ 'watch' ], plural: 'namespaces' }, + '/v1/ConfigMap': { verbs: [ 'watch' ], plural: 'configmaps' } + }; expect(result).toEqual(expected); }); diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index 88da7154c..8e0bdb2ca 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -21,23 +21,17 @@ export const createRBACMap = (capabilities: CapabilityExport[]): RBACMap => { capability.bindings.forEach(binding => { const key = `${binding.kind.group}/${binding.kind.version}/${binding.kind.kind}`; - if (!acc[key]) { + acc["pepr.dev/v1/peprstore"] = { + verbs: ["*"], + plural: "peprstores", + }; + + if (!acc[key] && binding.isWatch) { acc[key] = { - verbs: ["get", "list"], + verbs: ["watch"], plural: binding.kind.plural || `${binding.kind.kind.toLowerCase()}s`, }; - } - - if (binding.isWatch) { - addVerbIfNotExists(acc[key].verbs, "watch"); - } - - if (binding.event === "CREATEORUPDATE") { - addVerbIfNotExists(acc[key].verbs, "create"); - addVerbIfNotExists(acc[key].verbs, "update"); - } else { - addVerbIfNotExists(acc[key].verbs, binding.event.toLowerCase()); - } + } }); return acc; From 99714fdd06459bdbce8aa4ac1961cc0edf62639a Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Fri, 20 Oct 2023 17:22:54 -0400 Subject: [PATCH 05/21] chore: preparing e2e Signed-off-by: Case Wylie --- src/cli/build.ts | 18 ++++++++++++------ src/lib/assets/index.ts | 4 ++-- src/lib/assets/rbac.ts | 42 +++++++++++++++++++++-------------------- src/lib/assets/yaml.ts | 4 ++-- src/lib/helpers.test.ts | 14 ++++++++------ src/lib/helpers.ts | 4 ++-- 6 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/cli/build.ts b/src/cli/build.ts index fffaf4aad..1d1305c2f 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -28,11 +28,7 @@ export default function (program: RootCmd) { "-r, --registry-info [/]", "Registry Info: Image registry and username. Note: You must be signed into the registry", ) - .option( - "-rm, --rbac-mode [admin|scoped]", - "Rbac Mode: admin, scoped (default: admin)", - - ) + .option("-rm, --rbac-mode [admin|scoped]", "Rbac Mode: admin, scoped (default: admin)") .action(async opts => { // Build the module const { cfg, path, uuid } = await buildModule(undefined, opts.entryPoint); @@ -42,6 +38,14 @@ export default function (program: RootCmd) { let image: string = ""; + // check rbacMode + if (opts.rbacMode !== undefined) { + if (opts.rbacMode !== "admin" && opts.rbacMode !== "scoped") { + console.error(`Invalid rbacMode: ${opts.rbacMode}, defaulting to admin`); + opts.rbacMode = ""; + } + } + if (opts.registryInfo !== undefined) { console.info(`Including ${includedFiles.length} files in controller image.`); @@ -79,7 +83,9 @@ export default function (program: RootCmd) { const yamlFile = `pepr-module-${uuid}.yaml`; const yamlPath = resolve("dist", yamlFile); - const yaml = await assets.allYaml(); + const yaml = await assets.allYaml( + opts.rbacMode === undefined ? "" : opts.rbacMode.toString(), + ); const zarfPath = resolve("dist", "zarf.yaml"); const zarf = assets.zarfYaml(yamlFile); diff --git a/src/lib/assets/index.ts b/src/lib/assets/index.ts index d94bbdddd..4c2a56ba6 100644 --- a/src/lib/assets/index.ts +++ b/src/lib/assets/index.ts @@ -40,8 +40,8 @@ export class Assets { zarfYaml = (path: string) => zarfYaml(this, path); - allYaml = async () => { + allYaml = async (rbacMode: string = "") => { this.capabilities = await loadCapabilities(this.path); - return allYaml(this); + return allYaml(this, rbacMode); }; } diff --git a/src/lib/assets/rbac.ts b/src/lib/assets/rbac.ts index 1ff5e9bec..cba2e46d8 100644 --- a/src/lib/assets/rbac.ts +++ b/src/lib/assets/rbac.ts @@ -10,32 +10,34 @@ import { createRBACMap } from "../helpers"; * @todo: should dynamically generate this based on resources used by the module. will also need to explore how this should work for multiple modules. * @returns */ -export function clusterRole(name: string, capabilities: CapabilityExport[]): kind.ClusterRole { - +export function clusterRole(name: string, capabilities: CapabilityExport[], rbacMode: string = ""): kind.ClusterRole { const rbacMap = createRBACMap(capabilities); return { apiVersion: "rbac.authorization.k8s.io/v1", kind: "ClusterRole", metadata: { name }, - rules: [ - ...Object.keys(rbacMap).map(key => { - // let group:string, version:string, kind:string; - let group: string; - key.split("/").length < 3 ? (group = "") : (group = key.split("/")[0]); + rules: + rbacMode === "scoped" + ? [ + ...Object.keys(rbacMap).map(key => { + // let group:string, version:string, kind:string; + let group: string; + key.split("/").length < 3 ? (group = "") : (group = key.split("/")[0]); - return { - apiGroups: [group], - resources: [rbacMap[key].plural], - verbs: rbacMap[key].verbs, - }; - }), - // { - // // @todo: make this configurable - // apiGroups: ["*"], - // resources: ["*"], - // verbs: ["create", "delete", "get", "list", "patch", "update", "watch"], - // }, - ], + return { + apiGroups: [group], + resources: [rbacMap[key].plural], + verbs: rbacMap[key].verbs, + }; + }), + ] + : [ + { + apiGroups: ["*"], + resources: ["*"], + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"], + }, + ], }; } diff --git a/src/lib/assets/yaml.ts b/src/lib/assets/yaml.ts index ef42fdf26..0ac41ce87 100644 --- a/src/lib/assets/yaml.ts +++ b/src/lib/assets/yaml.ts @@ -40,7 +40,7 @@ export function zarfYaml({ name, image, config }: Assets, path: string) { return dumpYaml(zarfCfg, { noRefs: true }); } -export async function allYaml(assets: Assets) { +export async function allYaml(assets: Assets, rbacMode: string = "") { const { name, tls, apiToken, path } = assets; const code = await fs.readFile(path); @@ -54,7 +54,7 @@ export async function allYaml(assets: Assets) { const resources = [ namespace, - clusterRole(name, assets.capabilities), + clusterRole(name, assets.capabilities, rbacMode), clusterRoleBinding(name), serviceAccount(name), apiTokenSecret(name, apiToken), diff --git a/src/lib/helpers.test.ts b/src/lib/helpers.test.ts index 3e332b4af..c3a730e9c 100644 --- a/src/lib/helpers.test.ts +++ b/src/lib/helpers.test.ts @@ -254,13 +254,15 @@ const capabilities: CapabilityExport[] = JSON.parse(`[ describe("createRBACMap", () => { test("should return the correct RBACMap for given capabilities", () => { const result = createRBACMap(capabilities); - console.log(result); - const expected = { - 'pepr.dev/v1/peprstore': { verbs: [ '*' ], plural: 'peprstores' }, - '/v1/Namespace': { verbs: [ 'watch' ], plural: 'namespaces' }, - '/v1/ConfigMap': { verbs: [ 'watch' ], plural: 'configmaps' } - }; + const expected = { + "pepr.dev/v1/peprstore": { + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"], + plural: "peprstores", + }, + "/v1/Namespace": { verbs: ["watch"], plural: "namespaces" }, + "/v1/ConfigMap": { verbs: ["watch"], plural: "configmaps" }, + }; expect(result).toEqual(expected); }); diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index 8e0bdb2ca..c8d1e3424 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -22,7 +22,7 @@ export const createRBACMap = (capabilities: CapabilityExport[]): RBACMap => { const key = `${binding.kind.group}/${binding.kind.version}/${binding.kind.kind}`; acc["pepr.dev/v1/peprstore"] = { - verbs: ["*"], + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"], plural: "peprstores", }; @@ -31,7 +31,7 @@ export const createRBACMap = (capabilities: CapabilityExport[]): RBACMap => { verbs: ["watch"], plural: binding.kind.plural || `${binding.kind.kind.toLowerCase()}s`, }; - } + } }); return acc; From 2826a12a3484aa6a52787a857cc9b3c20062b8e0 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Sat, 21 Oct 2023 08:34:54 -0400 Subject: [PATCH 06/21] chore: scoped clusterrole journey and remove duplicate entry in eslintrc Signed-off-by: Case Wylie --- .eslintrc.json | 1 - journey/pepr-build-wasm.ts | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 02abc4080..d633899fa 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -18,7 +18,6 @@ "plugins": ["@typescript-eslint"], "ignorePatterns": [ "src/templates", - "journey", "node_modules", "dist", "hack", diff --git a/journey/pepr-build-wasm.ts b/journey/pepr-build-wasm.ts index 666d6d61b..5a1d3ed89 100644 --- a/journey/pepr-build-wasm.ts +++ b/journey/pepr-build-wasm.ts @@ -11,7 +11,7 @@ import { cwd } from "./entrypoint.test"; export function peprBuild() { it("should successfully build the Pepr project", async () => { - execSync("npx pepr build -r gchr.io/defenseunicorns", { cwd: cwd, stdio: "inherit" }); + execSync("npx pepr build -r gchr.io/defenseunicorns -rm scoped", { cwd: cwd, stdio: "inherit" }); }); it("should generate produce the K8s yaml file", async () => { @@ -22,8 +22,44 @@ export function peprBuild() { await fs.access(resolve(cwd, "dist", "zarf.yaml")); await validateZarfYaml(); }); + + it("should generate a scoped ClusterRole", async () => { + await validateClusterRoleYaml(); + }); } +async function validateClusterRoleYaml() { + // Read the generated yaml files + const k8sYaml = await fs.readFile(resolve(cwd, "dist", "pepr-module-static-test.yaml"), "utf8"); + const expectedClusterRoleYaml = ` + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: pepr-static-test + rules: + - apiGroups: + - pepr.dev + resources: + - peprstores + verbs: + - '*' + - apiGroups: + - '' + resources: + - namespaces + verbs: + - watch + - apiGroups: + - '' + resources: + - configmaps + verbs: + - watch + ` + + expect(k8sYaml).toContain(expectedClusterRoleYaml.trim()) + +} async function validateZarfYaml() { // Get the version of the pepr binary const peprVer = execSync("npx pepr --version", { cwd }).toString().trim(); From 917e9971bb5eb39e1d92ef492a30be7ab18b1f3b Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Sat, 21 Oct 2023 08:38:04 -0400 Subject: [PATCH 07/21] chore: journey build test shouldnt change Signed-off-by: Case Wylie --- journey/pepr-build.ts | 44 ------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/journey/pepr-build.ts b/journey/pepr-build.ts index d78266547..e37b107f0 100644 --- a/journey/pepr-build.ts +++ b/journey/pepr-build.ts @@ -22,50 +22,6 @@ export function peprBuild() { await fs.access(resolve(cwd, "dist", "zarf.yaml")); await validateZarfYaml(); }); - - it('should generate correct ClusterRole yaml', async () => { - await validateClusterRoleYaml() - }); -} - -async function validateClusterRoleYaml() { - // Read the generated yaml files - const k8sYaml = await fs.readFile(resolve(cwd, "dist", "pepr-module-static-test.yaml"), "utf8"); - - // The expected ClusterRole - const expectedClusterRoleYaml = { - apiVersion: "rbac.authorization.k8s.io/v1", - kind: "ClusterRole", - metadata: { - name: "pepr-module-static-test", - }, - rules: [ - { - apiGroups: [""], - resources: ["namespaces"], - verbs: ["get", "list", "create","watch"], - }, - { - apiGroups: [""], - resources: ["configmaps"], - verbs: ["get", "list", "create","watch","update","delete"], - }, - { - apiGroups: [""], - resources: ["secrets"], - verbs: ["get", "list", "create"], - }, - { - apiGroups: ["pepr.dev"], - resources: ["unicorns"], - verbs: ["get", "list", "create"], - }, - ], - }; - - // Check the generated cluster role yaml - const actualClusterRoleYaml = loadYaml(k8sYaml); - expect(actualClusterRoleYaml).toEqual(expectedClusterRoleYaml); } async function validateZarfYaml() { From 7698d23fe98b32cfb297cfb249689b0bbc396356 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Sat, 21 Oct 2023 10:45:16 -0400 Subject: [PATCH 08/21] chore: fix e2e Signed-off-by: Case Wylie --- journey/pepr-build-wasm.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/journey/pepr-build-wasm.ts b/journey/pepr-build-wasm.ts index 5a1d3ed89..4a9cc8f1d 100644 --- a/journey/pepr-build-wasm.ts +++ b/journey/pepr-build-wasm.ts @@ -27,6 +27,12 @@ export function peprBuild() { await validateClusterRoleYaml(); }); } +function containsSubstring(fullString: string, substring: string): boolean { + const cleanString = (s: string) => s.replace(/\s+/g, '').toLowerCase(); + const cleanedFullString = cleanString(fullString); + const cleanedSubstring = cleanString(substring); + return cleanedFullString.includes(cleanedSubstring); +} async function validateClusterRoleYaml() { // Read the generated yaml files const k8sYaml = await fs.readFile(resolve(cwd, "dist", "pepr-module-static-test.yaml"), "utf8"); @@ -57,7 +63,7 @@ async function validateClusterRoleYaml() { - watch ` - expect(k8sYaml).toContain(expectedClusterRoleYaml.trim()) + expect(containsSubstring(k8sYaml,expectedClusterRoleYaml)).toEqual(true) } async function validateZarfYaml() { From fd4b23422d96066426c7d56a535fbf50b37aa833 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Sat, 21 Oct 2023 16:52:39 -0400 Subject: [PATCH 09/21] chore: expected e2e journey Signed-off-by: Case Wylie --- journey/entrypoint-wasm.test.ts | 2 +- journey/pepr-build-wasm.ts | 40 ++++-------------------------- journey/resources/clusterrole.yaml | 23 +++++++++++++++++ 3 files changed, 29 insertions(+), 36 deletions(-) create mode 100644 journey/resources/clusterrole.yaml diff --git a/journey/entrypoint-wasm.test.ts b/journey/entrypoint-wasm.test.ts index cc20e7e32..273ad4527 100644 --- a/journey/entrypoint-wasm.test.ts +++ b/journey/entrypoint-wasm.test.ts @@ -15,4 +15,4 @@ export const cwd = "pepr-test-module"; // Allow 5 minutes for the tests to run jest.setTimeout(1000 * 60 * 5); -describe("Journey: `npx pepr build -r gchr.io/defenseunicorns`", peprBuild); +describe("Journey: `npx pepr build -r gchr.io/defenseunicorns -rm scoped`", peprBuild); diff --git a/journey/pepr-build-wasm.ts b/journey/pepr-build-wasm.ts index 4a9cc8f1d..7f5792afc 100644 --- a/journey/pepr-build-wasm.ts +++ b/journey/pepr-build-wasm.ts @@ -10,7 +10,7 @@ import { resolve } from "path"; import { cwd } from "./entrypoint.test"; export function peprBuild() { - it("should successfully build the Pepr project", async () => { + it("should successfully build the Pepr project with arguments", async () => { execSync("npx pepr build -r gchr.io/defenseunicorns -rm scoped", { cwd: cwd, stdio: "inherit" }); }); @@ -27,45 +27,15 @@ export function peprBuild() { await validateClusterRoleYaml(); }); } -function containsSubstring(fullString: string, substring: string): boolean { - const cleanString = (s: string) => s.replace(/\s+/g, '').toLowerCase(); - const cleanedFullString = cleanString(fullString); - const cleanedSubstring = cleanString(substring); - return cleanedFullString.includes(cleanedSubstring); -} + async function validateClusterRoleYaml() { // Read the generated yaml files const k8sYaml = await fs.readFile(resolve(cwd, "dist", "pepr-module-static-test.yaml"), "utf8"); + const cr = await fs.readFile(resolve("journey", "resources", "clusterrole.yaml"), "utf8"); - const expectedClusterRoleYaml = ` - apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRole - metadata: - name: pepr-static-test - rules: - - apiGroups: - - pepr.dev - resources: - - peprstores - verbs: - - '*' - - apiGroups: - - '' - resources: - - namespaces - verbs: - - watch - - apiGroups: - - '' - resources: - - configmaps - verbs: - - watch - ` - - expect(containsSubstring(k8sYaml,expectedClusterRoleYaml)).toEqual(true) - + 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(); diff --git a/journey/resources/clusterrole.yaml b/journey/resources/clusterrole.yaml new file mode 100644 index 000000000..49bf0b6a7 --- /dev/null +++ b/journey/resources/clusterrole.yaml @@ -0,0 +1,23 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: pepr-static-test +rules: + - apiGroups: + - pepr.dev + resources: + - peprstores + verbs: + - '*' + - apiGroups: + - '' + resources: + - namespaces + verbs: + - watch + - apiGroups: + - '' + resources: + - configmaps + verbs: + - watch From 976720d4fa80b2a234f5977435176c8885dcaef7 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Sat, 21 Oct 2023 17:20:50 -0400 Subject: [PATCH 10/21] chore: fix test Signed-off-by: Case Wylie --- journey/resources/clusterrole.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/journey/resources/clusterrole.yaml b/journey/resources/clusterrole.yaml index 49bf0b6a7..5f3bc51d2 100644 --- a/journey/resources/clusterrole.yaml +++ b/journey/resources/clusterrole.yaml @@ -8,7 +8,13 @@ rules: resources: - peprstores verbs: - - '*' + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - '' resources: From 283307a0b51f8f2f3a5fe12b3fdbdde39eed37e8 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Sun, 22 Oct 2023 10:00:09 -0400 Subject: [PATCH 11/21] chore: update examples Signed-off-by: Case Wylie --- docs/rbac.md | 37 +++++------------------- src/templates/capabilities/hello-pepr.ts | 30 ++++++++++++------- 2 files changed, 26 insertions(+), 41 deletions(-) diff --git a/docs/rbac.md b/docs/rbac.md index 93f3d2a29..63f274ba9 100644 --- a/docs/rbac.md +++ b/docs/rbac.md @@ -27,54 +27,31 @@ If encountering unexpected behaviors in Pepr while running in scoped mode, check 1. Check Deployment logs for RBAC errors: ```bash -kubectl logs -n pepr-system -l app | jq 'select(.data.reason == "Forbidden")' | grep -A9 -B9 "Forbidden" +kubectl logs -n pepr-system -l app | jq # example output -dden")' | grep -A9 -B9 "Forbidden" - "time": 1697814589833, - "pid": 16, - "hostname": "pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10-watcher-6f44f996pmhhb", - "data": { - "kind": "Status", - "apiVersion": "v1", - "metadata": {}, - "status": "Failure", - "message": "peprstores.pepr.dev \"pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10-store\" is forbidden: User \"system:serviceaccount:pepr-system:pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10\" cannot get resource \"peprstores\" in API group \"pepr.dev\" in the namespace \"pepr-system\": RBAC: clusterrole.rbac.authorization.k8s.io \"pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10\" not found", - "reason": "Forbidden", - "details": { - "name": "pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10-store", - "group": "pepr.dev", - "kind": "peprstores" - }, - "code": 403 - }, - "ok": false, - "status": 403, - "statusText": "Forbidden" -} { "level": 50, - "time": 1697814589840, + "time": 1697983053758, "pid": 16, - "hostname": "pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10-watcher-6f44f996pmhhb", + "hostname": "pepr-static-test-watcher-745d65857d-pndg7", "data": { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", - "message": "peprstores.pepr.dev \"pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10-store\" is forbidden: User \"system:serviceaccount:pepr-system:pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10\" cannot patch resource \"peprstores\" in API group \"pepr.dev\" in the namespace \"pepr-system\": RBAC: clusterrole.rbac.authorization.k8s.io \"pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10\" not found", + "message": "configmaps \"pepr-ssa-demo\" is forbidden: User \"system:serviceaccount:pepr-system:pepr-static-test\" cannot patch resource \"configmaps\" in API group \"\" in the namespace \"pepr-demo-2\"", "reason": "Forbidden", "details": { - "name": "pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10-store", - "group": "pepr.dev", - "kind": "peprstores" + "name": "pepr-ssa-demo", + "kind": "configmaps" }, "code": 403 }, "ok": false, "status": 403, "statusText": "Forbidden", - "msg": "Failed to create Pepr store" + "msg": "Dooes the ServiceAccount permissions to CREATE and PATCH this ConfigMap?" } ``` diff --git a/src/templates/capabilities/hello-pepr.ts b/src/templates/capabilities/hello-pepr.ts index dc11710f1..405233061 100644 --- a/src/templates/capabilities/hello-pepr.ts +++ b/src/templates/capabilities/hello-pepr.ts @@ -52,19 +52,27 @@ When(a.Namespace) .Watch(async ns => { Log.info("Namespace pepr-demo-2 was created."); + try { + // Apply the ConfigMap using K8s server-side apply + await K8s(kind.ConfigMap).Apply({ + metadata: { + name: "pepr-ssa-demo", + namespace: "pepr-demo-2", + }, + data: { + "ns-uid": ns.metadata.uid, + }, + }); + } catch (error) { + // You can use the Log object to log messages to the Pepr controller pod + Log.error( + error, + "Does the ServiceAccount permissions to CREATE and PATCH this ConfigMap?", + ); + } + // You can share data between actions using the Store, including between different types of actions Store.setItem("watch-data", "This data was stored by a Watch Action."); - - // Apply the ConfigMap using K8s server-side apply - await K8s(kind.ConfigMap).Apply({ - metadata: { - name: "pepr-ssa-demo", - namespace: "pepr-demo-2", - }, - data: { - "ns-uid": ns.metadata.uid, - }, - }); }); /** From 1a64eb1f5277038b069ef06de17ebd6055f74923 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Sun, 22 Oct 2023 10:04:28 -0400 Subject: [PATCH 12/21] chore: update rbac docs Signed-off-by: Case Wylie --- docs/rbac.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/rbac.md b/docs/rbac.md index 63f274ba9..ace0261ed 100644 --- a/docs/rbac.md +++ b/docs/rbac.md @@ -60,13 +60,8 @@ kubectl logs -n pepr-system -l app | jq ```bash SA=$(kubectl get deploy -n pepr-system -o=jsonpath='{range .items[0]}{.spec.template.spec.serviceAccountName}{"\n"}{end}') -# Can i create configmaps as the service account? -kubectl auth can-i create cm --as=system:serviceaccount:pepr-system:$SA - -# example output: yes - -# Can i create configmaps as the service account in kube-system? -kubectl auth can-i create cm --as=system:serviceaccount:pepr-system:$SA -n kube-system +# Can i create configmaps as the service account in pepr-demo-2? +kubectl auth can-i create cm --as=system:serviceaccount:pepr-system:$SA -n pepr-demo-2 # example output: no ``` @@ -79,11 +74,13 @@ SA=$(kubectl get deploy -n pepr-system -o=jsonpath='{range .items[0]}{.spec.temp kubectl describe clusterrole $SA # example output: -Name: pepr-e868e97f-0512-5a48-92c3-96a2e3b6da10 +Name: pepr-static-test Labels: Annotations: PolicyRule: - Resources Non-Resource URLs Resource Names Verbs - --------- ----------------- -------------- ----- - *.* [] [] [create delete get list patch update watch] + Resources Non-Resource URLs Resource Names Verbs + --------- ----------------- -------------- ----- + peprstores.pepr.dev [] [] [create delete get list patch update watch] + configmaps [] [] [watch] + namespaces [] [] [watch] ``` From 9dc8336566be7ca119cf3611aed87d69a14479e1 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Sun, 22 Oct 2023 11:22:19 -0400 Subject: [PATCH 13/21] chore: links to docs in README.md Signed-off-by: Case Wylie --- docs/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/README.md b/docs/README.md index 78312dd23..3106f2cbc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,3 +7,5 @@ ## Additional Docs ### [Pepr Cli](cli.md) ### [Metrics](metrics.md) +### [RBAC](rbac.md) +### [WebAssembly](webassembly.md) From cfe85d53822086a00dc2a8458cc4382e22656667 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Mon, 23 Oct 2023 16:00:05 -0400 Subject: [PATCH 14/21] feat: finish feature Signed-off-by: Case Wylie --- docs/cli.md | 2 +- docs/rbac.md | 66 +++++++++++++++++++++++- journey/entrypoint-wasm.test.ts | 2 +- src/cli/build.ts | 15 +++--- src/lib/assets/rbac.ts | 2 +- src/lib/helpers.test.ts | 2 +- src/lib/helpers.ts | 2 +- src/templates/capabilities/hello-pepr.ts | 5 +- 8 files changed, 76 insertions(+), 20 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index a09e1526d..de7017018 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -56,5 +56,5 @@ Create a [zarf.yaml](https://zarf.dev) and K8s manifest for the current module. **Options:** - `-r, --registry-info [/]` - Registry Info: Image registry and username. Note: You must be signed into the registry -- `-rm, --rbac-mode [admin|scoped]` - Rbac Mode: admin, scoped (default: admin) +- `--rbac-mode [admin|scoped]` - Rbac Mode: admin, scoped (default: admin) - `-l, --log-level [level]` - Log level: debug, info, warn, error (default: "info") diff --git a/docs/rbac.md b/docs/rbac.md index ace0261ed..4b7c9447c 100644 --- a/docs/rbac.md +++ b/docs/rbac.md @@ -18,7 +18,7 @@ npx pepr build --rbac-mode admin npx pepr build --rbac-mode scoped ``` -**Description:** The service account is provided just enough permissions to perform its required tasks, and no more. This mode is recommended for most use cases as it limits potential attack vectors and aligns with best practices in security. _The admission controller's primary mutating or validating action doesn't require a ClusterRole (as the request is not persisted or executed while passing through admission control), if you have a use case where the admission controller's logic involves reading other Kubernetes resources or taking additional actions beyond just validating, mutating, or watching the incoming request, appropriate RBAC settings should be reflected in the ClusterRole._ +**Description:** The service account is provided just enough permissions to perform its required tasks, and no more. This mode is recommended for most use cases as it limits potential attack vectors and aligns with best practices in security. _The admission controller's primary mutating or validating action doesn't require a ClusterRole (as the request is not persisted or executed while passing through admission control), if you have a use case where the admission controller's logic involves reading other Kubernetes resources or taking additional actions beyond just validating, mutating, or watching the incoming request, appropriate RBAC settings should be reflected in the ClusterRole. See how in [Updating the ClusterRole](#updating-the-clusterrole)._ ## Debugging RBAC Issues @@ -66,7 +66,7 @@ kubectl auth can-i create cm --as=system:serviceaccount:pepr-system:$SA -n pepr- # example output: no ``` -3. Describe the ServiceAccount +3. Describe the ClusterRole ```bash SA=$(kubectl get deploy -n pepr-system -o=jsonpath='{range .items[0]}{.spec.template.spec.serviceAccountName}{"\n"}{end}') @@ -84,3 +84,65 @@ PolicyRule: configmaps [] [] [watch] namespaces [] [] [watch] ``` + +## Updating the ClusterRole + +As discussed in the [Modes](#modes) section, the admission controller's primary mutating or validating action doesn't require a ClusterRole (as the request is not persisted or executed while passing through admission control), if you have a use case where the admission controller's logic involves reading other Kubernetes resources or taking additional actions beyond just validating, mutating, or watching the incoming request, appropriate RBAC settings should be reflected in the ClusterRole. + +Step 1: Figure out the desired permissions. (`kubectl create clusterrole --help` is a good place to start figuring out the syntax) + +```bash + kubectl create clusterrole configMapApplier --verb=create,patch --resource=configmap --dry-run=client -oyaml + + # example output +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: configMapApplier +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - patch +``` + +Step 2: Update the ClusterRole in the `dist` folder. + +```yaml +... +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: pepr-static-test +rules: + - apiGroups: + - pepr.dev + resources: + - peprstores + verbs: + - create + - get + - patch + - watch + - apiGroups: + - '' + resources: + - namespaces + verbs: + - watch + - apiGroups: + - '' + resources: + - configmaps + verbs: + - watch + - create # New + - patch # New +... +``` + +Step 3: Apply the updated configuration diff --git a/journey/entrypoint-wasm.test.ts b/journey/entrypoint-wasm.test.ts index 273ad4527..844bdc8a0 100644 --- a/journey/entrypoint-wasm.test.ts +++ b/journey/entrypoint-wasm.test.ts @@ -15,4 +15,4 @@ export const cwd = "pepr-test-module"; // Allow 5 minutes for the tests to run jest.setTimeout(1000 * 60 * 5); -describe("Journey: `npx pepr build -r gchr.io/defenseunicorns -rm scoped`", peprBuild); +describe("Journey: `npx pepr build -r gchr.io/defenseunicorns --rbac-mode scoped`", peprBuild); diff --git a/src/cli/build.ts b/src/cli/build.ts index 1d1305c2f..8706774b9 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -10,6 +10,7 @@ import { Assets } from "../lib/assets"; import { dependencies, version } from "./init/templates"; import { RootCmd } from "./root"; import { peprFormat } from "./format"; +import { Option } from "commander"; const peprTS = "pepr.ts"; @@ -28,7 +29,11 @@ export default function (program: RootCmd) { "-r, --registry-info [/]", "Registry Info: Image registry and username. Note: You must be signed into the registry", ) - .option("-rm, --rbac-mode [admin|scoped]", "Rbac Mode: admin, scoped (default: admin)") + .addOption( + new Option("--rbac-mode [admin|scoped]", "Rbac Mode: admin, scoped (default: admin)") + .choices(["admin", "scoped"]) + .default("admin"), + ) .action(async opts => { // Build the module const { cfg, path, uuid } = await buildModule(undefined, opts.entryPoint); @@ -38,14 +43,6 @@ export default function (program: RootCmd) { let image: string = ""; - // check rbacMode - if (opts.rbacMode !== undefined) { - if (opts.rbacMode !== "admin" && opts.rbacMode !== "scoped") { - console.error(`Invalid rbacMode: ${opts.rbacMode}, defaulting to admin`); - opts.rbacMode = ""; - } - } - if (opts.registryInfo !== undefined) { console.info(`Including ${includedFiles.length} files in controller image.`); diff --git a/src/lib/assets/rbac.ts b/src/lib/assets/rbac.ts index cba2e46d8..03b67f3f4 100644 --- a/src/lib/assets/rbac.ts +++ b/src/lib/assets/rbac.ts @@ -80,7 +80,7 @@ export function storeRole(name: string): kind.Role { metadata: { name, namespace: "pepr-system" }, rules: [ { - apiGroups: ["pepr.dev/*"], + apiGroups: ["pepr.dev"], resources: ["peprstores"], resourceNames: [""], verbs: ["create", "get", "patch", "watch"], diff --git a/src/lib/helpers.test.ts b/src/lib/helpers.test.ts index c3a730e9c..90025a6d5 100644 --- a/src/lib/helpers.test.ts +++ b/src/lib/helpers.test.ts @@ -257,7 +257,7 @@ describe("createRBACMap", () => { const expected = { "pepr.dev/v1/peprstore": { - verbs: ["create", "delete", "get", "list", "patch", "update", "watch"], + verbs: ["create", "get", "patch", "watch"], plural: "peprstores", }, "/v1/Namespace": { verbs: ["watch"], plural: "namespaces" }, diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index c8d1e3424..067291a45 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -22,7 +22,7 @@ export const createRBACMap = (capabilities: CapabilityExport[]): RBACMap => { const key = `${binding.kind.group}/${binding.kind.version}/${binding.kind.kind}`; acc["pepr.dev/v1/peprstore"] = { - verbs: ["create", "delete", "get", "list", "patch", "update", "watch"], + verbs: ["create", "get", "patch", "watch"], plural: "peprstores", }; diff --git a/src/templates/capabilities/hello-pepr.ts b/src/templates/capabilities/hello-pepr.ts index 405233061..daed2bab9 100644 --- a/src/templates/capabilities/hello-pepr.ts +++ b/src/templates/capabilities/hello-pepr.ts @@ -65,10 +65,7 @@ When(a.Namespace) }); } catch (error) { // You can use the Log object to log messages to the Pepr controller pod - Log.error( - error, - "Does the ServiceAccount permissions to CREATE and PATCH this ConfigMap?", - ); + Log.error(error, "Failed to apply ConfigMap using server-side apply."); } // You can share data between actions using the Store, including between different types of actions From d1bcde3f4c240663b00b1c1cb54560117176892e Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Mon, 23 Oct 2023 16:04:57 -0400 Subject: [PATCH 15/21] chore: update website docs Signed-off-by: Case Wylie --- website/content/en/docs/cli.md | 3 +- website/content/en/docs/rbac.md | 71 ++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/website/content/en/docs/cli.md b/website/content/en/docs/cli.md index 9b01c5bfe..ad1d1eaa8 100644 --- a/website/content/en/docs/cli.md +++ b/website/content/en/docs/cli.md @@ -2,7 +2,6 @@ title: CLI linkTitle: CLI --- - # Pepr CLI ## `pepr init` @@ -61,5 +60,5 @@ Create a [zarf.yaml](https://zarf.dev) and K8s manifest for the current module. **Options:** - `-r, --registry-info [/]` - Registry Info: Image registry and username. Note: You must be signed into the registry -- `-rm, --rbac-mode [admin|scoped]` - Rbac Mode: admin, scoped (default: admin) +- `--rbac-mode [admin|scoped]` - Rbac Mode: admin, scoped (default: admin) - `-l, --log-level [level]` - Log level: debug, info, warn, error (default: "info") diff --git a/website/content/en/docs/rbac.md b/website/content/en/docs/rbac.md index 87b6c2fd4..31aeacd84 100644 --- a/website/content/en/docs/rbac.md +++ b/website/content/en/docs/rbac.md @@ -3,14 +3,13 @@ title: RBAC linkTitle: RBAC --- - # RBAC Modes During the build phase of Pepr (`npx pepr build --rbac-mode [admin|scoped]`), you have the option to specify the desired RBAC mode through specific flags. This allows fine-tuning the level of access granted based on requirements and preferences. ## Modes -### `admin` +**admin** ```bash npx pepr build --rbac-mode admin @@ -18,13 +17,13 @@ npx pepr build --rbac-mode admin **Description:** The service account is given cluster-admin permissions, granting it full, unrestricted access across the entire cluster. This can be useful for administrative tasks where broad permissions are necessary. However, use this mode with caution, as it can pose security risks if misused. This is the default mode. -### `scoped` +**scoped** ```bash npx pepr build --rbac-mode scoped ``` -**Description:** The service account is provided just enough permissions to perform its required tasks, and no more. This mode is recommended for most use cases as it limits potential attack vectors and aligns with best practices in security. _The admission controller's primary mutating or validating action doesn't require a ClusterRole (as the request is not persisted or executed while passing through admission control), if you have a use case where the admission controller's logic involves reading other Kubernetes resources or taking additional actions beyond just validating, mutating, or watching the incoming request, appropriate RBAC settings should be reflected in the ClusterRole._ +**Description:** The service account is provided just enough permissions to perform its required tasks, and no more. This mode is recommended for most use cases as it limits potential attack vectors and aligns with best practices in security. _The admission controller's primary mutating or validating action doesn't require a ClusterRole (as the request is not persisted or executed while passing through admission control), if you have a use case where the admission controller's logic involves reading other Kubernetes resources or taking additional actions beyond just validating, mutating, or watching the incoming request, appropriate RBAC settings should be reflected in the ClusterRole. See how in [Updating the ClusterRole](#updating-the-clusterrole)._ ## Debugging RBAC Issues @@ -72,7 +71,7 @@ kubectl auth can-i create cm --as=system:serviceaccount:pepr-system:$SA -n pepr- # example output: no ``` -3. Describe the ServiceAccount +3. Describe the ClusterRole ```bash SA=$(kubectl get deploy -n pepr-system -o=jsonpath='{range .items[0]}{.spec.template.spec.serviceAccountName}{"\n"}{end}') @@ -90,3 +89,65 @@ PolicyRule: configmaps [] [] [watch] namespaces [] [] [watch] ``` + +## Updating the ClusterRole + +As discussed in the [Modes](#modes) section, the admission controller's primary mutating or validating action doesn't require a ClusterRole (as the request is not persisted or executed while passing through admission control), if you have a use case where the admission controller's logic involves reading other Kubernetes resources or taking additional actions beyond just validating, mutating, or watching the incoming request, appropriate RBAC settings should be reflected in the ClusterRole. + +Step 1: Figure out the desired permissions. (`kubectl create clusterrole --help` is a good place to start figuring out the syntax) + +```bash + kubectl create clusterrole configMapApplier --verb=create,patch --resource=configmap --dry-run=client -oyaml + + # example output +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: configMapApplier +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - patch +``` + +Step 2: Update the ClusterRole in the `dist` folder. + +```yaml +... +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: pepr-static-test +rules: + - apiGroups: + - pepr.dev + resources: + - peprstores + verbs: + - create + - get + - patch + - watch + - apiGroups: + - '' + resources: + - namespaces + verbs: + - watch + - apiGroups: + - '' + resources: + - configmaps + verbs: + - watch + - create # New + - patch # New +... +``` + +Step 3: Apply the updated configuration From 509fa9ad96e99e892e0b7b177cfcf4ae33b60291 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Mon, 23 Oct 2023 16:13:21 -0400 Subject: [PATCH 16/21] chore: fix journey test for clusterrole Signed-off-by: Case Wylie --- journey/resources/clusterrole.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/journey/resources/clusterrole.yaml b/journey/resources/clusterrole.yaml index 5f3bc51d2..cfb9a3686 100644 --- a/journey/resources/clusterrole.yaml +++ b/journey/resources/clusterrole.yaml @@ -9,11 +9,8 @@ rules: - peprstores verbs: - create - - delete - get - - list - patch - - update - watch - apiGroups: - '' From 0bdb8048f67ec61f66711f8bebf64f391283c7bb Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Mon, 23 Oct 2023 16:17:43 -0400 Subject: [PATCH 17/21] chore: forgot to fix pepr-build-wasm Signed-off-by: Case Wylie --- journey/pepr-build-wasm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/journey/pepr-build-wasm.ts b/journey/pepr-build-wasm.ts index 7f5792afc..2127f429e 100644 --- a/journey/pepr-build-wasm.ts +++ b/journey/pepr-build-wasm.ts @@ -11,7 +11,7 @@ import { cwd } from "./entrypoint.test"; export function peprBuild() { it("should successfully build the Pepr project with arguments", async () => { - execSync("npx pepr build -r gchr.io/defenseunicorns -rm scoped", { cwd: cwd, stdio: "inherit" }); + execSync("npx pepr build -r gchr.io/defenseunicorns --rbac-mode scoped", { cwd: cwd, stdio: "inherit" }); }); it("should generate produce the K8s yaml file", async () => { From a5344815c4b1ae98f83bcacb1157f5c1180ab2a1 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Mon, 23 Oct 2023 17:12:32 -0400 Subject: [PATCH 18/21] chore: rbacMode defaults to admin, remoce check for undefined Signed-off-by: Case Wylie --- src/cli/build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/build.ts b/src/cli/build.ts index 8706774b9..4822d225b 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -81,7 +81,7 @@ export default function (program: RootCmd) { const yamlFile = `pepr-module-${uuid}.yaml`; const yamlPath = resolve("dist", yamlFile); const yaml = await assets.allYaml( - opts.rbacMode === undefined ? "" : opts.rbacMode.toString(), + opts.rbacMode ); const zarfPath = resolve("dist", "zarf.yaml"); From 5124d464f0d7ce413e9b57ad9c41b2cd96df339b Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Mon, 23 Oct 2023 17:15:54 -0400 Subject: [PATCH 19/21] chore: format fix Signed-off-by: Case Wylie --- src/cli/build.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cli/build.ts b/src/cli/build.ts index 4822d225b..496b50ceb 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -80,9 +80,7 @@ export default function (program: RootCmd) { const yamlFile = `pepr-module-${uuid}.yaml`; const yamlPath = resolve("dist", yamlFile); - const yaml = await assets.allYaml( - opts.rbacMode - ); + const yaml = await assets.allYaml(opts.rbacMode); const zarfPath = resolve("dist", "zarf.yaml"); const zarf = assets.zarfYaml(yamlFile); From d7506280c75ec6fa8414f2c4777316ca51117b28 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Wed, 25 Oct 2023 16:41:07 -0400 Subject: [PATCH 20/21] docs: update docs for theme Signed-off-by: Case Wylie --- website/content/en/docs/cli.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/website/content/en/docs/cli.md b/website/content/en/docs/cli.md index ad1d1eaa8..d2d6b18fd 100644 --- a/website/content/en/docs/cli.md +++ b/website/content/en/docs/cli.md @@ -4,7 +4,7 @@ linkTitle: CLI --- # Pepr CLI -## `pepr init` +## pepr init Initialize a new Pepr Module. @@ -15,7 +15,7 @@ Initialize a new Pepr Module. --- -## `pepr update` +## pepr update Update the current Pepr Module to the latest SDK version and update the global Pepr CLI to the same version. @@ -26,7 +26,7 @@ Update the current Pepr Module to the latest SDK version and update the global P --- -## `pepr dev` +## pepr dev Connect a local cluster to a local version of the Pepr Controller to do real-time debugging of your module. Note the `pepr dev` assumes a K3d cluster is running by default. If you are working with Kind or another docker-based @@ -41,7 +41,7 @@ cluster you will have to give Pepr a host path to your machine that is reachable --- -## `pepr deploy` +## pepr deploy Deploy the current module into a Kubernetes cluster, useful for CI systems. Not recommended for production use. @@ -53,7 +53,7 @@ Deploy the current module into a Kubernetes cluster, useful for CI systems. Not --- -## `pepr build` +## pepr build Create a [zarf.yaml](https://zarf.dev) and K8s manifest for the current module. This includes everything needed to deploy Pepr and the current module into production environments. From fb6ae87951f00527de5d14f3a6178fe985e74b1b Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Thu, 26 Oct 2023 11:16:18 -0400 Subject: [PATCH 21/21] chore: addressed unnecessary default Signed-off-by: Case Wylie --- src/lib/assets/index.ts | 2 +- src/lib/assets/yaml.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/assets/index.ts b/src/lib/assets/index.ts index 4c2a56ba6..52d260e41 100644 --- a/src/lib/assets/index.ts +++ b/src/lib/assets/index.ts @@ -40,7 +40,7 @@ export class Assets { zarfYaml = (path: string) => zarfYaml(this, path); - allYaml = async (rbacMode: string = "") => { + allYaml = async (rbacMode: string) => { this.capabilities = await loadCapabilities(this.path); return allYaml(this, rbacMode); }; diff --git a/src/lib/assets/yaml.ts b/src/lib/assets/yaml.ts index 0ac41ce87..b03de6503 100644 --- a/src/lib/assets/yaml.ts +++ b/src/lib/assets/yaml.ts @@ -40,7 +40,7 @@ export function zarfYaml({ name, image, config }: Assets, path: string) { return dumpYaml(zarfCfg, { noRefs: true }); } -export async function allYaml(assets: Assets, rbacMode: string = "") { +export async function allYaml(assets: Assets, rbacMode: string) { const { name, tls, apiToken, path } = assets; const code = await fs.readFile(path);