helpers.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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. "fmt"
  16. "sync"
  17. "time"
  18. "github.com/onsi/ginkgo"
  19. "github.com/pkg/errors"
  20. authorizationv1beta1 "k8s.io/api/authorization/v1beta1"
  21. rbacv1beta1 "k8s.io/api/rbac/v1beta1"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/runtime/schema"
  24. "k8s.io/apimachinery/pkg/util/wait"
  25. v1beta1authorization "k8s.io/client-go/kubernetes/typed/authorization/v1beta1"
  26. v1beta1rbac "k8s.io/client-go/kubernetes/typed/rbac/v1beta1"
  27. )
  28. const (
  29. policyCachePollInterval = 100 * time.Millisecond
  30. policyCachePollTimeout = 5 * time.Second
  31. )
  32. type bindingsGetter interface {
  33. v1beta1rbac.RoleBindingsGetter
  34. v1beta1rbac.ClusterRoleBindingsGetter
  35. v1beta1rbac.ClusterRolesGetter
  36. }
  37. // WaitForAuthorizationUpdate checks if the given user can perform the named verb and action.
  38. // If policyCachePollTimeout is reached without the expected condition matching, an error is returned
  39. func WaitForAuthorizationUpdate(c v1beta1authorization.SubjectAccessReviewsGetter, user, namespace, verb string, resource schema.GroupResource, allowed bool) error {
  40. return WaitForNamedAuthorizationUpdate(c, user, namespace, verb, "", resource, allowed)
  41. }
  42. // WaitForNamedAuthorizationUpdate checks if the given user can perform the named verb and action on the named resource.
  43. // If policyCachePollTimeout is reached without the expected condition matching, an error is returned
  44. func WaitForNamedAuthorizationUpdate(c v1beta1authorization.SubjectAccessReviewsGetter, user, namespace, verb, resourceName string, resource schema.GroupResource, allowed bool) error {
  45. review := &authorizationv1beta1.SubjectAccessReview{
  46. Spec: authorizationv1beta1.SubjectAccessReviewSpec{
  47. ResourceAttributes: &authorizationv1beta1.ResourceAttributes{
  48. Group: resource.Group,
  49. Verb: verb,
  50. Resource: resource.Resource,
  51. Namespace: namespace,
  52. Name: resourceName,
  53. },
  54. User: user,
  55. },
  56. }
  57. err := wait.Poll(policyCachePollInterval, policyCachePollTimeout, func() (bool, error) {
  58. response, err := c.SubjectAccessReviews().Create(review)
  59. if err != nil {
  60. return false, err
  61. }
  62. if response.Status.Allowed != allowed {
  63. return false, nil
  64. }
  65. return true, nil
  66. })
  67. return err
  68. }
  69. // BindClusterRole binds the cluster role at the cluster scope. If RBAC is not enabled, nil
  70. // is returned with no action.
  71. func BindClusterRole(c bindingsGetter, clusterRole, ns string, subjects ...rbacv1beta1.Subject) error {
  72. if !IsRBACEnabled(c) {
  73. return nil
  74. }
  75. // Since the namespace names are unique, we can leave this lying around so we don't have to race any caches
  76. _, err := c.ClusterRoleBindings().Create(&rbacv1beta1.ClusterRoleBinding{
  77. ObjectMeta: metav1.ObjectMeta{
  78. Name: ns + "--" + clusterRole,
  79. },
  80. RoleRef: rbacv1beta1.RoleRef{
  81. APIGroup: "rbac.authorization.k8s.io",
  82. Kind: "ClusterRole",
  83. Name: clusterRole,
  84. },
  85. Subjects: subjects,
  86. })
  87. if err != nil {
  88. return errors.Wrapf(err, "binding clusterrole/%s for %q for %v", clusterRole, ns, subjects)
  89. }
  90. return nil
  91. }
  92. // BindClusterRoleInNamespace binds the cluster role at the namespace scope. If RBAC is not enabled, nil
  93. // is returned with no action.
  94. func BindClusterRoleInNamespace(c bindingsGetter, clusterRole, ns string, subjects ...rbacv1beta1.Subject) error {
  95. return bindInNamespace(c, "ClusterRole", clusterRole, ns, subjects...)
  96. }
  97. // BindRoleInNamespace binds the role at the namespace scope. If RBAC is not enabled, nil
  98. // is returned with no action.
  99. func BindRoleInNamespace(c bindingsGetter, role, ns string, subjects ...rbacv1beta1.Subject) error {
  100. return bindInNamespace(c, "Role", role, ns, subjects...)
  101. }
  102. func bindInNamespace(c bindingsGetter, roleType, role, ns string, subjects ...rbacv1beta1.Subject) error {
  103. if !IsRBACEnabled(c) {
  104. return nil
  105. }
  106. // Since the namespace names are unique, we can leave this lying around so we don't have to race any caches
  107. _, err := c.RoleBindings(ns).Create(&rbacv1beta1.RoleBinding{
  108. ObjectMeta: metav1.ObjectMeta{
  109. Name: ns + "--" + role,
  110. },
  111. RoleRef: rbacv1beta1.RoleRef{
  112. APIGroup: "rbac.authorization.k8s.io",
  113. Kind: roleType,
  114. Name: role,
  115. },
  116. Subjects: subjects,
  117. })
  118. if err != nil {
  119. return errors.Wrapf(err, "binding %s/%s into %q for %v", roleType, role, ns, subjects)
  120. }
  121. return nil
  122. }
  123. var (
  124. isRBACEnabledOnce sync.Once
  125. isRBACEnabled bool
  126. )
  127. // IsRBACEnabled returns true if RBAC is enabled. Otherwise false.
  128. func IsRBACEnabled(crGetter v1beta1rbac.ClusterRolesGetter) bool {
  129. isRBACEnabledOnce.Do(func() {
  130. crs, err := crGetter.ClusterRoles().List(metav1.ListOptions{})
  131. if err != nil {
  132. logf("Error listing ClusterRoles; assuming RBAC is disabled: %v", err)
  133. isRBACEnabled = false
  134. } else if crs == nil || len(crs.Items) == 0 {
  135. logf("No ClusterRoles found; assuming RBAC is disabled.")
  136. isRBACEnabled = false
  137. } else {
  138. logf("Found ClusterRoles; assuming RBAC is enabled.")
  139. isRBACEnabled = true
  140. }
  141. })
  142. return isRBACEnabled
  143. }
  144. // logf logs INFO lines to the GinkgoWriter.
  145. // TODO: Log functions like these should be put into their own package,
  146. // see: https://github.com/kubernetes/kubernetes/issues/76728
  147. func logf(format string, args ...interface{}) {
  148. log("INFO", format, args...)
  149. }
  150. // log prints formatted log messages to the global GinkgoWriter.
  151. // TODO: Log functions like these should be put into their own package,
  152. // see: https://github.com/kubernetes/kubernetes/issues/76728
  153. func log(level string, format string, args ...interface{}) {
  154. fmt.Fprintf(ginkgo.GinkgoWriter, nowStamp()+": "+level+": "+format+"\n", args...)
  155. }
  156. // nowStamp returns the current time formatted for placement in the logs (time.StampMilli).
  157. // TODO: If only used for logging, this should be put into a logging package,
  158. // see: https://github.com/kubernetes/kubernetes/issues/76728
  159. func nowStamp() string {
  160. return time.Now().Format(time.StampMilli)
  161. }