user_commands.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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. "strings"
  19. "github.com/bgentry/speakeasy"
  20. "github.com/coreos/etcd/client"
  21. "github.com/urfave/cli"
  22. )
  23. func NewUserCommands() cli.Command {
  24. return cli.Command{
  25. Name: "user",
  26. Usage: "user add, grant and revoke subcommands",
  27. Subcommands: []cli.Command{
  28. {
  29. Name: "add",
  30. Usage: "add a new user for the etcd cluster",
  31. ArgsUsage: "<user>",
  32. Action: actionUserAdd,
  33. },
  34. {
  35. Name: "get",
  36. Usage: "get details for a user",
  37. ArgsUsage: "<user>",
  38. Action: actionUserGet,
  39. },
  40. {
  41. Name: "list",
  42. Usage: "list all current users",
  43. ArgsUsage: "<user>",
  44. Action: actionUserList,
  45. },
  46. {
  47. Name: "remove",
  48. Usage: "remove a user for the etcd cluster",
  49. ArgsUsage: "<user>",
  50. Action: actionUserRemove,
  51. },
  52. {
  53. Name: "grant",
  54. Usage: "grant roles to an etcd user",
  55. ArgsUsage: "<user>",
  56. Flags: []cli.Flag{cli.StringSliceFlag{Name: "roles", Value: new(cli.StringSlice), Usage: "List of roles to grant or revoke"}},
  57. Action: actionUserGrant,
  58. },
  59. {
  60. Name: "revoke",
  61. Usage: "revoke roles for an etcd user",
  62. ArgsUsage: "<user>",
  63. Flags: []cli.Flag{cli.StringSliceFlag{Name: "roles", Value: new(cli.StringSlice), Usage: "List of roles to grant or revoke"}},
  64. Action: actionUserRevoke,
  65. },
  66. {
  67. Name: "passwd",
  68. Usage: "change password for a user",
  69. ArgsUsage: "<user>",
  70. Action: actionUserPasswd,
  71. },
  72. },
  73. }
  74. }
  75. func mustNewAuthUserAPI(c *cli.Context) client.AuthUserAPI {
  76. hc := mustNewClient(c)
  77. if c.GlobalBool("debug") {
  78. fmt.Fprintf(os.Stderr, "Cluster-Endpoints: %s\n", strings.Join(hc.Endpoints(), ", "))
  79. }
  80. return client.NewAuthUserAPI(hc)
  81. }
  82. func actionUserList(c *cli.Context) error {
  83. if len(c.Args()) != 0 {
  84. fmt.Fprintln(os.Stderr, "No arguments accepted")
  85. os.Exit(1)
  86. }
  87. u := mustNewAuthUserAPI(c)
  88. ctx, cancel := contextWithTotalTimeout(c)
  89. users, err := u.ListUsers(ctx)
  90. cancel()
  91. if err != nil {
  92. fmt.Fprintln(os.Stderr, err.Error())
  93. os.Exit(1)
  94. }
  95. for _, user := range users {
  96. fmt.Printf("%s\n", user)
  97. }
  98. return nil
  99. }
  100. func actionUserAdd(c *cli.Context) error {
  101. api, userarg := mustUserAPIAndName(c)
  102. ctx, cancel := contextWithTotalTimeout(c)
  103. defer cancel()
  104. user, _, _ := getUsernamePassword("", userarg+":")
  105. currentUser, err := api.GetUser(ctx, user)
  106. if currentUser != nil {
  107. fmt.Fprintf(os.Stderr, "User %s already exists\n", user)
  108. os.Exit(1)
  109. }
  110. _, pass, err := getUsernamePassword("New password: ", userarg)
  111. if err != nil {
  112. fmt.Fprintln(os.Stderr, "Error reading password:", err)
  113. os.Exit(1)
  114. }
  115. err = api.AddUser(ctx, user, pass)
  116. if err != nil {
  117. fmt.Fprintln(os.Stderr, err.Error())
  118. os.Exit(1)
  119. }
  120. fmt.Printf("User %s created\n", user)
  121. return nil
  122. }
  123. func actionUserRemove(c *cli.Context) error {
  124. api, user := mustUserAPIAndName(c)
  125. ctx, cancel := contextWithTotalTimeout(c)
  126. err := api.RemoveUser(ctx, user)
  127. cancel()
  128. if err != nil {
  129. fmt.Fprintln(os.Stderr, err.Error())
  130. os.Exit(1)
  131. }
  132. fmt.Printf("User %s removed\n", user)
  133. return nil
  134. }
  135. func actionUserPasswd(c *cli.Context) error {
  136. api, user := mustUserAPIAndName(c)
  137. ctx, cancel := contextWithTotalTimeout(c)
  138. defer cancel()
  139. currentUser, err := api.GetUser(ctx, user)
  140. if currentUser == nil {
  141. fmt.Fprintln(os.Stderr, err.Error())
  142. os.Exit(1)
  143. }
  144. pass, err := speakeasy.Ask("New password: ")
  145. if err != nil {
  146. fmt.Fprintln(os.Stderr, "Error reading password:", err)
  147. os.Exit(1)
  148. }
  149. _, err = api.ChangePassword(ctx, user, pass)
  150. if err != nil {
  151. fmt.Fprintln(os.Stderr, err.Error())
  152. os.Exit(1)
  153. }
  154. fmt.Printf("Password updated\n")
  155. return nil
  156. }
  157. func actionUserGrant(c *cli.Context) error {
  158. userGrantRevoke(c, true)
  159. return nil
  160. }
  161. func actionUserRevoke(c *cli.Context) error {
  162. userGrantRevoke(c, false)
  163. return nil
  164. }
  165. func userGrantRevoke(c *cli.Context, grant bool) {
  166. roles := c.StringSlice("roles")
  167. if len(roles) == 0 {
  168. fmt.Fprintln(os.Stderr, "No roles specified; please use `--roles`")
  169. os.Exit(1)
  170. }
  171. ctx, cancel := contextWithTotalTimeout(c)
  172. defer cancel()
  173. api, user := mustUserAPIAndName(c)
  174. currentUser, err := api.GetUser(ctx, user)
  175. if currentUser == nil {
  176. fmt.Fprintln(os.Stderr, err.Error())
  177. os.Exit(1)
  178. }
  179. if grant {
  180. _, err = api.GrantUser(ctx, user, roles)
  181. } else {
  182. _, err = api.RevokeUser(ctx, user, roles)
  183. }
  184. if err != nil {
  185. fmt.Fprintln(os.Stderr, err.Error())
  186. os.Exit(1)
  187. }
  188. fmt.Printf("User %s updated\n", user)
  189. }
  190. func actionUserGet(c *cli.Context) error {
  191. api, username := mustUserAPIAndName(c)
  192. ctx, cancel := contextWithTotalTimeout(c)
  193. user, err := api.GetUser(ctx, username)
  194. cancel()
  195. if err != nil {
  196. fmt.Fprintln(os.Stderr, err.Error())
  197. os.Exit(1)
  198. }
  199. fmt.Printf("User: %s\n", user.User)
  200. fmt.Printf("Roles: %s\n", strings.Join(user.Roles, " "))
  201. return nil
  202. }
  203. func mustUserAPIAndName(c *cli.Context) (client.AuthUserAPI, string) {
  204. args := c.Args()
  205. if len(args) != 1 {
  206. fmt.Fprintln(os.Stderr, "Please provide a username")
  207. os.Exit(1)
  208. }
  209. api := mustNewAuthUserAPI(c)
  210. username := args[0]
  211. return api, username
  212. }