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 }