parser.go 8.2 KB

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