policy_test.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  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 bootstrappolicy_test
  14. import (
  15. "io/ioutil"
  16. "os"
  17. "path/filepath"
  18. "reflect"
  19. "testing"
  20. "sigs.k8s.io/yaml"
  21. "k8s.io/api/core/v1"
  22. rbacv1 "k8s.io/api/rbac/v1"
  23. "k8s.io/apimachinery/pkg/api/meta"
  24. "k8s.io/apimachinery/pkg/runtime"
  25. "k8s.io/apimachinery/pkg/util/diff"
  26. "k8s.io/apimachinery/pkg/util/sets"
  27. "k8s.io/kubernetes/pkg/api/legacyscheme"
  28. api "k8s.io/kubernetes/pkg/apis/core"
  29. _ "k8s.io/kubernetes/pkg/apis/core/install"
  30. _ "k8s.io/kubernetes/pkg/apis/rbac/install"
  31. rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
  32. rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
  33. "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy"
  34. )
  35. // semanticRoles is a few enumerated roles for which the relationships are well established
  36. // and we want to maintain symmetric roles
  37. type semanticRoles struct {
  38. admin *rbacv1.ClusterRole
  39. edit *rbacv1.ClusterRole
  40. view *rbacv1.ClusterRole
  41. }
  42. func getSemanticRoles(roles []rbacv1.ClusterRole) semanticRoles {
  43. ret := semanticRoles{}
  44. for i := range roles {
  45. role := roles[i]
  46. switch role.Name {
  47. case "system:aggregate-to-admin":
  48. ret.admin = &role
  49. case "system:aggregate-to-edit":
  50. ret.edit = &role
  51. case "system:aggregate-to-view":
  52. ret.view = &role
  53. }
  54. }
  55. return ret
  56. }
  57. // viewEscalatingNamespaceResources is the list of rules that would allow privilege escalation attacks based on
  58. // ability to view (GET) them
  59. var viewEscalatingNamespaceResources = []rbacv1.PolicyRule{
  60. rbacv1helpers.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/attach").RuleOrDie(),
  61. rbacv1helpers.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/proxy").RuleOrDie(),
  62. rbacv1helpers.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/exec").RuleOrDie(),
  63. rbacv1helpers.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/portforward").RuleOrDie(),
  64. rbacv1helpers.NewRule(bootstrappolicy.Read...).Groups("").Resources("secrets").RuleOrDie(),
  65. rbacv1helpers.NewRule(bootstrappolicy.Read...).Groups("").Resources("services/proxy").RuleOrDie(),
  66. }
  67. // ungettableResources is the list of rules that don't allow to view (GET) them
  68. // this is purposefully separate list to distinguish from escalating privs
  69. var ungettableResources = []rbacv1.PolicyRule{
  70. rbacv1helpers.NewRule(bootstrappolicy.Read...).Groups("apps", "extensions").Resources("deployments/rollback").RuleOrDie(),
  71. }
  72. func TestEditViewRelationship(t *testing.T) {
  73. readVerbs := sets.NewString(bootstrappolicy.Read...)
  74. semanticRoles := getSemanticRoles(bootstrappolicy.ClusterRoles())
  75. // modify the edit role rules to make then read-only for comparison against view role rules
  76. for i := range semanticRoles.edit.Rules {
  77. rule := semanticRoles.edit.Rules[i]
  78. remainingVerbs := []string{}
  79. for _, verb := range rule.Verbs {
  80. if readVerbs.Has(verb) {
  81. remainingVerbs = append(remainingVerbs, verb)
  82. }
  83. }
  84. rule.Verbs = remainingVerbs
  85. semanticRoles.edit.Rules[i] = rule
  86. }
  87. // confirm that the view role doesn't already have extra powers
  88. for _, rule := range viewEscalatingNamespaceResources {
  89. if covers, _ := rbacregistryvalidation.Covers(semanticRoles.view.Rules, []rbacv1.PolicyRule{rule}); covers {
  90. t.Errorf("view has extra powers: %#v", rule)
  91. }
  92. }
  93. semanticRoles.view.Rules = append(semanticRoles.view.Rules, viewEscalatingNamespaceResources...)
  94. // confirm that the view role doesn't have ungettable resources
  95. for _, rule := range ungettableResources {
  96. if covers, _ := rbacregistryvalidation.Covers(semanticRoles.view.Rules, []rbacv1.PolicyRule{rule}); covers {
  97. t.Errorf("view has ungettable resource: %#v", rule)
  98. }
  99. }
  100. semanticRoles.view.Rules = append(semanticRoles.view.Rules, ungettableResources...)
  101. }
  102. func TestBootstrapNamespaceRoles(t *testing.T) {
  103. list := &api.List{}
  104. names := sets.NewString()
  105. roles := map[string]runtime.Object{}
  106. namespaceRoles := bootstrappolicy.NamespaceRoles()
  107. for _, namespace := range sets.StringKeySet(namespaceRoles).List() {
  108. bootstrapRoles := namespaceRoles[namespace]
  109. for i := range bootstrapRoles {
  110. role := bootstrapRoles[i]
  111. names.Insert(role.Name)
  112. roles[role.Name] = &role
  113. }
  114. for _, name := range names.List() {
  115. list.Items = append(list.Items, roles[name])
  116. }
  117. }
  118. testObjects(t, list, "namespace-roles.yaml")
  119. }
  120. func TestBootstrapNamespaceRoleBindings(t *testing.T) {
  121. list := &api.List{}
  122. names := sets.NewString()
  123. roleBindings := map[string]runtime.Object{}
  124. namespaceRoleBindings := bootstrappolicy.NamespaceRoleBindings()
  125. for _, namespace := range sets.StringKeySet(namespaceRoleBindings).List() {
  126. bootstrapRoleBindings := namespaceRoleBindings[namespace]
  127. for i := range bootstrapRoleBindings {
  128. roleBinding := bootstrapRoleBindings[i]
  129. names.Insert(roleBinding.Name)
  130. roleBindings[roleBinding.Name] = &roleBinding
  131. }
  132. for _, name := range names.List() {
  133. list.Items = append(list.Items, roleBindings[name])
  134. }
  135. }
  136. testObjects(t, list, "namespace-role-bindings.yaml")
  137. }
  138. func TestBootstrapClusterRoles(t *testing.T) {
  139. list := &api.List{}
  140. names := sets.NewString()
  141. roles := map[string]runtime.Object{}
  142. bootstrapRoles := bootstrappolicy.ClusterRoles()
  143. for i := range bootstrapRoles {
  144. role := bootstrapRoles[i]
  145. names.Insert(role.Name)
  146. roles[role.Name] = &role
  147. }
  148. for _, name := range names.List() {
  149. list.Items = append(list.Items, roles[name])
  150. }
  151. testObjects(t, list, "cluster-roles.yaml")
  152. }
  153. func TestBootstrapClusterRoleBindings(t *testing.T) {
  154. list := &api.List{}
  155. names := sets.NewString()
  156. roleBindings := map[string]runtime.Object{}
  157. bootstrapRoleBindings := bootstrappolicy.ClusterRoleBindings()
  158. for i := range bootstrapRoleBindings {
  159. role := bootstrapRoleBindings[i]
  160. names.Insert(role.Name)
  161. roleBindings[role.Name] = &role
  162. }
  163. for _, name := range names.List() {
  164. list.Items = append(list.Items, roleBindings[name])
  165. }
  166. testObjects(t, list, "cluster-role-bindings.yaml")
  167. }
  168. func TestBootstrapControllerRoles(t *testing.T) {
  169. list := &api.List{}
  170. names := sets.NewString()
  171. roles := map[string]runtime.Object{}
  172. bootstrapRoles := bootstrappolicy.ControllerRoles()
  173. for i := range bootstrapRoles {
  174. role := bootstrapRoles[i]
  175. names.Insert(role.Name)
  176. roles[role.Name] = &role
  177. }
  178. for _, name := range names.List() {
  179. list.Items = append(list.Items, roles[name])
  180. }
  181. testObjects(t, list, "controller-roles.yaml")
  182. }
  183. func TestBootstrapControllerRoleBindings(t *testing.T) {
  184. list := &api.List{}
  185. names := sets.NewString()
  186. roleBindings := map[string]runtime.Object{}
  187. bootstrapRoleBindings := bootstrappolicy.ControllerRoleBindings()
  188. for i := range bootstrapRoleBindings {
  189. roleBinding := bootstrapRoleBindings[i]
  190. names.Insert(roleBinding.Name)
  191. roleBindings[roleBinding.Name] = &roleBinding
  192. }
  193. for _, name := range names.List() {
  194. list.Items = append(list.Items, roleBindings[name])
  195. }
  196. testObjects(t, list, "controller-role-bindings.yaml")
  197. }
  198. func testObjects(t *testing.T, list *api.List, fixtureFilename string) {
  199. filename := filepath.Join("testdata", fixtureFilename)
  200. expectedYAML, err := ioutil.ReadFile(filename)
  201. if err != nil {
  202. t.Fatal(err)
  203. }
  204. if err := runtime.EncodeList(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion, rbacv1.SchemeGroupVersion), list.Items); err != nil {
  205. t.Fatal(err)
  206. }
  207. jsonData, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion, rbacv1.SchemeGroupVersion), list)
  208. if err != nil {
  209. t.Fatal(err)
  210. }
  211. yamlData, err := yaml.JSONToYAML(jsonData)
  212. if err != nil {
  213. t.Fatal(err)
  214. }
  215. if string(yamlData) != string(expectedYAML) {
  216. t.Errorf("Bootstrap policy data does not match the test fixture in %s", filename)
  217. const updateEnvVar = "UPDATE_BOOTSTRAP_POLICY_FIXTURE_DATA"
  218. if os.Getenv(updateEnvVar) == "true" {
  219. if err := ioutil.WriteFile(filename, []byte(yamlData), os.FileMode(0755)); err == nil {
  220. t.Logf("Updated data in %s", filename)
  221. t.Logf("Verify the diff, commit changes, and rerun the tests")
  222. } else {
  223. t.Logf("Could not update data in %s: %v", filename, err)
  224. }
  225. } else {
  226. t.Logf("Diff between bootstrap data and fixture data in %s:\n-------------\n%s", filename, diff.StringDiff(string(yamlData), string(expectedYAML)))
  227. t.Logf("If the change is expected, re-run with %s=true to update the fixtures", updateEnvVar)
  228. }
  229. }
  230. }
  231. func TestClusterRoleLabel(t *testing.T) {
  232. roles := bootstrappolicy.ClusterRoles()
  233. for i := range roles {
  234. role := roles[i]
  235. accessor, err := meta.Accessor(&role)
  236. if err != nil {
  237. t.Fatalf("unexpected error: %v", err)
  238. }
  239. if accessor.GetLabels()["kubernetes.io/bootstrapping"] != "rbac-defaults" {
  240. t.Errorf("ClusterRole: %s GetLabels() = %s, want %s", accessor.GetName(), accessor.GetLabels(), map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"})
  241. }
  242. }
  243. rolebindings := bootstrappolicy.ClusterRoleBindings()
  244. for i := range rolebindings {
  245. rolebinding := rolebindings[i]
  246. accessor, err := meta.Accessor(&rolebinding)
  247. if err != nil {
  248. t.Fatalf("unexpected error: %v", err)
  249. }
  250. if got, want := accessor.GetLabels(), map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"}; !reflect.DeepEqual(got, want) {
  251. t.Errorf("ClusterRoleBinding: %s GetLabels() = %s, want %s", accessor.GetName(), got, want)
  252. }
  253. }
  254. }