Skip to content

Commit

Permalink
Merge pull request #1 from viccuad/hostpaths
Browse files Browse the repository at this point in the history
Add implementation of hostpaths policy
  • Loading branch information
viccuad authored Aug 12, 2021
2 parents a6cd75d + 1e197e0 commit 6b6eff8
Show file tree
Hide file tree
Showing 27 changed files with 1,532 additions and 1,317 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ jobs:
if: github.event.workflow_run.event != 'pull_request'
runs-on: ubuntu-latest
env:
OCI_TARGET: #TODO: change to something like ghcr.io/kubewarden/policies/safe-labels
OCI_TARGET: ghcr.io/kubewarden/policies/psp-hostpaths
KWCTL_VERSION: v0.1.10

steps:
Expand Down
132 changes: 30 additions & 102 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,115 +1,43 @@
# go-policy-template
# psp-hostpaths-policy

This is a template repository that can be used to to quickly scaffold a
Kubewarden policy written with Go language.
Replacement for the Kubernetes Pod Security Policy that controls the usage of
`hostPath` volumes. The policy inspects both the containers and the init
containers that are using `hostPath` volumes.

Don't forget to checkout Kubewarden's [official documentation](https://docs.kubewarden.io)
for more information about writing policies.
## Settings

## Introduction

This repository contains a working policy written in Go.

The policy looks at the `name` of a Kubernetes resource and rejects the request
if the name is on a deny list.

The deny list is configurable by the user via the runtime settings of the policy.
The configuration of the policy is expressed via this structure:

```json
{
"denied_names": [ "badname1", "badname2" ]
}
```yaml
allowedHostPaths:
- pathPrefix: "/foo"
readOnly: true
- pathPrefix: "/bar"
readOnly: false
```
## Code organization

The code that takes care of parsing the settings can be found inside of the
`settings.go` file.

The actual validation code is defined inside of the `validate.go` file.

The `main.go` contains only the code which registers the entry points of the
policy.

## Implementation details

> **DISCLAIMER:** WebAssembly is a constantly evolving topic. This document
> describes the status of the Go ecosystem at April 2021.
Currently the official Go compiler cannot produce WebAssembly binaries
that can be run **outside** of the browser. Because of that, Kubewarden Go
policies can be built only with the [TinyGo](https://tinygo.org/) compiler.

TinyGo doesn't yet support all the Go features (see [here](https://tinygo.org/lang-support/)
to see the current project status). Currently its biggest limitation
is the lack of a fully supported `reflect` package. Among other things, that
leads to the inability to use the `encoding/json` package against structures
and user defined types.
`allowedHostPaths` is a list of host paths that are allowed to be used by
`hostPath` volumes.

Kubewarden policies need to process JSON data like the policy settings and
the actual request received by Kubernetes.
However it's still possible to write a Kubewarden policy by using some 3rd party
libraries.
An empty `allowedHostPaths` list means there is no restriction on host paths
used.

This is a list of libraries that can be useful when writing a Kubewarden
policy:
Each entry of `allowedHostPaths` must have:
- A `pathPrefix` field, which allows `hostPath` volumes to mount a path that
begins with an allowed prefix.
- a `readOnly` field indicating it must be mounted read-only.

* Parsing JSON: queries against JSON documents can be written using the
[gjson](https://github.com/tidwall/gjson) library. The library features a
powerful query language that allows quick navigation of JSON documents and
data retrieval.
* Mutating JSON: changing the contents of a JSON document can be done using the
[sjson](https://github.com/tidwall/sjson) library.
* Generic `set` implementation: using [Set](https://en.wikipedia.org/wiki/Set_(abstract_data_type))
data types can significantly reduce the amount of code inside of a policy,
see the `union`, `intersection`, `difference`,... operations provided
by a Set implementation.
The [mapset](https://github.com/deckarep/golang-set) can be used when writing
policies.
### Special behaviour

Last but not least, this policy takes advantage of helper functions provided
by [Kubewarden's Go SDK](https://github.com/kubewarden/policy-sdk-go).
It's possible to have host paths sharing part of the prefix. In that case, the
`readOnly` attribute of the most specific path takes precedence.

## Testing
For example, given the following configuration:

This policy comes with a set of unit tests implemented using the Go testing
framework.

As usual, the tests are defined inside of the `_test.go` files. Given these
tests are not part of the final WebAssembly binary, the official Go compiler
can be used to run them. Hence they can take advantage of the `encoding/json`
package to reduce some testing boiler plate.

The unit tests can be run via a simple command:

```shell
make test
```yaml
allowedHostPaths:
- pathPrefix: "/foo"
readOnly: false
- pathPrefix: "/foo/bar"
readOnly: true
```

It's also important the test the final result of the TinyGo compilation:
the actual WebAssembly module.

This is done by a second set of end-to-end tests. These tests use the
`policicy-testdrive` cli provided by the Kubewarden project to load and execute
the policy.

The e2e tests are implemented using [bats](https://github.com/sstephenson/bats):
the Bash Automated Testing System.

The end-to-end tests are defined inside of the `e2e.bats` file and can
be run via this commmand:

```shell
make e2e-tests
```

## Automation

This project contains the following [GitHub Actions](https://docs.github.com/en/actions):

* `e2e-tests`: this action builds the WebAssembly policy, installs
the `bats` utility and then runs the end-to-end test
* `unit-tests`: this action runs the Go unit tests
* `release`: this action builds the WebAssembly policy and pushes it to a
user defined OCI registry ([ghcr](https://ghcr.io) is a perfect candidate)
Paths such as `/foo/bar/dir1`, `/foo/bar` must be read only.
30 changes: 23 additions & 7 deletions e2e.bats
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
#!/usr/bin/env bats

@test "reject because name is on deny list" {
run kwctl run policy.wasm -r test_data/ingress.json --settings-json '{"denied_names": ["foo", "tls-example-ingress"]}'
@test "reject because /data is not in settings" {
run kwctl run policy.wasm -r test_data/request-pod-hostpaths.json \
--settings-json \
'{ "allowedHostPaths": [
{"pathPrefix": "/var","readOnly": false},
{"pathPrefix": "/var/local/aaa","readOnly": false}
]
}'

# this prints the output when one the checks below fails
echo "output = ${output}"

# request rejected
[ "$status" -eq 0 ]
[ $(expr "$output" : '.*allowed.*false') -ne 0 ]
[ $(expr "$output" : ".*The 'tls-example-ingress' name is on the deny list.*") -ne 0 ]
[ $(expr "$output" : ".*hostPath '/data' mounted as 'test-data' is not in the AllowedHostPaths list.*") -ne 0 ]
}

@test "accept because name is not on the deny list" {
run kwctl run policy.wasm -r test_data/ingress.json --settings-json '{"denied_names": ["foo"]}'
@test "accept because pod has no hostPath volumes" {
run kwctl run policy.wasm -r test_data/request-pod-no-hostpaths.json \
--settings-json \
'{ "allowedHostPaths": [ {"pathPrefix": "/foo","readOnly": true} ] }'

# this prints the output when one the checks below fails
echo "output = ${output}"

Expand All @@ -22,8 +31,15 @@
[ $(expr "$output" : '.*allowed.*true') -ne 0 ]
}

@test "accept because the deny list is empty" {
run kwctl run policy.wasm -r test_data/ingress.json
@test "accept because /var/local has precedence over /var" {
run kwctl run policy.wasm -r test_data/request-pod-precedence.json \
--settings-json \
'{ "allowedHostPaths": [
{"pathPrefix": "/var","readOnly": false},
{"pathPrefix": "/var/local","readOnly": true}
]
}'

# this prints the output when one the checks below fails
echo "output = ${output}"

Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/kubewarden/go-policy-template
go 1.15

require (
github.com/deckarep/golang-set v1.7.1
github.com/francoispqt/onelog v0.0.0-20190304085423-060a273d6185
github.com/kubewarden/gjson v1.7.2
github.com/kubewarden/policy-sdk-go v0.1.2
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/francoispqt/gojay v0.0.0-20181220093123-f2cc13a668ca h1:F2BD6Vhei4w0rtm4eNpzylNsB07CcCbpYA+xlqMx3mA=
github.com/francoispqt/gojay v0.0.0-20181220093123-f2cc13a668ca/go.mod h1:H8Wgri1Asi1VevY3ySdpIK5+KCpqzToVswNq8g2xZj4=
github.com/francoispqt/onelog v0.0.0-20190304085423-060a273d6185 h1:EPOFlRpdU2+9wDoJeLNGyg9JPZGoBXMg7JReK/phXK8=
Expand Down
20 changes: 10 additions & 10 deletions hub.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
name: policy-name
description: DESCRIPTION OF YOUR POLICY
homepage: POLICY HOMEPAGE URL
name: psp-hostpaths
description: PSP that controls usage of hostPath volumes
homepage: https://github.com/kubewarden/psp-hostpaths-policy
author:
name: policy-authors
homepage: https://author1.website
name: Kuberwarden devs
homepage: https://github.com/kubewarden
download:
# Important: leave the __TAG__ around: this is automatically replaced with the value of the git tag
registry: <registry>/<policy-name>:__TAG__
# url is optional
url: https://github.com/yourorg/policy-name/releases/download/__TAG__/policy.wasm
registry: ghcr.io/kubewarden/policies/psp-hostpaths:__TAG__
url: https://github.com/kubewarden/psp-hostpaths-policy/releases/download/__TAG__/policy.wasm
keywords:
- this is freeform
- PSP
- Hostpaths
- Pod
resources:
- Pod
mutation: false
Expand Down
52 changes: 45 additions & 7 deletions metadata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,51 @@ rules:
mutating: false
contextAware: false
annotations:
io.kubewarden.policy.title: policy-name
io.kubewarden.policy.description: Short description
io.kubewarden.policy.author: policy-author
io.kubewarden.policy.url: https://github.com/yourorg/policy-name
io.kubewarden.policy.source: https://github.com/yourorg/policy-name
io.kubewarden.policy.title: psp-hostpaths
io.kubewarden.policy.description: PSP that controls usage of hostPath volumes
io.kubewarden.policy.author: Kubewarden devs
io.kubewarden.policy.url: https://github.com/kubewarden/psp-hostpaths-policy
io.kubewarden.policy.source: https://github.com/kubewarden/psp-hostpaths-policy
io.kubewarden.policy.license: Apache-2.0
io.kubewarden.policy.usage: |
Long explaination.
Replacement for the Kubernetes Pod Security Policy that controls the usage of
`hostPath` volumes. The policy inspects both the containers and the init
containers that are using `hostPath` volumes.
**Note well:** this can be Markdown text
## Settings
```yaml
allowedHostPaths:
- pathPrefix: "/foo"
readOnly: true
- pathPrefix: "/bar"
readOnly: false
```
`allowedHostPaths` is a list of host paths that are allowed to be used by
hostPath volumes.
An empty `allowedHostPaths` list means there is no restriction on host paths
used.
Each entry of `allowedHostPaths` must have:
- A `pathPrefix` field, which allows hostPath volumes to mount a path that
begins with an allowed prefix.
- a `readOnly` field indicating it must be mounted read-only.
### Special behaviour
It's possible to have host paths sharing part of the prefix. In that case, the
`readOnly` attribute of the most specific path takes precedence.
For example, given the following configuration:
```yaml
allowedHostPaths:
- pathPrefix: "/foo"
readOnly: false
- pathPrefix: "/foo/bar"
readOnly: true
```
Paths such as `/foo/bar/dir1`, `/foo/bar` must be read only.
Loading

0 comments on commit 6b6eff8

Please sign in to comment.