Skip to content

Commit

Permalink
Extend Worshop
Browse files Browse the repository at this point in the history
Include section about:
- parametrizing rules
- node rules
- common types of rules and their templates
  • Loading branch information
yuumasato committed Sep 26, 2023
1 parent c93d4e6 commit e2fd9d4
Show file tree
Hide file tree
Showing 4 changed files with 375 additions and 2 deletions.
4 changes: 2 additions & 2 deletions doc/tutorials/workshop/content/exercises/09-writing-rules.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
Title: Troubleshooting
PrevPage: 08-creating-profile-bundles
NextPage: ../finish
NextPage: 10-rule-parametrization
---
Writing your own Rules
======================
Expand Down Expand Up @@ -198,4 +198,4 @@ $ ./utils/add_platform_rule.py cluster-test --rule must_have_compliant_cm
Once you've tested your rule and feel its in a good shape, you should fill in the missing
parameters from the template so they'll appear nicely in the report. Finally, you can add
the rule to a relevant profile in the `ocp4/profiles/` directory, build it, upload it to a
`ProfileBundle` and take it into use as part of your regular compliance scans!
`ProfileBundle` and take it into use as part of your regular compliance scans!
189 changes: 189 additions & 0 deletions doc/tutorials/workshop/content/exercises/10-rule-parametrization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
---
Title: Rule parametrization
PrevPage: 09-writing-rules
NextPage: 11-node-rules
---

Rule parametrization
====================

In this section we will learn how one can make their rules more flexible with
parametrization, which allows us to slightly alter what a rule is checking for.
We will leverage `TailoredProfiless` to create our own `Profile` that enables a
`Rule` and tailors a `Variable`.

Additionally, we will go through more advanced testing procedures which will
be required to test our `TailoredProfile`.

## Variable parametrization

The rule we created initially accepted only `yep` as a compliant value, and
later we extended it to accept anything that matched a regular expression.

What if we had two different sets of clusters with distinct compliance needs.
One cluster needs to have a `compliant` value of `yep`, and the other the
value of `yes`.

The usual way of checking for different needs is to have different profiles.
So in our scenarios we would have one profile for each cluster.

But our rule, with its regular expression, accepts both values as compliant.
We cannot simply use the rule for both profiles and be sure that compliance
requirements for each cluster is met, as both values lead the rule to a
`COMPLIANT` result.

We could have two separate rules, each checking for one of the values, and
add them to their respective profiles.
But this creates two seemingly identical rules and increases our maintenance
costs. What if we had more clusters each with its different compliance needs?

The better approach is to parametrize the rule with a variable, and tailor
the variable to the value we want to assess in each profile.

### Creating the variable and testing basic functionality

So lets first create a variable named `var-my-compliant-value` as follows:
```
$ cat << EOF > applications/openshift/var-my-compliant-value.var
documentation_complete: true
title: 'My compliant value'
description: |-
The value that 'my-compliant-configmap' should have in the key 'compliant'.
Default value is 'yep'.
Other possible values are 'yes' and 'definitely.
type: string
operator: equals
options:
default: yep
yep: yep
yes: yes
definitely: definitely
EOF
```

Then run the following command to change the rule to use the variable
we just created:
```
$ ./utils/add_platform_rule.py create \
--rule must_have_compliant_cm \
--name my-compliance-configmap --namespace openshift --type configmap \
--title "Must have compliant CM" \
--description "The deployment must have a CM that's compliant with.... life!" \
--match-entity "at least one" \
--yamlpath '.data.compliant' \
--variable "var-my-compliant-value"
```

Our `ConfigMap` still has an incompliant value, if we test the rule right now
it will evaluate to `NON-COMPLIANT`.

Let's update it to one of the selections available in the variable.
The default value is `yep`, and that is the variable's value if used without any tailoring.
```
$ oc patch -n openshift configmap my-compliance-configmap \
-p '{"data": {"compliant": "yep"}}' --type=merge
```
```
$ ./utils/add_platform_rule.py cluster-test --rule must_have_compliant_cm
...
* The result is 'COMPLIANT'
```

### Testing Rules with Profile Tailorings

So far we have been using the `./utils/add_platform_rule.py` script to test
our rule. It creates very specific `ComplianceScans` that cannot cover all the use
cases.

For that reason we will now leverage the `ProfileBundles` created by the
`utils/build_ds_container.py` to test the rule customization with
`TailoredProfiles`.

Build and push the content to you cluster while creating `ProfileBundles` with
the following command:
```
$ ./utils/build_ds_container.py -p
```

One aspect to note though, is that `Profiles`, `Rules`, `Variables` and `Remediations`
created by this command will have the `upstream` prefix.
For example, our rule will be named `upstream-ocp4-must-have-compliant-cm`.

