The previous articles about Docker security have discussed many security features that can be applied at container's runtime. Those features, such as privileges management, kernel capabilities, mandatory access control, among many others, can be enforced in Kubernetes environments through security contexts and policies.
A security context can be applied for a Pod or container.
The features that a security context allows to define are:
- Discretionary Access Control: Permission to access an object, like a file, is based on user ID (UID) and group ID (GID).
- SELinux: Objects are assigned security labels.
- Privileges: Run as privileged or not (Docker --privileged flag).
- Linux Capabilities: Give a process some privileges, but not all the privileges of the root user.
- AppArmor: Use program profiles to restrict the capabilities of individual programs.
- Seccomp: Filter a process's system calls.
- AllowPrivilegeEscalation: Controls whether a process can gain more privileges than its parent process.
- readOnlyRootFilesystem: Mounts the container's root filesystem as read-only.
To specify these security settings for a Pod, the securityContext field must be included in the Pod Spec. For the container level case, then the field must be located within the container’s array definition.
Analyzing an example:
apiVersion: v1 kind: Pod metadata: name: security-context-example spec: securityContext: runAsUser: 1000 runAsGroup: 3000 containers: - name: ubuntu-container image: ubuntu command: [ "sh", "-c", "sleep 1h" ] securityContext: allowPrivilegeEscalation: false
|
As noted, securityContext is declared twice. The first is at the Pod level, the directives "runAsUser" and "runAsGroup" apply to all containers in the Pod definition file. The second securityContext declaration is inside the container definition, so "allowPrivilegeEscalation" will apply only for that container.
The image above shows that the UID and GID of the container are the ones specified in the security context.
Looking at another example:
apiVersion: v1 kind: Pod metadata: name: security-context-example-2 spec: containers: - name: nginx-container image: nginx ports: - containerPort: 80 securityContext: capabilities: drop: - all add: - NET_BIND_SERVICE |
In this case, the security context is used to drop all the kernel capabilities at container's runtime and add only the one needed for this scenario. The definition of capabilities can only be applied at container level.
An excellent practice is to define a Pod Security Policy for the cluster. This is a cluster-level resource, that defines a set of conditions related to security that a pod must run with, in order to be accepted into the system.
Pod Security Policies are enforced by enabling the admission controller, if implemented this acts an optional Kubernetes admission controller.
Below is an example of a restrictive policy that requires users to run as an unprivileged user, blocks possible escalations to root, and requires use of several security mechanisms.
apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: name: restricted annotations: seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default,runtime/default' apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default' seccomp.security.alpha.kubernetes.io/defaultProfileName: 'runtime/default' apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' spec: privileged: false # Required to prevent escalations to root. allowPrivilegeEscalation: false # This is redundant, but we can provide it for defense in depth. requiredDropCapabilities: - ALL # Allow core volume types. volumes: - 'configMap' - 'emptyDir' - 'projected' - 'secret' - 'downwardAPI' # Assume that persistentVolumes set up by the cluster admin are safe to use. - 'persistentVolumeClaim' hostNetwork: false hostIPC: false hostPID: false runAsUser: # Require the container to run without root privileges. rule: 'MustRunAsNonRoot' supplementalGroups: rule: 'MustRunAs' ranges: # Forbid adding the root group. - min: 1 max: 65535 fsGroup: rule: 'MustRunAs' ranges: # Forbid adding the root group. - min: 1 max: 65535 readOnlyRootFilesystem: false |
Create the security policy by running:
$ kubectl create -f pod-policy.yaml |
In order to use the Pod Security Policy created, the requesting user or target pod's service account must be authorized to use that policy. This can be achieved easily by using an RBAC Cluster Role.
The following Cluster Role definition allows to use the security policies specified.
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: security-policies-authorization rules: - apiGroups: ['policy'] resources: ['podsecuritypolicies'] verbs: ['use'] resourceNames: - < list of policies to authorize> |
Then the Cluster Role is bound to the authorized users or service accounts.
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: cr-binding-security-policies roleRef: kind: ClusterRole name: security-policies-authorization apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount name: < authorized service account name> |
From now on, all Pods created using the service account configured in the previous Cluster Role Binding, will have the Pod Security Policy enforced.
Do you want to learn more?
https://dreamlab.net/en/education/trainings-schedule/
__________________
References
Sheila A. Berta
Head of Research at Dreamlab Technologies