reconcile_role.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. /*
  2. Copyright 2017 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 reconciliation
  14. import (
  15. "fmt"
  16. "reflect"
  17. rbacv1 "k8s.io/api/rbac/v1"
  18. "k8s.io/apimachinery/pkg/api/equality"
  19. "k8s.io/apimachinery/pkg/api/errors"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/runtime"
  22. "k8s.io/kubernetes/pkg/registry/rbac/validation"
  23. )
  24. type ReconcileOperation string
  25. var (
  26. ReconcileCreate ReconcileOperation = "create"
  27. ReconcileUpdate ReconcileOperation = "update"
  28. ReconcileRecreate ReconcileOperation = "recreate"
  29. ReconcileNone ReconcileOperation = "none"
  30. )
  31. type RuleOwnerModifier interface {
  32. Get(namespace, name string) (RuleOwner, error)
  33. Create(RuleOwner) (RuleOwner, error)
  34. Update(RuleOwner) (RuleOwner, error)
  35. }
  36. type RuleOwner interface {
  37. GetObject() runtime.Object
  38. GetNamespace() string
  39. GetName() string
  40. GetLabels() map[string]string
  41. SetLabels(map[string]string)
  42. GetAnnotations() map[string]string
  43. SetAnnotations(map[string]string)
  44. GetRules() []rbacv1.PolicyRule
  45. SetRules([]rbacv1.PolicyRule)
  46. GetAggregationRule() *rbacv1.AggregationRule
  47. SetAggregationRule(*rbacv1.AggregationRule)
  48. DeepCopyRuleOwner() RuleOwner
  49. }
  50. type ReconcileRoleOptions struct {
  51. // Role is the expected role that will be reconciled
  52. Role RuleOwner
  53. // Confirm indicates writes should be performed. When false, results are returned as a dry-run.
  54. Confirm bool
  55. // RemoveExtraPermissions indicates reconciliation should remove extra permissions from an existing role
  56. RemoveExtraPermissions bool
  57. // Client is used to look up existing roles, and create/update the role when Confirm=true
  58. Client RuleOwnerModifier
  59. }
  60. type ReconcileClusterRoleResult struct {
  61. // Role is the reconciled role from the reconciliation operation.
  62. // If the reconcile was performed as a dry-run, or the existing role was protected, the reconciled role is not persisted.
  63. Role RuleOwner
  64. // MissingRules contains expected rules that were missing from the currently persisted role
  65. MissingRules []rbacv1.PolicyRule
  66. // ExtraRules contains extra permissions the currently persisted role had
  67. ExtraRules []rbacv1.PolicyRule
  68. // MissingAggregationRuleSelectors contains expected selectors that were missing from the currently persisted role
  69. MissingAggregationRuleSelectors []metav1.LabelSelector
  70. // ExtraAggregationRuleSelectors contains extra selectors the currently persisted role had
  71. ExtraAggregationRuleSelectors []metav1.LabelSelector
  72. // Operation is the API operation required to reconcile.
  73. // If no reconciliation was needed, it is set to ReconcileNone.
  74. // If options.Confirm == false, the reconcile was in dry-run mode, so the operation was not performed.
  75. // If result.Protected == true, the role opted out of reconciliation, so the operation was not performed.
  76. // Otherwise, the operation was performed.
  77. Operation ReconcileOperation
  78. // Protected indicates an existing role prevented reconciliation
  79. Protected bool
  80. }
  81. func (o *ReconcileRoleOptions) Run() (*ReconcileClusterRoleResult, error) {
  82. return o.run(0)
  83. }
  84. func (o *ReconcileRoleOptions) run(attempts int) (*ReconcileClusterRoleResult, error) {
  85. // This keeps us from retrying forever if a role keeps appearing and disappearing as we reconcile.
  86. // Conflict errors on update are handled at a higher level.
  87. if attempts > 2 {
  88. return nil, fmt.Errorf("exceeded maximum attempts")
  89. }
  90. var result *ReconcileClusterRoleResult
  91. existing, err := o.Client.Get(o.Role.GetNamespace(), o.Role.GetName())
  92. switch {
  93. case errors.IsNotFound(err):
  94. aggregationRule := o.Role.GetAggregationRule()
  95. if aggregationRule == nil {
  96. aggregationRule = &rbacv1.AggregationRule{}
  97. }
  98. result = &ReconcileClusterRoleResult{
  99. Role: o.Role,
  100. MissingRules: o.Role.GetRules(),
  101. MissingAggregationRuleSelectors: aggregationRule.ClusterRoleSelectors,
  102. Operation: ReconcileCreate,
  103. }
  104. case err != nil:
  105. return nil, err
  106. default:
  107. result, err = computeReconciledRole(existing, o.Role, o.RemoveExtraPermissions)
  108. if err != nil {
  109. return nil, err
  110. }
  111. }
  112. // If reconcile-protected, short-circuit
  113. if result.Protected {
  114. return result, nil
  115. }
  116. // If we're in dry-run mode, short-circuit
  117. if !o.Confirm {
  118. return result, nil
  119. }
  120. switch result.Operation {
  121. case ReconcileCreate:
  122. created, err := o.Client.Create(result.Role)
  123. // If created since we started this reconcile, re-run
  124. if errors.IsAlreadyExists(err) {
  125. return o.run(attempts + 1)
  126. }
  127. if err != nil {
  128. return nil, err
  129. }
  130. result.Role = created
  131. case ReconcileUpdate:
  132. updated, err := o.Client.Update(result.Role)
  133. // If deleted since we started this reconcile, re-run
  134. if errors.IsNotFound(err) {
  135. return o.run(attempts + 1)
  136. }
  137. if err != nil {
  138. return nil, err
  139. }
  140. result.Role = updated
  141. case ReconcileNone:
  142. // no-op
  143. default:
  144. return nil, fmt.Errorf("invalid operation: %v", result.Operation)
  145. }
  146. return result, nil
  147. }
  148. // computeReconciledRole returns the role that must be created and/or updated to make the
  149. // existing role's permissions match the expected role's permissions
  150. func computeReconciledRole(existing, expected RuleOwner, removeExtraPermissions bool) (*ReconcileClusterRoleResult, error) {
  151. result := &ReconcileClusterRoleResult{Operation: ReconcileNone}
  152. result.Protected = (existing.GetAnnotations()[rbacv1.AutoUpdateAnnotationKey] == "false")
  153. // Start with a copy of the existing object
  154. result.Role = existing.DeepCopyRuleOwner()
  155. // Merge expected annotations and labels
  156. result.Role.SetAnnotations(merge(expected.GetAnnotations(), result.Role.GetAnnotations()))
  157. if !reflect.DeepEqual(result.Role.GetAnnotations(), existing.GetAnnotations()) {
  158. result.Operation = ReconcileUpdate
  159. }
  160. result.Role.SetLabels(merge(expected.GetLabels(), result.Role.GetLabels()))
  161. if !reflect.DeepEqual(result.Role.GetLabels(), existing.GetLabels()) {
  162. result.Operation = ReconcileUpdate
  163. }
  164. // Compute extra and missing rules
  165. // Don't compute extra permissions if expected and existing roles are both aggregated
  166. if expected.GetAggregationRule() == nil || existing.GetAggregationRule() == nil {
  167. _, result.ExtraRules = validation.Covers(expected.GetRules(), existing.GetRules())
  168. }
  169. _, result.MissingRules = validation.Covers(existing.GetRules(), expected.GetRules())
  170. switch {
  171. case !removeExtraPermissions && len(result.MissingRules) > 0:
  172. // add missing rules in the union case
  173. result.Role.SetRules(append(result.Role.GetRules(), result.MissingRules...))
  174. result.Operation = ReconcileUpdate
  175. case removeExtraPermissions && (len(result.MissingRules) > 0 || len(result.ExtraRules) > 0):
  176. // stomp to expected rules in the non-union case
  177. result.Role.SetRules(expected.GetRules())
  178. result.Operation = ReconcileUpdate
  179. }
  180. // Compute extra and missing rules
  181. _, result.ExtraAggregationRuleSelectors = aggregationRuleCovers(expected.GetAggregationRule(), existing.GetAggregationRule())
  182. _, result.MissingAggregationRuleSelectors = aggregationRuleCovers(existing.GetAggregationRule(), expected.GetAggregationRule())
  183. switch {
  184. case expected.GetAggregationRule() == nil && existing.GetAggregationRule() != nil:
  185. // we didn't expect this to be an aggregated role at all, remove the existing aggregation
  186. result.Role.SetAggregationRule(nil)
  187. result.Operation = ReconcileUpdate
  188. case !removeExtraPermissions && len(result.MissingAggregationRuleSelectors) > 0:
  189. // add missing rules in the union case
  190. aggregationRule := result.Role.GetAggregationRule()
  191. if aggregationRule == nil {
  192. aggregationRule = &rbacv1.AggregationRule{}
  193. }
  194. aggregationRule.ClusterRoleSelectors = append(aggregationRule.ClusterRoleSelectors, result.MissingAggregationRuleSelectors...)
  195. result.Role.SetAggregationRule(aggregationRule)
  196. result.Operation = ReconcileUpdate
  197. case removeExtraPermissions && (len(result.MissingAggregationRuleSelectors) > 0 || len(result.ExtraAggregationRuleSelectors) > 0):
  198. result.Role.SetAggregationRule(expected.GetAggregationRule())
  199. result.Operation = ReconcileUpdate
  200. }
  201. return result, nil
  202. }
  203. // merge combines the given maps with the later annotations having higher precedence
  204. func merge(maps ...map[string]string) map[string]string {
  205. var output map[string]string = nil
  206. for _, m := range maps {
  207. if m != nil && output == nil {
  208. output = map[string]string{}
  209. }
  210. for k, v := range m {
  211. output[k] = v
  212. }
  213. }
  214. return output
  215. }
  216. // aggregationRuleCovers determines whether or not the ownerSelectors cover the servantSelectors in terms of semantically
  217. // equal label selectors.
  218. // It returns whether or not the ownerSelectors cover and a list of the rules that the ownerSelectors do not cover.
  219. func aggregationRuleCovers(ownerRule, servantRule *rbacv1.AggregationRule) (bool, []metav1.LabelSelector) {
  220. switch {
  221. case ownerRule == nil && servantRule == nil:
  222. return true, []metav1.LabelSelector{}
  223. case ownerRule == nil && servantRule != nil:
  224. return false, servantRule.ClusterRoleSelectors
  225. case ownerRule != nil && servantRule == nil:
  226. return true, []metav1.LabelSelector{}
  227. }
  228. ownerSelectors := ownerRule.ClusterRoleSelectors
  229. servantSelectors := servantRule.ClusterRoleSelectors
  230. uncoveredSelectors := []metav1.LabelSelector{}
  231. for _, servantSelector := range servantSelectors {
  232. covered := false
  233. for _, ownerSelector := range ownerSelectors {
  234. if equality.Semantic.DeepEqual(ownerSelector, servantSelector) {
  235. covered = true
  236. break
  237. }
  238. }
  239. if !covered {
  240. uncoveredSelectors = append(uncoveredSelectors, servantSelector)
  241. }
  242. }
  243. return (len(uncoveredSelectors) == 0), uncoveredSelectors
  244. }