add authz checks to allowed policies admission

pull/6/head
pweil- 2016-09-20 06:59:55 -04:00
parent 4dbc532c9a
commit bbe9c8f96d
10 changed files with 571 additions and 11 deletions

View 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 -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/examples/podsecuritypolicy/rbac/README.md?pixel)]()
<!-- END MUNGE: GENERATED_ANALYTICS -->

View 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

View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
name: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80

View 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

View 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'

View 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

View File

@ -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

View File

@ -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",
], ],
) )

View File

@ -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) {

View File

@ -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{