Check if containers are run as root user.
parent
80b6b4a8b1
commit
f2c948af77
31
checks.md
31
checks.md
|
@ -106,6 +106,37 @@ spec:
|
|||
- NET_ADMIN
|
||||
```
|
||||
|
||||
###### Run As Non-Root
|
||||
|
||||
Name: `run-as-non-root`
|
||||
|
||||
Group: `security`
|
||||
|
||||
Description: If containers within a pod are allowed to run with the pid `0`, then the host can be subjected to malicious attacks. It occurs when the container image or the base image is not from a trusted source. We recommend that a UID other than 0 be used in your container image for running applications. However, this can also be enforced in the Kubernetes pod configuration shown below.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
# Don't do this
|
||||
spec:
|
||||
containers:
|
||||
- name: mypod
|
||||
image: nginx
|
||||
```
|
||||
|
||||
How to fix:
|
||||
|
||||
```yaml
|
||||
# Specify to error out when container is run as root
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
containers:
|
||||
- name: mypod
|
||||
image: nginx
|
||||
|
||||
```
|
||||
|
||||
###### Fully Qualified Image
|
||||
|
||||
Name: `fully-qualified-image`
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
Copyright 2019 DigitalOcean
|
||||
|
||||
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
|
||||
|
||||
http://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 security
|
||||
|
||||
import (
|
||||
"github.com/digitalocean/clusterlint/kube"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func initPod() *kube.Objects {
|
||||
objs := &kube.Objects{
|
||||
Pods: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{Kind: "Pod", APIVersion: "v1"},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod_foo", Namespace: "k8s"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func containerPrivileged(privileged bool) *kube.Objects {
|
||||
objs := initPod()
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
SecurityContext: &corev1.SecurityContext{Privileged: &privileged},
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func containerNonRoot(pod, container *bool) *kube.Objects {
|
||||
objs := initPod()
|
||||
podSecurityContext := &corev1.PodSecurityContext{}
|
||||
if pod != nil {
|
||||
podSecurityContext = &corev1.PodSecurityContext{RunAsNonRoot: pod}
|
||||
}
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
SecurityContext: podSecurityContext,
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
SecurityContext: &corev1.SecurityContext{RunAsNonRoot: container},
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func initContainerNonRoot(pod, container *bool) *kube.Objects {
|
||||
objs := initPod()
|
||||
podSecurityContext := &corev1.PodSecurityContext{}
|
||||
if pod != nil {
|
||||
podSecurityContext = &corev1.PodSecurityContext{RunAsNonRoot: pod}
|
||||
}
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
SecurityContext: podSecurityContext,
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
SecurityContext: &corev1.SecurityContext{RunAsNonRoot: container},
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func containerSecurityContextNil() *kube.Objects {
|
||||
objs := initPod()
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func containerPrivilegedNil() *kube.Objects {
|
||||
objs := initPod()
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
SecurityContext: &corev1.SecurityContext{},
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func initContainerPrivileged(privileged bool) *kube.Objects {
|
||||
objs := initPod()
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
SecurityContext: &corev1.SecurityContext{Privileged: &privileged},
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func initContainerSecurityContextNil() *kube.Objects {
|
||||
objs := initPod()
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func initContainerPrivilegedNil() *kube.Objects {
|
||||
objs := initPod()
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
SecurityContext: &corev1.SecurityContext{},
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
|
@ -22,8 +22,6 @@ import (
|
|||
"github.com/digitalocean/clusterlint/checks"
|
||||
"github.com/digitalocean/clusterlint/kube"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestPrivilegedContainersCheckMeta(t *testing.T) {
|
||||
|
@ -55,8 +53,8 @@ func TestPrivilegedContainerWarning(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "pod with container in privileged mode",
|
||||
objs: container(true),
|
||||
expected: warnings(container(true), privilegedContainerCheck.Name()),
|
||||
objs: containerPrivileged(true),
|
||||
expected: warnings(containerPrivileged(true), privilegedContainerCheck.Name()),
|
||||
},
|
||||
{
|
||||
name: "pod with container.SecurityContext = nil",
|
||||
|
@ -70,13 +68,13 @@ func TestPrivilegedContainerWarning(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "pod with container in regular mode",
|
||||
objs: container(false),
|
||||
objs: containerPrivileged(false),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "pod with init container in privileged mode",
|
||||
objs: initContainer(true),
|
||||
expected: warnings(initContainer(true), privilegedContainerCheck.Name()),
|
||||
objs: initContainerPrivileged(true),
|
||||
expected: warnings(initContainerPrivileged(true), privilegedContainerCheck.Name()),
|
||||
},
|
||||
{
|
||||
name: "pod with initContainer.SecurityContext = nil",
|
||||
|
@ -90,7 +88,7 @@ func TestPrivilegedContainerWarning(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "pod with init container in regular mode",
|
||||
objs: initContainer(false),
|
||||
objs: initContainerPrivileged(false),
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
@ -104,90 +102,6 @@ func TestPrivilegedContainerWarning(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func initPod() *kube.Objects {
|
||||
objs := &kube.Objects{
|
||||
Pods: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{Kind: "Pod", APIVersion: "v1"},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod_foo", Namespace: "k8s"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func container(privileged bool) *kube.Objects {
|
||||
objs := initPod()
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
SecurityContext: &corev1.SecurityContext{Privileged: &privileged},
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func containerSecurityContextNil() *kube.Objects {
|
||||
objs := initPod()
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func containerPrivilegedNil() *kube.Objects {
|
||||
objs := initPod()
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
SecurityContext: &corev1.SecurityContext{},
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func initContainer(privileged bool) *kube.Objects {
|
||||
objs := initPod()
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
SecurityContext: &corev1.SecurityContext{Privileged: &privileged},
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func initContainerSecurityContextNil() *kube.Objects {
|
||||
objs := initPod()
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func initContainerPrivilegedNil() *kube.Objects {
|
||||
objs := initPod()
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
SecurityContext: &corev1.SecurityContext{},
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func warnings(objs *kube.Objects, name string) []checks.Diagnostic {
|
||||
pod := objs.Pods.Items[0]
|
||||
d := []checks.Diagnostic{
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
Copyright 2019 DigitalOcean
|
||||
|
||||
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
|
||||
|
||||
http://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 security
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/digitalocean/clusterlint/checks"
|
||||
"github.com/digitalocean/clusterlint/kube"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func init() {
|
||||
checks.Register(&nonRootUserCheck{})
|
||||
}
|
||||
|
||||
type nonRootUserCheck struct{}
|
||||
|
||||
// Name returns a unique name for this check.
|
||||
func (nr *nonRootUserCheck) Name() string {
|
||||
return "non-root-user"
|
||||
}
|
||||
|
||||
// Groups returns a list of group names this check should be part of.
|
||||
func (nr *nonRootUserCheck) Groups() []string {
|
||||
return []string{"security"}
|
||||
}
|
||||
|
||||
// Description returns a detailed human-readable description of what this check
|
||||
// does.
|
||||
func (nr *nonRootUserCheck) Description() string {
|
||||
return "Checks if there are pods which run as root user"
|
||||
}
|
||||
|
||||
// Run runs this check on a set of Kubernetes objects. It can return warnings
|
||||
// (low-priority problems) and errors (high-priority problems) as well as an
|
||||
// error value indicating that the check failed to run.
|
||||
func (nr *nonRootUserCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
|
||||
var diagnostics []checks.Diagnostic
|
||||
|
||||
for _, pod := range objects.Pods.Items {
|
||||
diagnostics = append(diagnostics, nr.checkRootUser(pod.Spec.Containers, pod)...)
|
||||
diagnostics = append(diagnostics, nr.checkRootUser(pod.Spec.InitContainers, pod)...)
|
||||
}
|
||||
|
||||
return diagnostics, nil
|
||||
}
|
||||
|
||||
func (nr *nonRootUserCheck) checkRootUser(containers []corev1.Container, pod corev1.Pod) []checks.Diagnostic {
|
||||
var diagnostics []checks.Diagnostic
|
||||
for _, container := range containers {
|
||||
podRunAsRoot := pod.Spec.SecurityContext == nil || pod.Spec.SecurityContext.RunAsNonRoot == nil || !*pod.Spec.SecurityContext.RunAsNonRoot
|
||||
containerRunAsRoot := container.SecurityContext == nil || container.SecurityContext.RunAsNonRoot == nil || !*container.SecurityContext.RunAsNonRoot
|
||||
|
||||
if containerRunAsRoot && podRunAsRoot {
|
||||
d := checks.Diagnostic{
|
||||
Check: nr.Name(),
|
||||
Severity: checks.Warning,
|
||||
Message: fmt.Sprintf("Container `%s` can run as root user. Please ensure that the image is from a trusted source.", container.Name),
|
||||
Kind: checks.Pod,
|
||||
Object: &pod.ObjectMeta,
|
||||
Owners: pod.ObjectMeta.GetOwnerReferences(),
|
||||
}
|
||||
diagnostics = append(diagnostics, d)
|
||||
}
|
||||
}
|
||||
return diagnostics
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
Copyright 2019 DigitalOcean
|
||||
|
||||
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
|
||||
|
||||
http://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 security
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/digitalocean/clusterlint/checks"
|
||||
"github.com/digitalocean/clusterlint/kube"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestNonRootUserCheckMeta(t *testing.T) {
|
||||
nonRootUserCheck := nonRootUserCheck{}
|
||||
assert.Equal(t, "non-root-user", nonRootUserCheck.Name())
|
||||
assert.Equal(t, []string{"security"}, nonRootUserCheck.Groups())
|
||||
assert.NotEmpty(t, nonRootUserCheck.Description())
|
||||
}
|
||||
|
||||
func TestNonRootUserCheckRegistration(t *testing.T) {
|
||||
nonRootUserCheck := &nonRootUserCheck{}
|
||||
check, err := checks.Get("non-root-user")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, check, nonRootUserCheck)
|
||||
}
|
||||
|
||||
func TestNonRootUserWarning(t *testing.T) {
|
||||
nonRootUserCheck := nonRootUserCheck{}
|
||||
trueVar := true
|
||||
falseVar := false
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
objs *kube.Objects
|
||||
expected []checks.Diagnostic
|
||||
}{
|
||||
{
|
||||
name: "no pods",
|
||||
objs: &kube.Objects{Pods: &corev1.PodList{}},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "pod security context and container security context unset",
|
||||
objs: containerSecurityContextNil(),
|
||||
expected: diagnostic(),
|
||||
},
|
||||
{
|
||||
name: "pod security context unset, container with run as non root set to true",
|
||||
objs: containerNonRoot(nil, &trueVar),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "pod security context unset, container with run as non root set to false",
|
||||
objs: containerNonRoot(nil, &falseVar),
|
||||
expected: diagnostic(),
|
||||
},
|
||||
{
|
||||
name: "pod run as non root true, container run as non root true",
|
||||
objs: containerNonRoot(&trueVar, &trueVar),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "pod run as non root true, container run as non root false",
|
||||
objs: containerNonRoot(&trueVar, &falseVar),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "pod run as non root false, container run as non root true",
|
||||
objs: containerNonRoot(&falseVar, &trueVar),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "pod run as non root false, container run as non root false",
|
||||
objs: containerNonRoot(&falseVar, &falseVar),
|
||||
expected: diagnostic(),
|
||||
},
|
||||
{
|
||||
name: "pod run as non root true, container security context unset",
|
||||
objs: containerNonRoot(&trueVar, nil),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "pod run as non root false, container security context unset",
|
||||
objs: containerNonRoot(&falseVar, nil),
|
||||
expected: diagnostic(),
|
||||
},
|
||||
// init container tests
|
||||
|
||||
{
|
||||
name: "pod security context and init container security context unset",
|
||||
objs: initContainerSecurityContextNil(),
|
||||
expected: diagnostic(),
|
||||
},
|
||||
{
|
||||
name: "pod security context unset, init container with run as non root set to true",
|
||||
objs: initContainerNonRoot(nil, &trueVar),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "pod security context unset, init container with run as non root set to false",
|
||||
objs: initContainerNonRoot(nil, &falseVar),
|
||||
expected: diagnostic(),
|
||||
},
|
||||
{
|
||||
name: "pod run as non root true, init container run as non root true",
|
||||
objs: initContainerNonRoot(&trueVar, &trueVar),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "pod run as non root true, init container run as non root false",
|
||||
objs: initContainerNonRoot(&trueVar, &falseVar),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "pod run as non root false, init container run as non root true",
|
||||
objs: initContainerNonRoot(&falseVar, &trueVar),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "pod run as non root false, init container run as non root false",
|
||||
objs: initContainerNonRoot(&falseVar, &falseVar),
|
||||
expected: diagnostic(),
|
||||
},
|
||||
{
|
||||
name: "pod run as non root true, init container security context unset",
|
||||
objs: initContainerNonRoot(&trueVar, nil),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "pod run as non root false, init container security context unset",
|
||||
objs: initContainerNonRoot(&falseVar, nil),
|
||||
expected: diagnostic(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
d, err := nonRootUserCheck.Run(test.objs)
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, test.expected, d)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func diagnostic() []checks.Diagnostic {
|
||||
pod := initPod().Pods.Items[0]
|
||||
d := []checks.Diagnostic{
|
||||
{
|
||||
Check: "non-root-user",
|
||||
Severity: checks.Warning,
|
||||
Message: "Container `bar` can run as root user. Please ensure that the image is from a trusted source.",
|
||||
Kind: checks.Pod,
|
||||
Object: &pod.ObjectMeta,
|
||||
Owners: pod.ObjectMeta.GetOwnerReferences(),
|
||||
},
|
||||
}
|
||||
return d
|
||||
}
|
Loading…
Reference in New Issue