a15d066f |
package podnodeconstraints
import (
"bytes"
"fmt"
"reflect"
"testing"
_ "github.com/openshift/origin/pkg/api/install"
authorizationapi "github.com/openshift/origin/pkg/authorization/api"
"github.com/openshift/origin/pkg/authorization/authorizer"
oadmission "github.com/openshift/origin/pkg/cmd/server/admission"
deployapi "github.com/openshift/origin/pkg/deploy/api"
"github.com/openshift/origin/pkg/scheduler/admission/podnodeconstraints/api" |
4cf2744c |
securityapi "github.com/openshift/origin/pkg/security/api" |
a15d066f |
admission "k8s.io/kubernetes/pkg/admission"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered" |
997787d8 |
"k8s.io/kubernetes/pkg/apis/batch" |
a15d066f |
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/auth/user"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/serviceaccount"
"k8s.io/kubernetes/pkg/util/sets"
)
func TestPodNodeConstraints(t *testing.T) {
ns := kapi.NamespaceDefault
tests := []struct {
config *api.PodNodeConstraintsConfig
resource runtime.Object
kind unversioned.GroupKind
groupresource unversioned.GroupResource
userinfo user.Info
reviewResponse *authorizationapi.SubjectAccessReviewResponse
expectedResource string
expectedErrorMsg string
}{
// 0: expect unspecified defaults to not error
{
config: emptyConfig(),
resource: defaultPod(),
userinfo: serviceaccount.UserInfo("", "", ""),
reviewResponse: reviewResponse(false, ""),
expectedResource: "pods/binding",
expectedErrorMsg: "",
},
// 1: expect nodeSelector to error with user which lacks "pods/binding" access
{
config: testConfig(),
resource: nodeSelectorPod(),
userinfo: serviceaccount.UserInfo("", "", ""),
reviewResponse: reviewResponse(false, ""),
expectedResource: "pods/binding",
expectedErrorMsg: "node selection by label(s) [bogus] is prohibited by policy for your role",
},
// 2: expect nodeName to fail with user that lacks "pods/binding" access
{
config: testConfig(),
resource: nodeNamePod(),
userinfo: serviceaccount.UserInfo("herpy", "derpy", ""),
reviewResponse: reviewResponse(false, ""),
expectedResource: "pods/binding",
expectedErrorMsg: "node selection by nodeName is prohibited by policy for your role",
},
// 3: expect nodeName and nodeSelector to fail with user that lacks "pods/binding" access
{
config: testConfig(),
resource: nodeNameNodeSelectorPod(),
userinfo: serviceaccount.UserInfo("herpy", "derpy", ""),
reviewResponse: reviewResponse(false, ""),
expectedResource: "pods/binding",
expectedErrorMsg: "node selection by nodeName and label(s) [bogus] is prohibited by policy for your role",
},
// 4: expect nodeSelector to succeed with user that has "pods/binding" access
{
config: testConfig(),
resource: nodeSelectorPod(),
userinfo: serviceaccount.UserInfo("openshift-infra", "daemonset-controller", ""),
reviewResponse: reviewResponse(true, ""),
expectedResource: "pods/binding",
expectedErrorMsg: "",
},
// 5: expect nodeName to succeed with user that has "pods/binding" access
{
config: testConfig(),
resource: nodeNamePod(),
userinfo: serviceaccount.UserInfo("openshift-infra", "daemonset-controller", ""),
reviewResponse: reviewResponse(true, ""),
expectedResource: "pods/binding",
expectedErrorMsg: "",
},
// 6: expect nil config to bypass admission
{
config: nil,
resource: defaultPod(),
userinfo: serviceaccount.UserInfo("", "", ""),
reviewResponse: reviewResponse(false, ""),
expectedResource: "pods/binding",
expectedErrorMsg: "",
},
}
for i, tc := range tests {
var expectedError error
errPrefix := fmt.Sprintf("%d", i)
prc := NewPodNodeConstraints(tc.config)
prc.(oadmission.WantsAuthorizer).SetAuthorizer(fakeAuthorizer(t))
err := prc.(oadmission.Validator).Validate()
if err != nil {
checkAdmitError(t, err, expectedError, errPrefix)
continue
} |
8201efbe |
attrs := admission.NewAttributesRecord(tc.resource, nil, kapi.Kind("Pod").WithVersion("version"), ns, "test", kapi.Resource("pods").WithVersion("version"), "", admission.Create, tc.userinfo) |
a15d066f |
if tc.expectedErrorMsg != "" {
expectedError = admission.NewForbidden(attrs, fmt.Errorf(tc.expectedErrorMsg))
}
err = prc.Admit(attrs)
checkAdmitError(t, err, expectedError, errPrefix)
}
}
func TestPodNodeConstraintsPodUpdate(t *testing.T) {
ns := kapi.NamespaceDefault
var expectedError error
errPrefix := "PodUpdate"
prc := NewPodNodeConstraints(testConfig())
prc.(oadmission.WantsAuthorizer).SetAuthorizer(fakeAuthorizer(t))
err := prc.(oadmission.Validator).Validate()
if err != nil {
checkAdmitError(t, err, expectedError, errPrefix)
return
} |
8201efbe |
attrs := admission.NewAttributesRecord(nodeNamePod(), nodeNamePod(), kapi.Kind("Pod").WithVersion("version"), ns, "test", kapi.Resource("pods").WithVersion("version"), "", admission.Update, serviceaccount.UserInfo("", "", "")) |
a15d066f |
err = prc.Admit(attrs)
checkAdmitError(t, err, expectedError, errPrefix)
}
func TestPodNodeConstraintsNonHandledResources(t *testing.T) {
ns := kapi.NamespaceDefault
errPrefix := "ResourceQuotaTest"
var expectedError error
prc := NewPodNodeConstraints(testConfig())
prc.(oadmission.WantsAuthorizer).SetAuthorizer(fakeAuthorizer(t))
err := prc.(oadmission.Validator).Validate()
if err != nil {
checkAdmitError(t, err, expectedError, errPrefix)
return
} |
8201efbe |
attrs := admission.NewAttributesRecord(resourceQuota(), nil, kapi.Kind("ResourceQuota").WithVersion("version"), ns, "test", kapi.Resource("resourcequotas").WithVersion("version"), "", admission.Create, serviceaccount.UserInfo("", "", "")) |
a15d066f |
err = prc.Admit(attrs)
checkAdmitError(t, err, expectedError, errPrefix)
}
func TestPodNodeConstraintsResources(t *testing.T) {
ns := kapi.NamespaceDefault
testconfigs := []struct {
config *api.PodNodeConstraintsConfig
userinfo user.Info
reviewResponse *authorizationapi.SubjectAccessReviewResponse
}{
{
config: testConfig(),
userinfo: serviceaccount.UserInfo("", "", ""),
reviewResponse: reviewResponse(false, ""),
},
}
testresources := []struct {
resource func(bool) runtime.Object
kind unversioned.GroupKind
groupresource unversioned.GroupResource
prefix string
}{
{
resource: replicationController,
kind: kapi.Kind("ReplicationController"),
groupresource: kapi.Resource("replicationcontrollers"),
prefix: "ReplicationController",
},
{
resource: deployment,
kind: extensions.Kind("Deployment"),
groupresource: extensions.Resource("deployments"),
prefix: "Deployment",
},
{
resource: replicaSet,
kind: extensions.Kind("ReplicaSet"),
groupresource: extensions.Resource("replicasets"),
prefix: "ReplicaSet",
},
{
resource: job,
kind: extensions.Kind("Job"),
groupresource: extensions.Resource("jobs"),
prefix: "Job",
},
{ |
997787d8 |
resource: job,
kind: batch.Kind("Job"),
groupresource: batch.Resource("jobs"),
prefix: "Job",
},
{ |
a15d066f |
resource: deploymentConfig,
kind: deployapi.Kind("DeploymentConfig"),
groupresource: deployapi.Resource("deploymentconfigs"),
prefix: "DeploymentConfig",
},
{
resource: podTemplate,
kind: deployapi.Kind("PodTemplate"),
groupresource: deployapi.Resource("podtemplates"),
prefix: "PodTemplate",
}, |
4cf2744c |
{
resource: podSecurityPolicySubjectReview,
kind: securityapi.Kind("PodSecurityPolicySubjectReview"),
groupresource: securityapi.Resource("podsecuritypolicysubjectreviews"),
prefix: "PodSecurityPolicy",
},
{
resource: podSecurityPolicySelfSubjectReview,
kind: securityapi.Kind("PodSecurityPolicySelfSubjectReview"),
groupresource: securityapi.Resource("podsecuritypolicyselfsubjectreviews"),
prefix: "PodSecurityPolicy",
},
{
resource: podSecurityPolicyReview,
kind: securityapi.Kind("PodSecurityPolicyReview"),
groupresource: securityapi.Resource("podsecuritypolicyreviews"),
prefix: "PodSecurityPolicy",
}, |
a15d066f |
}
testparams := []struct {
nodeselector bool
expectedErrorMsg string
prefix string
}{
{
nodeselector: true,
expectedErrorMsg: "node selection by label(s) [bogus] is prohibited by policy for your role",
prefix: "with nodeSelector",
},
{
nodeselector: false,
expectedErrorMsg: "",
prefix: "without nodeSelector",
},
}
testops := []struct {
operation admission.Operation
}{
{
operation: admission.Create,
},
{
operation: admission.Update,
},
}
for _, tc := range testconfigs {
for _, tr := range testresources {
for _, tp := range testparams {
for _, top := range testops {
var expectedError error
errPrefix := fmt.Sprintf("%s; %s; %s", tr.prefix, tp.prefix, top.operation)
prc := NewPodNodeConstraints(tc.config)
prc.(oadmission.WantsAuthorizer).SetAuthorizer(fakeAuthorizer(t))
err := prc.(oadmission.Validator).Validate()
if err != nil {
checkAdmitError(t, err, expectedError, errPrefix)
continue
} |
8201efbe |
attrs := admission.NewAttributesRecord(tr.resource(tp.nodeselector), nil, tr.kind.WithVersion("version"), ns, "test", tr.groupresource.WithVersion("version"), "", top.operation, tc.userinfo) |
a15d066f |
if tp.expectedErrorMsg != "" {
expectedError = admission.NewForbidden(attrs, fmt.Errorf(tp.expectedErrorMsg))
}
err = prc.Admit(attrs)
checkAdmitError(t, err, expectedError, errPrefix)
}
}
}
}
}
func emptyConfig() *api.PodNodeConstraintsConfig {
return &api.PodNodeConstraintsConfig{}
}
func testConfig() *api.PodNodeConstraintsConfig {
return &api.PodNodeConstraintsConfig{
NodeSelectorLabelBlacklist: []string{"bogus"},
}
}
func defaultPod() *kapi.Pod {
pod := &kapi.Pod{}
return pod
}
func pod(ns bool) runtime.Object {
pod := &kapi.Pod{}
if ns {
pod.Spec.NodeSelector = map[string]string{"bogus": "frank"}
}
return pod
}
func nodeNameNodeSelectorPod() *kapi.Pod {
pod := &kapi.Pod{}
pod.Spec.NodeName = "frank"
pod.Spec.NodeSelector = map[string]string{"bogus": "frank"}
return pod
}
func nodeNamePod() *kapi.Pod {
pod := &kapi.Pod{}
pod.Spec.NodeName = "frank"
return pod
}
func nodeSelectorPod() *kapi.Pod {
pod := &kapi.Pod{}
pod.Spec.NodeSelector = map[string]string{"bogus": "frank"}
return pod
}
func emptyNodeSelectorPod() *kapi.Pod {
pod := &kapi.Pod{}
pod.Spec.NodeSelector = map[string]string{}
return pod
}
|
4cf2744c |
func podSpec(setNodeSelector bool) *kapi.PodSpec {
ps := &kapi.PodSpec{}
if setNodeSelector {
ps.NodeSelector = map[string]string{"bogus": "frank"}
}
return ps
}
|
a15d066f |
func podTemplateSpec(setNodeSelector bool) *kapi.PodTemplateSpec {
pts := &kapi.PodTemplateSpec{}
if setNodeSelector {
pts.Spec.NodeSelector = map[string]string{"bogus": "frank"}
}
return pts
}
func podTemplate(setNodeSelector bool) runtime.Object {
pt := &kapi.PodTemplate{}
pt.Template = *podTemplateSpec(setNodeSelector)
return pt
}
func replicationController(setNodeSelector bool) runtime.Object {
rc := &kapi.ReplicationController{}
rc.Spec.Template = podTemplateSpec(setNodeSelector)
return rc
}
func deployment(setNodeSelector bool) runtime.Object {
d := &extensions.Deployment{}
d.Spec.Template = *podTemplateSpec(setNodeSelector)
return d
}
func replicaSet(setNodeSelector bool) runtime.Object {
rs := &extensions.ReplicaSet{} |
997787d8 |
rs.Spec.Template = *podTemplateSpec(setNodeSelector) |
a15d066f |
return rs
}
func job(setNodeSelector bool) runtime.Object { |
508f280e |
j := &batch.Job{} |
a15d066f |
j.Spec.Template = *podTemplateSpec(setNodeSelector)
return j
}
func resourceQuota() runtime.Object {
rq := &kapi.ResourceQuota{}
return rq
}
func deploymentConfig(setNodeSelector bool) runtime.Object {
dc := &deployapi.DeploymentConfig{}
dc.Spec.Template = podTemplateSpec(setNodeSelector)
return dc
}
|
4cf2744c |
func podSecurityPolicySubjectReview(setNodeSelector bool) runtime.Object {
pspsr := &securityapi.PodSecurityPolicySubjectReview{}
pspsr.Spec.PodSpec = *podSpec(setNodeSelector)
return pspsr
}
func podSecurityPolicySelfSubjectReview(setNodeSelector bool) runtime.Object {
pspssr := &securityapi.PodSecurityPolicySelfSubjectReview{}
pspssr.Spec.PodSpec = *podSpec(setNodeSelector)
return pspssr
}
func podSecurityPolicyReview(setNodeSelector bool) runtime.Object {
pspr := &securityapi.PodSecurityPolicyReview{}
pspr.Spec.PodSpec = *podSpec(setNodeSelector)
return pspr
}
|
a15d066f |
func checkAdmitError(t *testing.T, err error, expectedError error, prefix string) {
switch {
case expectedError == nil && err == nil:
// continue
case expectedError != nil && err != nil && err.Error() != expectedError.Error():
t.Errorf("%s: expected error %q, got: %q", prefix, expectedError.Error(), err.Error())
case expectedError == nil && err != nil:
t.Errorf("%s: expected no error, got: %q", prefix, err.Error())
case expectedError != nil && err == nil:
t.Errorf("%s: expected error %q, no error recieved", prefix, expectedError.Error())
}
}
type fakeTestAuthorizer struct {
t *testing.T
}
func fakeAuthorizer(t *testing.T) authorizer.Authorizer {
return &fakeTestAuthorizer{
t: t,
}
}
func (a *fakeTestAuthorizer) Authorize(ctx kapi.Context, passedAttributes authorizer.AuthorizationAttributes) (bool, string, error) {
a.t.Logf("Authorize: ctx: %#v", ctx)
ui, ok := kapi.UserFrom(ctx)
if !ok {
return false, "", fmt.Errorf("No valid UserInfo for Context")
}
// User with pods/bindings. permission:
if ui.GetName() == "system:serviceaccount:openshift-infra:daemonset-controller" {
return true, "", nil
}
// User without pods/bindings. permission:
return false, "", nil
}
func (a *fakeTestAuthorizer) GetAllowedSubjects(ctx kapi.Context, attributes authorizer.AuthorizationAttributes) (sets.String, sets.String, error) {
return nil, nil, nil
}
func reviewResponse(allowed bool, msg string) *authorizationapi.SubjectAccessReviewResponse {
return &authorizationapi.SubjectAccessReviewResponse{
Allowed: allowed,
Reason: msg,
}
}
func TestReadConfig(t *testing.T) {
configStr := `apiVersion: v1
kind: PodNodeConstraintsConfig
nodeSelectorLabelBlacklist:
- bogus
- foo
`
buf := bytes.NewBufferString(configStr)
config, err := readConfig(buf)
if err != nil {
t.Fatalf("unexpected error reading config: %v", err)
}
if len(config.NodeSelectorLabelBlacklist) == 0 {
t.Fatalf("NodeSelectorLabelBlacklist didn't take specified value")
}
}
func TestResourcesToCheck(t *testing.T) {
known := knownResourceKinds()
detected := kindsWithPodSpecs()
for _, k := range detected {
if _, isKnown := known[k]; !isKnown {
t.Errorf("Unknown resource kind %s contains a PodSpec", (&k).String())
continue
}
delete(known, k)
}
if len(known) > 0 {
t.Errorf("These known kinds were not detected to have a PodSpec: %#v", known)
}
}
var podSpecType = reflect.TypeOf(kapi.PodSpec{})
|
51a1e57a |
func hasPodSpec(visited map[reflect.Type]bool, t reflect.Type) bool {
if visited[t] {
return false
}
visited[t] = true
|
a15d066f |
switch t.Kind() {
case reflect.Struct:
if t == podSpecType {
return true
} |
c2147930 |
for i := 0; i < t.NumField(); i++ { |
51a1e57a |
if hasPodSpec(visited, t.Field(i).Type) { |
a15d066f |
return true
}
}
case reflect.Array, reflect.Slice, reflect.Chan, reflect.Map, reflect.Ptr: |
51a1e57a |
return hasPodSpec(visited, t.Elem()) |
a15d066f |
}
return false
}
func internalGroupVersions() []unversioned.GroupVersion {
groupVersions := registered.EnabledVersions()
groups := map[string]struct{}{}
for _, gv := range groupVersions {
groups[gv.Group] = struct{}{}
}
result := []unversioned.GroupVersion{}
for group := range groups {
result = append(result, unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal})
}
return result
}
func isList(t reflect.Type) bool {
if t.Kind() != reflect.Struct {
return false
}
_, hasListMeta := t.FieldByName("ListMeta")
return hasListMeta
}
func kindsWithPodSpecs() []unversioned.GroupKind {
result := []unversioned.GroupKind{}
for _, gv := range internalGroupVersions() {
knownTypes := kapi.Scheme.KnownTypes(gv)
for kind, knownType := range knownTypes { |
51a1e57a |
if !isList(knownType) && hasPodSpec(map[reflect.Type]bool{}, knownType) { |
a15d066f |
result = append(result, unversioned.GroupKind{Group: gv.Group, Kind: kind})
}
}
} |
51a1e57a |
|
a15d066f |
return result
}
func knownResourceKinds() map[unversioned.GroupKind]struct{} {
result := map[unversioned.GroupKind]struct{}{}
for _, ka := range resourcesToCheck {
result[ka] = struct{}{}
}
for _, ki := range resourcesToIgnore {
result[ki] = struct{}{}
}
return result
} |