Skip to content

Commit

Permalink
User and group PodSecurityPolicy added.
Browse files Browse the repository at this point in the history
Adds the logic to enforce the same behaviour from the Kubernetes
PodSecurityPolicy user to control user and groups used in the
containers.
  • Loading branch information
jvanz committed Oct 4, 2021
1 parent f900b56 commit e0bcc1f
Show file tree
Hide file tree
Showing 34 changed files with 4,007 additions and 109 deletions.
581 changes: 581 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
slog = "2.7"
wapc-guest = "0.4.0"
anyhow = "1.0"

[dev-dependencies]
jsonpath_lib = "0.2.6"
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ fmt:
lint:
cargo clippy -- -D warnings

.PHONY: e2e-test
e2e-test: build
bats e2e.bats

.PHONY: test
test: fmt lint
cargo test
Expand Down
187 changes: 186 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,195 @@
This Kubewarden Policy is a replacement for the Kubernetes Pod Security
Policy that controls containers [user and groups](https://kubernetes.io/docs/concepts/policy/pod-security-policy/#users-and-groups).

This policy is used to control users and groups in containers.

## Installation

Once you have Kuberwarden installed in you Kubernetes cluster, you can install
the policy with the following command:

```bash
kubectl apply -f - <<EOF
apiVersion: policies.kubewarden.io/v1alpha2
kind: ClusterAdmissionPolicy
metadata:
name: psp-user-group
spec:
policyServer: default
module: registry://ghcr.io/kubewarden/policies/psp-user-group:latest
rules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
operations:
- CREATE
- UPDATE
mutating: true
settings:
run_as_user:
rule: "MustRunAs"
ranges:
- min: 1000
max: 2000
- min: 4000
max: 5000
run_as_group:
rule: "RunAsAny"
supplemental_groups:
rule: "RunAsAny"
EOF
```

You can see more information about the setting in the following section.

## Settings


The policy has three settings:

* `run_as_user`: Controls which user ID the containers are run with.
* `run_as_group`: Controls which primary group ID the containers are run with.
* `supplemental_groups`: Controls which group IDs containers add.

All the three settings are JSON objects composed by two attributes: `rule` and `ranges`. The `rule` attribute defines
the strategy used by the policy to enforce users and groups used in containers. The available strategies are:

* `run_as_user`:
* `MustRunAs` - Requires at least one range to be specified. Uses the minimum value of the first range as the default. Validates against all ranges.
* `MustRunAsNonRoot` - Requires that the pod be submitted with a non-zero `runAsUser` or have the `USER` directive defined (using a numeric UID) in the image. Pods which have specified neither `runAsNonRoot` nor `runAsUser` settings will be mutated to set `runAsNonRoot=true`, thus requiring a defined non-zero numeric `USER` directive in the container. No default provided.
* `RunAsAny` - No default provided. Allows any `runAsUser` to be specified.
* `run_as_group`:
* `MustRunAs` - Requires at least one range to be specified. Uses the minimum value of the first range as the default. Validates against all ranges.
* `MayRunAs` - Does not require that `RunAsGroup` be specified. However, when `RunAsGroup` is specified, they have to fall in the defined range.
* `RunAsAny` - No default provided. Allows any `runAsGroup` to be specified.
* `supplemental_groups`:
* `MustRunAs` - Requires at least one range to be specified. Uses the minimum value of the first range as the default. Validates against all ranges.
* `MayRunAs` - Requires at least one range to be specified. Allows `supplementalGroups` to be left unset without providing a default. Validates against all ranges if `supplementalGroups` is set.
* `RunAsAny` - No default provided. Allows any `supplementalGroups` to be specified

The `ranges` is a list of JSON objects with two attributes: `min` and `max`. Each range object define the user/group ID range used by the rule.

### Examples

To enforce that user and groups must be set and it should be in the defined ranges:

```json
{
"run_as_user": {
"rule": "MustRunAs",
"ranges": [
{
"min": 1000,
"max": 1999
},
{
"min": 3000,
"max": 3999
}
]
},
"run_as_group": {
"rule": "MustRunAs",
"ranges": [
{
"min": 1000,
"max": 1999
},
{
"min": 3000,
"max": 3999
}
]
},
"supplemental_groups":{
"rule": "MustRunAs",
"ranges": [
{
"min": 1000,
"max": 1999
},
{
"min": 3000,
"max": 3999
}
]
}
}
```

To allow any user and group:

```json
{
"run_as_user": {
"rule": "RunAsAny"
},
"run_as_group": {
"rule": "RunAsAny"
},
"supplemental_groups":{
"rule": "RunAsAny"
}
}
```

To force running the container with non root user but any group:

```json
{
"run_as_user": {
"rule": "MustRunAsNonRoot"
},
"run_as_group": {
"rule": "RunAsAny"
},
"supplemental_groups":{
"rule": "RunAsAny"
}
}
```

To enforce a group when the container has some group defined

```json
{
"run_as_user": {
"rule": "RunAsAny"
},
"run_as_group": {
"rule": "MayRunAs",
"ranges": [
{
"min": 1000,
"max": 2000
},
{
"min": 2001,
"max": 3000
}
]
},
"supplemental_groups":{
"rule": "MayRunAs",
"ranges": [
{
"min": 1000,
"max": 2000
},
{
"min": 2001,
"max": 3000
}
]
}
}
```


## License

```
Copyright (C) 2021 José Guilherme Vanz <jvanz@jvanz.com>
Copyright (C) 2021 José Guilherme Vanz <jguilhermevanz@suse.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
93 changes: 93 additions & 0 deletions e2e.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env bats

@test "RunAsAny should accept empty runAsUser, runAsGroup and supplementalGroups" {
run kwctl run --request-path test_data/e2e/empty_security_context_pod.json --settings-path test_data/e2e/settings_run_as_any.json target/wasm32-unknown-unknown/release/psp_user_group.wasm
[ "$status" -eq 0 ]
echo "$output"
[ $(expr "$output" : '.*"allowed":true.*') -ne 0 ]
}

@test "MustRunAs should reject invalid user ID" {
run kwctl run --request-path test_data/e2e/invalid_user_id.json --settings-path test_data/e2e/settings_must_run_as.json target/wasm32-unknown-unknown/release/psp_user_group.wasm
[ "$status" -eq 0 ]
echo "$output"
[ $(expr "$output" : '.*"allowed":false.*') -ne 0 ]
[ $(expr "$output" : '.*"message":"User ID outside defined ranges".*') -ne 0 ]
}

@test "MustRunAs should reject invalid group ID" {
run kwctl run --request-path test_data/e2e/invalid_group_id.json --settings-path test_data/e2e/settings_must_run_as.json target/wasm32-unknown-unknown/release/psp_user_group.wasm
[ "$status" -eq 0 ]
echo "$output"
[ $(expr "$output" : '.*"allowed":false.*') -ne 0 ]
[ $(expr "$output" : '.*"message":"Group ID is outside defined ranges".*') -ne 0 ]
}

@test "MustRunAs should reject invalid supplemental group ID" {
run kwctl run --request-path test_data/e2e/invalid_supplemental_group_.json --settings-path test_data/e2e/settings_must_run_as.json target/wasm32-unknown-unknown/release/psp_user_group.wasm
[ "$status" -eq 0 ]
echo "$output"
[ $(expr "$output" : '.*"allowed":false.*') -ne 0 ]
[ $(expr "$output" : '.*"message":"Group ID is outside defined ranges".*') -ne 0 ]
}

@test "MustRunAs should patch empty runAsUser, runAsGroup and supplementalGroups" {
run kwctl run --request-path test_data/e2e/empty_security_context_pod.json --settings-path test_data/e2e/settings_must_run_as.json target/wasm32-unknown-unknown/release/psp_user_group.wasm
[ "$status" -eq 0 ]
echo "$output"
[ $(expr "$output" : '.*"allowed":true.*') -ne 0 ]
[ $(expr "$output" : '.*"patchType":"JSONPatch".*') -ne 0 ]
}

@test "MayRunAs should accept empty runAsGroup and supplementalGroups" {
run kwctl run --request-path test_data/e2e/empty_security_context_pod.json --settings-path test_data/e2e/settings_may_run_as.json target/wasm32-unknown-unknown/release/psp_user_group.wasm
[ "$status" -eq 0 ]
echo "$output"
[ $(expr "$output" : '.*"allowed":true.*') -ne 0 ]
}

@test "MayRunAs should reject invalid group ID" {
run kwctl run --request-path test_data/e2e/invalid_group_id.json --settings-path test_data/e2e/settings_may_run_as.json target/wasm32-unknown-unknown/release/psp_user_group.wasm
[ "$status" -eq 0 ]
echo "$output"
[ $(expr "$output" : '.*"allowed":false.*') -ne 0 ]
[ $(expr "$output" : '.*"message":"Group ID is outside defined ranges".*') -ne 0 ]
}

@test "MayRunAs should reject invalid supplemental group ID" {
run kwctl run --request-path test_data/e2e/invalid_supplemental_group_.json --settings-path test_data/e2e/settings_may_run_as.json target/wasm32-unknown-unknown/release/psp_user_group.wasm
[ "$status" -eq 0 ]
echo "$output"
[ $(expr "$output" : '.*"allowed":false.*') -ne 0 ]
[ $(expr "$output" : '.*"message":"Group ID is outside defined ranges".*') -ne 0 ]
}

@test "MustRunAs should accept valid runAsUser, runAsGroup and supplementalGroups" {
run kwctl run --request-path test_data/e2e/valid_security_context.json --settings-path test_data/e2e/settings_must_run_as.json target/wasm32-unknown-unknown/release/psp_user_group.wasm
[ "$status" -eq 0 ]
echo "$output"
[ $(expr "$output" : '.*"allowed":true.*') -ne 0 ]
}

@test "MustRunAsNonRoot should reject 0 as user ID" {
run kwctl run --request-path test_data/e2e/zero_as_user_id.json --settings-path test_data/e2e/settings_must_run_as_non_root.json target/wasm32-unknown-unknown/release/psp_user_group.wasm
[ "$status" -eq 0 ]
echo "$output"
[ $(expr "$output" : '.*"allowed":false.*') -ne 0 ]
[ $(expr "$output" : '.*"message":"Invalid user ID: cannot run container with root ID (0)".*') -ne 0 ]
}

@test "MustRunAsNonRoot should mutate request when runAsUser is not defined" {
run kwctl run --request-path test_data/e2e/empty_security_context_pod.json --settings-path test_data/e2e/settings_must_run_as_non_root.json target/wasm32-unknown-unknown/release/psp_user_group.wasm
[ "$status" -eq 0 ]
echo "$output"
[ $(expr "$output" : '.*"allowed":true.*') -ne 0 ]
[ $(expr "$output" : '.*"patchType":"JSONPatch".*') -ne 0 ]
}

@test "MustRunAsNonRoot should accept request when user defined is not root" {
run kwctl run --request-path test_data/e2e/valid_security_context.json --settings-path test_data/e2e/settings_must_run_as_non_root.json target/wasm32-unknown-unknown/release/psp_user_group.wasm
[ "$status" -eq 0 ]
echo "$output"
[ $(expr "$output" : '.*"allowed":true.*') -ne 0 ]
}
2 changes: 1 addition & 1 deletion metadata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ rules:
apiVersions: ["v1"]
resources: ["pods"]
operations: ["CREATE"]
mutating: false
mutating: true
contextAware: false
executionMode: kubewarden-wapc
annotations:
Expand Down
Loading

0 comments on commit e0bcc1f

Please sign in to comment.