rbac_test.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767
  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. "fmt"
  17. "io"
  18. "io/ioutil"
  19. "net/http"
  20. "net/http/httputil"
  21. "reflect"
  22. "strings"
  23. "testing"
  24. "time"
  25. api "k8s.io/api/core/v1"
  26. rbacapi "k8s.io/api/rbac/v1"
  27. apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
  28. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  29. "k8s.io/apimachinery/pkg/runtime/schema"
  30. "k8s.io/apimachinery/pkg/types"
  31. "k8s.io/apimachinery/pkg/watch"
  32. "k8s.io/apiserver/pkg/authentication/request/bearertoken"
  33. "k8s.io/apiserver/pkg/authentication/token/tokenfile"
  34. "k8s.io/apiserver/pkg/authentication/user"
  35. "k8s.io/apiserver/pkg/authorization/authorizer"
  36. genericfeatures "k8s.io/apiserver/pkg/features"
  37. "k8s.io/apiserver/pkg/registry/generic"
  38. utilfeature "k8s.io/apiserver/pkg/util/feature"
  39. clientset "k8s.io/client-go/kubernetes"
  40. restclient "k8s.io/client-go/rest"
  41. watchtools "k8s.io/client-go/tools/watch"
  42. "k8s.io/client-go/transport"
  43. featuregatetesting "k8s.io/component-base/featuregate/testing"
  44. "k8s.io/klog"
  45. "k8s.io/kubernetes/pkg/api/legacyscheme"
  46. "k8s.io/kubernetes/pkg/api/testapi"
  47. rbachelper "k8s.io/kubernetes/pkg/apis/rbac/v1"
  48. "k8s.io/kubernetes/pkg/master"
  49. "k8s.io/kubernetes/pkg/registry/rbac/clusterrole"
  50. clusterrolestore "k8s.io/kubernetes/pkg/registry/rbac/clusterrole/storage"
  51. "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding"
  52. clusterrolebindingstore "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding/storage"
  53. "k8s.io/kubernetes/pkg/registry/rbac/role"
  54. rolestore "k8s.io/kubernetes/pkg/registry/rbac/role/storage"
  55. "k8s.io/kubernetes/pkg/registry/rbac/rolebinding"
  56. rolebindingstore "k8s.io/kubernetes/pkg/registry/rbac/rolebinding/storage"
  57. "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
  58. "k8s.io/kubernetes/test/integration/framework"
  59. )
  60. func clientForToken(user string) *http.Client {
  61. return &http.Client{
  62. Transport: transport.NewBearerAuthRoundTripper(
  63. user,
  64. transport.DebugWrappers(http.DefaultTransport),
  65. ),
  66. }
  67. }
  68. func clientsetForToken(user string, config *restclient.Config) (clientset.Interface, clientset.Interface) {
  69. configCopy := *config
  70. configCopy.BearerToken = user
  71. return clientset.NewForConfigOrDie(&configCopy), clientset.NewForConfigOrDie(&configCopy)
  72. }
  73. func crdClientsetForToken(user string, config *restclient.Config) apiextensionsclient.Interface {
  74. configCopy := *config
  75. configCopy.BearerToken = user
  76. return apiextensionsclient.NewForConfigOrDie(&configCopy)
  77. }
  78. type testRESTOptionsGetter struct {
  79. config *master.Config
  80. }
  81. func (getter *testRESTOptionsGetter) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
  82. storageConfig, err := getter.config.ExtraConfig.StorageFactory.NewConfig(resource)
  83. if err != nil {
  84. return generic.RESTOptions{}, fmt.Errorf("failed to get storage: %v", err)
  85. }
  86. return generic.RESTOptions{StorageConfig: storageConfig, Decorator: generic.UndecoratedStorage, ResourcePrefix: resource.Resource}, nil
  87. }
  88. func newRBACAuthorizer(config *master.Config) authorizer.Authorizer {
  89. optsGetter := &testRESTOptionsGetter{config}
  90. roleRegistry := role.AuthorizerAdapter{Registry: role.NewRegistry(rolestore.NewREST(optsGetter))}
  91. roleBindingRegistry := rolebinding.AuthorizerAdapter{Registry: rolebinding.NewRegistry(rolebindingstore.NewREST(optsGetter))}
  92. clusterRoleRegistry := clusterrole.AuthorizerAdapter{Registry: clusterrole.NewRegistry(clusterrolestore.NewREST(optsGetter))}
  93. clusterRoleBindingRegistry := clusterrolebinding.AuthorizerAdapter{Registry: clusterrolebinding.NewRegistry(clusterrolebindingstore.NewREST(optsGetter))}
  94. return rbac.New(roleRegistry, roleBindingRegistry, clusterRoleRegistry, clusterRoleBindingRegistry)
  95. }
  96. // bootstrapRoles are a set of RBAC roles which will be populated before the test.
  97. type bootstrapRoles struct {
  98. roles []rbacapi.Role
  99. roleBindings []rbacapi.RoleBinding
  100. clusterRoles []rbacapi.ClusterRole
  101. clusterRoleBindings []rbacapi.ClusterRoleBinding
  102. }
  103. // bootstrap uses the provided client to create the bootstrap roles and role bindings.
  104. //
  105. // client should be authenticated as the RBAC super user.
  106. func (b bootstrapRoles) bootstrap(client clientset.Interface) error {
  107. for _, r := range b.clusterRoles {
  108. _, err := client.RbacV1().ClusterRoles().Create(&r)
  109. if err != nil {
  110. return fmt.Errorf("failed to make request: %v", err)
  111. }
  112. }
  113. for _, r := range b.roles {
  114. _, err := client.RbacV1().Roles(r.Namespace).Create(&r)
  115. if err != nil {
  116. return fmt.Errorf("failed to make request: %v", err)
  117. }
  118. }
  119. for _, r := range b.clusterRoleBindings {
  120. _, err := client.RbacV1().ClusterRoleBindings().Create(&r)
  121. if err != nil {
  122. return fmt.Errorf("failed to make request: %v", err)
  123. }
  124. }
  125. for _, r := range b.roleBindings {
  126. _, err := client.RbacV1().RoleBindings(r.Namespace).Create(&r)
  127. if err != nil {
  128. return fmt.Errorf("failed to make request: %v", err)
  129. }
  130. }
  131. return nil
  132. }
  133. // request is a test case which can.
  134. type request struct {
  135. // The bearer token sent as part of the request
  136. token string
  137. // Resource metadata
  138. verb string
  139. apiGroup string
  140. resource string
  141. namespace string
  142. name string
  143. // The actual resource.
  144. body string
  145. // The expected return status of this request.
  146. expectedStatus int
  147. }
  148. func (r request) String() string {
  149. return fmt.Sprintf("%s %s %s", r.token, r.verb, r.resource)
  150. }
  151. type statusCode int
  152. func (s statusCode) String() string {
  153. return fmt.Sprintf("%d %s", int(s), http.StatusText(int(s)))
  154. }
  155. // Declare a set of raw objects to use.
  156. var (
  157. // Make a role binding with the version enabled in testapi.Rbac
  158. // This assumes testapi is using rbac.authorization.k8s.io/v1beta1 or rbac.authorization.k8s.io/v1, which are identical in structure.
  159. // TODO: rework or remove testapi usage to allow writing integration tests that don't depend on envvars
  160. writeJobsRoleBinding = `
  161. {
  162. "apiVersion": "` + testapi.Rbac.GroupVersion().String() + `",
  163. "kind": "RoleBinding",
  164. "metadata": {
  165. "name": "pi"%s
  166. },
  167. "roleRef": {
  168. "apiGroup": "rbac.authorization.k8s.io",
  169. "kind": "ClusterRole",
  170. "name": "write-jobs"
  171. },
  172. "subjects": [{
  173. "apiGroup": "rbac.authorization.k8s.io",
  174. "kind": "User",
  175. "name": "admin"
  176. }]
  177. }`
  178. aJob = `
  179. {
  180. "apiVersion": "batch/v1",
  181. "kind": "Job",
  182. "metadata": {
  183. "name": "pi"%s
  184. },
  185. "spec": {
  186. "template": {
  187. "metadata": {
  188. "name": "a",
  189. "labels": {
  190. "name": "pijob"
  191. }
  192. },
  193. "spec": {
  194. "containers": [
  195. {
  196. "name": "pi",
  197. "image": "perl",
  198. "command": [
  199. "perl",
  200. "-Mbignum=bpi",
  201. "-wle",
  202. "print bpi(2000)"
  203. ]
  204. }
  205. ],
  206. "restartPolicy": "Never"
  207. }
  208. }
  209. }
  210. }
  211. `
  212. aLimitRange = `
  213. {
  214. "apiVersion": "v1",
  215. "kind": "LimitRange",
  216. "metadata": {
  217. "name": "a"%s
  218. }
  219. }
  220. `
  221. podNamespace = `
  222. {
  223. "apiVersion": "` + testapi.Groups[api.GroupName].GroupVersion().String() + `",
  224. "kind": "Namespace",
  225. "metadata": {
  226. "name": "pod-namespace"%s
  227. }
  228. }
  229. `
  230. jobNamespace = `
  231. {
  232. "apiVersion": "` + testapi.Groups[api.GroupName].GroupVersion().String() + `",
  233. "kind": "Namespace",
  234. "metadata": {
  235. "name": "job-namespace"%s
  236. }
  237. }
  238. `
  239. forbiddenNamespace = `
  240. {
  241. "apiVersion": "` + testapi.Groups[api.GroupName].GroupVersion().String() + `",
  242. "kind": "Namespace",
  243. "metadata": {
  244. "name": "forbidden-namespace"%s
  245. }
  246. }
  247. `
  248. limitRangeNamespace = `
  249. {
  250. "apiVersion": "` + testapi.Groups[api.GroupName].GroupVersion().String() + `",
  251. "kind": "Namespace",
  252. "metadata": {
  253. "name": "limitrange-namespace"%s
  254. }
  255. }
  256. `
  257. )
  258. // Declare some PolicyRules beforehand.
  259. var (
  260. ruleAllowAll = rbachelper.NewRule("*").Groups("*").Resources("*").RuleOrDie()
  261. ruleReadPods = rbachelper.NewRule("list", "get", "watch").Groups("").Resources("pods").RuleOrDie()
  262. ruleWriteJobs = rbachelper.NewRule("*").Groups("batch").Resources("*").RuleOrDie()
  263. )
  264. func TestRBAC(t *testing.T) {
  265. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
  266. superUser := "admin/system:masters"
  267. tests := []struct {
  268. bootstrapRoles bootstrapRoles
  269. requests []request
  270. }{
  271. {
  272. bootstrapRoles: bootstrapRoles{
  273. clusterRoles: []rbacapi.ClusterRole{
  274. {
  275. ObjectMeta: metav1.ObjectMeta{Name: "allow-all"},
  276. Rules: []rbacapi.PolicyRule{ruleAllowAll},
  277. },
  278. {
  279. ObjectMeta: metav1.ObjectMeta{Name: "read-pods"},
  280. Rules: []rbacapi.PolicyRule{ruleReadPods},
  281. },
  282. },
  283. clusterRoleBindings: []rbacapi.ClusterRoleBinding{
  284. {
  285. ObjectMeta: metav1.ObjectMeta{Name: "read-pods"},
  286. Subjects: []rbacapi.Subject{
  287. {Kind: "User", Name: "pod-reader"},
  288. },
  289. RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "read-pods"},
  290. },
  291. },
  292. },
  293. requests: []request{
  294. // Create the namespace used later in the test
  295. {superUser, "POST", "", "namespaces", "", "", podNamespace, http.StatusCreated},
  296. {superUser, "GET", "", "pods", "", "", "", http.StatusOK},
  297. {superUser, "GET", "", "pods", "pod-namespace", "a", "", http.StatusNotFound},
  298. {superUser, "POST", "", "pods", "pod-namespace", "", aPod, http.StatusCreated},
  299. {superUser, "GET", "", "pods", "pod-namespace", "a", "", http.StatusOK},
  300. {"bob", "GET", "", "pods", "", "", "", http.StatusForbidden},
  301. {"bob", "GET", "", "pods", "pod-namespace", "a", "", http.StatusForbidden},
  302. {"pod-reader", "GET", "", "pods", "", "", "", http.StatusOK},
  303. {"pod-reader", "POST", "", "pods", "pod-namespace", "", aPod, http.StatusForbidden},
  304. },
  305. },
  306. {
  307. bootstrapRoles: bootstrapRoles{
  308. clusterRoles: []rbacapi.ClusterRole{
  309. {
  310. ObjectMeta: metav1.ObjectMeta{Name: "write-jobs"},
  311. Rules: []rbacapi.PolicyRule{ruleWriteJobs},
  312. },
  313. {
  314. ObjectMeta: metav1.ObjectMeta{Name: "create-rolebindings"},
  315. Rules: []rbacapi.PolicyRule{
  316. rbachelper.NewRule("create").Groups("rbac.authorization.k8s.io").Resources("rolebindings").RuleOrDie(),
  317. },
  318. },
  319. {
  320. ObjectMeta: metav1.ObjectMeta{Name: "bind-any-clusterrole"},
  321. Rules: []rbacapi.PolicyRule{
  322. rbachelper.NewRule("bind").Groups("rbac.authorization.k8s.io").Resources("clusterroles").RuleOrDie(),
  323. },
  324. },
  325. },
  326. clusterRoleBindings: []rbacapi.ClusterRoleBinding{
  327. {
  328. ObjectMeta: metav1.ObjectMeta{Name: "write-jobs"},
  329. Subjects: []rbacapi.Subject{{Kind: "User", Name: "job-writer"}},
  330. RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "write-jobs"},
  331. },
  332. {
  333. ObjectMeta: metav1.ObjectMeta{Name: "create-rolebindings"},
  334. Subjects: []rbacapi.Subject{
  335. {Kind: "User", Name: "job-writer"},
  336. {Kind: "User", Name: "nonescalating-rolebinding-writer"},
  337. {Kind: "User", Name: "any-rolebinding-writer"},
  338. },
  339. RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "create-rolebindings"},
  340. },
  341. {
  342. ObjectMeta: metav1.ObjectMeta{Name: "bind-any-clusterrole"},
  343. Subjects: []rbacapi.Subject{{Kind: "User", Name: "any-rolebinding-writer"}},
  344. RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "bind-any-clusterrole"},
  345. },
  346. },
  347. roleBindings: []rbacapi.RoleBinding{
  348. {
  349. ObjectMeta: metav1.ObjectMeta{Name: "write-jobs", Namespace: "job-namespace"},
  350. Subjects: []rbacapi.Subject{{Kind: "User", Name: "job-writer-namespace"}},
  351. RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "write-jobs"},
  352. },
  353. {
  354. ObjectMeta: metav1.ObjectMeta{Name: "create-rolebindings", Namespace: "job-namespace"},
  355. Subjects: []rbacapi.Subject{
  356. {Kind: "User", Name: "job-writer-namespace"},
  357. {Kind: "User", Name: "any-rolebinding-writer-namespace"},
  358. },
  359. RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "create-rolebindings"},
  360. },
  361. {
  362. ObjectMeta: metav1.ObjectMeta{Name: "bind-any-clusterrole", Namespace: "job-namespace"},
  363. Subjects: []rbacapi.Subject{{Kind: "User", Name: "any-rolebinding-writer-namespace"}},
  364. RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "bind-any-clusterrole"},
  365. },
  366. },
  367. },
  368. requests: []request{
  369. // Create the namespace used later in the test
  370. {superUser, "POST", "", "namespaces", "", "", jobNamespace, http.StatusCreated},
  371. {superUser, "POST", "", "namespaces", "", "", forbiddenNamespace, http.StatusCreated},
  372. {"user-with-no-permissions", "POST", "batch", "jobs", "job-namespace", "", aJob, http.StatusForbidden},
  373. {"user-with-no-permissions", "GET", "batch", "jobs", "job-namespace", "pi", "", http.StatusForbidden},
  374. // job-writer-namespace cannot write to the "forbidden-namespace"
  375. {"job-writer-namespace", "GET", "batch", "jobs", "forbidden-namespace", "", "", http.StatusForbidden},
  376. {"job-writer-namespace", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusForbidden},
  377. {"job-writer-namespace", "POST", "batch", "jobs", "forbidden-namespace", "", aJob, http.StatusForbidden},
  378. {"job-writer-namespace", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusForbidden},
  379. // job-writer can write to any namespace
  380. {"job-writer", "GET", "batch", "jobs", "forbidden-namespace", "", "", http.StatusOK},
  381. {"job-writer", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusNotFound},
  382. {"job-writer", "POST", "batch", "jobs", "forbidden-namespace", "", aJob, http.StatusCreated},
  383. {"job-writer", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusOK},
  384. {"job-writer-namespace", "GET", "batch", "jobs", "job-namespace", "", "", http.StatusOK},
  385. {"job-writer-namespace", "GET", "batch", "jobs", "job-namespace", "pi", "", http.StatusNotFound},
  386. {"job-writer-namespace", "POST", "batch", "jobs", "job-namespace", "", aJob, http.StatusCreated},
  387. {"job-writer-namespace", "GET", "batch", "jobs", "job-namespace", "pi", "", http.StatusOK},
  388. // cannot bind role anywhere
  389. {"user-with-no-permissions", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
  390. // can only bind role in namespace where they have explicit bind permission
  391. {"any-rolebinding-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
  392. // can only bind role in namespace where they have covering permissions
  393. {"job-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
  394. {"job-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusCreated},
  395. {superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "pi", "", http.StatusOK},
  396. // can bind role in any namespace where they have covering permissions
  397. {"job-writer", "POST", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "", writeJobsRoleBinding, http.StatusCreated},
  398. {superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "pi", "", http.StatusOK},
  399. // cannot bind role because they don't have covering permissions
  400. {"nonescalating-rolebinding-writer", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
  401. // can bind role because they have explicit bind permission
  402. {"any-rolebinding-writer", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusCreated},
  403. {superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "pi", "", http.StatusOK},
  404. {"any-rolebinding-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusCreated},
  405. {superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "pi", "", http.StatusOK},
  406. },
  407. },
  408. {
  409. bootstrapRoles: bootstrapRoles{
  410. clusterRoles: []rbacapi.ClusterRole{
  411. {
  412. ObjectMeta: metav1.ObjectMeta{Name: "allow-all"},
  413. Rules: []rbacapi.PolicyRule{ruleAllowAll},
  414. },
  415. {
  416. ObjectMeta: metav1.ObjectMeta{Name: "update-limitranges"},
  417. Rules: []rbacapi.PolicyRule{
  418. rbachelper.NewRule("update").Groups("").Resources("limitranges").RuleOrDie(),
  419. },
  420. },
  421. },
  422. clusterRoleBindings: []rbacapi.ClusterRoleBinding{
  423. {
  424. ObjectMeta: metav1.ObjectMeta{Name: "update-limitranges"},
  425. Subjects: []rbacapi.Subject{
  426. {Kind: "User", Name: "limitrange-updater"},
  427. },
  428. RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "update-limitranges"},
  429. },
  430. },
  431. },
  432. requests: []request{
  433. // Create the namespace used later in the test
  434. {superUser, "POST", "", "namespaces", "", "", limitRangeNamespace, http.StatusCreated},
  435. {"limitrange-updater", "PUT", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusForbidden},
  436. {superUser, "PUT", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusCreated},
  437. {superUser, "PUT", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
  438. {"limitrange-updater", "PUT", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
  439. },
  440. },
  441. {
  442. bootstrapRoles: bootstrapRoles{
  443. clusterRoles: []rbacapi.ClusterRole{
  444. {
  445. ObjectMeta: metav1.ObjectMeta{Name: "allow-all"},
  446. Rules: []rbacapi.PolicyRule{ruleAllowAll},
  447. },
  448. {
  449. ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"},
  450. Rules: []rbacapi.PolicyRule{
  451. rbachelper.NewRule("patch").Groups("").Resources("limitranges").RuleOrDie(),
  452. },
  453. },
  454. },
  455. clusterRoleBindings: []rbacapi.ClusterRoleBinding{
  456. {
  457. ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"},
  458. Subjects: []rbacapi.Subject{
  459. {Kind: "User", Name: "limitrange-patcher"},
  460. },
  461. RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "patch-limitranges"},
  462. },
  463. },
  464. },
  465. requests: []request{
  466. // Create the namespace used later in the test
  467. {superUser, "POST", "", "namespaces", "", "", limitRangeNamespace, http.StatusCreated},
  468. {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusForbidden},
  469. {superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusCreated},
  470. {superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
  471. {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
  472. },
  473. },
  474. }
  475. for i, tc := range tests {
  476. // Create an API Server.
  477. masterConfig := framework.NewIntegrationTestMasterConfig()
  478. masterConfig.GenericConfig.Authorization.Authorizer = newRBACAuthorizer(masterConfig)
  479. masterConfig.GenericConfig.Authentication.Authenticator = bearertoken.New(tokenfile.New(map[string]*user.DefaultInfo{
  480. superUser: {Name: "admin", Groups: []string{"system:masters"}},
  481. "any-rolebinding-writer": {Name: "any-rolebinding-writer"},
  482. "any-rolebinding-writer-namespace": {Name: "any-rolebinding-writer-namespace"},
  483. "bob": {Name: "bob"},
  484. "job-writer": {Name: "job-writer"},
  485. "job-writer-namespace": {Name: "job-writer-namespace"},
  486. "nonescalating-rolebinding-writer": {Name: "nonescalating-rolebinding-writer"},
  487. "pod-reader": {Name: "pod-reader"},
  488. "limitrange-updater": {Name: "limitrange-updater"},
  489. "limitrange-patcher": {Name: "limitrange-patcher"},
  490. "user-with-no-permissions": {Name: "user-with-no-permissions"},
  491. }))
  492. masterConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig()
  493. _, s, closeFn := framework.RunAMaster(masterConfig)
  494. defer closeFn()
  495. clientConfig := &restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs}}
  496. // Bootstrap the API Server with the test case's initial roles.
  497. superuserClient, _ := clientsetForToken(superUser, clientConfig)
  498. if err := tc.bootstrapRoles.bootstrap(superuserClient); err != nil {
  499. t.Errorf("case %d: failed to apply initial roles: %v", i, err)
  500. continue
  501. }
  502. previousResourceVersion := make(map[string]float64)
  503. for j, r := range tc.requests {
  504. testGroup, ok := testapi.Groups[r.apiGroup]
  505. if !ok {
  506. t.Errorf("case %d %d: unknown api group %q, %s", i, j, r.apiGroup, r)
  507. continue
  508. }
  509. path := testGroup.ResourcePath(r.resource, r.namespace, r.name)
  510. var body io.Reader
  511. if r.body != "" {
  512. sub := ""
  513. if r.verb == "PUT" {
  514. // For update operations, insert previous resource version
  515. if resVersion := previousResourceVersion[getPreviousResourceVersionKey(path, "")]; resVersion != 0 {
  516. sub += fmt.Sprintf(",\"resourceVersion\": \"%v\"", resVersion)
  517. }
  518. }
  519. body = strings.NewReader(fmt.Sprintf(r.body, sub))
  520. }
  521. req, err := http.NewRequest(r.verb, s.URL+path, body)
  522. if r.verb == "PATCH" {
  523. // For patch operations, use the apply content type
  524. req.Header.Add("Content-Type", string(types.ApplyPatchType))
  525. q := req.URL.Query()
  526. q.Add("fieldManager", "rbac_test")
  527. req.URL.RawQuery = q.Encode()
  528. }
  529. if err != nil {
  530. t.Fatalf("failed to create request: %v", err)
  531. }
  532. func() {
  533. reqDump, err := httputil.DumpRequest(req, true)
  534. if err != nil {
  535. t.Fatalf("failed to dump request: %v", err)
  536. return
  537. }
  538. resp, err := clientForToken(r.token).Do(req)
  539. if err != nil {
  540. t.Errorf("case %d, req %d: failed to make request: %v", i, j, err)
  541. return
  542. }
  543. defer resp.Body.Close()
  544. respDump, err := httputil.DumpResponse(resp, true)
  545. if err != nil {
  546. t.Fatalf("failed to dump response: %v", err)
  547. return
  548. }
  549. if resp.StatusCode != r.expectedStatus {
  550. // When debugging is on, dump the entire request and response. Very helpful for
  551. // debugging malformed test cases.
  552. //
  553. // To turn on debugging, use the '-args' flag.
  554. //
  555. // go test -v -tags integration -run RBAC -args -v 10
  556. //
  557. klog.V(8).Infof("case %d, req %d: %s\n%s\n", i, j, reqDump, respDump)
  558. t.Errorf("case %d, req %d: %s expected %q got %q", i, j, r, statusCode(r.expectedStatus), statusCode(resp.StatusCode))
  559. }
  560. b, _ := ioutil.ReadAll(resp.Body)
  561. if r.verb == "POST" && (resp.StatusCode/100) == 2 {
  562. // For successful create operations, extract resourceVersion
  563. id, currentResourceVersion, err := parseResourceVersion(b)
  564. if err == nil {
  565. key := getPreviousResourceVersionKey(path, id)
  566. previousResourceVersion[key] = currentResourceVersion
  567. } else {
  568. t.Logf("error in trying to extract resource version: %s", err)
  569. }
  570. }
  571. }()
  572. }
  573. }
  574. }
  575. func TestBootstrapping(t *testing.T) {
  576. superUser := "admin/system:masters"
  577. masterConfig := framework.NewIntegrationTestMasterConfig()
  578. masterConfig.GenericConfig.Authorization.Authorizer = newRBACAuthorizer(masterConfig)
  579. masterConfig.GenericConfig.Authentication.Authenticator = bearertoken.New(tokenfile.New(map[string]*user.DefaultInfo{
  580. superUser: {Name: "admin", Groups: []string{"system:masters"}},
  581. }))
  582. _, s, closeFn := framework.RunAMaster(masterConfig)
  583. defer closeFn()
  584. clientset := clientset.NewForConfigOrDie(&restclient.Config{BearerToken: superUser, Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Groups[api.GroupName].GroupVersion()}})
  585. watcher, err := clientset.RbacV1().ClusterRoles().Watch(metav1.ListOptions{ResourceVersion: "0"})
  586. if err != nil {
  587. t.Fatalf("unexpected error: %v", err)
  588. }
  589. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  590. defer cancel()
  591. _, err = watchtools.UntilWithoutRetry(ctx, watcher, func(event watch.Event) (bool, error) {
  592. if event.Type != watch.Added {
  593. return false, nil
  594. }
  595. return true, nil
  596. })
  597. if err != nil {
  598. t.Fatalf("unexpected error: %v", err)
  599. }
  600. clusterRoles, err := clientset.RbacV1().ClusterRoles().List(metav1.ListOptions{})
  601. if err != nil {
  602. t.Fatalf("unexpected error: %v", err)
  603. }
  604. if len(clusterRoles.Items) == 0 {
  605. t.Fatalf("missing cluster roles")
  606. }
  607. for _, clusterRole := range clusterRoles.Items {
  608. if clusterRole.Name == "cluster-admin" {
  609. return
  610. }
  611. }
  612. t.Errorf("missing cluster-admin: %v", clusterRoles)
  613. healthBytes, err := clientset.Discovery().RESTClient().Get().AbsPath("/healthz/poststarthook/rbac/bootstrap-roles").DoRaw()
  614. if err != nil {
  615. t.Error(err)
  616. }
  617. t.Errorf("error bootstrapping roles: %s", string(healthBytes))
  618. }
  619. // TestDiscoveryUpgradeBootstrapping is primarily meant to test the behavior of
  620. // primePublicInfoClusterRoleBinding in storage_rbac.go during cluster upgrades.
  621. func TestDiscoveryUpgradeBootstrapping(t *testing.T) {
  622. var tearDownFn func()
  623. defer func() {
  624. if tearDownFn != nil {
  625. tearDownFn()
  626. }
  627. }()
  628. superUser := "admin/system:masters"
  629. masterConfig := framework.NewIntegrationTestMasterConfig()
  630. masterConfig.GenericConfig.Authorization.Authorizer = newRBACAuthorizer(masterConfig)
  631. masterConfig.GenericConfig.Authentication.Authenticator = bearertoken.New(tokenfile.New(map[string]*user.DefaultInfo{
  632. superUser: {Name: "admin", Groups: []string{"system:masters"}},
  633. }))
  634. _, s, tearDownFn := framework.RunAMaster(masterConfig)
  635. client := clientset.NewForConfigOrDie(&restclient.Config{BearerToken: superUser, Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Groups[api.GroupName].GroupVersion()}})
  636. // Modify the default RBAC discovery ClusterRoleBidnings to look more like the defaults that
  637. // existed prior to v1.14, but with user modifications.
  638. t.Logf("Modifying default `system:discovery` ClusterRoleBinding")
  639. discRoleBinding, err := client.RbacV1().ClusterRoleBindings().Get("system:discovery", metav1.GetOptions{})
  640. discRoleBinding.Annotations["rbac.authorization.kubernetes.io/autoupdate"] = "false"
  641. discRoleBinding.Annotations["rbac-discovery-upgrade-test"] = "pass"
  642. discRoleBinding.Subjects = []rbacapi.Subject{
  643. {
  644. Name: "system:authenticated",
  645. Kind: "Group",
  646. APIGroup: "rbac.authorization.k8s.io",
  647. },
  648. }
  649. if discRoleBinding, err = client.RbacV1().ClusterRoleBindings().Update(discRoleBinding); err != nil {
  650. t.Fatalf("Failed to update `system:discovery` ClusterRoleBinding: %v", err)
  651. }
  652. t.Logf("Modifying default `system:basic-user` ClusterRoleBinding")
  653. basicUserRoleBinding, err := client.RbacV1().ClusterRoleBindings().Get("system:basic-user", metav1.GetOptions{})
  654. basicUserRoleBinding.Annotations["rbac.authorization.kubernetes.io/autoupdate"] = "false"
  655. basicUserRoleBinding.Annotations["rbac-discovery-upgrade-test"] = "pass"
  656. if basicUserRoleBinding, err = client.RbacV1().ClusterRoleBindings().Update(basicUserRoleBinding); err != nil {
  657. t.Fatalf("Failed to update `system:basic-user` ClusterRoleBinding: %v", err)
  658. }
  659. t.Logf("Deleting default `system:public-info-viewer` ClusterRoleBinding")
  660. if err = client.RbacV1().ClusterRoleBindings().Delete("system:public-info-viewer", &metav1.DeleteOptions{}); err != nil {
  661. t.Fatalf("Failed to delete `system:public-info-viewer` ClusterRoleBinding: %v", err)
  662. }
  663. // Stop the first API server.
  664. tearDownFn()
  665. tearDownFn = nil
  666. // Check that upgraded API servers inherit `system:public-info-viewer` settings from
  667. // `system:discovery`, and respect auto-reconciliation annotations.
  668. _, s, tearDownFn = framework.RunAMaster(masterConfig)
  669. client = clientset.NewForConfigOrDie(&restclient.Config{BearerToken: superUser, Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Groups[api.GroupName].GroupVersion()}})
  670. newDiscRoleBinding, err := client.RbacV1().ClusterRoleBindings().Get("system:discovery", metav1.GetOptions{})
  671. if err != nil {
  672. t.Fatalf("Failed to get `system:discovery` ClusterRoleBinding: %v", err)
  673. }
  674. if !reflect.DeepEqual(newDiscRoleBinding, discRoleBinding) {
  675. t.Errorf("`system:discovery` should have been unmodified. Wanted: %v, got %v", discRoleBinding, newDiscRoleBinding)
  676. }
  677. newBasicUserRoleBinding, err := client.RbacV1().ClusterRoleBindings().Get("system:basic-user", metav1.GetOptions{})
  678. if err != nil {
  679. t.Fatalf("Failed to get `system:basic-user` ClusterRoleBinding: %v", err)
  680. }
  681. if !reflect.DeepEqual(newBasicUserRoleBinding, basicUserRoleBinding) {
  682. t.Errorf("`system:basic-user` should have been unmodified. Wanted: %v, got %v", basicUserRoleBinding, newBasicUserRoleBinding)
  683. }
  684. publicInfoViewerRoleBinding, err := client.RbacV1().ClusterRoleBindings().Get("system:public-info-viewer", metav1.GetOptions{})
  685. if err != nil {
  686. t.Fatalf("Failed to get `system:public-info-viewer` ClusterRoleBinding: %v", err)
  687. }
  688. if publicInfoViewerRoleBinding.Annotations["rbac.authorization.kubernetes.io/autoupdate"] != "false" {
  689. t.Errorf("publicInfoViewerRoleBinding.Annotations[\"rbac.authorization.kubernetes.io/autoupdate\"] should be %v, got %v", publicInfoViewerRoleBinding.Annotations["rbac.authorization.kubernetes.io/autoupdate"], "false")
  690. }
  691. if publicInfoViewerRoleBinding.Annotations["rbac-discovery-upgrade-test"] != "pass" {
  692. t.Errorf("publicInfoViewerRoleBinding.Annotations[\"rbac-discovery-upgrade-test\"] should be %v, got %v", publicInfoViewerRoleBinding.Annotations["rbac-discovery-upgrade-test"], "pass")
  693. }
  694. if !reflect.DeepEqual(publicInfoViewerRoleBinding.Subjects, newDiscRoleBinding.Subjects) {
  695. t.Errorf("`system:public-info-viewer` should have inherited Subjects from `system:discovery` Wanted: %v, got %v", newDiscRoleBinding.Subjects, publicInfoViewerRoleBinding.Subjects)
  696. }
  697. }