test/integration/authorization_test.go
61039345
 // +build integration,!no-etcd
 
 package integration
 
 import (
7d4a61e2
 	"fmt"
fdd9bb5b
 	"reflect"
61039345
 	"strings"
 	"testing"
7d4a61e2
 	"time"
61039345
 
83c702b4
 	kapierror "k8s.io/kubernetes/pkg/api/errors"
 	"k8s.io/kubernetes/pkg/fields"
 	"k8s.io/kubernetes/pkg/labels"
 	"k8s.io/kubernetes/pkg/util"
 	"k8s.io/kubernetes/pkg/util/wait"
61039345
 
fdd9bb5b
 	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
d3b5c424
 	"github.com/openshift/origin/pkg/client"
01221dec
 	policy "github.com/openshift/origin/pkg/cmd/admin/policy"
d1bc1b89
 	"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
8c5b680f
 	testutil "github.com/openshift/origin/test/util"
61039345
 )
 
969809c1
 func TestAuthorizationRestrictedAccessForProjectAdmins(t *testing.T) {
47caf770
 	_, clusterAdminKubeConfig, err := testutil.StartTestMaster()
61039345
 	if err != nil {
 		t.Fatalf("unexpected error: %v", err)
 	}
 
47caf770
 	clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
61039345
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
61039345
 	}
 
47caf770
 	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
61039345
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
61039345
 	}
47caf770
 
 	haroldClient, err := testutil.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "hammer-project", "harold")
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
47caf770
 	}
e6d83541
 
47caf770
 	markClient, err := testutil.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "mallet-project", "mark")
61039345
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
61039345
 	}
 
969809c1
 	_, err = haroldClient.DeploymentConfigs("hammer-project").List(labels.Everything(), fields.Everything())
61039345
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
61039345
 	}
 
969809c1
 	_, err = markClient.DeploymentConfigs("hammer-project").List(labels.Everything(), fields.Everything())
c76aabad
 	if (err == nil) || !kapierror.IsForbidden(err) {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
61039345
 	}
 
 	// projects are a special case where a get of a project actually sets a namespace.  Make sure that
 	// the namespace is properly special cased and set for authorization rules
 	_, err = haroldClient.Projects().Get("hammer-project")
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
61039345
 	}
 	_, err = markClient.Projects().Get("hammer-project")
c76aabad
 	if (err == nil) || !kapierror.IsForbidden(err) {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
61039345
 	}
 
 	// wait for the project authorization cache to catch the change.  It is on a one second period
f333605e
 	waitForProject(t, haroldClient, "hammer-project", 1*time.Second, 10)
 	waitForProject(t, markClient, "mallet-project", 1*time.Second, 10)
7d4a61e2
 }
61039345
 
7d4a61e2
 // waitForProject will execute a client list of projects looking for the project with specified name
 // if not found, it will retry up to numRetries at the specified delayInterval
 func waitForProject(t *testing.T, client client.Interface, projectName string, delayInterval time.Duration, numRetries int) {
 	for i := 0; i <= numRetries; i++ {
 		projects, err := client.Projects().List(labels.Everything(), fields.Everything())
 		if err != nil {
 			t.Errorf("unexpected error: %v", err)
 		}
 		if (len(projects.Items) == 1) && (projects.Items[0].Name == projectName) {
 			fmt.Printf("Waited %v times with interval %v\n", i, delayInterval)
 			return
 		} else {
 			time.Sleep(delayInterval)
 		}
 	}
 	t.Errorf("expected project %v not found", projectName)
61039345
 }
fdd9bb5b
 
969809c1
 func TestAuthorizationOnlyResolveRolesForBindingsThatMatter(t *testing.T) {
47caf770
 	_, clusterAdminKubeConfig, err := testutil.StartTestMaster()
1d15a871
 	if err != nil {
 		t.Fatalf("unexpected error: %v", err)
 	}
 
47caf770
 	clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
1d15a871
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
1d15a871
 	}
 
01221dec
 	addValerie := &policy.RoleModificationOptions{
ba6a7bc8
 		RoleNamespace:       "",
 		RoleName:            bootstrappolicy.ViewRoleName,
 		RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(clusterAdminClient),
 		Users:               []string{"valerie"},
1d15a871
 	}
01221dec
 	if err := addValerie.AddRole(); err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
1d15a871
 	}
 
ba6a7bc8
 	if err = clusterAdminClient.ClusterRoles().Delete(bootstrappolicy.ViewRoleName); err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