First, let's update the `ConfigMap` to one of the compliant values from the
variable:
```
$ oc patch -n openshift configmap my-compliance-configmap \
-p '{"data": {"compliant": "definitely"}}' --type=merge
```

Create a `TailoredProfile` that enables only our rule and changes the value of
the variable to check for `definitely`:
```
$ cat << EOF > my-own-profile.yaml
apiVersion: compliance.openshift.io/v1alpha1
kind: TailoredProfile
metadata:
name: my-own-profile
namespace: openshift-compliance
spec:
description: My compliance profile for OCP4
title: OCP4 profile with customized compliance value
enableRules:
- name: upstream-ocp4-must-have-compliant-cm
rationale: Scan with our new rule.
setValues:
- name: upstream-ocp4-var-my-compliant-value
rationale: The cluster scanned needs to be definitely compliant.
value: definitely
EOF
```

In the `TailoredProfile` above we are enabling rule `upstream-ocp4-must-have-compliant-cm`
and customizing the value of variable `upstream-ocp4-var-my-compiant-value`.

Then, to enable scans with our `TailoredProfile` bind it to a `ScanSetting` of your
choice, we will use the `default` `ScanSetting` which is available right after
install.
```
$ cat << EOF > default-own-profile.yaml
apiVersion: compliance.openshift.io/v1alpha1
kind: ScanSettingBinding
metadata:
name: default-own-profile
namespace: openshift-compliance
profiles:
- name: my-own-profile
kind: TailoredProfile
apiGroup: compliance.openshift.io/v1alpha1
settingsRef:
name: default
kind: ScanSetting
apiGroup: compliance.openshift.io/v1alpha1
EOF
```

```
$ oc create -f my-own-profile.yaml
tailoredprofile.compliance.openshift.io/my-own-profile created
$ oc create -f default-own-profile.yaml
scansettingbinding.compliance.openshift.io/default-own-profile created
```

After the `ScanSettingBinding` creation is processed a `ComplianceSuite`
will be created and evaluation with our `TailoredProfile` started.

After a few minutes check that the scan finished with result `COMPLIANT`
```
$ oc get compliancescan
NAME PHASE RESULT
my-own-profile DONE COMPLIANT
```

Our rule is ready to be enabled in multiple profiles checking different values
in each `Profile`.
86 changes: 86 additions & 0 deletions doc/tutorials/workshop/content/exercises/11-node-rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
Title: Node Rules
PrevPage: 10-rule-parametrization
NextPage: 12-common-rules
---

Node Rules
===================

## Platform vs Node rules

One important distinction to make is whether the configuration being checked is
a Kubernetes resource (a Platform check) or a setting in the node of the
cluster (a node check).

The Platform rules check the Kubernetes API Resources.
While the node rules check configuration of the node's operating system.

So far the rules we created were Platform rules, they checked a Kubernetes
configuration. But one can also check configurations of the operating
system at the node level, and that is what we'll be doing in this section.

## Creating Node rules

Let's create a rule that checks whether the file `/etc/system-release` is
owned by root in our cluster's nodes.

To create this node rule, execute the following `create node` command:
```
$ ./utils/add_platform_rule.py create node \
--rule file_owner_etc_system_release \
--title "File /etc/system-release must be owned by root" \
--description "We need to ensure that root owns the system release file" \
--template file_owner \
--template-vars "filepath: /etc/system-release, fileuid: '0'"
```

We already know the `rule`, `title` and `description` are for, they are
the same arguments passed when creating a `Platform` rule.
The `template` argument is used to specify which template to use, and
`template-vars` is a comma separated string with the values to be used.

If you are curious about what templates are available, don't worry,
in the next section we will go through the most used templates and their
input arguments.

Here is another example of how to quickly generate a a node rule that checks
the sysctl `kernel.randomize_va_space` value:
```
$ ./utils/add_platform_rule.py create node \
--rule sysctl_kernel_randomize_va_space \
--title "Ensure ASLR is fully enabled" \
--description "Make it harder to exploit vulnerabilities by employing full address space layout randomization" \
--template sysctl \
--template-vars "sysctlvar: kernel.randomize_va_space, sysctlval: '2', datatype: int"
```

### Selecting the nodes to check

