client.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. package hashicorp
  2. import (
  3. "bytes"
  4. "encoding/base64"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "git.aetherial.dev/aeth/yosai/pkg/daemon"
  10. "git.aetherial.dev/aeth/yosai/pkg/keytags"
  11. )
  12. const (
  13. SecretsApiPath = "v1/secret/data"
  14. )
  15. type VaultAdd struct {
  16. Data map[string]string `json:"data"`
  17. }
  18. type VaultResponse struct {
  19. Data VaultResponseInner `json:"data"`
  20. }
  21. type VaultResponseInner struct {
  22. Data VaultItem `json:"data"`
  23. }
  24. type VaultItem struct {
  25. Name string `json:"name"`
  26. Public string `json:"public"`
  27. Secret string `json:"secret"`
  28. Type string `json:"type"`
  29. }
  30. type VaultConnection struct {
  31. VaultUrl string
  32. HttpProto string
  33. KeyRing daemon.DaemonKeyRing
  34. Client *http.Client
  35. }
  36. func (v VaultItem) GetPublic() string { return v.Public }
  37. func (v VaultItem) GetSecret() string { return v.Secret }
  38. func (v VaultItem) GetType() string { return v.Type }
  39. func (v VaultItem) Prepare() string { return "Unimplemented method" }
  40. // Returns the 'public' field of the credential, i.e. a username or something
  41. func (v VaultResponse) GetPublic() string {
  42. return v.Data.Data.Public
  43. }
  44. // returns the 'private' field of the credential, like the API key or password
  45. func (v VaultResponse) GetSecret() string {
  46. return v.Data.Data.Secret
  47. }
  48. // this is an extra implementation so VaultResponse can implement the daemon.Key interface
  49. func (v VaultResponse) Prepare() string {
  50. if v.Data.Data.Type == "bearer" {
  51. return fmt.Sprintf("Bearer %s", v.GetSecret())
  52. }
  53. if v.Data.Data.Type == "basic" {
  54. encodedcreds := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", v.GetPublic(), v.GetSecret())))
  55. return fmt.Sprintf("Basic %s", encodedcreds)
  56. }
  57. return "CREDENTIAL TYPE INVALID"
  58. }
  59. /*
  60. Implementing the daemon.Key interface and returning the keys 'type'
  61. */
  62. func (v VaultResponse) GetType() string {
  63. return v.Data.Data.Type
  64. }
  65. /*
  66. Retrieve a key from hashicorp. the 2nd argument, 'name' is the path of the secret in hashicorp
  67. :param keyring: a daemon.DaemonKeyRing interface that will have the hashicorp API key
  68. :param name: the name of the secret in hashicorp. It will be injected as the 'path' in the API call,
  69. See the Hashicorp Vault documentation for details
  70. */
  71. func (v VaultConnection) GetKey(name string) (daemon.Key, error) {
  72. vaultBase := fmt.Sprintf("%s://%s/%s/%s", v.HttpProto, v.VaultUrl, SecretsApiPath, name)
  73. var vaultResp VaultResponse
  74. req, err := http.NewRequest("GET", vaultBase, nil)
  75. if err != nil {
  76. return vaultResp, err
  77. }
  78. req.Header.Add("Content-Type", "application/json")
  79. vaultApiKey, err := v.KeyRing.GetKey(keytags.HASHICORP_VAULT_KEYNAME)
  80. if err != nil {
  81. return vaultResp, err
  82. }
  83. req.Header.Add("Authorization", vaultApiKey.Prepare())
  84. resp, err := v.Client.Do(req)
  85. if err != nil {
  86. return vaultResp, err
  87. }
  88. defer resp.Body.Close()
  89. if resp.StatusCode != 200 {
  90. return vaultResp, daemon.KeyNotFound
  91. }
  92. b, err := io.ReadAll(resp.Body)
  93. if err != nil {
  94. return vaultResp, err
  95. }
  96. err = json.Unmarshal(b, &vaultResp)
  97. if err != nil {
  98. return vaultResp, err
  99. }
  100. return vaultResp, nil
  101. }
  102. /*
  103. Add the root users for your VPS to Hashicorp vault
  104. :param pass: the password to store in vault
  105. */
  106. func (v VaultConnection) AddKey(name string, key daemon.Key) error {
  107. body := VaultAdd{
  108. Data: map[string]string{"public": key.GetPublic(), "secret": key.GetSecret(), "type": key.GetType()},
  109. }
  110. b, err := json.Marshal(&body)
  111. if err != nil {
  112. return err
  113. }
  114. vaultBase := fmt.Sprintf("%s://%s/%s/%s", v.HttpProto, v.VaultUrl, SecretsApiPath, name)
  115. req, err := http.NewRequest("POST", vaultBase, bytes.NewReader(b))
  116. if err != nil {
  117. return err
  118. }
  119. req.Header.Add("Content-Type", "application/json")
  120. vaultApiKey, err := v.KeyRing.GetKey(keytags.HASHICORP_VAULT_KEYNAME)
  121. if err != nil {
  122. return err
  123. }
  124. req.Header.Add("Authorization", vaultApiKey.Prepare())
  125. resp, err := v.Client.Do(req)
  126. if err != nil {
  127. return err
  128. }
  129. defer resp.Body.Close()
  130. if resp.StatusCode != 200 {
  131. return &HashicorpClientError{Msg: resp.Status}
  132. }
  133. return nil
  134. }
  135. /*
  136. Removes a key from the vault
  137. :param name: the 'path' of the key as Hashicorp knows it
  138. */
  139. func (v VaultConnection) RemoveKey(name string) error {
  140. vaultBase := fmt.Sprintf("%s://%s/%s/%s", v.HttpProto, v.VaultUrl, SecretsApiPath, name)
  141. req, err := http.NewRequest("DELETE", vaultBase, nil)
  142. if err != nil {
  143. return err
  144. }
  145. vaultApiKey, err := v.KeyRing.GetKey(keytags.HASHICORP_VAULT_KEYNAME)
  146. if err != nil {
  147. return err
  148. }
  149. req.Header.Add("Authorization", vaultApiKey.Prepare())
  150. req.Header.Add("Content-Type", "application/json")
  151. resp, err := v.Client.Do(req)
  152. if err != nil {
  153. return err
  154. }
  155. defer resp.Body.Close()
  156. if resp.StatusCode != 200 {
  157. return &HashicorpClientError{Msg: resp.Status}
  158. }
  159. return nil
  160. }
  161. // Return the resource name for logging purposes
  162. func (v VaultConnection) Source() string {
  163. return "Hashicorp Vault"
  164. }
  165. /*
  166. Handles the routing for the hashicorp keyring routes
  167. :param msg: a daemon.SockMessage that contains request data
  168. */
  169. func (v VaultConnection) VaultRouter(msg daemon.SockMessage) daemon.SockMessage {
  170. switch msg.Method {
  171. case "add":
  172. var req VaultItem
  173. err := json.Unmarshal(msg.Body, &req)
  174. if err != nil {
  175. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  176. }
  177. err = v.AddKey(req.Name, req)
  178. if err != nil {
  179. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  180. }
  181. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, []byte("Key successfully added."))
  182. default:
  183. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_UNRESOLVED, []byte("Unresolvable method"))
  184. }
  185. }
  186. /*
  187. #####################
  188. ###### ERRORS #######
  189. #####################
  190. */
  191. type HashicorpClientError struct {
  192. Msg string
  193. }
  194. func (h *HashicorpClientError) Error() string {
  195. return fmt.Sprintf("There was an error with the client call: %s", h.Msg)
  196. }