accessreview_test.go 10 KB

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