Skip to content

Commit

Permalink
Add override for unconfined apparmor profile ✨ (#489)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ser87ch authored Oct 12, 2022
1 parent 2061bc7 commit fbffa4a
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 7 deletions.
34 changes: 31 additions & 3 deletions auditors/apparmor/apparmor.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ import (
"github.com/Shopify/kubeaudit"
"github.com/Shopify/kubeaudit/pkg/fix"
"github.com/Shopify/kubeaudit/pkg/k8s"
"github.com/Shopify/kubeaudit/pkg/override"
)

const Name = "apparmor"

const (
// AppArmorAnnotationMissing occurs when the apparmor annotation is missing
AppArmorAnnotationMissing = "AppArmorAnnotationMissing"
// AppArmorDisabled occurs when the apparmor annotation is set to a bad value
// AppArmorDisabled occurs when the apparmor annotation is set to the unconfined value
AppArmorDisabled = "AppArmorDisabled"
// AppArmorDisabled occurs when the apparmor annotation is set to a bad value
AppArmorBadValue = "AppArmorBadValue"
// AppArmorInvalidAnnotation occurs when the apparmor annotation key refers to a container which doesn't exist. This will
// prevent the manifest from being applied to a cluster with AppArmor enabled.
AppArmorInvalidAnnotation = "AppArmorInvalidAnnotation"
Expand All @@ -29,10 +32,14 @@ const (

// The profile specifying the runtime default.
ProfileRuntimeDefault = "runtime/default"
// The profile specifying the unconfined profile.
ProfileUnconfined = "unconfined"
// The prefix for specifying profiles loaded on the node.
ProfileNamePrefix = "localhost/"
)

const OverrideLabel = "allow-disabled-apparmor"

// AppArmor implements Auditable
type AppArmor struct{}

Expand All @@ -46,8 +53,10 @@ func (a *AppArmor) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.
var containerNames []string

for _, container := range k8s.GetContainers(resource) {
containerNames = append(containerNames, container.Name)
containerName := container.Name
containerNames = append(containerNames, containerName)
auditResult := auditContainer(container, resource)
auditResult = applyDisabledOverride(auditResult, containerName, resource)
if auditResult != nil {
auditResults = append(auditResults, auditResult)
}
Expand Down Expand Up @@ -80,9 +89,16 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi
}

if isAppArmorDisabled(containerAnnotation, annotations) {
var rule string
if isAppArmorUnconfined(containerAnnotation, annotations) {
rule = AppArmorDisabled
} else {
rule = AppArmorBadValue
}

return &kubeaudit.AuditResult{
Auditor: Name,
Rule: AppArmorDisabled,
Rule: rule,
Message: fmt.Sprintf("AppArmor is disabled. The apparmor annotation should be set to '%s' or start with '%s'.", ProfileRuntimeDefault, ProfileNamePrefix),
Severity: kubeaudit.Error,
Metadata: kubeaudit.Metadata{
Expand All @@ -100,6 +116,13 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi
return nil
}

func applyDisabledOverride(auditResult *kubeaudit.AuditResult, containerName string, resource k8s.Resource) *kubeaudit.AuditResult {
if auditResult == nil || auditResult.Rule != AppArmorDisabled {
return auditResult
}
return override.ApplyOverride(auditResult, Name, containerName, resource, OverrideLabel)
}

func auditPodAnnotations(resource k8s.Resource, containerNames []string) []*kubeaudit.AuditResult {
var auditResults []*kubeaudit.AuditResult
for annotationKey, annotationValue := range k8s.GetAnnotations(resource) {
Expand Down Expand Up @@ -136,6 +159,11 @@ func isAppArmorDisabled(apparmorAnnotation string, annotations map[string]string
return !ok || profileName != ProfileRuntimeDefault && !strings.HasPrefix(profileName, ProfileNamePrefix)
}

func isAppArmorUnconfined(apparmorAnnotation string, annotations map[string]string) bool {
profileName, ok := annotations[apparmorAnnotation]
return ok && profileName == ProfileUnconfined
}

func getContainerAnnotation(container *k8s.ContainerV1) string {
return ContainerAnnotationKeyPrefix + container.Name
}
Expand Down
7 changes: 6 additions & 1 deletion auditors/apparmor/apparmor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/Shopify/kubeaudit/internal/test"
"github.com/Shopify/kubeaudit/pkg/k8s"
"github.com/Shopify/kubeaudit/pkg/override"
"github.com/stretchr/testify/assert"
)

Expand All @@ -21,8 +22,12 @@ func TestAuditAppArmor(t *testing.T) {
{"apparmor-annotation-missing.yml", []string{AppArmorAnnotationMissing}, true},
{"apparmor-annotation-init-container-enabled.yml", nil, true},
{"apparmor-annotation-init-container-missing.yml", []string{AppArmorAnnotationMissing}, true},
{"apparmor-disabled.yml", []string{AppArmorDisabled}, true},
{"apparmor-disabled-overriden.yml", []string{override.GetOverriddenResultName(AppArmorDisabled)}, true},
{"apparmor-disabled-overriden-multiple.yml", []string{AppArmorAnnotationMissing, override.GetOverriddenResultName(AppArmorDisabled)}, true},
// These are invalid manifests so we should only test it in manifest mode as kubernetes will fail to apply it
{"apparmor-disabled.yml", []string{AppArmorDisabled}, false},
{"apparmor-bad-value.yml", []string{AppArmorBadValue}, false},
{"apparmor-bad-value-override.yml", []string{AppArmorBadValue}, false},
{"apparmor-invalid-annotation.yml", []string{AppArmorInvalidAnnotation}, false},
}

Expand Down
13 changes: 13 additions & 0 deletions auditors/apparmor/fixtures/apparmor-bad-value-override.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v1
kind: Pod
metadata:
name: pod
namespace: apparmor-bad-value-override
annotations:
container.apparmor.security.beta.kubernetes.io/container: badval
labels:
container.audit.kubernetes.io/container.allow-disabled-apparmor: "SomeReason"
spec:
containers:
- name: container
image: scratch
11 changes: 11 additions & 0 deletions auditors/apparmor/fixtures/apparmor-bad-value.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Pod
metadata:
name: pod
namespace: apparmor-bad-value
annotations:
container.apparmor.security.beta.kubernetes.io/container: badval
spec:
containers:
- name: container
image: scratch
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: pod
namespace: apparmor-disabled-overriden-multiple
annotations:
container.apparmor.security.beta.kubernetes.io/container2: unconfined
labels:
container.audit.kubernetes.io/container2.allow-disabled-apparmor: "SomeReason"
spec:
containers:
- name: container
image: scratch
- name: container2
image: scratch
13 changes: 13 additions & 0 deletions auditors/apparmor/fixtures/apparmor-disabled-overriden.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v1
kind: Pod
metadata:
name: pod
namespace: apparmor-disabled-overriden
annotations:
container.apparmor.security.beta.kubernetes.io/container: unconfined
labels:
container.audit.kubernetes.io/container.allow-disabled-apparmor: "SomeReason"
spec:
containers:
- name: container
image: scratch
2 changes: 1 addition & 1 deletion auditors/apparmor/fixtures/apparmor-disabled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ metadata:
name: pod
namespace: apparmor-disabled
annotations:
container.apparmor.security.beta.kubernetes.io/container: badval
container.apparmor.security.beta.kubernetes.io/container: unconfined
spec:
containers:
- name: container
Expand Down
26 changes: 25 additions & 1 deletion docs/auditors/apparmor.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,28 @@ To learn more about AppArmor in Kubernetes, see https://kubernetes.io/docs/tutor
## Override Errors
Overrides are not currently supported for `apparmor`.
First, see the [Introduction to Override Errors](/README.md#override-errors).
Override identifier for the `unconfined` apparmor profile value: `allow-disabled-apparmor`

Container overrides have the form:
```yaml
container.audit.kubernetes.io/[container name].allow-disabled-apparmor: "SomeReason"
```

Example of resource with the `unconfined` apparmor profile overridden for a specific container:
```yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
metadata:
annotations:
container.apparmor.security.beta.kubernetes.io/myContainer: unconfined
labels:
container.audit.kubernetes.io/myContainer.allow-disabled-apparmor: "SomeReason"
spec:
containers:
- name: myContainer
image: scratch
```
2 changes: 1 addition & 1 deletion pkg/override/override.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func NewRedundantOverrideResult(auditorName, containerName, overrideReason, over
}

// ApplyOverride checks if hasOverride is true. If it is, it changes the severity of the audit result from error to
// warn, adds the override reason to the metadata and removes the pending fix
// info, adds the override reason to the metadata and removes the pending fix
func ApplyOverride(auditResult *kubeaudit.AuditResult, auditorName, containerName string, resource k8s.Resource, overrideLabel string) *kubeaudit.AuditResult {
hasOverride, overrideReason := GetContainerOverrideReason(containerName, resource, overrideLabel)

Expand Down

0 comments on commit fbffa4a

Please sign in to comment.