github.com/regadas/controller-tools@v0.5.1-0.20210408091555-18885b17ff7b/pkg/rbac/parser.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package rbac contain libraries for generating RBAC manifests from RBAC
    18  // markers in Go source files.
    19  //
    20  // The markers take the form:
    21  //
    22  //  +kubebuilder:rbac:groups=<groups>,resources=<resources>,resourceNames=<resource names>,verbs=<verbs>,urls=<non resource urls>
    23  package rbac
    24  
    25  import (
    26  	"fmt"
    27  	"sort"
    28  	"strings"
    29  
    30  	rbacv1 "k8s.io/api/rbac/v1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  
    33  	"github.com/regadas/controller-tools/pkg/genall"
    34  	"github.com/regadas/controller-tools/pkg/markers"
    35  )
    36  
    37  var (
    38  	// RuleDefinition is a marker for defining RBAC rules.
    39  	// Call ToRule on the value to get a Kubernetes RBAC policy rule.
    40  	RuleDefinition = markers.Must(markers.MakeDefinition("kubebuilder:rbac", markers.DescribesPackage, Rule{}))
    41  )
    42  
    43  // +controllertools:marker:generateHelp:category=RBAC
    44  
    45  // Rule specifies an RBAC rule to all access to some resources or non-resource URLs.
    46  type Rule struct {
    47  	// Groups specifies the API groups that this rule encompasses.
    48  	Groups []string `marker:",optional"`
    49  	// Resources specifies the API resources that this rule encompasses.
    50  	Resources []string `marker:",optional"`
    51  	// ResourceNames specifies the names of the API resources that this rule encompasses.
    52  	//
    53  	// Create requests cannot be restricted by resourcename, as the object's name
    54  	// is not known at authorization time.
    55  	ResourceNames []string `marker:",optional"`
    56  	// Verbs specifies the (lowercase) kubernetes API verbs that this rule encompasses.
    57  	Verbs []string
    58  	// URL specifies the non-resource URLs that this rule encompasses.
    59  	URLs []string `marker:"urls,optional"`
    60  	// Namespace specifies the scope of the Rule.
    61  	// If not set, the Rule belongs to the generated ClusterRole.
    62  	// If set, the Rule belongs to a Role, whose namespace is specified by this field.
    63  	Namespace string `marker:",optional"`
    64  }
    65  
    66  // ruleKey represents the resources and non-resources a Rule applies.
    67  type ruleKey struct {
    68  	Groups        string
    69  	Resources     string
    70  	ResourceNames string
    71  	URLs          string
    72  }
    73  
    74  func (key ruleKey) String() string {
    75  	return fmt.Sprintf("%s + %s + %s + %s", key.Groups, key.Resources, key.ResourceNames, key.URLs)
    76  }
    77  
    78  // ruleKeys implements sort.Interface
    79  type ruleKeys []ruleKey
    80  
    81  func (keys ruleKeys) Len() int           { return len(keys) }
    82  func (keys ruleKeys) Swap(i, j int)      { keys[i], keys[j] = keys[j], keys[i] }
    83  func (keys ruleKeys) Less(i, j int) bool { return keys[i].String() < keys[j].String() }
    84  
    85  // key normalizes the Rule and returns a ruleKey object.
    86  func (r *Rule) key() ruleKey {
    87  	r.normalize()
    88  	return ruleKey{
    89  		Groups:        strings.Join(r.Groups, "&"),
    90  		Resources:     strings.Join(r.Resources, "&"),
    91  		ResourceNames: strings.Join(r.ResourceNames, "&"),
    92  		URLs:          strings.Join(r.URLs, "&"),
    93  	}
    94  }
    95  
    96  // addVerbs adds new verbs into a Rule.
    97  // The duplicates in `r.Verbs` will be removed, and then `r.Verbs` will be sorted.
    98  func (r *Rule) addVerbs(verbs []string) {
    99  	r.Verbs = removeDupAndSort(append(r.Verbs, verbs...))
   100  }
   101  
   102  // normalize removes duplicates from each field of a Rule, and sorts each field.
   103  func (r *Rule) normalize() {
   104  	r.Groups = removeDupAndSort(r.Groups)
   105  	r.Resources = removeDupAndSort(r.Resources)
   106  	r.ResourceNames = removeDupAndSort(r.ResourceNames)
   107  	r.Verbs = removeDupAndSort(r.Verbs)
   108  	r.URLs = removeDupAndSort(r.URLs)
   109  }
   110  
   111  // removeDupAndSort removes duplicates in strs, sorts the items, and returns a
   112  // new slice of strings.
   113  func removeDupAndSort(strs []string) []string {
   114  	set := make(map[string]bool)
   115  	for _, str := range strs {
   116  		if _, ok := set[str]; !ok {
   117  			set[str] = true
   118  		}
   119  	}
   120  
   121  	var result []string
   122  	for str := range set {
   123  		result = append(result, str)
   124  	}
   125  	sort.Strings(result)
   126  	return result
   127  }
   128  
   129  // ToRule converts this rule to its Kubernetes API form.
   130  func (r *Rule) ToRule() rbacv1.PolicyRule {
   131  	// fix the group names first, since letting people type "core" is nice
   132  	for i, group := range r.Groups {
   133  		if group == "core" {
   134  			r.Groups[i] = ""
   135  		}
   136  	}
   137  	return rbacv1.PolicyRule{
   138  		APIGroups:       r.Groups,
   139  		Verbs:           r.Verbs,
   140  		Resources:       r.Resources,
   141  		ResourceNames:   r.ResourceNames,
   142  		NonResourceURLs: r.URLs,
   143  	}
   144  }
   145  
   146  // +controllertools:marker:generateHelp
   147  
   148  // Generator generates ClusterRole objects.
   149  type Generator struct {
   150  	// RoleName sets the name of the generated ClusterRole.
   151  	RoleName string
   152  }
   153  
   154  func (Generator) RegisterMarkers(into *markers.Registry) error {
   155  	if err := into.Register(RuleDefinition); err != nil {
   156  		return err
   157  	}
   158  	into.AddHelp(RuleDefinition, Rule{}.Help())
   159  	return nil
   160  }
   161  
   162  // GenerateRoles generate a slice of objs representing either a ClusterRole or a Role object
   163  // The order of the objs in the returned slice is stable and determined by their namespaces.
   164  func GenerateRoles(ctx *genall.GenerationContext, roleName string) ([]interface{}, error) {
   165  	rulesByNS := make(map[string][]*Rule)
   166  	for _, root := range ctx.Roots {
   167  		markerSet, err := markers.PackageMarkers(ctx.Collector, root)
   168  		if err != nil {
   169  			root.AddError(err)
   170  		}
   171  
   172  		// group RBAC markers by namespace
   173  		for _, markerValue := range markerSet[RuleDefinition.Name] {
   174  			rule := markerValue.(Rule)
   175  			namespace := rule.Namespace
   176  			if _, ok := rulesByNS[namespace]; !ok {
   177  				rules := make([]*Rule, 0)
   178  				rulesByNS[namespace] = rules
   179  			}
   180  			rulesByNS[namespace] = append(rulesByNS[namespace], &rule)
   181  		}
   182  	}
   183  
   184  	// NormalizeRules merge Rule with the same ruleKey and sort the Rules
   185  	NormalizeRules := func(rules []*Rule) []rbacv1.PolicyRule {
   186  		ruleMap := make(map[ruleKey]*Rule)
   187  		// all the Rules having the same ruleKey will be merged into the first Rule
   188  		for _, rule := range rules {
   189  			key := rule.key()
   190  			if _, ok := ruleMap[key]; !ok {
   191  				ruleMap[key] = rule
   192  				continue
   193  			}
   194  			ruleMap[key].addVerbs(rule.Verbs)
   195  		}
   196  
   197  		// sort the Rules in rules according to their ruleKeys
   198  		keys := make([]ruleKey, 0, len(ruleMap))
   199  		for key := range ruleMap {
   200  			keys = append(keys, key)
   201  		}
   202  		sort.Sort(ruleKeys(keys))
   203  
   204  		var policyRules []rbacv1.PolicyRule
   205  		for _, key := range keys {
   206  			policyRules = append(policyRules, ruleMap[key].ToRule())
   207  
   208  		}
   209  		return policyRules
   210  	}
   211  
   212  	// collect all the namespaces and sort them
   213  	var namespaces []string
   214  	for ns := range rulesByNS {
   215  		namespaces = append(namespaces, ns)
   216  	}
   217  	sort.Strings(namespaces)
   218  
   219  	// process the items in rulesByNS by the order specified in `namespaces` to make sure that the Role order is stable
   220  	var objs []interface{}
   221  	for _, ns := range namespaces {
   222  		rules := rulesByNS[ns]
   223  		policyRules := NormalizeRules(rules)
   224  		if len(policyRules) == 0 {
   225  			continue
   226  		}
   227  		if ns == "" {
   228  			objs = append(objs, rbacv1.ClusterRole{
   229  				TypeMeta: metav1.TypeMeta{
   230  					Kind:       "ClusterRole",
   231  					APIVersion: rbacv1.SchemeGroupVersion.String(),
   232  				},
   233  				ObjectMeta: metav1.ObjectMeta{
   234  					Name: roleName,
   235  				},
   236  				Rules: policyRules,
   237  			})
   238  		} else {
   239  			objs = append(objs, rbacv1.Role{
   240  				TypeMeta: metav1.TypeMeta{
   241  					Kind:       "Role",
   242  					APIVersion: rbacv1.SchemeGroupVersion.String(),
   243  				},
   244  				ObjectMeta: metav1.ObjectMeta{
   245  					Name:      roleName,
   246  					Namespace: ns,
   247  				},
   248  				Rules: policyRules,
   249  			})
   250  		}
   251  	}
   252  
   253  	return objs, nil
   254  }
   255  
   256  func (g Generator) Generate(ctx *genall.GenerationContext) error {
   257  	objs, err := GenerateRoles(ctx, g.RoleName)
   258  	if err != nil {
   259  		return err
   260  	}
   261  
   262  	if len(objs) == 0 {
   263  		return nil
   264  	}
   265  
   266  	return ctx.WriteYAML("role.yaml", objs...)
   267  }