1
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
43 func LoadAppArmorProfiles(ctx context.Context, nsName string, clientset clientset.Interface) {
44 createAppArmorProfileCM(ctx, nsName, clientset)
45 createAppArmorProfileLoader(ctx, nsName, clientset)
46 }
47
48
49
50
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
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
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