Change:
This is a Feature Request What would you like to be Post-merge of , move the tutorials under into Merge clusters section into security section here: Why is this needed Issue description is a slight modification from original request in the comment Comments /hold for /cc /sig docs security
this might be a good first issue for new contributors in sig-security-docs /triage accepted
Hi, I would like to work on this issue. /assign
/kind cleanup
Hi , I a query: moving the tutorials from the cluster sub-directory into the security sub-directory, the cluster sub-directory will only have its file so do I need to do something regarding it ( file)? Thanks
Whenever you are free can you please help me with the above mentioned query. Thanks

Discussion:
* [Running ZooKeeper, A CP Distributed System](/docs/tutorials/stateful-application/zookeeper/) <del> ## Clusters * [AppArmor](/docs/tutorials/clusters/apparmor/) * [seccomp](/docs/tutorials/clusters/seccomp/) </del> ## Services * [Using Source IP](/docs/tutorials/services/source-ip/)

* [Apply Pod Security Standards at Cluster level](/docs/tutorials/security/cluster-level-pss/) * [Apply Pod Security Standards at Namespace level](/docs/tutorials/security/ns-level-pss/) <ins> * [AppArmor](/docs/tutorials/security/apparmor/) * [seccomp](/docs/tutorials/security/seccomp/) </ins> ## {{% heading "whatsnext" %}} If you would like to write a tutorial, see

<del> --- title: "Clusters" weight: 60 --- </del>

