1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-08 10:04:25 +00:00

Merge commit 'cfbd2120938b8a7f81f4a9c325fa3f6e816d2bf1' into 158_array_validation

This commit is contained in:
Shuting Zhao 2019-11-05 09:43:28 -08:00
commit 3fbb9f8a35
40 changed files with 417 additions and 284 deletions

View file

@ -34,15 +34,53 @@ A validation rule is expressed as an overlay pattern that expresses the desired
There is no operator for `equals` as providing a field value in the pattern requires equality to the value.
## Anchors
Anchors allow conditional processing (i.e. "if-then-else) and other logical checks in validation patterns. The following types of anchors are supported:
| Anchor | Tag | Behavior |
|------------- |----- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Conditional | () | If tag with the given value is specified, then following resource elements must satisfy the conditions. <br/>e.g. If image has tag latest then imagePullPolicy cannot be IfNotPresent. <br/>&nbsp;&nbsp;&nbsp;&nbsp;(image): "*:latest" <br>&nbsp;&nbsp;&nbsp;&nbsp;imagePullPolicy: "!IfNotPresent"<br/> |
| Equality | =() | If tag is specified, then it should have the provided value. <br/>e.g. If hostPath is defined then the path cannot be /var/lib<br/>&nbsp;&nbsp;&nbsp;&nbsp;=(hostPath):<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;path: "!/var/lib"<br/> |
| Existence | ^() | Works on the list/array type only. If at least one element in the satisfies the pattern. <br/>e.g. At least one container with image nginx:latest must exist. <br/>&nbsp;&nbsp;&nbsp;&nbsp;^(containers):<br/>&nbsp;&nbsp;&nbsp;&nbsp;- image: nginx:latest<br/> |
| Conditional | () | If tag with the given value (including child elements) is specified, then peer elements will be processed. <br/>e.g. If image has tag latest then imagePullPolicy cannot be IfNotPresent. <br/>&nbsp;&nbsp;&nbsp;&nbsp;(image): "*:latest" <br>&nbsp;&nbsp;&nbsp;&nbsp;imagePullPolicy: "!IfNotPresent"<br/> |
| Equality | =() | If tag is specified, then processing continues. For tags with scalar values, the value must match. For tags with child elements, the child element is further evaluated as a validation pattern. <br/>e.g. If hostPath is defined then the path cannot be /var/lib<br/>&nbsp;&nbsp;&nbsp;&nbsp;=(hostPath):<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;path: "!/var/lib"<br/> |
| Existence | ^() | Works on the list/array type only. If at least one element in the list satisfies the pattern. In contrast, a conditional anchor would validate that all elements in the list match the pattern. <br/>e.g. At least one container with image nginx:latest must exist. <br/>&nbsp;&nbsp;&nbsp;&nbsp;^(containers):<br/>&nbsp;&nbsp;&nbsp;&nbsp;- image: nginx:latest<br/> |
| Negation | X() | The tag cannot be specified. The value of the tag is not evaulated. <br/>e.g. Hostpath tag cannot be defined.<br/>&nbsp;&nbsp;&nbsp;&nbsp;X(hostPath):<br/> |
## Example
## Anchors and child elements
Child elements are handled differently for conditional and equality anchors.
For conditional anchors, the child element is considered to be part of the "if" clause, and all peer elements are considered to be part of the "then" clause. For example, consider the pattern:
````yaml
pattern:
spec:
metadata:
labels:
allow-docker: true
(volumes):
(hostPath):
path: "/var/run/docker.sock"
````
This reads as "If a hostPath volume exists and the path it equals /var/run/docker.sock, then a label "allow-docker" must be specified with a value of true."
For equality anchors, a child element is considered to be part of the "then" clause. Consider this pattern:
````yaml
pattern:
spec:
=(volumes):
=(hostPath):
path: "!/var/run/docker.sock"
````
This is read as "If a hostPath volume exists, then the path must not be equal to /var/run/docker.sock".
## Examples
The following rule prevents the creation of Deployment, StatefuleSet and DaemonSet resources without label 'app' in selector:
````yaml
apiVersion : kyverno.io/v1alpha1
@ -75,10 +113,11 @@ spec :
````
### Check if one exist
A variation of an anchor, is to check existance of one element. This is done by using the ^(...) notation for the field.
### Existence anchor: at least one
For example, this pattern will check the existance of "name" field in the list:
A variation of an anchor, is to check that in a list of elements at least one element exists that matches the patterm. This is done by using the ^(...) notation for the field.
For example, this pattern will check that at least one container has memory requests and limits defined and that the request is less than the limit:
````yaml
apiVersion : kyverno.io/v1alpha1
@ -101,7 +140,6 @@ spec :
pattern:
spec:
^(containers):
- (name): "*"
resources:
requests:
memory: "$(<=./../../limits/memory)"
@ -109,11 +147,13 @@ spec :
memory: "2048Mi"
````
### Allow OR across overlay pattern
In some cases one content can be defined at a different level. For example, a security context can be defined at the Pod or Container level. The validation rule should pass if one of the conditions is met.
`anyPattern` can be used to check on at least one of condition, it is the array of pattern, and the rule will be passed if at least one pattern is true.
### Logical OR across validation patterns
<small>*Note: either `pattern` or `anyPattern` is allowed in each rule, they can't be decalred in the same rule.*</small>
In some cases content can be defined at a different level. For example, a security context can be defined at the Pod or Container level. The validation rule should pass if either one of the conditions is met.
The `anyPattern` tag can be used to check if any one of the patterns in the list match.
<small>*Note: either one of `pattern` or `anyPattern` is allowed in a rule, they both can't be declared in the same rule.*</small>
````yaml
apiVersion: kyverno.io/v1alpha1
@ -145,7 +185,7 @@ spec:
````
Additional examples are available in [examples](/examples/)
Additional examples are available in [samples](/samples/README.md)
---

View file

@ -1,7 +1,6 @@
package engine
import (
"encoding/json"
"time"
"fmt"
@ -9,7 +8,6 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@ -149,38 +147,12 @@ func applyRuleGenerator(client *client.Client, ns unstructured.Unstructured, rul
//checkResource checks if the config is present in th eresource
func checkResource(config interface{}, resource *unstructured.Unstructured) (bool, error) {
var err error
objByte, err := resource.MarshalJSON()
// we are checking if config is a subset of resource with default pattern
path, err := validateResourceWithPattern(resource.Object, config)
if err != nil {
// unable to parse the json
glog.V(4).Infof("config not a subset of resource. failed at path %s: %v", path, err)
return false, err
}
err = resource.UnmarshalJSON(objByte)
if err != nil {
// unable to parse the json
return false, err
}
// marshall and unmarshall json to verify if its right format
configByte, err := json.Marshal(config)
if err != nil {
// unable to marshall the config
return false, err
}
var configData interface{}
err = json.Unmarshal(configByte, &configData)
if err != nil {
// unable to unmarshall
return false, err
}
var objData interface{}
err = json.Unmarshal(objByte, &objData)
if err != nil {
// unable to unmarshall
return false, err
}
// Check if the config is a subset of resource
return utils.JSONsubsetValue(configData, objData), nil
return true, nil
}

View file

@ -3,6 +3,7 @@ package engine
import (
"math"
"regexp"
"strconv"
"strings"
"github.com/golang/glog"
@ -91,6 +92,14 @@ func validateValueWithIntPattern(value interface{}, pattern int64) bool {
glog.Warningf("Expected int, found float: %f\n", typedValue)
return false
case string:
// extract int64 from string
int64Num, err := strconv.ParseInt(typedValue, 10, 64)
if err != nil {
glog.Warningf("Failed to parse int64 from string: %v", err)
return false
}
return int64Num == pattern
default:
glog.Warningf("Expected int, found: %T\n", value)
return false
@ -105,11 +114,25 @@ func validateValueWithFloatPattern(value interface{}, pattern float64) bool {
if pattern == math.Trunc(pattern) {
return int(pattern) == value
}
glog.Warningf("Expected float, found int: %d\n", typedValue)
return false
case int64:
// check that float has no fraction
if pattern == math.Trunc(pattern) {
return int64(pattern) == value
}
glog.Warningf("Expected float, found int: %d\n", typedValue)
return false
case float64:
return typedValue == pattern
case string:
// extract float64 from string
float64Num, err := strconv.ParseFloat(typedValue, 64)
if err != nil {
glog.Warningf("Failed to parse float64 from string: %v", err)
return false
}
return float64Num == pattern
default:
glog.Warningf("Expected float, found: %T\n", value)
return false

View file

@ -87,7 +87,7 @@ func validatePatterns(resource unstructured.Unstructured, rule kyverno.Rule) (re
// rule application failed
glog.V(4).Infof("Validation rule '%s' failed at '%s' for resource %s/%s/%s. %s: %v", rule.Name, path, resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Validation.Message, err)
response.Success = false
response.Message = fmt.Sprintf("Validation rule '%s' failed at '%s' for resource %s/%s/%s. %s", rule.Name, path, resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Validation.Message)
response.Message = fmt.Sprintf("Validation rule '%s' failed at '%s' for resource %s/%s/%s. %s.", rule.Name, path, resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Validation.Message)
return response
}
// rule application succesful
@ -122,7 +122,7 @@ func validatePatterns(resource unstructured.Unstructured, rule kyverno.Rule) (re
response.Success = false
response.Success = false
var errorStr []string
errorStr = append(errorStr, fmt.Sprintf("Validation rule '%s' failed to validate patterns defined in anyPattern. %s", rule.Name, rule.Validation.Message))
errorStr = append(errorStr, fmt.Sprintf("Validation rule '%s' failed to validate patterns defined in anyPattern. %s.", rule.Name, rule.Validation.Message))
for index, err := range errs {
glog.V(4).Infof("anyPattern[%d] failed at path %s: %v", index, failedPaths[index], err)
str := fmt.Sprintf("anyPattern[%d] failed at path %s", index, failedPaths[index])

View file

@ -1818,7 +1818,7 @@ func TestValidate_image_tag_fail(t *testing.T) {
assert.NilError(t, err)
msgs := []string{
"Validation rule 'validate-tag' succesfully validated",
"Validation rule 'validate-latest' failed at '/spec/containers/0/imagePullPolicy/' for resource Pod//myapp-pod. imagePullPolicy 'Always' required with tag 'latest'",
"Validation rule 'validate-latest' failed at '/spec/containers/0/imagePullPolicy/' for resource Pod//myapp-pod. imagePullPolicy 'Always' required with tag 'latest'.",
}
er := Validate(policy, *resourceUnstructured)
for index, r := range er.PolicyResponse.Rules {
@ -1992,7 +1992,7 @@ func TestValidate_Fail_anyPattern(t *testing.T) {
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(policy, *resourceUnstructured)
msgs := []string{"Validation rule 'check-default-namespace' failed to validate patterns defined in anyPattern. A namespace is required; anyPattern[0] failed at path /metadata/namespace/; anyPattern[1] failed at path /metadata/namespace/"}
msgs := []string{"Validation rule 'check-default-namespace' failed to validate patterns defined in anyPattern. A namespace is required.; anyPattern[0] failed at path /metadata/namespace/; anyPattern[1] failed at path /metadata/namespace/"}
for index, r := range er.PolicyResponse.Rules {
assert.Equal(t, r.Message, msgs[index])
}
@ -2073,7 +2073,7 @@ func TestValidate_host_network_port(t *testing.T) {
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(policy, *resourceUnstructured)
msgs := []string{"Validation rule 'validate-host-network-port' failed at '/spec/containers/0/ports/0/hostPort/' for resource Pod//nginx-host-network. Host network and port are not allowed"}
msgs := []string{"Validation rule 'validate-host-network-port' failed at '/spec/containers/0/ports/0/hostPort/' for resource Pod//nginx-host-network. Host network and port are not allowed."}
for index, r := range er.PolicyResponse.Rules {
assert.Equal(t, r.Message, msgs[index])
@ -2250,7 +2250,7 @@ func TestValidate_anchor_arraymap_fail(t *testing.T) {
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(policy, *resourceUnstructured)
msgs := []string{"Validation rule 'validate-host-path' failed at '/spec/volumes/0/hostPath/path/' for resource Pod//image-with-hostpath. Host path '/var/lib/' is not allowed"}
msgs := []string{"Validation rule 'validate-host-path' failed at '/spec/volumes/0/hostPath/path/' for resource Pod//image-with-hostpath. Host path '/var/lib/' is not allowed."}
for index, r := range er.PolicyResponse.Rules {
assert.Equal(t, r.Message, msgs[index])
@ -2463,7 +2463,7 @@ func TestValidate_anchor_map_found_invalid(t *testing.T) {
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(policy, *resourceUnstructured)
msgs := []string{"Validation rule 'pod rule 2' failed at '/spec/securityContext/runAsNonRoot/' for resource Pod//myapp-pod. pod: validate run as non root user"}
msgs := []string{"Validation rule 'pod rule 2' failed at '/spec/securityContext/runAsNonRoot/' for resource Pod//myapp-pod. pod: validate run as non root user."}
for index, r := range er.PolicyResponse.Rules {
assert.Equal(t, r.Message, msgs[index])
@ -2847,7 +2847,7 @@ func TestValidate_negationAnchor_deny(t *testing.T) {
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(policy, *resourceUnstructured)
msgs := []string{"Validation rule 'validate-host-path' failed at '/spec/volumes/0/hostPath/' for resource Pod//image-with-hostpath. Host path is not allowed"}
msgs := []string{"Validation rule 'validate-host-path' failed at '/spec/volumes/0/hostPath/' for resource Pod//image-with-hostpath. Host path is not allowed."}
for index, r := range er.PolicyResponse.Rules {
assert.Equal(t, r.Message, msgs[index])

View file

@ -115,3 +115,14 @@ func Test_validate_disallow_host_filesystem_fail(t *testing.T) {
func Test_validate_disallow_host_filesystem_pass(t *testing.T) {
testScenario(t, "test/scenarios/samples/best_practices/scenario_validate_disallow_host_filesystem_pass.yaml")
}
func Test_validate_disallow_new_capabilities(t *testing.T) {
testScenario(t, "/test/scenarios/samples/best_practices/scenario_validate_disallow_new_capabilities.yaml")
}
func Test_validate_disallow_docker_sock_mount(t *testing.T) {
testScenario(t, "test/scenarios/samples/best_practices/scenario_validate_disallow_docker_sock_mount.yaml")
}
func Test_validate_disallow_helm_tiller(t *testing.T) {
testScenario(t, "test/scenarios/samples/best_practices/scenario_validate_disallow_helm_tiller.yaml")
}

View file

@ -1,193 +1,5 @@
package utils
import (
"github.com/golang/glog"
)
//JSONsubsetValue checks if JSON a is contained in JSON b
func JSONsubsetValue(a interface{}, b interface{}) bool {
switch typed := a.(type) {
case bool:
bv, ok := b.(bool)
if !ok {
glog.Errorf("expected bool found %T", b)
return false
}
av, _ := a.(bool)
if av == bv {
return true
}
case int:
bv, ok := b.(int)
if !ok {
glog.Errorf("expected int found %T", b)
return false
}
av, _ := a.(int)
if av == bv {
return true
}
case float64:
bv, ok := b.(float64)
if !ok {
glog.Errorf("expected float64 found %T", b)
return false
}
av, _ := a.(float64)
if av == bv {
return true
}
case string:
bv, ok := b.(string)
if !ok {
glog.Errorf("expected string found %T", b)
return false
}
av, _ := a.(string)
if av == bv {
return true
}
case map[string]interface{}:
bv, ok := b.(map[string]interface{})
if !ok {
glog.Errorf("expected map[string]interface{} found %T", b)
return false
}
av, _ := a.(map[string]interface{})
return subsetMap(av, bv)
case []interface{}:
// TODO: verify the logic
bv, ok := b.([]interface{})
if !ok {
glog.Errorf("expected []interface{} found %T", b)
return false
}
av, _ := a.([]interface{})
return subsetSlice(av, bv)
default:
glog.Errorf("Unspported type %s", typed)
}
return false
}
func subsetMap(a, b map[string]interface{}) bool {
// check if keys are present
for k := range a {
if _, ok := b[k]; !ok {
glog.Errorf("key %s, not present in resource", k)
return false
}
}
// check if values for the keys match
for ak, av := range a {
bv := b[ak]
if !JSONsubsetValue(av, bv) {
return false
}
}
return true
}
func containsInt(a interface{}, b []interface{}) bool {
switch typed := a.(type) {
case bool:
for _, bv := range b {
bv, ok := bv.(bool)
if !ok {
return false
}
av, _ := a.(bool)
if bv == av {
return true
}
}
case int:
for _, bv := range b {
bv, ok := bv.(int)
if !ok {
return false
}
av, _ := a.(int)
if bv == av {
return true
}
}
case float64:
for _, bv := range b {
bv, ok := bv.(float64)
if !ok {
return false
}
av, _ := a.(float64)
if bv == av {
return true
}
}
case string:
for _, bv := range b {
bv, ok := bv.(string)
if !ok {
return false
}
av, _ := a.(string)
if bv == av {
return true
}
}
case map[string]interface{}:
for _, bv := range b {
bv, ok := bv.(map[string]interface{})
if !ok {
return false
}
av, _ := a.(map[string]interface{})
if subsetMap(av, bv) {
return true
}
}
case []interface{}:
for _, bv := range b {
bv, ok := bv.([]interface{})
if !ok {
return false
}
av, _ := a.([]interface{})
if JSONsubsetValue(av, bv) {
return true
}
}
default:
glog.Errorf("Unspported type %s", typed)
}
return false
}
func subsetSlice(a, b []interface{}) bool {
// if empty
if len(a) == 0 {
return true
}
// check if len is not greater
if len(a) > len(b) {
return false
}
for _, av := range a {
if !containsInt(av, b) {
return false
}
}
return true
}
// JoinPatches joins array of serialized JSON patches to the single JSONPatch array
func JoinPatches(patches [][]byte) []byte {
var result []byte

View file

@ -90,8 +90,6 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, pat
glog.V(2).Infof("Handling validation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
resource.GetKind(), resource.GetNamespace(), resource.GetName(), request.UID, request.Operation)
// glog.V(4).Infof("Validating resource %s/%s/%s with policy %s with %d rules\n", resource.GetKind(), resource.GetNamespace(), resource.GetName(), policy.ObjectMeta.Name, len(policy.Spec.Rules))
engineResponse := engine.Validate(*policy, *resource)
engineResponses = append(engineResponses, engineResponse)
// Gather policy application statistics

View file

@ -0,0 +1,35 @@
# Disallow Docker socket bind mount
The Docker socket bind mount allows access to the
Docker daemon on the node. This access can be used for privilege escalation and
to manage containers outside of Kubernetes, and hence should not be allowed.
## Policy YAML
[disallow_docker_sock_mount.yaml](best_practices/disallow_docker_sock_mount.yaml)
````yaml
apiVersion: kyverno.io/v1alpha1
kind: ClusterPolicy
metadata:
name: disallow-docker-sock-mount
annotations:
policies.kyverno.io/category: Security
policies.kyverno.io/description: The Docker socket bind mount allows access to the
Docker daemon on the node. This access can be used for privilege escalation and
to manage containers outside of Kubernetes, and hence should not be allowed.
spec:
rules:
- name: validate-docker-sock-mount
match:
resources:
kinds:
- Pod
validate:
message: "Use of the Docker Unix socket is not allowed"
pattern:
spec:
=(volumes):
=(hostPath):
path: "!/var/run/docker.sock"
````

View file

@ -0,0 +1,30 @@
# Disallow Helm Tiller
Tiller has known security challenges. It requires adminstrative privileges and acts as a shared resource accessible to any authenticated user. Tiller can lead to privilge escalation as restricted users can impact other users.
## Policy YAML
````yaml
apiVersion : kyverno.io/v1alpha1
kind: ClusterPolicy
metadata:
name: disallow-helm-tiller
annotations:
policies.kyverno.io/category: Security
policies.kyverno.io/description:
spec:
rules:
- name: validate-helm-tiller
match:
resources:
kinds:
- Pod
validate:
message: "Helm Tiller is not allowed"
pattern:
spec:
containers:
- name: "*"
image: "!*tiller*"
````

View file

@ -21,7 +21,7 @@ spec:
kinds:
- Pod
validate:
message: "Defining hostNetwork and hostPort are not allowed."
message: "Defining hostNetwork and hostPort are not allowed"
pattern:
spec:
(hostNetwork): false

View file

@ -0,0 +1,45 @@
# Disallow new capabilities
Linux allows defining fine-grained permissions using
capabilities. With Kubernetes, it is possible to add capabilities that escalate the
level of kernel access and allow other potentially dangerous behaviors. This policy
enforces that pods cannot add new capabilities. Other policies can be used to set
default capabilities.
## Policy YAML
[disallow_new_capabilities.yaml](best_practices/disallow_new_capabilities.yaml)
````yaml
apiVersion: kyverno.io/v1alpha1
kind: ClusterPolicy
metadata:
name: validate-new-capabilities
annotations:
policies.kyverno.io/category: Security Context
policies.kyverno.io/description: Linux allows defining fine-grained permissions using
capabilities. With Kubernetes, it is possible to add capabilities that escalate the
level of kernel access and allow other potentially dangerous behaviors. This policy
enforces that pods cannot add new capabilities. Other policies can be used to set
default capabilities.
spec:
rules:
- name: deny-new-capabilities
match:
resources:
kinds:
- Pod
validate:
message: "Capabilities cannot be added"
anyPattern:
- spec:
=(securityContext):
=(capabilities):
X(add): null
- spec:
containers:
- name: "*"
=(securityContext):
=(capabilities):
X(add): null
````

View file

@ -38,27 +38,29 @@ These policies are highly recommended.
1. [Run as non-root user](RunAsNonRootUser.md)
2. [Disable privileged containers and disallow privilege escalation](DisablePrivilegedContainers.md)
3. [Require Read-only root filesystem](RequireReadOnlyFS.md)
4. [Disallow use of bind mounts (`hostPath` volumes)](DisallowHostFS.md)
5. [Disallow `hostNetwork` and `hostPort`](DisallowHostNetworkPort.md)
6. [Disallow `hostPID` and `hostIPC`](DisallowHostPIDIPC.md)
7. [Disallow unknown image registries](DisallowUnknownRegistries.md)
8. [Disallow latest image tag](DisallowLatestTag.md)
9. [Disallow use of default namespace](DisallowDefaultNamespace.md)
10. [Require namespace limits and quotas](RequireNSLimitsQuotas.md)
11. [Require pod resource requests and limits](RequirePodRequestsLimits.md)
12. [Require pod `livenessProbe` and `readinessProbe`](RequirePodProbes.md)
13. [Default deny all ingress traffic](DefaultDenyAllIngress.md)
3. [Disallow new capabilities](DisallowNewCapabilities.md)
4. [Require Read-only root filesystem](RequireReadOnlyFS.md)
5. [Disallow use of bind mounts (`hostPath` volumes)](DisallowHostFS.md)
6. [Disallow docker socket bind mount](DisallowDockerSockMount.md)
7. [Disallow `hostNetwork` and `hostPort`](DisallowHostNetworkPort.md)
8. [Disallow `hostPID` and `hostIPC`](DisallowHostPIDIPC.md)
9. [Disallow unknown image registries](DisallowUnknownRegistries.md)
10. [Disallow latest image tag](DisallowLatestTag.md)
11. [Disallow use of default namespace](DisallowDefaultNamespace.md)
12. [Require namespace limits and quotas](RequireNSLimitsQuotas.md)
13. [Require pod resource requests and limits](RequirePodRequestsLimits.md)
14. [Require pod `livenessProbe` and `readinessProbe`](RequirePodProbes.md)
15. [Default deny all ingress traffic](DefaultDenyAllIngress.md)
16. [Disallow Helm Tiller](DisallowHelmTiller.md)
## Additional Policies
The policies provide additional best practices and are worthy of close consideration. These policies may require workload specific changes.
14. [Limit use of `NodePort` services](LimitNodePort.md)
15. [Limit automount of Service Account credentials](DisallowAutomountSACredentials.md)
16. [Configure Linux Capabilities](AssignLinuxCapabilities.md)
17. [Limit Kernel parameter access](ConfigureKernelParmeters.md)
16. [Limit use of `NodePort` services](LimitNodePort.md)
17. [Limit automount of Service Account credentials](DisallowAutomountSACredentials.md)
18. [Configure Linux Capabilities](AssignLinuxCapabilities.md)
19. [Limit Kernel parameter access](ConfigureKernelParmeters.md)

View file

@ -23,7 +23,7 @@ spec:
kinds:
- Pod
validate:
message: "Root user is not allowed. Set runAsNonRoot to true."
message: "Root user is not allowed. Set runAsNonRoot to true"
anyPattern:
- spec:
securityContext:

View file

@ -16,7 +16,7 @@ spec:
kinds:
- Pod
validate:
message: "Root user is not allowed. Set runAsNonRoot to true."
message: "Root user is not allowed. Set runAsNonRoot to true"
anyPattern:
- spec:
securityContext:

View file

@ -0,0 +1,23 @@
apiVersion: kyverno.io/v1alpha1
kind: ClusterPolicy
metadata:
name: disallow-docker-sock-mount
annotations:
policies.kyverno.io/category: Security
policies.kyverno.io/description: The Docker socket bind mount allows access to the
Docker daemon on the node. This access can be used for privilege escalation and
to manage containers outside of Kubernetes, and hence should not be allowed.
spec:
rules:
- name: validate-docker-sock-mount
match:
resources:
kinds:
- Pod
validate:
message: "Use of the Docker Unix socket is not allowed"
pattern:
spec:
=(volumes):
=(hostPath):
path: "!/var/run/docker.sock"

View file

@ -0,0 +1,21 @@
apiVersion : kyverno.io/v1alpha1
kind: ClusterPolicy
metadata:
name: disallow-helm-tiller
annotations:
policies.kyverno.io/category: Security
policies.kyverno.io/description:
spec:
rules:
- name: validate-helm-tiller
match:
resources:
kinds:
- Pod
validate:
message: "Helm Tiller is not allowed"
pattern:
spec:
containers:
- name: "*"
image: "!*tiller*"

View file

@ -14,7 +14,7 @@ spec:
kinds:
- Pod
validate:
message: "Defining hostNetwork and hostPort are not allowed."
message: "Defining hostNetwork and hostPort are not allowed"
pattern:
spec:
(hostNetwork): false

View file

@ -0,0 +1,31 @@
apiVersion: kyverno.io/v1alpha1
kind: ClusterPolicy
metadata:
name: validate-new-capabilities
annotations:
policies.kyverno.io/category: Security Context
policies.kyverno.io/description: Linux allows defining fine-grained permissions using
capabilities. With Kubernetes, it is possible to add capabilities that escalate the
level of kernel access and allow other potentially dangerous behaviors. This policy
enforces that pods cannot add new capabilities. Other policies can be used to set
default capabilities.
spec:
rules:
- name: deny-new-capabilities
match:
resources:
kinds:
- Pod
validate:
message: "Capabilities cannot be added"
anyPattern:
- spec:
=(securityContext):
=(capabilities):
X(add): null
- spec:
containers:
- name: "*"
=(securityContext):
=(capabilities):
X(add): null

View file

@ -20,7 +20,7 @@ spec:
data:
spec:
hard:
requests.cpu: '4'
requests.memory: "16Gi"
limits.cpu: '4'
limits.memory: "16Gi"
requests.cpu: 4
requests.memory: 16Gi
limits.cpu: 4
limits.memory: 16Gi

View file

@ -20,7 +20,7 @@ spec:
pattern:
spec:
securityContext:
runAsUser: '1000'
runAsUser: 1000
- name: validate-groupid
match:
resources:
@ -31,7 +31,7 @@ spec:
pattern:
spec:
securityContext:
runAsGroup: '3000'
runAsGroup: 3000
- name: validate-fsgroup
match:
resources:
@ -42,7 +42,7 @@ spec:
pattern:
spec:
securityContext:
fsGroup: '2000'
fsGroup: 2000
# Alls processes inside the pod can be made to run with specific user and groupID by setting runAsUser and runAsGroup respectively.
# fsGroup can be specified to make sure any file created in the volume with have the specified groupID.
# The above parameters can also be used in a validate policy to restrict user & group IDs.

View file

@ -14,7 +14,7 @@ spec:
kinds:
- Pod
validate:
message: "Prevent mounting of default service account."
message: "Prevent mounting of default service account"
pattern:
spec:
serviceAccountName: "!default"

View file

@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-with-docker-sock-mount
spec:
containers:
- name: myshell
image: "ubuntu:18.04"
command:
- /bin/sleep
- "300"
volumes:
- name: dockersock
hostPath:
path: /var/run/docker.sock

View file

@ -0,0 +1,8 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-helm-tiller
spec:
containers:
- name: helm-tiller
image: docker.io/tiller:latest

View file

@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: add-new-capabilities
spec:
containers:
- name: add-new-capabilities
image: "ubuntu:18.04"
command:
- /bin/sleep
- "300"
securityContext:
capabilities:
add:
- NET_ADMIN

View file

@ -15,5 +15,5 @@ expected:
rules:
- name: validate-selinux-options
type: Validation
message: "Validation rule 'validate-selinux-options' failed at '/spec/containers/0/securityContext/seLinuxOptions/' for resource Pod/default/busybox-selinux. SELinux level is required"
message: "Validation rule 'validate-selinux-options' failed at '/spec/containers/0/securityContext/seLinuxOptions/' for resource Pod/default/busybox-selinux. SELinux level is required."
success: false

View file

@ -18,5 +18,5 @@ expected:
success: true
- name: image-tag-not-latest
type: Validation
message: "Validation rule 'image-tag-not-latest' failed at '/spec/containers/0/image/' for resource Pod//myapp-pod. Using 'latest' image tag is restricted. Set image tag to a specific version"
message: "Validation rule 'image-tag-not-latest' failed at '/spec/containers/0/image/' for resource Pod//myapp-pod. Using 'latest' image tag is restricted. Set image tag to a specific version."
success: false

View file

@ -16,7 +16,7 @@ expected:
rules:
- name: check-default-namespace
type: Validation
message: "Validation rule 'check-default-namespace' failed at '/metadata/namespace/' for resource Pod/default/myapp-pod. Using 'default' namespace is restricted"
message: "Validation rule 'check-default-namespace' failed at '/metadata/namespace/' for resource Pod/default/myapp-pod. Using 'default' namespace is restricted."
success: false
- name: check-namespace-exist
type: Validation

View file

@ -0,0 +1,18 @@
# file path relative to project root
input:
policy: samples/best_practices/disallow_docker_sock_mount.yaml
resource: test/resources/disallow_docker_sock_mount.yaml
expected:
validation:
policyresponse:
policy: disallow-docker-sock-mount
resource:
kind: Pod
apiVersion: v1
namespace: ''
name: pod-with-docker-sock-mount
rules:
- name: validate-docker-sock-mount
type: Validation
message: Validation rule 'validate-docker-sock-mount' failed at '/spec/volumes/' for resource Pod//pod-with-docker-sock-mount. Use of the Docker Unix socket is not allowed.
success: false

View file

@ -0,0 +1,16 @@
# file paths are relative to project root
input:
policy: samples/best_practices/disallow_helm_tiller.yaml
resource: test/resources/disallow_helm_tiller.yaml
expected:
validation:
policyresponse:
policy: disallow-helm-tiller
resource:
kind: Pod
name: pod-helm-tiller
rules:
- name: validate-helm-tiller
type: Validation
message: "Validation rule 'validate-helm-tiller' failed at '/spec/containers/0/image/' for resource Pod//pod-helm-tiller. Helm Tiller is not allowed."
success: false

View file

@ -14,5 +14,5 @@ expected:
rules:
- name: deny-use-of-host-fs
type: Validation
message: Validation rule 'deny-use-of-host-fs' failed at '/spec/volumes/0/hostPath/' for resource Pod//image-with-hostpath. Host path is not allowed
message: Validation rule 'deny-use-of-host-fs' failed at '/spec/volumes/0/hostPath/' for resource Pod//image-with-hostpath. Host path is not allowed.
success: false

View file

@ -14,5 +14,5 @@ expected:
rules:
- name: validate-hostpid-hostipc
type: Validation
message: Validation rule 'validate-hostpid-hostipc' failed at '/spec/hostIPC/' for resource Pod//nginx-with-hostpid. Disallow use of host's pid namespace and host's ipc namespace
message: Validation rule 'validate-hostpid-hostipc' failed at '/spec/hostIPC/' for resource Pod//nginx-with-hostpid. Disallow use of host's pid namespace and host's ipc namespace.
success: false

View file

@ -0,0 +1,18 @@
# file path relative to project root
input:
policy: samples/best_practices/disallow_new_capabilities.yaml
resource: test/resources/disallow_new_capabilities.yaml
expected:
validation:
policyresponse:
policy: validate-new-capabilities
resource:
kind: Pod
apiVersion: v1
namespace: ''
name: "add-new-capabilities"
rules:
- name: deny-new-capabilities
type: Validation
message: Validation rule 'deny-new-capabilities' failed to validate patterns defined in anyPattern. Capabilities cannot be added.; anyPattern[0] failed at path /spec/; anyPattern[1] failed at path /spec/containers/0/securityContext/capabilities/add/
success: false

View file

@ -13,5 +13,5 @@ expected:
rules:
- name: disallow-node-port
type: Validation
message: Validation rule 'disallow-node-port' failed at '/spec/type/' for resource Service//my-service. Disallow service of type NodePort
message: Validation rule 'disallow-node-port' failed at '/spec/type/' for resource Service//my-service. Disallow service of type NodePort.
success: false

View file

@ -14,6 +14,6 @@ expected:
rules:
- name: deny-privileged-priviligedescalation
type: Validation
message: "Validation rule 'deny-privileged-priviligedescalation' failed to validate patterns defined in anyPattern. Privileged mode is not allowed. Set allowPrivilegeEscalation and privileged to false; anyPattern[0] failed at path /spec/securityContext/; anyPattern[1] failed at path /spec/containers/0/securityContext/allowPrivilegeEscalation/"
message: "Validation rule 'deny-privileged-priviligedescalation' failed to validate patterns defined in anyPattern. Privileged mode is not allowed. Set allowPrivilegeEscalation and privileged to false.; anyPattern[0] failed at path /spec/securityContext/; anyPattern[1] failed at path /spec/containers/0/securityContext/allowPrivilegeEscalation/"
success: false

View file

@ -14,5 +14,5 @@ expected:
rules:
- name: check-probes
type: Validation
message: Validation rule 'check-probes' failed at '/spec/containers/0/livenessProbe/' for resource Pod//myapp-pod. Liveness and readiness probes are required
message: Validation rule 'check-probes' failed at '/spec/containers/0/livenessProbe/' for resource Pod//myapp-pod. Liveness and readiness probes are required.
success: false

View file

@ -14,5 +14,5 @@ expected:
rules:
- name: check-resource-request-limit
type: Validation
message: Validation rule 'check-resource-request-limit' failed at '/spec/containers/0/resources/limits/cpu/' for resource Pod//myapp-pod. CPU and memory resource requests and limits are required
message: Validation rule 'check-resource-request-limit' failed at '/spec/containers/0/resources/limits/cpu/' for resource Pod//myapp-pod. CPU and memory resource requests and limits are required.
success: false

View file

@ -14,5 +14,5 @@ expected:
rules:
- name: validate-readonly-rootfilesystem
type: Validation
message: Validation rule 'validate-readonly-rootfilesystem' failed at '/spec/containers/0/securityContext/readOnlyRootFilesystem/' for resource Pod//ghost-with-readonly-rootfilesystem. Container require read-only rootfilesystem
message: Validation rule 'validate-readonly-rootfilesystem' failed at '/spec/containers/0/securityContext/readOnlyRootFilesystem/' for resource Pod//ghost-with-readonly-rootfilesystem. Container require read-only rootfilesystem.
success: false

View file

@ -15,5 +15,5 @@ expected:
rules:
- name: validate-container-capablities
type: Validation
message: "Validation rule 'validate-container-capablities' failed at '/spec/containers/0/securityContext/capabilities/add/0/' for resource Pod//add-capabilities. Allow certain linux capability"
message: "Validation rule 'validate-container-capablities' failed at '/spec/containers/0/securityContext/capabilities/add/0/' for resource Pod//add-capabilities. Allow certain linux capability."
success: false

View file

@ -15,5 +15,5 @@ expected:
rules:
- name: allow-portrange-with-sysctl
type: Validation
message: "Validation rule 'allow-portrange-with-sysctl' failed at '/spec/securityContext/sysctls/0/value/' for resource Pod//nginx. Allowed port range is from 1024 to 65535"
message: "Validation rule 'allow-portrange-with-sysctl' failed at '/spec/securityContext/sysctls/0/value/' for resource Pod//nginx. Allowed port range is from 1024 to 65535."
success: false