1d15a871
 	}
 
01221dec
 	addEdgar := &policy.RoleModificationOptions{
ba6a7bc8
 		RoleNamespace:       "",
 		RoleName:            bootstrappolicy.EditRoleName,
 		RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(clusterAdminClient),
 		Users:               []string{"edgar"},
1d15a871
 	}
01221dec
 	if err := addEdgar.AddRole(); err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
1d15a871
 	}
 
 	// try to add Valerie to a non-existent role
01221dec
 	if err := addValerie.AddRole(); !kapierror.IsNotFound(err) {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
1d15a871
 	}
 
 }
 
fdd9bb5b
 // TODO this list should start collapsing as we continue to tighten access on generated system ids
1ab326d9
 var globalClusterAdminUsers = util.NewStringSet()
 var globalClusterAdminGroups = util.NewStringSet("system:cluster-admins", "system:masters")
fdd9bb5b
 
d3b5c424
 type resourceAccessReviewTest struct {
 	clientInterface client.ResourceAccessReviewInterface
 	review          *authorizationapi.ResourceAccessReview
 
f35dc17e
 	response authorizationapi.ResourceAccessReviewResponse
 	err      string
d3b5c424
 }
 
 func (test resourceAccessReviewTest) run(t *testing.T) {
 	actualResponse, err := test.clientInterface.Create(test.review)
 	if len(test.err) > 0 {
 		if err == nil {
 			t.Errorf("Expected error: %v", test.err)
 		} else if !strings.Contains(err.Error(), test.err) {
 			t.Errorf("expected %v, got %v", test.err, err)
 		}
 	} else {
 		if err != nil {
 			t.Errorf("unexpected error: %v", err)
 		}
 	}
 
05ead3f2
 	if actualResponse.Namespace != test.response.Namespace || !reflect.DeepEqual(actualResponse.Users.List(), test.response.Users.List()) || !reflect.DeepEqual(actualResponse.Groups.List(), test.response.Groups.List()) {
f35dc17e
 		t.Errorf("%#v: expected %v, got %v", test.review, test.response, actualResponse)
d3b5c424
 	}
 }
 
969809c1
 func TestAuthorizationResourceAccessReview(t *testing.T) {
47caf770
 	_, clusterAdminKubeConfig, err := testutil.StartTestMaster()
fdd9bb5b
 	if err != nil {
 		t.Fatalf("unexpected error: %v", err)
 	}
 
47caf770
 	clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
47caf770
 	}
 
 	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
fdd9bb5b
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
fdd9bb5b
 	}
 
47caf770
 	haroldClient, err := testutil.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "hammer-project", "harold")
fdd9bb5b
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
fdd9bb5b
 	}
47caf770
 
e6d83541
 	markClient, err := testutil.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "mallet-project", "mark")
fdd9bb5b
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
fdd9bb5b
 	}
 
01221dec
 	addValerie := &policy.RoleModificationOptions{
ba6a7bc8
 		RoleNamespace:       "",
 		RoleName:            bootstrappolicy.ViewRoleName,
 		RoleBindingAccessor: policy.NewLocalRoleBindingAccessor("hammer-project", haroldClient),
 		Users:               []string{"valerie"},
fdd9bb5b
 	}
01221dec
 	if err := addValerie.AddRole(); err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
fdd9bb5b
 	}
 
01221dec
 	addEdgar := &policy.RoleModificationOptions{
ba6a7bc8
 		RoleNamespace:       "",
 		RoleName:            bootstrappolicy.EditRoleName,
 		RoleBindingAccessor: policy.NewLocalRoleBindingAccessor("mallet-project", markClient),
 		Users:               []string{"edgar"},
fdd9bb5b
 	}
01221dec
 	if err := addEdgar.AddRole(); err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
fdd9bb5b
 	}
 
 	requestWhoCanViewDeployments := &authorizationapi.ResourceAccessReview{Verb: "get", Resource: "deployments"}
 
d3b5c424
 	{
 		test := resourceAccessReviewTest{
 			clientInterface: haroldClient.ResourceAccessReviews("hammer-project"),
 			review:          requestWhoCanViewDeployments,
f35dc17e
 			response: authorizationapi.ResourceAccessReviewResponse{
f69463d3
 				Users:     util.NewStringSet("harold", "valerie"),
f35dc17e
 				Groups:    globalClusterAdminGroups,
 				Namespace: "hammer-project",
 			},
d3b5c424
 		}
f35dc17e
 		test.response.Users.Insert(globalClusterAdminUsers.List()...)
4dd6cd97
 		test.response.Groups.Insert("system:cluster-readers")
d3b5c424
 		test.run(t)
fdd9bb5b
 	}