<del> --- reviewers: - stclair title: Restrict a Container's Access to Resources with AppArmor content_type: tutorial weight: 10 --- <!-- overview --> {{< feature-state for_k8s_version="v1.4" state="beta" >}} 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 in-depth defense. It is configured through profiles tuned to allow 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 violations. AppArmor can help you to run a more secure deployment by restricting what containers are allowed to 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. ## {{% heading "objectives" %}} * 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 ## {{% heading "prerequisites" %}} Make sure: 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: ```shell 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 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: ```shell 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 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. {{< /note >}} 3. Container runtime supports AppArmor -- Currently all common Kubernetes-supported container runtimes should support AppArmor, like {{< glossary_tooltip term_id="docker">}}, {{< glossary_tooltip term_id="cri-o" >}} or {{< glossary_tooltip term_id="containerd" >}}. Please refer to the corresponding runtime documentation and verify that the cluster fulfills the requirements to use AppArmor. 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: ```shell 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). As long as the Kubelet version includes AppArmor support (>= v1.4), the Kubelet will reject a Pod with AppArmor options if any of the prerequisites are not met. You can also verify AppArmor support on nodes by checking the node ready condition message (though this is likely to be removed in a later release): ```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 ``` <!-- lessoncontent --> ## Securing a Pod {{< 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)). {{< /note >}} 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: ```yaml container.apparmor.security.beta.kubernetes.io/<container_name>: <profile_ref> ``` Where `<container_name>` is the name of the container to apply the profile to, and `<profile_ref>` specifies the profile to apply. The `profile_ref` can be one of: * `runtime/default` to apply the runtime's default profile * `localhost/<profile_name>` to apply the profile loaded on the host with the name `<profile_name>` * `unconfined` to indicate that no profiles will be loaded See the [API Reference](#api-reference) for the full details on the annotation and profile name formats. 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 look for the AppArmor security option listed in the container created event: ```shell kubectl get events | grep Created ``` ``` 22s        22s         1         hello-apparmor     Pod       spec.containers{hello}   Normal    Created     {kubelet e2e-test-stclair-node-pool-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: ```shell kubectl exec <pod_name> cat /proc/1/attr/current ``` ``` k8s-apparmor-example-deny-write (enforce) ``` ## Example *This example assumes you have already set up a cluster with AppArmor support.* First, we need to load the profile we want to use onto our nodes. This profile denies all file writes: ```shell #include <tunables/global> profile k8s-apparmor-example-deny-write flags=(attach_disconnected) { #include <abstractions/base> file, # Deny all file writes. deny /** w, } ``` 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 use SSH to install the profiles, but other approaches are discussed in [Setting up nodes with profiles](#setting-up-nodes-with-profiles). ```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 <<EOF #include <tunables/global> profile k8s-apparmor-example-deny-write flags=(attach_disconnected) { #include <abstractions/base> file, # Deny all file writes. deny /** w, } EOF' done ``` Next, we'll run a simple "Hello AppArmor" pod with the deny-write profile: {{< codenew file="pods/security/hello-apparmor.yaml" >}} ```shell kubectl create -f ./hello-apparmor.yaml ``` If we look at the pod events, we can see that the Pod container was created with the AppArmor profile "k8s-apparmor-example-deny-write": ```shell kubectl get events | grep hello-apparmor ``` ``` 14s        14s         1         hello-apparmor   Pod                                Normal    Scheduled   {default-scheduler }                           Successfully assigned hello-apparmor to gke-test-default-pool-239f5d02-gyn2 14s        14s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Pulling     {kubelet gke-test-default-pool-239f5d02-gyn2}   pulling image "busybox" 13s        13s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Pulled      {kubelet gke-test-default-pool-239f5d02-gyn2}   Successfully pulled image "busybox" 13s        13s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Created     {kubelet gke-test-default-pool-239f5d02-gyn2}   Created container with docker id 06b6cd1c0989; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write] 13s        13s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Started     {kubelet gke-test-default-pool-239f5d02-gyn2}   Started container with docker id 06b6cd1c0989 ``` We can verify that the container is actually running with that profile by checking its proc attr: ```shell kubectl exec hello-apparmor -- cat /proc/1/attr/current ``` ``` k8s-apparmor-example-deny-write (enforce) ``` Finally, we can see what happens if we try to violate the profile by writing to a file: ```shell kubectl exec hello-apparmor -- touch /tmp/test ``` ``` touch: /tmp/test: Permission denied error: error executing remote command: command terminated with non-zero exit code: Error executing in Docker Container: 1 ``` To wrap up, let's look at what happens if we try to specify a profile that hasn't been loaded: ```shell kubectl create -f /dev/stdin <<EOF ``` ```yaml apiVersion: v1 kind: Pod metadata: name: hello-apparmor-2 annotations: container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-allow-write spec: containers: - name: hello image: busybox command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ] EOF pod/hello-apparmor-2 created ``` ```shell 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:        <none> Annotations:   container.apparmor.security.beta.kubernetes.io/hello=localhost/k8s-apparmor-example-allow-write Status:        Pending Reason:        AppArmor Message:       Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded IP: Controllers:   <none> Containers: hello: Container ID: Image:     busybox Image ID: Port: Command: sh -c echo 'Hello AppArmor!' && sleep 1h State:              Waiting Reason:           Blocked Ready:              False Restart Count:      0 Environment:        <none> Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-dnz7v (ro) Conditions: Type          Status Initialized   True Ready         False PodScheduled  True Volumes: default-token-dnz7v: Type:    Secret (a volume populated by a Secret) SecretName:    default-token-dnz7v Optional:   false QoS Class:      BestEffort Node-Selectors: <none> Tolerations:    <none> 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-node-pool-t1f5 23s          23s         1        {kubelet e2e-test-stclair-node-pool-t1f5}             Warning        AppArmor    Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded ``` Note the pod status is Pending, 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. ## Administration ### Setting up nodes with profiles 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 ensure the correct profiles are loaded. An example implementation can be found [here](https://git.k8s.io/kubernetes/test/images/apparmor-loader). * 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 [Example](#example). The scheduler is not aware of which profiles are loaded onto which node, so the full set of profiles must be loaded onto every node.  An alternative approach is to add a node label for each profile (or class of profiles) on the node, and use a [node selector](/docs/concepts/scheduling-eviction/assign-pod-node/) to ensure the Pod is run on a node with the required profile. ### Restricting profiles with the PodSecurityPolicy {{< note >}} PodSecurityPolicy is deprecated in Kubernetes v1.21, and will be removed in v1.25. See [PodSecurityPolicy documentation](/docs/concepts/policy/pod-security-policy/) for more information. {{< /note >}} If the PodSecurityPolicy extension is enabled, cluster-wide AppArmor restrictions can be applied. To enable the PodSecurityPolicy, the following flag must be set on the `apiserver`: ``` --enable-admission-plugins=PodSecurityPolicy[,others...] ``` The AppArmor options can be specified as annotations on the PodSecurityPolicy: ```yaml apparmor.security.beta.kubernetes.io/defaultProfileName: <profile_ref> apparmor.security.beta.kubernetes.io/allowedProfileNames: <profile_ref>[,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 allowed to be run with. If both options are provided, the default must be allowed. The profiles are specified in the same format as on containers. See the [API Reference](#api-reference) for the full specification. ### Disabling AppArmor 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 ``` 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 pods (if the AppArmor kernel module is enabled), and will continue to do so even if the feature-gate is disabled. The option to disable AppArmor will be removed when AppArmor graduates to general availability (GA). ### Upgrading to Kubernetes v1.4 with AppArmor No action is required with respect to AppArmor to upgrade your cluster to v1.4. However, if any existing pods had an AppArmor annotation, they will not go through validation (or PodSecurityPolicy admission). If permissive profiles are loaded on the nodes, a malicious user could pre-apply a permissive profile to escalate the pod privileges above the docker-default. If this is a concern, it is recommended to scrub the cluster of any pods containing an annotation with `apparmor.security.beta.kubernetes.io`. ### Upgrade path to General Availability When AppArmor is ready to be graduated to general availability (GA), the options currently specified through annotations will be converted to fields. Supporting all the upgrade and downgrade paths through the transition is very nuanced, and will be explained in detail when the transition occurs. We will commit to supporting both fields and annotations for at least 2 releases, and will explicitly reject the annotations for at least 2 releases after that. ## Authoring Profiles 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 logs, and admitting the actions it takes. Further instructions are provided by the [AppArmor documentation](https://gitlab.com/apparmor/apparmor/wikis/Profiling_with_tools). * [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 the profiles, but there is nothing preventing running the tools on the Kubernetes node where your Pod is running. To debug problems with AppArmor, you can check the system logs to see what, specifically, was denied. AppArmor logs verbose messages to `dmesg`, and errors can usually be found in the system logs or through `journalctl`. More information is provided in [AppArmor failures](https://gitlab.com/apparmor/apparmor/wikis/AppArmor_Failures). ## API Reference ### Pod Annotation Specifying the profile a container will run with: - **key**: `container.apparmor.security.beta.kubernetes.io/<container_name>` Where `<container_name>` matches the name of a container in the Pod. A separate profile can be specified for each container in the Pod. - **value**: a profile reference, described below ### Profile Reference - `runtime/default`: Refers to the default runtime profile. - Equivalent to not specifying a profile (without a PodSecurityPolicy default), except it still requires AppArmor to be enabled. - For Docker, this resolves to the [`docker-default`](https://docs.docker.com/engine/security/apparmor/) profile for non-privileged containers, and unconfined (no profile) for privileged containers. - `localhost/<profile_name>`: Refers to a profile loaded on the node (localhost) by name. - The possible profile names are detailed in the [core policy reference](https://gitlab.com/apparmor/apparmor/wikis/AppArmor_Core_Policy_Reference#profile-names-and-attachment-specifications). - `unconfined`: This effectively disables AppArmor on the container. Any other profile reference format is invalid. ### 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 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) - Although an escaped comma is a legal character in a profile name, it cannot be explicitly allowed here. ## {{% heading "whatsnext" %}} Additional resources: * [Quick guide to the AppArmor profile language](https://gitlab.com/apparmor/apparmor/wikis/QuickProfileLanguage) * [AppArmor core policy reference](https://gitlab.com/apparmor/apparmor/wikis/Policy_Layout) </del>

<del> --- reviewers: - hasheddan - pjbgf - saschagrunert title: Restrict a Container's Syscalls with seccomp content_type: tutorial weight: 20 min-kubernetes-server-version: v1.22 --- <!-- overview --> {{< feature-state for_k8s_version="v1.19" state="stable" >}} Seccomp stands for secure computing mode and has been a feature of the Linux kernel since version 2.6.12. It can be used to sandbox the privileges of a process, restricting the calls it is able to make from userspace into the kernel. Kubernetes lets you automatically apply seccomp profiles loaded onto a {{< glossary_tooltip text="node" term_id="node" >}} to your Pods and containers. Identifying the privileges required for your workloads can be difficult. In this tutorial, you will go through how to load seccomp profiles into a local Kubernetes cluster, how to apply them to a Pod, and how you can begin to craft profiles that give only the necessary privileges to your container processes. ## {{% heading "objectives" %}} * Learn how to load seccomp profiles on a node * Learn how to apply a seccomp profile to a container * Observe auditing of syscalls made by a container process * Observe behavior when a missing profile is specified * Observe a violation of a seccomp profile * Learn how to create fine-grained seccomp profiles * Learn how to apply a container runtime default seccomp profile ## {{% heading "prerequisites" %}} In order to complete all steps in this tutorial, you must install [kind](/docs/tasks/tools/#kind) and [kubectl](/docs/tasks/tools/#kubectl). This tutorial shows some examples that are still alpha (since v1.22) and others that use only generally available seccomp functionality. You should make sure that your cluster is [configured correctly](https://kind.sigs.k8s.io/docs/user/quick-start/#setting-kubernetes-version) for the version you are using. The tutorial also uses the `curl` tool for downloading examples to your computer. You can adapt the steps to use a different tool if you prefer. {{< note >}} It is not possible to apply a seccomp profile to a container running with `privileged: true` set in the container's `securityContext`. Privileged containers always run as `Unconfined`. {{< /note >}} <!-- steps --> ## Download example seccomp profiles {#download-profiles} The contents of these profiles will be explored later on, but for now go ahead and download them into a directory named `profiles/` so that they can be loaded into the cluster. {{< tabs name="tab_with_code" >}} {{{< tab name="audit.json" >}} {{< codenew file="pods/security/seccomp/profiles/audit.json" >}} {{< /tab >}} {{< tab name="violation.json" >}} {{< codenew file="pods/security/seccomp/profiles/violation.json" >}} {{< /tab >}}} {{< tab name="fine-grained.json" >}} {{< codenew file="pods/security/seccomp/profiles/fine-grained.json" >}} {{< /tab >}}} {{< /tabs >}} Run these commands: ```shell mkdir ./profiles curl -L -o profiles/audit.json https://k8s.io/examples/pods/security/seccomp/profiles/audit.json curl -L -o profiles/violation.json https://k8s.io/examples/pods/security/seccomp/profiles/violation.json curl -L -o profiles/fine-grained.json https://k8s.io/examples/pods/security/seccomp/profiles/fine-grained.json ls profiles ``` You should see three profiles listed at the end of the final step: ``` audit.json  fine-grained.json  violation.json ``` ## Create a local Kubernetes cluster with kind For simplicity, [kind](https://kind.sigs.k8s.io/) can be used to create a single node cluster with the seccomp profiles loaded. Kind runs Kubernetes in Docker, so each node of the cluster is a container. This allows for files to be mounted in the filesystem of each container similar to loading files onto a node. {{< codenew file="pods/security/seccomp/kind.yaml" >}} Download that example kind configuration, and save it to a file named `kind.yaml`: ```shell curl -L -O https://k8s.io/examples/pods/security/seccomp/kind.yaml ``` You can set a specific Kubernetes version by setting the node's container image. See [Nodes](https://kind.sigs.k8s.io/docs/user/configuration/#nodes) within the kind documentation about configuration for more details on this. This tutorial assumes you are using Kubernetes {{< param "version" >}}. As an alpha feature, you can configure Kubernetes to use the profile that the {{< glossary_tooltip text="container runtime" term_id="container-runtime" >}} prefers by default, rather than falling back to `Unconfined`. If you want to try that, see [enable the use of `RuntimeDefault` as the default seccomp profile for all workloads](#enable-the-use-of-runtimedefault-as-the-default-seccomp-profile-for-all-workloads) before you continue. Once you have a kind configuration in place, create the kind cluster with that configuration: ```shell kind create cluster --config=kind.yaml ``` After the new Kubernetes cluster is ready, identify the Docker container running as the single node cluster: ```shell docker ps ``` You should see output indicating that a container is running with name `kind-control-plane`. The output is similar to: ``` CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                       NAMES 6a96207fed4b        kindest/node:v1.18.2   "/usr/local/bin/entr…"   27 seconds ago      Up 24 seconds       127.0.0.1:42223->6443/tcp   kind-control-plane ``` If observing the filesystem of that container, you should see that the `profiles/` directory has been successfully loaded into the default seccomp path of the kubelet. Use `docker exec` to run a command in the Pod: ```shell # Change 6a96207fed4b to the container ID you saw from "docker ps" docker exec -it 6a96207fed4b ls /var/lib/kubelet/seccomp/profiles ``` ``` audit.json  fine-grained.json  violation.json ``` You have verified that these seccomp profiles are available to the kubelet running within kind. ## Enable the use of `RuntimeDefault` as the default seccomp profile for all workloads {{< feature-state state="alpha" for_k8s_version="v1.22" >}} `SeccompDefault` is an optional kubelet [feature gate](/docs/reference/command-line-tools-reference/feature-gates) as well as corresponding `--seccomp-default` [command line flag](/docs/reference/command-line-tools-reference/kubelet). Both have to be enabled simultaneously to use the feature. If enabled, the kubelet will use the `RuntimeDefault` seccomp profile by default, which is defined by the container runtime, instead of using the `Unconfined` (seccomp disabled) mode. The default profiles aim to provide a strong set of security defaults while preserving the functionality of the workload. It is possible that the default profiles differ between container runtimes and their release versions, for example when comparing those from CRI-O and containerd. Some workloads may require a lower amount of syscall restrictions than others. This means that they can fail during runtime even with the `RuntimeDefault` profile. To mitigate such a failure, you can: - Run the workload explicitly as `Unconfined`. - Disable the `SeccompDefault` feature for the nodes. Also making sure that workloads get scheduled on nodes where the feature is disabled. - Create a custom seccomp profile for the workload. If you were introducing this feature into production-like cluster, the Kubernetes project recommends that you enable this feature gate on a subset of your nodes and then test workload execution before rolling the change out cluster-wide. More detailed information about a possible upgrade and downgrade strategy can be found in the [related Kubernetes Enhancement Proposal (KEP)](https://github.com/kubernetes/enhancements/tree/a70cc18/keps/sig-node/2413-seccomp-by-default#upgrade--downgrade-strategy). Since the feature is in alpha state it is disabled per default. To enable it, pass the flags `--feature-gates=SeccompDefault=true --seccomp-default` to the `kubelet` CLI or enable it via the [kubelet configuration file](/docs/tasks/administer-cluster/kubelet-config-file/). To enable the feature gate in [kind](https://kind.sigs.k8s.io), ensure that `kind` provides the minimum required Kubernetes version and enables the `SeccompDefault` feature [in the kind configuration](https://kind.sigs.k8s.io/docs/user/quick-start/#enable-feature-gates-in-your-cluster): ```yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 featureGates: SeccompDefault: true ``` ## Create a Pod with a seccomp profile for syscall auditing To start off, apply the `audit.json` profile, which will log all syscalls of the process, to a new Pod. Here's a manifest for that Pod: {{< codenew file="pods/security/seccomp/ga/audit-pod.yaml" >}} {{< note >}} The functional support for the already deprecated seccomp annotations `seccomp.security.alpha.kubernetes.io/pod` (for the whole pod) and `container.seccomp.security.alpha.kubernetes.io/[name]` (for a single container) is going to be removed with the release of Kubernetes v1.25. Please always use the native API fields in favor of the annotations. {{< /note >}} Create the Pod in the cluster: ```shell kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/audit-pod.yaml ``` This profile does not restrict any syscalls, so the Pod should start successfully. ```shell kubectl get pod/audit-pod ``` ``` NAME        READY   STATUS    RESTARTS   AGE audit-pod   1/1     Running   0          30s ``` In order to be able to interact with this endpoint exposed by this container, create a NodePort {{< glossary_tooltip text="Services" term_id="service" >}} that allows access to the endpoint from inside the kind control plane container. ```shell kubectl expose pod audit-pod --type NodePort --port 5678 ``` Check what port the Service has been assigned on the node. ```shell kubectl get service audit-pod ``` The output is similar to: ``` NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE audit-pod   NodePort   10.111.36.142   <none>        5678:32373/TCP   72s ``` Now you can use `curl` to access that endpoint from inside the kind control plane container, at the port exposed by this Service. Use `docker exec` to run the `curl` command within the container belonging to that control plane container: ```shell # Change 6a96207fed4b to the control plane container ID you saw from "docker ps" docker exec -it 6a96207fed4b curl localhost:32373 ``` ``` just made some syscalls! ``` You can see that the process is running, but what syscalls did it actually make? Because this Pod is running in a local cluster, you should be able to see those in `/var/log/syslog`. Open up a new terminal window and `tail` the output for calls from `http-echo`: ```shell tail -f /var/log/syslog | grep 'http-echo' ``` You should already see some logs of syscalls made by `http-echo`, and if you `curl` the endpoint in the control plane container you will see more written. For example: ``` Jul  6 15:37:40 my-machine kernel: [369128.669452] audit: type=1326 audit(1594067860.484:14536): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=51 compat=0 ip=0x46fe1f code=0x7ffc0000 Jul  6 15:37:40 my-machine kernel: [369128.669453] audit: type=1326 audit(1594067860.484:14537): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=54 compat=0 ip=0x46fdba code=0x7ffc0000 Jul  6 15:37:40 my-machine kernel: [369128.669455] audit: type=1326 audit(1594067860.484:14538): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000 Jul  6 15:37:40 my-machine kernel: [369128.669456] audit: type=1326 audit(1594067860.484:14539): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=288 compat=0 ip=0x46fdba code=0x7ffc0000 Jul  6 15:37:40 my-machine kernel: [369128.669517] audit: type=1326 audit(1594067860.484:14540): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=0 compat=0 ip=0x46fd44 code=0x7ffc0000 Jul  6 15:37:40 my-machine kernel: [369128.669519] audit: type=1326 audit(1594067860.484:14541): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000 Jul  6 15:38:40 my-machine kernel: [369188.671648] audit: type=1326 audit(1594067920.488:14559): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000 Jul  6 15:38:40 my-machine kernel: [369188.671726] audit: type=1326 audit(1594067920.488:14560): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000 ``` You can begin to understand the syscalls required by the `http-echo` process by looking at the `syscall=` entry on each line. While these are unlikely to encompass all syscalls it uses, it can serve as a basis for a seccomp profile for this container. Clean up that Pod and Service before moving to the next section: ```shell kubectl delete service audit-pod --wait kubectl delete pod audit-pod --wait --now ``` ## Create Pod with seccomp profile that causes violation For demonstration, apply a profile to the Pod that does not allow for any syscalls. The manifest for this demonstration is: {{< codenew file="pods/security/seccomp/ga/violation-pod.yaml" >}} Attempt to create the Pod in the cluster: ```shell kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/violation-pod.yaml ``` The Pod creates, but there is an issue. If you check the status of the Pod, you should see that it failed to start. ```shell kubectl get pod/violation-pod ``` ``` NAME            READY   STATUS             RESTARTS   AGE violation-pod   0/1     CrashLoopBackOff   1          6s ``` As seen in the previous example, the `http-echo` process requires quite a few syscalls. Here seccomp has been instructed to error on any syscall by setting `"defaultAction": "SCMP_ACT_ERRNO"`. This is extremely secure, but removes the ability to do anything meaningful. What you really want is to give workloads only the privileges they need. Clean up that Pod before moving to the next section: ```shell kubectl delete pod violation-pod --wait --now ``` ## Create Pod with seccomp profile that only allows necessary syscalls If you take a look at the `fine-grained.json` profile, you will notice some of the syscalls seen in syslog of the first example where the profile set `"defaultAction": "SCMP_ACT_LOG"`. Now the profile is setting `"defaultAction": "SCMP_ACT_ERRNO"`, but explicitly allowing a set of syscalls in the `"action": "SCMP_ACT_ALLOW"` block. Ideally, the container will run successfully and you will see no messages sent to `syslog`. The manifest for this example is: {{< codenew file="pods/security/seccomp/ga/fine-pod.yaml" >}} Create the Pod in your cluster: ```shell kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/fine-pod.yaml ``` ```shell kubectl get pod fine-pod ``` The Pod should be showing as having started successfully: ``` NAME        READY   STATUS    RESTARTS   AGE fine-pod   1/1     Running   0          30s ``` Open up a new terminal window and use `tail` to monitor for log entries that mention calls from `http-echo`: ```shell # The log path on your computer might be different from "/var/log/syslog" tail -f /var/log/syslog | grep 'http-echo' ``` Next, expose the Pod with a NodePort Service: ```shell kubectl expose pod fine-pod --type NodePort --port 5678 ``` Check what port the Service has been assigned on the node: ```shell kubectl get service fine-pod ``` The output is similar to: ``` NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE fine-pod    NodePort   10.111.36.142   <none>        5678:32373/TCP   72s ``` Use `curl` to access that endpoint from inside the kind control plane container: ```shell # Change 6a96207fed4b to the control plane container ID you saw from "docker ps" docker exec -it 6a96207fed4b curl localhost:32373 ``` ``` just made some syscalls! ``` You should see no output in the `syslog`. This is because the profile allowed all necessary syscalls and specified that an error should occur if one outside of the list is invoked. This is an ideal situation from a security perspective, but required some effort in analyzing the program. It would be nice if there was a simple way to get closer to this security without requiring as much effort. Clean up that Pod and Service before moving to the next section: ```shell kubectl delete service fine-pod --wait kubectl delete pod fine-pod --wait --now ``` ## Create Pod that uses the container runtime default seccomp profile Most container runtimes provide a sane set of default syscalls that are allowed or not. You can adopt these defaults for your workload by setting the seccomp type in the security context of a pod or container to `RuntimeDefault`. {{< note >}} If you have the `SeccompDefault` [feature gate](/docs/reference/command-line-tools-reference/feature-gates/) enabled, then Pods use the `RuntimeDefault` seccomp profile whenever no other seccomp profile is specified. Otherwise, the default is `Unconfined`. {{< /note >}} Here's a manifest for a Pod that requests the `RuntimeDefault` seccomp profile for all its containers: {{< codenew file="pods/security/seccomp/ga/default-pod.yaml" >}} Create that Pod: ```shell kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/default-pod.yaml ``` ```shell kubectl get pod default-pod ``` The Pod should be showing as having started successfully: ``` NAME        READY   STATUS    RESTARTS   AGE default-pod 1/1     Running   0          20s ``` Finally, now that you saw that work OK, clean up: ```shell kubectl delete pod default-pod --wait --now ``` ## {{% heading "whatsnext" %}} You can learn more about Linux seccomp: * [A seccomp Overview](https://lwn.net/Articles/656307/) * [Seccomp Security Profiles for Docker](https://docs.docker.com/engine/security/seccomp/) </del>

<ins> --- reviewers: - stclair title: Restrict a Container's Access to Resources with AppArmor content_type: tutorial weight: 10 --- <!-- overview --> {{< feature-state for_k8s_version="v1.4" state="beta" >}} 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 in-depth defense. It is configured through profiles tuned to allow 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 violations. AppArmor can help you to run a more secure deployment by restricting what containers are allowed to 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. ## {{% heading "objectives" %}} * 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 ## {{% heading "prerequisites" %}} Make sure: 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: ```shell 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 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: ```shell 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 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. {{< /note >}} 3. Container runtime supports AppArmor -- Currently all common Kubernetes-supported container runtimes should support AppArmor, like {{< glossary_tooltip term_id="docker">}}, {{< glossary_tooltip term_id="cri-o" >}} or {{< glossary_tooltip term_id="containerd" >}}. Please refer to the corresponding runtime documentation and verify that the cluster fulfills the requirements to use AppArmor. 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: ```shell 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). As long as the Kubelet version includes AppArmor support (>= v1.4), the Kubelet will reject a Pod with AppArmor options if any of the prerequisites are not met. You can also verify AppArmor support on nodes by checking the node ready condition message (though this is likely to be removed in a later release): ```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 ``` <!-- lessoncontent --> ## Securing a Pod {{< 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)). {{< /note >}} 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: ```yaml container.apparmor.security.beta.kubernetes.io/<container_name>: <profile_ref> ``` Where `<container_name>` is the name of the container to apply the profile to, and `<profile_ref>` specifies the profile to apply. The `profile_ref` can be one of: * `runtime/default` to apply the runtime's default profile * `localhost/<profile_name>` to apply the profile loaded on the host with the name `<profile_name>` * `unconfined` to indicate that no profiles will be loaded See the [API Reference](#api-reference) for the full details on the annotation and profile name formats. 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 look for the AppArmor security option listed in the container created event: ```shell kubectl get events | grep Created ``` ``` 22s        22s         1         hello-apparmor     Pod       spec.containers{hello}   Normal    Created     {kubelet e2e-test-stclair-node-pool-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: ```shell kubectl exec <pod_name> cat /proc/1/attr/current ``` ``` k8s-apparmor-example-deny-write (enforce) ``` ## Example *This example assumes you have already set up a cluster with AppArmor support.* First, we need to load the profile we want to use onto our nodes. This profile denies all file writes: ```shell #include <tunables/global> profile k8s-apparmor-example-deny-write flags=(attach_disconnected) { #include <abstractions/base> file, # Deny all file writes. deny /** w, } ``` 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 use SSH to install the profiles, but other approaches are discussed in [Setting up nodes with profiles](#setting-up-nodes-with-profiles). ```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 <<EOF #include <tunables/global> profile k8s-apparmor-example-deny-write flags=(attach_disconnected) { #include <abstractions/base> file, # Deny all file writes. deny /** w, } EOF' done ``` Next, we'll run a simple "Hello AppArmor" pod with the deny-write profile: {{< codenew file="pods/security/hello-apparmor.yaml" >}} ```shell kubectl create -f ./hello-apparmor.yaml ``` If we look at the pod events, we can see that the Pod container was created with the AppArmor profile "k8s-apparmor-example-deny-write": ```shell kubectl get events | grep hello-apparmor ``` ``` 14s        14s         1         hello-apparmor   Pod                                Normal    Scheduled   {default-scheduler }                           Successfully assigned hello-apparmor to gke-test-default-pool-239f5d02-gyn2 14s        14s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Pulling     {kubelet gke-test-default-pool-239f5d02-gyn2}   pulling image "busybox" 13s        13s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Pulled      {kubelet gke-test-default-pool-239f5d02-gyn2}   Successfully pulled image "busybox" 13s        13s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Created     {kubelet gke-test-default-pool-239f5d02-gyn2}   Created container with docker id 06b6cd1c0989; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write] 13s        13s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Started     {kubelet gke-test-default-pool-239f5d02-gyn2}   Started container with docker id 06b6cd1c0989 ``` We can verify that the container is actually running with that profile by checking its proc attr: ```shell kubectl exec hello-apparmor -- cat /proc/1/attr/current ``` ``` k8s-apparmor-example-deny-write (enforce) ``` Finally, we can see what happens if we try to violate the profile by writing to a file: ```shell kubectl exec hello-apparmor -- touch /tmp/test ``` ``` touch: /tmp/test: Permission denied error: error executing remote command: command terminated with non-zero exit code: Error executing in Docker Container: 1 ``` To wrap up, let's look at what happens if we try to specify a profile that hasn't been loaded: ```shell kubectl create -f /dev/stdin <<EOF ``` ```yaml apiVersion: v1 kind: Pod metadata: name: hello-apparmor-2 annotations: container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-allow-write spec: containers: - name: hello image: busybox command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ] EOF pod/hello-apparmor-2 created ``` ```shell 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:        <none> Annotations:   container.apparmor.security.beta.kubernetes.io/hello=localhost/k8s-apparmor-example-allow-write Status:        Pending Reason:        AppArmor Message:       Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded IP: Controllers:   <none> Containers: hello: Container ID: Image:     busybox Image ID: Port: Command: sh -c echo 'Hello AppArmor!' && sleep 1h State:              Waiting Reason:           Blocked Ready:              False Restart Count:      0 Environment:        <none> Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-dnz7v (ro) Conditions: Type          Status Initialized   True Ready         False PodScheduled  True Volumes: default-token-dnz7v: Type:    Secret (a volume populated by a Secret) SecretName:    default-token-dnz7v Optional:   false QoS Class:      BestEffort Node-Selectors: <none> Tolerations:    <none> 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-node-pool-t1f5 23s          23s         1        {kubelet e2e-test-stclair-node-pool-t1f5}             Warning        AppArmor    Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded ``` Note the pod status is Pending, 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. ## Administration ### Setting up nodes with profiles 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 ensure the correct profiles are loaded. An example implementation can be found [here](https://git.k8s.io/kubernetes/test/images/apparmor-loader). * 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 [Example](#example). The scheduler is not aware of which profiles are loaded onto which node, so the full set of profiles must be loaded onto every node.  An alternative approach is to add a node label for each profile (or class of profiles) on the node, and use a [node selector](/docs/concepts/scheduling-eviction/assign-pod-node/) to ensure the Pod is run on a node with the required profile. ### Restricting profiles with the PodSecurityPolicy {{< note >}} PodSecurityPolicy is deprecated in Kubernetes v1.21, and will be removed in v1.25. See [PodSecurityPolicy documentation](/docs/concepts/policy/pod-security-policy/) for more information. {{< /note >}} If the PodSecurityPolicy extension is enabled, cluster-wide AppArmor restrictions can be applied. To enable the PodSecurityPolicy, the following flag must be set on the `apiserver`: ``` --enable-admission-plugins=PodSecurityPolicy[,others...] ``` The AppArmor options can be specified as annotations on the PodSecurityPolicy: ```yaml apparmor.security.beta.kubernetes.io/defaultProfileName: <profile_ref> apparmor.security.beta.kubernetes.io/allowedProfileNames: <profile_ref>[,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 allowed to be run with. If both options are provided, the default must be allowed. The profiles are specified in the same format as on containers. See the [API Reference](#api-reference) for the full specification. ### Disabling AppArmor 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 ``` 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 pods (if the AppArmor kernel module is enabled), and will continue to do so even if the feature-gate is disabled. The option to disable AppArmor will be removed when AppArmor graduates to general availability (GA). ### Upgrading to Kubernetes v1.4 with AppArmor No action is required with respect to AppArmor to upgrade your cluster to v1.4. However, if any existing pods had an AppArmor annotation, they will not go through validation (or PodSecurityPolicy admission). If permissive profiles are loaded on the nodes, a malicious user could pre-apply a permissive profile to escalate the pod privileges above the docker-default. If this is a concern, it is recommended to scrub the cluster of any pods containing an annotation with `apparmor.security.beta.kubernetes.io`. ### Upgrade path to General Availability When AppArmor is ready to be graduated to general availability (GA), the options currently specified through annotations will be converted to fields. Supporting all the upgrade and downgrade paths through the transition is very nuanced, and will be explained in detail when the transition occurs. We will commit to supporting both fields and annotations for at least 2 releases, and will explicitly reject the annotations for at least 2 releases after that. ## Authoring Profiles 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 logs, and admitting the actions it takes. Further instructions are provided by the [AppArmor documentation](https://gitlab.com/apparmor/apparmor/wikis/Profiling_with_tools). * [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 the profiles, but there is nothing preventing running the tools on the Kubernetes node where your Pod is running. To debug problems with AppArmor, you can check the system logs to see what, specifically, was denied. AppArmor logs verbose messages to `dmesg`, and errors can usually be found in the system logs or through `journalctl`. More information is provided in [AppArmor failures](https://gitlab.com/apparmor/apparmor/wikis/AppArmor_Failures). ## API Reference ### Pod Annotation Specifying the profile a container will run with: - **key**: `container.apparmor.security.beta.kubernetes.io/<container_name>` Where `<container_name>` matches the name of a container in the Pod. A separate profile can be specified for each container in the Pod. - **value**: a profile reference, described below ### Profile Reference - `runtime/default`: Refers to the default runtime profile. - Equivalent to not specifying a profile (without a PodSecurityPolicy default), except it still requires AppArmor to be enabled. - For Docker, this resolves to the [`docker-default`](https://docs.docker.com/engine/security/apparmor/) profile for non-privileged containers, and unconfined (no profile) for privileged containers. - `localhost/<profile_name>`: Refers to a profile loaded on the node (localhost) by name. - The possible profile names are detailed in the [core policy reference](https://gitlab.com/apparmor/apparmor/wikis/AppArmor_Core_Policy_Reference#profile-names-and-attachment-specifications). - `unconfined`: This effectively disables AppArmor on the container. Any other profile reference format is invalid. ### 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 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) - Although an escaped comma is a legal character in a profile name, it cannot be explicitly allowed here. ## {{% heading "whatsnext" %}} Additional resources: * [Quick guide to the AppArmor profile language](https://gitlab.com/apparmor/apparmor/wikis/QuickProfileLanguage) * [AppArmor core policy reference](https://gitlab.com/apparmor/apparmor/wikis/Policy_Layout) </ins>

<ins> --- reviewers: - hasheddan - pjbgf - saschagrunert title: Restrict a Container's Syscalls with seccomp content_type: tutorial weight: 20 min-kubernetes-server-version: v1.22 --- <!-- overview --> {{< feature-state for_k8s_version="v1.19" state="stable" >}} Seccomp stands for secure computing mode and has been a feature of the Linux kernel since version 2.6.12. It can be used to sandbox the privileges of a process, restricting the calls it is able to make from userspace into the kernel. Kubernetes lets you automatically apply seccomp profiles loaded onto a {{< glossary_tooltip text="node" term_id="node" >}} to your Pods and containers. Identifying the privileges required for your workloads can be difficult. In this tutorial, you will go through how to load seccomp profiles into a local Kubernetes cluster, how to apply them to a Pod, and how you can begin to craft profiles that give only the necessary privileges to your container processes. ## {{% heading "objectives" %}} * Learn how to load seccomp profiles on a node * Learn how to apply a seccomp profile to a container * Observe auditing of syscalls made by a container process * Observe behavior when a missing profile is specified * Observe a violation of a seccomp profile * Learn how to create fine-grained seccomp profiles * Learn how to apply a container runtime default seccomp profile ## {{% heading "prerequisites" %}} In order to complete all steps in this tutorial, you must install [kind](/docs/tasks/tools/#kind) and [kubectl](/docs/tasks/tools/#kubectl). This tutorial shows some examples that are still alpha (since v1.22) and others that use only generally available seccomp functionality. You should make sure that your cluster is [configured correctly](https://kind.sigs.k8s.io/docs/user/quick-start/#setting-kubernetes-version) for the version you are using. The tutorial also uses the `curl` tool for downloading examples to your computer. You can adapt the steps to use a different tool if you prefer. {{< note >}} It is not possible to apply a seccomp profile to a container running with `privileged: true` set in the container's `securityContext`. Privileged containers always run as `Unconfined`. {{< /note >}} <!-- steps --> ## Download example seccomp profiles {#download-profiles} The contents of these profiles will be explored later on, but for now go ahead and download them into a directory named `profiles/` so that they can be loaded into the cluster. {{< tabs name="tab_with_code" >}} {{{< tab name="audit.json" >}} {{< codenew file="pods/security/seccomp/profiles/audit.json" >}} {{< /tab >}} {{< tab name="violation.json" >}} {{< codenew file="pods/security/seccomp/profiles/violation.json" >}} {{< /tab >}}} {{< tab name="fine-grained.json" >}} {{< codenew file="pods/security/seccomp/profiles/fine-grained.json" >}} {{< /tab >}}} {{< /tabs >}} Run these commands: ```shell mkdir ./profiles curl -L -o profiles/audit.json https://k8s.io/examples/pods/security/seccomp/profiles/audit.json curl -L -o profiles/violation.json https://k8s.io/examples/pods/security/seccomp/profiles/violation.json curl -L -o profiles/fine-grained.json https://k8s.io/examples/pods/security/seccomp/profiles/fine-grained.json ls profiles ``` You should see three profiles listed at the end of the final step: ``` audit.json  fine-grained.json  violation.json ``` ## Create a local Kubernetes cluster with kind For simplicity, [kind](https://kind.sigs.k8s.io/) can be used to create a single node cluster with the seccomp profiles loaded. Kind runs Kubernetes in Docker, so each node of the cluster is a container. This allows for files to be mounted in the filesystem of each container similar to loading files onto a node. {{< codenew file="pods/security/seccomp/kind.yaml" >}} Download that example kind configuration, and save it to a file named `kind.yaml`: ```shell curl -L -O https://k8s.io/examples/pods/security/seccomp/kind.yaml ``` You can set a specific Kubernetes version by setting the node's container image. See [Nodes](https://kind.sigs.k8s.io/docs/user/configuration/#nodes) within the kind documentation about configuration for more details on this. This tutorial assumes you are using Kubernetes {{< param "version" >}}. As an alpha feature, you can configure Kubernetes to use the profile that the {{< glossary_tooltip text="container runtime" term_id="container-runtime" >}} prefers by default, rather than falling back to `Unconfined`. If you want to try that, see [enable the use of `RuntimeDefault` as the default seccomp profile for all workloads](#enable-the-use-of-runtimedefault-as-the-default-seccomp-profile-for-all-workloads) before you continue. Once you have a kind configuration in place, create the kind cluster with that configuration: ```shell kind create cluster --config=kind.yaml ``` After the new Kubernetes cluster is ready, identify the Docker container running as the single node cluster: ```shell docker ps ``` You should see output indicating that a container is running with name `kind-control-plane`. The output is similar to: ``` CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                       NAMES 6a96207fed4b        kindest/node:v1.18.2   "/usr/local/bin/entr…"   27 seconds ago      Up 24 seconds       127.0.0.1:42223->6443/tcp   kind-control-plane ``` If observing the filesystem of that container, you should see that the `profiles/` directory has been successfully loaded into the default seccomp path of the kubelet. Use `docker exec` to run a command in the Pod: ```shell # Change 6a96207fed4b to the container ID you saw from "docker ps" docker exec -it 6a96207fed4b ls /var/lib/kubelet/seccomp/profiles ``` ``` audit.json  fine-grained.json  violation.json ``` You have verified that these seccomp profiles are available to the kubelet running within kind. ## Enable the use of `RuntimeDefault` as the default seccomp profile for all workloads {{< feature-state state="alpha" for_k8s_version="v1.22" >}} `SeccompDefault` is an optional kubelet [feature gate](/docs/reference/command-line-tools-reference/feature-gates) as well as corresponding `--seccomp-default` [command line flag](/docs/reference/command-line-tools-reference/kubelet). Both have to be enabled simultaneously to use the feature. If enabled, the kubelet will use the `RuntimeDefault` seccomp profile by default, which is defined by the container runtime, instead of using the `Unconfined` (seccomp disabled) mode. The default profiles aim to provide a strong set of security defaults while preserving the functionality of the workload. It is possible that the default profiles differ between container runtimes and their release versions, for example when comparing those from CRI-O and containerd. Some workloads may require a lower amount of syscall restrictions than others. This means that they can fail during runtime even with the `RuntimeDefault` profile. To mitigate such a failure, you can: - Run the workload explicitly as `Unconfined`. - Disable the `SeccompDefault` feature for the nodes. Also making sure that workloads get scheduled on nodes where the feature is disabled. - Create a custom seccomp profile for the workload. If you were introducing this feature into production-like cluster, the Kubernetes project recommends that you enable this feature gate on a subset of your nodes and then test workload execution before rolling the change out cluster-wide. More detailed information about a possible upgrade and downgrade strategy can be found in the [related Kubernetes Enhancement Proposal (KEP)](https://github.com/kubernetes/enhancements/tree/a70cc18/keps/sig-node/2413-seccomp-by-default#upgrade--downgrade-strategy). Since the feature is in alpha state it is disabled per default. To enable it, pass the flags `--feature-gates=SeccompDefault=true --seccomp-default` to the `kubelet` CLI or enable it via the [kubelet configuration file](/docs/tasks/administer-cluster/kubelet-config-file/). To enable the feature gate in [kind](https://kind.sigs.k8s.io), ensure that `kind` provides the minimum required Kubernetes version and enables the `SeccompDefault` feature [in the kind configuration](https://kind.sigs.k8s.io/docs/user/quick-start/#enable-feature-gates-in-your-cluster): ```yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 featureGates: SeccompDefault: true ``` ## Create a Pod with a seccomp profile for syscall auditing To start off, apply the `audit.json` profile, which will log all syscalls of the process, to a new Pod. Here's a manifest for that Pod: {{< codenew file="pods/security/seccomp/ga/audit-pod.yaml" >}} {{< note >}} The functional support for the already deprecated seccomp annotations `seccomp.security.alpha.kubernetes.io/pod` (for the whole pod) and `container.seccomp.security.alpha.kubernetes.io/[name]` (for a single container) is going to be removed with the release of Kubernetes v1.25. Please always use the native API fields in favor of the annotations. {{< /note >}} Create the Pod in the cluster: ```shell kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/audit-pod.yaml ``` This profile does not restrict any syscalls, so the Pod should start successfully. ```shell kubectl get pod/audit-pod ``` ``` NAME        READY   STATUS    RESTARTS   AGE audit-pod   1/1     Running   0          30s ``` In order to be able to interact with this endpoint exposed by this container, create a NodePort {{< glossary_tooltip text="Services" term_id="service" >}} that allows access to the endpoint from inside the kind control plane container. ```shell kubectl expose pod audit-pod --type NodePort --port 5678 ``` Check what port the Service has been assigned on the node. ```shell kubectl get service audit-pod ``` The output is similar to: ``` NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE audit-pod   NodePort   10.111.36.142   <none>        5678:32373/TCP   72s ``` Now you can use `curl` to access that endpoint from inside the kind control plane container, at the port exposed by this Service. Use `docker exec` to run the `curl` command within the container belonging to that control plane container: ```shell # Change 6a96207fed4b to the control plane container ID you saw from "docker ps" docker exec -it 6a96207fed4b curl localhost:32373 ``` ``` just made some syscalls! ``` You can see that the process is running, but what syscalls did it actually make? Because this Pod is running in a local cluster, you should be able to see those in `/var/log/syslog`. Open up a new terminal window and `tail` the output for calls from `http-echo`: ```shell tail -f /var/log/syslog | grep 'http-echo' ``` You should already see some logs of syscalls made by `http-echo`, and if you `curl` the endpoint in the control plane container you will see more written. For example: ``` Jul  6 15:37:40 my-machine kernel: [369128.669452] audit: type=1326 audit(1594067860.484:14536): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=51 compat=0 ip=0x46fe1f code=0x7ffc0000 Jul  6 15:37:40 my-machine kernel: [369128.669453] audit: type=1326 audit(1594067860.484:14537): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=54 compat=0 ip=0x46fdba code=0x7ffc0000 Jul  6 15:37:40 my-machine kernel: [369128.669455] audit: type=1326 audit(1594067860.484:14538): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000 Jul  6 15:37:40 my-machine kernel: [369128.669456] audit: type=1326 audit(1594067860.484:14539): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=288 compat=0 ip=0x46fdba code=0x7ffc0000 Jul  6 15:37:40 my-machine kernel: [369128.669517] audit: type=1326 audit(1594067860.484:14540): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=0 compat=0 ip=0x46fd44 code=0x7ffc0000 Jul  6 15:37:40 my-machine kernel: [369128.669519] audit: type=1326 audit(1594067860.484:14541): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000 Jul  6 15:38:40 my-machine kernel: [369188.671648] audit: type=1326 audit(1594067920.488:14559): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000 Jul  6 15:38:40 my-machine kernel: [369188.671726] audit: type=1326 audit(1594067920.488:14560): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000 ``` You can begin to understand the syscalls required by the `http-echo` process by looking at the `syscall=` entry on each line. While these are unlikely to encompass all syscalls it uses, it can serve as a basis for a seccomp profile for this container. Clean up that Pod and Service before moving to the next section: ```shell kubectl delete service audit-pod --wait kubectl delete pod audit-pod --wait --now ``` ## Create Pod with seccomp profile that causes violation For demonstration, apply a profile to the Pod that does not allow for any syscalls. The manifest for this demonstration is: {{< codenew file="pods/security/seccomp/ga/violation-pod.yaml" >}} Attempt to create the Pod in the cluster: ```shell kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/violation-pod.yaml ``` The Pod creates, but there is an issue. If you check the status of the Pod, you should see that it failed to start. ```shell kubectl get pod/violation-pod ``` ``` NAME            READY   STATUS             RESTARTS   AGE violation-pod   0/1     CrashLoopBackOff   1          6s ``` As seen in the previous example, the `http-echo` process requires quite a few syscalls. Here seccomp has been instructed to error on any syscall by setting `"defaultAction": "SCMP_ACT_ERRNO"`. This is extremely secure, but removes the ability to do anything meaningful. What you really want is to give workloads only the privileges they need. Clean up that Pod before moving to the next section: ```shell kubectl delete pod violation-pod --wait --now ``` ## Create Pod with seccomp profile that only allows necessary syscalls If you take a look at the `fine-grained.json` profile, you will notice some of the syscalls seen in syslog of the first example where the profile set `"defaultAction": "SCMP_ACT_LOG"`. Now the profile is setting `"defaultAction": "SCMP_ACT_ERRNO"`, but explicitly allowing a set of syscalls in the `"action": "SCMP_ACT_ALLOW"` block. Ideally, the container will run successfully and you will see no messages sent to `syslog`. The manifest for this example is: {{< codenew file="pods/security/seccomp/ga/fine-pod.yaml" >}} Create the Pod in your cluster: ```shell kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/fine-pod.yaml ``` ```shell kubectl get pod fine-pod ``` The Pod should be showing as having started successfully: ``` NAME        READY   STATUS    RESTARTS   AGE fine-pod   1/1     Running   0          30s ``` Open up a new terminal window and use `tail` to monitor for log entries that mention calls from `http-echo`: ```shell # The log path on your computer might be different from "/var/log/syslog" tail -f /var/log/syslog | grep 'http-echo' ``` Next, expose the Pod with a NodePort Service: ```shell kubectl expose pod fine-pod --type NodePort --port 5678 ``` Check what port the Service has been assigned on the node: ```shell kubectl get service fine-pod ``` The output is similar to: ``` NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE fine-pod    NodePort   10.111.36.142   <none>        5678:32373/TCP   72s ``` Use `curl` to access that endpoint from inside the kind control plane container: ```shell # Change 6a96207fed4b to the control plane container ID you saw from "docker ps" docker exec -it 6a96207fed4b curl localhost:32373 ``` ``` just made some syscalls! ``` You should see no output in the `syslog`. This is because the profile allowed all necessary syscalls and specified that an error should occur if one outside of the list is invoked. This is an ideal situation from a security perspective, but required some effort in analyzing the program. It would be nice if there was a simple way to get closer to this security without requiring as much effort. Clean up that Pod and Service before moving to the next section: ```shell kubectl delete service fine-pod --wait kubectl delete pod fine-pod --wait --now ``` ## Create Pod that uses the container runtime default seccomp profile Most container runtimes provide a sane set of default syscalls that are allowed or not. You can adopt these defaults for your workload by setting the seccomp type in the security context of a pod or container to `RuntimeDefault`. {{< note >}} If you have the `SeccompDefault` [feature gate](/docs/reference/command-line-tools-reference/feature-gates/) enabled, then Pods use the `RuntimeDefault` seccomp profile whenever no other seccomp profile is specified. Otherwise, the default is `Unconfined`. {{< /note >}} Here's a manifest for a Pod that requests the `RuntimeDefault` seccomp profile for all its containers: {{< codenew file="pods/security/seccomp/ga/default-pod.yaml" >}} Create that Pod: ```shell kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/default-pod.yaml ``` ```shell kubectl get pod default-pod ``` The Pod should be showing as having started successfully: ``` NAME        READY   STATUS    RESTARTS   AGE default-pod 1/1     Running   0          20s ``` Finally, now that you saw that work OK, clean up: ```shell kubectl delete pod default-pod --wait --now ``` ## {{% heading "whatsnext" %}} You can learn more about Linux seccomp: * [A seccomp Overview](https://lwn.net/Articles/656307/) * [Seccomp Security Profiles for Docker](https://docs.docker.com/engine/security/seccomp/) </ins>

/docs/tutorials/kubernetes-basics/expose-intro/     /docs/tutorials/kubernetes-basics/expose/expose-intro/ 301 /docs/tutorials/kubernetes-basics/scale-interactive/     /docs/tutorials/kubernetes-basics/scale/scale-interactive/ 301 /docs/tutorials/kubernetes-basics/scale-intro/     /docs/tutorials/kubernetes-basics/scale/scale-intro/ 301 <ins> /docs/tutorials/clusters/apparmor/     /docs/tutorials/security/apparmor/   301 /docs/tutorials/clusters/seccomp/      /docs/tutorials/security/seccomp/    301 </ins> /ja/docs/tutorials/kubernetes-basics/scale-intro/     /ja/docs/tutorials/kubernetes-basics/scale/scale-intro/ 301 /ko/docs/tutorials/kubernetes-basics/scale-intro/     /ko/docs/tutorials/kubernetes-basics/scale/scale-intro/ 301 /docs/tutorials/kubernetes-basics/update-interactive/    /docs/tutorials/kubernetes-basics/update/update-interactive/ 301