github.com/alex123012/deckhouse-controller-tools@v0.0.0-20230510090815-d594daf1af8c/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 "sigs.k8s.io/controller-tools/pkg/genall" 34 "sigs.k8s.io/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 // HeaderFile specifies the header text (e.g. license) to prepend to generated files. 154 HeaderFile string `marker:",optional"` 155 156 // Year specifies the year to substitute for " YEAR" in the header file. 157 Year string `marker:",optional"` 158 } 159 160 func (Generator) RegisterMarkers(into *markers.Registry) error { 161 if err := into.Register(RuleDefinition); err != nil { 162 return err 163 } 164 into.AddHelp(RuleDefinition, Rule{}.Help()) 165 return nil 166 } 167 168 // GenerateRoles generate a slice of objs representing either a ClusterRole or a Role object 169 // The order of the objs in the returned slice is stable and determined by their namespaces. 170 func GenerateRoles(ctx *genall.GenerationContext, roleName string) ([]interface{}, error) { 171 rulesByNS := make(map[string][]*Rule) 172 for _, root := range ctx.Roots { 173 markerSet, err := markers.PackageMarkers(ctx.Collector, root) 174 if err != nil { 175 root.AddError(err) 176 } 177 178 // group RBAC markers by namespace 179 for _, markerValue := range markerSet[RuleDefinition.Name] { 180 rule := markerValue.(Rule) 181 namespace := rule.Namespace 182 if _, ok := rulesByNS[namespace]; !ok { 183 rules := make([]*Rule, 0) 184 rulesByNS[namespace] = rules 185 } 186 rulesByNS[namespace] = append(rulesByNS[namespace], &rule) 187 } 188 } 189 190 // NormalizeRules merge Rule with the same ruleKey and sort the Rules 191 NormalizeRules := func(rules []*Rule) []rbacv1.PolicyRule { 192 ruleMap := make(map[ruleKey]*Rule) 193 // all the Rules having the same ruleKey will be merged into the first Rule 194 for _, rule := range rules { 195 key := rule.key() 196 if _, ok := ruleMap[key]; !ok { 197 ruleMap[key] = rule 198 continue 199 } 200 ruleMap[key].addVerbs(rule.Verbs) 201 } 202 203 // sort the Rules in rules according to their ruleKeys 204 keys := make([]ruleKey, 0, len(ruleMap)) 205 for key := range ruleMap { 206 keys = append(keys, key) 207 } 208 sort.Sort(ruleKeys(keys)) 209 210 var policyRules []rbacv1.PolicyRule 211 for _, key := range keys { 212 policyRules = append(policyRules, ruleMap[key].ToRule()) 213 214 } 215 return policyRules 216 } 217 218 // collect all the namespaces and sort them 219 var namespaces []string 220 for ns := range rulesByNS { 221 namespaces = append(namespaces, ns) 222 } 223 sort.Strings(namespaces) 224 225 // process the items in rulesByNS by the order specified in `namespaces` to make sure that the Role order is stable 226 var objs []interface{} 227 for _, ns := range namespaces { 228 rules := rulesByNS[ns] 229 policyRules := NormalizeRules(rules) 230 if len(policyRules) == 0 { 231 continue 232 } 233 if ns == "" { 234 objs = append(objs, rbacv1.ClusterRole{ 235 TypeMeta: metav1.TypeMeta{ 236 Kind: "ClusterRole", 237 APIVersion: rbacv1.SchemeGroupVersion.String(), 238 }, 239 ObjectMeta: metav1.ObjectMeta{ 240 Name: roleName, 241 }, 242 Rules: policyRules, 243 }) 244 } else { 245 objs = append(objs, rbacv1.Role{ 246 TypeMeta: metav1.TypeMeta{ 247 Kind: "Role", 248 APIVersion: rbacv1.SchemeGroupVersion.String(), 249 }, 250 ObjectMeta: metav1.ObjectMeta{ 251 Name: roleName, 252 Namespace: ns, 253 }, 254 Rules: policyRules, 255 }) 256 } 257 } 258 259 return objs, nil 260 } 261 262 func (g Generator) Generate(ctx *genall.GenerationContext) error { 263 objs, err := GenerateRoles(ctx, g.RoleName) 264 if err != nil { 265 return err 266 } 267 268 if len(objs) == 0 { 269 return nil 270 } 271 272 var headerText string 273 if g.HeaderFile != "" { 274 headerBytes, err := ctx.ReadFile(g.HeaderFile) 275 if err != nil { 276 return err 277 } 278 headerText = string(headerBytes) 279 } 280 headerText = strings.ReplaceAll(headerText, " YEAR", " "+g.Year) 281 282 return ctx.WriteYAML("role.yaml", headerText, objs, genall.WithTransform(genall.TransformRemoveCreationTimestamp)) 283 }