mirror of https://github.com/k3s-io/k3s
add authz checks to allowed policies admission
parent
4dbc532c9a
commit
bbe9c8f96d
|
@ -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 -->
|
|
@ -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
|
|
@ -0,0 +1,12 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
|
@ -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
|
|
@ -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'
|
||||
|
|
@ -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))
|
||||
}
|
||||
matchedPolicies = append(matchedPolicies, constraint)
|
||||
|
||||
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{
|
||||
|
|
Loading…
Reference in New Issue