mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 18:28:13 +00:00 
			
		
		
		
	add authz checks to allowed policies admission
This commit is contained in:
		
							
								
								
									
										221
									
								
								examples/podsecuritypolicy/rbac/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								examples/podsecuritypolicy/rbac/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,221 @@ | |||||||
|  | <!-- BEGIN MUNGE: UNVERSIONED_WARNING --> | ||||||
|  |  | ||||||
|  | <!-- BEGIN STRIP_FOR_RELEASE --> | ||||||
|  |  | ||||||
|  | <img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING" | ||||||
|  |      width="25" height="25"> | ||||||
|  | <img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING" | ||||||
|  |      width="25" height="25"> | ||||||
|  | <img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING" | ||||||
|  |      width="25" height="25"> | ||||||
|  | <img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING" | ||||||
|  |      width="25" height="25"> | ||||||
|  | <img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING" | ||||||
|  |      width="25" height="25"> | ||||||
|  |  | ||||||
|  | <h2>PLEASE NOTE: This document applies to the HEAD of the source tree</h2> | ||||||
|  |  | ||||||
|  | If you are using a released version of Kubernetes, you should | ||||||
|  | refer to the docs that go with that version. | ||||||
|  |  | ||||||
|  | Documentation for other releases can be found at | ||||||
|  | [releases.k8s.io](http://releases.k8s.io). | ||||||
|  | </strong> | ||||||
|  | -- | ||||||
|  |  | ||||||
|  | <!-- END STRIP_FOR_RELEASE --> | ||||||
|  |  | ||||||
|  | <!-- END MUNGE: UNVERSIONED_WARNING --> | ||||||
|  |  | ||||||
|  | ## PSP RBAC Example | ||||||
|  |  | ||||||
|  | This example demonstrates the usage of *PodSecurityPolicy* to control access to privileged containers | ||||||
|  | based on role and groups. | ||||||
|  |  | ||||||
|  | ### Prerequisites | ||||||
|  |  | ||||||
|  | The server must be started to enable the appropriate APIs and flags | ||||||
|  |  | ||||||
|  | 1.  allow privileged containers | ||||||
|  | 1.  allow security contexts | ||||||
|  | 1.  enable RBAC and accept any token | ||||||
|  | 1.  enable PodSecurityPolicies | ||||||
|  | 1.  use the PodSecurityPolicy admission controller | ||||||
|  |  | ||||||
|  | If you are using the `local-up-cluster.sh` script you may enable these settings with the following syntax | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | PSP_ADMISSION=true ALLOW_PRIVILEGED=true ALLOW_SECURITY_CONTEXT=true ALLOW_ANY_TOKEN=true ENABLE_RBAC=true RUNTIME_CONFIG="extensions/v1beta1=true,extensions/v1beta1/podsecuritypolicy=true" hack/local-up-cluster.sh | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Using the protected port | ||||||
|  |  | ||||||
|  | It is important to note that this example uses the following syntax to test with RBAC | ||||||
|  |  | ||||||
|  | 1.  `--server=https://127.0.0.1:6443`: when performing requests this ensures that the protected port is used so | ||||||
|  | that RBAC will be enforced | ||||||
|  | 1.  `--token={user}/{group(s)}`: this syntax allows a request to specify the username and groups to use for | ||||||
|  | testing.  It relies on the `ALLOW_ANY_TOKEN` setting. | ||||||
|  |  | ||||||
|  | ## Creating the policies, roles, and bindings | ||||||
|  |  | ||||||
|  | ### Policies | ||||||
|  |  | ||||||
|  | The first step to enforcing cluster constraints via PSP is to create your policies.  In this | ||||||
|  | example we will use two policies, `restricted` and `privileged`.  For simplicity, the only difference | ||||||
|  | between these policies is the ability to run a privileged container. | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | apiVersion: extensions/v1beta1 | ||||||
|  | kind: PodSecurityPolicy | ||||||
|  | metadata: | ||||||
|  |   name: privileged | ||||||
|  | spec: | ||||||
|  |   fsGroup: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   privileged: true | ||||||
|  |   runAsUser: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   seLinux: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   supplementalGroups: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   volumes: | ||||||
|  |   - '*' | ||||||
|  | --- | ||||||
|  | apiVersion: extensions/v1beta1 | ||||||
|  | kind: PodSecurityPolicy | ||||||
|  | metadata: | ||||||
|  |   name: restricted | ||||||
|  | spec: | ||||||
|  |   fsGroup: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   runAsUser: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   seLinux: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   supplementalGroups: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   volumes: | ||||||
|  |   - '*' | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | To create these policies run | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | $ kubectl --server=https://127.0.0.1:6443 --token=foo/system:masters create -f examples/podsecuritypolicy/rbac/policies.yaml  | ||||||
|  | podsecuritypolicy "privileged" created | ||||||
|  | podsecuritypolicy "restricted" created | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Roles and bindings | ||||||
|  |  | ||||||
|  | In order to a `PodSecurityPolicy` a user must have the ability to perform the `use` verb on the policy. | ||||||
|  | The `use` verb is a special verb that grants access to use the policy while | ||||||
|  | not allowing any other access.  This verb is specific to `PodSecurityPolicy`. | ||||||
|  | To enable the `use` access we will create cluster roles.  In this example we will provide the roles: | ||||||
|  |  | ||||||
|  | 1. `restricted-psp-user`: this role allows the `use` verb on the `restricted` policy only | ||||||
|  | 2. `privileged-psp-user`: this role allows the `use` verb on the `privileged` policy only | ||||||
|  |  | ||||||
|  |  | ||||||
|  | To associate roles with users we will use groups via a `RoleBinding`.  This example uses | ||||||
|  | the following groups: | ||||||
|  |  | ||||||
|  | 1. `privileged`: this group is bound to the `privilegedPSP` role and `restrictedPSP` role which gives users | ||||||
|  | in this group access to both policies. | ||||||
|  | 1. `restricted`: this group is bound to the `restrictedPSP` role | ||||||
|  | 1. `system:authenticated`: this is a system group for any authenticated user.  It is bound to the `edit` | ||||||
|  | role which is already provided by the cluster. | ||||||
|  |  | ||||||
|  | To create these roles and bindings run | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | $ kubectl --server=https://127.0.0.1:6443 --token=foo/system:masters create -f examples/podsecuritypolicy/rbac/roles.yaml  | ||||||
|  | clusterrole "restricted-psp-user" created | ||||||
|  | clusterrole "privileged-psp-user" created | ||||||
|  |  | ||||||
|  | $ kubectl --server=https://127.0.0.1:6443 --token=foo/system:masters create -f examples/podsecuritypolicy/rbac/bindings.yaml  | ||||||
|  | clusterrolebinding "privileged-psp-users" created | ||||||
|  | clusterrolebinding "restricted-psp-users" created | ||||||
|  | clusterrolebinding "edit" created | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Testing access | ||||||
|  |  | ||||||
|  | ### Restricted user can create non-privileged pods | ||||||
|  |  | ||||||
|  | Create the pod | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | $ kubectl --server=https://127.0.0.1:6443 --token=foo/restricted-psp-users create -f examples/podsecuritypolicy/rbac/pod.yaml  | ||||||
|  | pod "nginx" created | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Check the PSP that allowed the pod | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | $ kubectl get pod nginx -o yaml | grep psp | ||||||
|  |     kubernetes.io/psp: restricted | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Restricted user cannot create privileged pods | ||||||
|  |  | ||||||
|  | Delete the existing pod | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | $ kubectl delete pod nginx | ||||||
|  | pod "nginx" deleted | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Create the privileged pod | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | $ kubectl --server=https://127.0.0.1:6443 --token=foo/restricted-psp-users create -f examples/podsecuritypolicy/rbac/pod_priv.yaml  | ||||||
|  | Error from server (Forbidden): error when creating "examples/podsecuritypolicy/rbac/pod_priv.yaml": pods "nginx" is forbidden: unable to validate against any pod security policy: [spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed] | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Privileged user can create non-privileged pods | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | $ kubectl --server=https://127.0.0.1:6443 --token=foo/privileged-psp-users create -f examples/podsecuritypolicy/rbac/pod.yaml  | ||||||
|  | pod "nginx" created | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Check the PSP that allowed the pod.  Note, this could be the `restricted` or `privileged` PSP since both allow | ||||||
|  | for the creation of non-privileged pods. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | $ kubectl get pod nginx -o yaml | egrep "psp|privileged" | ||||||
|  |     kubernetes.io/psp: privileged | ||||||
|  |       privileged: false | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Privileged user can create privileged pods | ||||||
|  |  | ||||||
|  | Delete the existing pod | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | $ kubectl delete pod nginx | ||||||
|  | pod "nginx" deleted | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Create the privileged pod | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | $ kubectl --server=https://127.0.0.1:6443 --token=foo/privileged-psp-users create -f examples/podsecuritypolicy/rbac/pod_priv.yaml  | ||||||
|  | pod "nginx" created | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Check the PSP that allowed the pod. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | $ kubectl get pod nginx -o yaml | egrep "psp|privileged" | ||||||
|  |     kubernetes.io/psp: privileged | ||||||
|  |       privileged: true | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | <!-- BEGIN MUNGE: GENERATED_ANALYTICS --> | ||||||
|  | []() | ||||||
|  | <!-- END MUNGE: GENERATED_ANALYTICS --> | ||||||
							
								
								
									
										49
									
								
								examples/podsecuritypolicy/rbac/bindings.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								examples/podsecuritypolicy/rbac/bindings.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | # privilegedPSP gives the privilegedPSP role | ||||||
|  | # to the group privileged. | ||||||
|  | apiVersion: rbac.authorization.k8s.io/v1alpha1 | ||||||
|  | kind: ClusterRoleBinding | ||||||
|  | metadata: | ||||||
|  |     name: privileged-psp-users | ||||||
|  | subjects: | ||||||
|  | - kind: Group | ||||||
|  |   apiVersion: rbac.authorization.k8s.io/v1alpha1 | ||||||
|  |   name: privileged-psp-users | ||||||
|  | roleRef: | ||||||
|  |    apiGroup: rbac.authorization.k8s.io | ||||||
|  |    kind: ClusterRole | ||||||
|  |    name: privileged-psp-user | ||||||
|  | --- | ||||||
|  | # restrictedPSP grants the restrictedPSP role to | ||||||
|  | # the groups restricted and privileged. | ||||||
|  | apiVersion: rbac.authorization.k8s.io/v1alpha1 | ||||||
|  | kind: ClusterRoleBinding | ||||||
|  | metadata: | ||||||
|  |     name: restricted-psp-users | ||||||
|  | subjects: | ||||||
|  | - kind: Group | ||||||
|  |   apiVersion: rbac.authorization.k8s.io/v1alpha1 | ||||||
|  |   name: restricted-psp-users | ||||||
|  | - kind: Group | ||||||
|  |   apiVersion: rbac.authorization.k8s.io/v1alpha1 | ||||||
|  |   name: privileged-psp-users | ||||||
|  | roleRef: | ||||||
|  |    apiGroup: rbac.authorization.k8s.io | ||||||
|  |    kind: ClusterRole | ||||||
|  |    name: restricted-psp-user | ||||||
|  | --- | ||||||
|  | # edit grants edit role to system:authenticated. | ||||||
|  | apiVersion: rbac.authorization.k8s.io/v1alpha1 | ||||||
|  | kind: ClusterRoleBinding | ||||||
|  | metadata: | ||||||
|  |     name: edit | ||||||
|  | subjects: | ||||||
|  | - kind: Group | ||||||
|  |   apiVersion: rbac.authorization.k8s.io/v1alpha1 | ||||||
|  |   name: privileged-psp-users | ||||||
|  | - kind: Group | ||||||
|  |   apiVersion: rbac.authorization.k8s.io/v1alpha1 | ||||||
|  |   name: restricted-psp-users | ||||||
|  | roleRef: | ||||||
|  |    apiGroup: rbac.authorization.k8s.io | ||||||
|  |    kind: ClusterRole | ||||||
|  |    name: edit | ||||||
							
								
								
									
										12
									
								
								examples/podsecuritypolicy/rbac/pod.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								examples/podsecuritypolicy/rbac/pod.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | apiVersion: v1 | ||||||
|  | kind: Pod | ||||||
|  | metadata: | ||||||
|  |   name: nginx | ||||||
|  |   labels: | ||||||
|  |     name: nginx | ||||||
|  | spec: | ||||||
|  |   containers: | ||||||
|  |   - name: nginx | ||||||
|  |     image: nginx | ||||||
|  |     ports: | ||||||
|  |     - containerPort: 80 | ||||||
							
								
								
									
										14
									
								
								examples/podsecuritypolicy/rbac/pod_priv.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								examples/podsecuritypolicy/rbac/pod_priv.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | apiVersion: v1 | ||||||
|  | kind: Pod | ||||||
|  | metadata: | ||||||
|  |   name: nginx | ||||||
|  |   labels: | ||||||
|  |     name: nginx | ||||||
|  | spec: | ||||||
|  |   containers: | ||||||
|  |   - name: nginx | ||||||
|  |     image: nginx | ||||||
|  |     ports: | ||||||
|  |     - containerPort: 80 | ||||||
|  |     securityContext: | ||||||
|  |       privileged: true | ||||||
							
								
								
									
										38
									
								
								examples/podsecuritypolicy/rbac/policies.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								examples/podsecuritypolicy/rbac/policies.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | apiVersion: extensions/v1beta1 | ||||||
|  | kind: PodSecurityPolicy | ||||||
|  | metadata: | ||||||
|  |   name: privileged | ||||||
|  | spec: | ||||||
|  |   fsGroup: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   privileged: true | ||||||
|  |   runAsUser: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   seLinux: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   supplementalGroups: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   volumes: | ||||||
|  |   - '*' | ||||||
|  | --- | ||||||
|  | apiVersion: extensions/v1beta1 | ||||||
|  | kind: PodSecurityPolicy | ||||||
|  | metadata: | ||||||
|  |   name: restricted | ||||||
|  | spec: | ||||||
|  |   privileged: false | ||||||
|  |   fsGroup: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   runAsUser: | ||||||
|  |     rule: MustRunAsNonRoot | ||||||
|  |   seLinux: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   supplementalGroups: | ||||||
|  |     rule: RunAsAny | ||||||
|  |   volumes: | ||||||
|  |   - 'emptyDir' | ||||||
|  |   - 'secret' | ||||||
|  |   - 'downwardAPI' | ||||||
|  |   - 'configMap' | ||||||
|  |   - 'persistentVolumeClaim' | ||||||
|  |  | ||||||
							
								
								
									
										33
									
								
								examples/podsecuritypolicy/rbac/roles.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								examples/podsecuritypolicy/rbac/roles.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | # restrictedPSP grants access to use | ||||||
|  | # the restricted PSP. | ||||||
|  | apiVersion: rbac.authorization.k8s.io/v1alpha1 | ||||||
|  | kind: ClusterRole | ||||||
|  | metadata: | ||||||
|  |   name: restricted-psp-user | ||||||
|  | rules:  | ||||||
|  | - apiGroups: | ||||||
|  |   - extensions | ||||||
|  |   resources: | ||||||
|  |   - podsecuritypolicies | ||||||
|  |   resourceNames: | ||||||
|  |   - restricted | ||||||
|  |   verbs: | ||||||
|  |   - use | ||||||
|  | --- | ||||||
|  | # privilegedPSP grants access to use the privileged | ||||||
|  | # PSP. | ||||||
|  | apiVersion: rbac.authorization.k8s.io/v1alpha1 | ||||||
|  | kind: ClusterRole | ||||||
|  | metadata: | ||||||
|  |   name: privileged-psp-user | ||||||
|  | rules:  | ||||||
|  | - apiGroups: | ||||||
|  |   - extensions | ||||||
|  |   resources: | ||||||
|  |   - podsecuritypolicies | ||||||
|  |   resourceNames: | ||||||
|  |   - privileged | ||||||
|  |   verbs: | ||||||
|  |   - use | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -22,6 +22,7 @@ DOCKER=(docker ${DOCKER_OPTS}) | |||||||
| DOCKERIZE_KUBELET=${DOCKERIZE_KUBELET:-""} | DOCKERIZE_KUBELET=${DOCKERIZE_KUBELET:-""} | ||||||
| ALLOW_PRIVILEGED=${ALLOW_PRIVILEGED:-""} | ALLOW_PRIVILEGED=${ALLOW_PRIVILEGED:-""} | ||||||
| ALLOW_SECURITY_CONTEXT=${ALLOW_SECURITY_CONTEXT:-""} | ALLOW_SECURITY_CONTEXT=${ALLOW_SECURITY_CONTEXT:-""} | ||||||
|  | PSP_ADMISSION=${PSP_ADMISSION:-""} | ||||||
| RUNTIME_CONFIG=${RUNTIME_CONFIG:-""} | RUNTIME_CONFIG=${RUNTIME_CONFIG:-""} | ||||||
| KUBELET_AUTHORIZATION_WEBHOOK=${KUBELET_AUTHORIZATION_WEBHOOK:-""} | KUBELET_AUTHORIZATION_WEBHOOK=${KUBELET_AUTHORIZATION_WEBHOOK:-""} | ||||||
| KUBELET_AUTHENTICATION_WEBHOOK=${KUBELET_AUTHENTICATION_WEBHOOK:-""} | KUBELET_AUTHENTICATION_WEBHOOK=${KUBELET_AUTHENTICATION_WEBHOOK:-""} | ||||||
| @@ -316,12 +317,17 @@ function set_service_accounts { | |||||||
| } | } | ||||||
|  |  | ||||||
| function start_apiserver { | function start_apiserver { | ||||||
|     # Admission Controllers to invoke prior to persisting objects in cluster |     security_admission="" | ||||||
|     if [[ -z "${ALLOW_SECURITY_CONTEXT}" ]]; then |     if [[ -z "${ALLOW_SECURITY_CONTEXT}" ]]; then | ||||||
|       ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota,DefaultStorageClass |       security_admission=",SecurityContextDeny" | ||||||
|     else |  | ||||||
|       ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,DefaultStorageClass |  | ||||||
|     fi |     fi | ||||||
|  |     if [[ -n "${PSP_ADMISSION}" ]]; then | ||||||
|  |       security_admission=",PodSecurityPolicy" | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     # Admission Controllers to invoke prior to persisting objects in cluster | ||||||
|  |     ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,ServiceAccount${security_admission},ResourceQuota,DefaultStorageClass | ||||||
|  |  | ||||||
|     # This is the default dir and filename where the apiserver will generate a self-signed cert |     # This is the default dir and filename where the apiserver will generate a self-signed cert | ||||||
|     # which should be able to be used as the CA to verify itself |     # which should be able to be used as the CA to verify itself | ||||||
|  |  | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ go_library( | |||||||
|         "//pkg/api:go_default_library", |         "//pkg/api:go_default_library", | ||||||
|         "//pkg/api/errors:go_default_library", |         "//pkg/api/errors:go_default_library", | ||||||
|         "//pkg/apis/extensions:go_default_library", |         "//pkg/apis/extensions:go_default_library", | ||||||
|  |         "//pkg/auth/authorizer:go_default_library", | ||||||
|         "//pkg/auth/user:go_default_library", |         "//pkg/auth/user:go_default_library", | ||||||
|         "//pkg/client/cache:go_default_library", |         "//pkg/client/cache:go_default_library", | ||||||
|         "//pkg/client/clientset_generated/internalclientset:go_default_library", |         "//pkg/client/clientset_generated/internalclientset:go_default_library", | ||||||
| @@ -43,6 +44,7 @@ go_test( | |||||||
|         "//pkg/admission:go_default_library", |         "//pkg/admission:go_default_library", | ||||||
|         "//pkg/api:go_default_library", |         "//pkg/api:go_default_library", | ||||||
|         "//pkg/apis/extensions:go_default_library", |         "//pkg/apis/extensions:go_default_library", | ||||||
|  |         "//pkg/auth/authorizer:go_default_library", | ||||||
|         "//pkg/auth/user:go_default_library", |         "//pkg/auth/user:go_default_library", | ||||||
|         "//pkg/client/cache:go_default_library", |         "//pkg/client/cache:go_default_library", | ||||||
|         "//pkg/client/clientset_generated/internalclientset:go_default_library", |         "//pkg/client/clientset_generated/internalclientset:go_default_library", | ||||||
| @@ -52,6 +54,7 @@ go_test( | |||||||
|         "//pkg/security/podsecuritypolicy/seccomp:go_default_library", |         "//pkg/security/podsecuritypolicy/seccomp:go_default_library", | ||||||
|         "//pkg/security/podsecuritypolicy/util:go_default_library", |         "//pkg/security/podsecuritypolicy/util:go_default_library", | ||||||
|         "//pkg/util/diff:go_default_library", |         "//pkg/util/diff:go_default_library", | ||||||
|  |         "//pkg/util/sets:go_default_library", | ||||||
|         "//vendor:github.com/stretchr/testify/assert", |         "//vendor:github.com/stretchr/testify/assert", | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -23,10 +23,11 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/golang/glog" | 	"github.com/golang/glog" | ||||||
|  |  | ||||||
| 	admission "k8s.io/kubernetes/pkg/admission" | 	"k8s.io/kubernetes/pkg/admission" | ||||||
| 	api "k8s.io/kubernetes/pkg/api" | 	"k8s.io/kubernetes/pkg/api" | ||||||
| 	"k8s.io/kubernetes/pkg/api/errors" | 	"k8s.io/kubernetes/pkg/api/errors" | ||||||
| 	"k8s.io/kubernetes/pkg/apis/extensions" | 	"k8s.io/kubernetes/pkg/apis/extensions" | ||||||
|  | 	"k8s.io/kubernetes/pkg/auth/authorizer" | ||||||
| 	"k8s.io/kubernetes/pkg/auth/user" | 	"k8s.io/kubernetes/pkg/auth/user" | ||||||
| 	"k8s.io/kubernetes/pkg/client/cache" | 	"k8s.io/kubernetes/pkg/client/cache" | ||||||
| 	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" | 	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" | ||||||
| @@ -46,14 +47,14 @@ const ( | |||||||
|  |  | ||||||
| func init() { | func init() { | ||||||
| 	admission.RegisterPlugin(PluginName, func(client clientset.Interface, config io.Reader) (admission.Interface, error) { | 	admission.RegisterPlugin(PluginName, func(client clientset.Interface, config io.Reader) (admission.Interface, error) { | ||||||
| 		plugin := NewPlugin(client, psp.NewSimpleStrategyFactory(), getMatchingPolicies, false) | 		plugin := NewPlugin(client, psp.NewSimpleStrategyFactory(), getMatchingPolicies, true) | ||||||
| 		plugin.Run() | 		plugin.Run() | ||||||
| 		return plugin, nil | 		return plugin, nil | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| // PSPMatchFn allows plugging in how PSPs are matched against user information. | // PSPMatchFn allows plugging in how PSPs are matched against user information. | ||||||
| type PSPMatchFn func(store cache.Store, user user.Info, sa user.Info) ([]*extensions.PodSecurityPolicy, error) | type PSPMatchFn func(store cache.Store, user user.Info, sa user.Info, authz authorizer.Authorizer) ([]*extensions.PodSecurityPolicy, error) | ||||||
|  |  | ||||||
| // podSecurityPolicyPlugin holds state for and implements the admission plugin. | // podSecurityPolicyPlugin holds state for and implements the admission plugin. | ||||||
| type podSecurityPolicyPlugin struct { | type podSecurityPolicyPlugin struct { | ||||||
| @@ -62,13 +63,28 @@ type podSecurityPolicyPlugin struct { | |||||||
| 	strategyFactory  psp.StrategyFactory | 	strategyFactory  psp.StrategyFactory | ||||||
| 	pspMatcher       PSPMatchFn | 	pspMatcher       PSPMatchFn | ||||||
| 	failOnNoPolicies bool | 	failOnNoPolicies bool | ||||||
|  | 	authz            authorizer.Authorizer | ||||||
|  |  | ||||||
| 	reflector *cache.Reflector | 	reflector *cache.Reflector | ||||||
| 	stopChan  chan struct{} | 	stopChan  chan struct{} | ||||||
| 	store     cache.Store | 	store     cache.Store | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // SetAuthorizer sets the authorizer. | ||||||
|  | func (plugin *podSecurityPolicyPlugin) SetAuthorizer(authz authorizer.Authorizer) { | ||||||
|  | 	plugin.authz = authz | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Validate ensures an authorizer is set. | ||||||
|  | func (plugin *podSecurityPolicyPlugin) Validate() error { | ||||||
|  | 	if plugin.authz == nil { | ||||||
|  | 		return fmt.Errorf("%s requires an authorizer", PluginName) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| var _ admission.Interface = &podSecurityPolicyPlugin{} | var _ admission.Interface = &podSecurityPolicyPlugin{} | ||||||
|  | var _ admission.WantsAuthorizer = &podSecurityPolicyPlugin{} | ||||||
|  |  | ||||||
| // NewPlugin creates a new PSP admission plugin. | // NewPlugin creates a new PSP admission plugin. | ||||||
| func NewPlugin(kclient clientset.Interface, strategyFactory psp.StrategyFactory, pspMatcher PSPMatchFn, failOnNoPolicies bool) *podSecurityPolicyPlugin { | func NewPlugin(kclient clientset.Interface, strategyFactory psp.StrategyFactory, pspMatcher PSPMatchFn, failOnNoPolicies bool) *podSecurityPolicyPlugin { | ||||||
| @@ -142,7 +158,7 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error { | |||||||
| 		saInfo = serviceaccount.UserInfo(a.GetNamespace(), pod.Spec.ServiceAccountName, "") | 		saInfo = serviceaccount.UserInfo(a.GetNamespace(), pod.Spec.ServiceAccountName, "") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	matchedPolicies, err := c.pspMatcher(c.store, a.GetUserInfo(), saInfo) | 	matchedPolicies, err := c.pspMatcher(c.store, a.GetUserInfo(), saInfo, c.authz) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return admission.NewForbidden(a, err) | 		return admission.NewForbidden(a, err) | ||||||
| 	} | 	} | ||||||
| @@ -287,7 +303,11 @@ func (c *podSecurityPolicyPlugin) createProvidersFromPolicies(psps []*extensions | |||||||
|  |  | ||||||
| // getMatchingPolicies returns policies from the store.  For now this returns everything | // getMatchingPolicies returns policies from the store.  For now this returns everything | ||||||
| // in the future it can filter based on UserInfo and permissions. | // in the future it can filter based on UserInfo and permissions. | ||||||
| func getMatchingPolicies(store cache.Store, user user.Info, sa user.Info) ([]*extensions.PodSecurityPolicy, error) { | // | ||||||
|  | // TODO: this will likely need optimization since the initial implementation will | ||||||
|  | // always query for authorization.  Needs scale testing and possibly checking against | ||||||
|  | // a cache. | ||||||
|  | func getMatchingPolicies(store cache.Store, user user.Info, sa user.Info, authz authorizer.Authorizer) ([]*extensions.PodSecurityPolicy, error) { | ||||||
| 	matchedPolicies := make([]*extensions.PodSecurityPolicy, 0) | 	matchedPolicies := make([]*extensions.PodSecurityPolicy, 0) | ||||||
|  |  | ||||||
| 	for _, c := range store.List() { | 	for _, c := range store.List() { | ||||||
| @@ -295,12 +315,42 @@ func getMatchingPolicies(store cache.Store, user user.Info, sa user.Info) ([]*ex | |||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.NewInternalError(fmt.Errorf("error converting object from store to a pod security policy: %v", c)) | 			return nil, errors.NewInternalError(fmt.Errorf("error converting object from store to a pod security policy: %v", c)) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if authorizedForPolicy(user, constraint, authz) || authorizedForPolicy(sa, constraint, authz) { | ||||||
| 			matchedPolicies = append(matchedPolicies, constraint) | 			matchedPolicies = append(matchedPolicies, constraint) | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return matchedPolicies, nil | 	return matchedPolicies, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // authorizedForPolicy returns true if info is authorized to perform a "get" on policy. | ||||||
|  | func authorizedForPolicy(info user.Info, policy *extensions.PodSecurityPolicy, authz authorizer.Authorizer) bool { | ||||||
|  | 	// if no info exists then the API is being hit via the unsecured port.  In this case | ||||||
|  | 	// authorize the request. | ||||||
|  | 	if info == nil { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	attr := buildAttributes(info, policy) | ||||||
|  | 	allowed, _, _ := authz.Authorize(attr) | ||||||
|  | 	return allowed | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // buildAttributes builds an attributes record for a SAR based on the user info and policy. | ||||||
|  | func buildAttributes(info user.Info, policy *extensions.PodSecurityPolicy) authorizer.Attributes { | ||||||
|  | 	// TODO consider checking against the namespace that the pod is being | ||||||
|  | 	// created in to allow per-namespace PSP definitions. | ||||||
|  | 	attr := authorizer.AttributesRecord{ | ||||||
|  | 		User:            info, | ||||||
|  | 		Verb:            "use", | ||||||
|  | 		Name:            policy.Name, | ||||||
|  | 		APIGroup:        extensions.GroupName, | ||||||
|  | 		Resource:        "podsecuritypolicies", | ||||||
|  | 		ResourceRequest: true, | ||||||
|  | 	} | ||||||
|  | 	return attr | ||||||
|  | } | ||||||
|  |  | ||||||
| // logProviders logs what providers were found for the pod as well as any errors that were encountered | // logProviders logs what providers were found for the pod as well as any errors that were encountered | ||||||
| // while creating providers. | // while creating providers. | ||||||
| func logProviders(pod *api.Pod, providers []psp.Provider, providerCreationErrs []error) { | func logProviders(pod *api.Pod, providers []psp.Provider, providerCreationErrs []error) { | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ import ( | |||||||
| 	kadmission "k8s.io/kubernetes/pkg/admission" | 	kadmission "k8s.io/kubernetes/pkg/admission" | ||||||
| 	kapi "k8s.io/kubernetes/pkg/api" | 	kapi "k8s.io/kubernetes/pkg/api" | ||||||
| 	"k8s.io/kubernetes/pkg/apis/extensions" | 	"k8s.io/kubernetes/pkg/apis/extensions" | ||||||
|  | 	"k8s.io/kubernetes/pkg/auth/authorizer" | ||||||
| 	"k8s.io/kubernetes/pkg/auth/user" | 	"k8s.io/kubernetes/pkg/auth/user" | ||||||
| 	"k8s.io/kubernetes/pkg/client/cache" | 	"k8s.io/kubernetes/pkg/client/cache" | ||||||
| 	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" | 	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" | ||||||
| @@ -36,10 +37,13 @@ import ( | |||||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" | 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" | ||||||
| 	psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" | 	psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" | ||||||
| 	"k8s.io/kubernetes/pkg/util/diff" | 	"k8s.io/kubernetes/pkg/util/diff" | ||||||
|  | 	"k8s.io/kubernetes/pkg/util/sets" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const defaultContainerName = "test-c" | const defaultContainerName = "test-c" | ||||||
|  |  | ||||||
|  | // NewTestAdmission provides an admission plugin with test implementations of internal structs.  It uses | ||||||
|  | // an authorizer that always returns true. | ||||||
| func NewTestAdmission(store cache.Store, kclient clientset.Interface) kadmission.Interface { | func NewTestAdmission(store cache.Store, kclient clientset.Interface) kadmission.Interface { | ||||||
| 	return &podSecurityPolicyPlugin{ | 	return &podSecurityPolicyPlugin{ | ||||||
| 		Handler:         kadmission.NewHandler(kadmission.Create), | 		Handler:         kadmission.NewHandler(kadmission.Create), | ||||||
| @@ -47,9 +51,28 @@ func NewTestAdmission(store cache.Store, kclient clientset.Interface) kadmission | |||||||
| 		store:           store, | 		store:           store, | ||||||
| 		strategyFactory: kpsp.NewSimpleStrategyFactory(), | 		strategyFactory: kpsp.NewSimpleStrategyFactory(), | ||||||
| 		pspMatcher:      getMatchingPolicies, | 		pspMatcher:      getMatchingPolicies, | ||||||
|  | 		authz:           &TestAuthorizer{}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // TestAlwaysAllowedAuthorizer is a testing struct for testing that fulfills the authorizer interface. | ||||||
|  | type TestAuthorizer struct { | ||||||
|  | 	// disallowed contains names of disallowed policies.  Map is keyed by user.Info.GetName() | ||||||
|  | 	disallowed map[string][]string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t *TestAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { | ||||||
|  | 	disallowedForUser, _ := t.disallowed[a.GetUser().GetName()] | ||||||
|  | 	for _, name := range disallowedForUser { | ||||||
|  | 		if a.GetName() == name { | ||||||
|  | 			return false, "", nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return true, "", nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var _ authorizer.Authorizer = &TestAuthorizer{} | ||||||
|  |  | ||||||
| func useInitContainers(pod *kapi.Pod) *kapi.Pod { | func useInitContainers(pod *kapi.Pod) *kapi.Pod { | ||||||
| 	pod.Spec.InitContainers = pod.Spec.Containers | 	pod.Spec.InitContainers = pod.Spec.Containers | ||||||
| 	pod.Spec.Containers = []kapi.Container{} | 	pod.Spec.Containers = []kapi.Container{} | ||||||
| @@ -1522,6 +1545,117 @@ func TestCreateProvidersFromConstraints(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestGetMatchingPolicies(t *testing.T) { | ||||||
|  | 	policyWithName := func(name string) *extensions.PodSecurityPolicy { | ||||||
|  | 		p := restrictivePSP() | ||||||
|  | 		p.Name = name | ||||||
|  | 		return p | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tests := map[string]struct { | ||||||
|  | 		user               user.Info | ||||||
|  | 		sa                 user.Info | ||||||
|  | 		expectedPolicies   sets.String | ||||||
|  | 		inPolicies         []*extensions.PodSecurityPolicy | ||||||
|  | 		disallowedPolicies map[string][]string | ||||||
|  | 	}{ | ||||||
|  | 		"policy allowed by user": { | ||||||
|  | 			user: &user.DefaultInfo{Name: "user"}, | ||||||
|  | 			sa:   &user.DefaultInfo{Name: "sa"}, | ||||||
|  | 			disallowedPolicies: map[string][]string{ | ||||||
|  | 				"sa": {"policy"}, | ||||||
|  | 			}, | ||||||
|  | 			inPolicies:       []*extensions.PodSecurityPolicy{policyWithName("policy")}, | ||||||
|  | 			expectedPolicies: sets.NewString("policy"), | ||||||
|  | 		}, | ||||||
|  | 		"policy allowed by sa": { | ||||||
|  | 			user: &user.DefaultInfo{Name: "user"}, | ||||||
|  | 			sa:   &user.DefaultInfo{Name: "sa"}, | ||||||
|  | 			disallowedPolicies: map[string][]string{ | ||||||
|  | 				"user": {"policy"}, | ||||||
|  | 			}, | ||||||
|  | 			inPolicies:       []*extensions.PodSecurityPolicy{policyWithName("policy")}, | ||||||
|  | 			expectedPolicies: sets.NewString("policy"), | ||||||
|  | 		}, | ||||||
|  | 		"no policies allowed": { | ||||||
|  | 			user: &user.DefaultInfo{Name: "user"}, | ||||||
|  | 			sa:   &user.DefaultInfo{Name: "sa"}, | ||||||
|  | 			disallowedPolicies: map[string][]string{ | ||||||
|  | 				"user": {"policy"}, | ||||||
|  | 				"sa":   {"policy"}, | ||||||
|  | 			}, | ||||||
|  | 			inPolicies:       []*extensions.PodSecurityPolicy{policyWithName("policy")}, | ||||||
|  | 			expectedPolicies: sets.NewString(), | ||||||
|  | 		}, | ||||||
|  | 		"multiple policies allowed": { | ||||||
|  | 			user: &user.DefaultInfo{Name: "user"}, | ||||||
|  | 			sa:   &user.DefaultInfo{Name: "sa"}, | ||||||
|  | 			disallowedPolicies: map[string][]string{ | ||||||
|  | 				"user": {"policy1", "policy3"}, | ||||||
|  | 				"sa":   {"policy2", "policy3"}, | ||||||
|  | 			}, | ||||||
|  | 			inPolicies: []*extensions.PodSecurityPolicy{ | ||||||
|  | 				policyWithName("policy1"), // allowed by sa | ||||||
|  | 				policyWithName("policy2"), // allowed by user | ||||||
|  | 				policyWithName("policy3"), // not allowed | ||||||
|  | 			}, | ||||||
|  | 			expectedPolicies: sets.NewString("policy1", "policy2"), | ||||||
|  | 		}, | ||||||
|  | 		"policies are allowed for nil user info": { | ||||||
|  | 			user: nil, | ||||||
|  | 			sa:   &user.DefaultInfo{Name: "sa"}, | ||||||
|  | 			disallowedPolicies: map[string][]string{ | ||||||
|  | 				"user": {"policy1", "policy3"}, | ||||||
|  | 				"sa":   {"policy2", "policy3"}, | ||||||
|  | 			}, | ||||||
|  | 			inPolicies: []*extensions.PodSecurityPolicy{ | ||||||
|  | 				policyWithName("policy1"), | ||||||
|  | 				policyWithName("policy2"), | ||||||
|  | 				policyWithName("policy3"), | ||||||
|  | 			}, | ||||||
|  | 			// all policies are allowed regardless of the permissions when user info is nil | ||||||
|  | 			// (ie. a request hitting the unsecure port) | ||||||
|  | 			expectedPolicies: sets.NewString("policy1", "policy2", "policy3"), | ||||||
|  | 		}, | ||||||
|  | 		"policies are allowed for nil sa info": { | ||||||
|  | 			user: &user.DefaultInfo{Name: "user"}, | ||||||
|  | 			sa:   nil, | ||||||
|  | 			disallowedPolicies: map[string][]string{ | ||||||
|  | 				"user": {"policy1", "policy3"}, | ||||||
|  | 				"sa":   {"policy2", "policy3"}, | ||||||
|  | 			}, | ||||||
|  | 			inPolicies: []*extensions.PodSecurityPolicy{ | ||||||
|  | 				policyWithName("policy1"), | ||||||
|  | 				policyWithName("policy2"), | ||||||
|  | 				policyWithName("policy3"), | ||||||
|  | 			}, | ||||||
|  | 			// all policies are allowed regardless of the permissions when sa info is nil | ||||||
|  | 			// (ie. a request hitting the unsecure port) | ||||||
|  | 			expectedPolicies: sets.NewString("policy1", "policy2", "policy3"), | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	for k, v := range tests { | ||||||
|  | 		store := cache.NewStore(cache.MetaNamespaceKeyFunc) | ||||||
|  | 		for _, psp := range v.inPolicies { | ||||||
|  | 			store.Add(psp) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		authz := &TestAuthorizer{disallowed: v.disallowedPolicies} | ||||||
|  | 		allowedPolicies, err := getMatchingPolicies(store, v.user, v.sa, authz) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Errorf("%s got unexpected error %#v", k, err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		allowedPolicyNames := sets.NewString() | ||||||
|  | 		for _, p := range allowedPolicies { | ||||||
|  | 			allowedPolicyNames.Insert(p.Name) | ||||||
|  | 		} | ||||||
|  | 		if !v.expectedPolicies.Equal(allowedPolicyNames) { | ||||||
|  | 			t.Errorf("%s received unexpected policies.  Expected %#v but got %#v", k, v.expectedPolicies.List(), allowedPolicyNames.List()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func restrictivePSP() *extensions.PodSecurityPolicy { | func restrictivePSP() *extensions.PodSecurityPolicy { | ||||||
| 	return &extensions.PodSecurityPolicy{ | 	return &extensions.PodSecurityPolicy{ | ||||||
| 		ObjectMeta: kapi.ObjectMeta{ | 		ObjectMeta: kapi.ObjectMeta{ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 pweil-
					pweil-