/builtin/logical/rabbitmq/path_role_create.go

https://github.com/hashicorp/vault · Go · 200 lines · 166 code · 22 blank · 12 comment · 51 complexity · 15982965cd9d806830b631558fb7be43 MD5 · raw file

  1. package rabbitmq
  2. import (
  3. "context"
  4. "fmt"
  5. "io/ioutil"
  6. "github.com/hashicorp/go-uuid"
  7. "github.com/hashicorp/vault/sdk/framework"
  8. "github.com/hashicorp/vault/sdk/logical"
  9. rabbithole "github.com/michaelklishin/rabbit-hole"
  10. )
  11. func pathCreds(b *backend) *framework.Path {
  12. return &framework.Path{
  13. Pattern: "creds/" + framework.GenericNameRegex("name"),
  14. Fields: map[string]*framework.FieldSchema{
  15. "name": &framework.FieldSchema{
  16. Type: framework.TypeString,
  17. Description: "Name of the role.",
  18. },
  19. },
  20. Callbacks: map[logical.Operation]framework.OperationFunc{
  21. logical.ReadOperation: b.pathCredsRead,
  22. },
  23. HelpSynopsis: pathRoleCreateReadHelpSyn,
  24. HelpDescription: pathRoleCreateReadHelpDesc,
  25. }
  26. }
  27. // Issues the credential based on the role name
  28. func (b *backend) pathCredsRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
  29. name := d.Get("name").(string)
  30. if name == "" {
  31. return logical.ErrorResponse("missing name"), nil
  32. }
  33. // Get the role
  34. role, err := b.Role(ctx, req.Storage, name)
  35. if err != nil {
  36. return nil, err
  37. }
  38. if role == nil {
  39. return logical.ErrorResponse(fmt.Sprintf("unknown role: %s", name)), nil
  40. }
  41. // Ensure username is unique
  42. uuidVal, err := uuid.GenerateUUID()
  43. if err != nil {
  44. return nil, err
  45. }
  46. username := fmt.Sprintf("%s-%s", req.DisplayName, uuidVal)
  47. config, err := readConfig(ctx, req.Storage)
  48. if err != nil {
  49. return nil, fmt.Errorf("unable to read configuration: %w", err)
  50. }
  51. password, err := b.generatePassword(ctx, config.PasswordPolicy)
  52. if err != nil {
  53. return nil, err
  54. }
  55. // Get the client configuration
  56. client, err := b.Client(ctx, req.Storage)
  57. if err != nil {
  58. return nil, err
  59. }
  60. if client == nil {
  61. return logical.ErrorResponse("failed to get the client"), nil
  62. }
  63. // Register the generated credentials in the backend, with the RabbitMQ server
  64. resp, err := client.PutUser(username, rabbithole.UserSettings{
  65. Password: password,
  66. Tags: role.Tags,
  67. })
  68. if err != nil {
  69. return nil, fmt.Errorf("failed to create a new user with the generated credentials")
  70. }
  71. defer func() {
  72. if err := resp.Body.Close(); err != nil {
  73. b.Logger().Error(fmt.Sprintf("unable to close response body: %s", err))
  74. }
  75. }()
  76. if !isIn200s(resp.StatusCode) {
  77. body, _ := ioutil.ReadAll(resp.Body)
  78. return nil, fmt.Errorf("error creating user %s - %d: %s", username, resp.StatusCode, body)
  79. }
  80. success := false
  81. defer func() {
  82. if success {
  83. return
  84. }
  85. // Delete the user because it's in an unknown state.
  86. resp, err := client.DeleteUser(username)
  87. if err != nil {
  88. b.Logger().Error(fmt.Sprintf("deleting %s due to permissions being in an unknown state, but failed: %s", username, err))
  89. }
  90. if !isIn200s(resp.StatusCode) {
  91. body, _ := ioutil.ReadAll(resp.Body)
  92. b.Logger().Error(fmt.Sprintf("deleting %s due to permissions being in an unknown state, but error deleting: %d: %s", username, resp.StatusCode, body))
  93. }
  94. }()
  95. // If the role had vhost permissions specified, assign those permissions
  96. // to the created username for respective vhosts.
  97. for vhost, permission := range role.VHosts {
  98. if err := func() error {
  99. resp, err := client.UpdatePermissionsIn(vhost, username, rabbithole.Permissions{
  100. Configure: permission.Configure,
  101. Write: permission.Write,
  102. Read: permission.Read,
  103. })
  104. if err != nil {
  105. return err
  106. }
  107. defer func() {
  108. if err := resp.Body.Close(); err != nil {
  109. b.Logger().Error(fmt.Sprintf("unable to close response body: %s", err))
  110. }
  111. }()
  112. if !isIn200s(resp.StatusCode) {
  113. body, _ := ioutil.ReadAll(resp.Body)
  114. return fmt.Errorf("error updating vhost permissions for %s - %d: %s", vhost, resp.StatusCode, body)
  115. }
  116. return nil
  117. }(); err != nil {
  118. return nil, err
  119. }
  120. }
  121. // If the role had vhost topic permissions specified, assign those permissions
  122. // to the created username for respective vhosts and exchange.
  123. for vhost, permissions := range role.VHostTopics {
  124. for exchange, permission := range permissions {
  125. if err := func() error {
  126. resp, err := client.UpdateTopicPermissionsIn(vhost, username, rabbithole.TopicPermissions{
  127. Exchange: exchange,
  128. Write: permission.Write,
  129. Read: permission.Read,
  130. })
  131. if err != nil {
  132. return err
  133. }
  134. defer func() {
  135. if err := resp.Body.Close(); err != nil {
  136. b.Logger().Error(fmt.Sprintf("unable to close response body: %s", err))
  137. }
  138. }()
  139. if !isIn200s(resp.StatusCode) {
  140. body, _ := ioutil.ReadAll(resp.Body)
  141. return fmt.Errorf("error updating vhost permissions for %s - %d: %s", vhost, resp.StatusCode, body)
  142. }
  143. return nil
  144. }(); err != nil {
  145. return nil, err
  146. }
  147. }
  148. }
  149. success = true
  150. // Return the secret
  151. response := b.Secret(SecretCredsType).Response(map[string]interface{}{
  152. "username": username,
  153. "password": password,
  154. }, map[string]interface{}{
  155. "username": username,
  156. })
  157. // Determine if we have a lease
  158. lease, err := b.Lease(ctx, req.Storage)
  159. if err != nil {
  160. return nil, err
  161. }
  162. if lease != nil {
  163. response.Secret.TTL = lease.TTL
  164. response.Secret.MaxTTL = lease.MaxTTL
  165. }
  166. return response, nil
  167. }
  168. func isIn200s(respStatus int) bool {
  169. return respStatus >= 200 && respStatus < 300
  170. }
  171. const pathRoleCreateReadHelpSyn = `
  172. Request RabbitMQ credentials for a certain role.
  173. `
  174. const pathRoleCreateReadHelpDesc = `
  175. This path reads RabbitMQ credentials for a certain role. The
  176. RabbitMQ credentials will be generated on demand and will be automatically
  177. revoked when the lease is up.
  178. `