From e763168abd8802afe6dbd59fddd1aca52ca70353 Mon Sep 17 00:00:00 2001 From: Andrew Chen Date: Sun, 14 May 2017 15:02:23 -0700 Subject: [PATCH] WtD: edit AppArmor (#3754) * WtD: edit AppArmor * WtD: edit AppArmor --- docs/tutorials/clusters/apparmor.md | 340 +++++++++++++++------------- 1 file changed, 185 insertions(+), 155 deletions(-) diff --git a/docs/tutorials/clusters/apparmor.md b/docs/tutorials/clusters/apparmor.md index 13e7b52c49..a6ec38868f 100644 --- a/docs/tutorials/clusters/apparmor.md +++ b/docs/tutorials/clusters/apparmor.md @@ -12,20 +12,17 @@ redirect_from: {% assign for_k8s_version="v1.4" %}{% include feature-state-beta.md %} -AppArmor is a Linux kernel security module that can reduce the potential attack surface of an -application and provide greater defense in depth for Applications. - -AppArmor supplements the standard Linux user and group based +AppArmor is a Linux kernel security module that supplements the standard Linux user and group based permissions to confine programs to a limited set of resources. AppArmor can be configured for any -application to reduce its potential attack surface and provide greater defense in depth. It is +application to reduce its potential attack surface and provide greater in-depth defense. It is configured through profiles tuned to whitelist the access needed by a specific program or container, such as Linux capabilities, network access, file permissions, etc. Each profile can be run in either -enforcing mode, which blocks access to disallowed resources, or complain mode, which only reports +*enforcing* mode, which blocks access to disallowed resources, or *complain* mode, which only reports violations. AppArmor can help you to run a more secure deployment by restricting what containers are allowed to -do, and /or providing better auditing through system logs. However, it is important to keep in mind -that AppArmor is not a silver bullet, and can only do so much to protect against exploits in your +do, and/or provide better auditing through system logs. However, it is important to keep in mind +that AppArmor is not a silver bullet and can only do so much to protect against exploits in your application code. It is important to provide good, restrictive profiles, and harden your applications and cluster from other angles as well. @@ -33,11 +30,11 @@ applications and cluster from other angles as well. {% capture objectives %} -* One way to load a profile on a node -* How to enforce the profile on a Pod -* How to check that the profile is loaded -* What happens when a profile is violated -* What happens when a profile cannot be loaded +* See an example of how to load a profile on a node +* Learn how to enforce the profile on a Pod +* Learn how to check that the profile is loaded +* See what happens when a profile is violated +* See what happens when a profile cannot be loaded {% endcapture %} @@ -45,53 +42,61 @@ applications and cluster from other angles as well. Make sure: -1. **Kubernetes version is at least v1.4**. Kubernetes support for AppArmor was added in +1. Kubernetes version is at least v1.4 -- Kubernetes support for AppArmor was added in v1.4. Kubernetes components older than v1.4 are not aware of the new AppArmor annotations, and will **silently ignore** any AppArmor settings that are provided. To ensure that your Pods are receiving the expected protections, it is important to verify the Kubelet version of your nodes: - $ kubectl get nodes -o=jsonpath=$'{range .items[*]}{@.metadata.name}: {@.status.nodeInfo.kubeletVersion}\n{end}' - gke-test-default-pool-239f5d02-gyn2: v1.4.0 - gke-test-default-pool-239f5d02-x1kf: v1.4.0 - gke-test-default-pool-239f5d02-xwux: v1.4.0 + ``` + $ kubectl get nodes -o=jsonpath=$'{range .items[*]}{@.metadata.name}: {@.status.nodeInfo.kubeletVersion}\n{end}' + gke-test-default-pool-239f5d02-gyn2: v1.4.0 + gke-test-default-pool-239f5d02-x1kf: v1.4.0 + gke-test-default-pool-239f5d02-xwux: v1.4.0 + ``` -2. **AppArmor kernel module is enabled**. For the Linux kernel to enforce an AppArmor profile, the +2. AppArmor kernel module is enabled -- For the Linux kernel to enforce an AppArmor profile, the AppArmor kernel module must be installed and enabled. Several distributions enable the module by default, such as Ubuntu and SUSE, and many others provide optional support. To check whether the module is enabled, check the `/sys/module/apparmor/parameters/enabled` file: - $ cat /sys/module/apparmor/parameters/enabled - Y + ``` + $ cat /sys/module/apparmor/parameters/enabled + Y + ``` If the Kubelet contains AppArmor support (>= v1.4), it will refuse to run a Pod with AppArmor options if the kernel module is not enabled. - *Note: Ubuntu carries many AppArmor patches that have not been merged into the upstream Linux + **Note:** Ubuntu carries many AppArmor patches that have not been merged into the upstream Linux kernel, including patches that add additional hooks and features. Kubernetes has only been - tested with the upstream version, and does not promise support for other features.* + tested with the upstream version, and does not promise support for other features. -3. **Container runtime is Docker**. Currently the only Kubernetes-supported container runtime that +3. Container runtime is Docker -- Currently the only Kubernetes-supported container runtime that also supports AppArmor is Docker. As more runtimes add AppArmor support, the options will be expanded. You can verify that your nodes are running docker with: - $ kubectl get nodes -o=jsonpath=$'{range .items[*]}{@.metadata.name}: {@.status.nodeInfo.containerRuntimeVersion}\n{end}' - gke-test-default-pool-239f5d02-gyn2: docker://1.11.2 - gke-test-default-pool-239f5d02-x1kf: docker://1.11.2 - gke-test-default-pool-239f5d02-xwux: docker://1.11.2 + ``` + $ kubectl get nodes -o=jsonpath=$'{range .items[*]}{@.metadata.name}: {@.status.nodeInfo.containerRuntimeVersion}\n{end}' + gke-test-default-pool-239f5d02-gyn2: docker://1.11.2 + gke-test-default-pool-239f5d02-x1kf: docker://1.11.2 + gke-test-default-pool-239f5d02-xwux: docker://1.11.2 + ``` If the Kubelet contains AppArmor support (>= v1.4), it will refuse to run a Pod with AppArmor options if the runtime is not Docker. -4. **Profile is loaded**. AppArmor is applied to a Pod by specifying an AppArmor profile that each +4. Profile is loaded -- AppArmor is applied to a Pod by specifying an AppArmor profile that each container should be run with. If any of the specified profiles is not already loaded in the kernel, the Kubelet (>= v1.4) will reject the Pod. You can view which profiles are loaded on a node by checking the `/sys/kernel/security/apparmor/profiles` file. For example: - $ ssh gke-test-default-pool-239f5d02-gyn2 "sudo cat /sys/kernel/security/apparmor/profiles | sort" - apparmor-test-deny-write (enforce) - apparmor-test-audit-write (enforce) - docker-default (enforce) - k8s-nginx (enforce) + ``` + $ ssh gke-test-default-pool-239f5d02-gyn2 "sudo cat /sys/kernel/security/apparmor/profiles | sort" + apparmor-test-deny-write (enforce) + apparmor-test-audit-write (enforce) + docker-default (enforce) + k8s-nginx (enforce) + ``` For more details on loading profiles on nodes, see [Setting up nodes with profiles](#setting-up-nodes-with-profiles). @@ -101,10 +106,12 @@ with AppArmor options if any of the prerequisites are not met. You can also veri on nodes by checking the node ready condition message (though this is likely to be removed in a later release): - $ kubectl get nodes -o=jsonpath=$'{range .items[*]}{@.metadata.name}: {.status.conditions[?(@.reason=="KubeletReady")].message}\n{end}' - gke-test-default-pool-239f5d02-gyn2: kubelet is posting ready status. AppArmor enabled - gke-test-default-pool-239f5d02-x1kf: kubelet is posting ready status. AppArmor enabled - gke-test-default-pool-239f5d02-xwux: kubelet is posting ready status. AppArmor enabled +```shell +$ kubectl get nodes -o=jsonpath=$'{range .items[*]}{@.metadata.name}: {.status.conditions[?(@.reason=="KubeletReady")].message}\n{end}' +gke-test-default-pool-239f5d02-gyn2: kubelet is posting ready status. AppArmor enabled +gke-test-default-pool-239f5d02-x1kf: kubelet is posting ready status. AppArmor enabled +gke-test-default-pool-239f5d02-xwux: kubelet is posting ready status. AppArmor enabled +``` {% endcapture %} @@ -112,36 +119,42 @@ later release): ## Securing a Pod -*Note: AppArmor is currently in beta, so options are specified as annotations. Once support graduates to +**Note:** AppArmor is currently in beta, so options are specified as annotations. Once support graduates to general availability, the annotations will be replaced with first-class fields (more details in -[Upgrade path to GA](#upgrade-path-to-general-availability)).* +[Upgrade path to GA](#upgrade-path-to-general-availability)). AppArmor profiles are specified *per-container*. To specify the AppArmor profile to run a Pod container with, add an annotation to the Pod's metadata: - container.apparmor.security.beta.kubernetes.io/: +```yaml +container.apparmor.security.beta.kubernetes.io/: +``` Where `` is the name of the container to apply the profile to, and `` specifies the profile to apply. The `profile_ref` can be one of: -- `runtime/default` to apply the runtime's default profile. -- `localhost/` to apply the profile loaded on the host with the name `` +* `runtime/default` to apply the runtime's default profile. +* `localhost/` to apply the profile loaded on the host with the name `` See the [API Reference](#api-reference) for the full details on the annotation and profile name formats. -The Kubernetes AppArmor enforcement works by first checking that all the prerequisites have been +Kubernetes AppArmor enforcement works by first checking that all the prerequisites have been met, and then forwarding the profile selection to the container runtime for enforcement. If the prerequisites have not been met, the Pod will be rejected, and will not run. -To verify that the profile was applied, you can expect to see the AppArmor security option listed in the container created event: +To verify that the profile was applied, you can look for the AppArmor security option listed in the container created event: - $ kubectl get events | grep Created - 22s 22s 1 hello-apparmor Pod spec.containers{hello} Normal Created {kubelet e2e-test-stclair-minion-group-31nt} Created container with docker id 269a53b202d3; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write] +```shell +$ kubectl get events | grep Created +22s 22s 1 hello-apparmor Pod spec.containers{hello} Normal Created {kubelet e2e-test-stclair-minion-group-31nt} Created container with docker id 269a53b202d3; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write] +``` You can also verify directly that the container's root process is running with the correct profile by checking its proc attr: - $ kubectl exec cat /proc/1/attr/current - k8s-apparmor-example-deny-write (enforce) +```shell +$ kubectl exec cat /proc/1/attr/current +k8s-apparmor-example-deny-write (enforce) +``` ## Example @@ -150,121 +163,132 @@ You can also verify directly that the container's root process is running with t First, we need to load the profile we want to use onto our nodes. The profile we'll use simply denies all file writes: - {% include code.html language="text" file="deny-write.profile" ghlink="/docs/tutorials/clusters/deny-write.profile" %} Since we don't know where the Pod will be scheduled, we'll need to load the profile on all our nodes. For this example we'll just use SSH to install the profiles, but other approaches are discussed in [Setting up nodes with profiles](#setting-up-nodes-with-profiles). - $ NODES=( - # The SSH-accessible domain names of your nodes - gke-test-default-pool-239f5d02-gyn2.us-central1-a.my-k8s - gke-test-default-pool-239f5d02-x1kf.us-central1-a.my-k8s - gke-test-default-pool-239f5d02-xwux.us-central1-a.my-k8s) - $ for NODE in ${NODES[*]}; do ssh $NODE 'sudo apparmor_parser -q < +```shell +$ NODES=( + # The SSH-accessible domain names of your nodes + gke-test-default-pool-239f5d02-gyn2.us-central1-a.my-k8s + gke-test-default-pool-239f5d02-x1kf.us-central1-a.my-k8s + gke-test-default-pool-239f5d02-xwux.us-central1-a.my-k8s) +$ for NODE in ${NODES[*]}; do ssh $NODE 'sudo apparmor_parser -q < - profile k8s-apparmor-example-deny-write flags=(attach_disconnected) { - #include +profile k8s-apparmor-example-deny-write flags=(attach_disconnected) { + #include - file, + file, - # Deny all file writes. - deny /** w, - } - EOF' - done + # Deny all file writes. + deny /** w, +} +EOF' +done +``` Next, we'll run a simple "Hello AppArmor" pod with the deny-write profile: {% include code.html language="yaml" file="hello-apparmor-pod.yaml" ghlink="/docs/tutorials/clusters/hello-apparmor-pod.yaml" %} - $ kubectl create -f /dev/stdin < - Status: Failed - Reason: AppArmor - Message: Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded - IP: - Controllers: - Containers: - hello: - Image: busybox - Port: - Command: - sh - -c - echo 'Hello AppArmor!' && sleep 1h - Requests: - cpu: 100m - Environment Variables: - Volumes: - default-token-dnz7v: - Type: Secret (a volume populated by a Secret) - SecretName: default-token-dnz7v - QoS Tier: Burstable - Events: - FirstSeen LastSeen Count From SubobjectPath Type Reason Message - --------- -------- ----- ---- ------------- -------- ------ ------- - 23s 23s 1 {default-scheduler } Normal Scheduled Successfully assigned hello-apparmor-2 to e2e-test-stclair-minion-group-t1f5 - 23s 23s 1 {kubelet e2e-test-stclair-minion-group-t1f5} Warning AppArmor Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded +$ kubectl describe pod hello-apparmor-2 +Name: hello-apparmor-2 +Namespace: default +Node: gke-test-default-pool-239f5d02-x1kf/ +Start Time: Tue, 30 Aug 2016 17:58:56 -0700 +Labels: +Status: Failed +Reason: AppArmor +Message: Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded +IP: +Controllers: +Containers: + hello: + Image: busybox + Port: + Command: + sh + -c + echo 'Hello AppArmor!' && sleep 1h + Requests: + cpu: 100m + Environment Variables: +Volumes: + default-token-dnz7v: + Type: Secret (a volume populated by a Secret) + SecretName: default-token-dnz7v +QoS Tier: Burstable +Events: + FirstSeen LastSeen Count From SubobjectPath Type Reason Message + --------- -------- ----- ---- ------------- -------- ------ ------- + 23s 23s 1 {default-scheduler } Normal Scheduled Successfully assigned hello-apparmor-2 to e2e-test-stclair-minion-group-t1f5 + 23s 23s 1 {kubelet e2e-test-stclair-minion-group-t1f5} Warning AppArmor Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded +``` Note the pod status is Failed, with a helpful error message: `Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded`. An event was also recorded with the same message. @@ -276,12 +300,12 @@ Note the pod status is Failed, with a helpful error message: `Pod Cannot enforce Kubernetes does not currently provide any native mechanisms for loading AppArmor profiles onto nodes. There are lots of ways to setup the profiles though, such as: -- Through a [DaemonSet](/docs/concepts/workloads/controllers/daemonset/) that runs a Pod on each node to +* Through a [DaemonSet](/docs/concepts/workloads/controllers/daemonset/) that runs a Pod on each node to ensure the correct profiles are loaded. An example implementation can be found [here](https://github.com/kubernetes/contrib/tree/master/apparmor/loader). -- At node initialization time, using your node initialization scripts (e.g. Salt, Ansible, etc.) or +* At node initialization time, using your node initialization scripts (e.g. Salt, Ansible, etc.) or image. -- By copying the profiles to each node and loading them through SSH, as demonstrated in the +* By copying the profiles to each node and loading them through SSH, as demonstrated in the [Example](#example). The scheduler is not aware of which profiles are loaded onto which node, so the full set of profiles @@ -295,13 +319,17 @@ node with the required profile. If the PodSecurityPolicy extension is enabled, cluster-wide AppArmor restrictions can be applied. To enable the PodSecurityPolicy, two flags must be set on the `apiserver`: - --admission-control=PodSecurityPolicy[,others...] - --runtime-config=extensions/v1beta1/podsecuritypolicy[,others...] +``` +--admission-control=PodSecurityPolicy[,others...] +--runtime-config=extensions/v1beta1/podsecuritypolicy[,others...] +``` With the extension enabled, the AppArmor options can be specified as annotations on the PodSecurityPolicy: - apparmor.security.beta.kubernetes.io/defaultProfileName: - apparmor.security.beta.kubernetes.io/allowedProfileNames: [,others...] +```yaml +apparmor.security.beta.kubernetes.io/defaultProfileName: +apparmor.security.beta.kubernetes.io/allowedProfileNames: [,others...] +``` The default profile name option specifies the profile to apply to containers by default when none is specified. The allowed profile names option specifies a list of profiles that Pod containers are @@ -313,7 +341,9 @@ specification. If you do not want AppArmor to be available on your cluster, it can be disabled by a command-line flag: - --feature-gates=AppArmor=false +``` +--feature-gates=AppArmor=false +``` When disabled, any Pod that includes an AppArmor profile will fail validation with a "Forbidden" error. Note that by default docker always enables the "docker-default" profile on non-privileged @@ -343,10 +373,10 @@ explicitly reject the annotations for at least 2 releases after that. Getting AppArmor profiles specified correctly can be a tricky business. Fortunately there are some tools to help with that: -- `aa-genprof` and `aa-logprof` generate profile rules by monitoring an application's activity and +* `aa-genprof` and `aa-logprof` generate profile rules by monitoring an application's activity and logs, and admitting the actions it takes. Further instructions are provided by the [AppArmor documentation](http://wiki.apparmor.net/index.php/Profiling_with_tools). -- [bane](https://github.com/jfrazelle/bane) is an AppArmor profile generator for Docker that uses a +* [bane](https://github.com/jfrazelle/bane) is an AppArmor profile generator for Docker that uses a simplified profile language. It is recommended to run your application through Docker on a development workstation to generate @@ -361,7 +391,7 @@ logs or through `journalctl`. More information is provided in ## API Reference -**Pod Annotation**: +### Pod Annotation Specifying the profile a container will run with: @@ -370,7 +400,7 @@ Specifying the profile a container will run with: A separate profile can be specified for each container in the Pod. - **value**: a profile reference, described below -**Profile Reference**: +### Profile Reference - `runtime/default`: Refers to the default runtime profile. - Equivalent to not specifying a profile (without a PodSecurityPolicy default), except it still @@ -384,17 +414,17 @@ Specifying the profile a container will run with: Any other profile reference format is invalid. -**PodSecurityPolicy Annotations** +### PodSecurityPolicy Annotations Specifying the default profile to apply to containers when none is provided: -- **key**: `apparmor.security.beta.kubernetes.io/defaultProfileName` -- **value**: a profile reference, described above +* **key**: `apparmor.security.beta.kubernetes.io/defaultProfileName` +* **value**: a profile reference, described above Specifying the list of profiles Pod containers is allowed to specify: -- **key**: `apparmor.security.beta.kubernetes.io/allowedProfileNames` -- **value**: a comma-separated list of profile references (described above) +* **key**: `apparmor.security.beta.kubernetes.io/allowedProfileNames` +* **value**: a comma-separated list of profile references (described above) - Although an escaped comma is a legal character in a profile name, it cannot be explicitly allowed here @@ -404,8 +434,8 @@ Specifying the list of profiles Pod containers is allowed to specify: Additional resources: -- [Quick guide to the AppArmor profile language](http://wiki.apparmor.net/index.php/QuickProfileLanguage) -- [AppArmor core policy reference](http://wiki.apparmor.net/index.php/ProfileLanguage) +* [Quick guide to the AppArmor profile language](http://wiki.apparmor.net/index.php/QuickProfileLanguage) +* [AppArmor core policy reference](http://wiki.apparmor.net/index.php/ProfileLanguage) {% endcapture %}