mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 02:08: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:-""} | ||||
| ALLOW_PRIVILEGED=${ALLOW_PRIVILEGED:-""} | ||||
| ALLOW_SECURITY_CONTEXT=${ALLOW_SECURITY_CONTEXT:-""} | ||||
| PSP_ADMISSION=${PSP_ADMISSION:-""} | ||||
| RUNTIME_CONFIG=${RUNTIME_CONFIG:-""} | ||||
| KUBELET_AUTHORIZATION_WEBHOOK=${KUBELET_AUTHORIZATION_WEBHOOK:-""} | ||||
| KUBELET_AUTHENTICATION_WEBHOOK=${KUBELET_AUTHENTICATION_WEBHOOK:-""} | ||||
| @@ -316,12 +317,17 @@ function set_service_accounts { | ||||
| } | ||||
|  | ||||
| function start_apiserver { | ||||
|     # Admission Controllers to invoke prior to persisting objects in cluster | ||||
|     security_admission="" | ||||
|     if [[ -z "${ALLOW_SECURITY_CONTEXT}" ]]; then | ||||
|       ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota,DefaultStorageClass | ||||
|     else | ||||
|       ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,DefaultStorageClass | ||||
|       security_admission=",SecurityContextDeny" | ||||
|     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 | ||||
|     # 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/errors:go_default_library", | ||||
|         "//pkg/apis/extensions:go_default_library", | ||||
|         "//pkg/auth/authorizer:go_default_library", | ||||
|         "//pkg/auth/user:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/internalclientset:go_default_library", | ||||
| @@ -43,6 +44,7 @@ go_test( | ||||
|         "//pkg/admission:go_default_library", | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/apis/extensions:go_default_library", | ||||
|         "//pkg/auth/authorizer:go_default_library", | ||||
|         "//pkg/auth/user:go_default_library", | ||||
|         "//pkg/client/cache: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/util:go_default_library", | ||||
|         "//pkg/util/diff:go_default_library", | ||||
|         "//pkg/util/sets:go_default_library", | ||||
|         "//vendor:github.com/stretchr/testify/assert", | ||||
|     ], | ||||
| ) | ||||
|   | ||||
| @@ -23,10 +23,11 @@ import ( | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
|  | ||||
| 	admission "k8s.io/kubernetes/pkg/admission" | ||||
| 	api "k8s.io/kubernetes/pkg/api" | ||||
| 	"k8s.io/kubernetes/pkg/admission" | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| 	"k8s.io/kubernetes/pkg/api/errors" | ||||
| 	"k8s.io/kubernetes/pkg/apis/extensions" | ||||
| 	"k8s.io/kubernetes/pkg/auth/authorizer" | ||||
| 	"k8s.io/kubernetes/pkg/auth/user" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" | ||||
| @@ -46,14 +47,14 @@ const ( | ||||
|  | ||||
| func init() { | ||||
| 	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() | ||||
| 		return plugin, nil | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // 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. | ||||
| type podSecurityPolicyPlugin struct { | ||||
| @@ -62,13 +63,28 @@ type podSecurityPolicyPlugin struct { | ||||
| 	strategyFactory  psp.StrategyFactory | ||||
| 	pspMatcher       PSPMatchFn | ||||
| 	failOnNoPolicies bool | ||||
| 	authz            authorizer.Authorizer | ||||
|  | ||||
| 	reflector *cache.Reflector | ||||
| 	stopChan  chan struct{} | ||||
| 	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.WantsAuthorizer = &podSecurityPolicyPlugin{} | ||||
|  | ||||
| // NewPlugin creates a new PSP admission plugin. | ||||
| 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, "") | ||||
| 	} | ||||
|  | ||||
| 	matchedPolicies, err := c.pspMatcher(c.store, a.GetUserInfo(), saInfo) | ||||
| 	matchedPolicies, err := c.pspMatcher(c.store, a.GetUserInfo(), saInfo, c.authz) | ||||
| 	if err != nil { | ||||
| 		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 | ||||
| // 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) | ||||
|  | ||||
| 	for _, c := range store.List() { | ||||
| @@ -295,12 +315,42 @@ func getMatchingPolicies(store cache.Store, user user.Info, sa user.Info) ([]*ex | ||||
| 		if !ok { | ||||
| 			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) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| // while creating providers. | ||||
| func logProviders(pod *api.Pod, providers []psp.Provider, providerCreationErrs []error) { | ||||
|   | ||||
| @@ -27,6 +27,7 @@ import ( | ||||
| 	kadmission "k8s.io/kubernetes/pkg/admission" | ||||
| 	kapi "k8s.io/kubernetes/pkg/api" | ||||
| 	"k8s.io/kubernetes/pkg/apis/extensions" | ||||
| 	"k8s.io/kubernetes/pkg/auth/authorizer" | ||||
| 	"k8s.io/kubernetes/pkg/auth/user" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" | ||||
| @@ -36,10 +37,13 @@ import ( | ||||
| 	"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" | ||||
| 	psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" | ||||
| 	"k8s.io/kubernetes/pkg/util/diff" | ||||
| 	"k8s.io/kubernetes/pkg/util/sets" | ||||
| ) | ||||
|  | ||||
| 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 { | ||||
| 	return &podSecurityPolicyPlugin{ | ||||
| 		Handler:         kadmission.NewHandler(kadmission.Create), | ||||
| @@ -47,9 +51,28 @@ func NewTestAdmission(store cache.Store, kclient clientset.Interface) kadmission | ||||
| 		store:           store, | ||||
| 		strategyFactory: kpsp.NewSimpleStrategyFactory(), | ||||
| 		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 { | ||||
| 	pod.Spec.InitContainers = pod.Spec.Containers | ||||
| 	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 { | ||||
| 	return &extensions.PodSecurityPolicy{ | ||||
| 		ObjectMeta: kapi.ObjectMeta{ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 pweil-
					pweil-