Skip to content
This repository has been archived by the owner on Jan 12, 2023. It is now read-only.

Commit

Permalink
Merge pull request #40 from alpe/alex/size_limit_mutation_policy_36
Browse files Browse the repository at this point in the history
Add EmptyDir size limit policy
  • Loading branch information
dustin-decker authored Jan 9, 2020
2 parents b33c2bc + f848797 commit cafdc99
Show file tree
Hide file tree
Showing 12 changed files with 465 additions and 20 deletions.
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,22 @@ k-rail is a workload policy enforcement tool for Kubernetes. It can help you sec
* [No Exec](#no-exec)
* [No Bind Mounts](#no-bind-mounts)
* [No Docker Sock Mount](#no-docker-sock-mount)
* [Mutate Default Seccomp Profile](#mutate-default-seccomp-profile)
* [EmptyDir size limit](#emptyDir-size-limit)
+ [Policy configuration](#policy-configuration)
* [Mutate Default Seccomp Profile](#mutate-default-seccomp-profile)
+ [Policy configuration](#policy-configuration-1)
* [Immutable Image Reference](#immutable-image-reference)
* [No Host Network](#no-host-network)
* [No Host PID](#no-host-pid)
* [No New Capabilities](#no-new-capabilities)
* [No Privileged Container](#no-privileged-container)
* [No Helm Tiller](#no-helm-tiller)
* [Trusted Image Repository](#trusted-image-repository)
+ [Policy configuration](#policy-configuration-1)
+ [Policy configuration](#policy-configuration-2)
* [Safe to Evict (DEPRECATED)](#safe-to-evict--deprecated)
* [Mutate Safe to Evict](#mutate-safe-to-evict)
* [Require Ingress Exemption](#require-ingress-exemption)
+ [Policy configuration](#policy-configuration-2)
+ [Policy configuration](#policy-configuration-3)
- [Configuration](#configuration)
* [Logging](#logging)
* [Modes of operation](#modes-of-operation)
Expand Down Expand Up @@ -230,6 +232,21 @@ The Docker socket bind mount provides API access to the host Docker daemon, whic

**Note:** It is recommended to use the `No Bind Mounts` policy to disable all `hostPath` mounts rather than only this policy.

## EmptyDir size limit
By [default](https://kubernetes.io/docs/concepts/storage/volumes/#example-pod), an `emptyDir` lacks a `sizeLimit` parameter, and is disk-based;
a Pod with access to said `emptyDir` can consume the Node's entire disk (i.e. the limit is unbounded) until the offending Pod is deleted or evicted, which can constitute a denial-of-service condition at the affected Node (i.e. DiskPressure).
This policy
* sets the configured default size when none is set for an `emptyDir` volume
* reports a violation when the size is greater then the configured max size

### Policy configuration
```yaml
policy_config:
mutate_empty_dir_size_limit:
maximum_size_limit: "1Gi"
default_size_limit: "512Mi"
```

## Mutate Default Seccomp Profile

Sets a default seccomp profile (`runtime/default` or a configured one) for Pods if they have no existing seccomp configuration. The default seccomp policy for Docker and Containerd both block over 40 syscalls, [many of which](https://docs.docker.com/engine/security/seccomp/#significant-syscalls-blocked-by-the-default-profile) are potentially dangerous. The default policies are [usually very compatible](https://blog.jessfraz.com/post/containers-security-and-echo-chambers/#breaking-changes) with applications, too.
Expand Down
6 changes: 6 additions & 0 deletions deploy/helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ config:
- '^k8s.gcr.io/.*' # official k8s GCR repo
- '^[A-Za-z0-9\-:@]+$' # official docker hub images
policy_default_seccomp_policy: "runtime/default"
mutate_empty_dir_size_limit:
maximum_size_limit: "1Gi"
default_size_limit: "512Mi"
policies:
- name: "pod_no_exec"
enabled: True
Expand Down Expand Up @@ -73,6 +76,9 @@ config:
- name: "pod_mutate_safe_to_evict"
enabled: True
report_only: False
- name: "pod_empty_dir_size_limit"
enabled: True
report_only: False
- name: "pod_default_seccomp_policy"
enabled: True
report_only: False
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ require (
golang.org/x/text v0.3.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/inf.v0 v0.9.0 // indirect
gopkg.in/yaml.v2 v2.2.2
gopkg.in/yaml.v2 v2.2.2 // indirect
k8s.io/api v0.0.0-20190301173355-16f65c82b8fa
k8s.io/apimachinery v0.0.0-20190301173222-2f7e9cae4418
k8s.io/klog v0.0.0-20181108234604-8139d8cb77af // indirect
sigs.k8s.io/yaml v1.1.0 // indirect
sigs.k8s.io/yaml v1.1.0
)

replace git.apache.org/thrift.git => github.com/apache/thrift v0.12.0
49 changes: 46 additions & 3 deletions policies/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,57 @@

package policies

import (
"encoding/json"
"errors"
"fmt"

apiresource "k8s.io/apimachinery/pkg/api/resource"
)

// Config contains configuration for Policies
type Config struct {
// PolicyRequireIngressExemptionClasses contains the Ingress classes that an exemption is required for
// to use. Typically this would include your public ingress classes.
PolicyRequireIngressExemptionClasses []string `yaml:"policy_require_ingress_exemption_classes"`
PolicyRequireIngressExemptionClasses []string `json:"policy_require_ingress_exemption_classes"`
// PolicyTrustedRepositoryRegexes contains regexes that match image repositories that you want to allow.
PolicyTrustedRepositoryRegexes []string `yaml:"policy_trusted_repository_regexes"`
PolicyTrustedRepositoryRegexes []string `json:"policy_trusted_repository_regexes"`
// PolicyDefaultSeccompPolicy contains the seccomp policy that you want to be applied on Pods by default.
// Defaults to 'runtime/default'
PolicyDefaultSeccompPolicy string `yaml:"policy_default_seccomp_policy"`
PolicyDefaultSeccompPolicy string `json:"policy_default_seccomp_policy"`

MutateEmptyDirSizeLimit MutateEmptyDirSizeLimit `json:"mutate_empty_dir_size_limit"`
}

type MutateEmptyDirSizeLimit struct {
MaximumSizeLimit apiresource.Quantity `json:"maximum_size_limit"`
DefaultSizeLimit apiresource.Quantity `json:"default_size_limit"`
}

func (m *MutateEmptyDirSizeLimit) UnmarshalJSON(value []byte) error {
var v map[string]json.RawMessage
if err := json.Unmarshal(value, &v); err != nil {
return err
}

if max, ok := v["maximum_size_limit"]; ok {
if err := m.MaximumSizeLimit.UnmarshalJSON(max); err != nil {
return fmt.Errorf("maximum_size_limit failed: %s", err)
}
}
if def, ok := v["default_size_limit"]; ok {
if err := m.DefaultSizeLimit.UnmarshalJSON(def); err != nil {
return fmt.Errorf("default_size_limit failed: %s", err)
}
}
if m.DefaultSizeLimit.IsZero() {
return errors.New("default size must not be empty")
}
if m.MaximumSizeLimit.IsZero() {
return errors.New("max size must not be empty")
}
if m.DefaultSizeLimit.Cmp(m.MaximumSizeLimit) > 0 {
return errors.New("default size must not be greater than max size")
}
return nil
}
77 changes: 77 additions & 0 deletions policies/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package policies

import (
"reflect"
"testing"

apiresource "k8s.io/apimachinery/pkg/api/resource"
"sigs.k8s.io/yaml"
)

func TestMutateEmptyDirSizeLimit(t *testing.T) {
specs := map[string]struct {
src string
exp *MutateEmptyDirSizeLimit
expErr bool
}{

"all good": {
src: `
mutate_empty_dir_size_limit:
maximum_size_limit: "1Gi"
default_size_limit: "512Mi"
`,
exp: &MutateEmptyDirSizeLimit{
MaximumSizeLimit: apiresource.MustParse("1Gi"),
DefaultSizeLimit: apiresource.MustParse("512Mi"),
},
},
"default > max": {
src: `
mutate_empty_dir_size_limit:
maximum_size_limit: "1Gi"
default_size_limit: "2Gi"
`,
expErr: true,
},
"default not set": {
src: `
mutate_empty_dir_size_limit:
maximum_size_limit: "1Gi"
`,
expErr: true,
},
"max not set": {
src: `
mutate_empty_dir_size_limit:
default_size_limit: "2Gi"
`,
expErr: true,
},
"unsupported type": {
src: `
mutate_empty_dir_size_limit:
default_size_limit: "2ALX"
maximum_size_limit: "2ALX"
`,
expErr: true,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
var cfg Config
switch err := yaml.Unmarshal([]byte(spec.src), &cfg); {
case spec.expErr && err != nil:
return
case spec.expErr:
t.Fatal("expected error")
case !spec.expErr && err != nil:
t.Fatalf("unexpected error: %+v", err)
}
if exp, got := *spec.exp, cfg.MutateEmptyDirSizeLimit; !reflect.DeepEqual(exp, got) {
t.Errorf("expected %v but got %v", exp, got)
}
})
}

}
12 changes: 6 additions & 6 deletions policies/exemption.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ import (

"github.com/gobwas/glob"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
authenticationv1 "k8s.io/api/authentication/v1"
"sigs.k8s.io/yaml"
)

// RawExemption is the configuration for a policy exemption
type RawExemption struct {
ResourceName string `yaml:"resource_name"`
Namespace string `yaml:"namespace"`
Username string `yaml:"username"`
Group string `yaml:"group"`
ExemptPolicies []string `yaml:"exempt_policies"`
ResourceName string `json:"resource_name"`
Namespace string `json:"namespace"`
Username string `json:"username"`
Group string `json:"group"`
ExemptPolicies []string `json:"exempt_policies"`
}

// CompiledExemption is the compiled configuration for a policy exemption
Expand Down
81 changes: 81 additions & 0 deletions policies/pod/empty_dir_size_limit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2019 Cruise LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// https://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.package ingress

package pod

import (
"context"
"fmt"

"github.com/cruise-automation/k-rail/policies"
"github.com/cruise-automation/k-rail/resource"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
)

type PolicyEmptyDirSizeLimit struct {
}

func (p PolicyEmptyDirSizeLimit) Name() string {
return "pod_empty_dir_size_limit"
}

const violationText = "Empty dir size limit: size limit exceeds the max value"

func (p PolicyEmptyDirSizeLimit) Validate(ctx context.Context, config policies.Config, ar *admissionv1beta1.AdmissionRequest) ([]policies.ResourceViolation, []policies.PatchOperation) {
var resourceViolations []policies.ResourceViolation

podResource := resource.GetPodResource(ar, ctx)
if podResource == nil {
return resourceViolations, nil
}

cfg := config.MutateEmptyDirSizeLimit
var patches []policies.PatchOperation

for i, volume := range podResource.PodSpec.Volumes {
if volume.EmptyDir == nil {
continue
}
if volume.EmptyDir.SizeLimit == nil || volume.EmptyDir.SizeLimit.IsZero() {
patches = append(patches, policies.PatchOperation{
Op: "replace",
Path: fmt.Sprintf(volumePatchPath(podResource.ResourceKind)+"/%d/emptyDir/sizeLimit", i),
Value: cfg.DefaultSizeLimit.String(),
})
continue
}

if volume.EmptyDir.SizeLimit.Cmp(cfg.MaximumSizeLimit) > 0 {
resourceViolations = append(resourceViolations, policies.ResourceViolation{
Namespace: ar.Namespace,
ResourceName: podResource.ResourceName,
ResourceKind: podResource.ResourceKind,
Violation: violationText,
Policy: p.Name(),
})
}
}
return resourceViolations, patches
}

const templateVolumePath = "/spec/template/spec/volumes"

func volumePatchPath(podKind string) string {
nonTemplateKinds := map[string]string{
"Pod": "/spec/volumes",
"CronJob": "/spec/jobTemplate/spec/template/spec/volumes",
}
if pathPath, ok := nonTemplateKinds[podKind]; ok {
return pathPath
}
return templateVolumePath
}
Loading

0 comments on commit cafdc99

Please sign in to comment.