Skip to content
This repository has been archived by the owner on Oct 30, 2024. It is now read-only.

Commit

Permalink
Adds a new 'mounts' command to audit sensitive host paths mounts (#322)
Browse files Browse the repository at this point in the history
* Add a new 'mounts' command to audit sensitive host paths mounts
  • Loading branch information
jcbbc authored Feb 24, 2021
1 parent e4cfb47 commit fb6003a
Show file tree
Hide file tree
Showing 15 changed files with 609 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ Auditors can also be run individually.
| `image` | Finds containers which do not use the desired version of an image (via the tag) or use an image without a tag. | [docs](docs/auditors/image.md) |
| `limits` | Finds containers which exceed the specified CPU and memory limits or do not specify any. | [docs](docs/auditors/limits.md) |
| `mountds` | Finds containers that have docker socket mounted. | [docs](docs/auditors/mountds.md) |
| `mounts` | Finds containers that have sensitive host paths mounted. | [docs](docs/auditors/mountds.md) |
| `netpols` | Finds namespaces that do not have a default-deny network policy. | [docs](docs/auditors/netpols.md) |
| `nonroot` | Finds containers running as root. | [docs](docs/auditors/nonroot.md) |
| `privesc` | Finds containers that allow privilege escalation. | [docs](docs/auditors/privesc.md) |
Expand Down
4 changes: 4 additions & 0 deletions auditors/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/Shopify/kubeaudit/auditors/image"
"github.com/Shopify/kubeaudit/auditors/limits"
"github.com/Shopify/kubeaudit/auditors/mountds"
"github.com/Shopify/kubeaudit/auditors/mounts"
"github.com/Shopify/kubeaudit/auditors/netpols"
"github.com/Shopify/kubeaudit/auditors/nonroot"
"github.com/Shopify/kubeaudit/auditors/privesc"
Expand All @@ -30,6 +31,7 @@ var AuditorNames = []string{
hostns.Name,
image.Name,
limits.Name,
mounts.Name,
mountds.Name,
netpols.Name,
nonroot.Name,
Expand Down Expand Up @@ -71,6 +73,8 @@ func initAuditor(name string, conf config.KubeauditConfig) (kubeaudit.Auditable,
return image.New(conf.GetAuditorConfigs().Image), nil
case limits.Name:
return limits.New(conf.GetAuditorConfigs().Limits)
case mounts.Name:
return mounts.New(conf.GetAuditorConfigs().Mounts), nil
case mountds.Name:
return mountds.New(), nil
case netpols.Name:
Expand Down
12 changes: 12 additions & 0 deletions auditors/mounts/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package mounts

type Config struct {
SensitivePaths []string `yaml:"denyPathsList"`
}

func (config *Config) GetSensitivePaths() []string {
if config == nil || len(config.SensitivePaths) == 0 {
return DefaultSensitivePaths
}
return config.SensitivePaths
}
16 changes: 16 additions & 0 deletions auditors/mounts/fixtures/docker-sock-mounted.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
name: pod
namespace: docker-sock-mounted
spec:
containers:
- name: container
image: scratch
volumeMounts:
- mountPath: /var/run/docker.sock
name: docker-sock-volume
volumes:
- name: docker-sock-volume
hostPath:
path: /var/run/docker.sock
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: v1
kind: Pod
metadata:
name: pod
labels:
name: pod
container.audit.kubernetes.io/container1.allow-host-path-mount-proc-volume: "SomeReason"
container.audit.kubernetes.io/container2.allow-host-path-mount-proc-volume: "SomeReason"
namespace: proc-mounted-allowed-multi-containers-multi-labels
spec:
containers:
- name: container1
image: scratch
volumeMounts:
- mountPath: /host/proc
name: proc-volume
- name: container2
image: scratch
volumeMounts:
- mountPath: /host/proc
name: proc-volume
volumes:
- name: proc-volume
hostPath:
path: /proc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: v1
kind: Pod
metadata:
name: pod
labels:
name: pod
container.audit.kubernetes.io/container1.allow-host-path-mount-proc-volume: "SomeReason"
namespace: proc-mounted-allowed-multi-containers-single-label
spec:
containers:
- name: container1
image: scratch
volumeMounts:
- mountPath: /host/proc
name: proc-volume
- name: container2
image: scratch
volumeMounts:
- mountPath: /host/proc
name: proc-volume
volumes:
- name: proc-volume
hostPath:
path: /proc
19 changes: 19 additions & 0 deletions auditors/mounts/fixtures/proc-mounted-allowed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: v1
kind: Pod
metadata:
name: pod
labels:
name: pod
audit.kubernetes.io/pod.allow-host-path-mount-proc-volume: "SomeReason"
namespace: proc-mounted-allowed
spec:
containers:
- name: container
image: scratch
volumeMounts:
- mountPath: /host/proc
name: proc-volume
volumes:
- name: proc-volume
hostPath:
path: /proc
16 changes: 16 additions & 0 deletions auditors/mounts/fixtures/proc-mounted.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
name: pod
namespace: proc-mounted
spec:
containers:
- name: container
image: scratch
volumeMounts:
- mountPath: /host/proc
name: proc-volume
volumes:
- name: proc-volume
hostPath:
path: /proc
123 changes: 123 additions & 0 deletions auditors/mounts/mounts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package mounts

import (
"fmt"
"github.com/Shopify/kubeaudit"
"github.com/Shopify/kubeaudit/internal/k8s"
"github.com/Shopify/kubeaudit/internal/override"
"github.com/Shopify/kubeaudit/k8stypes"
v1 "k8s.io/api/core/v1"
)

const Name = "mounts"

const (
// SensitivePathsMounted occurs when a container has sensitive host paths mounted
SensitivePathsMounted = "SensitivePathsMounted"
)

// DefaultSensitivePaths is the default list of sensitive mount paths (from Falco rule: https://github.com/falcosecurity/falco/blob/master/rules/k8s_audit_rules.yaml#L155)
var DefaultSensitivePaths = []string{"/proc", "/var/run/docker.sock", "/", "/etc", "/root", "/var/run/crio/crio.sock", "/home/admin", "/var/lib/kubelet", "/var/lib/kubelet/pki", "/etc/kubernetes", "/etc/kubernetes/manifests"}

const overrideLabelPrefix = "allow-host-path-mount-"

const (
MountNameMetadataKey = "MountName"
MountPathMetadataKey = "MountPath"
MountReadOnlyMetadataKey = "MountReadOnly"
MountVolumeNameKey = "MountVolume"
MountVolumeHostPathKey = "MountVolumeHostPath"
)

// SensitivePathMounts implements Auditable
type SensitivePathMounts struct {
sensitivePaths map[string]bool
}

func New(config Config) *SensitivePathMounts {
paths := make(map[string]bool)
for _, path := range config.GetSensitivePaths() {
paths[path] = true
}
return &SensitivePathMounts{
sensitivePaths: paths,
}
}

// Audit checks that the container does not have any sensitive host path
func (sensitive *SensitivePathMounts) Audit(resource k8stypes.Resource, _ []k8stypes.Resource) ([]*kubeaudit.AuditResult, error) {
var auditResults []*kubeaudit.AuditResult

spec := k8s.GetPodSpec(resource)
if spec == nil {
return auditResults, nil
}

sensitiveVolumes := auditPodVolumes(spec, sensitive.sensitivePaths)

if len(sensitiveVolumes) == 0 {
return auditResults, nil
}

for _, container := range k8s.GetContainers(resource) {
for _, auditResult := range auditContainer(container, sensitiveVolumes) {
auditResult = override.ApplyOverride(auditResult, container.Name, resource, getOverrideLabel(auditResult.Metadata[MountNameMetadataKey]))
if auditResult != nil {
auditResults = append(auditResults, auditResult)
}
}
}

return auditResults, nil
}

func auditPodVolumes(podSpec *k8stypes.PodSpecV1, sensitivePaths map[string]bool) map[string]v1.Volume {
if podSpec.Volumes == nil {
return nil
}

found := make(map[string]v1.Volume)
for _, volume := range podSpec.Volumes {
if volume.HostPath == nil {
continue
}

if _, ok := sensitivePaths[volume.HostPath.Path]; ok {
found[volume.Name] = volume
}
}

return found
}

func auditContainer(container *k8stypes.ContainerV1, sensitiveVolumes map[string]v1.Volume) []*kubeaudit.AuditResult {
if container.VolumeMounts == nil {
return nil
}

var auditResults []*kubeaudit.AuditResult

for _, mount := range container.VolumeMounts {
if volume, ok := sensitiveVolumes[mount.Name]; ok {
auditResults = append(auditResults, &kubeaudit.AuditResult{
Name: SensitivePathsMounted,
Severity: kubeaudit.Error,
Message: fmt.Sprintf("Sensitive path mounted as volume: %s (hostPath: %s). It should be removed from the container's mounts list.", mount.Name, volume.HostPath.Path),
Metadata: kubeaudit.Metadata{
"Container": container.Name,
MountNameMetadataKey: mount.Name,
MountPathMetadataKey: mount.MountPath,
MountReadOnlyMetadataKey: fmt.Sprintf("%t", mount.ReadOnly),
MountVolumeNameKey: volume.Name,
MountVolumeHostPathKey: volume.HostPath.Path,
},
})
}
}

return auditResults
}

func getOverrideLabel(mountName string) string {
return overrideLabelPrefix + mountName
}
34 changes: 34 additions & 0 deletions auditors/mounts/mounts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package mounts

import (
"github.com/Shopify/kubeaudit/internal/override"
"strings"
"testing"

"github.com/Shopify/kubeaudit/internal/test"
)

const fixtureDir = "fixtures"

func TestSensitivePathsMounted(t *testing.T) {
cases := []struct {
file string
fixtureDir string
expectedErrors []string
}{
{"docker-sock-mounted.yml", fixtureDir, []string{SensitivePathsMounted}},
{"proc-mounted.yml", fixtureDir, []string{SensitivePathsMounted}},
{"proc-mounted-allowed.yml", fixtureDir, []string{override.GetOverriddenResultName(SensitivePathsMounted)}},
{"proc-mounted-allowed-multi-containers-multi-labels.yml", fixtureDir, []string{override.GetOverriddenResultName(SensitivePathsMounted)}},
{"proc-mounted-allowed-multi-containers-single-label.yml", fixtureDir, []string{SensitivePathsMounted, override.GetOverriddenResultName(SensitivePathsMounted)}},
}

config := Config{}

for _, tc := range cases {
t.Run(tc.file, func(t *testing.T) {
test.AuditManifest(t, tc.fixtureDir, tc.file, New(config), tc.expectedErrors)
test.AuditLocal(t, tc.fixtureDir, tc.file, New(config), strings.Split(tc.file, ".")[0], tc.expectedErrors)
})
}
}
5 changes: 5 additions & 0 deletions cmd/commands/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ func setConfigFromFlags(cmd *cobra.Command, conf config.KubeauditConfig) config.
conf.AuditorConfig.Capabilities.AllowAddList = capabilitiesConfig.AllowAddList
}

if flagset.Changed(sensitivePathsFlagName) {
conf.AuditorConfig.Mounts.SensitivePaths = mountsConfig.SensitivePaths
}

return conf
}

Expand Down Expand Up @@ -88,4 +92,5 @@ func init() {
setImageFlags(auditAllCmd)
setLimitsFlags(auditAllCmd)
setCapabilitiesFlags(auditAllCmd)
setPathsFlags(auditAllCmd)
}
48 changes: 48 additions & 0 deletions cmd/commands/mounts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package commands

import (
"bytes"
"fmt"
"github.com/Shopify/kubeaudit/auditors/mounts"
"github.com/spf13/cobra"
"strings"
)

const sensitivePathsFlagName = "denyPathsList"

var mountsConfig mounts.Config

var mountsCmd = &cobra.Command{
Use: "mounts",
Short: "Audit containers that mount sensitive paths",
Long: fmt.Sprintf(`This command determines which containers mount sensitive host paths. If no paths list is provided, the following
paths are used:
%s
A WARN result is generated when a container mounts one or more paths specified with the '--denyPathsList' argument.
Example usage:
kubeaudit mounts --denyPathsList "%s"`, formatPathsList(), strings.Join(mounts.DefaultSensitivePaths[:3], ",")),
Run: func(cmd *cobra.Command, args []string) {
runAudit(mounts.New(mountsConfig))(cmd, args)
},
}

func init() {
RootCmd.AddCommand(mountsCmd)
setPathsFlags(mountsCmd)
}

func setPathsFlags(cmd *cobra.Command) {
cmd.Flags().StringSliceVarP(&mountsConfig.SensitivePaths, sensitivePathsFlagName, "d", mounts.DefaultSensitivePaths,
"List of sensitive paths that shouldn't be mounted")
}

func formatPathsList() string {
var buffer bytes.Buffer
for _, path := range mounts.DefaultSensitivePaths {
buffer.WriteString("\n- ")
buffer.WriteString(path)
}
return buffer.String()
}
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"github.com/Shopify/kubeaudit/auditors/mounts"
"io"
"io/ioutil"

Expand Down Expand Up @@ -54,4 +55,5 @@ type AuditorConfig struct {
Capabilities capabilities.Config `yaml:"capabilities"`
Image image.Config `yaml:"image"`
Limits limits.Config `yaml:"limits"`
Mounts mounts.Config `yaml:"mounts"`
}
Loading

0 comments on commit fb6003a

Please sign in to comment.