Skip to content

Commit

Permalink
docs(tutorial): Writing Custom Configuration Audit Policies (aquasecu…
Browse files Browse the repository at this point in the history
…rity#1075)

Co-authored-by: chenk <hen.keinan@gmail.com>
Co-authored-by: AnaisUrlichs <urlichsanais@gmail.com>

Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>
  • Loading branch information
danielpacak authored Mar 28, 2022
1 parent aef53b2 commit 689ef5f
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 0 deletions.
25 changes: 25 additions & 0 deletions docs/tutorials/recommended_labels.rego
Original file line number Diff line number Diff line change
@@ -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}
}
218 changes: 218 additions & 0 deletions docs/tutorials/writing_custom_configaudit_policies.md
Original file line number Diff line number Diff line change
@@ -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.<your_policy_name>.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.<your_policy_name>.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
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 689ef5f

Please sign in to comment.