d3b5c424
 	{
 		test := resourceAccessReviewTest{
 			clientInterface: markClient.ResourceAccessReviews("mallet-project"),
 			review:          requestWhoCanViewDeployments,
f35dc17e
 			response: authorizationapi.ResourceAccessReviewResponse{
f69463d3
 				Users:     util.NewStringSet("mark", "edgar"),
f35dc17e
 				Groups:    globalClusterAdminGroups,
 				Namespace: "mallet-project",
 			},
d3b5c424
 		}
f35dc17e
 		test.response.Users.Insert(globalClusterAdminUsers.List()...)
4dd6cd97
 		test.response.Groups.Insert("system:cluster-readers")
d3b5c424
 		test.run(t)
fdd9bb5b
 	}
d3b5c424
 
 	// mark should not be able to make global access review requests
 	{
 		test := resourceAccessReviewTest{
384f11fa
 			clientInterface: markClient.ClusterResourceAccessReviews(),
d3b5c424
 			review:          requestWhoCanViewDeployments,
92763011
 			err:             "cannot ",
d3b5c424
 		}
 		test.run(t)
fdd9bb5b
 	}
 
d3b5c424
 	// a cluster-admin should be able to make global access review requests
 	{
 		test := resourceAccessReviewTest{
384f11fa
 			clientInterface: clusterAdminClient.ClusterResourceAccessReviews(),
d3b5c424
 			review:          requestWhoCanViewDeployments,
f35dc17e
 			response: authorizationapi.ResourceAccessReviewResponse{
 				Users:  globalClusterAdminUsers,
 				Groups: globalClusterAdminGroups,
 			},
d3b5c424
 		}
4dd6cd97
 		test.response.Groups.Insert("system:cluster-readers")
d3b5c424
 		test.run(t)
fdd9bb5b
 	}
d3b5c424
 }
fdd9bb5b
 
d3b5c424
 type subjectAccessReviewTest struct {
05ead3f2
 	description     string
d3b5c424
 	clientInterface client.SubjectAccessReviewInterface
 	review          *authorizationapi.SubjectAccessReview
 
 	response authorizationapi.SubjectAccessReviewResponse
 	err      string
 }
 
 func (test subjectAccessReviewTest) run(t *testing.T) {
8c5b680f
 	failMessage := ""
 	err := wait.Poll(testutil.PolicyCachePollInterval, testutil.PolicyCachePollTimeout, func() (bool, error) {
 		actualResponse, err := test.clientInterface.Create(test.review)
 		if len(test.err) > 0 {
 			if err == nil {
 				failMessage = fmt.Sprintf("%s: Expected error: %v", test.description, test.err)
 				return false, nil
 			} else if !strings.HasPrefix(err.Error(), test.err) {
 				failMessage = fmt.Sprintf("%s: expected\n\t%v\ngot\n\t%v", test.description, test.err, err)
 				return false, nil
 			}
 		} else {
 			if err != nil {
 				failMessage = fmt.Sprintf("%s: unexpected error: %v", test.description, err)
 				return false, nil
 			}
d3b5c424
 		}
8c5b680f
 
 		if (actualResponse.Namespace != test.response.Namespace) ||
 			(actualResponse.Allowed != test.response.Allowed) ||
 			(!strings.HasPrefix(actualResponse.Reason, test.response.Reason)) {
 			failMessage = fmt.Sprintf("%s: from review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", test.description, test.review, &test.response, actualResponse)
 			return false, nil
d3b5c424
 		}
 
8c5b680f
 		failMessage = ""
 		return true, nil
 	})
 
 	if err != nil {
 		t.Error(err)
 	}
 	if len(failMessage) != 0 {
 		t.Error(failMessage)
fdd9bb5b
 	}
d3b5c424
 }
fdd9bb5b
 
969809c1
 func TestAuthorizationSubjectAccessReview(t *testing.T) {
47caf770
 	_, clusterAdminKubeConfig, err := testutil.StartTestMaster()
d3b5c424
 	if err != nil {
 		t.Fatalf("unexpected error: %v", err)
fdd9bb5b
 	}
 
47caf770
 	clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
fdd9bb5b
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
fdd9bb5b
 	}
 
47caf770
 	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
