client.go 6.3 KB

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