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

Commit

Permalink
Add Deny Unconfined AppArmor policy (#95)
Browse files Browse the repository at this point in the history
Signed-off-by: Dustin Decker <[email protected]>
  • Loading branch information
dustin-decker authored Jan 4, 2021
1 parent f667eb2 commit a3f73cd
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 1 deletion.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ k-rail is a workload policy enforcement tool for Kubernetes. It can help you sec
- [No Anonymous Role Binding](#no-anonymous-role-binding)
- [Invalid Pod Disruption Budget](#invalid-pod-disruption-budget)
- [No External IP on Service](#no-external-ip-on-service)
- [Deny Unconfined AppArmor Policies](#deny-unconfined-apparmor-policies)
- [Configuration](#configuration)
- [Webhook Configuration](#webhook-configuration)
- [Logging](#logging)
Expand Down Expand Up @@ -461,6 +462,10 @@ Prevent misconfigured pod disruption budgets from disrupting normal system maint

Prevents providing External IPs on a Service to mitigate [CVE-2020-8554](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-8554).

## Deny Unconfined AppArmor Policies

Prevents users from specifing an unconfined apparmor policy which can be used with other conditions to lead to [container escape](https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/).

# Configuration

For the Helm deployment, all configuration is contained in [`charts/k-rail/values.yaml`](charts/k-rail/values.yaml).
Expand Down
2 changes: 1 addition & 1 deletion charts/k-rail/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ apiVersion: v1
name: k-rail
description: Kubernetes security tool for policy enforcement
home: https://github.com/cruise-automation/k-rail
version: v2.5.0
version: v2.6.0
3 changes: 3 additions & 0 deletions charts/k-rail/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ config:
- name: "service_no_external_ip"
enabled: True
report_only: False
- name: "pod_deny_unconfined_apparmor_policy"
enabled: True
report_only: False

exemptions:
- resource_name: "*"
Expand Down
56 changes: 56 additions & 0 deletions policies/pod/deny_unconfined_apparmor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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 pod

import (
"context"
"strings"

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

type PolicyDenyUnconfinedApparmorPolicy struct{}

func (p PolicyDenyUnconfinedApparmorPolicy) Name() string {
return "pod_deny_unconfined_apparmor_policy"
}

func (p PolicyDenyUnconfinedApparmorPolicy) Validate(ctx context.Context, config policies.Config, ar *admissionv1beta1.AdmissionRequest) ([]policies.ResourceViolation, []policies.PatchOperation) {
resourceViolations := []policies.ResourceViolation{}

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

if podResource.ResourceKind == "Pod" {
for name, value := range podResource.PodAnnotations {
if strings.HasPrefix(name, "container.apparmor.security.beta.kubernetes.io") {
if value == "unconfined" {
resourceViolations = append(resourceViolations, policies.ResourceViolation{
Namespace: ar.Namespace,
ResourceName: podResource.ResourceName,
ResourceKind: podResource.ResourceKind,
Violation: violationText,
Policy: p.Name(),
Error: nil,
})
}
}
}
}

return resourceViolations, nil
}
79 changes: 79 additions & 0 deletions policies/pod/deny_unconfined_apparmor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// 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 pod

import (
"context"
"encoding/json"
"testing"

"github.com/cruise-automation/k-rail/policies"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)

func TestPolicyDenyUnconfinedApparmorPolicy(t *testing.T) {
type args struct {
ctx context.Context
config policies.Config
ar *admissionv1beta1.AdmissionRequest
}
tests := []struct {
name string
podSpec v1.PodSpec
annotations map[string]string
violations int
}{
{
name: "violation",
podSpec: v1.PodSpec{},
annotations: map[string]string{
"container.apparmor.security.beta.kubernetes.io/app": "unconfined",
},
violations: 1,
},
{
name: "no violation",
podSpec: v1.PodSpec{},
annotations: map[string]string{},
violations: 0,
},
{
name: "no violation, using other than unconfined",
podSpec: v1.PodSpec{},
annotations: map[string]string{
"container.apparmor.security.beta.kubernetes.io/app": "runtime/default",
},
violations: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := PolicyDenyUnconfinedApparmorPolicy{}
raw, _ := json.Marshal(corev1.Pod{Spec: tt.podSpec, ObjectMeta: metav1.ObjectMeta{Annotations: tt.annotations}})
ar := &admissionv1beta1.AdmissionRequest{
Namespace: "namespace",
Name: "name",
Object: runtime.RawExtension{Raw: raw},
Resource: metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"},
}
got, _ := p.Validate(context.Background(), policies.Config{}, ar)
if len(got) != tt.violations {
t.Errorf("PolicyDenyUnconfinedApparmorPolicy.Validate() got = %v, want %v", len(got), tt.violations)
}
})
}
}
1 change: 1 addition & 0 deletions server/policies.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func (s *Server) registerPolicies() {
s.registerPolicy(pod.PolicyDefaultSeccompPolicy{})
s.registerPolicy(pod.PolicyNoShareProcessNamespace{})
s.registerPolicy(pod.PolicyImagePullPolicy{})
s.registerPolicy(pod.PolicyDenyUnconfinedApparmorPolicy{})
s.registerPolicy(ingress.PolicyRequireIngressExemption{})
s.registerPolicy(service.PolicyRequireServiceLoadbalancerExemption{})
s.registerPolicy(service.PolicyServiceNoExternalIP{})
Expand Down

0 comments on commit a3f73cd

Please sign in to comment.