create_role.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. /*
  2. Copyright 2017 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 create
  14. import (
  15. "fmt"
  16. "strings"
  17. "github.com/spf13/cobra"
  18. rbacv1 "k8s.io/api/rbac/v1"
  19. "k8s.io/apimachinery/pkg/api/meta"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/runtime"
  22. "k8s.io/apimachinery/pkg/runtime/schema"
  23. "k8s.io/apimachinery/pkg/util/sets"
  24. "k8s.io/cli-runtime/pkg/genericclioptions"
  25. clientgorbacv1 "k8s.io/client-go/kubernetes/typed/rbac/v1"
  26. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  27. "k8s.io/kubernetes/pkg/kubectl/scheme"
  28. "k8s.io/kubernetes/pkg/kubectl/util/i18n"
  29. "k8s.io/kubernetes/pkg/kubectl/util/templates"
  30. )
  31. var (
  32. roleLong = templates.LongDesc(i18n.T(`
  33. Create a role with single rule.`))
  34. roleExample = templates.Examples(i18n.T(`
  35. # Create a Role named "pod-reader" that allows user to perform "get", "watch" and "list" on pods
  36. kubectl create role pod-reader --verb=get --verb=list --verb=watch --resource=pods
  37. # Create a Role named "pod-reader" with ResourceName specified
  38. kubectl create role pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
  39. # Create a Role named "foo" with API Group specified
  40. kubectl create role foo --verb=get,list,watch --resource=rs.extensions
  41. # Create a Role named "foo" with SubResource specified
  42. kubectl create role foo --verb=get,list,watch --resource=pods,pods/status`))
  43. // Valid resource verb list for validation.
  44. validResourceVerbs = []string{"*", "get", "delete", "list", "create", "update", "patch", "watch", "proxy", "deletecollection", "use", "bind", "escalate", "impersonate"}
  45. // Specialized verbs and GroupResources
  46. specialVerbs = map[string][]schema.GroupResource{
  47. "use": {
  48. {
  49. Group: "extensions",
  50. Resource: "podsecuritypolicies",
  51. },
  52. },
  53. "bind": {
  54. {
  55. Group: "rbac.authorization.k8s.io",
  56. Resource: "roles",
  57. },
  58. {
  59. Group: "rbac.authorization.k8s.io",
  60. Resource: "clusterroles",
  61. },
  62. },
  63. "escalate": {
  64. {
  65. Group: "rbac.authorization.k8s.io",
  66. Resource: "roles",
  67. },
  68. {
  69. Group: "rbac.authorization.k8s.io",
  70. Resource: "clusterroles",
  71. },
  72. },
  73. "impersonate": {
  74. {
  75. Group: "",
  76. Resource: "users",
  77. },
  78. {
  79. Group: "",
  80. Resource: "serviceaccounts",
  81. },
  82. {
  83. Group: "",
  84. Resource: "groups",
  85. },
  86. {
  87. Group: "authentication.k8s.io",
  88. Resource: "userextras",
  89. },
  90. },
  91. }
  92. )
  93. // ResourceOptions holds the related options for '--resource' option
  94. type ResourceOptions struct {
  95. Group string
  96. Resource string
  97. SubResource string
  98. }
  99. // CreateRoleOptions holds the options for 'create role' sub command
  100. type CreateRoleOptions struct {
  101. PrintFlags *genericclioptions.PrintFlags
  102. Name string
  103. Verbs []string
  104. Resources []ResourceOptions
  105. ResourceNames []string
  106. DryRun bool
  107. OutputFormat string
  108. Namespace string
  109. Client clientgorbacv1.RbacV1Interface
  110. Mapper meta.RESTMapper
  111. PrintObj func(obj runtime.Object) error
  112. genericclioptions.IOStreams
  113. }
  114. // NewCreateRoleOptions returns an initialized CreateRoleOptions instance
  115. func NewCreateRoleOptions(ioStreams genericclioptions.IOStreams) *CreateRoleOptions {
  116. return &CreateRoleOptions{
  117. PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
  118. IOStreams: ioStreams,
  119. }
  120. }
  121. // NewCmdCreateRole returnns an initialized Command instance for 'create role' sub command
  122. func NewCmdCreateRole(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
  123. o := NewCreateRoleOptions(ioStreams)
  124. cmd := &cobra.Command{
  125. Use: "role NAME --verb=verb --resource=resource.group/subresource [--resource-name=resourcename] [--dry-run]",
  126. DisableFlagsInUseLine: true,
  127. Short: roleLong,
  128. Long: roleLong,
  129. Example: roleExample,
  130. Run: func(cmd *cobra.Command, args []string) {
  131. cmdutil.CheckErr(o.Complete(f, cmd, args))
  132. cmdutil.CheckErr(o.Validate())
  133. cmdutil.CheckErr(o.RunCreateRole())
  134. },
  135. }
  136. o.PrintFlags.AddFlags(cmd)
  137. cmdutil.AddApplyAnnotationFlags(cmd)
  138. cmdutil.AddValidateFlags(cmd)
  139. cmdutil.AddDryRunFlag(cmd)
  140. cmd.Flags().StringSliceVar(&o.Verbs, "verb", o.Verbs, "Verb that applies to the resources contained in the rule")
  141. cmd.Flags().StringSlice("resource", []string{}, "Resource that the rule applies to")
  142. cmd.Flags().StringArrayVar(&o.ResourceNames, "resource-name", o.ResourceNames, "Resource in the white list that the rule applies to, repeat this flag for multiple items")
  143. return cmd
  144. }
  145. // Complete completes all the required options
  146. func (o *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
  147. name, err := NameFromCommandArgs(cmd, args)
  148. if err != nil {
  149. return err
  150. }
  151. o.Name = name
  152. // Remove duplicate verbs.
  153. verbs := []string{}
  154. for _, v := range o.Verbs {
  155. // VerbAll respresents all kinds of verbs.
  156. if v == "*" {
  157. verbs = []string{"*"}
  158. break
  159. }
  160. if !arrayContains(verbs, v) {
  161. verbs = append(verbs, v)
  162. }
  163. }
  164. o.Verbs = verbs
  165. // Support resource.group pattern. If no API Group specified, use "" as core API Group.
  166. // e.g. --resource=pods,deployments.extensions
  167. resources := cmdutil.GetFlagStringSlice(cmd, "resource")
  168. for _, r := range resources {
  169. sections := strings.SplitN(r, "/", 2)
  170. resource := &ResourceOptions{}
  171. if len(sections) == 2 {
  172. resource.SubResource = sections[1]
  173. }
  174. parts := strings.SplitN(sections[0], ".", 2)
  175. if len(parts) == 2 {
  176. resource.Group = parts[1]
  177. }
  178. resource.Resource = parts[0]
  179. if resource.Resource == "*" && len(parts) == 1 && len(sections) == 1 {
  180. o.Resources = []ResourceOptions{*resource}
  181. break
  182. }
  183. o.Resources = append(o.Resources, *resource)
  184. }
  185. // Remove duplicate resource names.
  186. resourceNames := []string{}
  187. for _, n := range o.ResourceNames {
  188. if !arrayContains(resourceNames, n) {
  189. resourceNames = append(resourceNames, n)
  190. }
  191. }
  192. o.ResourceNames = resourceNames
  193. // Complete other options for Run.
  194. o.Mapper, err = f.ToRESTMapper()
  195. if err != nil {
  196. return err
  197. }
  198. o.DryRun = cmdutil.GetDryRunFlag(cmd)
  199. o.OutputFormat = cmdutil.GetFlagString(cmd, "output")
  200. if o.DryRun {
  201. o.PrintFlags.Complete("%s (dry run)")
  202. }
  203. printer, err := o.PrintFlags.ToPrinter()
  204. if err != nil {
  205. return err
  206. }
  207. o.PrintObj = func(obj runtime.Object) error {
  208. return printer.PrintObj(obj, o.Out)
  209. }
  210. o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
  211. if err != nil {
  212. return err
  213. }
  214. clientset, err := f.KubernetesClientSet()
  215. if err != nil {
  216. return err
  217. }
  218. o.Client = clientset.RbacV1()
  219. return nil
  220. }
  221. // Validate makes sure there is no discrepency in provided option values
  222. func (o *CreateRoleOptions) Validate() error {
  223. if o.Name == "" {
  224. return fmt.Errorf("name must be specified")
  225. }
  226. // validate verbs.
  227. if len(o.Verbs) == 0 {
  228. return fmt.Errorf("at least one verb must be specified")
  229. }
  230. for _, v := range o.Verbs {
  231. if !arrayContains(validResourceVerbs, v) {
  232. return fmt.Errorf("invalid verb: '%s'", v)
  233. }
  234. }
  235. // validate resources.
  236. if len(o.Resources) == 0 {
  237. return fmt.Errorf("at least one resource must be specified")
  238. }
  239. return o.validateResource()
  240. }
  241. func (o *CreateRoleOptions) validateResource() error {
  242. for _, r := range o.Resources {
  243. if len(r.Resource) == 0 {
  244. return fmt.Errorf("resource must be specified if apiGroup/subresource specified")
  245. }
  246. if r.Resource == "*" {
  247. return nil
  248. }
  249. resource := schema.GroupVersionResource{Resource: r.Resource, Group: r.Group}
  250. groupVersionResource, err := o.Mapper.ResourceFor(schema.GroupVersionResource{Resource: r.Resource, Group: r.Group})
  251. if err == nil {
  252. resource = groupVersionResource
  253. }
  254. for _, v := range o.Verbs {
  255. if groupResources, ok := specialVerbs[v]; ok {
  256. match := false
  257. for _, extra := range groupResources {
  258. if resource.Resource == extra.Resource && resource.Group == extra.Group {
  259. match = true
  260. err = nil
  261. break
  262. }
  263. }
  264. if !match {
  265. return fmt.Errorf("can not perform '%s' on '%s' in group '%s'", v, resource.Resource, resource.Group)
  266. }
  267. }
  268. }
  269. if err != nil {
  270. return err
  271. }
  272. }
  273. return nil
  274. }
  275. // RunCreateRole performs the execution of 'create role' sub command
  276. func (o *CreateRoleOptions) RunCreateRole() error {
  277. role := &rbacv1.Role{
  278. // this is ok because we know exactly how we want to be serialized
  279. TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "Role"},
  280. }
  281. role.Name = o.Name
  282. rules, err := generateResourcePolicyRules(o.Mapper, o.Verbs, o.Resources, o.ResourceNames, []string{})
  283. if err != nil {
  284. return err
  285. }
  286. role.Rules = rules
  287. // Create role.
  288. if !o.DryRun {
  289. role, err = o.Client.Roles(o.Namespace).Create(role)
  290. if err != nil {
  291. return err
  292. }
  293. }
  294. return o.PrintObj(role)
  295. }
  296. func arrayContains(s []string, e string) bool {
  297. for _, a := range s {
  298. if a == e {
  299. return true
  300. }
  301. }
  302. return false
  303. }
  304. func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resources []ResourceOptions, resourceNames []string, nonResourceURLs []string) ([]rbacv1.PolicyRule, error) {
  305. // groupResourceMapping is a apigroup-resource map. The key of this map is api group, while the value
  306. // is a string array of resources under this api group.
  307. // E.g. groupResourceMapping = {"extensions": ["replicasets", "deployments"], "batch":["jobs"]}
  308. groupResourceMapping := map[string][]string{}
  309. // This loop does the following work:
  310. // 1. Constructs groupResourceMapping based on input resources.
  311. // 2. Prevents pointing to non-existent resources.
  312. // 3. Transfers resource short name to long name. E.g. rs.extensions is transferred to replicasets.extensions
  313. for _, r := range resources {
  314. resource := schema.GroupVersionResource{Resource: r.Resource, Group: r.Group}
  315. groupVersionResource, err := mapper.ResourceFor(schema.GroupVersionResource{Resource: r.Resource, Group: r.Group})
  316. if err == nil {
  317. resource = groupVersionResource
  318. }
  319. if len(r.SubResource) > 0 {
  320. resource.Resource = resource.Resource + "/" + r.SubResource
  321. }
  322. if !arrayContains(groupResourceMapping[resource.Group], resource.Resource) {
  323. groupResourceMapping[resource.Group] = append(groupResourceMapping[resource.Group], resource.Resource)
  324. }
  325. }
  326. // Create separate rule for each of the api group.
  327. rules := []rbacv1.PolicyRule{}
  328. for _, g := range sets.StringKeySet(groupResourceMapping).List() {
  329. rule := rbacv1.PolicyRule{}
  330. rule.Verbs = verbs
  331. rule.Resources = groupResourceMapping[g]
  332. rule.APIGroups = []string{g}
  333. rule.ResourceNames = resourceNames
  334. rules = append(rules, rule)
  335. }
  336. if len(nonResourceURLs) > 0 {
  337. rule := rbacv1.PolicyRule{}
  338. rule.Verbs = verbs
  339. rule.NonResourceURLs = nonResourceURLs
  340. rules = append(rules, rule)
  341. }
  342. return rules, nil
  343. }