123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- /*
- Copyright 2017 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package reconciliation
- import (
- "fmt"
- "reflect"
- rbacv1 "k8s.io/api/rbac/v1"
- "k8s.io/apimachinery/pkg/api/equality"
- "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/kubernetes/pkg/registry/rbac/validation"
- )
- type ReconcileOperation string
- var (
- ReconcileCreate ReconcileOperation = "create"
- ReconcileUpdate ReconcileOperation = "update"
- ReconcileRecreate ReconcileOperation = "recreate"
- ReconcileNone ReconcileOperation = "none"
- )
- type RuleOwnerModifier interface {
- Get(namespace, name string) (RuleOwner, error)
- Create(RuleOwner) (RuleOwner, error)
- Update(RuleOwner) (RuleOwner, error)
- }
- type RuleOwner interface {
- GetObject() runtime.Object
- GetNamespace() string
- GetName() string
- GetLabels() map[string]string
- SetLabels(map[string]string)
- GetAnnotations() map[string]string
- SetAnnotations(map[string]string)
- GetRules() []rbacv1.PolicyRule
- SetRules([]rbacv1.PolicyRule)
- GetAggregationRule() *rbacv1.AggregationRule
- SetAggregationRule(*rbacv1.AggregationRule)
- DeepCopyRuleOwner() RuleOwner
- }
- type ReconcileRoleOptions struct {
- // Role is the expected role that will be reconciled
- Role RuleOwner
- // Confirm indicates writes should be performed. When false, results are returned as a dry-run.
- Confirm bool
- // RemoveExtraPermissions indicates reconciliation should remove extra permissions from an existing role
- RemoveExtraPermissions bool
- // Client is used to look up existing roles, and create/update the role when Confirm=true
- Client RuleOwnerModifier
- }
- type ReconcileClusterRoleResult struct {
- // Role is the reconciled role from the reconciliation operation.
- // If the reconcile was performed as a dry-run, or the existing role was protected, the reconciled role is not persisted.
- Role RuleOwner
- // MissingRules contains expected rules that were missing from the currently persisted role
- MissingRules []rbacv1.PolicyRule
- // ExtraRules contains extra permissions the currently persisted role had
- ExtraRules []rbacv1.PolicyRule
- // MissingAggregationRuleSelectors contains expected selectors that were missing from the currently persisted role
- MissingAggregationRuleSelectors []metav1.LabelSelector
- // ExtraAggregationRuleSelectors contains extra selectors the currently persisted role had
- ExtraAggregationRuleSelectors []metav1.LabelSelector
- // Operation is the API operation required to reconcile.
- // If no reconciliation was needed, it is set to ReconcileNone.
- // If options.Confirm == false, the reconcile was in dry-run mode, so the operation was not performed.
- // If result.Protected == true, the role opted out of reconciliation, so the operation was not performed.
- // Otherwise, the operation was performed.
- Operation ReconcileOperation
- // Protected indicates an existing role prevented reconciliation
- Protected bool
- }
- func (o *ReconcileRoleOptions) Run() (*ReconcileClusterRoleResult, error) {
- return o.run(0)
- }
- func (o *ReconcileRoleOptions) run(attempts int) (*ReconcileClusterRoleResult, error) {
- // This keeps us from retrying forever if a role keeps appearing and disappearing as we reconcile.
- // Conflict errors on update are handled at a higher level.
- if attempts > 2 {
- return nil, fmt.Errorf("exceeded maximum attempts")
- }
- var result *ReconcileClusterRoleResult
- existing, err := o.Client.Get(o.Role.GetNamespace(), o.Role.GetName())
- switch {
- case errors.IsNotFound(err):
- aggregationRule := o.Role.GetAggregationRule()
- if aggregationRule == nil {
- aggregationRule = &rbacv1.AggregationRule{}
- }
- result = &ReconcileClusterRoleResult{
- Role: o.Role,
- MissingRules: o.Role.GetRules(),
- MissingAggregationRuleSelectors: aggregationRule.ClusterRoleSelectors,
- Operation: ReconcileCreate,
- }
- case err != nil:
- return nil, err
- default:
- result, err = computeReconciledRole(existing, o.Role, o.RemoveExtraPermissions)
- if err != nil {
- return nil, err
- }
- }
- // If reconcile-protected, short-circuit
- if result.Protected {
- return result, nil
- }
- // If we're in dry-run mode, short-circuit
- if !o.Confirm {
- return result, nil
- }
- switch result.Operation {
- case ReconcileCreate:
- created, err := o.Client.Create(result.Role)
- // If created since we started this reconcile, re-run
- if errors.IsAlreadyExists(err) {
- return o.run(attempts + 1)
- }
- if err != nil {
- return nil, err
- }
- result.Role = created
- case ReconcileUpdate:
- updated, err := o.Client.Update(result.Role)
- // If deleted since we started this reconcile, re-run
- if errors.IsNotFound(err) {
- return o.run(attempts + 1)
- }
- if err != nil {
- return nil, err
- }
- result.Role = updated
- case ReconcileNone:
- // no-op
- default:
- return nil, fmt.Errorf("invalid operation: %v", result.Operation)
- }
- return result, nil
- }
- // computeReconciledRole returns the role that must be created and/or updated to make the
- // existing role's permissions match the expected role's permissions
- func computeReconciledRole(existing, expected RuleOwner, removeExtraPermissions bool) (*ReconcileClusterRoleResult, error) {
- result := &ReconcileClusterRoleResult{Operation: ReconcileNone}
- result.Protected = (existing.GetAnnotations()[rbacv1.AutoUpdateAnnotationKey] == "false")
- // Start with a copy of the existing object
- result.Role = existing.DeepCopyRuleOwner()
- // Merge expected annotations and labels
- result.Role.SetAnnotations(merge(expected.GetAnnotations(), result.Role.GetAnnotations()))
- if !reflect.DeepEqual(result.Role.GetAnnotations(), existing.GetAnnotations()) {
- result.Operation = ReconcileUpdate
- }
- result.Role.SetLabels(merge(expected.GetLabels(), result.Role.GetLabels()))
- if !reflect.DeepEqual(result.Role.GetLabels(), existing.GetLabels()) {
- result.Operation = ReconcileUpdate
- }
- // Compute extra and missing rules
- // Don't compute extra permissions if expected and existing roles are both aggregated
- if expected.GetAggregationRule() == nil || existing.GetAggregationRule() == nil {
- _, result.ExtraRules = validation.Covers(expected.GetRules(), existing.GetRules())
- }
- _, result.MissingRules = validation.Covers(existing.GetRules(), expected.GetRules())
- switch {
- case !removeExtraPermissions && len(result.MissingRules) > 0:
- // add missing rules in the union case
- result.Role.SetRules(append(result.Role.GetRules(), result.MissingRules...))
- result.Operation = ReconcileUpdate
- case removeExtraPermissions && (len(result.MissingRules) > 0 || len(result.ExtraRules) > 0):
- // stomp to expected rules in the non-union case
- result.Role.SetRules(expected.GetRules())
- result.Operation = ReconcileUpdate
- }
- // Compute extra and missing rules
- _, result.ExtraAggregationRuleSelectors = aggregationRuleCovers(expected.GetAggregationRule(), existing.GetAggregationRule())
- _, result.MissingAggregationRuleSelectors = aggregationRuleCovers(existing.GetAggregationRule(), expected.GetAggregationRule())
- switch {
- case expected.GetAggregationRule() == nil && existing.GetAggregationRule() != nil:
- // we didn't expect this to be an aggregated role at all, remove the existing aggregation
- result.Role.SetAggregationRule(nil)
- result.Operation = ReconcileUpdate
- case !removeExtraPermissions && len(result.MissingAggregationRuleSelectors) > 0:
- // add missing rules in the union case
- aggregationRule := result.Role.GetAggregationRule()
- if aggregationRule == nil {
- aggregationRule = &rbacv1.AggregationRule{}
- }
- aggregationRule.ClusterRoleSelectors = append(aggregationRule.ClusterRoleSelectors, result.MissingAggregationRuleSelectors...)
- result.Role.SetAggregationRule(aggregationRule)
- result.Operation = ReconcileUpdate
- case removeExtraPermissions && (len(result.MissingAggregationRuleSelectors) > 0 || len(result.ExtraAggregationRuleSelectors) > 0):
- result.Role.SetAggregationRule(expected.GetAggregationRule())
- result.Operation = ReconcileUpdate
- }
- return result, nil
- }
- // merge combines the given maps with the later annotations having higher precedence
- func merge(maps ...map[string]string) map[string]string {
- var output map[string]string = nil
- for _, m := range maps {
- if m != nil && output == nil {
- output = map[string]string{}
- }
- for k, v := range m {
- output[k] = v
- }
- }
- return output
- }
- // aggregationRuleCovers determines whether or not the ownerSelectors cover the servantSelectors in terms of semantically
- // equal label selectors.
- // It returns whether or not the ownerSelectors cover and a list of the rules that the ownerSelectors do not cover.
- func aggregationRuleCovers(ownerRule, servantRule *rbacv1.AggregationRule) (bool, []metav1.LabelSelector) {
- switch {
- case ownerRule == nil && servantRule == nil:
- return true, []metav1.LabelSelector{}
- case ownerRule == nil && servantRule != nil:
- return false, servantRule.ClusterRoleSelectors
- case ownerRule != nil && servantRule == nil:
- return true, []metav1.LabelSelector{}
- }
- ownerSelectors := ownerRule.ClusterRoleSelectors
- servantSelectors := servantRule.ClusterRoleSelectors
- uncoveredSelectors := []metav1.LabelSelector{}
- for _, servantSelector := range servantSelectors {
- covered := false
- for _, ownerSelector := range ownerSelectors {
- if equality.Semantic.DeepEqual(ownerSelector, servantSelector) {
- covered = true
- break
- }
- }
- if !covered {
- uncoveredSelectors = append(uncoveredSelectors, servantSelector)
- }
- }
- return (len(uncoveredSelectors) == 0), uncoveredSelectors
- }
|