When we created our node rule wi`--scan-type option.
This, by default, creates a rule that is applicable to all nodes in the cluster,
i.e.: `worker` and `master` nodes.
To restrict a node rule to scan only on master nodes, you need to change the
`platform` key in the `rule.yml` to `ocp4-master-node`.

To set a node check to run on all nodes, set the rule's platform to:
`platform: {{{ product }}}-node`, or more explicitly `platform: ocp4-node`

To set a node check to run on only on master nodes, set the rule's platform to:
`platform: {{{ product }}}-master-node`

### Use of yamlfile_value on node rules

The `yamlfile_value` template is a template like any other in CaC/content
project, its purpose is to assert whether a yaml key's value satisfiyes a
certain criteria.

The use of this template is not limited to platform rules, it is very handy
to check configurations defined in yaml file on the node.

One such example is the Kubelet configuration in each node. The
`/etc/kubernetes/kubelet.conf` is a yaml that can be checked using the
`yamlfile_value` template.

Check the rule [kubelet_enable_cert_rotation](https://github.com/ComplianceAsCode/content/blob/master/applications/openshift/kubelet/kubelet_enable_cert_rotation/rule.yml)
for an example of how the `yamlfile_value` template is used.
98 changes: 98 additions & 0 deletions doc/tutorials/workshop/content/exercises/12-common-rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
Title: All types of rules
PrevPage: 11-node-rules
NextPage: ../finish
---
Common types of rules
===================

The rules we created in the previous chapters leveraged the `yamlfile_value`,
`file_permissions` and `sysctl` template from CaC/content project. There are
many more templates available in the project and each one enable us to quickly
create rules with a specific behavior.

In this section we will go through the most common types of checks when
assessing the security posture of a cluster and the templates used to
implement them.

## Platform checks

### Checking Kubernetes resources

Checking the configuration of a Kubernetes resource is the obvious task when
one wants to check the cluster's security posture.
This type of check is handled by the [yamlfile_value](https://complianceascode.readthedocs.io/en/latest/templates/template_reference.html#yamlfile-value)
template.

You can write a rule and make use of the template by yourself, or
use the `./utils/add_platform_rule.py` script showcased in past sections.
Note that more advanced uses of the template will require you to write the input data by yourself.

One example of this type of rule is a checking whether any registry configured
for import allow use of insecure protocols. The rule checks the Cluster API and
assess whether any `allowedRegistriesForImport` has `'{"insecure": true}':
[ocp_insecure_allowed_registries_for_import](https://github.com/ComplianceAsCode/content/blob/master/applications/openshift/registry/ocp_insecure_allowed_registries_for_import/rule.yml)

Another example is checking whether RBAC roles are defined.
[rbac_roles_defined](https://github.com/ComplianceAsCode/content/blob/master/applications/openshift/rbac/rbac_roles_defined/rule.yml)

## Node checks

### Checking for a KubeletConfig setting

With Kubelet being the primary agent on the node, its configuration becomes
important to assess.

You can also use the [yamlfile_value](https://complianceascode.readthedocs.io/en/latest/templates/template_reference.html#yamlfile-value)
template for this. Note though that these checks are Node checks, not Platform checks.

For example, this rule ensures that the node does not have a `kubelet.conf` that defines
a `KubeletConfiguration` with an `authorization.mode` with value equal to `AllowAll`:

[kubelet_authorization_mode](https://github.com/ComplianceAsCode/content/blob/master/applications/openshift/kubelet/kubelet_authorization_mode/rule.yml)


### Checking ownership and permissions of files

A very common requirement is to ensure that files in the cluster node have
appropriate ownership and permissions.

To check the owner, group owner or permissions in files use the following
templates,
[file_owner](https://complianceascode.readthedocs.io/en/latest/templates/template_reference.html#file-owner),
[file_groupowner](https://complianceascode.readthedocs.io/en/latest/templates/template_reference.html#file-groupowner),
[file_permissions](https://complianceascode.readthedocs.io/en/latest/templates/template_reference.html#file-permissions)
respectivetly

For example, to ensure the permissions of file `/etc/passwd` in the node is
'0644' write a new rule and use the template `file_permissions`:

```
template:
name: file_permissions
vars:
filepath: /etc/passwd
filemode: '0644'
```

Check rule [file_permissions_etc_passwd](https://github.com/ComplianceAsCode/content/blob/cc4375ca0cb7f8aa3a789ba619504c7590e7af21/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_etc_passwd/rule.yml) for a complete example.

### Checking kernel parameters

The Kernel parameters are also an important setting that can be checked.

### sysctl
To check for sysctl parameters on the node use the the
[sysctl](https://complianceascode.readthedocs.io/en/latest/templates/template_reference.html#sysctl) template.
For example, to ensure that `kernel.panic_on_oops` is set to one, write a rule
with the following template.

```
template:
name: sysctl
vars:
sysctlvar: kernel.panic_on_oops
sysctlval: '1'
datatype: int
```
Check rule [kubelet_enable_protect_kernel_sysctl_kernel_panic_on_oops](https://github.com/ComplianceAsCode/content/blob/master/applications/openshift/kubelet/kubelet_enable_protect_kernel_sysctl_kernel_panic_on_oops/rule.yml) for a complete example.

0 comments on commit e2fd9d4

Please sign in to comment.