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

Commit

Permalink
Migrate to Seccomp profile in security Context ⚠️ (#475)
Browse files Browse the repository at this point in the history
* Initial commit

* Fix tests

* update docs

* cover localhost

* Update usages of seccomp

* remove test

* Update docs

* Add warning when annotations are present

* upd

* Add warning for manifest mode when annotations are present and no seccomp profile

* Update auditors/seccomp/seccomp.go

Co-authored-by: Genevieve Luyt <11131143+genevieveluyt@users.noreply.github.com>

* yml formatting

* fix name

Co-authored-by: Genevieve Luyt <11131143+genevieveluyt@users.noreply.github.com>
  • Loading branch information
Ser87ch and genevieveluyt authored Oct 5, 2022
1 parent c875a37 commit 2061bc7
Show file tree
Hide file tree
Showing 23 changed files with 438 additions and 429 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/Shopify/kubeaudit)](https://goreportcard.com/report/github.com/Shopify/kubeaudit)
[![GoDoc](https://godoc.org/github.com/Shopify/kubeaudit?status.png)](https://godoc.org/github.com/Shopify/kubeaudit)

> Kubeaudit no longer supports APIs deprecated as of [Kubernetes v.1.16 release](https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/). So, it is now a requirement for clusters to run Kubernetes >=1.16
> It is now a requirement for clusters to run Kubernetes >=1.19.

# kubeaudit :cloud: :lock: :muscle:
Expand Down
4 changes: 2 additions & 2 deletions auditors/all/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestAuditAll(t *testing.T) {
privesc.AllowPrivilegeEscalationNil,
privileged.PrivilegedNil,
rootfs.ReadOnlyRootFilesystemNil,
seccomp.SeccompAnnotationMissing,
seccomp.SeccompProfileMissing,
}

allAuditors, err := Auditors(
Expand Down Expand Up @@ -86,7 +86,7 @@ func TestAllWithConfig(t *testing.T) {
}
expectedErrors := []string{
apparmor.AppArmorAnnotationMissing,
seccomp.SeccompAnnotationMissing,
seccomp.SeccompProfileMissing,
}

conf := config.KubeauditConfig{
Expand Down
4 changes: 2 additions & 2 deletions auditors/apparmor/apparmor.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ func auditPodAnnotations(resource k8s.Resource, containerNames []string) []*kube
"Container": containerName,
"Annotation": fmt.Sprintf("%s: %s", annotationKey, annotationValue),
},
PendingFix: &fix.ByRemovingPodAnnotation{
Key: annotationKey,
PendingFix: &fix.ByRemovingPodAnnotations{
Keys: []string{annotationKey},
},
})
}
Expand Down
4 changes: 3 additions & 1 deletion auditors/image/fixtures/image-tag-missing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ spec:
name: deployment
annotations:
container.apparmor.security.beta.kubernetes.io/container: runtime/default
seccomp.security.alpha.kubernetes.io/pod: runtime/default
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: container
image: scratch
59 changes: 59 additions & 0 deletions auditors/seccomp/fix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package seccomp

import (
"fmt"

"github.com/Shopify/kubeaudit/pkg/k8s"
apiv1 "k8s.io/api/core/v1"
)

type BySettingSeccompProfile struct {
seccompProfileType apiv1.SeccompProfileType
}

func (pending *BySettingSeccompProfile) Plan() string {
return fmt.Sprintf("Set SeccompProfile type to '%s' in pod SecurityContext", pending.seccompProfileType)
}

func (pending *BySettingSeccompProfile) Apply(resource k8s.Resource) []k8s.Resource {
podSpec := k8s.GetPodSpec(resource)
if podSpec.SecurityContext == nil {
podSpec.SecurityContext = &apiv1.PodSecurityContext{}
}
podSpec.SecurityContext.SeccompProfile = &apiv1.SeccompProfile{Type: pending.seccompProfileType}

return nil
}

type BySettingSeccompProfileInContainer struct {
container *k8s.ContainerV1
seccompProfileType apiv1.SeccompProfileType
}

func (pending *BySettingSeccompProfileInContainer) Plan() string {
return fmt.Sprintf("Set SeccompProfile type to '%s' in SecurityContext for container `%s`", pending.seccompProfileType, pending.container.Name)
}

func (pending *BySettingSeccompProfileInContainer) Apply(resource k8s.Resource) []k8s.Resource {
if pending.container.SecurityContext == nil {
pending.container.SecurityContext = &apiv1.SecurityContext{}
}
pending.container.SecurityContext.SeccompProfile = &apiv1.SeccompProfile{Type: pending.seccompProfileType}
return nil
}

type ByRemovingSeccompProfileInContainer struct {
container *k8s.ContainerV1
}

func (pending *ByRemovingSeccompProfileInContainer) Plan() string {
return fmt.Sprintf("Remove SeccompProfile in SecurityContext for container `%s`", pending.container.Name)
}

func (pending *ByRemovingSeccompProfileInContainer) Apply(resource k8s.Resource) []k8s.Resource {
if pending.container.SecurityContext == nil {
return nil
}
pending.container.SecurityContext.SeccompProfile = nil
return nil
}
83 changes: 83 additions & 0 deletions auditors/seccomp/fix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package seccomp

import (
"strings"
"testing"

"github.com/Shopify/kubeaudit/internal/test"
"github.com/Shopify/kubeaudit/pkg/k8s"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apiv1 "k8s.io/api/core/v1"
)

const fixtureDir = "fixtures"
const emptyProfile = apiv1.SeccompProfileType("EMPTY")
const defaultProfile = apiv1.SeccompProfileTypeRuntimeDefault
const localhostProfile = apiv1.SeccompProfileTypeLocalhost

func TestFixSeccomp(t *testing.T) {
cases := []struct {
file string
expectedPodSeccompProfile apiv1.SeccompProfileType
expectedContainerSeccompProfiles []apiv1.SeccompProfileType
}{
{"seccomp-profile-missing.yml", defaultProfile, []apiv1.SeccompProfileType{emptyProfile}},
{"seccomp-profile-missing-disabled-container.yml", defaultProfile, []apiv1.SeccompProfileType{emptyProfile}},
{"seccomp-profile-missing-annotations.yml", defaultProfile, []apiv1.SeccompProfileType{emptyProfile}},
{"seccomp-disabled-pod.yml", defaultProfile, []apiv1.SeccompProfileType{defaultProfile}},
{"seccomp-disabled.yml", defaultProfile, []apiv1.SeccompProfileType{emptyProfile, emptyProfile}},
{"seccomp-disabled-localhost.yml", localhostProfile, []apiv1.SeccompProfileType{defaultProfile, emptyProfile}},
}

for _, tc := range cases {
// This line is needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721)
tc := tc
t.Run(tc.file, func(t *testing.T) {
resources, _ := test.FixSetup(t, fixtureDir, tc.file, New())
require.Len(t, resources, 1)
resource := resources[0]

updatedPodSpec := k8s.GetPodSpec(resource)
checkPodSeccompProfile(t, updatedPodSpec, tc.expectedPodSeccompProfile)
checkContainerSeccompProfiles(t, updatedPodSpec, tc.expectedContainerSeccompProfiles)
checkNoSeccompAnnotations(t, resource)
})
}
}

func checkPodSeccompProfile(t *testing.T, podSpec *apiv1.PodSpec, expectedPodSeccompProfile apiv1.SeccompProfileType) {
securityContext := podSpec.SecurityContext
if expectedPodSeccompProfile == emptyProfile {
require.Nil(t, securityContext)
} else {
assert.Equal(t, expectedPodSeccompProfile, securityContext.SeccompProfile.Type)
}
}

func checkContainerSeccompProfiles(t *testing.T, podSpec *apiv1.PodSpec, expectedContainerSeccompProfiles []apiv1.SeccompProfileType) {
for i, container := range podSpec.Containers {
securityContext := container.SecurityContext
expectedProfile := expectedContainerSeccompProfiles[i]
if expectedProfile == emptyProfile {
require.True(t, securityContext == nil || securityContext.SeccompProfile == nil)
} else {
assert.Equal(t, expectedProfile, securityContext.SeccompProfile.Type)
}
}
}

func checkNoSeccompAnnotations(t *testing.T, resource k8s.Resource) {
annotations := k8s.GetAnnotations(resource)
if annotations == nil {
return
}

seccompAnnotations := []string{}
for annotation := range annotations {
if annotation == PodAnnotationKey || strings.HasPrefix(annotation, ContainerAnnotationKeyPrefix) {
seccompAnnotations = append(seccompAnnotations, annotation)
}
}
assert.Empty(t, seccompAnnotations)
}
11 changes: 0 additions & 11 deletions auditors/seccomp/fixtures/seccomp-deprecated.yml

This file was deleted.

18 changes: 18 additions & 0 deletions auditors/seccomp/fixtures/seccomp-disabled-localhost.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: pod
namespace: seccomp-disabled-localhost
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: my-seccomp-profile.json
containers:
- name: container1
image: scratch
securityContext:
seccompProfile:
type: Unconfined
- name: container2
image: scratch
9 changes: 6 additions & 3 deletions auditors/seccomp/fixtures/seccomp-disabled-pod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ kind: Pod
metadata:
name: pod
namespace: seccomp-disabled-pod
annotations:
seccomp.security.alpha.kubernetes.io/pod: unconfined
container.seccomp.security.alpha.kubernetes.io/container: runtime/default
spec:
securityContext:
seccompProfile:
type: Unconfined
containers:
- name: container
image: scratch
securityContext:
seccompProfile:
type: RuntimeDefault
12 changes: 6 additions & 6 deletions auditors/seccomp/fixtures/seccomp-disabled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ kind: Pod
metadata:
name: pod
namespace: seccomp-disabled
annotations:
seccomp.security.alpha.kubernetes.io/pod: runtime/default
container.seccomp.security.alpha.kubernetes.io/container1: badval
container.seccomp.security.alpha.kubernetes.io/container2: unconfined
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: container1
image: scratch
securityContext:
seccompProfile:
type: Unconfined
- name: container2
image: scratch
- name: container3
image: scratch
6 changes: 4 additions & 2 deletions auditors/seccomp/fixtures/seccomp-enabled-pod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ kind: Pod
metadata:
name: pod
namespace: seccomp-enabled-pod
annotations:
seccomp.security.alpha.kubernetes.io/pod: localhost/bla
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: my-seccomp-profile.json
containers:
- name: container
image: scratch
5 changes: 3 additions & 2 deletions auditors/seccomp/fixtures/seccomp-enabled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ kind: Pod
metadata:
name: pod
namespace: seccomp-enabled
annotations:
container.seccomp.security.alpha.kubernetes.io/container: runtime/default
spec:
containers:
- name: container
image: scratch
securityContext:
seccompProfile:
type: RuntimeDefault
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ apiVersion: v1
kind: Pod
metadata:
name: pod
namespace: seccomp-deprecated-pod
namespace: seccomp-profile-missing-annotations
annotations:
seccomp.security.alpha.kubernetes.io/pod: docker/default
seccomp.security.alpha.kubernetes.io/pod: runtime/default
container.seccomp.security.alpha.kubernetes.io/container: localhost/bla
spec:
containers:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Pod
metadata:
name: pod
namespace: seccomp-profile-missing-disabled-container
spec:
containers:
- name: container
image: scratch
securityContext:
seccompProfile:
type: Unconfined
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: v1
kind: Pod
metadata:
name: pod
namespace: seccomp-annotation-missing
namespace: seccomp-profile-missing
spec:
containers:
- name: container
Expand Down
Loading

0 comments on commit 2061bc7

Please sign in to comment.