d3b5c424
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
d3b5c424
 	}
47caf770
 
 	haroldClient, err := testutil.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "hammer-project", "harold")
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
47caf770
 	}
 
e6d83541
 	markClient, err := testutil.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "mallet-project", "mark")
fdd9bb5b
 	if err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
fdd9bb5b
 	}
d3b5c424
 
33608784
 	dannyClient, _, _, err := testutil.GetClientForUser(*clusterAdminClientConfig, "danny")
05ead3f2
 	if err != nil {
 		t.Fatalf("error requesting token: %v", err)
 	}
 
 	addDanny := &policy.RoleModificationOptions{
 		RoleNamespace:       "",
 		RoleName:            bootstrappolicy.ViewRoleName,
 		RoleBindingAccessor: policy.NewLocalRoleBindingAccessor("default", clusterAdminClient),
 		Users:               []string{"danny"},
 	}
 	if err := addDanny.AddRole(); err != nil {
 		t.Errorf("unexpected error: %v", err)
 	}
 	askCanDannyGetProject := &authorizationapi.SubjectAccessReview{User: "danny", Verb: "get", Resource: "projects"}
 	subjectAccessReviewTest{
 		description:     "cluster admin told danny can get project default",
 		clientInterface: clusterAdminClient.SubjectAccessReviews("default"),
 		review:          askCanDannyGetProject,
 		response: authorizationapi.SubjectAccessReviewResponse{
 			Allowed:   true,
 			Reason:    "allowed by rule in default",
 			Namespace: "default",
 		},
 	}.run(t)
 	subjectAccessReviewTest{
 		description:     "cluster admin told danny cannot get projects cluster-wide",
 		clientInterface: clusterAdminClient.ClusterSubjectAccessReviews(),
 		review:          askCanDannyGetProject,
 		response: authorizationapi.SubjectAccessReviewResponse{
 			Allowed:   false,
 			Reason:    `User "danny" cannot get projects at the cluster scope`,
 			Namespace: "",
 		},
 	}.run(t)
 	subjectAccessReviewTest{
 		description:     "as danny, can I make cluster subject access reviews",
 		clientInterface: dannyClient.ClusterSubjectAccessReviews(),
 		review:          askCanDannyGetProject,
 		err:             `User "danny" cannot create subjectaccessreviews at the cluster scope`,
 	}.run(t)
 
01221dec
 	addValerie := &policy.RoleModificationOptions{
ba6a7bc8
 		RoleNamespace:       "",
 		RoleName:            bootstrappolicy.ViewRoleName,
 		RoleBindingAccessor: policy.NewLocalRoleBindingAccessor("hammer-project", haroldClient),
 		Users:               []string{"valerie"},
d3b5c424
 	}
01221dec
 	if err := addValerie.AddRole(); err != nil {
d3b5c424
 		t.Errorf("unexpected error: %v", err)
 	}
 
01221dec
 	addEdgar := &policy.RoleModificationOptions{
ba6a7bc8
 		RoleNamespace:       "",
 		RoleName:            bootstrappolicy.EditRoleName,
 		RoleBindingAccessor: policy.NewLocalRoleBindingAccessor("mallet-project", markClient),
 		Users:               []string{"edgar"},
fdd9bb5b
 	}
01221dec
 	if err := addEdgar.AddRole(); err != nil {
e6d83541
 		t.Fatalf("unexpected error: %v", err)
fdd9bb5b
 	}
d3b5c424
 
f69463d3
 	askCanValerieGetProject := &authorizationapi.SubjectAccessReview{User: "valerie", Verb: "get", Resource: "projects"}
d3b5c424
 	subjectAccessReviewTest{
05ead3f2
 		description:     "harold told valerie can get project hammer-project",
d3b5c424
 		clientInterface: haroldClient.SubjectAccessReviews("hammer-project"),
 		review:          askCanValerieGetProject,
 		response: authorizationapi.SubjectAccessReviewResponse{
 			Allowed:   true,
 			Reason:    "allowed by rule in hammer-project",
 			Namespace: "hammer-project",
 		},
 	}.run(t)
 	subjectAccessReviewTest{
05ead3f2
 		description:     "mark told valerie cannot get project mallet-project",
d3b5c424
 		clientInterface: markClient.SubjectAccessReviews("mallet-project"),
 		review:          askCanValerieGetProject,
 		response: authorizationapi.SubjectAccessReviewResponse{
 			Allowed:   false,
05ead3f2
 			Reason:    `User "valerie" cannot get projects in project "mallet-project"`,
d3b5c424
 			Namespace: "mallet-project",
 		},
 	}.run(t)
 
