...

Source file src/k8s.io/kubernetes/test/integration/auth/accessreview_test.go

Documentation: k8s.io/kubernetes/test/integration/auth

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package auth
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"net/http"
    23  	"strings"
    24  	"sync"
    25  	"testing"
    26  
    27  	authorizationapi "k8s.io/api/authorization/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apiserver/pkg/authentication/authenticator"
    30  	"k8s.io/apiserver/pkg/authentication/user"
    31  	"k8s.io/apiserver/pkg/authorization/authorizer"
    32  	api "k8s.io/kubernetes/pkg/apis/core"
    33  	"k8s.io/kubernetes/pkg/controlplane"
    34  	"k8s.io/kubernetes/test/integration/framework"
    35  	"k8s.io/kubernetes/test/utils/ktesting"
    36  )
    37  
    38  // Inject into control plane an authorizer that uses user info.
    39  // TODO(etune): remove this test once a more comprehensive built-in authorizer is implemented.
    40  type sarAuthorizer struct{}
    41  
    42  func (sarAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
    43  	if a.GetUser().GetName() == "dave" {
    44  		return authorizer.DecisionNoOpinion, "no", errors.New("I'm sorry, Dave")
    45  	}
    46  
    47  	return authorizer.DecisionAllow, "you're not dave", nil
    48  }
    49  
    50  func alwaysAlice(req *http.Request) (*authenticator.Response, bool, error) {
    51  	return &authenticator.Response{
    52  		User: &user.DefaultInfo{
    53  			Name:   "alice",
    54  			UID:    "alice",
    55  			Groups: []string{user.AllAuthenticated},
    56  		},
    57  	}, true, nil
    58  }
    59  
    60  func TestSubjectAccessReview(t *testing.T) {
    61  	tCtx := ktesting.Init(t)
    62  	clientset, _, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
    63  		ModifyServerConfig: func(config *controlplane.Config) {
    64  			// Unset BearerToken to disable BearerToken authenticator.
    65  			config.GenericConfig.LoopbackClientConfig.BearerToken = ""
    66  			config.GenericConfig.Authentication.Authenticator = authenticator.RequestFunc(alwaysAlice)
    67  			config.GenericConfig.Authorization.Authorizer = sarAuthorizer{}
    68  		},
    69  	})
    70  	defer tearDownFn()
    71  
    72  	tests := []struct {
    73  		name           string
    74  		sar            *authorizationapi.SubjectAccessReview
    75  		expectedError  string
    76  		expectedStatus authorizationapi.SubjectAccessReviewStatus
    77  	}{
    78  		{
    79  			name: "simple allow",
    80  			sar: &authorizationapi.SubjectAccessReview{
    81  				Spec: authorizationapi.SubjectAccessReviewSpec{
    82  					ResourceAttributes: &authorizationapi.ResourceAttributes{
    83  						Verb:     "list",
    84  						Group:    api.GroupName,
    85  						Version:  "v1",
    86  						Resource: "pods",
    87  					},
    88  					User: "alice",
    89  				},
    90  			},
    91  			expectedStatus: authorizationapi.SubjectAccessReviewStatus{
    92  				Allowed: true,
    93  				Reason:  "you're not dave",
    94  			},
    95  		},
    96  		{
    97  			name: "simple deny",
    98  			sar: &authorizationapi.SubjectAccessReview{
    99  				Spec: authorizationapi.SubjectAccessReviewSpec{
   100  					ResourceAttributes: &authorizationapi.ResourceAttributes{
   101  						Verb:     "list",
   102  						Group:    api.GroupName,
   103  						Version:  "v1",
   104  						Resource: "pods",
   105  					},
   106  					User: "dave",
   107  				},
   108  			},
   109  			expectedStatus: authorizationapi.SubjectAccessReviewStatus{
   110  				Allowed:         false,
   111  				Reason:          "no",
   112  				EvaluationError: "I'm sorry, Dave",
   113  			},
   114  		},
   115  		{
   116  			name: "simple error",
   117  			sar: &authorizationapi.SubjectAccessReview{
   118  				Spec: authorizationapi.SubjectAccessReviewSpec{
   119  					ResourceAttributes: &authorizationapi.ResourceAttributes{
   120  						Verb:     "list",
   121  						Group:    api.GroupName,
   122  						Version:  "v1",
   123  						Resource: "pods",
   124  					},
   125  				},
   126  			},
   127  			expectedError: "at least one of user or group must be specified",
   128  		},
   129  	}
   130  
   131  	for _, test := range tests {
   132  		response, err := clientset.AuthorizationV1().SubjectAccessReviews().Create(tCtx, test.sar, metav1.CreateOptions{})
   133  		switch {
   134  		case err == nil && len(test.expectedError) == 0:
   135  
   136  		case err != nil && strings.Contains(err.Error(), test.expectedError):
   137  			continue
   138  
   139  		case err != nil && len(test.expectedError) != 0:
   140  			t.Errorf("%s: unexpected error: %v", test.name, err)
   141  			continue
   142  		default:
   143  			t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err)
   144  			continue
   145  		}
   146  		if response.Status != test.expectedStatus {
   147  			t.Errorf("%s: expected %v, got %v", test.name, test.expectedStatus, response.Status)
   148  			continue
   149  		}
   150  	}
   151  }
   152  
   153  func TestSelfSubjectAccessReview(t *testing.T) {
   154  	tCtx := ktesting.Init(t)
   155  
   156  	var mutex sync.Mutex
   157  
   158  	username := "alice"
   159  	authenticatorFunc := func(req *http.Request) (*authenticator.Response, bool, error) {
   160  		mutex.Lock()
   161  		defer mutex.Unlock()
   162  
   163  		return &authenticator.Response{
   164  			User: &user.DefaultInfo{
   165  				Name:   username,
   166  				UID:    username,
   167  				Groups: []string{user.AllAuthenticated},
   168  			},
   169  		}, true, nil
   170  	}
   171  
   172  	clientset, _, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
   173  		ModifyServerConfig: func(config *controlplane.Config) {
   174  			// Unset BearerToken to disable BearerToken authenticator.
   175  			config.GenericConfig.LoopbackClientConfig.BearerToken = ""
   176  			config.GenericConfig.Authentication.Authenticator = authenticator.RequestFunc(authenticatorFunc)
   177  			config.GenericConfig.Authorization.Authorizer = sarAuthorizer{}
   178  		},
   179  	})
   180  	defer tearDownFn()
   181  
   182  	tests := []struct {
   183  		name           string
   184  		username       string
   185  		sar            *authorizationapi.SelfSubjectAccessReview
   186  		expectedError  string
   187  		expectedStatus authorizationapi.SubjectAccessReviewStatus
   188  	}{
   189  		{
   190  			name:     "simple allow",
   191  			username: "alice",
   192  			sar: &authorizationapi.SelfSubjectAccessReview{
   193  				Spec: authorizationapi.SelfSubjectAccessReviewSpec{
   194  					ResourceAttributes: &authorizationapi.ResourceAttributes{
   195  						Verb:     "list",
   196  						Group:    api.GroupName,
   197  						Version:  "v1",
   198  						Resource: "pods",
   199  					},
   200  				},
   201  			},
   202  			expectedStatus: authorizationapi.SubjectAccessReviewStatus{
   203  				Allowed: true,
   204  				Reason:  "you're not dave",
   205  			},
   206  		},
   207  		{
   208  			name:     "simple deny",
   209  			username: "dave",
   210  			sar: &authorizationapi.SelfSubjectAccessReview{
   211  				Spec: authorizationapi.SelfSubjectAccessReviewSpec{
   212  					ResourceAttributes: &authorizationapi.ResourceAttributes{
   213  						Verb:     "list",
   214  						Group:    api.GroupName,
   215  						Version:  "v1",
   216  						Resource: "pods",
   217  					},
   218  				},
   219  			},
   220  			expectedStatus: authorizationapi.SubjectAccessReviewStatus{
   221  				Allowed:         false,
   222  				Reason:          "no",
   223  				EvaluationError: "I'm sorry, Dave",
   224  			},
   225  		},
   226  	}
   227  
   228  	for _, test := range tests {
   229  		mutex.Lock()
   230  		username = test.username
   231  		mutex.Unlock()
   232  
   233  		response, err := clientset.AuthorizationV1().SelfSubjectAccessReviews().Create(tCtx, test.sar, metav1.CreateOptions{})
   234  		switch {
   235  		case err == nil && len(test.expectedError) == 0:
   236  
   237  		case err != nil && strings.Contains(err.Error(), test.expectedError):
   238  			continue
   239  
   240  		case err != nil && len(test.expectedError) != 0:
   241  			t.Errorf("%s: unexpected error: %v", test.name, err)
   242  			continue
   243  		default:
   244  			t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err)
   245  			continue
   246  		}
   247  		if response.Status != test.expectedStatus {
   248  			t.Errorf("%s: expected %v, got %v", test.name, test.expectedStatus, response.Status)
   249  			continue
   250  		}
   251  	}
   252  }
   253  
   254  func TestLocalSubjectAccessReview(t *testing.T) {
   255  	tCtx := ktesting.Init(t)
   256  	clientset, _, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
   257  		ModifyServerConfig: func(config *controlplane.Config) {
   258  			// Unset BearerToken to disable BearerToken authenticator.
   259  			config.GenericConfig.LoopbackClientConfig.BearerToken = ""
   260  			config.GenericConfig.Authentication.Authenticator = authenticator.RequestFunc(alwaysAlice)
   261  			config.GenericConfig.Authorization.Authorizer = sarAuthorizer{}
   262  		},
   263  	})
   264  	defer tearDownFn()
   265  
   266  	tests := []struct {
   267  		name           string
   268  		namespace      string
   269  		sar            *authorizationapi.LocalSubjectAccessReview
   270  		expectedError  string
   271  		expectedStatus authorizationapi.SubjectAccessReviewStatus
   272  	}{
   273  		{
   274  			name:      "simple allow",
   275  			namespace: "foo",
   276  			sar: &authorizationapi.LocalSubjectAccessReview{
   277  				ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
   278  				Spec: authorizationapi.SubjectAccessReviewSpec{
   279  					ResourceAttributes: &authorizationapi.ResourceAttributes{
   280  						Verb:      "list",
   281  						Group:     api.GroupName,
   282  						Version:   "v1",
   283  						Resource:  "pods",
   284  						Namespace: "foo",
   285  					},
   286  					User: "alice",
   287  				},
   288  			},
   289  			expectedStatus: authorizationapi.SubjectAccessReviewStatus{
   290  				Allowed: true,
   291  				Reason:  "you're not dave",
   292  			},
   293  		},
   294  		{
   295  			name:      "simple deny",
   296  			namespace: "foo",
   297  			sar: &authorizationapi.LocalSubjectAccessReview{
   298  				ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
   299  				Spec: authorizationapi.SubjectAccessReviewSpec{
   300  					ResourceAttributes: &authorizationapi.ResourceAttributes{
   301  						Verb:      "list",
   302  						Group:     api.GroupName,
   303  						Version:   "v1",
   304  						Resource:  "pods",
   305  						Namespace: "foo",
   306  					},
   307  					User: "dave",
   308  				},
   309  			},
   310  			expectedStatus: authorizationapi.SubjectAccessReviewStatus{
   311  				Allowed:         false,
   312  				Reason:          "no",
   313  				EvaluationError: "I'm sorry, Dave",
   314  			},
   315  		},
   316  		{
   317  			name:      "conflicting namespace",
   318  			namespace: "foo",
   319  			sar: &authorizationapi.LocalSubjectAccessReview{
   320  				ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
   321  				Spec: authorizationapi.SubjectAccessReviewSpec{
   322  					ResourceAttributes: &authorizationapi.ResourceAttributes{
   323  						Verb:      "list",
   324  						Group:     api.GroupName,
   325  						Version:   "v1",
   326  						Resource:  "pods",
   327  						Namespace: "bar",
   328  					},
   329  					User: "dave",
   330  				},
   331  			},
   332  			expectedError: "must match metadata.namespace",
   333  		},
   334  		{
   335  			name:      "missing namespace",
   336  			namespace: "foo",
   337  			sar: &authorizationapi.LocalSubjectAccessReview{
   338  				ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
   339  				Spec: authorizationapi.SubjectAccessReviewSpec{
   340  					ResourceAttributes: &authorizationapi.ResourceAttributes{
   341  						Verb:     "list",
   342  						Group:    api.GroupName,
   343  						Version:  "v1",
   344  						Resource: "pods",
   345  					},
   346  					User: "dave",
   347  				},
   348  			},
   349  			expectedError: "must match metadata.namespace",
   350  		},
   351  	}
   352  
   353  	for _, test := range tests {
   354  		response, err := clientset.AuthorizationV1().LocalSubjectAccessReviews(test.namespace).Create(tCtx, test.sar, metav1.CreateOptions{})
   355  		switch {
   356  		case err == nil && len(test.expectedError) == 0:
   357  
   358  		case err != nil && strings.Contains(err.Error(), test.expectedError):
   359  			continue
   360  
   361  		case err != nil && len(test.expectedError) != 0:
   362  			t.Errorf("%s: unexpected error: %v", test.name, err)
   363  			continue
   364  		default:
   365  			t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err)
   366  			continue
   367  		}
   368  		if response.Status != test.expectedStatus {
   369  			t.Errorf("%s: expected %#v, got %#v", test.name, test.expectedStatus, response.Status)
   370  			continue
   371  		}
   372  	}
   373  }
   374  

View as plain text