role_commands.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. // Copyright 2015 The etcd Authors
  2. //
  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. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package command
  15. import (
  16. "fmt"
  17. "os"
  18. "reflect"
  19. "strings"
  20. "github.com/urfave/cli"
  21. "go.etcd.io/etcd/client"
  22. "go.etcd.io/etcd/pkg/pathutil"
  23. )
  24. func NewRoleCommands() cli.Command {
  25. return cli.Command{
  26. Name: "role",
  27. Usage: "role add, grant and revoke subcommands",
  28. Subcommands: []cli.Command{
  29. {
  30. Name: "add",
  31. Usage: "add a new role for the etcd cluster",
  32. ArgsUsage: "<role> ",
  33. Action: actionRoleAdd,
  34. },
  35. {
  36. Name: "get",
  37. Usage: "get details for a role",
  38. ArgsUsage: "<role>",
  39. Action: actionRoleGet,
  40. },
  41. {
  42. Name: "list",
  43. Usage: "list all roles",
  44. ArgsUsage: " ",
  45. Action: actionRoleList,
  46. },
  47. {
  48. Name: "remove",
  49. Usage: "remove a role from the etcd cluster",
  50. ArgsUsage: "<role>",
  51. Action: actionRoleRemove,
  52. },
  53. {
  54. Name: "grant",
  55. Usage: "grant path matches to an etcd role",
  56. ArgsUsage: "<role>",
  57. Flags: []cli.Flag{
  58. cli.StringFlag{Name: "path", Value: "", Usage: "Path granted for the role to access"},
  59. cli.BoolFlag{Name: "read", Usage: "Grant read-only access"},
  60. cli.BoolFlag{Name: "write", Usage: "Grant write-only access"},
  61. cli.BoolFlag{Name: "readwrite, rw", Usage: "Grant read-write access"},
  62. },
  63. Action: actionRoleGrant,
  64. },
  65. {
  66. Name: "revoke",
  67. Usage: "revoke path matches for an etcd role",
  68. ArgsUsage: "<role>",
  69. Flags: []cli.Flag{
  70. cli.StringFlag{Name: "path", Value: "", Usage: "Path revoked for the role to access"},
  71. cli.BoolFlag{Name: "read", Usage: "Revoke read access"},
  72. cli.BoolFlag{Name: "write", Usage: "Revoke write access"},
  73. cli.BoolFlag{Name: "readwrite, rw", Usage: "Revoke read-write access"},
  74. },
  75. Action: actionRoleRevoke,
  76. },
  77. },
  78. }
  79. }
  80. func mustNewAuthRoleAPI(c *cli.Context) client.AuthRoleAPI {
  81. hc := mustNewClient(c)
  82. if c.GlobalBool("debug") {
  83. fmt.Fprintf(os.Stderr, "Cluster-Endpoints: %s\n", strings.Join(hc.Endpoints(), ", "))
  84. }
  85. return client.NewAuthRoleAPI(hc)
  86. }
  87. func actionRoleList(c *cli.Context) error {
  88. if len(c.Args()) != 0 {
  89. fmt.Fprintln(os.Stderr, "No arguments accepted")
  90. os.Exit(1)
  91. }
  92. r := mustNewAuthRoleAPI(c)
  93. ctx, cancel := contextWithTotalTimeout(c)
  94. roles, err := r.ListRoles(ctx)
  95. cancel()
  96. if err != nil {
  97. fmt.Fprintln(os.Stderr, err.Error())
  98. os.Exit(1)
  99. }
  100. for _, role := range roles {
  101. fmt.Printf("%s\n", role)
  102. }
  103. return nil
  104. }
  105. func actionRoleAdd(c *cli.Context) error {
  106. api, role := mustRoleAPIAndName(c)
  107. ctx, cancel := contextWithTotalTimeout(c)
  108. defer cancel()
  109. currentRole, _ := api.GetRole(ctx, role)
  110. if currentRole != nil {
  111. fmt.Fprintf(os.Stderr, "Role %s already exists\n", role)
  112. os.Exit(1)
  113. }
  114. err := api.AddRole(ctx, role)
  115. if err != nil {
  116. fmt.Fprintln(os.Stderr, err.Error())
  117. os.Exit(1)
  118. }
  119. fmt.Printf("Role %s created\n", role)
  120. return nil
  121. }
  122. func actionRoleRemove(c *cli.Context) error {
  123. api, role := mustRoleAPIAndName(c)
  124. ctx, cancel := contextWithTotalTimeout(c)
  125. err := api.RemoveRole(ctx, role)
  126. cancel()
  127. if err != nil {
  128. fmt.Fprintln(os.Stderr, err.Error())
  129. os.Exit(1)
  130. }
  131. fmt.Printf("Role %s removed\n", role)
  132. return nil
  133. }
  134. func actionRoleGrant(c *cli.Context) error {
  135. roleGrantRevoke(c, true)
  136. return nil
  137. }
  138. func actionRoleRevoke(c *cli.Context) error {
  139. roleGrantRevoke(c, false)
  140. return nil
  141. }
  142. func roleGrantRevoke(c *cli.Context, grant bool) {
  143. path := c.String("path")
  144. if path == "" {
  145. fmt.Fprintln(os.Stderr, "No path specified; please use `--path`")
  146. os.Exit(1)
  147. }
  148. if pathutil.CanonicalURLPath(path) != path {
  149. fmt.Fprintf(os.Stderr, "Not canonical path; please use `--path=%s`\n", pathutil.CanonicalURLPath(path))
  150. os.Exit(1)
  151. }
  152. read := c.Bool("read")
  153. write := c.Bool("write")
  154. rw := c.Bool("readwrite")
  155. permcount := 0
  156. for _, v := range []bool{read, write, rw} {
  157. if v {
  158. permcount++
  159. }
  160. }
  161. if permcount != 1 {
  162. fmt.Fprintln(os.Stderr, "Please specify exactly one of --read, --write or --readwrite")
  163. os.Exit(1)
  164. }
  165. var permType client.PermissionType
  166. switch {
  167. case read:
  168. permType = client.ReadPermission
  169. case write:
  170. permType = client.WritePermission
  171. case rw:
  172. permType = client.ReadWritePermission
  173. }
  174. api, role := mustRoleAPIAndName(c)
  175. ctx, cancel := contextWithTotalTimeout(c)
  176. defer cancel()
  177. currentRole, err := api.GetRole(ctx, role)
  178. if err != nil {
  179. fmt.Fprintln(os.Stderr, err.Error())
  180. os.Exit(1)
  181. }
  182. var newRole *client.Role
  183. if grant {
  184. newRole, err = api.GrantRoleKV(ctx, role, []string{path}, permType)
  185. } else {
  186. newRole, err = api.RevokeRoleKV(ctx, role, []string{path}, permType)
  187. }
  188. if err != nil {
  189. fmt.Fprintln(os.Stderr, err.Error())
  190. os.Exit(1)
  191. }
  192. if reflect.DeepEqual(newRole, currentRole) {
  193. if grant {
  194. fmt.Printf("Role unchanged; already granted")
  195. } else {
  196. fmt.Printf("Role unchanged; already revoked")
  197. }
  198. }
  199. fmt.Printf("Role %s updated\n", role)
  200. }
  201. func actionRoleGet(c *cli.Context) error {
  202. api, rolename := mustRoleAPIAndName(c)
  203. ctx, cancel := contextWithTotalTimeout(c)
  204. role, err := api.GetRole(ctx, rolename)
  205. cancel()
  206. if err != nil {
  207. fmt.Fprintln(os.Stderr, err.Error())
  208. os.Exit(1)
  209. }
  210. fmt.Printf("Role: %s\n", role.Role)
  211. fmt.Printf("KV Read:\n")
  212. for _, v := range role.Permissions.KV.Read {
  213. fmt.Printf("\t%s\n", v)
  214. }
  215. fmt.Printf("KV Write:\n")
  216. for _, v := range role.Permissions.KV.Write {
  217. fmt.Printf("\t%s\n", v)
  218. }
  219. return nil
  220. }
  221. func mustRoleAPIAndName(c *cli.Context) (client.AuthRoleAPI, string) {
  222. args := c.Args()
  223. if len(args) != 1 {
  224. fmt.Fprintln(os.Stderr, "Please provide a role name")
  225. os.Exit(1)
  226. }
  227. name := args[0]
  228. api := mustNewAuthRoleAPI(c)
  229. return api, name
  230. }