package integration
import (
"fmt"
"reflect"
"strings"
"testing"
"time"
kapi "k8s.io/kubernetes/pkg/api"
kapierror "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/unversioned"
kunvapi "k8s.io/kubernetes/pkg/api/unversioned"
extensionsapi "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/util/wait"
authorizationapi "github.com/openshift/origin/pkg/authorization/api"
buildapi "github.com/openshift/origin/pkg/build/api"
"github.com/openshift/origin/pkg/client"
policy "github.com/openshift/origin/pkg/cmd/admin/policy"
"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
deployapi "github.com/openshift/origin/pkg/deploy/api"
imageapi "github.com/openshift/origin/pkg/image/api"
oauthapi "github.com/openshift/origin/pkg/oauth/api"
testutil "github.com/openshift/origin/test/util"
testserver "github.com/openshift/origin/test/util/server"
)
func prettyPrintAction(act *authorizationapi.Action, defaultNamespaceStr string) string {
nsStr := fmt.Sprintf("in namespace %q", act.Namespace)
if act.Namespace == "" {
nsStr = defaultNamespaceStr
}
var resourceStr string
if act.Group == "" && act.Version == "" {
resourceStr = act.Resource
} else {
groupVer := kunvapi.GroupVersion{Group: act.Group, Version: act.Version}
resourceStr = fmt.Sprintf("%s/%s", act.Resource, groupVer.String())
}
var base string
if act.ResourceName == "" {
base = fmt.Sprintf("who can %s %s %s", act.Verb, resourceStr, nsStr)
} else {
base = fmt.Sprintf("who can %s the %s named %q %s", act.Verb, resourceStr, act.ResourceName, nsStr)
}
if act.Content != nil {
return fmt.Sprintf("%s with content %#v", base, act.Content)
}
return base
}
func prettyPrintReviewResponse(resp *authorizationapi.ResourceAccessReviewResponse) string {
nsStr := fmt.Sprintf("(in the namespace %q)\n", resp.Namespace)
if resp.Namespace == "" {
nsStr = "(in all namespaces)\n"
}
var usersStr string
if resp.Users.Len() > 0 {
userStrList := make([]string, 0, len(resp.Users))
for userName := range resp.Users {
userStrList = append(userStrList, fmt.Sprintf(" - %s\n", userName))
}
usersStr = fmt.Sprintf(" users:\n%s", strings.Join(userStrList, ""))
}
var groupsStr string
if resp.Groups.Len() > 0 {
groupStrList := make([]string, 0, len(resp.Groups))
for groupName := range resp.Groups {
groupStrList = append(groupStrList, fmt.Sprintf(" - %s\n", groupName))
}
groupsStr = fmt.Sprintf(" groups:\n%s", strings.Join(groupStrList, ""))
}
return fmt.Sprintf(nsStr + usersStr + groupsStr)
}
func TestClusterReaderCoverage(t *testing.T) {
testutil.RequireEtcd(t)
defer testutil.DumpEtcdOnFailure(t)
_, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
discoveryClient := client.NewDiscoveryClient(clusterAdminClient.RESTClient)
// (map[string]*unversioned.APIResourceList, error)
allResourceList, err := discoveryClient.ServerResources()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
allResources := map[unversioned.GroupResource]bool{}
for _, resources := range allResourceList {
version, err := unversioned.ParseGroupVersion(resources.GroupVersion)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
for _, resource := range resources.APIResources {
allResources[version.WithResource(resource.Name).GroupResource()] = true
}
}
escalatingResources := map[unversioned.GroupResource]bool{
oauthapi.Resource("oauthauthorizetokens"): true,
oauthapi.Resource("oauthaccesstokens"): true,
oauthapi.Resource("oauthclients"): true,
imageapi.Resource("imagestreams/secrets"): true,
kapi.Resource("secrets"): true,
kapi.Resource("pods/exec"): true,
kapi.Resource("pods/proxy"): true,
kapi.Resource("pods/portforward"): true,
kapi.Resource("nodes/proxy"): true,
kapi.Resource("services/proxy"): true,
}
readerRole, err := clusterAdminClient.ClusterRoles().Get(bootstrappolicy.ClusterReaderRoleName)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
for _, rule := range readerRole.Rules {
for _, group := range rule.APIGroups {
for resource := range rule.Resources {
gr := unversioned.GroupResource{Group: group, Resource: resource}
if escalatingResources[gr] {
t.Errorf("cluster-reader role has escalating resource %v. Check pkg/cmd/server/bootstrappolicy/policy.go.", gr)
}
delete(allResources, gr)
}
}
}
// remove escalating resources that cluster-reader should not have access to
for resource := range escalatingResources {
delete(allResources, resource)
}
// remove resources without read APIs
nonreadingResources := []unversioned.GroupResource{
buildapi.Resource("buildconfigs/instantiatebinary"), buildapi.Resource("buildconfigs/instantiate"), buildapi.Resource("builds/clone"),
deployapi.Resource("deploymentconfigrollbacks"), deployapi.Resource("generatedeploymentconfigs"),
deployapi.Resource("deploymentconfigs/rollback"), deployapi.Resource("deploymentconfigs/instantiate"),
imageapi.Resource("imagestreamimports"), imageapi.Resource("imagestreammappings"),
extensionsapi.Resource("deployments/rollback"),
kapi.Resource("pods/attach"), kapi.Resource("namespaces/finalize"),
}
for _, resource := range nonreadingResources {
delete(allResources, resource)
}
// anything left in the map is missing from the permissions
if len(allResources) > 0 {
t.Errorf("cluster-reader role is missing %v. Check pkg/cmd/server/bootstrappolicy/policy.go.", allResources)
}
}
func TestAuthorizationRestrictedAccessForProjectAdmins(t *testing.T) {
testutil.RequireEtcd(t)
defer testutil.DumpEtcdOnFailure(t)
_, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
haroldClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "hammer-project", "harold")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
markClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "mallet-project", "mark")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = haroldClient.DeploymentConfigs("hammer-project").List(kapi.ListOptions{})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = markClient.DeploymentConfigs("hammer-project").List(kapi.ListOptions{})
if (err == nil) || !kapierror.IsForbidden(err) {
t.Fatalf("unexpected error: %v", err)
}
// 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 {
t.Fatalf("unexpected error: %v", err)
}
_, err = markClient.Projects().Get("hammer-project")
if (err == nil) || !kapierror.IsForbidden(err) {
t.Fatalf("unexpected error: %v", err)
}
// wait for the project authorization cache to catch the change. It is on a one second period
waitForProject(t, haroldClient, "hammer-project", 1*time.Second, 10)
waitForProject(t, markClient, "mallet-project", 1*time.Second, 10)
}
// 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(kapi.ListOptions{})
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)
}
func TestAuthorizationResolution(t *testing.T) {
testutil.RequireEtcd(t)
defer testutil.DumpEtcdOnFailure(t)
_, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
addValerie := &policy.RoleModificationOptions{
RoleNamespace: "",
RoleName: bootstrappolicy.ViewRoleName,
RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(clusterAdminClient),
Users: []string{"valerie"},
}
if err := addValerie.AddRole(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err = clusterAdminClient.ClusterRoles().Delete(bootstrappolicy.ViewRoleName); err != nil {
t.Fatalf("unexpected error: %v", err)
}
addEdgar := &policy.RoleModificationOptions{
RoleNamespace: "",
RoleName: bootstrappolicy.EditRoleName,
RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(clusterAdminClient),
Users: []string{"edgar"},
}
if err := addEdgar.AddRole(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
// try to add Valerie to a non-existent role, looping until it is true due to
// the policy cache taking time to react
if err := wait.Poll(time.Second, 2*time.Minute, func() (bool, error) {
err := addValerie.AddRole()
if kapierror.IsNotFound(err) {
return true, nil
}
return false, err
}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
roleWithGroup := &authorizationapi.ClusterRole{}
roleWithGroup.Name = "with-group"
roleWithGroup.Rules = append(roleWithGroup.Rules, authorizationapi.PolicyRule{
Verbs: sets.NewString("list"),
Resources: sets.NewString("resourcegroup:builds"),
})
if _, err := clusterAdminClient.ClusterRoles().Create(roleWithGroup); err != nil {
t.Fatalf("unexpected error: %v", err)
}
addBuildLister := &policy.RoleModificationOptions{
RoleNamespace: "",
RoleName: "with-group",
RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(clusterAdminClient),
Users: []string{"build-lister"},
}
if err := addBuildLister.AddRole(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
buildListerClient, _, _, err := testutil.GetClientForUser(*clusterAdminConfig, "build-lister")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// the authorization cache may not be up to date, retry
if err := wait.Poll(10*time.Millisecond, 2*time.Minute, func() (bool, error) {
_, err := buildListerClient.Builds(kapi.NamespaceDefault).List(kapi.ListOptions{})
if kapierror.IsForbidden(err) {
return false, nil
}
return err == nil, err
}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err := buildListerClient.Builds(kapi.NamespaceDefault).List(kapi.ListOptions{}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err := buildListerClient.DeploymentConfigs(kapi.NamespaceDefault).List(kapi.ListOptions{}); !kapierror.IsForbidden(err) {
t.Errorf("expected forbidden, got %v", err)
}
}
// TODO this list should start collapsing as we continue to tighten access on generated system ids
var globalClusterAdminUsers = sets.NewString("system:admin")
var globalClusterAdminGroups = sets.NewString("system:cluster-admins", "system:masters")
// This list includes the admins from above, plus users or groups known to have global view access
var globalClusterReaderUsers = sets.NewString("system:serviceaccount:openshift-infra:namespace-controller", "system:admin")
var globalClusterReaderGroups = sets.NewString("system:cluster-readers", "system:cluster-admins", "system:masters")
// this list includes any other users who can get DeploymentConfigs
var globalDeploymentConfigGetterUsers = sets.NewString("system:serviceaccount:openshift-infra:unidling-controller")
type resourceAccessReviewTest struct {
description string
clientInterface client.ResourceAccessReviewInterface
review *authorizationapi.ResourceAccessReview
response authorizationapi.ResourceAccessReviewResponse
err string
}
func (test resourceAccessReviewTest) run(t *testing.T) {
failMessage := ""
// keep trying the test until you get a success or you timeout. Every time you have a failure, set the fail message
// so that if you never have a success, we can call t.Errorf with a reasonable message
// exiting the poll with `failMessage=""` indicates success.
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.Contains(err.Error(), test.err) {
failMessage = fmt.Sprintf("%s: expected %v, got %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
}
}
if actualResponse.Namespace != test.response.Namespace ||
!reflect.DeepEqual(actualResponse.Users.List(), test.response.Users.List()) ||
!reflect.DeepEqual(actualResponse.Groups.List(), test.response.Groups.List()) ||
actualResponse.EvaluationError != test.response.EvaluationError {
failMessage = fmt.Sprintf("%s:\n %s:\n expected %s\n got %s", test.description, prettyPrintAction(&test.review.Action, "(in any namespace)"), prettyPrintReviewResponse(&test.response), prettyPrintReviewResponse(actualResponse))
return false, nil
}
failMessage = ""
return true, nil
})
if err != nil {
t.Error(err)
}
if len(failMessage) != 0 {
t.Error(failMessage)
}
}
type localResourceAccessReviewTest struct {
description string
clientInterface client.LocalResourceAccessReviewInterface
review *authorizationapi.LocalResourceAccessReview
response authorizationapi.ResourceAccessReviewResponse
err string
}
func (test localResourceAccessReviewTest) run(t *testing.T) {
failMessage := ""
// keep trying the test until you get a success or you timeout. Every time you have a failure, set the fail message
// so that if you never have a success, we can call t.Errorf with a reasonable message
// exiting the poll with `failMessage=""` indicates success.
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.Contains(err.Error(), test.err) {
failMessage = fmt.Sprintf("%s: expected %v, got %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
}
}
if actualResponse.Namespace != test.response.Namespace ||
!reflect.DeepEqual(actualResponse.Users.List(), test.response.Users.List()) ||
!reflect.DeepEqual(actualResponse.Groups.List(), test.response.Groups.List()) ||
actualResponse.EvaluationError != test.response.EvaluationError {
failMessage = fmt.Sprintf("%s:\n %s:\n expected %s\n got %s", test.description, prettyPrintAction(&test.review.Action, "(in the current namespace)"), prettyPrintReviewResponse(&test.response), prettyPrintReviewResponse(actualResponse))
return false, nil
}
failMessage = ""
return true, nil
})
if err != nil {
t.Error(err)
}
if len(failMessage) != 0 {
t.Error(failMessage)
}
}
func TestAuthorizationResourceAccessReview(t *testing.T) {
testutil.RequireEtcd(t)
defer testutil.DumpEtcdOnFailure(t)
_, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
haroldClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "hammer-project", "harold")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
markClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "mallet-project", "mark")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
addValerie := &policy.RoleModificationOptions{
RoleNamespace: "",
RoleName: bootstrappolicy.ViewRoleName,
RoleBindingAccessor: policy.NewLocalRoleBindingAccessor("hammer-project", haroldClient),
Users: []string{"valerie"},
}
if err := addValerie.AddRole(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
addEdgar := &policy.RoleModificationOptions{
RoleNamespace: "",
RoleName: bootstrappolicy.EditRoleName,
RoleBindingAccessor: policy.NewLocalRoleBindingAccessor("mallet-project", markClient),
Users: []string{"edgar"},
}
if err := addEdgar.AddRole(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
requestWhoCanViewDeploymentConfigs := &authorizationapi.ResourceAccessReview{
Action: authorizationapi.Action{Verb: "get", Resource: "deploymentconfigs"},
}
localRequestWhoCanViewDeploymentConfigs := &authorizationapi.LocalResourceAccessReview{
Action: authorizationapi.Action{Verb: "get", Resource: "deploymentconfigs"},
}
{
test := localResourceAccessReviewTest{
description: "who can view deploymentconfigs in hammer by harold",
clientInterface: haroldClient.LocalResourceAccessReviews("hammer-project"),
review: localRequestWhoCanViewDeploymentConfigs,
response: authorizationapi.ResourceAccessReviewResponse{
Users: sets.NewString("harold", "valerie"),
Groups: sets.NewString(),
Namespace: "hammer-project",
},
}
test.response.Users.Insert(globalClusterReaderUsers.List()...)
test.response.Users.Insert(globalDeploymentConfigGetterUsers.List()...)
test.response.Groups.Insert(globalClusterReaderGroups.List()...)
test.run(t)
}
{
test := localResourceAccessReviewTest{
description: "who can view deploymentconfigs in mallet by mark",
clientInterface: markClient.LocalResourceAccessReviews("mallet-project"),
review: localRequestWhoCanViewDeploymentConfigs,
response: authorizationapi.ResourceAccessReviewResponse{
Users: sets.NewString("mark", "edgar"),
Groups: sets.NewString(),
Namespace: "mallet-project",
},
}
test.response.Users.Insert(globalClusterReaderUsers.List()...)
test.response.Users.Insert(globalDeploymentConfigGetterUsers.List()...)
test.response.Groups.Insert(globalClusterReaderGroups.List()...)
test.run(t)
}
// mark should not be able to make global access review requests
{
test := resourceAccessReviewTest{
description: "who can view deploymentconfigs in all by mark",
clientInterface: markClient.ResourceAccessReviews(),
review: requestWhoCanViewDeploymentConfigs,
err: "cannot ",
}
test.run(t)
}
// a cluster-admin should be able to make global access review requests
{
test := resourceAccessReviewTest{
description: "who can view deploymentconfigs in all by cluster-admin",
clientInterface: clusterAdminClient.ResourceAccessReviews(),
review: requestWhoCanViewDeploymentConfigs,
response: authorizationapi.ResourceAccessReviewResponse{
Users: sets.NewString(),
Groups: sets.NewString(),
},
}
test.response.Users.Insert(globalClusterReaderUsers.List()...)
test.response.Users.Insert(globalDeploymentConfigGetterUsers.List()...)
test.response.Groups.Insert(globalClusterReaderGroups.List()...)
test.run(t)
}
{
if err := clusterAdminClient.ClusterRoles().Delete(bootstrappolicy.AdminRoleName); err != nil {
t.Errorf("unexpected error: %v", err)
}
test := localResourceAccessReviewTest{
description: "who can view deploymentconfigs in mallet by cluster-admin",
clientInterface: clusterAdminClient.LocalResourceAccessReviews("mallet-project"),
review: localRequestWhoCanViewDeploymentConfigs,
response: authorizationapi.ResourceAccessReviewResponse{
Users: sets.NewString("edgar"),
Groups: sets.NewString(),
Namespace: "mallet-project",
EvaluationError: `role "admin" not found`,
},
}
test.response.Users.Insert(globalClusterReaderUsers.List()...)
test.response.Users.Insert(globalDeploymentConfigGetterUsers.List()...)
test.response.Groups.Insert(globalClusterReaderGroups.List()...)
test.run(t)
}
}
type subjectAccessReviewTest struct {
description string
localInterface client.LocalSubjectAccessReviewInterface
clusterInterface client.SubjectAccessReviewInterface
localReview *authorizationapi.LocalSubjectAccessReview
clusterReview *authorizationapi.SubjectAccessReview
response authorizationapi.SubjectAccessReviewResponse
err string
}
func (test subjectAccessReviewTest) run(t *testing.T) {
failMessage := ""
err := wait.Poll(testutil.PolicyCachePollInterval, testutil.PolicyCachePollTimeout, func() (bool, error) {
var err error
var actualResponse *authorizationapi.SubjectAccessReviewResponse
if test.localReview != nil {
actualResponse, err = test.localInterface.Create(test.localReview)
} else {
actualResponse, err = test.clusterInterface.Create(test.clusterReview)
}
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
}
}
if (actualResponse.Namespace != test.response.Namespace) ||
(actualResponse.Allowed != test.response.Allowed) ||
(!strings.HasPrefix(actualResponse.Reason, test.response.Reason)) {
if test.localReview != nil {
failMessage = fmt.Sprintf("%s: from local review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", test.description, test.localReview, &test.response, actualResponse)
} else {
failMessage = fmt.Sprintf("%s: from review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", test.description, test.clusterReview, &test.response, actualResponse)
}
return false, nil
}
failMessage = ""
return true, nil
})
if err != nil {
t.Error(err)
}
if len(failMessage) != 0 {
t.Error(failMessage)
}
}
func TestAuthorizationSubjectAccessReviewAPIGroup(t *testing.T) {
testutil.RequireEtcd(t)
defer testutil.DumpEtcdOnFailure(t)
_, clusterAdminKubeConfig, err := testserver.StartTestMaster()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "hammer-project", "harold")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// SAR honors API Group
subjectAccessReviewTest{
description: "cluster admin told harold can get extensions.horizontalpodautoscalers in project hammer-project",
localInterface: clusterAdminClient.LocalSubjectAccessReviews("hammer-project"),
localReview: &authorizationapi.LocalSubjectAccessReview{
User: "harold",
Action: authorizationapi.Action{Verb: "get", Group: "extensions", Resource: "horizontalpodautoscalers"},
},
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: true,
Reason: "allowed by rule in hammer-project",
Namespace: "hammer-project",
},
}.run(t)
subjectAccessReviewTest{
description: "cluster admin told harold cannot get horizontalpodautoscalers (with no API group) in project hammer-project",
localInterface: clusterAdminClient.LocalSubjectAccessReviews("hammer-project"),
localReview: &authorizationapi.LocalSubjectAccessReview{
User: "harold",
Action: authorizationapi.Action{Verb: "get", Group: "", Resource: "horizontalpodautoscalers"},
},
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: false,
Reason: `User "harold" cannot get horizontalpodautoscalers in project "hammer-project"`,
Namespace: "hammer-project",
},
}.run(t)
subjectAccessReviewTest{
description: "cluster admin told harold cannot get horizontalpodautoscalers (with invalid API group) in project hammer-project",
localInterface: clusterAdminClient.LocalSubjectAccessReviews("hammer-project"),
localReview: &authorizationapi.LocalSubjectAccessReview{
User: "harold",
Action: authorizationapi.Action{Verb: "get", Group: "foo", Resource: "horizontalpodautoscalers"},
},
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: false,
Reason: `User "harold" cannot get foo.horizontalpodautoscalers in project "hammer-project"`,
Namespace: "hammer-project",
},
}.run(t)
subjectAccessReviewTest{
description: "cluster admin told harold cannot get horizontalpodautoscalers (with * API group) in project hammer-project",
localInterface: clusterAdminClient.LocalSubjectAccessReviews("hammer-project"),
localReview: &authorizationapi.LocalSubjectAccessReview{
User: "harold",
Action: authorizationapi.Action{Verb: "get", Group: "*", Resource: "horizontalpodautoscalers"},
},
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: false,
Reason: `User "harold" cannot get *.horizontalpodautoscalers in project "hammer-project"`,
Namespace: "hammer-project",
},
}.run(t)
// SAR honors API Group for cluster admin self SAR
subjectAccessReviewTest{
description: "cluster admin told they can get extensions.horizontalpodautoscalers in project hammer-project",
localInterface: clusterAdminClient.LocalSubjectAccessReviews("any-project"),
localReview: &authorizationapi.LocalSubjectAccessReview{
Action: authorizationapi.Action{Verb: "get", Group: "extensions", Resource: "horizontalpodautoscalers"},
},
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: true,
Reason: "allowed by rule in any-project",
Namespace: "any-project",
},
}.run(t)
subjectAccessReviewTest{
description: "cluster admin told they can get horizontalpodautoscalers (with no API group) in project any-project",
localInterface: clusterAdminClient.LocalSubjectAccessReviews("any-project"),
localReview: &authorizationapi.LocalSubjectAccessReview{
Action: authorizationapi.Action{Verb: "get", Group: "", Resource: "horizontalpodautoscalers"},
},
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: true,
Reason: "allowed by rule in any-project",
Namespace: "any-project",
},
}.run(t)
subjectAccessReviewTest{
description: "cluster admin told they can get horizontalpodautoscalers (with invalid API group) in project any-project",
localInterface: clusterAdminClient.LocalSubjectAccessReviews("any-project"),
localReview: &authorizationapi.LocalSubjectAccessReview{
Action: authorizationapi.Action{Verb: "get", Group: "foo", Resource: "horizontalpodautoscalers"},
},
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: true,
Reason: "allowed by rule in any-project",
Namespace: "any-project",
},
}.run(t)
subjectAccessReviewTest{
description: "cluster admin told they can get horizontalpodautoscalers (with * API group) in project any-project",
localInterface: clusterAdminClient.LocalSubjectAccessReviews("any-project"),
localReview: &authorizationapi.LocalSubjectAccessReview{
Action: authorizationapi.Action{Verb: "get", Group: "*", Resource: "horizontalpodautoscalers"},
},
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: true,
Reason: "allowed by rule in any-project",
Namespace: "any-project",
},
}.run(t)
}
func TestAuthorizationSubjectAccessReview(t *testing.T) {
testutil.RequireEtcd(t)
defer testutil.DumpEtcdOnFailure(t)
_, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
haroldClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "hammer-project", "harold")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
markClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "mallet-project", "mark")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
dannyClient, _, dannyConfig, err := testutil.GetClientForUser(*clusterAdminClientConfig, "danny")
if err != nil {
t.Fatalf("error requesting token: %v", err)
}
anonymousConfig := clientcmd.AnonymousClientConfig(clusterAdminClientConfig)
anonymousClient, err := client.New(&anonymousConfig)
if err != nil {
t.Fatalf("error getting anonymous client: %v", err)
}
addAnonymous := &policy.RoleModificationOptions{
RoleNamespace: "",
RoleName: bootstrappolicy.EditRoleName,
RoleBindingAccessor: policy.NewLocalRoleBindingAccessor("hammer-project", clusterAdminClient),
Users: []string{"system:anonymous"},
}
if err := addAnonymous.AddRole(); err != nil {
t.Errorf("unexpected error: %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",
Action: authorizationapi.Action{Verb: "get", Resource: "projects"},
}
subjectAccessReviewTest{
description: "cluster admin told danny can get project default",
localInterface: clusterAdminClient.LocalSubjectAccessReviews("default"),
localReview: &authorizationapi.LocalSubjectAccessReview{
User: "danny",
Action: authorizationapi.Action{Verb: "get", Resource: "projects"},
},
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",
clusterInterface: clusterAdminClient.SubjectAccessReviews(),
clusterReview: 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",
clusterInterface: dannyClient.SubjectAccessReviews(),
clusterReview: askCanDannyGetProject,
err: `User "danny" cannot create subjectaccessreviews at the cluster scope`,
}.run(t)
subjectAccessReviewTest{
description: "as anonymous, can I make cluster subject access reviews",
clusterInterface: anonymousClient.SubjectAccessReviews(),
clusterReview: askCanDannyGetProject,
err: `User "system:anonymous" cannot create subjectaccessreviews at the cluster scope`,
}.run(t)
addValerie := &policy.RoleModificationOptions{
RoleNamespace: "",
RoleName: bootstrappolicy.ViewRoleName,
RoleBindingAccessor: policy.NewLocalRoleBindingAccessor("hammer-project", haroldClient),
Users: []string{"valerie"},
}
if err := addValerie.AddRole(); err != nil {
t.Errorf("unexpected error: %v", err)
}
addEdgar := &policy.RoleModificationOptions{
RoleNamespace: "",
RoleName: bootstrappolicy.EditRoleName,
RoleBindingAccessor: policy.NewLocalRoleBindingAccessor("mallet-project", markClient),
Users: []string{"edgar"},
}
if err := addEdgar.AddRole(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
askCanValerieGetProject := &authorizationapi.LocalSubjectAccessReview{
User: "valerie",
Action: authorizationapi.Action{Verb: "get", Resource: "projects"},
}
subjectAccessReviewTest{
description: "harold told valerie can get project hammer-project",
localInterface: haroldClient.LocalSubjectAccessReviews("hammer-project"),
localReview: askCanValerieGetProject,
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: true,
Reason: "allowed by rule in hammer-project",
Namespace: "hammer-project",
},
}.run(t)
subjectAccessReviewTest{
description: "mark told valerie cannot get project mallet-project",
localInterface: markClient.LocalSubjectAccessReviews("mallet-project"),
localReview: askCanValerieGetProject,
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: false,
Reason: `User "valerie" cannot get projects in project "mallet-project"`,
Namespace: "mallet-project",
},
}.run(t)
askCanEdgarDeletePods := &authorizationapi.LocalSubjectAccessReview{
User: "edgar",
Action: authorizationapi.Action{Verb: "delete", Resource: "pods"},
}
subjectAccessReviewTest{
description: "mark told edgar can delete pods in mallet-project",
localInterface: markClient.LocalSubjectAccessReviews("mallet-project"),
localReview: askCanEdgarDeletePods,
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: true,
Reason: "allowed by rule in mallet-project",
Namespace: "mallet-project",
},
}.run(t)
// ensure unprivileged users cannot check other users' access
subjectAccessReviewTest{
description: "harold denied ability to run subject access review in project mallet-project",
localInterface: haroldClient.LocalSubjectAccessReviews("mallet-project"),
localReview: askCanEdgarDeletePods,
err: `User "harold" cannot create localsubjectaccessreviews in project "mallet-project"`,
}.run(t)
subjectAccessReviewTest{
description: "system:anonymous denied ability to run subject access review in project mallet-project",
localInterface: anonymousClient.LocalSubjectAccessReviews("mallet-project"),
localReview: askCanEdgarDeletePods,
err: `User "system:anonymous" cannot create localsubjectaccessreviews in project "mallet-project"`,
}.run(t)
// ensure message does not leak whether the namespace exists or not
subjectAccessReviewTest{
description: "harold denied ability to run subject access review in project nonexistent-project",
localInterface: haroldClient.LocalSubjectAccessReviews("nonexistent-project"),
localReview: askCanEdgarDeletePods,
err: `User "harold" cannot create localsubjectaccessreviews in project "nonexistent-project"`,
}.run(t)
subjectAccessReviewTest{
description: "system:anonymous denied ability to run subject access review in project nonexistent-project",
localInterface: anonymousClient.LocalSubjectAccessReviews("nonexistent-project"),
localReview: askCanEdgarDeletePods,
err: `User "system:anonymous" cannot create localsubjectaccessreviews in project "nonexistent-project"`,
}.run(t)
askCanHaroldUpdateProject := &authorizationapi.LocalSubjectAccessReview{
User: "harold",
Action: authorizationapi.Action{Verb: "update", Resource: "projects"},
}
subjectAccessReviewTest{
description: "harold told harold can update project hammer-project",
localInterface: haroldClient.LocalSubjectAccessReviews("hammer-project"),
localReview: askCanHaroldUpdateProject,
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: true,
Reason: "allowed by rule in hammer-project",
Namespace: "hammer-project",
},
}.run(t)
askCanClusterAdminsCreateProject := &authorizationapi.SubjectAccessReview{
Groups: sets.NewString("system:cluster-admins"),
Action: authorizationapi.Action{Verb: "create", Resource: "projects"},
}
subjectAccessReviewTest{
description: "cluster admin told cluster admins can create projects",
clusterInterface: clusterAdminClient.SubjectAccessReviews(),
clusterReview: askCanClusterAdminsCreateProject,
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: true,
Reason: "allowed by cluster rule",
Namespace: "",
},
}.run(t)
subjectAccessReviewTest{
description: "harold denied ability to run cluster subject access review",
clusterInterface: haroldClient.SubjectAccessReviews(),
clusterReview: askCanClusterAdminsCreateProject,
err: `User "harold" cannot create subjectaccessreviews at the cluster scope`,
}.run(t)
askCanICreatePods := &authorizationapi.LocalSubjectAccessReview{
Action: authorizationapi.Action{Verb: "create", Resource: "pods"},
}
subjectAccessReviewTest{
description: "harold told he can create pods in project hammer-project",
localInterface: haroldClient.LocalSubjectAccessReviews("hammer-project"),
localReview: askCanICreatePods,
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: true,
Reason: "allowed by rule in hammer-project",
Namespace: "hammer-project",
},
}.run(t)
subjectAccessReviewTest{
description: "system:anonymous told he can create pods in project hammer-project",
localInterface: anonymousClient.LocalSubjectAccessReviews("hammer-project"),
localReview: askCanICreatePods,
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: true,
Reason: "allowed by rule in hammer-project",
Namespace: "hammer-project",
},
}.run(t)
// test checking self permissions when denied
subjectAccessReviewTest{
description: "harold told he cannot create pods in project mallet-project",
localInterface: haroldClient.LocalSubjectAccessReviews("mallet-project"),
localReview: askCanICreatePods,
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: false,
Reason: `User "harold" cannot create pods in project "mallet-project"`,
Namespace: "mallet-project",
},
}.run(t)
subjectAccessReviewTest{
description: "system:anonymous told he cannot create pods in project mallet-project",
localInterface: anonymousClient.LocalSubjectAccessReviews("mallet-project"),
localReview: askCanICreatePods,
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: false,
Reason: `User "system:anonymous" cannot create pods in project "mallet-project"`,
Namespace: "mallet-project",
},
}.run(t)
// test checking self-permissions doesn't leak whether namespace exists or not
// We carry a patch to allow this
subjectAccessReviewTest{
description: "harold told he cannot create pods in project nonexistent-project",
localInterface: haroldClient.LocalSubjectAccessReviews("nonexistent-project"),
localReview: askCanICreatePods,
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: false,
Reason: `User "harold" cannot create pods in project "nonexistent-project"`,
Namespace: "nonexistent-project",
},
}.run(t)
subjectAccessReviewTest{
description: "system:anonymous told he cannot create pods in project nonexistent-project",
localInterface: anonymousClient.LocalSubjectAccessReviews("nonexistent-project"),
localReview: askCanICreatePods,
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: false,
Reason: `User "system:anonymous" cannot create pods in project "nonexistent-project"`,
Namespace: "nonexistent-project",
},
}.run(t)
askCanICreatePolicyBindings := &authorizationapi.LocalSubjectAccessReview{
Action: authorizationapi.Action{Verb: "create", Resource: "policybindings"},
}
subjectAccessReviewTest{
description: "harold told he can create policybindings in project hammer-project",
localInterface: haroldClient.LocalSubjectAccessReviews("hammer-project"),
localReview: askCanICreatePolicyBindings,
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: false,
Reason: `User "harold" cannot create policybindings in project "hammer-project"`,
Namespace: "hammer-project",
},
}.run(t)
// impersonate SAR tests
// impersonated empty token SAR shouldn't be allowed at all
// impersonated danny token SAR shouldn't be allowed to see pods in hammer or in cluster
// impersonated danny token SAR should be allowed to see pods in default
// we need a token client for overriding
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
otherAdminClient, _, _, err := testutil.GetClientForUser(*clusterAdminClientConfig, "other-admin")
if err != nil {
t.Fatalf("error requesting token: %v", err)
}
addOtherAdmin := &policy.RoleModificationOptions{
RoleNamespace: "",
RoleName: bootstrappolicy.ClusterAdminRoleName,
RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(clusterAdminClient),
Users: []string{"other-admin"},
}
if err := addOtherAdmin.AddRole(); err != nil {
t.Errorf("unexpected error: %v", err)
}
subjectAccessReviewTest{
description: "empty token impersonate can't see pods in namespace",
localInterface: otherAdminClient.ImpersonateLocalSubjectAccessReviews("hammer-project", ""),
localReview: &authorizationapi.LocalSubjectAccessReview{
Action: authorizationapi.Action{Verb: "list", Resource: "pods"},
},
err: `impersonating token may not be empty`,
}.run(t)
subjectAccessReviewTest{
description: "empty token impersonate can't see pods in cluster",
clusterInterface: otherAdminClient.ImpersonateSubjectAccessReviews(""),
clusterReview: &authorizationapi.SubjectAccessReview{
Action: authorizationapi.Action{Verb: "list", Resource: "pods"},
},
err: `impersonating token may not be empty`,
}.run(t)
subjectAccessReviewTest{
description: "danny impersonate can't see pods in hammer namespace",
localInterface: otherAdminClient.ImpersonateLocalSubjectAccessReviews("hammer-project", dannyConfig.BearerToken),
localReview: &authorizationapi.LocalSubjectAccessReview{
Action: authorizationapi.Action{Verb: "list", Resource: "pods"},
},
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: false,
Reason: `User "danny" cannot list pods in project "hammer-project"`,
Namespace: "hammer-project",
},
}.run(t)
subjectAccessReviewTest{
description: "danny impersonate can't see pods in cluster",
clusterInterface: otherAdminClient.ImpersonateSubjectAccessReviews(dannyConfig.BearerToken),
clusterReview: &authorizationapi.SubjectAccessReview{
Action: authorizationapi.Action{Verb: "list", Resource: "pods"},
},
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: false,
Reason: `User "danny" cannot list all pods in the cluster`,
},
}.run(t)
subjectAccessReviewTest{
description: "danny impersonate can see pods in default",
localInterface: otherAdminClient.ImpersonateLocalSubjectAccessReviews("default", dannyConfig.BearerToken),
localReview: &authorizationapi.LocalSubjectAccessReview{
Action: authorizationapi.Action{Verb: "list", Resource: "pods"},
},
response: authorizationapi.SubjectAccessReviewResponse{
Allowed: true,
Reason: `allowed by rule in default`,
Namespace: "default",
},
}.run(t)
}
// TestOldLocalSubjectAccessReviewEndpoint checks to make sure that the old subject access review endpoint still functions properly
// this is needed to support old docker registry images
func TestOldLocalSubjectAccessReviewEndpoint(t *testing.T) {
testutil.RequireEtcd(t)
defer testutil.DumpEtcdOnFailure(t)
_, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
haroldClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "hammer-project", "harold")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
namespace := "hammer-project"
// simple check
{
sar := &authorizationapi.SubjectAccessReview{
Action: authorizationapi.Action{
Verb: "get",
Resource: "imagestreams/layers",
},
}
actualResponse := &authorizationapi.SubjectAccessReviewResponse{}
err := haroldClient.Post().Namespace(namespace).Resource("subjectAccessReviews").Body(sar).Do().Into(actualResponse)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
expectedResponse := &authorizationapi.SubjectAccessReviewResponse{
Allowed: true,
Reason: `allowed by rule in hammer-project`,
Namespace: namespace,
}
if (actualResponse.Namespace != expectedResponse.Namespace) ||
(actualResponse.Allowed != expectedResponse.Allowed) ||
(!strings.HasPrefix(actualResponse.Reason, expectedResponse.Reason)) {
t.Errorf("review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", sar, expectedResponse, actualResponse)
}
}
// namespace forced to allowed namespace so we can't trick the server into leaking
{
sar := &authorizationapi.SubjectAccessReview{
Action: authorizationapi.Action{
Namespace: "sneaky-user",
Verb: "get",
Resource: "imagestreams/layers",
},
}
actualResponse := &authorizationapi.SubjectAccessReviewResponse{}
err := haroldClient.Post().Namespace(namespace).Resource("subjectAccessReviews").Body(sar).Do().Into(actualResponse)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
expectedResponse := &authorizationapi.SubjectAccessReviewResponse{
Allowed: true,
Reason: `allowed by rule in hammer-project`,
Namespace: namespace,
}
if (actualResponse.Namespace != expectedResponse.Namespace) ||
(actualResponse.Allowed != expectedResponse.Allowed) ||
(!strings.HasPrefix(actualResponse.Reason, expectedResponse.Reason)) {
t.Errorf("review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", sar, expectedResponse, actualResponse)
}
}
// harold should be able to issue a self SAR against any project with the OLD policy
{
otherNamespace := "chisel-project"
// we need a real project for this to make it past admission.
// TODO, this is an information leaking problem. This admission plugin leaks knowledge of which projects exist via SARs
if _, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, otherNamespace, "charlie"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
// remove the new permission for localSAR
basicUserRole, err := clusterAdminClient.ClusterRoles().Get(bootstrappolicy.BasicUserRoleName)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
for i := range basicUserRole.Rules {
basicUserRole.Rules[i].Resources.Delete("localsubjectaccessreviews")
}
if _, err := clusterAdminClient.ClusterRoles().Update(basicUserRole); err != nil {
t.Fatalf("unexpected error: %v", err)
}
sar := &authorizationapi.SubjectAccessReview{
Action: authorizationapi.Action{
Verb: "get",
Resource: "imagestreams/layers",
},
}
actualResponse := &authorizationapi.SubjectAccessReviewResponse{}
err = haroldClient.Post().Namespace(otherNamespace).Resource("subjectAccessReviews").Body(sar).Do().Into(actualResponse)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
expectedResponse := &authorizationapi.SubjectAccessReviewResponse{
Allowed: false,
Reason: `User "harold" cannot get imagestreams/layers in project "chisel-project"`,
Namespace: otherNamespace,
}
if (actualResponse.Namespace != expectedResponse.Namespace) ||
(actualResponse.Allowed != expectedResponse.Allowed) ||
(!strings.HasPrefix(actualResponse.Reason, expectedResponse.Reason)) {
t.Errorf("review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", sar, expectedResponse, actualResponse)
}
}
}
// TestOldLocalResourceAccessReviewEndpoint checks to make sure that the old resource access review endpoint still functions properly
// this is needed to support old who-can client
func TestOldLocalResourceAccessReviewEndpoint(t *testing.T) {
testutil.RequireEtcd(t)
defer testutil.DumpEtcdOnFailure(t)
_, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
haroldClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "hammer-project", "harold")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
namespace := "hammer-project"
// simple check
{
rar := &authorizationapi.ResourceAccessReview{
Action: authorizationapi.Action{
Verb: "get",
Resource: "imagestreams/layers",
},
}
actualResponse := &authorizationapi.ResourceAccessReviewResponse{}
err := haroldClient.Post().Namespace(namespace).Resource("resourceAccessReviews").Body(rar).Do().Into(actualResponse)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
expectedResponse := &authorizationapi.ResourceAccessReviewResponse{
Namespace: namespace,
Users: sets.NewString("harold", "system:serviceaccount:hammer-project:builder", "system:serviceaccount:openshift-infra:namespace-controller", "system:admin"),
Groups: sets.NewString("system:cluster-admins", "system:masters", "system:cluster-readers", "system:serviceaccounts:hammer-project"),
}
if (actualResponse.Namespace != expectedResponse.Namespace) ||
!reflect.DeepEqual(actualResponse.Users.List(), expectedResponse.Users.List()) ||
!reflect.DeepEqual(actualResponse.Groups.List(), expectedResponse.Groups.List()) {
t.Errorf("review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", rar, expectedResponse, actualResponse)
}
}
// namespace forced to allowed namespace so we can't trick the server into leaking
{
rar := &authorizationapi.ResourceAccessReview{
Action: authorizationapi.Action{
Namespace: "sneaky-user",
Verb: "get",
Resource: "imagestreams/layers",
},
}
actualResponse := &authorizationapi.ResourceAccessReviewResponse{}
err := haroldClient.Post().Namespace(namespace).Resource("resourceAccessReviews").Body(rar).Do().Into(actualResponse)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
expectedResponse := &authorizationapi.ResourceAccessReviewResponse{
Namespace: namespace,
Users: sets.NewString("harold", "system:serviceaccount:hammer-project:builder", "system:serviceaccount:openshift-infra:namespace-controller", "system:admin"),
Groups: sets.NewString("system:cluster-admins", "system:masters", "system:cluster-readers", "system:serviceaccounts:hammer-project"),
}
if (actualResponse.Namespace != expectedResponse.Namespace) ||
!reflect.DeepEqual(actualResponse.Users.List(), expectedResponse.Users.List()) ||
!reflect.DeepEqual(actualResponse.Groups.List(), expectedResponse.Groups.List()) {
t.Errorf("review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", rar, expectedResponse, actualResponse)
}
}
}