From 9ea7e0af26895e33c2bf539c0cb254f97d8306b4 Mon Sep 17 00:00:00 2001 From: "Tim St. Clair" Date: Fri, 2 Dec 2016 10:37:28 -0800 Subject: [PATCH] Add cluster-level AppArmor E2E test --- test/e2e/BUILD | 2 + test/e2e/apparmor.go | 177 +++++++++++++++++++++++++++++++++++++ test/e2e/framework/util.go | 15 ++++ 3 files changed, 194 insertions(+) create mode 100644 test/e2e/apparmor.go diff --git a/test/e2e/BUILD b/test/e2e/BUILD index 121fa48d96..0cc1516255 100644 --- a/test/e2e/BUILD +++ b/test/e2e/BUILD @@ -14,6 +14,7 @@ go_library( name = "go_default_library", srcs = [ "addon_update.go", + "apparmor.go", "autoscaling_utils.go", "batch_v1_jobs.go", "cadvisor.go", @@ -162,6 +163,7 @@ go_library( "//pkg/registry/generic/registry:go_default_library", "//pkg/runtime:go_default_library", "//pkg/runtime/schema:go_default_library", + "//pkg/security/apparmor:go_default_library", "//pkg/types:go_default_library", "//pkg/util:go_default_library", "//pkg/util/exec:go_default_library", diff --git a/test/e2e/apparmor.go b/test/e2e/apparmor.go new file mode 100644 index 0000000000..9b0378f6ed --- /dev/null +++ b/test/e2e/apparmor.go @@ -0,0 +1,177 @@ +/* +Copyright 2016 The Kubernetes Authors. + +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 e2e + +import ( + "fmt" + + api "k8s.io/kubernetes/pkg/api/v1" + extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/security/apparmor" + "k8s.io/kubernetes/test/e2e/framework" + + . "github.com/onsi/ginkgo" +) + +const ( + profilePrefix = "e2e-apparmor-test-" + allowedPath = "/expect_allowed_write" + deniedPath = "/expect_permission_denied" +) + +var _ = framework.KubeDescribe("AppArmor", func() { + f := framework.NewDefaultFramework("apparmor") + + BeforeEach(func() { + SkipIfAppArmorNotSupported() + LoadAppArmorProfiles(f) + }) + + It("should enforce an AppArmor profile", func() { + profile := "localhost/" + profilePrefix + f.Namespace.Name + testCmd := fmt.Sprintf(` +if touch %[1]s; then + echo "FAILURE: write to %[1]s should be denied" + exit 1 +elif ! touch %[2]s; then + echo "FAILURE: write to %[2]s should be allowed" + exit 2 +fi`, deniedPath, allowedPath) + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "test-apparmor", + Annotations: map[string]string{ + apparmor.ContainerAnnotationKeyPrefix + "test": profile, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{ + Name: "test", + Image: "gcr.io/google_containers/busybox:1.24", + Command: []string{"sh", "-c", testCmd}, + }}, + RestartPolicy: api.RestartPolicyNever, + }, + } + f.PodClient().Create(pod) + framework.ExpectNoError(framework.WaitForPodSuccessInNamespace( + f.ClientSet, pod.Name, f.Namespace.Name)) + framework.LogFailedContainers(f.ClientSet, f.Namespace.Name, framework.Logf) + }) +}) + +func SkipIfAppArmorNotSupported() { + framework.SkipUnlessNodeOSDistroIs("gci", "ubuntu") +} + +func LoadAppArmorProfiles(f *framework.Framework) { + _, err := createAppArmorProfileCM(f) + framework.ExpectNoError(err) + _, err = createAppArmorProfileLoader(f) + framework.ExpectNoError(err) +} + +func createAppArmorProfileCM(f *framework.Framework) (*api.ConfigMap, error) { + profileName := profilePrefix + f.Namespace.Name + profile := fmt.Sprintf(`#include +profile %s flags=(attach_disconnected) { + #include + + file, + + deny %s w, + audit %s w, +} +`, profileName, deniedPath, allowedPath) + + cm := &api.ConfigMap{ + ObjectMeta: api.ObjectMeta{ + Name: "apparmor-profiles", + Namespace: f.Namespace.Name, + }, + Data: map[string]string{ + profileName: profile, + }, + } + return f.ClientSet.Core().ConfigMaps(f.Namespace.Name).Create(cm) +} + +func createAppArmorProfileLoader(f *framework.Framework) (*extensions.DaemonSet, error) { + True := true + // Copied from https://github.com/kubernetes/contrib/blob/master/apparmor/loader/example-configmap.yaml + loader := &extensions.DaemonSet{ + ObjectMeta: api.ObjectMeta{ + Name: "apparmor-loader", + Namespace: f.Namespace.Name, + }, + Spec: extensions.DaemonSetSpec{ + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"name": "apparmor-loader"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{ + Name: "apparmor-loader", + Image: "gcr.io/google_containers/apparmor-loader:0.1", + Args: []string{"-poll", "10s", "/profiles"}, + SecurityContext: &api.SecurityContext{ + Privileged: &True, + }, + VolumeMounts: []api.VolumeMount{{ + Name: "sys", + MountPath: "/sys", + ReadOnly: true, + }, { + Name: "apparmor-includes", + MountPath: "/etc/apparmor.d", + ReadOnly: true, + }, { + Name: "profiles", + MountPath: "/profiles", + ReadOnly: true, + }}, + }}, + Volumes: []api.Volume{{ + Name: "sys", + VolumeSource: api.VolumeSource{ + HostPath: &api.HostPathVolumeSource{ + Path: "/sys", + }, + }, + }, { + Name: "apparmor-includes", + VolumeSource: api.VolumeSource{ + HostPath: &api.HostPathVolumeSource{ + Path: "/etc/apparmor.d", + }, + }, + }, { + Name: "profiles", + VolumeSource: api.VolumeSource{ + ConfigMap: &api.ConfigMapVolumeSource{ + LocalObjectReference: api.LocalObjectReference{ + Name: "apparmor-profiles", + }, + }, + }, + }}, + }, + }, + }, + } + return f.ClientSet.Extensions().DaemonSets(f.Namespace.Name).Create(loader) +} diff --git a/test/e2e/framework/util.go b/test/e2e/framework/util.go index a48b9cf7bc..a291c19575 100644 --- a/test/e2e/framework/util.go +++ b/test/e2e/framework/util.go @@ -313,6 +313,12 @@ func SkipUnlessProviderIs(supportedProviders ...string) { } } +func SkipUnlessNodeOSDistroIs(supportedNodeOsDistros ...string) { + if !NodeOSDistroIs(supportedNodeOsDistros...) { + Skipf("Only supported for node OS distro %v (not %s)", supportedNodeOsDistros, TestContext.NodeOSDistro) + } +} + func SkipIfContainerRuntimeIs(runtimes ...string) { for _, runtime := range runtimes { if runtime == TestContext.ContainerRuntime { @@ -330,6 +336,15 @@ func ProviderIs(providers ...string) bool { return false } +func NodeOSDistroIs(supportedNodeOsDistros ...string) bool { + for _, distro := range supportedNodeOsDistros { + if strings.ToLower(distro) == strings.ToLower(TestContext.NodeOSDistro) { + return true + } + } + return false +} + func SkipUnlessServerVersionGTE(v semver.Version, c discovery.ServerVersionInterface) { gte, err := ServerVersionGTE(v, c) if err != nil {