mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 18:28:13 +00:00 
			
		
		
		
	Merge pull request #109798 from liggitt/psp
Remove PodSecurityPolicy admission plugin
This commit is contained in:
		| @@ -1,16 +0,0 @@ | ||||
| apiVersion: rbac.authorization.k8s.io/v1 | ||||
| kind: RoleBinding | ||||
| metadata: | ||||
|   name: gce:podsecuritypolicy:event-exporter | ||||
|   namespace: kube-system | ||||
|   labels: | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
|     kubernetes.io/cluster-service: "true" | ||||
| roleRef: | ||||
|   apiGroup: rbac.authorization.k8s.io | ||||
|   kind: Role | ||||
|   name: gce:podsecuritypolicy:event-exporter | ||||
| subjects: | ||||
| - kind: ServiceAccount | ||||
|   name: event-exporter-sa | ||||
|   namespace: kube-system | ||||
| @@ -1,17 +0,0 @@ | ||||
| apiVersion: rbac.authorization.k8s.io/v1 | ||||
| kind: Role | ||||
| metadata: | ||||
|   name: gce:podsecuritypolicy:event-exporter | ||||
|   namespace: kube-system | ||||
|   labels: | ||||
|     kubernetes.io/cluster-service: "true" | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
| rules: | ||||
| - apiGroups: | ||||
|   - policy | ||||
|   resourceNames: | ||||
|   - gce.event-exporter | ||||
|   resources: | ||||
|   - podsecuritypolicies | ||||
|   verbs: | ||||
|   - use | ||||
| @@ -1,39 +0,0 @@ | ||||
| apiVersion: policy/v1beta1 | ||||
| kind: PodSecurityPolicy | ||||
| metadata: | ||||
|   name: gce.event-exporter | ||||
|   annotations: | ||||
|     kubernetes.io/description: 'Policy used by the event-exporter addon.' | ||||
|     seccomp.security.alpha.kubernetes.io/defaultProfileName:  'docker/default' | ||||
|     seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'runtime/default,docker/default' | ||||
|     # 'runtime/default' is already the default, but must be filled in on the | ||||
|     # pod to pass admission. | ||||
|     apparmor.security.beta.kubernetes.io/defaultProfileName:  'runtime/default' | ||||
|     apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default' | ||||
|   labels: | ||||
|     kubernetes.io/cluster-service: 'true' | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
| spec: | ||||
|   privileged: false | ||||
|   allowPrivilegeEscalation: false | ||||
|   volumes: | ||||
|   - 'hostPath' | ||||
|   - 'secret' | ||||
|   - 'projected' | ||||
|   # TODO: This only needs a hostPath to read /etc/ssl/certs, | ||||
|   #   but it should be able to just include these in the image. | ||||
|   allowedHostPaths: | ||||
|     - pathPrefix: /etc/ssl/certs | ||||
|   hostNetwork: false | ||||
|   hostIPC: false | ||||
|   hostPID: false | ||||
|   # TODO: This doesn't need to run as root. | ||||
|   runAsUser: | ||||
|     rule: 'RunAsAny' | ||||
|   seLinux: | ||||
|     rule: 'RunAsAny' | ||||
|   supplementalGroups: | ||||
|     rule: 'RunAsAny' | ||||
|   fsGroup: | ||||
|     rule: 'RunAsAny' | ||||
|   readOnlyRootFilesystem: false | ||||
| @@ -1,16 +0,0 @@ | ||||
| apiVersion: rbac.authorization.k8s.io/v1 | ||||
| kind: RoleBinding | ||||
| metadata: | ||||
|   name: gce:podsecuritypolicy:fluentd-gcp | ||||
|   namespace: kube-system | ||||
|   labels: | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
|     kubernetes.io/cluster-service: "true" | ||||
| roleRef: | ||||
|   apiGroup: rbac.authorization.k8s.io | ||||
|   kind: Role | ||||
|   name: gce:podsecuritypolicy:fluentd-gcp | ||||
| subjects: | ||||
| - kind: ServiceAccount | ||||
|   name: fluentd-gcp | ||||
|   namespace: kube-system | ||||
| @@ -1,17 +0,0 @@ | ||||
| apiVersion: rbac.authorization.k8s.io/v1 | ||||
| kind: Role | ||||
| metadata: | ||||
|   name: gce:podsecuritypolicy:fluentd-gcp | ||||
|   namespace: kube-system | ||||
|   labels: | ||||
|     kubernetes.io/cluster-service: "true" | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
| rules: | ||||
| - apiGroups: | ||||
|   - policy | ||||
|   resourceNames: | ||||
|   - gce.fluentd-gcp | ||||
|   resources: | ||||
|   - podsecuritypolicies | ||||
|   verbs: | ||||
|   - use | ||||
| @@ -1,39 +0,0 @@ | ||||
| apiVersion: policy/v1beta1 | ||||
| kind: PodSecurityPolicy | ||||
| metadata: | ||||
|   name: gce.fluentd-gcp | ||||
|   annotations: | ||||
|     kubernetes.io/description: 'Policy used by the fluentd-gcp addon.' | ||||
|     seccomp.security.alpha.kubernetes.io/defaultProfileName:  'docker/default' | ||||
|     seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'runtime/default,docker/default' | ||||
|     # 'runtime/default' is already the default, but must be filled in on the | ||||
|     # pod to pass admission. | ||||
|     apparmor.security.beta.kubernetes.io/defaultProfileName:  'runtime/default' | ||||
|     apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default' | ||||
|   labels: | ||||
|     kubernetes.io/cluster-service: 'true' | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
| spec: | ||||
|   privileged: false | ||||
|   allowPrivilegeEscalation: false | ||||
|   volumes: | ||||
|   - 'configMap' | ||||
|   - 'hostPath' | ||||
|   - 'secret' | ||||
|   - 'projected' | ||||
|   allowedHostPaths: | ||||
|     - pathPrefix: /var/log | ||||
|     - pathPrefix: /var/lib/docker/containers | ||||
|     - pathPrefix: /usr/lib64 | ||||
|   hostNetwork: true | ||||
|   hostIPC: false | ||||
|   hostPID: false | ||||
|   runAsUser: | ||||
|     rule: 'RunAsAny' | ||||
|   seLinux: | ||||
|     rule: 'RunAsAny' | ||||
|   supplementalGroups: | ||||
|     rule: 'RunAsAny' | ||||
|   fsGroup: | ||||
|     rule: 'RunAsAny' | ||||
|   readOnlyRootFilesystem: false | ||||
| @@ -1,16 +0,0 @@ | ||||
| apiVersion: rbac.authorization.k8s.io/v1 | ||||
| kind: RoleBinding | ||||
| metadata: | ||||
|   name: gce:podsecuritypolicy:kube-proxy | ||||
|   namespace: kube-system | ||||
|   labels: | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
|     kubernetes.io/cluster-service: "true" | ||||
| roleRef: | ||||
|   apiGroup: rbac.authorization.k8s.io | ||||
|   kind: ClusterRole | ||||
|   name: gce:podsecuritypolicy:privileged | ||||
| subjects: | ||||
|   - kind: ServiceAccount | ||||
|     name: kube-proxy | ||||
|     namespace: kube-system | ||||
| @@ -1,17 +0,0 @@ | ||||
| apiVersion: rbac.authorization.k8s.io/v1 | ||||
| kind: RoleBinding | ||||
| metadata: | ||||
|   name: gce:podsecuritypolicy:unprivileged-addon | ||||
|   namespace: kube-system | ||||
|   labels: | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
|     kubernetes.io/cluster-service: "true" | ||||
| roleRef: | ||||
|   apiGroup: rbac.authorization.k8s.io | ||||
|   kind: Role | ||||
|   name: gce:podsecuritypolicy:unprivileged-addon | ||||
| subjects: | ||||
| - kind: Group | ||||
|   # All service accounts in the kube-system namespace are allowed to use this. | ||||
|   name: system:serviceaccounts:kube-system | ||||
|   apiGroup: rbac.authorization.k8s.io | ||||
| @@ -1,24 +0,0 @@ | ||||
| apiVersion: rbac.authorization.k8s.io/v1 | ||||
| kind: RoleBinding | ||||
| metadata: | ||||
|   name: gce:podsecuritypolicy:nodes | ||||
|   namespace: kube-system | ||||
|   annotations: | ||||
|     kubernetes.io/description: 'Allow nodes to create privileged pods. Should | ||||
|       be used in combination with the NodeRestriction admission plugin to limit | ||||
|       nodes to mirror pods bound to themselves.' | ||||
|   labels: | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
|     kubernetes.io/cluster-service: 'true' | ||||
| roleRef: | ||||
|   apiGroup: rbac.authorization.k8s.io | ||||
|   kind: ClusterRole | ||||
|   name: gce:podsecuritypolicy:privileged | ||||
| subjects: | ||||
|   - kind: Group | ||||
|     apiGroup: rbac.authorization.k8s.io | ||||
|     name: system:nodes | ||||
|   - kind: User | ||||
|     apiGroup: rbac.authorization.k8s.io | ||||
|     # Legacy node ID | ||||
|     name: kubelet | ||||
| @@ -1,18 +0,0 @@ | ||||
| apiVersion: rbac.authorization.k8s.io/v1 | ||||
| # The persistent volume binder creates recycler pods in the default namespace, | ||||
| # but the addon manager only creates namespaced objects in the kube-system | ||||
| # namespace, so this is a ClusterRoleBinding. | ||||
| kind: ClusterRoleBinding | ||||
| metadata: | ||||
|   name: gce:podsecuritypolicy:persistent-volume-binder | ||||
|   labels: | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
|     kubernetes.io/cluster-service: "true" | ||||
| roleRef: | ||||
|   apiGroup: rbac.authorization.k8s.io | ||||
|   kind: ClusterRole | ||||
|   name: gce:podsecuritypolicy:persistent-volume-binder | ||||
| subjects: | ||||
| - kind: ServiceAccount | ||||
|   name: persistent-volume-binder | ||||
|   namespace: kube-system | ||||
| @@ -1,20 +0,0 @@ | ||||
| apiVersion: rbac.authorization.k8s.io/v1 | ||||
| # The persistent volume binder creates recycler pods in the default namespace, | ||||
| # but the addon manager only creates namespaced objects in the kube-system | ||||
| # namespace, so this is a ClusterRole. | ||||
| kind: ClusterRole | ||||
| metadata: | ||||
|   name: gce:podsecuritypolicy:persistent-volume-binder | ||||
|   namespace: default | ||||
|   labels: | ||||
|     kubernetes.io/cluster-service: "true" | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
| rules: | ||||
| - apiGroups: | ||||
|   - policy | ||||
|   resourceNames: | ||||
|   - gce.persistent-volume-binder | ||||
|   resources: | ||||
|   - podsecuritypolicies | ||||
|   verbs: | ||||
|   - use | ||||
| @@ -1,30 +0,0 @@ | ||||
| apiVersion: policy/v1beta1 | ||||
| kind: PodSecurityPolicy | ||||
| metadata: | ||||
|   name: gce.persistent-volume-binder | ||||
|   annotations: | ||||
|     kubernetes.io/description: 'Policy used by the persistent-volume-binder | ||||
|       (a.k.a. persistentvolume-controller) to run recycler pods.' | ||||
|     seccomp.security.alpha.kubernetes.io/defaultProfileName:  'docker/default' | ||||
|     seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'runtime/default,docker/default' | ||||
|   labels: | ||||
|     kubernetes.io/cluster-service: 'true' | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
| spec: | ||||
|   privileged: false | ||||
|   volumes: | ||||
|   - 'nfs' | ||||
|   - 'secret'   # Required for service account credentials. | ||||
|   - 'projected' | ||||
|   hostNetwork: false | ||||
|   hostIPC: false | ||||
|   hostPID: false | ||||
|   runAsUser: | ||||
|     rule: 'RunAsAny' | ||||
|   seLinux: | ||||
|     rule: 'RunAsAny' | ||||
|   supplementalGroups: | ||||
|     rule: 'RunAsAny' | ||||
|   fsGroup: | ||||
|     rule: 'RunAsAny' | ||||
|   readOnlyRootFilesystem: false | ||||
| @@ -1,16 +0,0 @@ | ||||
| apiVersion: rbac.authorization.k8s.io/v1 | ||||
| kind: ClusterRole | ||||
| metadata: | ||||
|   name: gce:podsecuritypolicy:privileged | ||||
|   labels: | ||||
|     kubernetes.io/cluster-service: "true" | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
| rules: | ||||
| - apiGroups: | ||||
|   - policy | ||||
|   resourceNames: | ||||
|   - gce.privileged | ||||
|   resources: | ||||
|   - podsecuritypolicies | ||||
|   verbs: | ||||
|   - use | ||||
| @@ -1,33 +0,0 @@ | ||||
| apiVersion: policy/v1beta1 | ||||
| kind: PodSecurityPolicy | ||||
| metadata: | ||||
|   name: gce.privileged | ||||
|   annotations: | ||||
|     kubernetes.io/description: 'privileged allows full unrestricted access to | ||||
|       pod features, as if the PodSecurityPolicy controller was not enabled.' | ||||
|     seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*' | ||||
|   labels: | ||||
|     kubernetes.io/cluster-service: "true" | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
| spec: | ||||
|   privileged: true | ||||
|   allowPrivilegeEscalation: true | ||||
|   allowedCapabilities: | ||||
|   - '*' | ||||
|   volumes: | ||||
|   - '*' | ||||
|   hostNetwork: true | ||||
|   hostPorts: | ||||
|   - min: 0 | ||||
|     max: 65535 | ||||
|   hostIPC: true | ||||
|   hostPID: true | ||||
|   runAsUser: | ||||
|     rule: 'RunAsAny' | ||||
|   seLinux: | ||||
|     rule: 'RunAsAny' | ||||
|   supplementalGroups: | ||||
|     rule: 'RunAsAny' | ||||
|   fsGroup: | ||||
|     rule: 'RunAsAny' | ||||
|   readOnlyRootFilesystem: false | ||||
| @@ -1,17 +0,0 @@ | ||||
| apiVersion: rbac.authorization.k8s.io/v1 | ||||
| kind: Role | ||||
| metadata: | ||||
|   name: gce:podsecuritypolicy:unprivileged-addon | ||||
|   namespace: kube-system | ||||
|   labels: | ||||
|     kubernetes.io/cluster-service: "true" | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
| rules: | ||||
| - apiGroups: | ||||
|   - policy | ||||
|   resourceNames: | ||||
|   - gce.unprivileged-addon | ||||
|   resources: | ||||
|   - podsecuritypolicies | ||||
|   verbs: | ||||
|   - use | ||||
| @@ -1,55 +0,0 @@ | ||||
| apiVersion: policy/v1beta1 | ||||
| kind: PodSecurityPolicy | ||||
| metadata: | ||||
|   name: gce.unprivileged-addon | ||||
|   annotations: | ||||
|     kubernetes.io/description: 'This policy grants the minimum amount of | ||||
|       privilege necessary to run non-privileged kube-system pods. This policy is | ||||
|       not intended for use outside of kube-system, and may include further | ||||
|       restrictions in the future.' | ||||
|     seccomp.security.alpha.kubernetes.io/defaultProfileName:  'docker/default' | ||||
|     seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'runtime/default,docker/default' | ||||
|     # 'runtime/default' is already the default, but must be filled in on the | ||||
|     # pod to pass admission. | ||||
|     apparmor.security.beta.kubernetes.io/defaultProfileName:  'runtime/default' | ||||
|     apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default' | ||||
|   labels: | ||||
|     kubernetes.io/cluster-service: 'true' | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
| spec: | ||||
|   privileged: false | ||||
|   allowPrivilegeEscalation: false | ||||
|   # The docker default set of capabilities | ||||
|   allowedCapabilities: | ||||
|   - SETPCAP | ||||
|   - MKNOD | ||||
|   - AUDIT_WRITE | ||||
|   - CHOWN | ||||
|   - NET_RAW | ||||
|   - DAC_OVERRIDE | ||||
|   - FOWNER | ||||
|   - FSETID | ||||
|   - KILL | ||||
|   - SETGID | ||||
|   - SETUID | ||||
|   - NET_BIND_SERVICE | ||||
|   - SYS_CHROOT | ||||
|   - SETFCAP | ||||
|   volumes: | ||||
|   - 'emptyDir' | ||||
|   - 'configMap' | ||||
|   - 'secret' | ||||
|   - 'projected' | ||||
|   hostNetwork: false | ||||
|   hostIPC: false | ||||
|   hostPID: false | ||||
|   # TODO: The addons using this profile should not run as root. | ||||
|   runAsUser: | ||||
|     rule: 'RunAsAny' | ||||
|   seLinux: | ||||
|     rule: 'RunAsAny' | ||||
|   supplementalGroups: | ||||
|     rule: 'RunAsAny' | ||||
|   fsGroup: | ||||
|     rule: 'RunAsAny' | ||||
|   readOnlyRootFilesystem: false | ||||
| @@ -366,10 +366,6 @@ CUSTOM_INGRESS_YAML="${CUSTOM_INGRESS_YAML:-}" | ||||
| # Admission Controllers to invoke prior to persisting objects in cluster | ||||
| ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,PersistentVolumeClaimResize,DefaultTolerationSeconds,NodeRestriction,Priority,StorageObjectInUseProtection,RuntimeClass | ||||
|  | ||||
| if [[ "${ENABLE_POD_SECURITY_POLICY:-}" == "true" ]]; then | ||||
|   ADMISSION_CONTROL="${ADMISSION_CONTROL},PodSecurityPolicy" | ||||
| fi | ||||
|  | ||||
| # MutatingAdmissionWebhook should be the last controller that modifies the | ||||
| # request object, otherwise users will be confused if the mutating webhooks' | ||||
| # modification is overwritten. | ||||
|   | ||||
| @@ -411,9 +411,6 @@ CUSTOM_INGRESS_YAML=${CUSTOM_INGRESS_YAML:-} | ||||
|  | ||||
| if [[ -z "${KUBE_ADMISSION_CONTROL:-}" ]]; then | ||||
|   ADMISSION_CONTROL='NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,Priority,StorageObjectInUseProtection,PersistentVolumeClaimResize,RuntimeClass' | ||||
|   if [[ "${ENABLE_POD_SECURITY_POLICY:-}" = 'true' ]]; then | ||||
|     ADMISSION_CONTROL="${ADMISSION_CONTROL},PodSecurityPolicy" | ||||
|   fi | ||||
|   # ResourceQuota must come last, or a creation is recorded, but the pod may be forbidden. | ||||
|   ADMISSION_CONTROL="${ADMISSION_CONTROL},MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota" | ||||
| else | ||||
|   | ||||
| @@ -2337,15 +2337,6 @@ function setup-addon-manifests { | ||||
|   local -r dst_dir="/etc/kubernetes/$1/$2" | ||||
|  | ||||
|   copy-manifests "${src_dir}/$2" "${dst_dir}" | ||||
|  | ||||
|   # If the PodSecurityPolicy admission controller is enabled, | ||||
|   # set up the corresponding addon policies. | ||||
|   if [[ "${ENABLE_POD_SECURITY_POLICY:-}" == "true" ]]; then | ||||
|     local -r psp_dir="${src_dir}/${3:-$2}/podsecuritypolicies" | ||||
|     if [[ -d "${psp_dir}" ]]; then | ||||
|       copy-manifests "${psp_dir}" "${dst_dir}" | ||||
|     fi | ||||
|   fi | ||||
| } | ||||
|  | ||||
| # A function that downloads extra addons from a URL and puts them in the GCI | ||||
| @@ -2695,10 +2686,6 @@ function start-kube-addons { | ||||
|     setup-addon-manifests "addons" "rbac/legacy-kubelet-user-disable" | ||||
|   fi | ||||
|  | ||||
|   if [[ "${ENABLE_POD_SECURITY_POLICY:-}" == "true" ]]; then | ||||
|     setup-addon-manifests "addons" "podsecuritypolicies" | ||||
|   fi | ||||
|  | ||||
|   # Set up manifests of other addons. | ||||
|   if [[ "${KUBE_PROXY_DAEMONSET:-}" == "true" ]] && [[ "${KUBE_PROXY_DISABLE:-}" != "true" ]]; then | ||||
|     if [ -n "${CUSTOM_KUBE_PROXY_YAML:-}" ]; then | ||||
|   | ||||
| @@ -1124,7 +1124,6 @@ KUBE_PROXY_MODE: $(yaml-quote "${KUBE_PROXY_MODE:-iptables}") | ||||
| DETECT_LOCAL_MODE: $(yaml-quote "${DETECT_LOCAL_MODE:-}") | ||||
| NODE_PROBLEM_DETECTOR_TOKEN: $(yaml-quote "${NODE_PROBLEM_DETECTOR_TOKEN:-}") | ||||
| ADMISSION_CONTROL: $(yaml-quote "${ADMISSION_CONTROL:-}") | ||||
| ENABLE_POD_SECURITY_POLICY: $(yaml-quote "${ENABLE_POD_SECURITY_POLICY:-}") | ||||
| MASTER_IP_RANGE: $(yaml-quote "${MASTER_IP_RANGE}") | ||||
| RUNTIME_CONFIG: $(yaml-quote "${RUNTIME_CONFIG}") | ||||
| CA_CERT: $(yaml-quote "${CA_CERT_BASE64:-}") | ||||
|   | ||||
| @@ -26,7 +26,6 @@ export DOCKER=(docker "${DOCKER_OPTS[@]}") | ||||
| DOCKER_ROOT=${DOCKER_ROOT:-""} | ||||
| ALLOW_PRIVILEGED=${ALLOW_PRIVILEGED:-""} | ||||
| DENY_SECURITY_CONTEXT_ADMISSION=${DENY_SECURITY_CONTEXT_ADMISSION:-""} | ||||
| PSP_ADMISSION=${PSP_ADMISSION:-""} | ||||
| RUNTIME_CONFIG=${RUNTIME_CONFIG:-""} | ||||
| KUBELET_AUTHORIZATION_WEBHOOK=${KUBELET_AUTHORIZATION_WEBHOOK:-""} | ||||
| KUBELET_AUTHENTICATION_WEBHOOK=${KUBELET_AUTHENTICATION_WEBHOOK:-""} | ||||
| @@ -480,9 +479,6 @@ function start_apiserver { | ||||
|     if [[ -n "${DENY_SECURITY_CONTEXT_ADMISSION}" ]]; then | ||||
|       security_admission=",SecurityContextDeny" | ||||
|     fi | ||||
|     if [[ -n "${PSP_ADMISSION}" ]]; then | ||||
|       security_admission=",PodSecurityPolicy" | ||||
|     fi | ||||
|  | ||||
|     # Append security_admission plugin | ||||
|     ENABLE_ADMISSION_PLUGINS="${ENABLE_ADMISSION_PLUGINS}${security_admission}" | ||||
| @@ -939,13 +935,6 @@ function start_csi_snapshotter { | ||||
|     fi | ||||
| } | ||||
|  | ||||
| function create_psp_policy { | ||||
|     echo "Create podsecuritypolicy policies for RBAC." | ||||
|     ${KUBECTL} --kubeconfig="${CERT_DIR}/admin.kubeconfig" create -f "${KUBE_ROOT}/examples/podsecuritypolicy/rbac/policies.yaml" | ||||
|     ${KUBECTL} --kubeconfig="${CERT_DIR}/admin.kubeconfig" create -f "${KUBE_ROOT}/examples/podsecuritypolicy/rbac/roles.yaml" | ||||
|     ${KUBECTL} --kubeconfig="${CERT_DIR}/admin.kubeconfig" create -f "${KUBE_ROOT}/examples/podsecuritypolicy/rbac/bindings.yaml" | ||||
| } | ||||
|  | ||||
| function create_storage_class { | ||||
|     if [ -z "${CLOUD_PROVIDER}" ]; then | ||||
|         CLASS_FILE=${KUBE_ROOT}/cluster/addons/storage-class/local/default.yaml | ||||
| @@ -1208,10 +1197,6 @@ if [[ "${START_MODE}" != "kubeletonly" ]]; then | ||||
|   fi | ||||
| fi | ||||
|  | ||||
| if [[ -n "${PSP_ADMISSION}" && "${AUTHORIZATION_MODE}" = *RBAC* ]]; then | ||||
|   create_psp_policy | ||||
| fi | ||||
|  | ||||
| if [[ "${DEFAULT_STORAGE_CLASS}" = "true" ]]; then | ||||
|   create_storage_class | ||||
| fi | ||||
|   | ||||
| @@ -33,8 +33,15 @@ import ( | ||||
| 	core "k8s.io/kubernetes/pkg/apis/core" | ||||
| 	apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" | ||||
| 	"k8s.io/kubernetes/pkg/apis/policy" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" | ||||
| 	psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// AllowAny is the wildcard used to allow any profile. | ||||
| 	seccompAllowAny = "*" | ||||
| 	// DefaultProfileAnnotationKey specifies the default seccomp profile. | ||||
| 	seccompDefaultProfileAnnotationKey = "seccomp.security.alpha.kubernetes.io/defaultProfileName" | ||||
| 	// AllowedProfilesAnnotationKey specifies the allowed seccomp profiles. | ||||
| 	seccompAllowedProfilesAnnotationKey = "seccomp.security.alpha.kubernetes.io/allowedProfileNames" | ||||
| ) | ||||
|  | ||||
| // ValidatePodDisruptionBudget validates a PodDisruptionBudget and returns an ErrorList | ||||
| @@ -149,15 +156,15 @@ func ValidatePodSecurityPolicySpecificAnnotations(annotations map[string]string, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if p := annotations[seccomp.DefaultProfileAnnotationKey]; p != "" { | ||||
| 		allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.DefaultProfileAnnotationKey))...) | ||||
| 	if p := annotations[seccompDefaultProfileAnnotationKey]; p != "" { | ||||
| 		allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccompDefaultProfileAnnotationKey))...) | ||||
| 	} | ||||
| 	if allowed := annotations[seccomp.AllowedProfilesAnnotationKey]; allowed != "" { | ||||
| 	if allowed := annotations[seccompAllowedProfilesAnnotationKey]; allowed != "" { | ||||
| 		for _, p := range strings.Split(allowed, ",") { | ||||
| 			if p == seccomp.AllowAny { | ||||
| 			if p == seccompAllowAny { | ||||
| 				continue | ||||
| 			} | ||||
| 			allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.AllowedProfilesAnnotationKey))...) | ||||
| 			allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccompAllowedProfilesAnnotationKey))...) | ||||
| 		} | ||||
| 	} | ||||
| 	return allErrs | ||||
| @@ -321,7 +328,7 @@ func validatePSPSupplementalGroup(fldPath *field.Path, groupOptions *policy.Supp | ||||
| // validatePodSecurityPolicyVolumes validates the volume fields of PodSecurityPolicy. | ||||
| func validatePodSecurityPolicyVolumes(fldPath *field.Path, volumes []policy.FSType) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
| 	allowed := psputil.GetAllFSTypesAsSet() | ||||
| 	allowed := getAllFSTypesAsSet() | ||||
| 	// add in the * value since that is a pseudo type that is not included by default | ||||
| 	allowed.Insert(string(policy.All)) | ||||
| 	for _, v := range volumes { | ||||
| @@ -332,6 +339,44 @@ func validatePodSecurityPolicyVolumes(fldPath *field.Path, volumes []policy.FSTy | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| // getAllFSTypesAsSet returns all actual volume types, regardless | ||||
| // of feature gates. The special policy.All pseudo type is not included. | ||||
| func getAllFSTypesAsSet() sets.String { | ||||
| 	fstypes := sets.NewString() | ||||
| 	fstypes.Insert( | ||||
| 		string(policy.HostPath), | ||||
| 		string(policy.AzureFile), | ||||
| 		string(policy.Flocker), | ||||
| 		string(policy.FlexVolume), | ||||
| 		string(policy.EmptyDir), | ||||
| 		string(policy.GCEPersistentDisk), | ||||
| 		string(policy.AWSElasticBlockStore), | ||||
| 		string(policy.GitRepo), | ||||
| 		string(policy.Secret), | ||||
| 		string(policy.NFS), | ||||
| 		string(policy.ISCSI), | ||||
| 		string(policy.Glusterfs), | ||||
| 		string(policy.PersistentVolumeClaim), | ||||
| 		string(policy.RBD), | ||||
| 		string(policy.Cinder), | ||||
| 		string(policy.CephFS), | ||||
| 		string(policy.DownwardAPI), | ||||
| 		string(policy.FC), | ||||
| 		string(policy.ConfigMap), | ||||
| 		string(policy.VsphereVolume), | ||||
| 		string(policy.Quobyte), | ||||
| 		string(policy.AzureDisk), | ||||
| 		string(policy.PhotonPersistentDisk), | ||||
| 		string(policy.StorageOS), | ||||
| 		string(policy.Projected), | ||||
| 		string(policy.PortworxVolume), | ||||
| 		string(policy.ScaleIO), | ||||
| 		string(policy.CSI), | ||||
| 		string(policy.Ephemeral), | ||||
| 	) | ||||
| 	return fstypes | ||||
| } | ||||
|  | ||||
| // validatePSPDefaultAllowPrivilegeEscalation validates the DefaultAllowPrivilegeEscalation field against the AllowPrivilegeEscalation field of a PodSecurityPolicy. | ||||
| func validatePSPDefaultAllowPrivilegeEscalation(fldPath *field.Path, defaultAllowPrivilegeEscalation *bool, allowPrivilegeEscalation bool) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
|   | ||||
| @@ -30,8 +30,6 @@ import ( | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| 	"k8s.io/kubernetes/pkg/apis/policy" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" | ||||
| 	psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" | ||||
| 	"k8s.io/utils/pointer" | ||||
| ) | ||||
|  | ||||
| @@ -373,15 +371,15 @@ func TestValidatePodSecurityPolicy(t *testing.T) { | ||||
|  | ||||
| 	invalidSeccompDefault := validPSP() | ||||
| 	invalidSeccompDefault.Annotations = map[string]string{ | ||||
| 		seccomp.DefaultProfileAnnotationKey: "not-good", | ||||
| 		seccompDefaultProfileAnnotationKey: "not-good", | ||||
| 	} | ||||
| 	invalidSeccompAllowAnyDefault := validPSP() | ||||
| 	invalidSeccompAllowAnyDefault.Annotations = map[string]string{ | ||||
| 		seccomp.DefaultProfileAnnotationKey: "*", | ||||
| 		seccompDefaultProfileAnnotationKey: "*", | ||||
| 	} | ||||
| 	invalidSeccompAllowed := validPSP() | ||||
| 	invalidSeccompAllowed.Annotations = map[string]string{ | ||||
| 		seccomp.AllowedProfilesAnnotationKey: api.SeccompProfileRuntimeDefault + ",not-good", | ||||
| 		seccompAllowedProfilesAnnotationKey: api.SeccompProfileRuntimeDefault + ",not-good", | ||||
| 	} | ||||
|  | ||||
| 	invalidAllowedHostPathMissingPath := validPSP() | ||||
| @@ -660,8 +658,8 @@ func TestValidatePodSecurityPolicy(t *testing.T) { | ||||
|  | ||||
| 	validSeccomp := validPSP() | ||||
| 	validSeccomp.Annotations = map[string]string{ | ||||
| 		seccomp.DefaultProfileAnnotationKey:  api.SeccompProfileRuntimeDefault, | ||||
| 		seccomp.AllowedProfilesAnnotationKey: api.SeccompProfileRuntimeDefault + ",unconfined,localhost/foo,*", | ||||
| 		seccompDefaultProfileAnnotationKey:  api.SeccompProfileRuntimeDefault, | ||||
| 		seccompAllowedProfilesAnnotationKey: api.SeccompProfileRuntimeDefault + ",unconfined,localhost/foo,*", | ||||
| 	} | ||||
|  | ||||
| 	validDefaultAllowPrivilegeEscalation := validPSP() | ||||
| @@ -779,7 +777,7 @@ func TestValidatePSPVolumes(t *testing.T) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	volumes := psputil.GetAllFSTypesAsSet() | ||||
| 	volumes := getAllFSTypesAsSet() | ||||
| 	// add in the * value since that is a pseudo type that is not included by default | ||||
| 	volumes.Insert(string(policy.All)) | ||||
|  | ||||
|   | ||||
| @@ -45,7 +45,6 @@ import ( | ||||
| 	podpriority "k8s.io/kubernetes/plugin/pkg/admission/priority" | ||||
| 	"k8s.io/kubernetes/plugin/pkg/admission/runtimeclass" | ||||
| 	"k8s.io/kubernetes/plugin/pkg/admission/security/podsecurity" | ||||
| 	"k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy" | ||||
| 	"k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny" | ||||
| 	"k8s.io/kubernetes/plugin/pkg/admission/serviceaccount" | ||||
| 	"k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/label" | ||||
| @@ -75,8 +74,7 @@ var AllOrderedPlugins = []string{ | ||||
| 	nodetaint.PluginName,                    // TaintNodesByCondition | ||||
| 	alwayspullimages.PluginName,             // AlwaysPullImages | ||||
| 	imagepolicy.PluginName,                  // ImagePolicyWebhook | ||||
| 	podsecurity.PluginName,                  // PodSecurity - before PodSecurityPolicy so audit/warn get exercised even if PodSecurityPolicy denies | ||||
| 	podsecuritypolicy.PluginName,            // PodSecurityPolicy | ||||
| 	podsecurity.PluginName,                  // PodSecurity | ||||
| 	podnodeselector.PluginName,              // PodNodeSelector | ||||
| 	podpriority.PluginName,                  // Priority | ||||
| 	defaulttolerationseconds.PluginName,     // DefaultTolerationSeconds | ||||
| @@ -129,7 +127,6 @@ func RegisterAllAdmissionPlugins(plugins *admission.Plugins) { | ||||
| 	runtimeclass.Register(plugins) | ||||
| 	resourcequota.Register(plugins) | ||||
| 	podsecurity.Register(plugins) | ||||
| 	podsecuritypolicy.Register(plugins) | ||||
| 	podpriority.Register(plugins) | ||||
| 	scdeny.Register(plugins) | ||||
| 	serviceaccount.Register(plugins) | ||||
|   | ||||
| @@ -108,7 +108,6 @@ import ( | ||||
| 	"k8s.io/kubernetes/pkg/kubelet/util/sliceutils" | ||||
| 	"k8s.io/kubernetes/pkg/kubelet/volumemanager" | ||||
| 	"k8s.io/kubernetes/pkg/security/apparmor" | ||||
| 	sysctlallowlist "k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl" | ||||
| 	"k8s.io/kubernetes/pkg/util/oom" | ||||
| 	"k8s.io/kubernetes/pkg/volume" | ||||
| 	"k8s.io/kubernetes/pkg/volume/csi" | ||||
| @@ -776,7 +775,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, | ||||
|  | ||||
| 	// Safe, allowed sysctls can always be used as unsafe sysctls in the spec. | ||||
| 	// Hence, we concatenate those two lists. | ||||
| 	safeAndUnsafeSysctls := append(sysctlallowlist.SafeSysctlAllowlist(), allowedUnsafeSysctls...) | ||||
| 	safeAndUnsafeSysctls := append(sysctl.SafeSysctlAllowlist(), allowedUnsafeSysctls...) | ||||
| 	sysctlsAllowlist, err := sysctl.NewAllowlist(safeAndUnsafeSysctls) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
|   | ||||
| @@ -18,8 +18,6 @@ package sysctl | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl" | ||||
| ) | ||||
|  | ||||
| func TestNewAllowlist(t *testing.T) { | ||||
| @@ -37,7 +35,7 @@ func TestNewAllowlist(t *testing.T) { | ||||
| 		{sysctls: []string{"net.*/foo"}, err: true}, | ||||
| 		{sysctls: []string{"foo"}, err: true}, | ||||
| 	} { | ||||
| 		_, err := NewAllowlist(append(sysctl.SafeSysctlAllowlist(), test.sysctls...)) | ||||
| 		_, err := NewAllowlist(append(SafeSysctlAllowlist(), test.sysctls...)) | ||||
| 		if test.err && err == nil { | ||||
| 			t.Errorf("expected an error creating a allowlist for %v", test.sysctls) | ||||
| 		} else if !test.err && err != nil { | ||||
| @@ -69,7 +67,7 @@ func TestAllowlist(t *testing.T) { | ||||
| 		{sysctl: "kernel.sem", hostIPC: true}, | ||||
| 	} | ||||
|  | ||||
| 	w, err := NewAllowlist(append(sysctl.SafeSysctlAllowlist(), "kernel.msg*", "kernel.sem")) | ||||
| 	w, err := NewAllowlist(append(SafeSysctlAllowlist(), "kernel.msg*", "kernel.sem")) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to create allowlist: %v", err) | ||||
| 	} | ||||
|   | ||||
| @@ -16,13 +16,17 @@ limitations under the License. | ||||
| 
 | ||||
| package sysctl | ||||
| 
 | ||||
| import ( | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
| 
 | ||||
| // SysctlsStrategy defines the interface for all sysctl strategies. | ||||
| type SysctlsStrategy interface { | ||||
| 	// Validate ensures that the specified values fall within the range of the strategy. | ||||
| 	Validate(pod *api.Pod) field.ErrorList | ||||
| // SafeSysctlAllowlist returns the allowlist of safe sysctls and safe sysctl patterns (ending in *). | ||||
| // | ||||
| // A sysctl is called safe iff | ||||
| // - it is namespaced in the container or the pod | ||||
| // - it is isolated, i.e. has no influence on any other pod on the same node. | ||||
| func SafeSysctlAllowlist() []string { | ||||
| 	return []string{ | ||||
| 		"kernel.shm_rmid_forced", | ||||
| 		"net.ipv4.ip_local_port_range", | ||||
| 		"net.ipv4.tcp_syncookies", | ||||
| 		"net.ipv4.ping_group_range", | ||||
| 		"net.ipv4.ip_unprivileged_port_start", | ||||
| 	} | ||||
| } | ||||
| @@ -1,8 +0,0 @@ | ||||
| # See the OWNERS docs at https://go.k8s.io/owners | ||||
|  | ||||
| approvers: | ||||
|   - sig-auth-policy-approvers | ||||
| reviewers: | ||||
|   - sig-auth-policy-reviewers | ||||
| labels: | ||||
|   - sig/auth | ||||
| @@ -1,111 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package apparmor | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"k8s.io/api/core/v1" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| 	"k8s.io/kubernetes/pkg/security/apparmor" | ||||
| 	"k8s.io/kubernetes/pkg/util/maps" | ||||
| ) | ||||
|  | ||||
| // Strategy defines the interface for all AppArmor constraint strategies. | ||||
| type Strategy interface { | ||||
| 	// Generate updates the annotations based on constraint rules. The updates are applied to a copy | ||||
| 	// of the annotations, and returned. | ||||
| 	Generate(annotations map[string]string, container *api.Container) (map[string]string, error) | ||||
| 	// Validate ensures that the specified values fall within the range of the strategy. | ||||
| 	Validate(pod *api.Pod, container *api.Container) field.ErrorList | ||||
| } | ||||
|  | ||||
| type strategy struct { | ||||
| 	defaultProfile  string | ||||
| 	allowedProfiles map[string]bool | ||||
| 	// For printing error messages (preserves order). | ||||
| 	allowedProfilesString string | ||||
| } | ||||
|  | ||||
| var _ Strategy = &strategy{} | ||||
|  | ||||
| // NewStrategy creates a new strategy that enforces AppArmor profile constraints. | ||||
| func NewStrategy(pspAnnotations map[string]string) Strategy { | ||||
| 	var allowedProfiles map[string]bool | ||||
| 	if allowed, ok := pspAnnotations[v1.AppArmorBetaAllowedProfilesAnnotationKey]; ok { | ||||
| 		profiles := strings.Split(allowed, ",") | ||||
| 		allowedProfiles = make(map[string]bool, len(profiles)) | ||||
| 		for _, p := range profiles { | ||||
| 			allowedProfiles[p] = true | ||||
| 		} | ||||
| 	} | ||||
| 	return &strategy{ | ||||
| 		defaultProfile:        pspAnnotations[v1.AppArmorBetaDefaultProfileAnnotationKey], | ||||
| 		allowedProfiles:       allowedProfiles, | ||||
| 		allowedProfilesString: pspAnnotations[v1.AppArmorBetaAllowedProfilesAnnotationKey], | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *strategy) Generate(annotations map[string]string, container *api.Container) (map[string]string, error) { | ||||
| 	copy := maps.CopySS(annotations) | ||||
|  | ||||
| 	if annotations[v1.AppArmorBetaContainerAnnotationKeyPrefix+container.Name] != "" { | ||||
| 		// Profile already set, nothing to do. | ||||
| 		return copy, nil | ||||
| 	} | ||||
|  | ||||
| 	if s.defaultProfile == "" { | ||||
| 		// No default set. | ||||
| 		return copy, nil | ||||
| 	} | ||||
|  | ||||
| 	if copy == nil { | ||||
| 		copy = map[string]string{} | ||||
| 	} | ||||
| 	// Add the default profile. | ||||
| 	copy[v1.AppArmorBetaContainerAnnotationKeyPrefix+container.Name] = s.defaultProfile | ||||
|  | ||||
| 	return copy, nil | ||||
| } | ||||
|  | ||||
| func (s *strategy) Validate(pod *api.Pod, container *api.Container) field.ErrorList { | ||||
| 	if s.allowedProfiles == nil { | ||||
| 		// Unrestricted: allow all. | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	allErrs := field.ErrorList{} | ||||
| 	fieldPath := field.NewPath("pod", "metadata", "annotations").Key(v1.AppArmorBetaContainerAnnotationKeyPrefix + container.Name) | ||||
|  | ||||
| 	profile := apparmor.GetProfileNameFromPodAnnotations(pod.Annotations, container.Name) | ||||
| 	if profile == "" { | ||||
| 		if len(s.allowedProfiles) > 0 { | ||||
| 			allErrs = append(allErrs, field.Forbidden(fieldPath, "AppArmor profile must be set")) | ||||
| 			return allErrs | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if !s.allowedProfiles[profile] { | ||||
| 		msg := fmt.Sprintf("%s is not an allowed profile. Allowed values: %q", profile, s.allowedProfilesString) | ||||
| 		allErrs = append(allErrs, field.Forbidden(fieldPath, msg)) | ||||
| 	} | ||||
|  | ||||
| 	return allErrs | ||||
| } | ||||
| @@ -1,174 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package apparmor | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/davecgh/go-spew/spew" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
|  | ||||
| 	"k8s.io/api/core/v1" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| 	"k8s.io/kubernetes/pkg/util/maps" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	containerName = "test-c" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	withoutAppArmor = map[string]string{"foo": "bar"} | ||||
| 	withDefault     = map[string]string{ | ||||
| 		"foo": "bar", | ||||
| 		v1.AppArmorBetaContainerAnnotationKeyPrefix + containerName: v1.AppArmorBetaProfileRuntimeDefault, | ||||
| 	} | ||||
| 	withLocal = map[string]string{ | ||||
| 		"foo": "bar", | ||||
| 		v1.AppArmorBetaContainerAnnotationKeyPrefix + containerName: v1.AppArmorBetaProfileNamePrefix + "foo", | ||||
| 	} | ||||
| 	withDisallowed = map[string]string{ | ||||
| 		"foo": "bar", | ||||
| 		v1.AppArmorBetaContainerAnnotationKeyPrefix + containerName: v1.AppArmorBetaProfileNamePrefix + "bad", | ||||
| 	} | ||||
|  | ||||
| 	noAppArmor               = map[string]string{"foo": "bar"} | ||||
| 	unconstrainedWithDefault = map[string]string{ | ||||
| 		v1.AppArmorBetaDefaultProfileAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault, | ||||
| 	} | ||||
| 	constrained = map[string]string{ | ||||
| 		v1.AppArmorBetaAllowedProfilesAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault + "," + | ||||
| 			v1.AppArmorBetaProfileNamePrefix + "foo", | ||||
| 	} | ||||
| 	constrainedWithDefault = map[string]string{ | ||||
| 		v1.AppArmorBetaDefaultProfileAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault, | ||||
| 		v1.AppArmorBetaAllowedProfilesAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault + "," + | ||||
| 			v1.AppArmorBetaProfileNamePrefix + "foo", | ||||
| 	} | ||||
|  | ||||
| 	container = api.Container{ | ||||
| 		Name:  containerName, | ||||
| 		Image: "busybox", | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func TestGenerate(t *testing.T) { | ||||
| 	type testcase struct { | ||||
| 		pspAnnotations map[string]string | ||||
| 		podAnnotations map[string]string | ||||
| 		expected       map[string]string | ||||
| 	} | ||||
| 	tests := []testcase{{ | ||||
| 		pspAnnotations: noAppArmor, | ||||
| 		podAnnotations: withoutAppArmor, | ||||
| 		expected:       withoutAppArmor, | ||||
| 	}, { | ||||
| 		pspAnnotations: unconstrainedWithDefault, | ||||
| 		podAnnotations: withoutAppArmor, | ||||
| 		expected:       withDefault, | ||||
| 	}, { | ||||
| 		pspAnnotations: constrained, | ||||
| 		podAnnotations: withoutAppArmor, | ||||
| 		expected:       withoutAppArmor, | ||||
| 	}, { | ||||
| 		pspAnnotations: constrainedWithDefault, | ||||
| 		podAnnotations: withoutAppArmor, | ||||
| 		expected:       withDefault, | ||||
| 	}} | ||||
|  | ||||
| 	// Add unchanging permutations. | ||||
| 	for _, podAnnotations := range []map[string]string{withDefault, withLocal} { | ||||
| 		for _, pspAnnotations := range []map[string]string{noAppArmor, unconstrainedWithDefault, constrained, constrainedWithDefault} { | ||||
| 			tests = append(tests, testcase{ | ||||
| 				pspAnnotations: pspAnnotations, | ||||
| 				podAnnotations: podAnnotations, | ||||
| 				expected:       podAnnotations, | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for i, test := range tests { | ||||
| 		s := NewStrategy(test.pspAnnotations) | ||||
| 		msgAndArgs := []interface{}{"testcase[%d]: %s", i, spew.Sdump(test)} | ||||
| 		actual, err := s.Generate(test.podAnnotations, &container) | ||||
| 		assert.NoError(t, err, msgAndArgs...) | ||||
| 		assert.Equal(t, test.expected, actual, msgAndArgs...) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestValidate(t *testing.T) { | ||||
| 	type testcase struct { | ||||
| 		pspAnnotations map[string]string | ||||
| 		podAnnotations map[string]string | ||||
| 		expectErr      bool | ||||
| 	} | ||||
| 	tests := []testcase{} | ||||
| 	// Valid combinations | ||||
| 	for _, podAnnotations := range []map[string]string{withDefault, withLocal} { | ||||
| 		for _, pspAnnotations := range []map[string]string{noAppArmor, unconstrainedWithDefault, constrained, constrainedWithDefault} { | ||||
| 			tests = append(tests, testcase{ | ||||
| 				pspAnnotations: pspAnnotations, | ||||
| 				podAnnotations: podAnnotations, | ||||
| 				expectErr:      false, | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| 	for _, podAnnotations := range []map[string]string{withoutAppArmor, withDisallowed} { | ||||
| 		for _, pspAnnotations := range []map[string]string{noAppArmor, unconstrainedWithDefault} { | ||||
| 			tests = append(tests, testcase{ | ||||
| 				pspAnnotations: pspAnnotations, | ||||
| 				podAnnotations: podAnnotations, | ||||
| 				expectErr:      false, | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| 	// Invalid combinations | ||||
| 	for _, podAnnotations := range []map[string]string{withoutAppArmor, withDisallowed} { | ||||
| 		for _, pspAnnotations := range []map[string]string{constrained, constrainedWithDefault} { | ||||
| 			tests = append(tests, testcase{ | ||||
| 				pspAnnotations: pspAnnotations, | ||||
| 				podAnnotations: podAnnotations, | ||||
| 				expectErr:      true, | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for i, test := range tests { | ||||
| 		s := NewStrategy(test.pspAnnotations) | ||||
| 		pod, container := makeTestPod(test.podAnnotations) | ||||
| 		msgAndArgs := []interface{}{"testcase[%d]: %s", i, spew.Sdump(test)} | ||||
| 		errs := s.Validate(pod, container) | ||||
| 		if test.expectErr { | ||||
| 			assert.Len(t, errs, 1, msgAndArgs...) | ||||
| 		} else { | ||||
| 			assert.Len(t, errs, 0, msgAndArgs...) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func makeTestPod(annotations map[string]string) (*api.Pod, *api.Container) { | ||||
| 	return &api.Pod{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:        "test-pod", | ||||
| 			Annotations: maps.CopySS(annotations), | ||||
| 		}, | ||||
| 		Spec: api.PodSpec{ | ||||
| 			Containers: []api.Container{container}, | ||||
| 		}, | ||||
| 	}, &container | ||||
| } | ||||
| @@ -1,165 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package capabilities | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| // defaultCapabilities implements the Strategy interface | ||||
| type defaultCapabilities struct { | ||||
| 	defaultAddCapabilities   []api.Capability | ||||
| 	requiredDropCapabilities []api.Capability | ||||
| 	allowedCaps              []api.Capability | ||||
| } | ||||
|  | ||||
| var _ Strategy = &defaultCapabilities{} | ||||
|  | ||||
| // NewDefaultCapabilities creates a new defaultCapabilities strategy that will provide defaults and validation | ||||
| // based on the configured initial caps and allowed caps. | ||||
| func NewDefaultCapabilities(defaultAddCapabilities, requiredDropCapabilities, allowedCaps []corev1.Capability) (Strategy, error) { | ||||
| 	internalDefaultAddCaps := make([]api.Capability, len(defaultAddCapabilities)) | ||||
| 	for i, capability := range defaultAddCapabilities { | ||||
| 		internalDefaultAddCaps[i] = api.Capability(capability) | ||||
| 	} | ||||
| 	internalRequiredDropCaps := make([]api.Capability, len(requiredDropCapabilities)) | ||||
| 	for i, capability := range requiredDropCapabilities { | ||||
| 		internalRequiredDropCaps[i] = api.Capability(capability) | ||||
| 	} | ||||
| 	internalAllowedCaps := make([]api.Capability, len(allowedCaps)) | ||||
| 	for i, capability := range allowedCaps { | ||||
| 		internalAllowedCaps[i] = api.Capability(capability) | ||||
| 	} | ||||
| 	return &defaultCapabilities{ | ||||
| 		defaultAddCapabilities:   internalDefaultAddCaps, | ||||
| 		requiredDropCapabilities: internalRequiredDropCaps, | ||||
| 		allowedCaps:              internalAllowedCaps, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // Generate creates the capabilities based on policy rules.  Generate will produce the following: | ||||
| // 1.  a capabilities.Add set containing all the required adds (unless the | ||||
| // 		container specifically is dropping the cap) and container requested adds | ||||
| // 2.  a capabilities.Drop set containing all the required drops and container requested drops | ||||
| // | ||||
| // Returns the original container capabilities if no changes are required. | ||||
| func (s *defaultCapabilities) Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error) { | ||||
| 	defaultAdd := makeCapSet(s.defaultAddCapabilities) | ||||
| 	requiredDrop := makeCapSet(s.requiredDropCapabilities) | ||||
| 	containerAdd := sets.NewString() | ||||
| 	containerDrop := sets.NewString() | ||||
|  | ||||
| 	var containerCapabilities *api.Capabilities | ||||
| 	if container.SecurityContext != nil && container.SecurityContext.Capabilities != nil { | ||||
| 		containerCapabilities = container.SecurityContext.Capabilities | ||||
| 		containerAdd = makeCapSet(container.SecurityContext.Capabilities.Add) | ||||
| 		containerDrop = makeCapSet(container.SecurityContext.Capabilities.Drop) | ||||
| 	} | ||||
|  | ||||
| 	// remove any default adds that the container is specifically dropping | ||||
| 	defaultAdd = defaultAdd.Difference(containerDrop) | ||||
|  | ||||
| 	combinedAdd := defaultAdd.Union(containerAdd) | ||||
| 	combinedDrop := requiredDrop.Union(containerDrop) | ||||
|  | ||||
| 	// no changes? return the original capabilities | ||||
| 	if (len(combinedAdd) == len(containerAdd)) && (len(combinedDrop) == len(containerDrop)) { | ||||
| 		return containerCapabilities, nil | ||||
| 	} | ||||
|  | ||||
| 	return &api.Capabilities{ | ||||
| 		Add:  capabilityFromStringSlice(combinedAdd.List()), | ||||
| 		Drop: capabilityFromStringSlice(combinedDrop.List()), | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // Validate ensures that the specified values fall within the range of the strategy. | ||||
| func (s *defaultCapabilities) Validate(fldPath *field.Path, pod *api.Pod, container *api.Container, capabilities *api.Capabilities) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
|  | ||||
| 	if capabilities == nil { | ||||
| 		// if container.SC.Caps is nil then nothing was defaulted by the strategy or requested by the pod author | ||||
| 		// if there are no required caps on the strategy and nothing is requested on the pod | ||||
| 		// then we can safely return here without further validation. | ||||
| 		if len(s.defaultAddCapabilities) == 0 && len(s.requiredDropCapabilities) == 0 { | ||||
| 			return allErrs | ||||
| 		} | ||||
|  | ||||
| 		// container has no requested caps but we have required caps.  We should have something in | ||||
| 		// at least the drops on the container. | ||||
| 		allErrs = append(allErrs, field.Invalid(fldPath, capabilities, | ||||
| 			"required capabilities are not set on the securityContext")) | ||||
| 		return allErrs | ||||
| 	} | ||||
|  | ||||
| 	allowedAdd := makeCapSet(s.allowedCaps) | ||||
| 	allowAllCaps := allowedAdd.Has(string(policy.AllowAllCapabilities)) | ||||
| 	if allowAllCaps { | ||||
| 		// skip validation against allowed/defaultAdd/requiredDrop because all capabilities are allowed by a wildcard | ||||
| 		return allErrs | ||||
| 	} | ||||
|  | ||||
| 	// validate that anything being added is in the default or allowed sets | ||||
| 	defaultAdd := makeCapSet(s.defaultAddCapabilities) | ||||
|  | ||||
| 	for _, cap := range capabilities.Add { | ||||
| 		sCap := string(cap) | ||||
| 		if !defaultAdd.Has(sCap) && !allowedAdd.Has(sCap) { | ||||
| 			allErrs = append(allErrs, field.Invalid(fldPath.Child("add"), sCap, "capability may not be added")) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// validate that anything that is required to be dropped is in the drop set | ||||
| 	containerDrops := makeCapSet(capabilities.Drop) | ||||
|  | ||||
| 	for _, requiredDrop := range s.requiredDropCapabilities { | ||||
| 		sDrop := string(requiredDrop) | ||||
| 		if !containerDrops.Has(sDrop) { | ||||
| 			allErrs = append(allErrs, field.Invalid(fldPath.Child("drop"), capabilities.Drop, | ||||
| 				fmt.Sprintf("%s is required to be dropped but was not found", sDrop))) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| // capabilityFromStringSlice creates a capability slice from a string slice. | ||||
| func capabilityFromStringSlice(slice []string) []api.Capability { | ||||
| 	if len(slice) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	caps := []api.Capability{} | ||||
| 	for _, c := range slice { | ||||
| 		caps = append(caps, api.Capability(c)) | ||||
| 	} | ||||
| 	return caps | ||||
| } | ||||
|  | ||||
| // makeCapSet makes a string set from capabilities. | ||||
| func makeCapSet(caps []api.Capability) sets.String { | ||||
| 	s := sets.NewString() | ||||
| 	for _, c := range caps { | ||||
| 		s.Insert(string(c)) | ||||
| 	} | ||||
| 	return s | ||||
| } | ||||
| @@ -1,412 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package capabilities | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
|  | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| func TestGenerateAdds(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		defaultAddCaps []corev1.Capability | ||||
| 		containerCaps  *api.Capabilities | ||||
| 		expectedCaps   *api.Capabilities | ||||
| 	}{ | ||||
| 		"no required, no container requests": {}, | ||||
| 		"no required, no container requests, non-nil": { | ||||
| 			containerCaps: &api.Capabilities{}, | ||||
| 			expectedCaps:  &api.Capabilities{}, | ||||
| 		}, | ||||
| 		"required, no container requests": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo"}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"required, container requests add required": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"foo"}, | ||||
| 			}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"multiple required, container requests add required": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo", "bar", "baz"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"foo"}, | ||||
| 			}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"bar", "baz", "foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"required, container requests add non-required": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"bar"}, | ||||
| 			}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"bar", "foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"generation does not mutate unnecessarily": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo", "bar"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"foo", "foo", "bar", "baz"}, | ||||
| 			}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"foo", "foo", "bar", "baz"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"generation dedupes": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo", "bar"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"foo", "baz"}, | ||||
| 			}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"bar", "baz", "foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"generation is case sensitive - will not dedupe": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"FOO"}, | ||||
| 			}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"FOO", "foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range tests { | ||||
| 		container := &api.Container{ | ||||
| 			SecurityContext: &api.SecurityContext{ | ||||
| 				Capabilities: v.containerCaps, | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		strategy, err := NewDefaultCapabilities(v.defaultAddCaps, nil, nil) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("%s failed: %v", k, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		generatedCaps, err := strategy.Generate(nil, container) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("%s failed generating: %v", k, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		if v.expectedCaps == nil && generatedCaps != nil { | ||||
| 			t.Errorf("%s expected nil caps to be generated but got %v", k, generatedCaps) | ||||
| 			continue | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(v.expectedCaps, generatedCaps) { | ||||
| 			t.Errorf("%s did not generate correctly.  Expected: %#v, Actual: %#v", k, v.expectedCaps, generatedCaps) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGenerateDrops(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		defaultAddCaps   []corev1.Capability | ||||
| 		requiredDropCaps []corev1.Capability | ||||
| 		containerCaps    *api.Capabilities | ||||
| 		expectedCaps     *api.Capabilities | ||||
| 	}{ | ||||
| 		"no required, no container requests": { | ||||
| 			expectedCaps: nil, | ||||
| 		}, | ||||
| 		"no required, no container requests, non-nil": { | ||||
| 			containerCaps: &api.Capabilities{}, | ||||
| 			expectedCaps:  &api.Capabilities{}, | ||||
| 		}, | ||||
| 		"required drops are defaulted": { | ||||
| 			requiredDropCaps: []corev1.Capability{"foo"}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"required drops are defaulted when making container requests": { | ||||
| 			requiredDropCaps: []corev1.Capability{"baz"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"foo", "bar"}, | ||||
| 			}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"bar", "baz", "foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"required drops do not mutate unnecessarily": { | ||||
| 			requiredDropCaps: []corev1.Capability{"baz"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"foo", "bar", "baz"}, | ||||
| 			}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"foo", "bar", "baz"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"can drop a required add": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"foo"}, | ||||
| 			}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"can drop non-required add": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"bar"}, | ||||
| 			}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Add:  []api.Capability{"foo"}, | ||||
| 				Drop: []api.Capability{"bar"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"defaulting adds and drops, dropping a required add": { | ||||
| 			defaultAddCaps:   []corev1.Capability{"foo", "bar", "baz"}, | ||||
| 			requiredDropCaps: []corev1.Capability{"abc"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"foo"}, | ||||
| 			}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Add:  []api.Capability{"bar", "baz"}, | ||||
| 				Drop: []api.Capability{"abc", "foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"generation dedupes": { | ||||
| 			requiredDropCaps: []corev1.Capability{"baz", "foo"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"bar", "foo"}, | ||||
| 			}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"bar", "baz", "foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"generation is case sensitive - will not dedupe": { | ||||
| 			requiredDropCaps: []corev1.Capability{"bar"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"BAR"}, | ||||
| 			}, | ||||
| 			expectedCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"BAR", "bar"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for k, v := range tests { | ||||
| 		container := &api.Container{ | ||||
| 			SecurityContext: &api.SecurityContext{ | ||||
| 				Capabilities: v.containerCaps, | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		strategy, err := NewDefaultCapabilities(v.defaultAddCaps, v.requiredDropCaps, nil) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("%s failed: %v", k, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		generatedCaps, err := strategy.Generate(nil, container) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("%s failed generating: %v", k, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		if v.expectedCaps == nil && generatedCaps != nil { | ||||
| 			t.Errorf("%s expected nil caps to be generated but got %#v", k, generatedCaps) | ||||
| 			continue | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(v.expectedCaps, generatedCaps) { | ||||
| 			t.Errorf("%s did not generate correctly.  Expected: %#v, Actual: %#v", k, v.expectedCaps, generatedCaps) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestValidateAdds(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		defaultAddCaps []corev1.Capability | ||||
| 		allowedCaps    []corev1.Capability | ||||
| 		containerCaps  *api.Capabilities | ||||
| 		expectedError  string | ||||
| 	}{ | ||||
| 		// no container requests | ||||
| 		"no required, no allowed, no container requests": {}, | ||||
| 		"no required, allowed, no container requests": { | ||||
| 			allowedCaps: []corev1.Capability{"foo"}, | ||||
| 		}, | ||||
| 		"required, no allowed, no container requests": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo"}, | ||||
| 			expectedError:  `capabilities: Invalid value: "null": required capabilities are not set on the securityContext`, | ||||
| 		}, | ||||
|  | ||||
| 		// container requests match required | ||||
| 		"required, no allowed, container requests valid": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"required, no allowed, container requests invalid": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"bar"}, | ||||
| 			}, | ||||
| 			expectedError: `capabilities.add: Invalid value: "bar": capability may not be added`, | ||||
| 		}, | ||||
|  | ||||
| 		// container requests match allowed | ||||
| 		"no required, allowed, container requests valid": { | ||||
| 			allowedCaps: []corev1.Capability{"foo"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"no required, all allowed, container requests valid": { | ||||
| 			allowedCaps: []corev1.Capability{policy.AllowAllCapabilities}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"no required, allowed, container requests invalid": { | ||||
| 			allowedCaps: []corev1.Capability{"foo"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"bar"}, | ||||
| 			}, | ||||
| 			expectedError: `capabilities.add: Invalid value: "bar": capability may not be added`, | ||||
| 		}, | ||||
|  | ||||
| 		// required and allowed | ||||
| 		"required, allowed, container requests valid required": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo"}, | ||||
| 			allowedCaps:    []corev1.Capability{"bar"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"required, allowed, container requests valid allowed": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo"}, | ||||
| 			allowedCaps:    []corev1.Capability{"bar"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"bar"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"required, allowed, container requests invalid": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo"}, | ||||
| 			allowedCaps:    []corev1.Capability{"bar"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"baz"}, | ||||
| 			}, | ||||
| 			expectedError: `capabilities.add: Invalid value: "baz": capability may not be added`, | ||||
| 		}, | ||||
| 		"validation is case sensitive": { | ||||
| 			defaultAddCaps: []corev1.Capability{"foo"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Add: []api.Capability{"FOO"}, | ||||
| 			}, | ||||
| 			expectedError: `capabilities.add: Invalid value: "FOO": capability may not be added`, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range tests { | ||||
| 		strategy, err := NewDefaultCapabilities(v.defaultAddCaps, nil, v.allowedCaps) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("%s failed: %v", k, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		errs := strategy.Validate(field.NewPath("capabilities"), nil, nil, v.containerCaps) | ||||
| 		if v.expectedError == "" && len(errs) > 0 { | ||||
| 			t.Errorf("%s should have passed but had errors %v", k, errs) | ||||
| 			continue | ||||
| 		} | ||||
| 		if v.expectedError != "" && len(errs) == 0 { | ||||
| 			t.Errorf("%s should have failed but received no errors", k) | ||||
| 			continue | ||||
| 		} | ||||
| 		if len(errs) == 1 && errs[0].Error() != v.expectedError { | ||||
| 			t.Errorf("%s should have failed with %v but received %v", k, v.expectedError, errs[0]) | ||||
| 			continue | ||||
| 		} | ||||
| 		if len(errs) > 1 { | ||||
| 			t.Errorf("%s should have failed with at most one error, but received %v: %v", k, len(errs), errs) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestValidateDrops(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		requiredDropCaps []corev1.Capability | ||||
| 		containerCaps    *api.Capabilities | ||||
| 		expectedError    string | ||||
| 	}{ | ||||
| 		// no container requests | ||||
| 		"no required, no container requests": {}, | ||||
| 		"required, no container requests": { | ||||
| 			requiredDropCaps: []corev1.Capability{"foo"}, | ||||
| 			expectedError:    `capabilities: Invalid value: "null": required capabilities are not set on the securityContext`, | ||||
| 		}, | ||||
|  | ||||
| 		// container requests match required | ||||
| 		"required, container requests valid": { | ||||
| 			requiredDropCaps: []corev1.Capability{"foo"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"required, container requests invalid": { | ||||
| 			requiredDropCaps: []corev1.Capability{"foo"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"bar"}, | ||||
| 			}, | ||||
| 			expectedError: `capabilities.drop: Invalid value: []core.Capability{"bar"}: foo is required to be dropped but was not found`, | ||||
| 		}, | ||||
| 		"validation is case sensitive": { | ||||
| 			requiredDropCaps: []corev1.Capability{"foo"}, | ||||
| 			containerCaps: &api.Capabilities{ | ||||
| 				Drop: []api.Capability{"FOO"}, | ||||
| 			}, | ||||
| 			expectedError: `capabilities.drop: Invalid value: []core.Capability{"FOO"}: foo is required to be dropped but was not found`, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range tests { | ||||
| 		strategy, err := NewDefaultCapabilities(nil, v.requiredDropCaps, nil) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("%s failed: %v", k, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		errs := strategy.Validate(field.NewPath("capabilities"), nil, nil, v.containerCaps) | ||||
| 		if v.expectedError == "" && len(errs) > 0 { | ||||
| 			t.Errorf("%s should have passed but had errors %v", k, errs) | ||||
| 			continue | ||||
| 		} | ||||
| 		if v.expectedError != "" && len(errs) == 0 { | ||||
| 			t.Errorf("%s should have failed but received no errors", k) | ||||
| 			continue | ||||
| 		} | ||||
| 		if len(errs) == 1 && errs[0].Error() != v.expectedError { | ||||
| 			t.Errorf("%s should have failed with %v but received %v", k, v.expectedError, errs[0]) | ||||
| 			continue | ||||
| 		} | ||||
| 		if len(errs) > 1 { | ||||
| 			t.Errorf("%s should have failed with at most one error, but received %v: %v", k, len(errs), errs) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,19 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| // Package capabilities contains code for validating and defaulting a pod's | ||||
| // kernel capabilities according to a security policy. | ||||
| package capabilities | ||||
| @@ -1,30 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package capabilities | ||||
|  | ||||
| import ( | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| // Strategy defines the interface for all cap constraint strategies. | ||||
| type Strategy interface { | ||||
| 	// Generate creates the capabilities based on policy rules. | ||||
| 	Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error) | ||||
| 	// Validate ensures that the specified values fall within the range of the strategy. | ||||
| 	Validate(fldPath *field.Path, pod *api.Pod, container *api.Container, capabilities *api.Capabilities) field.ErrorList | ||||
| } | ||||
| @@ -1,20 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| // Package podsecuritypolicy contains code for validating and defaulting the | ||||
| // security context of a pod and its containers according to a security | ||||
| // policy. | ||||
| package podsecuritypolicy | ||||
| @@ -1,196 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package podsecuritypolicy | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/errors" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/group" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/user" | ||||
| ) | ||||
|  | ||||
| type simpleStrategyFactory struct{} | ||||
|  | ||||
| var _ StrategyFactory = &simpleStrategyFactory{} | ||||
|  | ||||
| func NewSimpleStrategyFactory() StrategyFactory { | ||||
| 	return &simpleStrategyFactory{} | ||||
| } | ||||
|  | ||||
| func (f *simpleStrategyFactory) CreateStrategies(psp *policy.PodSecurityPolicy, namespace string) (*ProviderStrategies, error) { | ||||
| 	errs := []error{} | ||||
|  | ||||
| 	userStrat, err := createUserStrategy(&psp.Spec.RunAsUser) | ||||
| 	if err != nil { | ||||
| 		errs = append(errs, err) | ||||
| 	} | ||||
|  | ||||
| 	var groupStrat group.GroupStrategy | ||||
| 	groupStrat, err = createRunAsGroupStrategy(psp.Spec.RunAsGroup) | ||||
| 	if err != nil { | ||||
| 		errs = append(errs, err) | ||||
| 	} | ||||
|  | ||||
| 	seLinuxStrat, err := createSELinuxStrategy(&psp.Spec.SELinux) | ||||
| 	if err != nil { | ||||
| 		errs = append(errs, err) | ||||
| 	} | ||||
|  | ||||
| 	appArmorStrat, err := createAppArmorStrategy(psp) | ||||
| 	if err != nil { | ||||
| 		errs = append(errs, err) | ||||
| 	} | ||||
|  | ||||
| 	seccompStrat, err := createSeccompStrategy(psp) | ||||
| 	if err != nil { | ||||
| 		errs = append(errs, err) | ||||
| 	} | ||||
|  | ||||
| 	fsGroupStrat, err := createFSGroupStrategy(&psp.Spec.FSGroup) | ||||
| 	if err != nil { | ||||
| 		errs = append(errs, err) | ||||
| 	} | ||||
|  | ||||
| 	supGroupStrat, err := createSupplementalGroupStrategy(&psp.Spec.SupplementalGroups) | ||||
| 	if err != nil { | ||||
| 		errs = append(errs, err) | ||||
| 	} | ||||
|  | ||||
| 	capStrat, err := createCapabilitiesStrategy(psp.Spec.DefaultAddCapabilities, psp.Spec.RequiredDropCapabilities, psp.Spec.AllowedCapabilities) | ||||
| 	if err != nil { | ||||
| 		errs = append(errs, err) | ||||
| 	} | ||||
|  | ||||
| 	sysctlsStrat := createSysctlsStrategy(sysctl.SafeSysctlAllowlist(), psp.Spec.AllowedUnsafeSysctls, psp.Spec.ForbiddenSysctls) | ||||
|  | ||||
| 	if len(errs) > 0 { | ||||
| 		return nil, errors.NewAggregate(errs) | ||||
| 	} | ||||
|  | ||||
| 	strategies := &ProviderStrategies{ | ||||
| 		RunAsUserStrategy:         userStrat, | ||||
| 		RunAsGroupStrategy:        groupStrat, | ||||
| 		SELinuxStrategy:           seLinuxStrat, | ||||
| 		AppArmorStrategy:          appArmorStrat, | ||||
| 		FSGroupStrategy:           fsGroupStrat, | ||||
| 		SupplementalGroupStrategy: supGroupStrat, | ||||
| 		CapabilitiesStrategy:      capStrat, | ||||
| 		SeccompStrategy:           seccompStrat, | ||||
| 		SysctlsStrategy:           sysctlsStrat, | ||||
| 	} | ||||
|  | ||||
| 	return strategies, nil | ||||
| } | ||||
|  | ||||
| // createUserStrategy creates a new user strategy. | ||||
| func createUserStrategy(opts *policy.RunAsUserStrategyOptions) (user.RunAsUserStrategy, error) { | ||||
| 	switch opts.Rule { | ||||
| 	case policy.RunAsUserStrategyMustRunAs: | ||||
| 		return user.NewMustRunAs(opts) | ||||
| 	case policy.RunAsUserStrategyMustRunAsNonRoot: | ||||
| 		return user.NewRunAsNonRoot(opts) | ||||
| 	case policy.RunAsUserStrategyRunAsAny: | ||||
| 		return user.NewRunAsAny(opts) | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("Unrecognized RunAsUser strategy type %s", opts.Rule) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // createRunAsGroupStrategy creates a new group strategy. | ||||
| func createRunAsGroupStrategy(opts *policy.RunAsGroupStrategyOptions) (group.GroupStrategy, error) { | ||||
| 	if opts == nil { | ||||
| 		return group.NewRunAsAny() | ||||
| 	} | ||||
| 	switch opts.Rule { | ||||
| 	case policy.RunAsGroupStrategyMustRunAs: | ||||
| 		return group.NewMustRunAs(opts.Ranges) | ||||
| 	case policy.RunAsGroupStrategyRunAsAny: | ||||
| 		return group.NewRunAsAny() | ||||
| 	case policy.RunAsGroupStrategyMayRunAs: | ||||
| 		return group.NewMayRunAs(opts.Ranges) | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("Unrecognized RunAsGroup strategy type %s", opts.Rule) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // createSELinuxStrategy creates a new selinux strategy. | ||||
| func createSELinuxStrategy(opts *policy.SELinuxStrategyOptions) (selinux.SELinuxStrategy, error) { | ||||
| 	switch opts.Rule { | ||||
| 	case policy.SELinuxStrategyMustRunAs: | ||||
| 		return selinux.NewMustRunAs(opts) | ||||
| 	case policy.SELinuxStrategyRunAsAny: | ||||
| 		return selinux.NewRunAsAny(opts) | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("Unrecognized SELinuxContext strategy type %s", opts.Rule) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // createAppArmorStrategy creates a new AppArmor strategy. | ||||
| func createAppArmorStrategy(psp *policy.PodSecurityPolicy) (apparmor.Strategy, error) { | ||||
| 	return apparmor.NewStrategy(psp.Annotations), nil | ||||
| } | ||||
|  | ||||
| // createSeccompStrategy creates a new seccomp strategy. | ||||
| func createSeccompStrategy(psp *policy.PodSecurityPolicy) (seccomp.Strategy, error) { | ||||
| 	return seccomp.NewStrategy(psp.Annotations), nil | ||||
| } | ||||
|  | ||||
| // createFSGroupStrategy creates a new fsgroup strategy | ||||
| func createFSGroupStrategy(opts *policy.FSGroupStrategyOptions) (group.GroupStrategy, error) { | ||||
| 	switch opts.Rule { | ||||
| 	case policy.FSGroupStrategyRunAsAny: | ||||
| 		return group.NewRunAsAny() | ||||
| 	case policy.FSGroupStrategyMayRunAs: | ||||
| 		return group.NewMayRunAs(opts.Ranges) | ||||
| 	case policy.FSGroupStrategyMustRunAs: | ||||
| 		return group.NewMustRunAs(opts.Ranges) | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("Unrecognized FSGroup strategy type %s", opts.Rule) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // createSupplementalGroupStrategy creates a new supplemental group strategy | ||||
| func createSupplementalGroupStrategy(opts *policy.SupplementalGroupsStrategyOptions) (group.GroupStrategy, error) { | ||||
| 	switch opts.Rule { | ||||
| 	case policy.SupplementalGroupsStrategyRunAsAny: | ||||
| 		return group.NewRunAsAny() | ||||
| 	case policy.SupplementalGroupsStrategyMayRunAs: | ||||
| 		return group.NewMayRunAs(opts.Ranges) | ||||
| 	case policy.SupplementalGroupsStrategyMustRunAs: | ||||
| 		return group.NewMustRunAs(opts.Ranges) | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("Unrecognized SupplementalGroups strategy type %s", opts.Rule) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // createCapabilitiesStrategy creates a new capabilities strategy. | ||||
| func createCapabilitiesStrategy(defaultAddCaps, requiredDropCaps, allowedCaps []corev1.Capability) (capabilities.Strategy, error) { | ||||
| 	return capabilities.NewDefaultCapabilities(defaultAddCaps, requiredDropCaps, allowedCaps) | ||||
| } | ||||
|  | ||||
| // createSysctlsStrategy creates a new sysctls strategy. | ||||
| func createSysctlsStrategy(safeAllowlist, allowedUnsafeSysctls, forbiddenSysctls []string) sysctl.SysctlsStrategy { | ||||
| 	return sysctl.NewMustMatchPatterns(safeAllowlist, allowedUnsafeSysctls, forbiddenSysctls) | ||||
| } | ||||
| @@ -1,19 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| // Package group contains code for validating and defaulting the FSGroup and | ||||
| // supplemental groups of a pod according to a security policy. | ||||
| package group | ||||
| @@ -1,46 +0,0 @@ | ||||
| /* | ||||
| Copyright 2018 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package group | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" | ||||
| ) | ||||
|  | ||||
| func ValidateGroupsInRanges(fldPath *field.Path, ranges []policy.IDRange, groups []int64) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
|  | ||||
| 	for _, group := range groups { | ||||
| 		if !isGroupInRanges(group, ranges) { | ||||
| 			detail := fmt.Sprintf("group %d must be in the ranges: %v", group, ranges) | ||||
| 			allErrs = append(allErrs, field.Invalid(fldPath, groups, detail)) | ||||
| 		} | ||||
| 	} | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| func isGroupInRanges(group int64, ranges []policy.IDRange) bool { | ||||
| 	for _, rng := range ranges { | ||||
| 		if psputil.GroupFallsInRange(group, rng) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| @@ -1,59 +0,0 @@ | ||||
| /* | ||||
| Copyright 2018 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package group | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| // mayRunAs implements the GroupStrategy interface. | ||||
| type mayRunAs struct { | ||||
| 	ranges []policy.IDRange | ||||
| } | ||||
|  | ||||
| var _ GroupStrategy = &mayRunAs{} | ||||
|  | ||||
| // NewMayRunAs provides a new MayRunAs strategy. | ||||
| func NewMayRunAs(ranges []policy.IDRange) (GroupStrategy, error) { | ||||
| 	if len(ranges) == 0 { | ||||
| 		return nil, fmt.Errorf("ranges must be supplied for MayRunAs") | ||||
| 	} | ||||
| 	return &mayRunAs{ | ||||
| 		ranges: ranges, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // Generate creates the group based on policy rules.  This strategy returns an empty slice. | ||||
| func (s *mayRunAs) Generate(_ *api.Pod) ([]int64, error) { | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| // Generate a single value to be applied.  This is used for FSGroup.  This strategy returns nil. | ||||
| func (s *mayRunAs) GenerateSingle(_ *api.Pod) (*int64, error) { | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| // Validate ensures that the specified values fall within the range of the strategy. | ||||
| // Groups are passed in here to allow this strategy to support multiple group fields (fsgroup and | ||||
| // supplemental groups). | ||||
| func (s *mayRunAs) Validate(fldPath *field.Path, _ *api.Pod, groups []int64) field.ErrorList { | ||||
| 	return ValidateGroupsInRanges(fldPath, s.ranges, groups) | ||||
| } | ||||
| @@ -1,185 +0,0 @@ | ||||
| /* | ||||
| Copyright 2018 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package group | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| ) | ||||
|  | ||||
| func TestMayRunAsOptions(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		ranges []policy.IDRange | ||||
| 		pass   bool | ||||
| 	}{ | ||||
| 		"empty": { | ||||
| 			ranges: []policy.IDRange{}, | ||||
| 		}, | ||||
| 		"ranges": { | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 1}, | ||||
| 			}, | ||||
| 			pass: true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range tests { | ||||
| 		_, err := NewMayRunAs(v.ranges) | ||||
| 		if v.pass && err != nil { | ||||
| 			t.Errorf("error creating strategy for %s: %v", k, err) | ||||
| 		} | ||||
| 		if !v.pass && err == nil { | ||||
| 			t.Errorf("expected error for %s but got none", k) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestMayRunAsValidate(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		ranges         []policy.IDRange | ||||
| 		groups         []int64 | ||||
| 		expectedErrors []string | ||||
| 	}{ | ||||
| 		"empty groups": { | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 3}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"not in range": { | ||||
| 			groups: []int64{5}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 3}, | ||||
| 				{Min: 4, Max: 4}, | ||||
| 			}, | ||||
| 			expectedErrors: []string{"group 5 must be in the ranges: [{1 3} {4 4}]"}, | ||||
| 		}, | ||||
| 		"not in ranges - multiple groups": { | ||||
| 			groups: []int64{5, 10, 2020}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 3}, | ||||
| 				{Min: 15, Max: 70}, | ||||
| 			}, | ||||
| 			expectedErrors: []string{ | ||||
| 				"group 5 must be in the ranges: [{1 3} {15 70}]", | ||||
| 				"group 10 must be in the ranges: [{1 3} {15 70}]", | ||||
| 				"group 2020 must be in the ranges: [{1 3} {15 70}]", | ||||
| 			}, | ||||
| 		}, | ||||
| 		"not in ranges - one of multiple groups does not match": { | ||||
| 			groups: []int64{5, 10, 2020}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 5}, | ||||
| 				{Min: 8, Max: 12}, | ||||
| 				{Min: 15, Max: 70}, | ||||
| 			}, | ||||
| 			expectedErrors: []string{ | ||||
| 				"group 2020 must be in the ranges: [{1 5} {8 12} {15 70}]", | ||||
| 			}, | ||||
| 		}, | ||||
| 		"in range 1": { | ||||
| 			groups: []int64{2}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 3}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"in range boundary min": { | ||||
| 			groups: []int64{1}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 3}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"in range boundary max": { | ||||
| 			groups: []int64{3}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 3}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"singular range": { | ||||
| 			groups: []int64{4}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 4, Max: 4}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"in one of multiple ranges": { | ||||
| 			groups: []int64{4}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 4}, | ||||
| 				{Min: 10, Max: 15}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"multiple groups matches one range": { | ||||
| 			groups: []int64{4, 8, 12}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 20}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"multiple groups match multiple ranges": { | ||||
| 			groups: []int64{4, 8, 12}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 4}, | ||||
| 				{Min: 200, Max: 2000}, | ||||
| 				{Min: 7, Max: 11}, | ||||
| 				{Min: 5, Max: 7}, | ||||
| 				{Min: 17, Max: 53}, | ||||
| 				{Min: 12, Max: 71}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range tests { | ||||
| 		s, err := NewMayRunAs(v.ranges) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("error creating strategy for %s: %v", k, err) | ||||
| 		} | ||||
| 		errs := s.Validate(field.NewPath(""), nil, v.groups) | ||||
| 		if len(v.expectedErrors) != len(errs) { | ||||
| 			// number of expected errors is different from actual, includes cases when we expected errors and they appeared or vice versa | ||||
| 			t.Errorf("number of expected errors for '%s' does not match with errors received:\n"+ | ||||
| 				"expected:\n%s\nbut got:\n%s", | ||||
| 				k, concatenateStrings(v.expectedErrors), concatenateErrors(errs)) | ||||
| 		} else if len(v.expectedErrors) > 0 { | ||||
| 			// check that the errors received match the expectations | ||||
| 			for i, s := range v.expectedErrors { | ||||
| 				if !strings.Contains(errs[i].Error(), s) { | ||||
| 					t.Errorf("expected errors in particular order for '%s':\n%s\nbut got:\n%s", | ||||
| 						k, concatenateStrings(v.expectedErrors), concatenateErrors(errs)) | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func concatenateErrors(errs field.ErrorList) string { | ||||
| 	var errStrings []string | ||||
| 	for _, e := range errs { | ||||
| 		errStrings = append(errStrings, e.Error()) | ||||
| 	} | ||||
| 	return concatenateStrings(errStrings) | ||||
| } | ||||
|  | ||||
| func concatenateStrings(ss []string) string { | ||||
| 	var ret string | ||||
| 	for i, v := range ss { | ||||
| 		ret += fmt.Sprintf("%d: %s\n", i+1, v) | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
| @@ -1,71 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package group | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| // mustRunAs implements the GroupStrategy interface | ||||
| type mustRunAs struct { | ||||
| 	ranges []policy.IDRange | ||||
| } | ||||
|  | ||||
| var _ GroupStrategy = &mustRunAs{} | ||||
|  | ||||
| // NewMustRunAs provides a new MustRunAs strategy based on ranges. | ||||
| func NewMustRunAs(ranges []policy.IDRange) (GroupStrategy, error) { | ||||
| 	if len(ranges) == 0 { | ||||
| 		return nil, fmt.Errorf("ranges must be supplied for MustRunAs") | ||||
| 	} | ||||
| 	return &mustRunAs{ | ||||
| 		ranges: ranges, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // Generate creates the group based on policy rules.  By default this returns the first group of the | ||||
| // first range (min val). | ||||
| func (s *mustRunAs) Generate(_ *api.Pod) ([]int64, error) { | ||||
| 	return []int64{s.ranges[0].Min}, nil | ||||
| } | ||||
|  | ||||
| // Generate a single value to be applied.  This is used for FSGroup.  This strategy will return | ||||
| // the first group of the first range (min val). | ||||
| func (s *mustRunAs) GenerateSingle(_ *api.Pod) (*int64, error) { | ||||
| 	single := new(int64) | ||||
| 	*single = s.ranges[0].Min | ||||
| 	return single, nil | ||||
| } | ||||
|  | ||||
| // Validate ensures that the specified values fall within the range of the strategy. | ||||
| // Groups are passed in here to allow this strategy to support multiple group fields (fsgroup and | ||||
| // supplemental groups). | ||||
| func (s *mustRunAs) Validate(fldPath *field.Path, _ *api.Pod, groups []int64) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
|  | ||||
| 	if len(groups) == 0 && len(s.ranges) > 0 { | ||||
| 		allErrs = append(allErrs, field.Invalid(fldPath, groups, "unable to validate empty groups against required ranges")) | ||||
| 	} | ||||
|  | ||||
| 	allErrs = append(allErrs, ValidateGroupsInRanges(fldPath, s.ranges, groups)...) | ||||
|  | ||||
| 	return allErrs | ||||
| } | ||||
| @@ -1,180 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package group | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| ) | ||||
|  | ||||
| func TestMustRunAsOptions(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		ranges []policy.IDRange | ||||
| 		pass   bool | ||||
| 	}{ | ||||
| 		"empty": { | ||||
| 			ranges: []policy.IDRange{}, | ||||
| 		}, | ||||
| 		"ranges": { | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 1}, | ||||
| 			}, | ||||
| 			pass: true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range tests { | ||||
| 		_, err := NewMustRunAs(v.ranges) | ||||
| 		if v.pass && err != nil { | ||||
| 			t.Errorf("error creating strategy for %s: %v", k, err) | ||||
| 		} | ||||
| 		if !v.pass && err == nil { | ||||
| 			t.Errorf("expected error for %s but got none", k) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGenerate(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		ranges   []policy.IDRange | ||||
| 		expected []int64 | ||||
| 	}{ | ||||
| 		"multi value": { | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 2}, | ||||
| 			}, | ||||
| 			expected: []int64{1}, | ||||
| 		}, | ||||
| 		"single value": { | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 1}, | ||||
| 			}, | ||||
| 			expected: []int64{1}, | ||||
| 		}, | ||||
| 		"multi range": { | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 1}, | ||||
| 				{Min: 2, Max: 500}, | ||||
| 			}, | ||||
| 			expected: []int64{1}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range tests { | ||||
| 		s, err := NewMustRunAs(v.ranges) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("error creating strategy for %s: %v", k, err) | ||||
| 		} | ||||
| 		actual, err := s.Generate(nil) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("unexpected error for %s: %v", k, err) | ||||
| 		} | ||||
| 		if len(actual) != len(v.expected) { | ||||
| 			t.Errorf("unexpected generated values.  Expected %v, got %v", v.expected, actual) | ||||
| 			continue | ||||
| 		} | ||||
| 		if len(actual) > 0 && len(v.expected) > 0 { | ||||
| 			if actual[0] != v.expected[0] { | ||||
| 				t.Errorf("unexpected generated values.  Expected %v, got %v", v.expected, actual) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		single, err := s.GenerateSingle(nil) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("unexpected error for %s: %v", k, err) | ||||
| 		} | ||||
| 		if single == nil { | ||||
| 			t.Errorf("unexpected nil generated value for %s: %v", k, single) | ||||
| 		} | ||||
| 		if *single != v.expected[0] { | ||||
| 			t.Errorf("unexpected generated single value.  Expected %v, got %v", v.expected, actual) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestValidate(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		ranges        []policy.IDRange | ||||
| 		groups        []int64 | ||||
| 		expectedError string | ||||
| 	}{ | ||||
| 		"nil security context": { | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 3}, | ||||
| 			}, | ||||
| 			expectedError: "unable to validate empty groups against required ranges", | ||||
| 		}, | ||||
| 		"empty groups": { | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 3}, | ||||
| 			}, | ||||
| 			expectedError: "unable to validate empty groups against required ranges", | ||||
| 		}, | ||||
| 		"not in range": { | ||||
| 			groups: []int64{5}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 3}, | ||||
| 				{Min: 4, Max: 4}, | ||||
| 			}, | ||||
| 			expectedError: "group 5 must be in the ranges: [{1 3} {4 4}]", | ||||
| 		}, | ||||
| 		"in range 1": { | ||||
| 			groups: []int64{2}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 3}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"in range boundary min": { | ||||
| 			groups: []int64{1}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 3}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"in range boundary max": { | ||||
| 			groups: []int64{3}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 1, Max: 3}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"singular range": { | ||||
| 			groups: []int64{4}, | ||||
| 			ranges: []policy.IDRange{ | ||||
| 				{Min: 4, Max: 4}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range tests { | ||||
| 		s, err := NewMustRunAs(v.ranges) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("error creating strategy for %s: %v", k, err) | ||||
| 		} | ||||
| 		errs := s.Validate(field.NewPath(""), nil, v.groups) | ||||
| 		if v.expectedError == "" && len(errs) > 0 { | ||||
| 			t.Errorf("unexpected errors for %s: %v", k, errs) | ||||
| 		} | ||||
| 		if v.expectedError != "" && len(errs) == 0 { | ||||
| 			t.Errorf("expected errors for %s but got: %v", k, errs) | ||||
| 		} | ||||
| 		if v.expectedError != "" && len(errs) > 0 && !strings.Contains(errs[0].Error(), v.expectedError) { | ||||
| 			t.Errorf("expected error for %s: %v, but got: %v", k, v.expectedError, errs[0]) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,49 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package group | ||||
|  | ||||
| import ( | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| // runAsAny implements the GroupStrategy interface. | ||||
| type runAsAny struct { | ||||
| } | ||||
|  | ||||
| var _ GroupStrategy = &runAsAny{} | ||||
|  | ||||
| // NewRunAsAny provides a new RunAsAny strategy. | ||||
| func NewRunAsAny() (GroupStrategy, error) { | ||||
| 	return &runAsAny{}, nil | ||||
| } | ||||
|  | ||||
| // Generate creates the group based on policy rules.  This strategy returns an empty slice. | ||||
| func (s *runAsAny) Generate(_ *api.Pod) ([]int64, error) { | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| // Generate a single value to be applied.  This is used for FSGroup.  This strategy returns nil. | ||||
| func (s *runAsAny) GenerateSingle(_ *api.Pod) (*int64, error) { | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| // Validate ensures that the specified values fall within the range of the strategy. | ||||
| func (s *runAsAny) Validate(fldPath *field.Path, _ *api.Pod, groups []int64) field.ErrorList { | ||||
| 	return field.ErrorList{} | ||||
|  | ||||
| } | ||||
| @@ -1,62 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package group | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| ) | ||||
|  | ||||
| func TestRunAsAnyGenerate(t *testing.T) { | ||||
| 	s, err := NewRunAsAny() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewRunAsAny %v", err) | ||||
| 	} | ||||
| 	groups, err := s.Generate(nil) | ||||
| 	if len(groups) > 0 { | ||||
| 		t.Errorf("expected empty but got %v", groups) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error generating groups: %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRunAsAnyGenerateSingle(t *testing.T) { | ||||
| 	s, err := NewRunAsAny() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewRunAsAny %v", err) | ||||
| 	} | ||||
| 	group, err := s.GenerateSingle(nil) | ||||
| 	if group != nil { | ||||
| 		t.Errorf("expected empty but got %v", group) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error generating groups: %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRunAsAnyValidate(t *testing.T) { | ||||
| 	s, err := NewRunAsAny() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewRunAsAny %v", err) | ||||
| 	} | ||||
| 	errs := s.Validate(field.NewPath(""), nil, nil) | ||||
| 	if len(errs) != 0 { | ||||
| 		t.Errorf("unexpected errors: %v", errs) | ||||
| 	} | ||||
| } | ||||
| @@ -1,35 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package group | ||||
|  | ||||
| import ( | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| // GroupStrategy defines the interface for all group constraint strategies. | ||||
| type GroupStrategy interface { | ||||
| 	// Generate creates the group based on policy rules.  The underlying implementation can | ||||
| 	// decide whether it will return a full range of values or a subset of values from the | ||||
| 	// configured ranges. | ||||
| 	Generate(pod *api.Pod) ([]int64, error) | ||||
| 	// Generate a single value to be applied.  The underlying implementation decides which | ||||
| 	// value to return if configured with multiple ranges.  This is used for FSGroup. | ||||
| 	GenerateSingle(pod *api.Pod) (*int64, error) | ||||
| 	// Validate ensures that the specified values fall within the range of the strategy. | ||||
| 	Validate(fldPath *field.Path, pod *api.Pod, groups []int64) field.ErrorList | ||||
| } | ||||
| @@ -1,459 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package podsecuritypolicy | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	utilfeature "k8s.io/apiserver/pkg/util/feature" | ||||
| 	podutil "k8s.io/kubernetes/pkg/api/pod" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| 	"k8s.io/kubernetes/pkg/apis/core/pods" | ||||
| 	"k8s.io/kubernetes/pkg/features" | ||||
| 	psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" | ||||
| 	"k8s.io/kubernetes/pkg/securitycontext" | ||||
| ) | ||||
|  | ||||
| // simpleProvider is the default implementation of Provider. | ||||
| type simpleProvider struct { | ||||
| 	psp        *policy.PodSecurityPolicy | ||||
| 	strategies *ProviderStrategies | ||||
| } | ||||
|  | ||||
| // ensure we implement the interface correctly. | ||||
| var _ Provider = &simpleProvider{} | ||||
|  | ||||
| // NewSimpleProvider creates a new Provider instance. | ||||
| func NewSimpleProvider(psp *policy.PodSecurityPolicy, namespace string, strategyFactory StrategyFactory) (Provider, error) { | ||||
| 	if psp == nil { | ||||
| 		return nil, fmt.Errorf("NewSimpleProvider requires a PodSecurityPolicy") | ||||
| 	} | ||||
| 	if strategyFactory == nil { | ||||
| 		return nil, fmt.Errorf("NewSimpleProvider requires a StrategyFactory") | ||||
| 	} | ||||
|  | ||||
| 	strategies, err := strategyFactory.CreateStrategies(psp, namespace) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &simpleProvider{ | ||||
| 		psp:        psp, | ||||
| 		strategies: strategies, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // MutatePod sets the default values of the required but not filled fields. | ||||
| // Validation should be used after the context is defaulted to ensure it | ||||
| // complies with the required restrictions. | ||||
| func (s *simpleProvider) MutatePod(pod *api.Pod) error { | ||||
| 	sc := securitycontext.NewPodSecurityContextMutator(pod.Spec.SecurityContext) | ||||
|  | ||||
| 	if sc.SupplementalGroups() == nil { | ||||
| 		supGroups, err := s.strategies.SupplementalGroupStrategy.Generate(pod) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		sc.SetSupplementalGroups(supGroups) | ||||
| 	} | ||||
|  | ||||
| 	if sc.FSGroup() == nil { | ||||
| 		fsGroup, err := s.strategies.FSGroupStrategy.GenerateSingle(pod) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		sc.SetFSGroup(fsGroup) | ||||
| 	} | ||||
|  | ||||
| 	if sc.SELinuxOptions() == nil { | ||||
| 		seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, nil) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		sc.SetSELinuxOptions(seLinux) | ||||
| 	} | ||||
|  | ||||
| 	// This is only generated on the pod level.  Containers inherit the pod's profile.  If the | ||||
| 	// container has a specific profile set then it will be caught in the validation step. | ||||
| 	seccompProfile, err := s.strategies.SeccompStrategy.Generate(pod.Annotations, pod) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if seccompProfile != "" { | ||||
| 		if pod.Annotations == nil { | ||||
| 			pod.Annotations = map[string]string{} | ||||
| 		} | ||||
| 		pod.Annotations[api.SeccompPodAnnotationKey] = seccompProfile | ||||
| 	} | ||||
|  | ||||
| 	pod.Spec.SecurityContext = sc.PodSecurityContext() | ||||
|  | ||||
| 	if s.psp.Spec.RuntimeClass != nil && pod.Spec.RuntimeClassName == nil { | ||||
| 		pod.Spec.RuntimeClassName = s.psp.Spec.RuntimeClass.DefaultRuntimeClassName | ||||
| 	} | ||||
|  | ||||
| 	var retErr error | ||||
| 	podutil.VisitContainers(&pod.Spec, podutil.AllContainers, func(c *api.Container, containerType podutil.ContainerType) bool { | ||||
| 		retErr = s.mutateContainer(pod, c) | ||||
| 		if retErr != nil { | ||||
| 			return false | ||||
| 		} | ||||
| 		return true | ||||
| 	}) | ||||
|  | ||||
| 	return retErr | ||||
| } | ||||
|  | ||||
| // mutateContainer sets the default values of the required but not filled fields. | ||||
| // It modifies the SecurityContext of the container and annotations of the pod. Validation should | ||||
| // be used after the context is defaulted to ensure it complies with the required restrictions. | ||||
| func (s *simpleProvider) mutateContainer(pod *api.Pod, container *api.Container) error { | ||||
| 	sc := securitycontext.NewEffectiveContainerSecurityContextMutator( | ||||
| 		securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext), | ||||
| 		securitycontext.NewContainerSecurityContextMutator(container.SecurityContext), | ||||
| 	) | ||||
|  | ||||
| 	if sc.RunAsUser() == nil { | ||||
| 		uid, err := s.strategies.RunAsUserStrategy.Generate(pod, container) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		sc.SetRunAsUser(uid) | ||||
| 	} | ||||
|  | ||||
| 	if sc.RunAsGroup() == nil { | ||||
| 		gid, err := s.strategies.RunAsGroupStrategy.GenerateSingle(pod) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		sc.SetRunAsGroup(gid) | ||||
| 	} | ||||
|  | ||||
| 	if sc.SELinuxOptions() == nil { | ||||
| 		seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, container) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		sc.SetSELinuxOptions(seLinux) | ||||
| 	} | ||||
|  | ||||
| 	annotations, err := s.strategies.AppArmorStrategy.Generate(pod.Annotations, container) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// if we're using the non-root strategy set the marker that this container should not be | ||||
| 	// run as root which will signal to the kubelet to do a final check either on the runAsUser | ||||
| 	// or, if runAsUser is not set, the image UID will be checked. | ||||
| 	if sc.RunAsNonRoot() == nil && sc.RunAsUser() == nil && s.psp.Spec.RunAsUser.Rule == policy.RunAsUserStrategyMustRunAsNonRoot { | ||||
| 		nonRoot := true | ||||
| 		sc.SetRunAsNonRoot(&nonRoot) | ||||
| 	} | ||||
|  | ||||
| 	caps, err := s.strategies.CapabilitiesStrategy.Generate(pod, container) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	sc.SetCapabilities(caps) | ||||
|  | ||||
| 	// if the PSP requires a read only root filesystem and the container has not made a specific | ||||
| 	// request then default ReadOnlyRootFilesystem to true. | ||||
| 	if s.psp.Spec.ReadOnlyRootFilesystem && sc.ReadOnlyRootFilesystem() == nil { | ||||
| 		readOnlyRootFS := true | ||||
| 		sc.SetReadOnlyRootFilesystem(&readOnlyRootFS) | ||||
| 	} | ||||
|  | ||||
| 	// if the PSP sets DefaultAllowPrivilegeEscalation and the container security context | ||||
| 	// allowPrivilegeEscalation is not set, then default to that set by the PSP. | ||||
| 	if s.psp.Spec.DefaultAllowPrivilegeEscalation != nil && sc.AllowPrivilegeEscalation() == nil { | ||||
| 		sc.SetAllowPrivilegeEscalation(s.psp.Spec.DefaultAllowPrivilegeEscalation) | ||||
| 	} | ||||
|  | ||||
| 	// if the PSP sets psp.AllowPrivilegeEscalation to false, set that as the default | ||||
| 	if !*s.psp.Spec.AllowPrivilegeEscalation && sc.AllowPrivilegeEscalation() == nil { | ||||
| 		sc.SetAllowPrivilegeEscalation(s.psp.Spec.AllowPrivilegeEscalation) | ||||
| 	} | ||||
|  | ||||
| 	pod.Annotations = annotations | ||||
| 	container.SecurityContext = sc.ContainerSecurityContext() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ValidatePod ensure a pod is in compliance with the given constraints. | ||||
| func (s *simpleProvider) ValidatePod(pod *api.Pod) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
|  | ||||
| 	sc := securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext) | ||||
| 	scPath := field.NewPath("spec", "securityContext") | ||||
|  | ||||
| 	var fsGroups []int64 | ||||
| 	if fsGroup := sc.FSGroup(); fsGroup != nil { | ||||
| 		fsGroups = []int64{*fsGroup} | ||||
| 	} | ||||
| 	allErrs = append(allErrs, s.strategies.FSGroupStrategy.Validate(scPath.Child("fsGroup"), pod, fsGroups)...) | ||||
| 	allErrs = append(allErrs, s.strategies.SupplementalGroupStrategy.Validate(scPath.Child("supplementalGroups"), pod, sc.SupplementalGroups())...) | ||||
| 	allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidatePod(pod)...) | ||||
|  | ||||
| 	allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(scPath.Child("seLinuxOptions"), pod, nil, sc.SELinuxOptions())...) | ||||
|  | ||||
| 	if !s.psp.Spec.HostNetwork && sc.HostNetwork() { | ||||
| 		allErrs = append(allErrs, field.Invalid(scPath.Child("hostNetwork"), sc.HostNetwork(), "Host network is not allowed to be used")) | ||||
| 	} | ||||
|  | ||||
| 	if !s.psp.Spec.HostPID && sc.HostPID() { | ||||
| 		allErrs = append(allErrs, field.Invalid(scPath.Child("hostPID"), sc.HostPID(), "Host PID is not allowed to be used")) | ||||
| 	} | ||||
|  | ||||
| 	if !s.psp.Spec.HostIPC && sc.HostIPC() { | ||||
| 		allErrs = append(allErrs, field.Invalid(scPath.Child("hostIPC"), sc.HostIPC(), "Host IPC is not allowed to be used")) | ||||
| 	} | ||||
|  | ||||
| 	allErrs = append(allErrs, s.strategies.SysctlsStrategy.Validate(pod)...) | ||||
|  | ||||
| 	allErrs = append(allErrs, s.validatePodVolumes(pod)...) | ||||
|  | ||||
| 	if s.psp.Spec.RuntimeClass != nil { | ||||
| 		allErrs = append(allErrs, validateRuntimeClassName(pod.Spec.RuntimeClassName, s.psp.Spec.RuntimeClass.AllowedRuntimeClassNames)...) | ||||
| 	} | ||||
|  | ||||
| 	pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, p *field.Path) bool { | ||||
| 		allErrs = append(allErrs, s.validateContainer(pod, c, p)...) | ||||
| 		return true | ||||
| 	}) | ||||
|  | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| func (s *simpleProvider) validatePodVolumes(pod *api.Pod) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
|  | ||||
| 	if len(pod.Spec.Volumes) > 0 { | ||||
| 		allowsAllVolumeTypes := psputil.PSPAllowsAllVolumes(s.psp) | ||||
| 		allowedVolumes := psputil.FSTypeToStringSet(s.psp.Spec.Volumes) | ||||
| 		for i, v := range pod.Spec.Volumes { | ||||
| 			fsType, err := psputil.GetVolumeFSType(v) | ||||
| 			if err != nil { | ||||
| 				allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "volumes").Index(i), string(fsType), err.Error())) | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			if !allowsAllVolumeTypes && !allowsVolumeType(allowedVolumes, fsType, v) { | ||||
| 				allErrs = append(allErrs, field.Invalid( | ||||
| 					field.NewPath("spec", "volumes").Index(i), string(fsType), | ||||
| 					fmt.Sprintf("%s volumes are not allowed to be used", string(fsType)))) | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			switch fsType { | ||||
| 			case policy.HostPath: | ||||
| 				allows, mustBeReadOnly := psputil.AllowsHostVolumePath(s.psp, v.HostPath.Path) | ||||
| 				if !allows { | ||||
| 					allErrs = append(allErrs, field.Invalid( | ||||
| 						field.NewPath("spec", "volumes").Index(i).Child("hostPath", "pathPrefix"), v.HostPath.Path, | ||||
| 						fmt.Sprintf("is not allowed to be used"))) | ||||
| 				} else if mustBeReadOnly { | ||||
| 					// Ensure all the VolumeMounts that use this volume are read-only | ||||
| 					pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, p *field.Path) bool { | ||||
| 						for i, cv := range c.VolumeMounts { | ||||
| 							if cv.Name == v.Name && !cv.ReadOnly { | ||||
| 								allErrs = append(allErrs, field.Invalid(p.Child("volumeMounts").Index(i).Child("readOnly"), cv.ReadOnly, "must be read-only")) | ||||
| 							} | ||||
| 						} | ||||
| 						return true | ||||
| 					}) | ||||
| 				} | ||||
|  | ||||
| 			case policy.FlexVolume: | ||||
| 				if len(s.psp.Spec.AllowedFlexVolumes) > 0 { | ||||
| 					found := false | ||||
| 					driver := v.FlexVolume.Driver | ||||
| 					for _, allowedFlexVolume := range s.psp.Spec.AllowedFlexVolumes { | ||||
| 						if driver == allowedFlexVolume.Driver { | ||||
| 							found = true | ||||
| 							break | ||||
| 						} | ||||
| 					} | ||||
| 					if !found { | ||||
| 						allErrs = append(allErrs, | ||||
| 							field.Invalid(field.NewPath("spec", "volumes").Index(i).Child("driver"), driver, | ||||
| 								"Flexvolume driver is not allowed to be used")) | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 			case policy.CSI: | ||||
| 				if utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) { | ||||
| 					if len(s.psp.Spec.AllowedCSIDrivers) > 0 { | ||||
| 						found := false | ||||
| 						driver := v.CSI.Driver | ||||
| 						for _, allowedCSIDriver := range s.psp.Spec.AllowedCSIDrivers { | ||||
| 							if driver == allowedCSIDriver.Name { | ||||
| 								found = true | ||||
| 								break | ||||
| 							} | ||||
| 						} | ||||
| 						if !found { | ||||
| 							allErrs = append(allErrs, | ||||
| 								field.Invalid(field.NewPath("spec", "volumes").Index(i).Child("csi", "driver"), driver, | ||||
| 									"Inline CSI driver is not allowed to be used")) | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| // Ensure a container's SecurityContext is in compliance with the given constraints | ||||
| func (s *simpleProvider) validateContainer(pod *api.Pod, container *api.Container, containerPath *field.Path) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
|  | ||||
| 	podSC := securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext) | ||||
| 	sc := securitycontext.NewEffectiveContainerSecurityContextAccessor(podSC, securitycontext.NewContainerSecurityContextMutator(container.SecurityContext)) | ||||
|  | ||||
| 	scPath := containerPath.Child("securityContext") | ||||
| 	allErrs = append(allErrs, s.strategies.RunAsUserStrategy.Validate(scPath, pod, container, sc.RunAsNonRoot(), sc.RunAsUser())...) | ||||
| 	var runAsGroups []int64 | ||||
| 	if sc.RunAsGroup() != nil { | ||||
| 		runAsGroups = []int64{*sc.RunAsGroup()} | ||||
| 	} | ||||
| 	allErrs = append(allErrs, s.strategies.RunAsGroupStrategy.Validate(scPath, pod, runAsGroups)...) | ||||
|  | ||||
| 	allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(scPath.Child("seLinuxOptions"), pod, container, sc.SELinuxOptions())...) | ||||
| 	allErrs = append(allErrs, s.strategies.AppArmorStrategy.Validate(pod, container)...) | ||||
| 	allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidateContainer(pod, container)...) | ||||
|  | ||||
| 	privileged := sc.Privileged() | ||||
| 	if !s.psp.Spec.Privileged && privileged != nil && *privileged { | ||||
| 		allErrs = append(allErrs, field.Invalid(scPath.Child("privileged"), *privileged, "Privileged containers are not allowed")) | ||||
| 	} | ||||
|  | ||||
| 	procMount := sc.ProcMount() | ||||
| 	allowedProcMounts := s.psp.Spec.AllowedProcMountTypes | ||||
| 	if len(allowedProcMounts) == 0 { | ||||
| 		allowedProcMounts = []corev1.ProcMountType{corev1.DefaultProcMount} | ||||
| 	} | ||||
| 	foundProcMountType := false | ||||
| 	for _, pm := range allowedProcMounts { | ||||
| 		if string(pm) == string(procMount) { | ||||
| 			foundProcMountType = true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if !foundProcMountType { | ||||
| 		allErrs = append(allErrs, field.Invalid(scPath.Child("procMount"), procMount, "ProcMountType is not allowed")) | ||||
| 	} | ||||
|  | ||||
| 	allErrs = append(allErrs, s.strategies.CapabilitiesStrategy.Validate(scPath.Child("capabilities"), pod, container, sc.Capabilities())...) | ||||
|  | ||||
| 	allErrs = append(allErrs, s.hasInvalidHostPort(container, containerPath)...) | ||||
|  | ||||
| 	if s.psp.Spec.ReadOnlyRootFilesystem { | ||||
| 		readOnly := sc.ReadOnlyRootFilesystem() | ||||
| 		if readOnly == nil { | ||||
| 			allErrs = append(allErrs, field.Invalid(scPath.Child("readOnlyRootFilesystem"), readOnly, "ReadOnlyRootFilesystem may not be nil and must be set to true")) | ||||
| 		} else if !*readOnly { | ||||
| 			allErrs = append(allErrs, field.Invalid(scPath.Child("readOnlyRootFilesystem"), *readOnly, "ReadOnlyRootFilesystem must be set to true")) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	allowEscalation := sc.AllowPrivilegeEscalation() | ||||
| 	if !*s.psp.Spec.AllowPrivilegeEscalation && (allowEscalation == nil || *allowEscalation) { | ||||
| 		allErrs = append(allErrs, field.Invalid(scPath.Child("allowPrivilegeEscalation"), allowEscalation, "Allowing privilege escalation for containers is not allowed")) | ||||
| 	} | ||||
|  | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| // hasInvalidHostPort checks whether the port definitions on the container fall outside of the ranges allowed by the PSP. | ||||
| func (s *simpleProvider) hasInvalidHostPort(container *api.Container, fldPath *field.Path) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
| 	for _, cp := range container.Ports { | ||||
| 		if cp.HostPort > 0 && !s.isValidHostPort(cp.HostPort) { | ||||
| 			detail := fmt.Sprintf("Host port %d is not allowed to be used. Allowed ports: [%s]", cp.HostPort, hostPortRangesToString(s.psp.Spec.HostPorts)) | ||||
| 			allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPort"), cp.HostPort, detail)) | ||||
| 		} | ||||
| 	} | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| // isValidHostPort returns true if the port falls in any range allowed by the PSP. | ||||
| func (s *simpleProvider) isValidHostPort(port int32) bool { | ||||
| 	for _, hostPortRange := range s.psp.Spec.HostPorts { | ||||
| 		if port >= hostPortRange.Min && port <= hostPortRange.Max { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // Get the name of the PSP that this provider was initialized with. | ||||
| func (s *simpleProvider) GetPSPName() string { | ||||
| 	return s.psp.Name | ||||
| } | ||||
|  | ||||
| func hostPortRangesToString(ranges []policy.HostPortRange) string { | ||||
| 	formattedString := "" | ||||
| 	if ranges != nil { | ||||
| 		strRanges := []string{} | ||||
| 		for _, r := range ranges { | ||||
| 			if r.Min == r.Max { | ||||
| 				strRanges = append(strRanges, fmt.Sprintf("%d", r.Min)) | ||||
| 			} else { | ||||
| 				strRanges = append(strRanges, fmt.Sprintf("%d-%d", r.Min, r.Max)) | ||||
| 			} | ||||
| 		} | ||||
| 		formattedString = strings.Join(strRanges, ",") | ||||
| 	} | ||||
| 	return formattedString | ||||
| } | ||||
|  | ||||
| // validates that the actual RuntimeClassName is contained in the list of valid names. | ||||
| func validateRuntimeClassName(actual *string, validNames []string) field.ErrorList { | ||||
| 	if actual == nil { | ||||
| 		return nil // An unset RuntimeClassName is always allowed. | ||||
| 	} | ||||
|  | ||||
| 	for _, valid := range validNames { | ||||
| 		if valid == policy.AllowAllRuntimeClassNames { | ||||
| 			return nil | ||||
| 		} | ||||
| 		if *actual == valid { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	return field.ErrorList{field.Invalid(field.NewPath("spec", "runtimeClassName"), *actual, "")} | ||||
| } | ||||
|  | ||||
| func allowsVolumeType(allowedVolumes sets.String, fsType policy.FSType, volume api.Volume) bool { | ||||
| 	if allowedVolumes.Has(string(fsType)) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// if secret volume is allowed, all the projected volume sources that projected service account token volumes expose are allowed, regardless of psp. | ||||
| 	if allowedVolumes.Has(string(policy.Secret)) && fsType == policy.Projected && psputil.IsOnlyServiceAccountTokenSources(volume.VolumeSource.Projected) { | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,178 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package seccomp | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	v1 "k8s.io/api/core/v1" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	podutil "k8s.io/kubernetes/pkg/api/pod" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// AllowAny is the wildcard used to allow any profile. | ||||
| 	AllowAny = "*" | ||||
| 	// DefaultProfileAnnotationKey specifies the default seccomp profile. | ||||
| 	DefaultProfileAnnotationKey = "seccomp.security.alpha.kubernetes.io/defaultProfileName" | ||||
| 	// AllowedProfilesAnnotationKey specifies the allowed seccomp profiles. | ||||
| 	AllowedProfilesAnnotationKey = "seccomp.security.alpha.kubernetes.io/allowedProfileNames" | ||||
| ) | ||||
|  | ||||
| // Strategy defines the interface for all seccomp constraint strategies. | ||||
| type Strategy interface { | ||||
| 	// Generate returns a profile based on constraint rules. | ||||
| 	Generate(annotations map[string]string, pod *api.Pod) (string, error) | ||||
| 	// Validate ensures that the specified values fall within the range of the strategy. | ||||
| 	ValidatePod(pod *api.Pod) field.ErrorList | ||||
| 	// Validate ensures that the specified values fall within the range of the strategy. | ||||
| 	ValidateContainer(pod *api.Pod, container *api.Container) field.ErrorList | ||||
| } | ||||
|  | ||||
| type strategy struct { | ||||
| 	defaultProfile  string | ||||
| 	allowedProfiles map[string]bool | ||||
| 	// For printing error messages (preserves order). | ||||
| 	allowedProfilesString string | ||||
| 	// does the strategy allow any profile (wildcard) | ||||
| 	allowAnyProfile bool | ||||
| } | ||||
|  | ||||
| var _ Strategy = &strategy{} | ||||
|  | ||||
| // NewStrategy creates a new strategy that enforces seccomp profile constraints. | ||||
| func NewStrategy(pspAnnotations map[string]string) Strategy { | ||||
| 	var allowedProfiles map[string]bool | ||||
| 	allowAnyProfile := false | ||||
| 	if allowed, ok := pspAnnotations[AllowedProfilesAnnotationKey]; ok { | ||||
| 		profiles := strings.Split(allowed, ",") | ||||
| 		allowedProfiles = make(map[string]bool, len(profiles)) | ||||
| 		for _, p := range profiles { | ||||
| 			if p == AllowAny { | ||||
| 				allowAnyProfile = true | ||||
| 				continue | ||||
| 			} | ||||
| 			// With the graduation of seccomp to GA we automatically convert | ||||
| 			// the deprecated seccomp profile annotation `docker/default` to | ||||
| 			// `runtime/default`. This means that we now have to automatically | ||||
| 			// allow `runtime/default` if a user specifies `docker/default` and | ||||
| 			// vice versa in a PSP. | ||||
| 			if p == v1.DeprecatedSeccompProfileDockerDefault || p == v1.SeccompProfileRuntimeDefault { | ||||
| 				allowedProfiles[v1.SeccompProfileRuntimeDefault] = true | ||||
| 				allowedProfiles[v1.DeprecatedSeccompProfileDockerDefault] = true | ||||
| 			} | ||||
| 			allowedProfiles[p] = true | ||||
| 		} | ||||
| 	} | ||||
| 	return &strategy{ | ||||
| 		defaultProfile:        pspAnnotations[DefaultProfileAnnotationKey], | ||||
| 		allowedProfiles:       allowedProfiles, | ||||
| 		allowedProfilesString: pspAnnotations[AllowedProfilesAnnotationKey], | ||||
| 		allowAnyProfile:       allowAnyProfile, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Generate returns a profile based on constraint rules. | ||||
| func (s *strategy) Generate(annotations map[string]string, pod *api.Pod) (string, error) { | ||||
| 	if annotations[api.SeccompPodAnnotationKey] != "" { | ||||
| 		// Profile already set, nothing to do. | ||||
| 		return annotations[api.SeccompPodAnnotationKey], nil | ||||
| 	} | ||||
| 	if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SeccompProfile != nil { | ||||
| 		// Profile field already set, translate to annotation | ||||
| 		return podutil.SeccompAnnotationForField(pod.Spec.SecurityContext.SeccompProfile), nil | ||||
| 	} | ||||
| 	return s.defaultProfile, nil | ||||
| } | ||||
|  | ||||
| // ValidatePod ensures that the specified values on the pod fall within the range | ||||
| // of the strategy. | ||||
| func (s *strategy) ValidatePod(pod *api.Pod) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
| 	podSpecFieldPath := field.NewPath("pod", "metadata", "annotations").Key(api.SeccompPodAnnotationKey) | ||||
| 	podProfile := pod.Annotations[api.SeccompPodAnnotationKey] | ||||
| 	// if the annotation is not set, see if the field is set and derive the corresponding annotation value | ||||
| 	if len(podProfile) == 0 && pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SeccompProfile != nil { | ||||
| 		podProfile = podutil.SeccompAnnotationForField(pod.Spec.SecurityContext.SeccompProfile) | ||||
| 	} | ||||
|  | ||||
| 	if !s.allowAnyProfile && len(s.allowedProfiles) == 0 && podProfile != "" { | ||||
| 		allErrs = append(allErrs, field.Forbidden(podSpecFieldPath, "seccomp may not be set")) | ||||
| 		return allErrs | ||||
| 	} | ||||
|  | ||||
| 	if !s.profileAllowed(podProfile) { | ||||
| 		msg := fmt.Sprintf("%s is not an allowed seccomp profile. Valid values are %v", podProfile, s.allowedProfilesString) | ||||
| 		allErrs = append(allErrs, field.Forbidden(podSpecFieldPath, msg)) | ||||
| 	} | ||||
|  | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| // ValidateContainer ensures that the specified values on the container fall within | ||||
| // the range of the strategy. | ||||
| func (s *strategy) ValidateContainer(pod *api.Pod, container *api.Container) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
| 	fieldPath := field.NewPath("pod", "metadata", "annotations").Key(api.SeccompContainerAnnotationKeyPrefix + container.Name) | ||||
| 	containerProfile := profileForContainer(pod, container) | ||||
|  | ||||
| 	if !s.allowAnyProfile && len(s.allowedProfiles) == 0 && containerProfile != "" { | ||||
| 		allErrs = append(allErrs, field.Forbidden(fieldPath, "seccomp may not be set")) | ||||
| 		return allErrs | ||||
| 	} | ||||
|  | ||||
| 	if !s.profileAllowed(containerProfile) { | ||||
| 		msg := fmt.Sprintf("%s is not an allowed seccomp profile. Valid values are %v", containerProfile, s.allowedProfilesString) | ||||
| 		allErrs = append(allErrs, field.Forbidden(fieldPath, msg)) | ||||
| 	} | ||||
|  | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| // profileAllowed checks if profile is in allowedProfiles or if allowedProfiles | ||||
| // contains the wildcard. | ||||
| func (s *strategy) profileAllowed(profile string) bool { | ||||
| 	// for backwards compatibility and PSPs without a defined list of allowed profiles. | ||||
| 	// If a PSP does not have allowedProfiles set then we should allow an empty profile. | ||||
| 	// This will mean that the runtime default is used. | ||||
| 	if len(s.allowedProfiles) == 0 && profile == "" { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return s.allowAnyProfile || s.allowedProfiles[profile] | ||||
| } | ||||
|  | ||||
| // profileForContainer returns the container profile if set, otherwise the pod profile. | ||||
| func profileForContainer(pod *api.Pod, container *api.Container) string { | ||||
| 	if container.SecurityContext != nil && container.SecurityContext.SeccompProfile != nil { | ||||
| 		// derive the annotation value from the container field | ||||
| 		return podutil.SeccompAnnotationForField(container.SecurityContext.SeccompProfile) | ||||
| 	} | ||||
| 	containerProfile, ok := pod.Annotations[api.SeccompContainerAnnotationKeyPrefix+container.Name] | ||||
| 	if ok { | ||||
| 		// return the existing container annotation | ||||
| 		return containerProfile | ||||
| 	} | ||||
| 	if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SeccompProfile != nil { | ||||
| 		// derive the annotation value from the pod field | ||||
| 		return podutil.SeccompAnnotationForField(pod.Spec.SecurityContext.SeccompProfile) | ||||
| 	} | ||||
| 	// return the existing pod annotation | ||||
| 	return pod.Annotations[api.SeccompPodAnnotationKey] | ||||
| } | ||||
| @@ -1,419 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package seccomp | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	v1 "k8s.io/api/core/v1" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	withoutSeccomp    = map[string]string{"foo": "bar"} | ||||
| 	allowAnyNoDefault = map[string]string{ | ||||
| 		AllowedProfilesAnnotationKey: "*", | ||||
| 	} | ||||
| 	allowAnyDefault = map[string]string{ | ||||
| 		AllowedProfilesAnnotationKey: "*", | ||||
| 		DefaultProfileAnnotationKey:  "foo", | ||||
| 	} | ||||
| 	allowAnyAndSpecificDefault = map[string]string{ | ||||
| 		AllowedProfilesAnnotationKey: "*,bar", | ||||
| 		DefaultProfileAnnotationKey:  "foo", | ||||
| 	} | ||||
| 	allowSpecific = map[string]string{ | ||||
| 		AllowedProfilesAnnotationKey: "foo", | ||||
| 	} | ||||
| 	allowSpecificLocalhost = map[string]string{ | ||||
| 		AllowedProfilesAnnotationKey: v1.SeccompLocalhostProfileNamePrefix + "foo", | ||||
| 	} | ||||
| 	allowSpecificDockerDefault = map[string]string{ | ||||
| 		AllowedProfilesAnnotationKey: v1.DeprecatedSeccompProfileDockerDefault, | ||||
| 	} | ||||
| 	allowSpecificRuntimeDefault = map[string]string{ | ||||
| 		AllowedProfilesAnnotationKey: v1.SeccompProfileRuntimeDefault, | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func TestNewStrategy(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		annotations                   map[string]string | ||||
| 		expectedAllowedProfilesString string | ||||
| 		expectedAllowAny              bool | ||||
| 		expectedAllowedProfiles       map[string]bool | ||||
| 		expectedDefaultProfile        string | ||||
| 	}{ | ||||
| 		"no seccomp": { | ||||
| 			annotations:                   withoutSeccomp, | ||||
| 			expectedAllowAny:              false, | ||||
| 			expectedAllowedProfilesString: "", | ||||
| 			expectedAllowedProfiles:       nil, | ||||
| 			expectedDefaultProfile:        "", | ||||
| 		}, | ||||
| 		"allow any, no default": { | ||||
| 			annotations:                   allowAnyNoDefault, | ||||
| 			expectedAllowAny:              true, | ||||
| 			expectedAllowedProfilesString: "*", | ||||
| 			expectedAllowedProfiles:       map[string]bool{}, | ||||
| 			expectedDefaultProfile:        "", | ||||
| 		}, | ||||
| 		"allow any, default": { | ||||
| 			annotations:                   allowAnyDefault, | ||||
| 			expectedAllowAny:              true, | ||||
| 			expectedAllowedProfilesString: "*", | ||||
| 			expectedAllowedProfiles:       map[string]bool{}, | ||||
| 			expectedDefaultProfile:        "foo", | ||||
| 		}, | ||||
| 		"allow any and specific, default": { | ||||
| 			annotations:                   allowAnyAndSpecificDefault, | ||||
| 			expectedAllowAny:              true, | ||||
| 			expectedAllowedProfilesString: "*,bar", | ||||
| 			expectedAllowedProfiles: map[string]bool{ | ||||
| 				"bar": true, | ||||
| 			}, | ||||
| 			expectedDefaultProfile: "foo", | ||||
| 		}, | ||||
| 	} | ||||
| 	for k, v := range tests { | ||||
| 		s := NewStrategy(v.annotations) | ||||
| 		internalStrat, _ := s.(*strategy) | ||||
|  | ||||
| 		if internalStrat.allowAnyProfile != v.expectedAllowAny { | ||||
| 			t.Errorf("%s expected allowAnyProfile to be %t but found %t", k, v.expectedAllowAny, internalStrat.allowAnyProfile) | ||||
| 		} | ||||
| 		if internalStrat.allowedProfilesString != v.expectedAllowedProfilesString { | ||||
| 			t.Errorf("%s expected allowedProfilesString to be %s but found %s", k, v.expectedAllowedProfilesString, internalStrat.allowedProfilesString) | ||||
| 		} | ||||
| 		if internalStrat.defaultProfile != v.expectedDefaultProfile { | ||||
| 			t.Errorf("%s expected defaultProfile to be %s but found %s", k, v.expectedDefaultProfile, internalStrat.defaultProfile) | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(v.expectedAllowedProfiles, internalStrat.allowedProfiles) { | ||||
| 			t.Errorf("%s expected expectedAllowedProfiles to be %#v but found %#v", k, v.expectedAllowedProfiles, internalStrat.allowedProfiles) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGenerate(t *testing.T) { | ||||
| 	bar := "bar" | ||||
| 	tests := map[string]struct { | ||||
| 		pspAnnotations  map[string]string | ||||
| 		podAnnotations  map[string]string | ||||
| 		seccompProfile  *api.SeccompProfile | ||||
| 		expectedProfile string | ||||
| 	}{ | ||||
| 		"no seccomp, no pod annotations": { | ||||
| 			pspAnnotations:  withoutSeccomp, | ||||
| 			podAnnotations:  nil, | ||||
| 			expectedProfile: "", | ||||
| 		}, | ||||
| 		"no seccomp, pod annotations": { | ||||
| 			pspAnnotations: withoutSeccomp, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompPodAnnotationKey: "foo", | ||||
| 			}, | ||||
| 			expectedProfile: "foo", | ||||
| 		}, | ||||
| 		"seccomp with no default, no pod annotations": { | ||||
| 			pspAnnotations:  allowAnyNoDefault, | ||||
| 			podAnnotations:  nil, | ||||
| 			expectedProfile: "", | ||||
| 		}, | ||||
| 		"seccomp with no default, pod annotations": { | ||||
| 			pspAnnotations: allowAnyNoDefault, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompPodAnnotationKey: "foo", | ||||
| 			}, | ||||
| 			expectedProfile: "foo", | ||||
| 		}, | ||||
| 		"seccomp with default, no pod annotations": { | ||||
| 			pspAnnotations:  allowAnyDefault, | ||||
| 			podAnnotations:  nil, | ||||
| 			expectedProfile: "foo", | ||||
| 		}, | ||||
| 		"seccomp with default, pod annotations": { | ||||
| 			pspAnnotations: allowAnyDefault, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompPodAnnotationKey: "bar", | ||||
| 			}, | ||||
| 			expectedProfile: "bar", | ||||
| 		}, | ||||
| 		"seccomp with default, pod field": { | ||||
| 			pspAnnotations: allowAnyDefault, | ||||
| 			seccompProfile: &api.SeccompProfile{ | ||||
| 				Type:             api.SeccompProfileTypeLocalhost, | ||||
| 				LocalhostProfile: &bar, | ||||
| 			}, | ||||
| 			expectedProfile: "localhost/bar", | ||||
| 		}, | ||||
| 	} | ||||
| 	for k, v := range tests { | ||||
| 		s := NewStrategy(v.pspAnnotations) | ||||
| 		actual, err := s.Generate(v.podAnnotations, &api.Pod{ | ||||
| 			Spec: api.PodSpec{ | ||||
| 				SecurityContext: &api.PodSecurityContext{ | ||||
| 					SeccompProfile: v.seccompProfile, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			t.Errorf("%s received error during generation %#v", k, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		if actual != v.expectedProfile { | ||||
| 			t.Errorf("%s expected profile %s but received %s", k, v.expectedProfile, actual) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestValidatePod(t *testing.T) { | ||||
| 	foo := "foo" | ||||
| 	tests := map[string]struct { | ||||
| 		pspAnnotations map[string]string | ||||
| 		podAnnotations map[string]string | ||||
| 		seccompProfile *api.SeccompProfile | ||||
| 		expectedError  string | ||||
| 	}{ | ||||
| 		"no pod annotations, required profiles": { | ||||
| 			pspAnnotations: allowSpecific, | ||||
| 			podAnnotations: nil, | ||||
| 			expectedError:  "Forbidden:  is not an allowed seccomp profile. Valid values are foo", | ||||
| 		}, | ||||
| 		"no pod annotations, no required profiles": { | ||||
| 			pspAnnotations: withoutSeccomp, | ||||
| 			podAnnotations: nil, | ||||
| 			expectedError:  "", | ||||
| 		}, | ||||
| 		"valid pod annotations, required profiles": { | ||||
| 			pspAnnotations: allowSpecific, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompPodAnnotationKey: "foo", | ||||
| 			}, | ||||
| 			expectedError: "", | ||||
| 		}, | ||||
| 		"invalid pod annotations, required profiles": { | ||||
| 			pspAnnotations: allowSpecific, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompPodAnnotationKey: "bar", | ||||
| 			}, | ||||
| 			expectedError: "Forbidden: bar is not an allowed seccomp profile. Valid values are foo", | ||||
| 		}, | ||||
| 		"pod annotations, no required profiles": { | ||||
| 			pspAnnotations: withoutSeccomp, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompPodAnnotationKey: "foo", | ||||
| 			}, | ||||
| 			expectedError: "Forbidden: seccomp may not be set", | ||||
| 		}, | ||||
| 		"pod annotations, allow any": { | ||||
| 			pspAnnotations: allowAnyNoDefault, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompPodAnnotationKey: "foo", | ||||
| 			}, | ||||
| 			expectedError: "", | ||||
| 		}, | ||||
| 		"no pod annotations, allow any": { | ||||
| 			pspAnnotations: allowAnyNoDefault, | ||||
| 			podAnnotations: nil, | ||||
| 			expectedError:  "", | ||||
| 		}, | ||||
| 		"valid pod annotations and field, required profiles": { | ||||
| 			pspAnnotations: allowSpecific, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompPodAnnotationKey: "foo", | ||||
| 			}, | ||||
| 			seccompProfile: &api.SeccompProfile{ | ||||
| 				Type:             api.SeccompProfileTypeLocalhost, | ||||
| 				LocalhostProfile: &foo, | ||||
| 			}, | ||||
| 			expectedError: "", | ||||
| 		}, | ||||
| 		"valid pod field and no annotation, required profiles": { | ||||
| 			pspAnnotations: allowSpecific, | ||||
| 			seccompProfile: &api.SeccompProfile{ | ||||
| 				Type:             api.SeccompProfileTypeLocalhost, | ||||
| 				LocalhostProfile: &foo, | ||||
| 			}, | ||||
| 			expectedError: "Forbidden: localhost/foo is not an allowed seccomp profile. Valid values are foo", | ||||
| 		}, | ||||
| 		"valid pod field and no annotation, required profiles (localhost)": { | ||||
| 			pspAnnotations: allowSpecificLocalhost, | ||||
| 			seccompProfile: &api.SeccompProfile{ | ||||
| 				Type:             api.SeccompProfileTypeLocalhost, | ||||
| 				LocalhostProfile: &foo, | ||||
| 			}, | ||||
| 			expectedError: "", | ||||
| 		}, | ||||
| 		"docker/default PSP annotation automatically allows runtime/default pods": { | ||||
| 			pspAnnotations: allowSpecificDockerDefault, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompPodAnnotationKey: v1.SeccompProfileRuntimeDefault, | ||||
| 			}, | ||||
| 			expectedError: "", | ||||
| 		}, | ||||
| 		"runtime/default PSP annotation automatically allows docker/default pods": { | ||||
| 			pspAnnotations: allowSpecificRuntimeDefault, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompPodAnnotationKey: v1.DeprecatedSeccompProfileDockerDefault, | ||||
| 			}, | ||||
| 			expectedError: "", | ||||
| 		}, | ||||
| 	} | ||||
| 	for k, v := range tests { | ||||
| 		pod := &api.Pod{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Annotations: v.podAnnotations, | ||||
| 			}, | ||||
| 			Spec: api.PodSpec{ | ||||
| 				SecurityContext: &api.PodSecurityContext{ | ||||
| 					SeccompProfile: v.seccompProfile, | ||||
| 				}, | ||||
| 			}, | ||||
| 		} | ||||
| 		s := NewStrategy(v.pspAnnotations) | ||||
| 		errs := s.ValidatePod(pod) | ||||
| 		if v.expectedError == "" && len(errs) != 0 { | ||||
| 			t.Errorf("%s expected no errors but received %#v", k, errs.ToAggregate().Error()) | ||||
| 		} | ||||
| 		if v.expectedError != "" && len(errs) == 0 { | ||||
| 			t.Errorf("%s expected error %s but received none", k, v.expectedError) | ||||
| 		} | ||||
| 		if v.expectedError != "" && len(errs) > 1 { | ||||
| 			t.Errorf("%s received multiple errors: %s", k, errs.ToAggregate().Error()) | ||||
| 		} | ||||
| 		if v.expectedError != "" && len(errs) == 1 && !strings.Contains(errs.ToAggregate().Error(), v.expectedError) { | ||||
| 			t.Errorf("%s expected error %s but received %s", k, v.expectedError, errs.ToAggregate().Error()) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestValidateContainer(t *testing.T) { | ||||
| 	foo := "foo" | ||||
| 	bar := "bar" | ||||
| 	tests := map[string]struct { | ||||
| 		pspAnnotations map[string]string | ||||
| 		podAnnotations map[string]string | ||||
| 		seccompProfile *api.SeccompProfile | ||||
| 		expectedError  string | ||||
| 	}{ | ||||
| 		"no pod annotations, required profiles": { | ||||
| 			pspAnnotations: allowSpecific, | ||||
| 			podAnnotations: nil, | ||||
| 			expectedError:  "Forbidden:  is not an allowed seccomp profile. Valid values are foo", | ||||
| 		}, | ||||
| 		"no pod annotations, no required profiles": { | ||||
| 			pspAnnotations: withoutSeccomp, | ||||
| 			podAnnotations: nil, | ||||
| 			expectedError:  "", | ||||
| 		}, | ||||
| 		"valid pod annotations, required profiles": { | ||||
| 			pspAnnotations: allowSpecific, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompContainerAnnotationKeyPrefix + "container": "foo", | ||||
| 			}, | ||||
| 			expectedError: "", | ||||
| 		}, | ||||
| 		"invalid pod annotations, required profiles": { | ||||
| 			pspAnnotations: allowSpecific, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompContainerAnnotationKeyPrefix + "container": "bar", | ||||
| 			}, | ||||
| 			expectedError: "Forbidden: bar is not an allowed seccomp profile. Valid values are foo", | ||||
| 		}, | ||||
| 		"pod annotations, no required profiles": { | ||||
| 			pspAnnotations: withoutSeccomp, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompContainerAnnotationKeyPrefix + "container": "foo", | ||||
| 			}, | ||||
| 			expectedError: "Forbidden: seccomp may not be set", | ||||
| 		}, | ||||
| 		"pod annotations, allow any": { | ||||
| 			pspAnnotations: allowAnyNoDefault, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompContainerAnnotationKeyPrefix + "container": "foo", | ||||
| 			}, | ||||
| 			expectedError: "", | ||||
| 		}, | ||||
| 		"no pod annotations, allow any": { | ||||
| 			pspAnnotations: allowAnyNoDefault, | ||||
| 			podAnnotations: nil, | ||||
| 			expectedError:  "", | ||||
| 		}, | ||||
| 		"container inherits valid pod annotation": { | ||||
| 			pspAnnotations: allowSpecific, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompPodAnnotationKey: "foo", | ||||
| 			}, | ||||
| 			expectedError: "", | ||||
| 		}, | ||||
| 		"container inherits invalid pod annotation": { | ||||
| 			pspAnnotations: allowSpecific, | ||||
| 			podAnnotations: map[string]string{ | ||||
| 				api.SeccompPodAnnotationKey: "bar", | ||||
| 			}, | ||||
| 			expectedError: "Forbidden: bar is not an allowed seccomp profile. Valid values are foo", | ||||
| 		}, | ||||
| 		"valid container field and no annotation, required profiles": { | ||||
| 			pspAnnotations: allowSpecificLocalhost, | ||||
| 			seccompProfile: &api.SeccompProfile{ | ||||
| 				Type:             api.SeccompProfileTypeLocalhost, | ||||
| 				LocalhostProfile: &foo, | ||||
| 			}, | ||||
| 			expectedError: "", | ||||
| 		}, | ||||
| 		"invalid container field and no annotation, required profiles": { | ||||
| 			pspAnnotations: allowSpecificLocalhost, | ||||
| 			seccompProfile: &api.SeccompProfile{ | ||||
| 				Type:             api.SeccompProfileTypeLocalhost, | ||||
| 				LocalhostProfile: &bar, | ||||
| 			}, | ||||
| 			expectedError: "Forbidden: localhost/bar is not an allowed seccomp profile. Valid values are localhost/foo", | ||||
| 		}, | ||||
| 	} | ||||
| 	for k, v := range tests { | ||||
| 		pod := &api.Pod{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Annotations: v.podAnnotations, | ||||
| 			}, | ||||
| 		} | ||||
| 		container := &api.Container{ | ||||
| 			Name: "container", | ||||
| 			SecurityContext: &api.SecurityContext{ | ||||
| 				SeccompProfile: v.seccompProfile, | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		s := NewStrategy(v.pspAnnotations) | ||||
| 		errs := s.ValidateContainer(pod, container) | ||||
| 		if v.expectedError == "" && len(errs) != 0 { | ||||
| 			t.Errorf("%s expected no errors but received %#v", k, errs.ToAggregate().Error()) | ||||
| 		} | ||||
| 		if v.expectedError != "" && len(errs) == 0 { | ||||
| 			t.Errorf("%s expected error %s but received none", k, v.expectedError) | ||||
| 		} | ||||
| 		if v.expectedError != "" && len(errs) > 1 { | ||||
| 			t.Errorf("%s received multiple errors: %s", k, errs.ToAggregate().Error()) | ||||
| 		} | ||||
| 		if v.expectedError != "" && len(errs) == 1 && !strings.Contains(errs.ToAggregate().Error(), v.expectedError) { | ||||
| 			t.Errorf("%s expected error %s but received %s", k, v.expectedError, errs.ToAggregate().Error()) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,19 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| // Package selinux contains code for validating and defaulting the SELinux | ||||
| // context of a pod according to a security policy. | ||||
| package selinux | ||||
| @@ -1,126 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package selinux | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
|  | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| 	"k8s.io/kubernetes/pkg/apis/core/v1" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" | ||||
| ) | ||||
|  | ||||
| type mustRunAs struct { | ||||
| 	opts *api.SELinuxOptions | ||||
| } | ||||
|  | ||||
| var _ SELinuxStrategy = &mustRunAs{} | ||||
|  | ||||
| func NewMustRunAs(options *policy.SELinuxStrategyOptions) (SELinuxStrategy, error) { | ||||
| 	if options == nil { | ||||
| 		return nil, fmt.Errorf("MustRunAs requires SELinuxContextStrategyOptions") | ||||
| 	} | ||||
| 	if options.SELinuxOptions == nil { | ||||
| 		return nil, fmt.Errorf("MustRunAs requires SELinuxOptions") | ||||
| 	} | ||||
|  | ||||
| 	internalSELinuxOptions := &api.SELinuxOptions{} | ||||
| 	if err := v1.Convert_v1_SELinuxOptions_To_core_SELinuxOptions(options.SELinuxOptions, internalSELinuxOptions, nil); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &mustRunAs{ | ||||
| 		opts: internalSELinuxOptions, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // Generate creates the SELinuxOptions based on constraint rules. | ||||
| func (s *mustRunAs) Generate(_ *api.Pod, _ *api.Container) (*api.SELinuxOptions, error) { | ||||
| 	return s.opts, nil | ||||
| } | ||||
|  | ||||
| // Validate ensures that the specified values fall within the range of the strategy. | ||||
| func (s *mustRunAs) Validate(fldPath *field.Path, _ *api.Pod, _ *api.Container, seLinux *api.SELinuxOptions) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
|  | ||||
| 	if seLinux == nil { | ||||
| 		allErrs = append(allErrs, field.Required(fldPath, "")) | ||||
| 		return allErrs | ||||
| 	} | ||||
| 	if !equalLevels(s.opts.Level, seLinux.Level) { | ||||
| 		detail := fmt.Sprintf("must be %s", s.opts.Level) | ||||
| 		allErrs = append(allErrs, field.Invalid(fldPath.Child("level"), seLinux.Level, detail)) | ||||
| 	} | ||||
| 	if seLinux.Role != s.opts.Role { | ||||
| 		detail := fmt.Sprintf("must be %s", s.opts.Role) | ||||
| 		allErrs = append(allErrs, field.Invalid(fldPath.Child("role"), seLinux.Role, detail)) | ||||
| 	} | ||||
| 	if seLinux.Type != s.opts.Type { | ||||
| 		detail := fmt.Sprintf("must be %s", s.opts.Type) | ||||
| 		allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), seLinux.Type, detail)) | ||||
| 	} | ||||
| 	if seLinux.User != s.opts.User { | ||||
| 		detail := fmt.Sprintf("must be %s", s.opts.User) | ||||
| 		allErrs = append(allErrs, field.Invalid(fldPath.Child("user"), seLinux.User, detail)) | ||||
| 	} | ||||
|  | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| // equalLevels compares SELinux levels for equality. | ||||
| func equalLevels(expected, actual string) bool { | ||||
| 	if expected == actual { | ||||
| 		return true | ||||
| 	} | ||||
| 	// "s0:c6,c0" => [ "s0", "c6,c0" ] | ||||
| 	expectedParts := strings.SplitN(expected, ":", 2) | ||||
| 	actualParts := strings.SplitN(actual, ":", 2) | ||||
|  | ||||
| 	// both SELinux levels must be in a format "sX:cY" | ||||
| 	if len(expectedParts) != 2 || len(actualParts) != 2 { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if !equalSensitivity(expectedParts[0], actualParts[0]) { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if !equalCategories(expectedParts[1], actualParts[1]) { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // equalSensitivity compares sensitivities of the SELinux levels for equality. | ||||
| func equalSensitivity(expected, actual string) bool { | ||||
| 	return expected == actual | ||||
| } | ||||
|  | ||||
| // equalCategories compares categories of the SELinux levels for equality. | ||||
| func equalCategories(expected, actual string) bool { | ||||
| 	expectedCategories := strings.Split(expected, ",") | ||||
| 	actualCategories := strings.Split(actual, ",") | ||||
|  | ||||
| 	sort.Strings(expectedCategories) | ||||
| 	sort.Strings(actualCategories) | ||||
|  | ||||
| 	return util.EqualStringSlices(expectedCategories, actualCategories) | ||||
| } | ||||
| @@ -1,183 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package selinux | ||||
|  | ||||
| import ( | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| 	"k8s.io/kubernetes/pkg/apis/core/v1" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestMustRunAsOptions(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		opts *policy.SELinuxStrategyOptions | ||||
| 		pass bool | ||||
| 	}{ | ||||
| 		"nil opts": { | ||||
| 			opts: nil, | ||||
| 			pass: false, | ||||
| 		}, | ||||
| 		"invalid opts": { | ||||
| 			opts: &policy.SELinuxStrategyOptions{}, | ||||
| 			pass: false, | ||||
| 		}, | ||||
| 		"valid opts": { | ||||
| 			opts: &policy.SELinuxStrategyOptions{SELinuxOptions: &corev1.SELinuxOptions{}}, | ||||
| 			pass: true, | ||||
| 		}, | ||||
| 	} | ||||
| 	for name, tc := range tests { | ||||
| 		_, err := NewMustRunAs(tc.opts) | ||||
| 		if err != nil && tc.pass { | ||||
| 			t.Errorf("%s expected to pass but received error %#v", name, err) | ||||
| 		} | ||||
| 		if err == nil && !tc.pass { | ||||
| 			t.Errorf("%s expected to fail but did not receive an error", name) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestMustRunAsGenerate(t *testing.T) { | ||||
| 	opts := &policy.SELinuxStrategyOptions{ | ||||
| 		SELinuxOptions: &corev1.SELinuxOptions{ | ||||
| 			User:  "user", | ||||
| 			Role:  "role", | ||||
| 			Type:  "type", | ||||
| 			Level: "level", | ||||
| 		}, | ||||
| 	} | ||||
| 	mustRunAs, err := NewMustRunAs(opts) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewMustRunAs %v", err) | ||||
| 	} | ||||
| 	generated, err := mustRunAs.Generate(nil, nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error generating selinux %v", err) | ||||
| 	} | ||||
| 	internalSELinuxOptions := &api.SELinuxOptions{} | ||||
| 	v1.Convert_v1_SELinuxOptions_To_core_SELinuxOptions(opts.SELinuxOptions, internalSELinuxOptions, nil) | ||||
| 	if !reflect.DeepEqual(generated, internalSELinuxOptions) { | ||||
| 		t.Errorf("generated selinux does not equal configured selinux") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestMustRunAsValidate(t *testing.T) { | ||||
| 	newValidOpts := func() *corev1.SELinuxOptions { | ||||
| 		return &corev1.SELinuxOptions{ | ||||
| 			User:  "user", | ||||
| 			Role:  "role", | ||||
| 			Level: "s0:c0,c6", | ||||
| 			Type:  "type", | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	newValidOptsWithLevel := func(level string) *corev1.SELinuxOptions { | ||||
| 		opts := newValidOpts() | ||||
| 		opts.Level = level | ||||
| 		return opts | ||||
| 	} | ||||
|  | ||||
| 	role := newValidOpts() | ||||
| 	role.Role = "invalid" | ||||
|  | ||||
| 	user := newValidOpts() | ||||
| 	user.User = "invalid" | ||||
|  | ||||
| 	seType := newValidOpts() | ||||
| 	seType.Type = "invalid" | ||||
|  | ||||
| 	validOpts := newValidOpts() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		podSeLinux  *corev1.SELinuxOptions | ||||
| 		pspSeLinux  *corev1.SELinuxOptions | ||||
| 		expectedMsg string | ||||
| 	}{ | ||||
| 		"invalid role": { | ||||
| 			podSeLinux:  role, | ||||
| 			pspSeLinux:  validOpts, | ||||
| 			expectedMsg: "role: Invalid value", | ||||
| 		}, | ||||
| 		"invalid user": { | ||||
| 			podSeLinux:  user, | ||||
| 			pspSeLinux:  validOpts, | ||||
| 			expectedMsg: "user: Invalid value", | ||||
| 		}, | ||||
| 		"levels are not equal": { | ||||
| 			podSeLinux:  newValidOptsWithLevel("s0"), | ||||
| 			pspSeLinux:  newValidOptsWithLevel("s0:c1,c2"), | ||||
| 			expectedMsg: "level: Invalid value", | ||||
| 		}, | ||||
| 		"levels differ by sensitivity": { | ||||
| 			podSeLinux:  newValidOptsWithLevel("s0:c6"), | ||||
| 			pspSeLinux:  newValidOptsWithLevel("s1:c6"), | ||||
| 			expectedMsg: "level: Invalid value", | ||||
| 		}, | ||||
| 		"levels differ by categories": { | ||||
| 			podSeLinux:  newValidOptsWithLevel("s0:c0,c8"), | ||||
| 			pspSeLinux:  newValidOptsWithLevel("s0:c1,c7"), | ||||
| 			expectedMsg: "level: Invalid value", | ||||
| 		}, | ||||
| 		"valid": { | ||||
| 			podSeLinux:  validOpts, | ||||
| 			pspSeLinux:  validOpts, | ||||
| 			expectedMsg: "", | ||||
| 		}, | ||||
| 		"valid with different order of categories": { | ||||
| 			podSeLinux:  newValidOptsWithLevel("s0:c6,c0"), | ||||
| 			pspSeLinux:  validOpts, | ||||
| 			expectedMsg: "", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for name, tc := range tests { | ||||
| 		opts := &policy.SELinuxStrategyOptions{ | ||||
| 			SELinuxOptions: tc.pspSeLinux, | ||||
| 		} | ||||
| 		mustRunAs, err := NewMustRunAs(opts) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("unexpected error initializing NewMustRunAs for testcase %s: %#v", name, err) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		internalSELinuxOptions := api.SELinuxOptions{} | ||||
| 		v1.Convert_v1_SELinuxOptions_To_core_SELinuxOptions(tc.podSeLinux, &internalSELinuxOptions, nil) | ||||
| 		errs := mustRunAs.Validate(nil, nil, nil, &internalSELinuxOptions) | ||||
| 		//should've passed but didn't | ||||
| 		if len(tc.expectedMsg) == 0 && len(errs) > 0 { | ||||
| 			t.Errorf("%s expected no errors but received %v", name, errs) | ||||
| 		} | ||||
| 		//should've failed but didn't | ||||
| 		if len(tc.expectedMsg) != 0 && len(errs) == 0 { | ||||
| 			t.Errorf("%s expected error %s but received no errors", name, tc.expectedMsg) | ||||
| 		} | ||||
| 		//failed with additional messages | ||||
| 		if len(tc.expectedMsg) != 0 && len(errs) > 1 { | ||||
| 			t.Errorf("%s expected error %s but received multiple errors: %v", name, tc.expectedMsg, errs) | ||||
| 		} | ||||
| 		//check that we got the right message | ||||
| 		if len(tc.expectedMsg) != 0 && len(errs) == 1 { | ||||
| 			if !strings.Contains(errs[0].Error(), tc.expectedMsg) { | ||||
| 				t.Errorf("%s expected error to contain %s but it did not: %v", name, tc.expectedMsg, errs) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,43 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package selinux | ||||
|  | ||||
| import ( | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| // runAsAny implements the SELinuxStrategy interface. | ||||
| type runAsAny struct{} | ||||
|  | ||||
| var _ SELinuxStrategy = &runAsAny{} | ||||
|  | ||||
| // NewRunAsAny provides a strategy that will return the configured se linux context or nil. | ||||
| func NewRunAsAny(options *policy.SELinuxStrategyOptions) (SELinuxStrategy, error) { | ||||
| 	return &runAsAny{}, nil | ||||
| } | ||||
|  | ||||
| // Generate creates the SELinuxOptions based on constraint rules. | ||||
| func (s *runAsAny) Generate(pod *api.Pod, container *api.Container) (*api.SELinuxOptions, error) { | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| // Validate ensures that the specified values fall within the range of the strategy. | ||||
| func (s *runAsAny) Validate(fldPath *field.Path, _ *api.Pod, _ *api.Container, options *api.SELinuxOptions) field.ErrorList { | ||||
| 	return field.ErrorList{} | ||||
| } | ||||
| @@ -1,72 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package selinux | ||||
|  | ||||
| import ( | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestRunAsAnyOptions(t *testing.T) { | ||||
| 	_, err := NewRunAsAny(nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewRunAsAny %v", err) | ||||
| 	} | ||||
| 	_, err = NewRunAsAny(&policy.SELinuxStrategyOptions{}) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error initializing NewRunAsAny %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRunAsAnyGenerate(t *testing.T) { | ||||
| 	s, err := NewRunAsAny(&policy.SELinuxStrategyOptions{}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewRunAsAny %v", err) | ||||
| 	} | ||||
| 	uid, err := s.Generate(nil, nil) | ||||
| 	if uid != nil { | ||||
| 		t.Errorf("expected nil uid but got %v", *uid) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error generating uid %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRunAsAnyValidate(t *testing.T) { | ||||
| 	s, err := NewRunAsAny(&policy.SELinuxStrategyOptions{ | ||||
| 		SELinuxOptions: &corev1.SELinuxOptions{ | ||||
| 			Level: "foo", | ||||
| 		}, | ||||
| 	}, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewRunAsAny %v", err) | ||||
| 	} | ||||
| 	errs := s.Validate(nil, nil, nil, nil) | ||||
| 	if len(errs) != 0 { | ||||
| 		t.Errorf("unexpected errors validating with ") | ||||
| 	} | ||||
| 	s, err = NewRunAsAny(&policy.SELinuxStrategyOptions{}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewRunAsAny %v", err) | ||||
| 	} | ||||
| 	errs = s.Validate(nil, nil, nil, nil) | ||||
| 	if len(errs) != 0 { | ||||
| 		t.Errorf("unexpected errors validating %v", errs) | ||||
| 	} | ||||
| } | ||||
| @@ -1,30 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package selinux | ||||
|  | ||||
| import ( | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| // SELinuxStrategy defines the interface for all SELinux constraint strategies. | ||||
| type SELinuxStrategy interface { | ||||
| 	// Generate creates the SELinuxOptions based on constraint rules. | ||||
| 	Generate(pod *api.Pod, container *api.Container) (*api.SELinuxOptions, error) | ||||
| 	// Validate ensures that the specified values fall within the range of the strategy. | ||||
| 	Validate(fldPath *field.Path, pod *api.Pod, container *api.Container, options *api.SELinuxOptions) field.ErrorList | ||||
| } | ||||
| @@ -1,126 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package sysctl | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| // SafeSysctlAllowlist returns the allowlist of safe sysctls and safe sysctl patterns (ending in *). | ||||
| // | ||||
| // A sysctl is called safe iff | ||||
| // - it is namespaced in the container or the pod | ||||
| // - it is isolated, i.e. has no influence on any other pod on the same node. | ||||
| func SafeSysctlAllowlist() []string { | ||||
| 	return []string{ | ||||
| 		"kernel.shm_rmid_forced", | ||||
| 		"net.ipv4.ip_local_port_range", | ||||
| 		"net.ipv4.tcp_syncookies", | ||||
| 		"net.ipv4.ping_group_range", | ||||
| 		"net.ipv4.ip_unprivileged_port_start", | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // mustMatchPatterns implements the SysctlsStrategy interface | ||||
| type mustMatchPatterns struct { | ||||
| 	safeAllowlist        []string | ||||
| 	allowedUnsafeSysctls []string | ||||
| 	forbiddenSysctls     []string | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	_ SysctlsStrategy = &mustMatchPatterns{} | ||||
| ) | ||||
|  | ||||
| // NewMustMatchPatterns creates a new mustMatchPatterns strategy that will provide validation. | ||||
| // Passing nil means the default pattern, passing an empty list means to disallow all sysctls. | ||||
| func NewMustMatchPatterns(safeAllowlist, allowedUnsafeSysctls, forbiddenSysctls []string) SysctlsStrategy { | ||||
| 	return &mustMatchPatterns{ | ||||
| 		safeAllowlist:        safeAllowlist, | ||||
| 		allowedUnsafeSysctls: allowedUnsafeSysctls, | ||||
| 		forbiddenSysctls:     forbiddenSysctls, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *mustMatchPatterns) isForbidden(sysctlName string) bool { | ||||
| 	// Is the sysctl forbidden? | ||||
| 	for _, s := range s.forbiddenSysctls { | ||||
| 		if strings.HasSuffix(s, "*") { | ||||
| 			prefix := strings.TrimSuffix(s, "*") | ||||
| 			if strings.HasPrefix(sysctlName, prefix) { | ||||
| 				return true | ||||
| 			} | ||||
| 		} else if sysctlName == s { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (s *mustMatchPatterns) isSafe(sysctlName string) bool { | ||||
| 	for _, ws := range s.safeAllowlist { | ||||
| 		if sysctlName == ws { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (s *mustMatchPatterns) isAllowedUnsafe(sysctlName string) bool { | ||||
| 	for _, s := range s.allowedUnsafeSysctls { | ||||
| 		if strings.HasSuffix(s, "*") { | ||||
| 			prefix := strings.TrimSuffix(s, "*") | ||||
| 			if strings.HasPrefix(sysctlName, prefix) { | ||||
| 				return true | ||||
| 			} | ||||
| 		} else if sysctlName == s { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // Validate ensures that the specified values fall within the range of the strategy. | ||||
| func (s *mustMatchPatterns) Validate(pod *api.Pod) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
|  | ||||
| 	var sysctls []api.Sysctl | ||||
| 	if pod.Spec.SecurityContext != nil { | ||||
| 		sysctls = pod.Spec.SecurityContext.Sysctls | ||||
| 	} | ||||
|  | ||||
| 	fieldPath := field.NewPath("pod", "spec", "securityContext").Child("sysctls") | ||||
|  | ||||
| 	for i, sysctl := range sysctls { | ||||
| 		switch { | ||||
| 		case s.isForbidden(sysctl.Name): | ||||
| 			allErrs = append(allErrs, field.ErrorList{field.Forbidden(fieldPath.Index(i), fmt.Sprintf("sysctl %q is not allowed", sysctl.Name))}...) | ||||
| 		case s.isSafe(sysctl.Name): | ||||
| 			continue | ||||
| 		case s.isAllowedUnsafe(sysctl.Name): | ||||
| 			continue | ||||
| 		default: | ||||
| 			allErrs = append(allErrs, field.ErrorList{field.Forbidden(fieldPath.Index(i), fmt.Sprintf("unsafe sysctl %q is not allowed", sysctl.Name))}...) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return allErrs | ||||
| } | ||||
| @@ -1,104 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package sysctl | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| func TestValidate(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		allowlist     []string | ||||
| 		forbiddenSafe []string | ||||
| 		allowedUnsafe []string | ||||
| 		allowed       []string | ||||
| 		disallowed    []string | ||||
| 	}{ | ||||
| 		// no container requests | ||||
| 		"with allow all": { | ||||
| 			allowlist: []string{"foo"}, | ||||
| 			allowed:   []string{"foo"}, | ||||
| 		}, | ||||
| 		"empty": { | ||||
| 			allowlist:     []string{"foo"}, | ||||
| 			forbiddenSafe: []string{"*"}, | ||||
| 			disallowed:    []string{"foo"}, | ||||
| 		}, | ||||
| 		"without wildcard": { | ||||
| 			allowlist:  []string{"a", "a.b"}, | ||||
| 			allowed:    []string{"a", "a.b"}, | ||||
| 			disallowed: []string{"b"}, | ||||
| 		}, | ||||
| 		"with catch-all wildcard and non-wildcard": { | ||||
| 			allowedUnsafe: []string{"a.b.c", "*"}, | ||||
| 			allowed:       []string{"a", "a.b", "a.b.c", "b"}, | ||||
| 		}, | ||||
| 		"without catch-all wildcard": { | ||||
| 			allowedUnsafe: []string{"a.*", "b.*", "c.d.e", "d.e.f.*"}, | ||||
| 			allowed:       []string{"a.b", "b.c", "c.d.e", "d.e.f.g.h"}, | ||||
| 			disallowed:    []string{"a", "b", "c", "c.d", "d.e", "d.e.f"}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range tests { | ||||
| 		strategy := NewMustMatchPatterns(v.allowlist, v.allowedUnsafe, v.forbiddenSafe) | ||||
|  | ||||
| 		pod := &api.Pod{} | ||||
| 		errs := strategy.Validate(pod) | ||||
| 		if len(errs) != 0 { | ||||
| 			t.Errorf("%s: unexpected validaton errors for empty sysctls: %v", k, errs) | ||||
| 		} | ||||
|  | ||||
| 		testAllowed := func() { | ||||
| 			sysctls := []api.Sysctl{} | ||||
| 			for _, s := range v.allowed { | ||||
| 				sysctls = append(sysctls, api.Sysctl{ | ||||
| 					Name:  s, | ||||
| 					Value: "dummy", | ||||
| 				}) | ||||
| 			} | ||||
| 			pod.Spec.SecurityContext = &api.PodSecurityContext{ | ||||
| 				Sysctls: sysctls, | ||||
| 			} | ||||
| 			errs = strategy.Validate(pod) | ||||
| 			if len(errs) != 0 { | ||||
| 				t.Errorf("%s: unexpected validaton errors for sysctls: %v", k, errs) | ||||
| 			} | ||||
| 		} | ||||
| 		testDisallowed := func() { | ||||
| 			for _, s := range v.disallowed { | ||||
| 				pod.Spec.SecurityContext = &api.PodSecurityContext{ | ||||
| 					Sysctls: []api.Sysctl{ | ||||
| 						{ | ||||
| 							Name:  s, | ||||
| 							Value: "dummy", | ||||
| 						}, | ||||
| 					}, | ||||
| 				} | ||||
| 				errs = strategy.Validate(pod) | ||||
| 				if len(errs) == 0 { | ||||
| 					t.Errorf("%s: expected error for sysctl %q", k, s) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		testAllowed() | ||||
| 		testDisallowed() | ||||
| 	} | ||||
| } | ||||
| @@ -1,67 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package podsecuritypolicy | ||||
|  | ||||
| import ( | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/group" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/user" | ||||
| ) | ||||
|  | ||||
| // Provider provides the implementation to generate a new security | ||||
| // context based on constraints or validate an existing security context against constraints. | ||||
| type Provider interface { | ||||
| 	// MutatePod sets the default values of the required but not filled fields of the pod and all | ||||
| 	// containers in the pod. | ||||
| 	MutatePod(pod *api.Pod) error | ||||
| 	// ValidatePod ensures a pod and all its containers are in compliance with the given constraints. | ||||
| 	// ValidatePod MUST NOT mutate the pod. | ||||
| 	ValidatePod(pod *api.Pod) field.ErrorList | ||||
| 	// Get the name of the PSP that this provider was initialized with. | ||||
| 	GetPSPName() string | ||||
| } | ||||
|  | ||||
| // StrategyFactory abstracts how the strategies are created from the provider so that you may | ||||
| // implement your own custom strategies that may pull information from other resources as necessary. | ||||
| // For example, if you would like to populate the strategies with values from namespace annotations | ||||
| // you may create a factory with a client that can pull the namespace and populate the appropriate | ||||
| // values. | ||||
| type StrategyFactory interface { | ||||
| 	// CreateStrategies creates the strategies that a provider will use.  The namespace argument | ||||
| 	// should be the namespace of the object being checked (the pod's namespace). | ||||
| 	CreateStrategies(psp *policy.PodSecurityPolicy, namespace string) (*ProviderStrategies, error) | ||||
| } | ||||
|  | ||||
| // ProviderStrategies is a holder for all strategies that the provider requires to be populated. | ||||
| type ProviderStrategies struct { | ||||
| 	RunAsUserStrategy         user.RunAsUserStrategy | ||||
| 	RunAsGroupStrategy        group.GroupStrategy | ||||
| 	SELinuxStrategy           selinux.SELinuxStrategy | ||||
| 	AppArmorStrategy          apparmor.Strategy | ||||
| 	FSGroupStrategy           group.GroupStrategy | ||||
| 	SupplementalGroupStrategy group.GroupStrategy | ||||
| 	CapabilitiesStrategy      capabilities.Strategy | ||||
| 	SysctlsStrategy           sysctl.SysctlsStrategy | ||||
| 	SeccompStrategy           seccomp.Strategy | ||||
| } | ||||
| @@ -1,19 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| // Package user contains code for validating and defaulting the UID of a pod | ||||
| // or container according to a security policy. | ||||
| package user | ||||
| @@ -1,74 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package user | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| 	psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" | ||||
| ) | ||||
|  | ||||
| // mustRunAs implements the RunAsUserStrategy interface | ||||
| type mustRunAs struct { | ||||
| 	opts *policy.RunAsUserStrategyOptions | ||||
| } | ||||
|  | ||||
| // NewMustRunAs provides a strategy that requires the container to run as a specific UID in a range. | ||||
| func NewMustRunAs(options *policy.RunAsUserStrategyOptions) (RunAsUserStrategy, error) { | ||||
| 	if options == nil { | ||||
| 		return nil, fmt.Errorf("MustRunAs requires run as user options") | ||||
| 	} | ||||
| 	if len(options.Ranges) == 0 { | ||||
| 		return nil, fmt.Errorf("MustRunAs requires at least one range") | ||||
| 	} | ||||
| 	return &mustRunAs{ | ||||
| 		opts: options, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // Generate creates the uid based on policy rules.  MustRunAs returns the first range's Min. | ||||
| func (s *mustRunAs) Generate(pod *api.Pod, container *api.Container) (*int64, error) { | ||||
| 	return &s.opts.Ranges[0].Min, nil | ||||
| } | ||||
|  | ||||
| // Validate ensures that the specified values fall within the range of the strategy. | ||||
| func (s *mustRunAs) Validate(scPath *field.Path, _ *api.Pod, _ *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
|  | ||||
| 	if runAsUser == nil { | ||||
| 		allErrs = append(allErrs, field.Required(scPath.Child("runAsUser"), "")) | ||||
| 		return allErrs | ||||
| 	} | ||||
|  | ||||
| 	if !s.isValidUID(*runAsUser) { | ||||
| 		detail := fmt.Sprintf("must be in the ranges: %v", s.opts.Ranges) | ||||
| 		allErrs = append(allErrs, field.Invalid(scPath.Child("runAsUser"), *runAsUser, detail)) | ||||
| 	} | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| func (s *mustRunAs) isValidUID(id int64) bool { | ||||
| 	for _, rng := range s.opts.Ranges { | ||||
| 		if psputil.UserFallsInRange(id, rng) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| @@ -1,144 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package user | ||||
|  | ||||
| import ( | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestNewMustRunAs(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		opts *policy.RunAsUserStrategyOptions | ||||
| 		pass bool | ||||
| 	}{ | ||||
| 		"nil opts": { | ||||
| 			opts: nil, | ||||
| 			pass: false, | ||||
| 		}, | ||||
| 		"invalid opts": { | ||||
| 			opts: &policy.RunAsUserStrategyOptions{}, | ||||
| 			pass: false, | ||||
| 		}, | ||||
| 		"valid opts": { | ||||
| 			opts: &policy.RunAsUserStrategyOptions{ | ||||
| 				Ranges: []policy.IDRange{ | ||||
| 					{Min: 1, Max: 1}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			pass: true, | ||||
| 		}, | ||||
| 	} | ||||
| 	for name, tc := range tests { | ||||
| 		_, err := NewMustRunAs(tc.opts) | ||||
| 		if err != nil && tc.pass { | ||||
| 			t.Errorf("%s expected to pass but received error %#v", name, err) | ||||
| 		} | ||||
| 		if err == nil && !tc.pass { | ||||
| 			t.Errorf("%s expected to fail but did not receive an error", name) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGenerate(t *testing.T) { | ||||
| 	opts := &policy.RunAsUserStrategyOptions{ | ||||
| 		Ranges: []policy.IDRange{ | ||||
| 			{Min: 1, Max: 1}, | ||||
| 		}, | ||||
| 	} | ||||
| 	mustRunAs, err := NewMustRunAs(opts) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewMustRunAs %v", err) | ||||
| 	} | ||||
| 	generated, err := mustRunAs.Generate(nil, nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error generating runAsUser %v", err) | ||||
| 	} | ||||
| 	if *generated != opts.Ranges[0].Min { | ||||
| 		t.Errorf("generated runAsUser does not equal configured runAsUser") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestValidate(t *testing.T) { | ||||
| 	opts := &policy.RunAsUserStrategyOptions{ | ||||
| 		Ranges: []policy.IDRange{ | ||||
| 			{Min: 1, Max: 1}, | ||||
| 			{Min: 10, Max: 20}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	validID := int64(15) | ||||
| 	invalidID := int64(21) | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		container   *api.Container | ||||
| 		expectedMsg string | ||||
| 	}{ | ||||
| 		"good container": { | ||||
| 			container: &api.Container{ | ||||
| 				SecurityContext: &api.SecurityContext{ | ||||
| 					RunAsUser: &validID, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"nil run as user": { | ||||
| 			container: &api.Container{ | ||||
| 				SecurityContext: &api.SecurityContext{ | ||||
| 					RunAsUser: nil, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedMsg: "runAsUser: Required", | ||||
| 		}, | ||||
| 		"invalid id": { | ||||
| 			container: &api.Container{ | ||||
| 				SecurityContext: &api.SecurityContext{ | ||||
| 					RunAsUser: &invalidID, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedMsg: "runAsUser: Invalid", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for name, tc := range tests { | ||||
| 		mustRunAs, err := NewMustRunAs(opts) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("unexpected error initializing NewMustRunAs for testcase %s: %#v", name, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		errs := mustRunAs.Validate(nil, nil, nil, tc.container.SecurityContext.RunAsNonRoot, tc.container.SecurityContext.RunAsUser) | ||||
| 		//should've passed but didn't | ||||
| 		if len(tc.expectedMsg) == 0 && len(errs) > 0 { | ||||
| 			t.Errorf("%s expected no errors but received %v", name, errs) | ||||
| 		} | ||||
| 		//should've failed but didn't | ||||
| 		if len(tc.expectedMsg) != 0 && len(errs) == 0 { | ||||
| 			t.Errorf("%s expected error %s but received no errors", name, tc.expectedMsg) | ||||
| 		} | ||||
| 		//failed with additional messages | ||||
| 		if len(tc.expectedMsg) != 0 && len(errs) > 1 { | ||||
| 			t.Errorf("%s expected error %s but received multiple errors: %v", name, tc.expectedMsg, errs) | ||||
| 		} | ||||
| 		//check that we got the right message | ||||
| 		if len(tc.expectedMsg) != 0 && len(errs) == 1 { | ||||
| 			if !strings.Contains(errs[0].Error(), tc.expectedMsg) { | ||||
| 				t.Errorf("%s expected error to contain %s but it did not: %v", name, tc.expectedMsg, errs) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,59 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package user | ||||
|  | ||||
| import ( | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| type nonRoot struct{} | ||||
|  | ||||
| var _ RunAsUserStrategy = &nonRoot{} | ||||
|  | ||||
| func NewRunAsNonRoot(options *policy.RunAsUserStrategyOptions) (RunAsUserStrategy, error) { | ||||
| 	return &nonRoot{}, nil | ||||
| } | ||||
|  | ||||
| // Generate creates the uid based on policy rules.  This strategy does return a UID.  It assumes | ||||
| // that the user will specify a UID or the container image specifies a UID. | ||||
| func (s *nonRoot) Generate(pod *api.Pod, container *api.Container) (*int64, error) { | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| // Validate ensures that the specified values fall within the range of the strategy.  Validation | ||||
| // of this will pass if either the UID is not set, assuming that the image will provided the UID | ||||
| // or if the UID is set it is not root.  Validation will fail if RunAsNonRoot is set to false. | ||||
| // In order to work properly this assumes that the kubelet performs a final check on runAsUser | ||||
| // or the image UID when runAsUser is nil. | ||||
| func (s *nonRoot) Validate(scPath *field.Path, _ *api.Pod, _ *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
| 	if runAsNonRoot == nil && runAsUser == nil { | ||||
| 		allErrs = append(allErrs, field.Required(scPath.Child("runAsNonRoot"), "must be true")) | ||||
| 		return allErrs | ||||
| 	} | ||||
| 	if runAsNonRoot != nil && *runAsNonRoot == false { | ||||
| 		allErrs = append(allErrs, field.Invalid(scPath.Child("runAsNonRoot"), *runAsNonRoot, "must be true")) | ||||
| 		return allErrs | ||||
| 	} | ||||
| 	if runAsUser != nil && *runAsUser == 0 { | ||||
| 		allErrs = append(allErrs, field.Invalid(scPath.Child("runAsUser"), *runAsUser, "running with the root UID is forbidden")) | ||||
| 		return allErrs | ||||
| 	} | ||||
| 	return allErrs | ||||
| } | ||||
| @@ -1,119 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package user | ||||
|  | ||||
| import ( | ||||
| 	api "k8s.io/api/core/v1" | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestNonRootOptions(t *testing.T) { | ||||
| 	_, err := NewRunAsNonRoot(nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewRunAsNonRoot %v", err) | ||||
| 	} | ||||
| 	_, err = NewRunAsNonRoot(&policy.RunAsUserStrategyOptions{}) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error initializing NewRunAsNonRoot %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestNonRootGenerate(t *testing.T) { | ||||
| 	s, err := NewRunAsNonRoot(&policy.RunAsUserStrategyOptions{}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewRunAsNonRoot %v", err) | ||||
| 	} | ||||
| 	uid, err := s.Generate(nil, nil) | ||||
| 	if uid != nil { | ||||
| 		t.Errorf("expected nil uid but got %d", *uid) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error generating uid %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestNonRootValidate(t *testing.T) { | ||||
| 	goodUID := int64(1) | ||||
| 	badUID := int64(0) | ||||
| 	untrue := false | ||||
| 	unfalse := true | ||||
| 	s, err := NewRunAsNonRoot(&policy.RunAsUserStrategyOptions{}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewMustRunAs %v", err) | ||||
| 	} | ||||
| 	tests := []struct { | ||||
| 		container   *api.Container | ||||
| 		expectedErr bool | ||||
| 		msg         string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			container: &api.Container{ | ||||
| 				SecurityContext: &api.SecurityContext{ | ||||
| 					RunAsUser: &badUID, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedErr: true, | ||||
| 			msg:         "in test case %d, expected errors from root uid but got none: %v", | ||||
| 		}, | ||||
| 		{ | ||||
| 			container: &api.Container{ | ||||
| 				SecurityContext: &api.SecurityContext{ | ||||
| 					RunAsUser: &goodUID, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedErr: false, | ||||
| 			msg:         "in test case %d, expected no errors from non-root uid but got %v", | ||||
| 		}, | ||||
| 		{ | ||||
| 			container: &api.Container{ | ||||
| 				SecurityContext: &api.SecurityContext{ | ||||
| 					RunAsNonRoot: &untrue, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedErr: true, | ||||
| 			msg:         "in test case %d, expected errors from RunAsNonRoot but got none: %v", | ||||
| 		}, | ||||
| 		{ | ||||
| 			container: &api.Container{ | ||||
| 				SecurityContext: &api.SecurityContext{ | ||||
| 					RunAsNonRoot: &unfalse, | ||||
| 					RunAsUser:    &goodUID, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedErr: false, | ||||
| 			msg:         "in test case %d, expected no errors from non-root uid but got %v", | ||||
| 		}, | ||||
| 		{ | ||||
| 			container: &api.Container{ | ||||
| 				SecurityContext: &api.SecurityContext{ | ||||
| 					RunAsNonRoot: nil, | ||||
| 					RunAsUser:    nil, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedErr: true, | ||||
| 			msg:         "in test case %d, expected errors from nil runAsNonRoot and nil runAsUser but got %v", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for i, tc := range tests { | ||||
| 		errs := s.Validate(nil, nil, nil, tc.container.SecurityContext.RunAsNonRoot, tc.container.SecurityContext.RunAsUser) | ||||
| 		if (len(errs) == 0) == tc.expectedErr { | ||||
| 			t.Errorf(tc.msg, i, errs) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,43 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package user | ||||
|  | ||||
| import ( | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| // runAsAny implements the interface RunAsUserStrategy. | ||||
| type runAsAny struct{} | ||||
|  | ||||
| var _ RunAsUserStrategy = &runAsAny{} | ||||
|  | ||||
| // NewRunAsAny provides a strategy that will return nil. | ||||
| func NewRunAsAny(options *policy.RunAsUserStrategyOptions) (RunAsUserStrategy, error) { | ||||
| 	return &runAsAny{}, nil | ||||
| } | ||||
|  | ||||
| // Generate creates the uid based on policy rules. | ||||
| func (s *runAsAny) Generate(pod *api.Pod, container *api.Container) (*int64, error) { | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| // Validate ensures that the specified values fall within the range of the strategy. | ||||
| func (s *runAsAny) Validate(_ *field.Path, _ *api.Pod, _ *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList { | ||||
| 	return field.ErrorList{} | ||||
| } | ||||
| @@ -1,59 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package user | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| ) | ||||
|  | ||||
| func TestRunAsAnyOptions(t *testing.T) { | ||||
| 	_, err := NewRunAsAny(nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewRunAsAny %v", err) | ||||
| 	} | ||||
| 	_, err = NewRunAsAny(&policy.RunAsUserStrategyOptions{}) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error initializing NewRunAsAny %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRunAsAnyGenerate(t *testing.T) { | ||||
| 	s, err := NewRunAsAny(&policy.RunAsUserStrategyOptions{}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewRunAsAny %v", err) | ||||
| 	} | ||||
| 	uid, err := s.Generate(nil, nil) | ||||
| 	if uid != nil { | ||||
| 		t.Errorf("expected nil uid but got %d", *uid) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error generating uid %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRunAsAnyValidate(t *testing.T) { | ||||
| 	s, err := NewRunAsAny(&policy.RunAsUserStrategyOptions{}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error initializing NewRunAsAny %v", err) | ||||
| 	} | ||||
| 	errs := s.Validate(nil, nil, nil, nil, nil) | ||||
| 	if len(errs) != 0 { | ||||
| 		t.Errorf("unexpected errors validating with ") | ||||
| 	} | ||||
| } | ||||
| @@ -1,31 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package user | ||||
|  | ||||
| import ( | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| // RunAsUserStrategy defines the interface for all uid constraint strategies. | ||||
| type RunAsUserStrategy interface { | ||||
| 	// Generate creates the uid based on policy rules. | ||||
| 	Generate(pod *api.Pod, container *api.Container) (*int64, error) | ||||
| 	// Validate ensures that the specified values fall within the range of the strategy. | ||||
| 	// scPath is the field path to the container's security context | ||||
| 	Validate(scPath *field.Path, pod *api.Pod, container *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList | ||||
| } | ||||
| @@ -1,19 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| // Package util contains utility code shared amongst different parts of the | ||||
| // pod security policy apparatus. | ||||
| package util | ||||
| @@ -1,276 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package util | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	ValidatedPSPAnnotation = "kubernetes.io/psp" | ||||
| ) | ||||
|  | ||||
| // GetAllFSTypesExcept returns the result of GetAllFSTypesAsSet minus | ||||
| // the given exceptions. | ||||
| func GetAllFSTypesExcept(exceptions ...string) sets.String { | ||||
| 	fstypes := GetAllFSTypesAsSet() | ||||
| 	for _, e := range exceptions { | ||||
| 		fstypes.Delete(e) | ||||
| 	} | ||||
| 	return fstypes | ||||
| } | ||||
|  | ||||
| // GetAllFSTypesAsSet returns all actual volume types, regardless | ||||
| // of feature gates. The special policy.All pseudo type is not included. | ||||
| func GetAllFSTypesAsSet() sets.String { | ||||
| 	fstypes := sets.NewString() | ||||
| 	fstypes.Insert( | ||||
| 		string(policy.HostPath), | ||||
| 		string(policy.AzureFile), | ||||
| 		string(policy.Flocker), | ||||
| 		string(policy.FlexVolume), | ||||
| 		string(policy.EmptyDir), | ||||
| 		string(policy.GCEPersistentDisk), | ||||
| 		string(policy.AWSElasticBlockStore), | ||||
| 		string(policy.GitRepo), | ||||
| 		string(policy.Secret), | ||||
| 		string(policy.NFS), | ||||
| 		string(policy.ISCSI), | ||||
| 		string(policy.Glusterfs), | ||||
| 		string(policy.PersistentVolumeClaim), | ||||
| 		string(policy.RBD), | ||||
| 		string(policy.Cinder), | ||||
| 		string(policy.CephFS), | ||||
| 		string(policy.DownwardAPI), | ||||
| 		string(policy.FC), | ||||
| 		string(policy.ConfigMap), | ||||
| 		string(policy.VsphereVolume), | ||||
| 		string(policy.Quobyte), | ||||
| 		string(policy.AzureDisk), | ||||
| 		string(policy.PhotonPersistentDisk), | ||||
| 		string(policy.StorageOS), | ||||
| 		string(policy.Projected), | ||||
| 		string(policy.PortworxVolume), | ||||
| 		string(policy.ScaleIO), | ||||
| 		string(policy.CSI), | ||||
| 		string(policy.Ephemeral), | ||||
| 	) | ||||
| 	return fstypes | ||||
| } | ||||
|  | ||||
| // getVolumeFSType gets the FSType for a volume. | ||||
| func GetVolumeFSType(v api.Volume) (policy.FSType, error) { | ||||
| 	switch { | ||||
| 	case v.HostPath != nil: | ||||
| 		return policy.HostPath, nil | ||||
| 	case v.EmptyDir != nil: | ||||
| 		return policy.EmptyDir, nil | ||||
| 	case v.GCEPersistentDisk != nil: | ||||
| 		return policy.GCEPersistentDisk, nil | ||||
| 	case v.AWSElasticBlockStore != nil: | ||||
| 		return policy.AWSElasticBlockStore, nil | ||||
| 	case v.GitRepo != nil: | ||||
| 		return policy.GitRepo, nil | ||||
| 	case v.Secret != nil: | ||||
| 		return policy.Secret, nil | ||||
| 	case v.NFS != nil: | ||||
| 		return policy.NFS, nil | ||||
| 	case v.ISCSI != nil: | ||||
| 		return policy.ISCSI, nil | ||||
| 	case v.Glusterfs != nil: | ||||
| 		return policy.Glusterfs, nil | ||||
| 	case v.PersistentVolumeClaim != nil: | ||||
| 		return policy.PersistentVolumeClaim, nil | ||||
| 	case v.RBD != nil: | ||||
| 		return policy.RBD, nil | ||||
| 	case v.FlexVolume != nil: | ||||
| 		return policy.FlexVolume, nil | ||||
| 	case v.Cinder != nil: | ||||
| 		return policy.Cinder, nil | ||||
| 	case v.CephFS != nil: | ||||
| 		return policy.CephFS, nil | ||||
| 	case v.Flocker != nil: | ||||
| 		return policy.Flocker, nil | ||||
| 	case v.DownwardAPI != nil: | ||||
| 		return policy.DownwardAPI, nil | ||||
| 	case v.FC != nil: | ||||
| 		return policy.FC, nil | ||||
| 	case v.AzureFile != nil: | ||||
| 		return policy.AzureFile, nil | ||||
| 	case v.ConfigMap != nil: | ||||
| 		return policy.ConfigMap, nil | ||||
| 	case v.VsphereVolume != nil: | ||||
| 		return policy.VsphereVolume, nil | ||||
| 	case v.Quobyte != nil: | ||||
| 		return policy.Quobyte, nil | ||||
| 	case v.AzureDisk != nil: | ||||
| 		return policy.AzureDisk, nil | ||||
| 	case v.PhotonPersistentDisk != nil: | ||||
| 		return policy.PhotonPersistentDisk, nil | ||||
| 	case v.StorageOS != nil: | ||||
| 		return policy.StorageOS, nil | ||||
| 	case v.Projected != nil: | ||||
| 		return policy.Projected, nil | ||||
| 	case v.PortworxVolume != nil: | ||||
| 		return policy.PortworxVolume, nil | ||||
| 	case v.ScaleIO != nil: | ||||
| 		return policy.ScaleIO, nil | ||||
| 	case v.CSI != nil: | ||||
| 		return policy.CSI, nil | ||||
| 	case v.Ephemeral != nil: | ||||
| 		return policy.Ephemeral, nil | ||||
| 	} | ||||
|  | ||||
| 	return "", fmt.Errorf("unknown volume type for volume: %#v", v) | ||||
| } | ||||
|  | ||||
| // FSTypeToStringSet converts an FSType slice to a string set. | ||||
| func FSTypeToStringSet(fsTypes []policy.FSType) sets.String { | ||||
| 	set := sets.NewString() | ||||
| 	for _, v := range fsTypes { | ||||
| 		set.Insert(string(v)) | ||||
| 	} | ||||
| 	return set | ||||
| } | ||||
|  | ||||
| // PSPAllowsAllVolumes checks for FSTypeAll in the psp's allowed volumes. | ||||
| func PSPAllowsAllVolumes(psp *policy.PodSecurityPolicy) bool { | ||||
| 	return PSPAllowsFSType(psp, policy.All) | ||||
| } | ||||
|  | ||||
| // PSPAllowsFSType is a utility for checking if a PSP allows a particular FSType. | ||||
| // If all volumes are allowed then this will return true for any FSType passed. | ||||
| func PSPAllowsFSType(psp *policy.PodSecurityPolicy, fsType policy.FSType) bool { | ||||
| 	if psp == nil { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range psp.Spec.Volumes { | ||||
| 		if v == fsType || v == policy.All { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // UserFallsInRange is a utility to determine it the id falls in the valid range. | ||||
| func UserFallsInRange(id int64, rng policy.IDRange) bool { | ||||
| 	return id >= rng.Min && id <= rng.Max | ||||
| } | ||||
|  | ||||
| // GroupFallsInRange is a utility to determine it the id falls in the valid range. | ||||
| func GroupFallsInRange(id int64, rng policy.IDRange) bool { | ||||
| 	return id >= rng.Min && id <= rng.Max | ||||
| } | ||||
|  | ||||
| // AllowsHostVolumePath is a utility for checking if a PSP allows the host volume path. | ||||
| // This only checks the path. You should still check to make sure the host volume fs type is allowed. | ||||
| func AllowsHostVolumePath(psp *policy.PodSecurityPolicy, hostPath string) (pathIsAllowed, mustBeReadOnly bool) { | ||||
| 	if psp == nil { | ||||
| 		return false, false | ||||
| 	} | ||||
|  | ||||
| 	// If no allowed paths are specified then allow any path | ||||
| 	if len(psp.Spec.AllowedHostPaths) == 0 { | ||||
| 		return true, false | ||||
| 	} | ||||
|  | ||||
| 	for _, allowedPath := range psp.Spec.AllowedHostPaths { | ||||
| 		if hasPathPrefix(hostPath, allowedPath.PathPrefix) { | ||||
| 			if !allowedPath.ReadOnly { | ||||
| 				return true, allowedPath.ReadOnly | ||||
| 			} | ||||
| 			pathIsAllowed = true | ||||
| 			mustBeReadOnly = true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return pathIsAllowed, mustBeReadOnly | ||||
| } | ||||
|  | ||||
| // hasPathPrefix returns true if the string matches pathPrefix exactly, or if is prefixed with pathPrefix at a path segment boundary | ||||
| // the string and pathPrefix are both normalized to remove trailing slashes prior to checking. | ||||
| func hasPathPrefix(s, pathPrefix string) bool { | ||||
|  | ||||
| 	s = strings.TrimSuffix(s, "/") | ||||
| 	pathPrefix = strings.TrimSuffix(pathPrefix, "/") | ||||
|  | ||||
| 	// Short circuit if s doesn't contain the prefix at all | ||||
| 	if !strings.HasPrefix(s, pathPrefix) { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	pathPrefixLength := len(pathPrefix) | ||||
|  | ||||
| 	if len(s) == pathPrefixLength { | ||||
| 		// Exact match | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	if s[pathPrefixLength:pathPrefixLength+1] == "/" { | ||||
| 		// The next character in s is a path segment boundary | ||||
| 		// Check this instead of normalizing pathPrefix to avoid allocating on every call | ||||
| 		// Example where this check applies: s=/foo/bar and pathPrefix=/foo | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // EqualStringSlices compares string slices for equality. Slices are equal when | ||||
| // their sizes and elements on similar positions are equal. | ||||
| func EqualStringSlices(a, b []string) bool { | ||||
| 	if len(a) != len(b) { | ||||
| 		return false | ||||
| 	} | ||||
| 	for i := 0; i < len(a); i++ { | ||||
| 		if a[i] != b[i] { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func IsOnlyServiceAccountTokenSources(v *api.ProjectedVolumeSource) bool { | ||||
| 	for _, s := range v.Sources { | ||||
| 		// reject any projected source that does not match any of our expected source types | ||||
| 		if s.ServiceAccountToken == nil && s.ConfigMap == nil && s.DownwardAPI == nil { | ||||
| 			return false | ||||
| 		} | ||||
| 		if t := s.ServiceAccountToken; t != nil && (t.Path != "token" || t.Audience != "") { | ||||
| 			return false | ||||
| 		} | ||||
|  | ||||
| 		if s.ConfigMap != nil && s.ConfigMap.LocalObjectReference.Name != "kube-root-ca.crt" { | ||||
| 			return false | ||||
| 		} | ||||
|  | ||||
| 		if s.DownwardAPI != nil { | ||||
| 			for _, d := range s.DownwardAPI.Items { | ||||
| 				if d.Path != "namespace" || d.FieldRef == nil || d.FieldRef.APIVersion != "v1" || d.FieldRef.FieldPath != "metadata.namespace" { | ||||
| 					return false | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
| @@ -1,471 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package util | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
|  | ||||
| 	policy "k8s.io/api/policy/v1beta1" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| 	"k8s.io/kubernetes/pkg/serviceaccount" | ||||
| ) | ||||
|  | ||||
| // TestVolumeSourceFSTypeDrift ensures that for every known type of volume source (by the fields on | ||||
| // a VolumeSource object that GetVolumeFSType is returning a good value.  This ensures both that we're | ||||
| // returning an FSType for the VolumeSource field (protect the GetVolumeFSType method) and that we | ||||
| // haven't drifted (ensure new fields in VolumeSource are covered). | ||||
| func TestVolumeSourceFSTypeDrift(t *testing.T) { | ||||
| 	allFSTypes := GetAllFSTypesAsSet() | ||||
| 	val := reflect.ValueOf(api.VolumeSource{}) | ||||
|  | ||||
| 	for i := 0; i < val.NumField(); i++ { | ||||
| 		fieldVal := val.Type().Field(i) | ||||
|  | ||||
| 		volumeSource := api.VolumeSource{} | ||||
| 		volumeSourceVolume := reflect.New(fieldVal.Type.Elem()) | ||||
|  | ||||
| 		reflect.ValueOf(&volumeSource).Elem().FieldByName(fieldVal.Name).Set(volumeSourceVolume) | ||||
|  | ||||
| 		fsType, err := GetVolumeFSType(api.Volume{VolumeSource: volumeSource}) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("error getting fstype for field %s.  This likely means that drift has occurred between FSType and VolumeSource.  Please update the api and getVolumeFSType", fieldVal.Name) | ||||
| 		} | ||||
|  | ||||
| 		if !allFSTypes.Has(string(fsType)) { | ||||
| 			t.Errorf("%s was missing from GetFSTypesAsSet", fsType) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPSPAllowsFSType(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		psp    *policy.PodSecurityPolicy | ||||
| 		fsType policy.FSType | ||||
| 		allows bool | ||||
| 	}{ | ||||
| 		"nil psp": { | ||||
| 			psp:    nil, | ||||
| 			fsType: policy.HostPath, | ||||
| 			allows: false, | ||||
| 		}, | ||||
| 		"empty volumes": { | ||||
| 			psp:    &policy.PodSecurityPolicy{}, | ||||
| 			fsType: policy.HostPath, | ||||
| 			allows: false, | ||||
| 		}, | ||||
| 		"non-matching": { | ||||
| 			psp: &policy.PodSecurityPolicy{ | ||||
| 				Spec: policy.PodSecurityPolicySpec{ | ||||
| 					Volumes: []policy.FSType{policy.AWSElasticBlockStore}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			fsType: policy.HostPath, | ||||
| 			allows: false, | ||||
| 		}, | ||||
| 		"match on FSTypeAll": { | ||||
| 			psp: &policy.PodSecurityPolicy{ | ||||
| 				Spec: policy.PodSecurityPolicySpec{ | ||||
| 					Volumes: []policy.FSType{policy.All}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			fsType: policy.HostPath, | ||||
| 			allows: true, | ||||
| 		}, | ||||
| 		"match on direct match": { | ||||
| 			psp: &policy.PodSecurityPolicy{ | ||||
| 				Spec: policy.PodSecurityPolicySpec{ | ||||
| 					Volumes: []policy.FSType{policy.HostPath}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			fsType: policy.HostPath, | ||||
| 			allows: true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range tests { | ||||
| 		allows := PSPAllowsFSType(v.psp, v.fsType) | ||||
| 		if v.allows != allows { | ||||
| 			t.Errorf("%s expected PSPAllowsFSType to return %t but got %t", k, v.allows, allows) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestAllowsHostVolumePath(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		psp            *policy.PodSecurityPolicy | ||||
| 		path           string | ||||
| 		allows         bool | ||||
| 		mustBeReadOnly bool | ||||
| 	}{ | ||||
| 		"nil psp": { | ||||
| 			psp:            nil, | ||||
| 			path:           "/test", | ||||
| 			allows:         false, | ||||
| 			mustBeReadOnly: false, | ||||
| 		}, | ||||
| 		"empty allowed paths": { | ||||
| 			psp:            &policy.PodSecurityPolicy{}, | ||||
| 			path:           "/test", | ||||
| 			allows:         true, | ||||
| 			mustBeReadOnly: false, | ||||
| 		}, | ||||
| 		"non-matching": { | ||||
| 			psp: &policy.PodSecurityPolicy{ | ||||
| 				Spec: policy.PodSecurityPolicySpec{ | ||||
| 					AllowedHostPaths: []policy.AllowedHostPath{ | ||||
| 						{ | ||||
| 							PathPrefix: "/foo", | ||||
| 							ReadOnly:   true, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			path:           "/foobar", | ||||
| 			allows:         false, | ||||
| 			mustBeReadOnly: false, | ||||
| 		}, | ||||
| 		"match on direct match": { | ||||
| 			psp: &policy.PodSecurityPolicy{ | ||||
| 				Spec: policy.PodSecurityPolicySpec{ | ||||
| 					AllowedHostPaths: []policy.AllowedHostPath{ | ||||
| 						{ | ||||
| 							PathPrefix: "/foo", | ||||
| 							ReadOnly:   true, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			path:           "/foo", | ||||
| 			allows:         true, | ||||
| 			mustBeReadOnly: true, | ||||
| 		}, | ||||
| 		"match with trailing slash on host path": { | ||||
| 			psp: &policy.PodSecurityPolicy{ | ||||
| 				Spec: policy.PodSecurityPolicySpec{ | ||||
| 					AllowedHostPaths: []policy.AllowedHostPath{ | ||||
| 						{PathPrefix: "/foo"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			path:           "/foo/", | ||||
| 			allows:         true, | ||||
| 			mustBeReadOnly: false, | ||||
| 		}, | ||||
| 		"match with trailing slash on allowed path": { | ||||
| 			psp: &policy.PodSecurityPolicy{ | ||||
| 				Spec: policy.PodSecurityPolicySpec{ | ||||
| 					AllowedHostPaths: []policy.AllowedHostPath{ | ||||
| 						{PathPrefix: "/foo/"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			path:           "/foo", | ||||
| 			allows:         true, | ||||
| 			mustBeReadOnly: false, | ||||
| 		}, | ||||
| 		"match child directory": { | ||||
| 			psp: &policy.PodSecurityPolicy{ | ||||
| 				Spec: policy.PodSecurityPolicySpec{ | ||||
| 					AllowedHostPaths: []policy.AllowedHostPath{ | ||||
| 						{ | ||||
| 							PathPrefix: "/foo/", | ||||
| 							ReadOnly:   true, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			path:           "/foo/bar", | ||||
| 			allows:         true, | ||||
| 			mustBeReadOnly: true, | ||||
| 		}, | ||||
| 		"non-matching parent directory": { | ||||
| 			psp: &policy.PodSecurityPolicy{ | ||||
| 				Spec: policy.PodSecurityPolicySpec{ | ||||
| 					AllowedHostPaths: []policy.AllowedHostPath{ | ||||
| 						{PathPrefix: "/foo/bar"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			path:           "/foo", | ||||
| 			allows:         false, | ||||
| 			mustBeReadOnly: false, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range tests { | ||||
| 		allows, mustBeReadOnly := AllowsHostVolumePath(v.psp, v.path) | ||||
| 		if v.allows != allows { | ||||
| 			t.Errorf("allows: %s expected %t but got %t", k, v.allows, allows) | ||||
| 		} | ||||
| 		if v.mustBeReadOnly != mustBeReadOnly { | ||||
| 			t.Errorf("mustBeReadOnly: %s expected %t but got %t", k, v.mustBeReadOnly, mustBeReadOnly) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestEqualStringSlices(t *testing.T) { | ||||
| 	tests := map[string]struct { | ||||
| 		arg1           []string | ||||
| 		arg2           []string | ||||
| 		expectedResult bool | ||||
| 	}{ | ||||
| 		"nil equals to nil": { | ||||
| 			arg1:           nil, | ||||
| 			arg2:           nil, | ||||
| 			expectedResult: true, | ||||
| 		}, | ||||
| 		"equal by size": { | ||||
| 			arg1:           []string{"1", "1"}, | ||||
| 			arg2:           []string{"1", "1"}, | ||||
| 			expectedResult: true, | ||||
| 		}, | ||||
| 		"not equal by size": { | ||||
| 			arg1:           []string{"1"}, | ||||
| 			arg2:           []string{"1", "1"}, | ||||
| 			expectedResult: false, | ||||
| 		}, | ||||
| 		"not equal by elements": { | ||||
| 			arg1:           []string{"1", "1"}, | ||||
| 			arg2:           []string{"1", "2"}, | ||||
| 			expectedResult: false, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range tests { | ||||
| 		if result := EqualStringSlices(v.arg1, v.arg2); result != v.expectedResult { | ||||
| 			t.Errorf("%s expected to return %t but got %t", k, v.expectedResult, result) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestIsOnlyServiceAccountTokenSources(t *testing.T) { | ||||
| 	serviceAccountToken := api.VolumeProjection{ | ||||
| 		ServiceAccountToken: &api.ServiceAccountTokenProjection{ | ||||
| 			Path:              "token", | ||||
| 			ExpirationSeconds: serviceaccount.WarnOnlyBoundTokenExpirationSeconds, | ||||
| 		}} | ||||
| 	configMap := api.VolumeProjection{ | ||||
| 		ConfigMap: &api.ConfigMapProjection{ | ||||
| 			LocalObjectReference: api.LocalObjectReference{ | ||||
| 				Name: "kube-root-ca.crt", | ||||
| 			}, | ||||
| 			Items: []api.KeyToPath{ | ||||
| 				{ | ||||
| 					Key:  "ca.crt", | ||||
| 					Path: "ca.crt", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	downwardAPI := api.VolumeProjection{ | ||||
| 		DownwardAPI: &api.DownwardAPIProjection{ | ||||
| 			Items: []api.DownwardAPIVolumeFile{ | ||||
| 				{ | ||||
| 					Path: "namespace", | ||||
| 					FieldRef: &api.ObjectFieldSelector{ | ||||
| 						APIVersion: "v1", | ||||
| 						FieldPath:  "metadata.namespace", | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		desc   string | ||||
| 		volume *api.ProjectedVolumeSource | ||||
| 		want   bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			desc: "deny if ServiceAccountToken has wrong path", | ||||
| 			volume: &api.ProjectedVolumeSource{ | ||||
| 				Sources: []api.VolumeProjection{ | ||||
| 					{ServiceAccountToken: &api.ServiceAccountTokenProjection{ | ||||
| 						Path:              "notatoken", | ||||
| 						ExpirationSeconds: serviceaccount.WarnOnlyBoundTokenExpirationSeconds, | ||||
| 					}}, | ||||
| 					configMap, | ||||
| 					downwardAPI, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "deny if ServiceAccountToken has wrong audience", | ||||
| 			volume: &api.ProjectedVolumeSource{ | ||||
| 				Sources: []api.VolumeProjection{ | ||||
| 					{ServiceAccountToken: &api.ServiceAccountTokenProjection{ | ||||
| 						Path:              "token", | ||||
| 						Audience:          "not api server", | ||||
| 						ExpirationSeconds: serviceaccount.WarnOnlyBoundTokenExpirationSeconds, | ||||
| 					}}, | ||||
| 					configMap, | ||||
| 					downwardAPI, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "deny if CondigMap has wrong LocalObjectReference.Name", | ||||
| 			volume: &api.ProjectedVolumeSource{ | ||||
| 				Sources: []api.VolumeProjection{ | ||||
| 					serviceAccountToken, | ||||
| 					{ | ||||
| 						ConfigMap: &api.ConfigMapProjection{ | ||||
| 							LocalObjectReference: api.LocalObjectReference{ | ||||
| 								Name: "foo-ca.crt", | ||||
| 							}, | ||||
| 							Items: []api.KeyToPath{ | ||||
| 								{ | ||||
| 									Key:  "ca.crt", | ||||
| 									Path: "ca.crt", | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					downwardAPI, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "deny if DownwardAPI has wrong path", | ||||
| 			volume: &api.ProjectedVolumeSource{ | ||||
| 				Sources: []api.VolumeProjection{ | ||||
| 					serviceAccountToken, | ||||
| 					configMap, | ||||
| 					{ | ||||
| 						DownwardAPI: &api.DownwardAPIProjection{ | ||||
| 							Items: []api.DownwardAPIVolumeFile{ | ||||
| 								{ | ||||
| 									Path: "foo", | ||||
| 									FieldRef: &api.ObjectFieldSelector{ | ||||
| 										APIVersion: "v1", | ||||
| 										FieldPath:  "metadata.namespace", | ||||
| 									}, | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "deny if DownwardAPI has nil field ref", | ||||
| 			volume: &api.ProjectedVolumeSource{ | ||||
| 				Sources: []api.VolumeProjection{ | ||||
| 					serviceAccountToken, | ||||
| 					configMap, | ||||
| 					{ | ||||
| 						DownwardAPI: &api.DownwardAPIProjection{ | ||||
| 							Items: []api.DownwardAPIVolumeFile{ | ||||
| 								{ | ||||
| 									Path: "namespace", | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "deny if DownwardAPI has wrong api version", | ||||
| 			volume: &api.ProjectedVolumeSource{ | ||||
| 				Sources: []api.VolumeProjection{ | ||||
| 					serviceAccountToken, | ||||
| 					configMap, | ||||
| 					{ | ||||
| 						DownwardAPI: &api.DownwardAPIProjection{ | ||||
| 							Items: []api.DownwardAPIVolumeFile{ | ||||
| 								{ | ||||
| 									Path: "namespace", | ||||
| 									FieldRef: &api.ObjectFieldSelector{ | ||||
| 										APIVersion: "v1beta1", | ||||
| 										FieldPath:  "metadata.namespace", | ||||
| 									}, | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "deny if DownwardAPI has wrong field path", | ||||
| 			volume: &api.ProjectedVolumeSource{ | ||||
| 				Sources: []api.VolumeProjection{ | ||||
| 					serviceAccountToken, | ||||
| 					configMap, | ||||
| 					{ | ||||
| 						DownwardAPI: &api.DownwardAPIProjection{ | ||||
| 							Items: []api.DownwardAPIVolumeFile{ | ||||
| 								{ | ||||
| 									Path: "namespace", | ||||
| 									FieldRef: &api.ObjectFieldSelector{ | ||||
| 										APIVersion: "v1", | ||||
| 										FieldPath:  "metadata.foo", | ||||
| 									}, | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "deny if Secret exists", | ||||
| 			volume: &api.ProjectedVolumeSource{ | ||||
| 				Sources: []api.VolumeProjection{ | ||||
| 					{ | ||||
| 						Secret: &api.SecretProjection{}, | ||||
| 					}, | ||||
| 					configMap, | ||||
| 					downwardAPI, | ||||
| 					serviceAccountToken, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "deny if none of ServiceAccountToken, ConfigMap and DownwardAPI exist", | ||||
| 			volume: &api.ProjectedVolumeSource{ | ||||
| 				Sources: []api.VolumeProjection{ | ||||
| 					{}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "allow if any of ServiceAccountToken, ConfigMap and DownwardAPI matches", | ||||
| 			volume: &api.ProjectedVolumeSource{ | ||||
| 				Sources: []api.VolumeProjection{ | ||||
| 					configMap, | ||||
| 					downwardAPI, | ||||
| 					serviceAccountToken, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range tests { | ||||
| 		test := test | ||||
| 		t.Run(test.desc, func(t *testing.T) { | ||||
| 			t.Parallel() | ||||
|  | ||||
| 			if got := IsOnlyServiceAccountTokenSources(test.volume); got != test.want { | ||||
| 				t.Errorf("IsOnlyServiceAccountTokenSources(%+v) = %v, want %v", test.volume, got, test.want) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -1,8 +0,0 @@ | ||||
| # See the OWNERS docs at https://go.k8s.io/owners | ||||
|  | ||||
| approvers: | ||||
|   - sig-auth-policy-approvers | ||||
| reviewers: | ||||
|   - sig-auth-policy-reviewers | ||||
| labels: | ||||
|   - sig/auth | ||||
| @@ -1,380 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package podsecuritypolicy | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
|  | ||||
| 	"k8s.io/klog/v2" | ||||
|  | ||||
| 	policyv1beta1 "k8s.io/api/policy/v1beta1" | ||||
| 	apiequality "k8s.io/apimachinery/pkg/api/equality" | ||||
| 	"k8s.io/apimachinery/pkg/labels" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	"k8s.io/apiserver/pkg/admission" | ||||
| 	genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer" | ||||
| 	"k8s.io/apiserver/pkg/authentication/serviceaccount" | ||||
| 	"k8s.io/apiserver/pkg/authentication/user" | ||||
| 	"k8s.io/apiserver/pkg/authorization/authorizer" | ||||
| 	"k8s.io/client-go/informers" | ||||
| 	policylisters "k8s.io/client-go/listers/policy/v1beta1" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| 	"k8s.io/kubernetes/pkg/apis/extensions" | ||||
| 	"k8s.io/kubernetes/pkg/apis/policy" | ||||
| 	rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" | ||||
| 	psp "k8s.io/kubernetes/pkg/security/podsecuritypolicy" | ||||
| 	psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" | ||||
| ) | ||||
|  | ||||
| // PluginName is a string with the name of the plugin | ||||
| const PluginName = "PodSecurityPolicy" | ||||
|  | ||||
| // Register registers a plugin | ||||
| func Register(plugins *admission.Plugins) { | ||||
| 	plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { | ||||
| 		plugin := newPlugin(psp.NewSimpleStrategyFactory(), true) | ||||
| 		return plugin, nil | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // Plugin holds state for and implements the admission plugin. | ||||
| type Plugin struct { | ||||
| 	*admission.Handler | ||||
| 	strategyFactory  psp.StrategyFactory | ||||
| 	failOnNoPolicies bool | ||||
| 	authz            authorizer.Authorizer | ||||
| 	lister           policylisters.PodSecurityPolicyLister | ||||
| } | ||||
|  | ||||
| // SetAuthorizer sets the authorizer. | ||||
| func (p *Plugin) SetAuthorizer(authz authorizer.Authorizer) { | ||||
| 	p.authz = authz | ||||
| } | ||||
|  | ||||
| // ValidateInitialization ensures an authorizer is set. | ||||
| func (p *Plugin) ValidateInitialization() error { | ||||
| 	if p.authz == nil { | ||||
| 		return fmt.Errorf("%s requires an authorizer", PluginName) | ||||
| 	} | ||||
| 	if p.lister == nil { | ||||
| 		return fmt.Errorf("%s requires a lister", PluginName) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| var _ admission.MutationInterface = &Plugin{} | ||||
| var _ admission.ValidationInterface = &Plugin{} | ||||
| var _ genericadmissioninit.WantsAuthorizer = &Plugin{} | ||||
| var _ genericadmissioninit.WantsExternalKubeInformerFactory = &Plugin{} | ||||
| var auditKeyPrefix = strings.ToLower(PluginName) + "." + policy.GroupName + ".k8s.io" | ||||
|  | ||||
| // newPlugin creates a new PSP admission plugin. | ||||
| func newPlugin(strategyFactory psp.StrategyFactory, failOnNoPolicies bool) *Plugin { | ||||
| 	return &Plugin{ | ||||
| 		Handler:          admission.NewHandler(admission.Create, admission.Update), | ||||
| 		strategyFactory:  strategyFactory, | ||||
| 		failOnNoPolicies: failOnNoPolicies, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SetExternalKubeInformerFactory registers an informer | ||||
| func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { | ||||
| 	podSecurityPolicyInformer := f.Policy().V1beta1().PodSecurityPolicies() | ||||
| 	p.lister = podSecurityPolicyInformer.Lister() | ||||
| 	p.SetReadyFunc(podSecurityPolicyInformer.Informer().HasSynced) | ||||
| } | ||||
|  | ||||
| // Admit determines if the pod should be admitted based on the requested security context | ||||
| // and the available PSPs. | ||||
| // | ||||
| // 1.  Find available PSPs. | ||||
| // 2.  Create the providers, includes setting pre-allocated values if necessary. | ||||
| // 3.  Try to generate and validate a PSP with providers.  If we find one then admit the pod | ||||
| //     with the validated PSP.  If we don't find any reject the pod and give all errors from the | ||||
| //     failed attempts. | ||||
| func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { | ||||
| 	if ignore, err := shouldIgnore(a); err != nil { | ||||
| 		return err | ||||
| 	} else if ignore { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// only mutate if this is a CREATE request. On updates we only validate. | ||||
| 	if a.GetOperation() != admission.Create { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	pod := a.GetObject().(*api.Pod) | ||||
|  | ||||
| 	// compute the context. Mutation is allowed. ValidatedPSPAnnotation is not taken into account. | ||||
| 	allowedPod, pspName, validationErrs, err := p.computeSecurityContext(ctx, a, pod, true, "") | ||||
| 	if err != nil { | ||||
| 		return admission.NewForbidden(a, fmt.Errorf("PodSecurityPolicy: %w", err)) | ||||
| 	} | ||||
| 	if allowedPod != nil { | ||||
| 		*pod = *allowedPod | ||||
| 		// annotate and accept the pod | ||||
| 		klog.V(4).Infof("pod %s (generate: %s) in namespace %s validated against provider %s", pod.Name, pod.GenerateName, a.GetNamespace(), pspName) | ||||
| 		if pod.ObjectMeta.Annotations == nil { | ||||
| 			pod.ObjectMeta.Annotations = map[string]string{} | ||||
| 		} | ||||
| 		pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = pspName | ||||
| 		key := auditKeyPrefix + "/" + "admit-policy" | ||||
| 		if err := a.AddAnnotation(key, pspName); err != nil { | ||||
| 			klog.Warningf("failed to set admission audit annotation %s to %s: %v", key, pspName, err) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// we didn't validate against any provider, reject the pod and give the errors for each attempt | ||||
| 	klog.V(4).Infof("unable to admit pod %s (generate: %s) in namespace %s against any pod security policy: %v", pod.Name, pod.GenerateName, a.GetNamespace(), validationErrs) | ||||
| 	return admission.NewForbidden(a, fmt.Errorf("PodSecurityPolicy: unable to admit pod: %v", validationErrs)) | ||||
| } | ||||
|  | ||||
| // Validate verifies attributes against the PodSecurityPolicy | ||||
| func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { | ||||
| 	if ignore, err := shouldIgnore(a); err != nil { | ||||
| 		return err | ||||
| 	} else if ignore { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	pod := a.GetObject().(*api.Pod) | ||||
|  | ||||
| 	// compute the context. Mutation is not allowed. ValidatedPSPAnnotation is used as a hint to gain same speed-up. | ||||
| 	allowedPod, pspName, validationErrs, err := p.computeSecurityContext(ctx, a, pod, false, pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation]) | ||||
| 	if err != nil { | ||||
| 		return admission.NewForbidden(a, fmt.Errorf("PodSecurityPolicy: %w", err)) | ||||
| 	} | ||||
| 	if apiequality.Semantic.DeepEqual(pod, allowedPod) { | ||||
| 		key := auditKeyPrefix + "/" + "validate-policy" | ||||
| 		if err := a.AddAnnotation(key, pspName); err != nil { | ||||
| 			klog.Warningf("failed to set admission audit annotation %s to %s: %v", key, pspName, err) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// we didn't validate against any provider, reject the pod and give the errors for each attempt | ||||
| 	klog.V(4).Infof("unable to validate pod %s (generate: %s) in namespace %s against any pod security policy: %v", pod.Name, pod.GenerateName, a.GetNamespace(), validationErrs) | ||||
| 	return admission.NewForbidden(a, fmt.Errorf("PodSecurityPolicy: unable to validate pod: %v", validationErrs)) | ||||
| } | ||||
|  | ||||
| func shouldIgnore(a admission.Attributes) (bool, error) { | ||||
| 	if a.GetResource().GroupResource() != api.Resource("pods") { | ||||
| 		return true, nil | ||||
| 	} | ||||
| 	if len(a.GetSubresource()) != 0 { | ||||
| 		return true, nil | ||||
| 	} | ||||
|  | ||||
| 	// if we can't convert then fail closed since we've already checked that this is supposed to be a pod object. | ||||
| 	// this shouldn't normally happen during admission but could happen if an integrator passes a versioned | ||||
| 	// pod object rather than an internal object. | ||||
| 	if _, ok := a.GetObject().(*api.Pod); !ok { | ||||
| 		return false, admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject())) | ||||
| 	} | ||||
|  | ||||
| 	// if this is an update, see if we are only updating the ownerRef/finalizers.  Garbage collection does this | ||||
| 	// and we should allow it in general, since you had the power to update and the power to delete. | ||||
| 	// The worst that happens is that you delete something, but you aren't controlling the privileged object itself | ||||
| 	if a.GetOperation() == admission.Update && rbacregistry.IsOnlyMutatingGCFields(a.GetObject(), a.GetOldObject(), apiequality.Semantic) { | ||||
| 		return true, nil | ||||
| 	} | ||||
|  | ||||
| 	return false, nil | ||||
| } | ||||
|  | ||||
| // computeSecurityContext derives a valid security context while trying to avoid any changes to the given pod. I.e. | ||||
| // if there is a matching policy with the same security context as given, it will be reused. If there is no | ||||
| // matching policy the returned pod will be nil and the pspName empty. validatedPSPHint is the validated psp name | ||||
| // saved in kubernetes.io/psp annotation. This psp is usually the one we are looking for. | ||||
| func (p *Plugin) computeSecurityContext(ctx context.Context, a admission.Attributes, pod *api.Pod, specMutationAllowed bool, validatedPSPHint string) (*api.Pod, string, field.ErrorList, error) { | ||||
| 	// get all constraints that are usable by the user | ||||
| 	klog.V(4).Infof("getting pod security policies for pod %s (generate: %s)", pod.Name, pod.GenerateName) | ||||
| 	var saInfo user.Info | ||||
| 	if len(pod.Spec.ServiceAccountName) > 0 { | ||||
| 		saInfo = serviceaccount.UserInfo(a.GetNamespace(), pod.Spec.ServiceAccountName, "") | ||||
| 	} | ||||
|  | ||||
| 	policies, err := p.lister.List(labels.Everything()) | ||||
| 	if err != nil { | ||||
| 		return nil, "", nil, err | ||||
| 	} | ||||
|  | ||||
| 	// if we have no policies and want to succeed then return.  Otherwise we'll end up with no | ||||
| 	// providers and fail with "unable to validate against any pod security policy" below. | ||||
| 	if len(policies) == 0 && !p.failOnNoPolicies { | ||||
| 		return pod, "", nil, nil | ||||
| 	} | ||||
|  | ||||
| 	// sort policies by name to make order deterministic | ||||
| 	// If mutation is not allowed and validatedPSPHint is provided, check the validated policy first. | ||||
| 	sort.SliceStable(policies, func(i, j int) bool { | ||||
| 		if !specMutationAllowed { | ||||
| 			if policies[i].Name == validatedPSPHint { | ||||
| 				return true | ||||
| 			} | ||||
| 			if policies[j].Name == validatedPSPHint { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 		return strings.Compare(policies[i].Name, policies[j].Name) < 0 | ||||
| 	}) | ||||
|  | ||||
| 	providers, errs := p.createProvidersFromPolicies(policies, pod.Namespace) | ||||
| 	for _, err := range errs { | ||||
| 		klog.V(4).Infof("provider creation error: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if len(providers) == 0 { | ||||
| 		return nil, "", nil, fmt.Errorf("no providers available to validate pod request") | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		allowedMutatedPod   *api.Pod | ||||
| 		allowingMutatingPSP string | ||||
| 		// Map of PSP name to associated validation errors. | ||||
| 		validationErrs = map[string]field.ErrorList{} | ||||
| 	) | ||||
|  | ||||
| 	for _, provider := range providers { | ||||
| 		podCopy := pod.DeepCopy() | ||||
|  | ||||
| 		if errs := assignSecurityContext(provider, podCopy); len(errs) > 0 { | ||||
| 			validationErrs[provider.GetPSPName()] = errs | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// the entire pod validated | ||||
| 		mutated := !apiequality.Semantic.DeepEqual(pod, podCopy) | ||||
| 		if mutated && !specMutationAllowed { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if !isAuthorizedForPolicy(ctx, a.GetUserInfo(), saInfo, a.GetNamespace(), provider.GetPSPName(), p.authz) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		switch { | ||||
| 		case !mutated: | ||||
| 			// if it validated without mutating anything, use this result | ||||
| 			return podCopy, provider.GetPSPName(), nil, nil | ||||
|  | ||||
| 		case specMutationAllowed && allowedMutatedPod == nil: | ||||
| 			// if mutation is allowed and this is the first PSP to allow the pod, remember it, | ||||
| 			// but continue to see if another PSP allows without mutating | ||||
| 			allowedMutatedPod = podCopy | ||||
| 			allowingMutatingPSP = provider.GetPSPName() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if allowedMutatedPod != nil { | ||||
| 		return allowedMutatedPod, allowingMutatingPSP, nil, nil | ||||
| 	} | ||||
|  | ||||
| 	// Pod is rejected. Filter the validation errors to only include errors from authorized PSPs. | ||||
| 	aggregate := field.ErrorList{} | ||||
| 	for psp, errs := range validationErrs { | ||||
| 		if isAuthorizedForPolicy(ctx, a.GetUserInfo(), saInfo, a.GetNamespace(), psp, p.authz) { | ||||
| 			aggregate = append(aggregate, errs...) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, "", aggregate, nil | ||||
| } | ||||
|  | ||||
| // assignSecurityContext creates a security context for each container in the pod | ||||
| // and validates that the sc falls within the psp constraints.  All containers must validate against | ||||
| // the same psp or is not considered valid. | ||||
| func assignSecurityContext(provider psp.Provider, pod *api.Pod) field.ErrorList { | ||||
| 	errs := field.ErrorList{} | ||||
|  | ||||
| 	if err := provider.MutatePod(pod); err != nil { | ||||
| 		// TODO(tallclair): MutatePod should return a field.ErrorList | ||||
| 		errs = append(errs, field.Invalid(field.NewPath(""), pod, err.Error())) | ||||
| 	} | ||||
|  | ||||
| 	errs = append(errs, provider.ValidatePod(pod)...) | ||||
|  | ||||
| 	return errs | ||||
| } | ||||
|  | ||||
| // createProvidersFromPolicies creates providers from the constraints supplied. | ||||
| func (p *Plugin) createProvidersFromPolicies(psps []*policyv1beta1.PodSecurityPolicy, namespace string) ([]psp.Provider, []error) { | ||||
| 	var ( | ||||
| 		// collected providers | ||||
| 		providers []psp.Provider | ||||
| 		// collected errors to return | ||||
| 		errs []error | ||||
| 	) | ||||
|  | ||||
| 	for _, constraint := range psps { | ||||
| 		provider, err := psp.NewSimpleProvider(constraint, namespace, p.strategyFactory) | ||||
| 		if err != nil { | ||||
| 			errs = append(errs, fmt.Errorf("error creating provider for PSP %s: %v", constraint.Name, err)) | ||||
| 			continue | ||||
| 		} | ||||
| 		providers = append(providers, provider) | ||||
| 	} | ||||
| 	return providers, errs | ||||
| } | ||||
|  | ||||
| func isAuthorizedForPolicy(ctx context.Context, user, sa user.Info, namespace, policyName string, authz authorizer.Authorizer) bool { | ||||
| 	// Check the service account first, as that is the more common use case. | ||||
| 	return authorizedForPolicy(ctx, sa, namespace, policyName, authz) || | ||||
| 		authorizedForPolicy(ctx, user, namespace, policyName, authz) | ||||
| } | ||||
|  | ||||
| // authorizedForPolicy returns true if info is authorized to perform the "use" verb on the policy resource. | ||||
| // TODO: check against only the policy group when PSP will be completely moved out of the extensions | ||||
| func authorizedForPolicy(ctx context.Context, info user.Info, namespace string, policyName string, authz authorizer.Authorizer) bool { | ||||
| 	// Check against extensions API group for backward compatibility | ||||
| 	return authorizedForPolicyInAPIGroup(ctx, info, namespace, policyName, policy.GroupName, authz) || | ||||
| 		authorizedForPolicyInAPIGroup(ctx, info, namespace, policyName, extensions.GroupName, authz) | ||||
| } | ||||
|  | ||||
| // authorizedForPolicyInAPIGroup returns true if info is authorized to perform the "use" verb on the policy resource in the specified API group. | ||||
| func authorizedForPolicyInAPIGroup(ctx context.Context, info user.Info, namespace, policyName, apiGroupName string, authz authorizer.Authorizer) bool { | ||||
| 	if info == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	attr := buildAttributes(info, namespace, policyName, apiGroupName) | ||||
| 	decision, reason, err := authz.Authorize(ctx, attr) | ||||
| 	if err != nil { | ||||
| 		klog.V(5).Infof("cannot authorize for policy: %v,%v", reason, err) | ||||
| 	} | ||||
| 	return (decision == authorizer.DecisionAllow) | ||||
| } | ||||
|  | ||||
| // buildAttributes builds an attributes record for a SAR based on the user info and policy. | ||||
| func buildAttributes(info user.Info, namespace, policyName, apiGroupName string) authorizer.Attributes { | ||||
| 	// check against the namespace that the pod is being created in to allow per-namespace PSP grants. | ||||
| 	attr := authorizer.AttributesRecord{ | ||||
| 		User:            info, | ||||
| 		Verb:            "use", | ||||
| 		Namespace:       namespace, | ||||
| 		Name:            policyName, | ||||
| 		APIGroup:        apiGroupName, | ||||
| 		APIVersion:      "*", | ||||
| 		Resource:        "podsecuritypolicies", | ||||
| 		ResourceRequest: true, | ||||
| 	} | ||||
| 	return attr | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -420,17 +420,74 @@ run_deprecated_api_tests() { | ||||
|   set -o nounset | ||||
|   set -o errexit | ||||
|  | ||||
|   create_and_use_new_namespace | ||||
|   kube::log::status "Testing deprecated APIs" | ||||
|  | ||||
|   # Create deprecated CRD | ||||
|   kubectl "${kube_flags_with_token[@]:?}" create -f - << __EOF__ | ||||
| { | ||||
|   "kind": "CustomResourceDefinition", | ||||
|   "apiVersion": "apiextensions.k8s.io/v1", | ||||
|   "metadata": { | ||||
|     "name": "deprecated.example.com" | ||||
|   }, | ||||
|   "spec": { | ||||
|     "group": "example.com", | ||||
|     "scope": "Namespaced", | ||||
|     "names": { | ||||
|       "plural": "deprecated", | ||||
|       "kind": "DeprecatedKind" | ||||
|     }, | ||||
|     "versions": [ | ||||
|       { | ||||
|         "name": "v1", | ||||
|         "served": true, | ||||
|         "storage": true, | ||||
|         "schema": { | ||||
|           "openAPIV3Schema": { | ||||
|             "x-kubernetes-preserve-unknown-fields": true, | ||||
|             "type": "object" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       { | ||||
|         "name": "v1beta1", | ||||
|         "deprecated": true, | ||||
|         "served": true, | ||||
|         "storage": false, | ||||
|         "schema": { | ||||
|           "openAPIV3Schema": { | ||||
|             "x-kubernetes-preserve-unknown-fields": true, | ||||
|             "type": "object" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
| } | ||||
| __EOF__ | ||||
|  | ||||
|   # Ensure the API server has recognized and started serving the associated CR API | ||||
|   local tries=5 | ||||
|   for i in $(seq 1 $tries); do | ||||
|       local output | ||||
|       output=$(kubectl "${kube_flags[@]:?}" api-resources --api-group example.com -oname) | ||||
|       if kube::test::if_has_string "$output" deprecated.example.com; then | ||||
|           break | ||||
|       fi | ||||
|       echo "${i}: Waiting for CR API to be available" | ||||
|       sleep "$i" | ||||
|   done | ||||
|  | ||||
|   # Test deprecated API request output | ||||
|   # TODO(liggitt): switch this to a custom deprecated resource once CRDs support marking versions as deprecated | ||||
|   output_message=$(kubectl get podsecuritypolicies.v1beta1.policy 2>&1 "${kube_flags[@]}") | ||||
|   kube::test::if_has_string "${output_message}" 'PodSecurityPolicy is deprecated' | ||||
|   output_message=$(! kubectl get podsecuritypolicies.v1beta1.policy --warnings-as-errors 2>&1 "${kube_flags[@]}") | ||||
|   kube::test::if_has_string "${output_message}" 'PodSecurityPolicy is deprecated' | ||||
|   output_message=$(kubectl get deprecated.v1beta1.example.com 2>&1 "${kube_flags[@]}") | ||||
|   kube::test::if_has_string "${output_message}" 'example.com/v1beta1 DeprecatedKind is deprecated' | ||||
|   output_message=$(! kubectl get deprecated.v1beta1.example.com --warnings-as-errors 2>&1 "${kube_flags[@]}") | ||||
|   kube::test::if_has_string "${output_message}" 'example.com/v1beta1 DeprecatedKind is deprecated' | ||||
|   kube::test::if_has_string "${output_message}" 'error: 1 warning received' | ||||
|  | ||||
|   # Delete deprecated CRD | ||||
|   kubectl delete "${kube_flags[@]}" crd deprecated.example.com | ||||
|  | ||||
|   set +o nounset | ||||
|   set +o errexit | ||||
| } | ||||
|   | ||||
| @@ -90,7 +90,6 @@ nodes="nodes" | ||||
| persistentvolumeclaims="persistentvolumeclaims" | ||||
| persistentvolumes="persistentvolumes" | ||||
| pods="pods" | ||||
| podsecuritypolicies="podsecuritypolicies" | ||||
| podtemplates="podtemplates" | ||||
| replicasets="replicasets" | ||||
| replicationcontrollers="replicationcontrollers" | ||||
| @@ -934,7 +933,7 @@ runTests() { | ||||
|   # Kubectl deprecated APIs  # | ||||
|   ############################ | ||||
|  | ||||
|   if kube::test::if_supports_resource "${podsecuritypolicies}" ; then | ||||
|   if kube::test::if_supports_resource "${customresourcedefinitions}" ; then | ||||
|     record_command run_deprecated_api_tests | ||||
|   fi | ||||
|  | ||||
|   | ||||
| @@ -1,367 +0,0 @@ | ||||
| /* | ||||
| Copyright 2017 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package auth | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
|  | ||||
| 	v1 "k8s.io/api/core/v1" | ||||
| 	policyv1beta1 "k8s.io/api/policy/v1beta1" | ||||
| 	rbacv1 "k8s.io/api/rbac/v1" | ||||
| 	apierrors "k8s.io/apimachinery/pkg/api/errors" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||
| 	"k8s.io/apiserver/pkg/authentication/serviceaccount" | ||||
| 	clientset "k8s.io/client-go/kubernetes" | ||||
| 	restclient "k8s.io/client-go/rest" | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" | ||||
| 	psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" | ||||
| 	"k8s.io/kubernetes/test/e2e/framework" | ||||
| 	e2eauth "k8s.io/kubernetes/test/e2e/framework/auth" | ||||
| 	e2epod "k8s.io/kubernetes/test/e2e/framework/pod" | ||||
| 	e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" | ||||
| 	imageutils "k8s.io/kubernetes/test/utils/image" | ||||
| 	admissionapi "k8s.io/pod-security-admission/api" | ||||
| 	utilpointer "k8s.io/utils/pointer" | ||||
|  | ||||
| 	"github.com/onsi/ginkgo" | ||||
| ) | ||||
|  | ||||
| const nobodyUser = int64(65534) | ||||
|  | ||||
| var _ = SIGDescribe("PodSecurityPolicy [Feature:PodSecurityPolicy]", func() { | ||||
| 	f := framework.NewDefaultFramework("podsecuritypolicy") | ||||
| 	f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged | ||||
| 	f.SkipPrivilegedPSPBinding = true | ||||
|  | ||||
| 	// Client that will impersonate the default service account, in order to run | ||||
| 	// with reduced privileges. | ||||
| 	var c clientset.Interface | ||||
| 	var ns string // Test namespace, for convenience | ||||
| 	ginkgo.BeforeEach(func() { | ||||
| 		if !framework.IsPodSecurityPolicyEnabled(f.ClientSet) { | ||||
| 			framework.Failf("PodSecurityPolicy not enabled") | ||||
| 			return | ||||
| 		} | ||||
| 		if !e2eauth.IsRBACEnabled(f.ClientSet.RbacV1()) { | ||||
| 			e2eskipper.Skipf("RBAC not enabled") | ||||
| 		} | ||||
| 		ns = f.Namespace.Name | ||||
|  | ||||
| 		ginkgo.By("Creating a kubernetes client that impersonates the default service account") | ||||
| 		config, err := framework.LoadConfig() | ||||
| 		framework.ExpectNoError(err) | ||||
| 		config.Impersonate = restclient.ImpersonationConfig{ | ||||
| 			UserName: serviceaccount.MakeUsername(ns, "default"), | ||||
| 			Groups:   serviceaccount.MakeGroupNames(ns), | ||||
| 		} | ||||
| 		c, err = clientset.NewForConfig(config) | ||||
| 		framework.ExpectNoError(err) | ||||
|  | ||||
| 		ginkgo.By("Binding the edit role to the default SA") | ||||
| 		err = e2eauth.BindClusterRole(f.ClientSet.RbacV1(), "edit", ns, | ||||
| 			rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Namespace: ns, Name: "default"}) | ||||
| 		framework.ExpectNoError(err) | ||||
| 	}) | ||||
|  | ||||
| 	ginkgo.It("should forbid pod creation when no PSP is available", func() { | ||||
| 		ginkgo.By("Running a restricted pod") | ||||
| 		_, err := c.CoreV1().Pods(ns).Create(context.TODO(), restrictedPod("restricted"), metav1.CreateOptions{}) | ||||
| 		expectForbidden(err) | ||||
| 	}) | ||||
|  | ||||
| 	ginkgo.It("should enforce the restricted policy.PodSecurityPolicy", func() { | ||||
| 		ginkgo.By("Creating & Binding a restricted policy for the test service account") | ||||
| 		_, cleanup := createAndBindPSP(f, restrictedPSP("restrictive")) | ||||
| 		defer cleanup() | ||||
|  | ||||
| 		ginkgo.By("Running a restricted pod") | ||||
| 		pod, err := c.CoreV1().Pods(ns).Create(context.TODO(), restrictedPod("allowed"), metav1.CreateOptions{}) | ||||
| 		framework.ExpectNoError(err) | ||||
| 		framework.ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(c, pod.Name, pod.Namespace)) | ||||
|  | ||||
| 		testPrivilegedPods(func(pod *v1.Pod) { | ||||
| 			_, err := c.CoreV1().Pods(ns).Create(context.TODO(), pod, metav1.CreateOptions{}) | ||||
| 			expectForbidden(err) | ||||
| 		}) | ||||
| 	}) | ||||
|  | ||||
| 	ginkgo.It("should allow pods under the privileged policy.PodSecurityPolicy", func() { | ||||
| 		ginkgo.By("Creating & Binding a privileged policy for the test service account") | ||||
| 		// Ensure that the permissive policy is used even in the presence of the restricted policy. | ||||
| 		_, cleanup := createAndBindPSP(f, restrictedPSP("restrictive")) | ||||
| 		defer cleanup() | ||||
| 		expectedPSP, cleanup := createAndBindPSP(f, privilegedPSP("permissive")) | ||||
| 		defer cleanup() | ||||
|  | ||||
| 		testPrivilegedPods(func(pod *v1.Pod) { | ||||
| 			p, err := c.CoreV1().Pods(ns).Create(context.TODO(), pod, metav1.CreateOptions{}) | ||||
| 			framework.ExpectNoError(err) | ||||
| 			framework.ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(c, p.Name, p.Namespace)) | ||||
|  | ||||
| 			// Verify expected PSP was used. | ||||
| 			p, err = c.CoreV1().Pods(ns).Get(context.TODO(), p.Name, metav1.GetOptions{}) | ||||
| 			framework.ExpectNoError(err) | ||||
| 			validated, found := p.Annotations[psputil.ValidatedPSPAnnotation] | ||||
| 			framework.ExpectEqual(found, true, "PSP annotation not found") | ||||
| 			framework.ExpectEqual(validated, expectedPSP.Name, "Unexpected validated PSP") | ||||
| 		}) | ||||
| 	}) | ||||
| }) | ||||
|  | ||||
| func expectForbidden(err error) { | ||||
| 	framework.ExpectError(err, "should be forbidden") | ||||
| 	framework.ExpectEqual(apierrors.IsForbidden(err), true, "should be forbidden error") | ||||
| } | ||||
|  | ||||
| func testPrivilegedPods(tester func(pod *v1.Pod)) { | ||||
| 	ginkgo.By("Running a privileged pod", func() { | ||||
| 		privileged := restrictedPod("privileged") | ||||
| 		privileged.Spec.Containers[0].SecurityContext.Privileged = boolPtr(true) | ||||
| 		privileged.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation = nil | ||||
| 		tester(privileged) | ||||
| 	}) | ||||
|  | ||||
| 	ginkgo.By("Running a HostPath pod", func() { | ||||
| 		hostpath := restrictedPod("hostpath") | ||||
| 		hostpath.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{{ | ||||
| 			Name:      "hp", | ||||
| 			MountPath: "/hp", | ||||
| 		}} | ||||
| 		hostpath.Spec.Volumes = []v1.Volume{{ | ||||
| 			Name: "hp", | ||||
| 			VolumeSource: v1.VolumeSource{ | ||||
| 				HostPath: &v1.HostPathVolumeSource{Path: "/tmp"}, | ||||
| 			}, | ||||
| 		}} | ||||
| 		tester(hostpath) | ||||
| 	}) | ||||
|  | ||||
| 	ginkgo.By("Running a HostNetwork pod", func() { | ||||
| 		hostnet := restrictedPod("hostnet") | ||||
| 		hostnet.Spec.HostNetwork = true | ||||
| 		tester(hostnet) | ||||
| 	}) | ||||
|  | ||||
| 	ginkgo.By("Running a HostPID pod", func() { | ||||
| 		hostpid := restrictedPod("hostpid") | ||||
| 		hostpid.Spec.HostPID = true | ||||
| 		tester(hostpid) | ||||
| 	}) | ||||
|  | ||||
| 	ginkgo.By("Running a HostIPC pod", func() { | ||||
| 		hostipc := restrictedPod("hostipc") | ||||
| 		hostipc.Spec.HostIPC = true | ||||
| 		tester(hostipc) | ||||
| 	}) | ||||
|  | ||||
| 	ginkgo.By("Running an unconfined Seccomp pod", func() { | ||||
| 		unconfined := restrictedPod("seccomp") | ||||
| 		unconfined.Annotations[v1.SeccompPodAnnotationKey] = "unconfined" | ||||
| 		tester(unconfined) | ||||
| 	}) | ||||
|  | ||||
| 	ginkgo.By("Running a SYS_ADMIN pod", func() { | ||||
| 		sysadmin := restrictedPod("sysadmin") | ||||
| 		sysadmin.Spec.Containers[0].SecurityContext.Capabilities = &v1.Capabilities{ | ||||
| 			Add: []v1.Capability{"SYS_ADMIN"}, | ||||
| 		} | ||||
| 		sysadmin.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation = nil | ||||
| 		tester(sysadmin) | ||||
| 	}) | ||||
|  | ||||
| 	ginkgo.By("Running a RunAsGroup pod", func() { | ||||
| 		sysadmin := restrictedPod("runasgroup") | ||||
| 		gid := int64(0) | ||||
| 		sysadmin.Spec.Containers[0].SecurityContext.RunAsGroup = &gid | ||||
| 		tester(sysadmin) | ||||
| 	}) | ||||
|  | ||||
| 	ginkgo.By("Running a RunAsUser pod", func() { | ||||
| 		sysadmin := restrictedPod("runasuser") | ||||
| 		uid := int64(0) | ||||
| 		sysadmin.Spec.Containers[0].SecurityContext.RunAsUser = &uid | ||||
| 		tester(sysadmin) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // createAndBindPSP creates a PSP in the policy API group. | ||||
| func createAndBindPSP(f *framework.Framework, pspTemplate *policyv1beta1.PodSecurityPolicy) (psp *policyv1beta1.PodSecurityPolicy, cleanup func()) { | ||||
| 	// Create the PodSecurityPolicy object. | ||||
| 	psp = pspTemplate.DeepCopy() | ||||
| 	// Add the namespace to the name to ensure uniqueness and tie it to the namespace. | ||||
| 	ns := f.Namespace.Name | ||||
| 	name := fmt.Sprintf("%s-%s", ns, psp.Name) | ||||
| 	psp.Name = name | ||||
| 	psp, err := f.ClientSet.PolicyV1beta1().PodSecurityPolicies().Create(context.TODO(), psp, metav1.CreateOptions{}) | ||||
| 	framework.ExpectNoError(err, "Failed to create PSP") | ||||
|  | ||||
| 	// Create the Role to bind it to the namespace. | ||||
| 	_, err = f.ClientSet.RbacV1().Roles(ns).Create(context.TODO(), &rbacv1.Role{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name: name, | ||||
| 		}, | ||||
| 		Rules: []rbacv1.PolicyRule{{ | ||||
| 			APIGroups:     []string{"policy"}, | ||||
| 			Resources:     []string{"podsecuritypolicies"}, | ||||
| 			ResourceNames: []string{name}, | ||||
| 			Verbs:         []string{"use"}, | ||||
| 		}}, | ||||
| 	}, metav1.CreateOptions{}) | ||||
| 	framework.ExpectNoError(err, "Failed to create PSP role") | ||||
|  | ||||
| 	// Bind the role to the namespace. | ||||
| 	err = e2eauth.BindRoleInNamespace(f.ClientSet.RbacV1(), name, ns, rbacv1.Subject{ | ||||
| 		Kind:      rbacv1.ServiceAccountKind, | ||||
| 		Namespace: ns, | ||||
| 		Name:      "default", | ||||
| 	}) | ||||
| 	framework.ExpectNoError(err) | ||||
|  | ||||
| 	framework.ExpectNoError(e2eauth.WaitForNamedAuthorizationUpdate(f.ClientSet.AuthorizationV1(), | ||||
| 		serviceaccount.MakeUsername(ns, "default"), ns, "use", name, | ||||
| 		schema.GroupResource{Group: "policy", Resource: "podsecuritypolicies"}, true)) | ||||
|  | ||||
| 	return psp, func() { | ||||
| 		// Cleanup non-namespaced PSP object. | ||||
| 		f.ClientSet.PolicyV1beta1().PodSecurityPolicies().Delete(context.TODO(), name, metav1.DeleteOptions{}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func restrictedPod(name string) *v1.Pod { | ||||
| 	return &v1.Pod{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name: name, | ||||
| 			Annotations: map[string]string{ | ||||
| 				v1.SeccompPodAnnotationKey:                            v1.SeccompProfileRuntimeDefault, | ||||
| 				v1.AppArmorBetaContainerAnnotationKeyPrefix + "pause": v1.AppArmorBetaProfileRuntimeDefault, | ||||
| 			}, | ||||
| 		}, | ||||
| 		Spec: v1.PodSpec{ | ||||
| 			Containers: []v1.Container{{ | ||||
| 				Name:  "pause", | ||||
| 				Image: imageutils.GetPauseImageName(), | ||||
| 				SecurityContext: &v1.SecurityContext{ | ||||
| 					AllowPrivilegeEscalation: boolPtr(false), | ||||
| 					RunAsUser:                utilpointer.Int64Ptr(nobodyUser), | ||||
| 					RunAsGroup:               utilpointer.Int64Ptr(nobodyUser), | ||||
| 				}, | ||||
| 			}}, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // privilegedPSPInPolicy creates a PodSecurityPolicy (in the "policy" API Group) that allows everything. | ||||
| func privilegedPSP(name string) *policyv1beta1.PodSecurityPolicy { | ||||
| 	return &policyv1beta1.PodSecurityPolicy{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:        name, | ||||
| 			Annotations: map[string]string{seccomp.AllowedProfilesAnnotationKey: seccomp.AllowAny}, | ||||
| 		}, | ||||
| 		Spec: policyv1beta1.PodSecurityPolicySpec{ | ||||
| 			Privileged:               true, | ||||
| 			AllowPrivilegeEscalation: utilpointer.BoolPtr(true), | ||||
| 			AllowedCapabilities:      []v1.Capability{"*"}, | ||||
| 			Volumes:                  []policyv1beta1.FSType{policyv1beta1.All}, | ||||
| 			HostNetwork:              true, | ||||
| 			HostPorts:                []policyv1beta1.HostPortRange{{Min: 0, Max: 65535}}, | ||||
| 			HostIPC:                  true, | ||||
| 			HostPID:                  true, | ||||
| 			RunAsUser: policyv1beta1.RunAsUserStrategyOptions{ | ||||
| 				Rule: policyv1beta1.RunAsUserStrategyRunAsAny, | ||||
| 			}, | ||||
| 			RunAsGroup: &policyv1beta1.RunAsGroupStrategyOptions{ | ||||
| 				Rule: policyv1beta1.RunAsGroupStrategyRunAsAny, | ||||
| 			}, | ||||
| 			SELinux: policyv1beta1.SELinuxStrategyOptions{ | ||||
| 				Rule: policyv1beta1.SELinuxStrategyRunAsAny, | ||||
| 			}, | ||||
| 			SupplementalGroups: policyv1beta1.SupplementalGroupsStrategyOptions{ | ||||
| 				Rule: policyv1beta1.SupplementalGroupsStrategyRunAsAny, | ||||
| 			}, | ||||
| 			FSGroup: policyv1beta1.FSGroupStrategyOptions{ | ||||
| 				Rule: policyv1beta1.FSGroupStrategyRunAsAny, | ||||
| 			}, | ||||
| 			ReadOnlyRootFilesystem: false, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // restrictedPSPInPolicy creates a PodSecurityPolicy (in the "policy" API Group) that is most strict. | ||||
| func restrictedPSP(name string) *policyv1beta1.PodSecurityPolicy { | ||||
| 	return &policyv1beta1.PodSecurityPolicy{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name: name, | ||||
| 			Annotations: map[string]string{ | ||||
| 				seccomp.AllowedProfilesAnnotationKey:        v1.SeccompProfileRuntimeDefault, | ||||
| 				seccomp.DefaultProfileAnnotationKey:         v1.SeccompProfileRuntimeDefault, | ||||
| 				v1.AppArmorBetaAllowedProfilesAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault, | ||||
| 				v1.AppArmorBetaDefaultProfileAnnotationKey:  v1.AppArmorBetaProfileRuntimeDefault, | ||||
| 			}, | ||||
| 		}, | ||||
| 		Spec: policyv1beta1.PodSecurityPolicySpec{ | ||||
| 			Privileged:               false, | ||||
| 			AllowPrivilegeEscalation: utilpointer.BoolPtr(false), | ||||
| 			RequiredDropCapabilities: []v1.Capability{ | ||||
| 				"AUDIT_WRITE", | ||||
| 				"CHOWN", | ||||
| 				"DAC_OVERRIDE", | ||||
| 				"FOWNER", | ||||
| 				"FSETID", | ||||
| 				"KILL", | ||||
| 				"MKNOD", | ||||
| 				"NET_RAW", | ||||
| 				"SETGID", | ||||
| 				"SETUID", | ||||
| 				"SYS_CHROOT", | ||||
| 			}, | ||||
| 			Volumes: []policyv1beta1.FSType{ | ||||
| 				policyv1beta1.ConfigMap, | ||||
| 				policyv1beta1.EmptyDir, | ||||
| 				policyv1beta1.PersistentVolumeClaim, | ||||
| 				"projected", | ||||
| 				policyv1beta1.Secret, | ||||
| 			}, | ||||
| 			HostNetwork: false, | ||||
| 			HostIPC:     false, | ||||
| 			HostPID:     false, | ||||
| 			RunAsUser: policyv1beta1.RunAsUserStrategyOptions{ | ||||
| 				Rule: policyv1beta1.RunAsUserStrategyMustRunAsNonRoot, | ||||
| 			}, | ||||
| 			RunAsGroup: &policyv1beta1.RunAsGroupStrategyOptions{ | ||||
| 				Rule: policyv1beta1.RunAsGroupStrategyMustRunAs, | ||||
| 				Ranges: []policyv1beta1.IDRange{ | ||||
| 					{Min: nobodyUser, Max: nobodyUser}}, | ||||
| 			}, | ||||
| 			SELinux: policyv1beta1.SELinuxStrategyOptions{ | ||||
| 				Rule: policyv1beta1.SELinuxStrategyRunAsAny, | ||||
| 			}, | ||||
| 			SupplementalGroups: policyv1beta1.SupplementalGroupsStrategyOptions{ | ||||
| 				Rule: policyv1beta1.SupplementalGroupsStrategyRunAsAny, | ||||
| 			}, | ||||
| 			FSGroup: policyv1beta1.FSGroupStrategyOptions{ | ||||
| 				Rule: policyv1beta1.FSGroupStrategyRunAsAny, | ||||
| 			}, | ||||
| 			ReadOnlyRootFilesystem: false, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func boolPtr(b bool) *bool { | ||||
| 	return &b | ||||
| } | ||||
| @@ -83,7 +83,6 @@ type Framework struct { | ||||
| 	Namespace                        *v1.Namespace   // Every test has at least one namespace unless creation is skipped | ||||
| 	namespacesToDelete               []*v1.Namespace // Some tests have more than one. | ||||
| 	NamespaceDeletionTimeout         time.Duration | ||||
| 	SkipPrivilegedPSPBinding         bool               // Whether to skip creating a binding to the privileged PSP in the test namespace | ||||
| 	NamespacePodSecurityEnforceLevel admissionapi.Level // The pod security enforcement level for namespaces to be applied. | ||||
|  | ||||
| 	gatherer *ContainerResourceGatherer | ||||
| @@ -545,10 +544,6 @@ func (f *Framework) CreateNamespace(baseName string, labels map[string]string) ( | ||||
| 	// fail to create serviceAccount in it. | ||||
| 	f.AddNamespacesToDelete(ns) | ||||
|  | ||||
| 	if err == nil && !f.SkipPrivilegedPSPBinding { | ||||
| 		CreatePrivilegedPSPBinding(f.ClientSet, ns.Name) | ||||
| 	} | ||||
|  | ||||
| 	return ns, err | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,192 +0,0 @@ | ||||
| /* | ||||
| Copyright 2017 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package framework | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	v1 "k8s.io/api/core/v1" | ||||
| 	policyv1beta1 "k8s.io/api/policy/v1beta1" | ||||
| 	rbacv1 "k8s.io/api/rbac/v1" | ||||
| 	apierrors "k8s.io/apimachinery/pkg/api/errors" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||
| 	"k8s.io/apiserver/pkg/authentication/serviceaccount" | ||||
| 	clientset "k8s.io/client-go/kubernetes" | ||||
| 	imageutils "k8s.io/kubernetes/test/utils/image" | ||||
|  | ||||
| 	"github.com/onsi/ginkgo" | ||||
|  | ||||
| 	// TODO: Remove the following imports (ref: https://github.com/kubernetes/kubernetes/issues/81245) | ||||
| 	e2eauth "k8s.io/kubernetes/test/e2e/framework/auth" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	podSecurityPolicyPrivileged = "e2e-test-privileged-psp" | ||||
|  | ||||
| 	// allowAny is the wildcard used to allow any profile. | ||||
| 	allowAny = "*" | ||||
|  | ||||
| 	// allowedProfilesAnnotationKey specifies the allowed seccomp profiles. | ||||
| 	allowedProfilesAnnotationKey = "seccomp.security.alpha.kubernetes.io/allowedProfileNames" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	isPSPEnabledOnce sync.Once | ||||
| 	isPSPEnabled     bool | ||||
| ) | ||||
|  | ||||
| // privilegedPSP creates a PodSecurityPolicy that allows everything. | ||||
| func privilegedPSP(name string) *policyv1beta1.PodSecurityPolicy { | ||||
| 	allowPrivilegeEscalation := true | ||||
| 	return &policyv1beta1.PodSecurityPolicy{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:        name, | ||||
| 			Annotations: map[string]string{allowedProfilesAnnotationKey: allowAny}, | ||||
| 		}, | ||||
| 		Spec: policyv1beta1.PodSecurityPolicySpec{ | ||||
| 			Privileged:               true, | ||||
| 			AllowPrivilegeEscalation: &allowPrivilegeEscalation, | ||||
| 			AllowedCapabilities:      []v1.Capability{"*"}, | ||||
| 			Volumes:                  []policyv1beta1.FSType{policyv1beta1.All}, | ||||
| 			HostNetwork:              true, | ||||
| 			HostPorts:                []policyv1beta1.HostPortRange{{Min: 0, Max: 65535}}, | ||||
| 			HostIPC:                  true, | ||||
| 			HostPID:                  true, | ||||
| 			RunAsUser: policyv1beta1.RunAsUserStrategyOptions{ | ||||
| 				Rule: policyv1beta1.RunAsUserStrategyRunAsAny, | ||||
| 			}, | ||||
| 			SELinux: policyv1beta1.SELinuxStrategyOptions{ | ||||
| 				Rule: policyv1beta1.SELinuxStrategyRunAsAny, | ||||
| 			}, | ||||
| 			SupplementalGroups: policyv1beta1.SupplementalGroupsStrategyOptions{ | ||||
| 				Rule: policyv1beta1.SupplementalGroupsStrategyRunAsAny, | ||||
| 			}, | ||||
| 			FSGroup: policyv1beta1.FSGroupStrategyOptions{ | ||||
| 				Rule: policyv1beta1.FSGroupStrategyRunAsAny, | ||||
| 			}, | ||||
| 			ReadOnlyRootFilesystem: false, | ||||
| 			AllowedUnsafeSysctls:   []string{"*"}, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // IsPodSecurityPolicyEnabled returns true if PodSecurityPolicy is enabled. Otherwise false. | ||||
| func IsPodSecurityPolicyEnabled(kubeClient clientset.Interface) bool { | ||||
| 	isPSPEnabledOnce.Do(func() { | ||||
| 		psps, err := kubeClient.PolicyV1beta1().PodSecurityPolicies().List(context.TODO(), metav1.ListOptions{}) | ||||
| 		if err != nil { | ||||
| 			Logf("Error listing PodSecurityPolicies; assuming PodSecurityPolicy is disabled: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 		if psps == nil || len(psps.Items) == 0 { | ||||
| 			Logf("No PodSecurityPolicies found; assuming PodSecurityPolicy is disabled.") | ||||
| 			return | ||||
| 		} | ||||
| 		Logf("Found PodSecurityPolicies; testing pod creation to see if PodSecurityPolicy is enabled") | ||||
| 		testPod := &v1.Pod{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{GenerateName: "psp-test-pod-"}, | ||||
| 			Spec:       v1.PodSpec{Containers: []v1.Container{{Name: "test", Image: imageutils.GetPauseImageName()}}}, | ||||
| 		} | ||||
| 		dryRunPod, err := kubeClient.CoreV1().Pods("kube-system").Create(context.TODO(), testPod, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}) | ||||
| 		if err != nil { | ||||
| 			if strings.Contains(err.Error(), "PodSecurityPolicy") { | ||||
| 				Logf("PodSecurityPolicy error creating dryrun pod; assuming PodSecurityPolicy is enabled: %v", err) | ||||
| 				isPSPEnabled = true | ||||
| 			} else { | ||||
| 				Logf("Error creating dryrun pod; assuming PodSecurityPolicy is disabled: %v", err) | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
| 		pspAnnotation, pspAnnotationExists := dryRunPod.Annotations["kubernetes.io/psp"] | ||||
| 		if !pspAnnotationExists { | ||||
| 			Logf("No PSP annotation exists on dry run pod; assuming PodSecurityPolicy is disabled") | ||||
| 			return | ||||
| 		} | ||||
| 		Logf("PSP annotation exists on dry run pod: %q; assuming PodSecurityPolicy is enabled", pspAnnotation) | ||||
| 		isPSPEnabled = true | ||||
| 	}) | ||||
| 	return isPSPEnabled | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	privilegedPSPOnce sync.Once | ||||
| ) | ||||
|  | ||||
| // CreatePrivilegedPSPBinding creates the privileged PSP & role | ||||
| func CreatePrivilegedPSPBinding(kubeClient clientset.Interface, namespace string) { | ||||
| 	if !IsPodSecurityPolicyEnabled(kubeClient) { | ||||
| 		return | ||||
| 	} | ||||
| 	// Create the privileged PSP & role | ||||
| 	privilegedPSPOnce.Do(func() { | ||||
| 		_, err := kubeClient.PolicyV1beta1().PodSecurityPolicies().Get(context.TODO(), podSecurityPolicyPrivileged, metav1.GetOptions{}) | ||||
| 		if !apierrors.IsNotFound(err) { | ||||
| 			// Privileged PSP was already created. | ||||
| 			ExpectNoError(err, "Failed to get PodSecurityPolicy %s", podSecurityPolicyPrivileged) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		psp := privilegedPSP(podSecurityPolicyPrivileged) | ||||
| 		_, err = kubeClient.PolicyV1beta1().PodSecurityPolicies().Create(context.TODO(), psp, metav1.CreateOptions{}) | ||||
| 		if !apierrors.IsAlreadyExists(err) { | ||||
| 			ExpectNoError(err, "Failed to create PSP %s", podSecurityPolicyPrivileged) | ||||
| 		} | ||||
|  | ||||
| 		if e2eauth.IsRBACEnabled(kubeClient.RbacV1()) { | ||||
| 			// Create the Role to bind it to the namespace. | ||||
| 			_, err = kubeClient.RbacV1().ClusterRoles().Create(context.TODO(), &rbacv1.ClusterRole{ | ||||
| 				ObjectMeta: metav1.ObjectMeta{Name: podSecurityPolicyPrivileged}, | ||||
| 				Rules: []rbacv1.PolicyRule{{ | ||||
| 					APIGroups:     []string{"extensions"}, | ||||
| 					Resources:     []string{"podsecuritypolicies"}, | ||||
| 					ResourceNames: []string{podSecurityPolicyPrivileged}, | ||||
| 					Verbs:         []string{"use"}, | ||||
| 				}}, | ||||
| 			}, metav1.CreateOptions{}) | ||||
| 			if !apierrors.IsAlreadyExists(err) { | ||||
| 				ExpectNoError(err, "Failed to create PSP role") | ||||
| 			} | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	if e2eauth.IsRBACEnabled(kubeClient.RbacV1()) { | ||||
| 		ginkgo.By(fmt.Sprintf("Binding the %s PodSecurityPolicy to the default service account in %s", | ||||
| 			podSecurityPolicyPrivileged, namespace)) | ||||
| 		err := e2eauth.BindClusterRoleInNamespace(kubeClient.RbacV1(), | ||||
| 			podSecurityPolicyPrivileged, | ||||
| 			namespace, | ||||
| 			rbacv1.Subject{ | ||||
| 				Kind:      rbacv1.ServiceAccountKind, | ||||
| 				Namespace: namespace, | ||||
| 				Name:      "default", | ||||
| 			}, | ||||
| 			rbacv1.Subject{ | ||||
| 				Kind:     rbacv1.GroupKind, | ||||
| 				APIGroup: rbacv1.GroupName, | ||||
| 				Name:     "system:serviceaccounts:" + namespace, | ||||
| 			}, | ||||
| 		) | ||||
| 		ExpectNoError(err) | ||||
| 		ExpectNoError(e2eauth.WaitForNamedAuthorizationUpdate(kubeClient.AuthorizationV1(), | ||||
| 			serviceaccount.MakeUsername(namespace, "default"), namespace, "use", podSecurityPolicyPrivileged, | ||||
| 			schema.GroupResource{Group: "extensions", Resource: "podsecuritypolicies"}, true)) | ||||
| 	} | ||||
| } | ||||
| @@ -32,14 +32,12 @@ import ( | ||||
| 	"github.com/onsi/gomega" | ||||
|  | ||||
| 	v1 "k8s.io/api/core/v1" | ||||
| 	rbacv1 "k8s.io/api/rbac/v1" | ||||
| 	apierrors "k8s.io/apimachinery/pkg/api/errors" | ||||
| 	"k8s.io/apimachinery/pkg/api/resource" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| 	"k8s.io/client-go/dynamic" | ||||
| 	clientset "k8s.io/client-go/kubernetes" | ||||
| 	"k8s.io/kubernetes/test/e2e/framework" | ||||
| @@ -65,11 +63,6 @@ const ( | ||||
| 	maxValidSize string     = "10Ei" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// ClusterRole name for e2e test Priveledged Pod Security Policy User | ||||
| 	podSecurityPolicyPrivilegedClusterRoleName = "e2e-test-privileged-psp" | ||||
| ) | ||||
|  | ||||
| // VerifyFSGroupInPod verifies that the passed in filePath contains the expectedFSGroup | ||||
| func VerifyFSGroupInPod(f *framework.Framework, filePath, expectedFSGroup string, pod *v1.Pod) { | ||||
| 	cmd := fmt.Sprintf("ls -l %s", filePath) | ||||
| @@ -417,54 +410,6 @@ func StartExternalProvisioner(c clientset.Interface, ns string, externalPluginNa | ||||
| 	return pod | ||||
| } | ||||
|  | ||||
| // PrivilegedTestPSPClusterRoleBinding test Pod Security Policy Role bindings | ||||
| func PrivilegedTestPSPClusterRoleBinding(client clientset.Interface, | ||||
| 	namespace string, | ||||
| 	teardown bool, | ||||
| 	saNames []string) { | ||||
| 	bindingString := "Binding" | ||||
| 	if teardown { | ||||
| 		bindingString = "Unbinding" | ||||
| 	} | ||||
| 	roleBindingClient := client.RbacV1().RoleBindings(namespace) | ||||
| 	for _, saName := range saNames { | ||||
| 		ginkgo.By(fmt.Sprintf("%v priviledged Pod Security Policy to the service account %s", bindingString, saName)) | ||||
| 		binding := &rbacv1.RoleBinding{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Name:      "psp-" + saName, | ||||
| 				Namespace: namespace, | ||||
| 			}, | ||||
| 			Subjects: []rbacv1.Subject{ | ||||
| 				{ | ||||
| 					Kind:      rbacv1.ServiceAccountKind, | ||||
| 					Name:      saName, | ||||
| 					Namespace: namespace, | ||||
| 				}, | ||||
| 			}, | ||||
| 			RoleRef: rbacv1.RoleRef{ | ||||
| 				Kind:     "ClusterRole", | ||||
| 				Name:     podSecurityPolicyPrivilegedClusterRoleName, | ||||
| 				APIGroup: "rbac.authorization.k8s.io", | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		roleBindingClient.Delete(context.TODO(), binding.GetName(), metav1.DeleteOptions{}) | ||||
| 		err := wait.Poll(2*time.Second, 2*time.Minute, func() (bool, error) { | ||||
| 			_, err := roleBindingClient.Get(context.TODO(), binding.GetName(), metav1.GetOptions{}) | ||||
| 			return apierrors.IsNotFound(err), nil | ||||
| 		}) | ||||
| 		framework.ExpectNoError(err, "Timed out waiting for RBAC binding %s deletion: %v", binding.GetName(), err) | ||||
|  | ||||
| 		if teardown { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		_, err = roleBindingClient.Create(context.TODO(), binding, metav1.CreateOptions{}) | ||||
| 		framework.ExpectNoError(err, "Failed to create %s role binding: %v", binding.GetName(), err) | ||||
|  | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func isSudoPresent(nodeIP string, provider string) bool { | ||||
| 	framework.Logf("Checking if sudo command is present") | ||||
| 	sshResult, err := e2essh.SSH("sudo --version", nodeIP, provider) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Kubernetes Prow Robot
					Kubernetes Prow Robot