vault.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. // +build ee
  2. package vault
  3. import (
  4. "encoding/json"
  5. "fmt"
  6. "io/ioutil"
  7. "net/http"
  8. "net/url"
  9. "strings"
  10. "time"
  11. "github.com/porter-dev/porter/internal/models/integrations"
  12. "github.com/porter-dev/porter/internal/repository/credentials"
  13. )
  14. // Client contains an API client for IronPlans
  15. type Client struct {
  16. apiKey string
  17. serverURL string
  18. secretPrefix string
  19. httpClient *http.Client
  20. }
  21. // NewClient creates a new billing API client
  22. func NewClient(serverURL, apiKey, secretPrefix string) *Client {
  23. httpClient := &http.Client{
  24. Timeout: time.Minute,
  25. }
  26. return &Client{apiKey, serverURL, secretPrefix, httpClient}
  27. }
  28. func (c *Client) WriteOAuthCredential(
  29. oauthIntegration *integrations.OAuthIntegration,
  30. data *credentials.OAuthCredential,
  31. ) error {
  32. reqData := &CreateVaultSecretRequest{
  33. Data: data,
  34. }
  35. return c.postRequest(fmt.Sprintf("/v1/%s", c.getOAuthCredentialPath(oauthIntegration)), reqData, nil)
  36. }
  37. func (c *Client) GetOAuthCredential(oauthIntegration *integrations.OAuthIntegration) (*credentials.OAuthCredential, error) {
  38. resp := &GetOAuthCredentialResponse{}
  39. err := c.getRequest(fmt.Sprintf("/v1/%s", c.getOAuthCredentialPath(oauthIntegration)), resp)
  40. if err != nil {
  41. return nil, err
  42. }
  43. return resp.Data.Data, nil
  44. }
  45. func (c *Client) CreateOAuthToken(oauthIntegration *integrations.OAuthIntegration) (string, error) {
  46. credPath := c.getOAuthCredentialPath(oauthIntegration)
  47. policyName := fmt.Sprintf("access-%d-oauth-%d", oauthIntegration.ProjectID, oauthIntegration.ID)
  48. return c.getToken(credPath, policyName)
  49. }
  50. func (c *Client) getOAuthCredentialPath(oauthIntegration *integrations.OAuthIntegration) string {
  51. return fmt.Sprintf(
  52. "kv/data/secret/%s/%d/oauth/%d",
  53. c.secretPrefix,
  54. oauthIntegration.ProjectID,
  55. oauthIntegration.ID,
  56. )
  57. }
  58. func (c *Client) WriteGCPCredential(
  59. gcpIntegration *integrations.GCPIntegration,
  60. data *credentials.GCPCredential) error {
  61. reqData := &CreateVaultSecretRequest{
  62. Data: data,
  63. }
  64. return c.postRequest(fmt.Sprintf("/v1/%s", c.getGCPCredentialPath(gcpIntegration)), reqData, nil)
  65. }
  66. func (c *Client) GetGCPCredential(gcpIntegration *integrations.GCPIntegration) (*credentials.GCPCredential, error) {
  67. resp := &GetGCPCredentialResponse{}
  68. err := c.getRequest(fmt.Sprintf("/v1/%s", c.getGCPCredentialPath(gcpIntegration)), resp)
  69. if err != nil {
  70. return nil, err
  71. }
  72. return resp.Data.Data, nil
  73. }
  74. func (c *Client) CreateGCPToken(gcpIntegration *integrations.GCPIntegration) (string, error) {
  75. credPath := c.getGCPCredentialPath(gcpIntegration)
  76. policyName := fmt.Sprintf("access-%d-gcp-%d", gcpIntegration.ProjectID, gcpIntegration.ID)
  77. return c.getToken(credPath, policyName)
  78. }
  79. func (c *Client) getGCPCredentialPath(gcpIntegration *integrations.GCPIntegration) string {
  80. return fmt.Sprintf(
  81. "kv/data/secret/%s/%d/gcp/%d",
  82. c.secretPrefix,
  83. gcpIntegration.ProjectID,
  84. gcpIntegration.ID,
  85. )
  86. }
  87. func (c *Client) WriteAWSCredential(
  88. awsIntegration *integrations.AWSIntegration,
  89. data *credentials.AWSCredential) error {
  90. reqData := &CreateVaultSecretRequest{
  91. Data: data,
  92. }
  93. return c.postRequest(fmt.Sprintf("/v1/%s", c.getAWSCredentialPath(awsIntegration)), reqData, nil)
  94. }
  95. func (c *Client) GetAWSCredential(awsIntegration *integrations.AWSIntegration) (*credentials.AWSCredential, error) {
  96. resp := &GetAWSCredentialResponse{}
  97. err := c.getRequest(fmt.Sprintf("/v1/%s", c.getAWSCredentialPath(awsIntegration)), resp)
  98. if err != nil {
  99. return nil, err
  100. }
  101. return resp.Data.Data, nil
  102. }
  103. func (c *Client) CreateAWSToken(awsIntegration *integrations.AWSIntegration) (string, error) {
  104. credPath := c.getAWSCredentialPath(awsIntegration)
  105. policyName := fmt.Sprintf("access-%d-aws-%d", awsIntegration.ProjectID, awsIntegration.ID)
  106. return c.getToken(credPath, policyName)
  107. }
  108. func (c *Client) getAWSCredentialPath(awsIntegration *integrations.AWSIntegration) string {
  109. return fmt.Sprintf(
  110. "kv/data/secret/%s/%d/aws/%d",
  111. c.secretPrefix,
  112. awsIntegration.ProjectID,
  113. awsIntegration.ID,
  114. )
  115. }
  116. const readOnlyPolicyTemplate = `path "%s" {
  117. capabilities = ["read"]
  118. }`
  119. func (c *Client) getToken(credPath, policyName string) (string, error) {
  120. policy := fmt.Sprintf(readOnlyPolicyTemplate, credPath)
  121. policyReq := &CreatePolicyRequest{
  122. Policy: policy,
  123. }
  124. err := c.postRequest(fmt.Sprintf("/v1/sys/policy/%s", policyName), policyReq, nil)
  125. if err != nil {
  126. return "", err
  127. }
  128. tokenReq := &CreateTokenRequest{
  129. Policies: []string{policyName},
  130. Meta: Meta{
  131. User: policyName,
  132. },
  133. TTL: "6h",
  134. }
  135. tokenResp := &CreateTokenResponse{}
  136. err = c.postRequest("/v1/auth/token/create", tokenReq, tokenResp)
  137. if err != nil {
  138. return "", err
  139. }
  140. return tokenResp.Auth.Token, nil
  141. }
  142. func (c *Client) postRequest(path string, data interface{}, dst interface{}) error {
  143. return c.writeRequest("POST", path, data, dst)
  144. }
  145. func (c *Client) putRequest(path string, data interface{}, dst interface{}) error {
  146. return c.writeRequest("PUT", path, data, dst)
  147. }
  148. func (c *Client) deleteRequest(path string, data interface{}, dst interface{}) error {
  149. return c.writeRequest("DELETE", path, data, dst)
  150. }
  151. func (c *Client) getRequest(path string, dst interface{}) error {
  152. reqURL, err := url.Parse(c.serverURL)
  153. if err != nil {
  154. return nil
  155. }
  156. reqURL.Path = path
  157. req, err := http.NewRequest(
  158. "GET",
  159. reqURL.String(),
  160. nil,
  161. )
  162. if err != nil {
  163. return err
  164. }
  165. req.Header.Set("Content-Type", "application/json; charset=utf-8")
  166. req.Header.Set("Accept", "application/json; charset=utf-8")
  167. req.Header.Set("X-Vault-Token", c.apiKey)
  168. res, err := c.httpClient.Do(req)
  169. if err != nil {
  170. return err
  171. }
  172. defer res.Body.Close()
  173. if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
  174. resBytes, err := ioutil.ReadAll(res.Body)
  175. if err != nil {
  176. return fmt.Errorf("request failed with status code %d, but could not read body (%s)\n", res.StatusCode, err.Error())
  177. }
  178. return fmt.Errorf("request failed with status code %d: %s\n", res.StatusCode, string(resBytes))
  179. }
  180. if dst != nil {
  181. return json.NewDecoder(res.Body).Decode(dst)
  182. }
  183. return nil
  184. }
  185. func (c *Client) writeRequest(method, path string, data interface{}, dst interface{}) error {
  186. reqURL, err := url.Parse(c.serverURL)
  187. if err != nil {
  188. return nil
  189. }
  190. reqURL.Path = path
  191. var strData []byte
  192. if data != nil {
  193. strData, err = json.Marshal(data)
  194. if err != nil {
  195. return err
  196. }
  197. }
  198. req, err := http.NewRequest(
  199. method,
  200. reqURL.String(),
  201. strings.NewReader(string(strData)),
  202. )
  203. if err != nil {
  204. return err
  205. }
  206. req.Header.Set("Content-Type", "application/json; charset=utf-8")
  207. req.Header.Set("Accept", "application/json; charset=utf-8")
  208. req.Header.Set("X-Vault-Token", c.apiKey)
  209. res, err := c.httpClient.Do(req)
  210. if err != nil {
  211. return err
  212. }
  213. defer res.Body.Close()
  214. if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
  215. resBytes, err := ioutil.ReadAll(res.Body)
  216. if err != nil {
  217. return fmt.Errorf("request failed with status code %d, but could not read body (%s)\n", res.StatusCode, err.Error())
  218. }
  219. return fmt.Errorf("request failed with status code %d: %s\n", res.StatusCode, string(resBytes))
  220. }
  221. if dst != nil {
  222. return json.NewDecoder(res.Body).Decode(dst)
  223. }
  224. return nil
  225. }