...

Source file src/k8s.io/kubernetes/test/e2e/framework/security/apparmor.go

Documentation: k8s.io/kubernetes/test/e2e/framework/security

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package security
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"github.com/onsi/gomega"
    24  	v1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/labels"
    27  	clientset "k8s.io/client-go/kubernetes"
    28  	"k8s.io/kubernetes/test/e2e/framework"
    29  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    30  	imageutils "k8s.io/kubernetes/test/utils/image"
    31  )
    32  
    33  const (
    34  	appArmorProfilePrefix = "e2e-apparmor-test-"
    35  	appArmorAllowedPath   = "/expect_allowed_write"
    36  	appArmorDeniedPath    = "/expect_permission_denied"
    37  
    38  	loaderLabelKey   = "name"
    39  	loaderLabelValue = "e2e-apparmor-loader"
    40  )
    41  
    42  // LoadAppArmorProfiles creates apparmor-profiles ConfigMap and apparmor-loader ReplicationController.
    43  func LoadAppArmorProfiles(ctx context.Context, nsName string, clientset clientset.Interface) {
    44  	createAppArmorProfileCM(ctx, nsName, clientset)
    45  	createAppArmorProfileLoader(ctx, nsName, clientset)
    46  }
    47  
    48  // CreateAppArmorTestPod creates a pod that tests apparmor profile enforcement. The pod exits with
    49  // an error code if the profile is incorrectly enforced. If runOnce is true the pod will exit after
    50  // a single test, otherwise it will repeat the test every 1 second until failure.
    51  func AppArmorTestPod(nsName string, unconfined bool, runOnce bool) *v1.Pod {
    52  	localhostProfile := appArmorProfilePrefix + nsName
    53  	testCmd := fmt.Sprintf(`
    54  if touch %[1]s; then
    55    echo "FAILURE: write to %[1]s should be denied"
    56    exit 1
    57  elif ! touch %[2]s; then
    58    echo "FAILURE: write to %[2]s should be allowed"
    59    exit 2
    60  elif [[ $(< /proc/self/attr/current) != "%[3]s" ]]; then
    61    echo "FAILURE: not running with expected profile %[3]s"
    62    echo "found: $(cat /proc/self/attr/current)"
    63    exit 3
    64  fi`, appArmorDeniedPath, appArmorAllowedPath, appArmorProfilePrefix+nsName)
    65  
    66  	if unconfined {
    67  		testCmd = `
    68  if cat /proc/sysrq-trigger 2>&1 | grep 'Permission denied'; then
    69    echo 'FAILURE: reading /proc/sysrq-trigger should be allowed'
    70    exit 1
    71  elif [[ $(< /proc/self/attr/current) != "unconfined" ]]; then
    72    echo 'FAILURE: not running with expected profile unconfined'
    73    exit 2
    74  fi`
    75  	}
    76  
    77  	if !runOnce {
    78  		testCmd = fmt.Sprintf(`while true; do
    79  %s
    80  sleep 1
    81  done`, testCmd)
    82  	}
    83  
    84  	loaderAffinity := &v1.Affinity{
    85  		PodAffinity: &v1.PodAffinity{
    86  			RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{{
    87  				Namespaces: []string{nsName},
    88  				LabelSelector: &metav1.LabelSelector{
    89  					MatchLabels: map[string]string{loaderLabelKey: loaderLabelValue},
    90  				},
    91  				TopologyKey: "kubernetes.io/hostname",
    92  			}},
    93  		},
    94  	}
    95  
    96  	profile := &v1.AppArmorProfile{}
    97  	if unconfined {
    98  		profile.Type = v1.AppArmorProfileTypeUnconfined
    99  	} else {
   100  		profile.Type = v1.AppArmorProfileTypeLocalhost
   101  		profile.LocalhostProfile = &localhostProfile
   102  	}
   103  
   104  	pod := &v1.Pod{
   105  		ObjectMeta: metav1.ObjectMeta{
   106  			GenerateName: "test-apparmor-",
   107  			Labels: map[string]string{
   108  				"test": "apparmor",
   109  			},
   110  		},
   111  		Spec: v1.PodSpec{
   112  			SecurityContext: &v1.PodSecurityContext{
   113  				AppArmorProfile: profile,
   114  			},
   115  			Affinity: loaderAffinity,
   116  			Containers: []v1.Container{{
   117  				Name:    "test",
   118  				Image:   imageutils.GetE2EImage(imageutils.BusyBox),
   119  				Command: []string{"sh", "-c", testCmd},
   120  			}},
   121  			RestartPolicy: v1.RestartPolicyNever,
   122  		},
   123  	}
   124  
   125  	return pod
   126  }
   127  
   128  func RunAppArmorTestPod(ctx context.Context, pod *v1.Pod, clientset clientset.Interface, podClient *e2epod.PodClient, runOnce bool) *v1.Pod {
   129  	if runOnce {
   130  		pod = podClient.Create(ctx, pod)
   131  		framework.ExpectNoError(e2epod.WaitForPodSuccessInNamespace(ctx,
   132  			clientset, pod.Name, pod.Namespace))
   133  		var err error
   134  		pod, err = podClient.Get(ctx, pod.Name, metav1.GetOptions{})
   135  		framework.ExpectNoError(err)
   136  	} else {
   137  		pod = podClient.CreateSync(ctx, pod)
   138  		framework.ExpectNoError(e2epod.WaitTimeoutForPodReadyInNamespace(ctx, clientset, pod.Name, pod.Namespace, framework.PodStartTimeout))
   139  	}
   140  
   141  	// Verify Pod affinity colocated the Pods.
   142  	loader := getRunningLoaderPod(ctx, pod.Namespace, clientset)
   143  	gomega.Expect(pod.Spec.NodeName).To(gomega.Equal(loader.Spec.NodeName))
   144  
   145  	return pod
   146  }
   147  
   148  func createAppArmorProfileCM(ctx context.Context, nsName string, clientset clientset.Interface) {
   149  	profileName := appArmorProfilePrefix + nsName
   150  	profile := fmt.Sprintf(`#include <tunables/global>
   151  profile %s flags=(attach_disconnected) {
   152    #include <abstractions/base>
   153  
   154    file,
   155  
   156    deny %s w,
   157    audit %s w,
   158  }
   159  `, profileName, appArmorDeniedPath, appArmorAllowedPath)
   160  
   161  	cm := &v1.ConfigMap{
   162  		ObjectMeta: metav1.ObjectMeta{
   163  			Name:      "apparmor-profiles",
   164  			Namespace: nsName,
   165  		},
   166  		Data: map[string]string{
   167  			profileName: profile,
   168  		},
   169  	}
   170  	_, err := clientset.CoreV1().ConfigMaps(nsName).Create(ctx, cm, metav1.CreateOptions{})
   171  	framework.ExpectNoError(err, "Failed to create apparmor-profiles ConfigMap")
   172  }
   173  
   174  func createAppArmorProfileLoader(ctx context.Context, nsName string, clientset clientset.Interface) {
   175  	True := true
   176  	One := int32(1)
   177  	loader := &v1.ReplicationController{
   178  		ObjectMeta: metav1.ObjectMeta{
   179  			Name:      "apparmor-loader",
   180  			Namespace: nsName,
   181  		},
   182  		Spec: v1.ReplicationControllerSpec{
   183  			Replicas: &One,
   184  			Template: &v1.PodTemplateSpec{
   185  				ObjectMeta: metav1.ObjectMeta{
   186  					Labels: map[string]string{loaderLabelKey: loaderLabelValue},
   187  				},
   188  				Spec: v1.PodSpec{
   189  					Containers: []v1.Container{{
   190  						Name:  "apparmor-loader",
   191  						Image: imageutils.GetE2EImage(imageutils.AppArmorLoader),
   192  						Args:  []string{"-poll", "10s", "/profiles"},
   193  						SecurityContext: &v1.SecurityContext{
   194  							Privileged: &True,
   195  						},
   196  						VolumeMounts: []v1.VolumeMount{{
   197  							Name:      "sys",
   198  							MountPath: "/sys",
   199  							ReadOnly:  true,
   200  						}, {
   201  							Name:      "apparmor-includes",
   202  							MountPath: "/etc/apparmor.d",
   203  							ReadOnly:  true,
   204  						}, {
   205  							Name:      "profiles",
   206  							MountPath: "/profiles",
   207  							ReadOnly:  true,
   208  						}},
   209  					}},
   210  					Volumes: []v1.Volume{{
   211  						Name: "sys",
   212  						VolumeSource: v1.VolumeSource{
   213  							HostPath: &v1.HostPathVolumeSource{
   214  								Path: "/sys",
   215  							},
   216  						},
   217  					}, {
   218  						Name: "apparmor-includes",
   219  						VolumeSource: v1.VolumeSource{
   220  							HostPath: &v1.HostPathVolumeSource{
   221  								Path: "/etc/apparmor.d",
   222  							},
   223  						},
   224  					}, {
   225  						Name: "profiles",
   226  						VolumeSource: v1.VolumeSource{
   227  							ConfigMap: &v1.ConfigMapVolumeSource{
   228  								LocalObjectReference: v1.LocalObjectReference{
   229  									Name: "apparmor-profiles",
   230  								},
   231  							},
   232  						},
   233  					}},
   234  				},
   235  			},
   236  		},
   237  	}
   238  	_, err := clientset.CoreV1().ReplicationControllers(nsName).Create(ctx, loader, metav1.CreateOptions{})
   239  	framework.ExpectNoError(err, "Failed to create apparmor-loader ReplicationController")
   240  
   241  	// Wait for loader to be ready.
   242  	getRunningLoaderPod(ctx, nsName, clientset)
   243  }
   244  
   245  func getRunningLoaderPod(ctx context.Context, nsName string, clientset clientset.Interface) *v1.Pod {
   246  	label := labels.SelectorFromSet(labels.Set(map[string]string{loaderLabelKey: loaderLabelValue}))
   247  	pods, err := e2epod.WaitForPodsWithLabelScheduled(ctx, clientset, nsName, label)
   248  	framework.ExpectNoError(err, "Failed to schedule apparmor-loader Pod")
   249  	pod := &pods.Items[0]
   250  	framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(ctx, clientset, pod), "Failed to run apparmor-loader Pod")
   251  	return pod
   252  }
   253  

View as plain text