From 689ef5fd6882417e42c192269b0d814614b21ab8 Mon Sep 17 00:00:00 2001 From: Daniel Pacak Date: Mon, 28 Mar 2022 20:10:37 +0200 Subject: [PATCH] docs(tutorial): Writing Custom Configuration Audit Policies (#1075) Co-authored-by: chenk Co-authored-by: AnaisUrlichs Signed-off-by: Daniel Pacak --- docs/tutorials/recommended_labels.rego | 25 ++ .../writing_custom_configaudit_policies.md | 218 ++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 244 insertions(+) create mode 100644 docs/tutorials/recommended_labels.rego create mode 100644 docs/tutorials/writing_custom_configaudit_policies.md diff --git a/docs/tutorials/recommended_labels.rego b/docs/tutorials/recommended_labels.rego new file mode 100644 index 000000000..1070e073f --- /dev/null +++ b/docs/tutorials/recommended_labels.rego @@ -0,0 +1,25 @@ +package starboard.policy.k8s.custom + +__rego_metadata__ := { + "id": "recommended_labels", + "title": "Recommended labels", + "severity": "LOW", + "type": "Kubernetes Security Check", + "description": "A common set of labels allows tools to work interoperably, describing objects in a common manner that all tools can understand.", + "recommended_actions": "Take full advantage of using recommended labels and apply them on every resource object.", + "url": "https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/", +} + +recommended_labels := [ + "app.kubernetes.io/name", + "app.kubernetes.io/version", +] + +deny[res] { + provided := {label | input.metadata.labels[label]} + required := {label | label := recommended_labels[_]} + missing := required - provided + count(missing) > 0 + msg := sprintf("You must provide labels: %v", [missing]) + res := {"msg": msg} +} diff --git a/docs/tutorials/writing_custom_configaudit_policies.md b/docs/tutorials/writing_custom_configaudit_policies.md new file mode 100644 index 000000000..02464a05f --- /dev/null +++ b/docs/tutorials/writing_custom_configaudit_policies.md @@ -0,0 +1,218 @@ +# Writing Custom Configuration Audit Policies + +Starboard ships with a set of pre-installed configuration audit policies defined as OPA [Rego] policies. You can also +define custom policies and associate them with applicable Kubernetes resources to extend basic configuration audit +functionality. + +This tutorial will walk through the process of creating and testing a new configuration audit policy that fails whenever +a Kubernetes resource doesn't specify `app.kubernetes.io/name` or `app.kubernetes.io/version` labels. + +## Writing a Policy + +To define such a policy, you must first define its metadata. This includes setting a unique identifier, title, severity +(`CRITICAL`, `HIGH`, `MEDIUM`, `LOW`), descriptive text, and remediation steps. In Rego it's defined as the +`__rego_metadata__` rule, which defines the following composite value: + +```opa +package starboard.policy.k8s.custom + +__rego_metadata__ := { + "id": "recommended_labels", + "title": "Recommended labels", + "severity": "LOW", + "type": "Kubernetes Security Check", + "description": "A common set of labels allows tools to work interoperably, describing objects in a common manner that all tools can understand.", + "recommended_actions": "Take full advantage of using recommended labels and apply them on every resource object.", + "url": "https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/", +} +``` + +Note that the `recommended_labels` policy in scoped to the `starboard.policy.k8s.custom` package to avoid naming +collision with built-in policies that are pre-installed with Starboard. + +Once we've got our metadata defined, we need to create the logic of the policy, which is done in the `deny` or `warn` +rule. + +```opa +recommended_labels := [ + "app.kubernetes.io/name", + "app.kubernetes.io/version", +] + +deny[res] { + provided := {label | input.metadata.labels[label]} + required := {label | label := recommended_labels[_]} + missing := required - provided + count(missing) > 0 + msg := sprintf("You must provide labels: %v", [missing]) + res := {"msg": msg} +} +``` + +These matches are essentially Rego assertions, so anyone familiar with writing rules for OPA or other tools that use +Rego should find the process familiar. In this case, it’s pretty straightforward. We subtract the set of labels +specified by the `input` resource object from the set of recommended labels. The resulting set is stored in the variable +called `missing`. Finally, we check if the `missing` set is empty. If not, the `deny` rule fails with the appropriate +message. + +The `input` document is set by Starboard to a Kubernetes resource when the policy is evaluated. For pods, it would look +something like the following listing: + +```json +{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx", + "labels": { + "run": "nginx" + } + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:1.16", + } + ] + } +} +``` + +The labels set on the pod resource above can be retrieved with the following Rego expression: + +```opa +provided := {label | input.metadata.labels[label]} +``` + +You can find the complete Rego code listing in [recommended_labels.rego](./recommended_labels.rego). + +## Testing a Policy + +Now that you've created the policy, you need to test it to make sure it works as intended. To do that, add policy code to +the `starboard-policies-config` ConfigMap and associate it with any (`*`) Kubernetes resource kind: + +```yaml +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: starboard-policies-config + namespace: starboard-system + labels: + app.kubernetes.io/name: starboard-operator + app.kubernetes.io/instance: starboard-operator + app.kubernetes.io/version: "{{ git.tag[1:] }}" + app.kubernetes.io/managed-by: kubectl +data: + policy.recommended_labels.kinds: "*" + policy.recommended_labels.rego: | + package starboard.policy.k8s.custom + + __rego_metadata__ := { + "id": "recommended_labels", + "title": "Recommended labels", + "severity": "LOW", + "type": "Kubernetes Security Check", + "description": "A common set of labels allows tools to work interoperably, describing objects in a common manner that all tools can understand", + "recommended_actions": "Take full advantage of using recommended labels and apply them on every resource object.", + "url": "https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/", + } + + recommended_labels := [ + "app.kubernetes.io/name", + "app.kubernetes.io/version", + ] + + deny[res] { + provided := {label | input.metadata.labels[label]} + required := {label | label := recommended_labels[_]} + missing := required - provided + count(missing) > 0 + msg := sprintf("You must provide labels: %v", [missing]) + res := {"msg": msg} + } +``` + +In this example, to add a new policy, you must define two data entries in the `starboard-policies-config` +ConfigMap: + +1. The `policy..kinds` entry is used to designate applicable Kubernetes resources as a comma separated + list of Kubernetes kinds (e.g., `Pod,ConfigMap,NetworkPolicy`). There is also a special value (`Workload`) that you + can use to select all Kubernetes workloads, and (`*`) to select all Kubernetes resources recognized by Starboard. +2. The `policy..rego` entry holds the policy Rego code. + +Starboard automatically detects policies added to the `starboard-policies-config` ConfigMap and immediately rescans +applicable Kubernetes resources. + +Let's create the `test` ConfigMap without recommended labels: + +```console +$ kubectl create cm test --from-literal=foo=bar +configmap/test created +``` + +When you retrieve the corresponding configuration audit report, you'll see that there is one check with `LOW` severity +that's failing: + +```console +$ kubectl get configauditreport configmap-test -o wide +NAME SCANNER AGE CRITIAL HIGH MEDIUM LOW +configmap-test Starboard 24s 0 0 0 1 +``` + +If you describe the report you'll see that it's failing because of our custom policy: + +``` { .yaml .annotate } +apiVersion: aquasecurity.github.io/v1alpha1 +kind: ConfigAuditReport +metadata: + labels: + starboard.resource.kind: ConfigMap + starboard.resource.name: test + starboard.resource.namespace: default + plugin-config-hash: df767ff5f + resource-spec-hash: 7c96769cf + name: configmap-test + namespace: default + ownerReferences: + - apiVersion: v1 + blockOwnerDeletion: false + controller: true + kind: ConfigMap + name: test +report: + scanner: + name: Starboard + vendor: Aqua Security + version: {{ git.tag }} + summary: + criticalCount: 0 + highCount: 0 + lowCount: 1 + mediumCount: 0 + checks: + - checkID: recommended_labels # (1) + title: Recommended labels # (2) + severity: LOW # (3) + category: Kubernetes Security Check # (4) + description: | # (5) + A common set of labels allows tools to work interoperably, + describing objects in a common manner that all tools can + understand. + success: false # (6) + messages: # (7) + - 'You must provide labels: {"app.kubernetes.io/name", "app.kubernetes.io/version"}' +``` + +1. The `checkID` property corresponds to the policy identifier, i.e. `__rego_meatadata__.id`. +2. The `title` property as defined by the policy metadata in `__rego_metadata__.title`. +3. The `severity` property as defined by the policy metadata in `__rego_metadata__.severity`. +4. The `category` property as defined by the policy metadata in `__rego_metadata__.type`. +5. The `description` property as defined by the policy metadata in `__rego_metadata__.description`. +6. The flag indicating whether the configuration audit check has failed or passed. +7. The array of messages with details in case of failure. + +[Rego]: https://www.openpolicyagent.org/docs/latest/#rego + +[recommended labels]: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels diff --git a/mkdocs.yml b/mkdocs.yml index 2e08c0be8..3d48c92fc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,6 +44,7 @@ nav: - Octant Plugin: integrations/octant.md - Lens Extension: integrations/lens.md - Tutorials: + - Writing Custom Configuration Audit Policies: tutorials/writing_custom_configaudit_policies.md - Manage Access to Security Reports: tutorials/manage_access_to_security_reports.md - Custom Resource Definitions: - Overview: crds/index.md