f69463d3
 	askCanEdgarDeletePods := &authorizationapi.SubjectAccessReview{User: "edgar", Verb: "delete", Resource: "pods"}
d3b5c424
 	subjectAccessReviewTest{
05ead3f2
 		description:     "mark told edgar can delete pods in mallet-project",
d3b5c424
 		clientInterface: markClient.SubjectAccessReviews("mallet-project"),
 		review:          askCanEdgarDeletePods,
 		response: authorizationapi.SubjectAccessReviewResponse{
 			Allowed:   true,
 			Reason:    "allowed by rule in mallet-project",
 			Namespace: "mallet-project",
 		},
 	}.run(t)
 	subjectAccessReviewTest{
05ead3f2
 		description:     "harold denied ability to run subject access review in project mallet-project",
d3b5c424
 		clientInterface: haroldClient.SubjectAccessReviews("mallet-project"),
 		review:          askCanEdgarDeletePods,
05ead3f2
 		err:             `User "harold" cannot create subjectaccessreviews in project "mallet-project"`,
d3b5c424
 	}.run(t)
 
f69463d3
 	askCanHaroldUpdateProject := &authorizationapi.SubjectAccessReview{User: "harold", Verb: "update", Resource: "projects"}
d3b5c424
 	subjectAccessReviewTest{
05ead3f2
 		description:     "harold told harold can update project hammer-project",
d3b5c424
 		clientInterface: haroldClient.SubjectAccessReviews("hammer-project"),
 		review:          askCanHaroldUpdateProject,
 		response: authorizationapi.SubjectAccessReviewResponse{
 			Allowed:   true,
 			Reason:    "allowed by rule in hammer-project",
 			Namespace: "hammer-project",
 		},
 	}.run(t)
 
f35dc17e
 	askCanClusterAdminsCreateProject := &authorizationapi.SubjectAccessReview{Groups: util.NewStringSet("system:cluster-admins"), Verb: "create", Resource: "projects"}
d3b5c424
 	subjectAccessReviewTest{
05ead3f2
 		description:     "cluster admin told cluster admins can create projects",
384f11fa
 		clientInterface: clusterAdminClient.ClusterSubjectAccessReviews(),
d3b5c424
 		review:          askCanClusterAdminsCreateProject,
 		response: authorizationapi.SubjectAccessReviewResponse{
 			Allowed:   true,
c3fc484a
 			Reason:    "allowed by cluster rule",
d3b5c424
 			Namespace: "",
 		},
 	}.run(t)
 	subjectAccessReviewTest{
05ead3f2
 		description:     "harold denied ability to run cluster subject access review",
384f11fa
 		clientInterface: haroldClient.ClusterSubjectAccessReviews(),
d3b5c424
 		review:          askCanClusterAdminsCreateProject,
05ead3f2
 		err:             `User "harold" cannot create subjectaccessreviews at the cluster scope`,
d3b5c424
 	}.run(t)
5b57b99f
 
05ead3f2
 	askCanICreatePods := &authorizationapi.SubjectAccessReview{Verb: "create", Resource: "pods"}
5b57b99f
 	subjectAccessReviewTest{
05ead3f2
 		description:     "harold told he can create pods in project hammer-project",
5b57b99f
 		clientInterface: haroldClient.SubjectAccessReviews("hammer-project"),
 		review:          askCanICreatePods,
 		response: authorizationapi.SubjectAccessReviewResponse{
 			Allowed:   true,
 			Reason:    "allowed by rule in hammer-project",
 			Namespace: "hammer-project",
 		},
 	}.run(t)
 	askCanICreatePolicyBindings := &authorizationapi.SubjectAccessReview{Verb: "create", Resource: "policybindings"}
 	subjectAccessReviewTest{
05ead3f2
 		description:     "harold told he can create policybindings in project hammer-project",
5b57b99f
 		clientInterface: haroldClient.SubjectAccessReviews("hammer-project"),
 		review:          askCanICreatePolicyBindings,
 		response: authorizationapi.SubjectAccessReviewResponse{
 			Allowed:   false,
05ead3f2
 			Reason:    `User "harold" cannot create policybindings in project "hammer-project"`,
5b57b99f
 			Namespace: "hammer-project",
 		},
 	}.run(t)
 
fdd9bb5b
 }