vault.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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. func (c *Client) WriteAzureCredential(
  117. azIntegration *integrations.AzureIntegration,
  118. data *credentials.AzureCredential) error {
  119. reqData := &CreateVaultSecretRequest{
  120. Data: data,
  121. }
  122. return c.postRequest(fmt.Sprintf("/v1/%s", c.getAzureCredentialPath(azIntegration)), reqData, nil)
  123. }
  124. func (c *Client) GetAzureCredential(azIntegration *integrations.AzureIntegration) (*credentials.AzureCredential, error) {
  125. resp := &GetAzureCredentialResponse{}
  126. err := c.getRequest(fmt.Sprintf("/v1/%s", c.getAzureCredentialPath(azIntegration)), resp)
  127. if err != nil {
  128. return nil, err
  129. }
  130. return resp.Data.Data, nil
  131. }
  132. func (c *Client) CreateAzureToken(azIntegration *integrations.AzureIntegration) (string, error) {
  133. credPath := c.getAzureCredentialPath(azIntegration)
  134. policyName := fmt.Sprintf("access-%d-azure-%d", azIntegration.ProjectID, azIntegration.ID)
  135. return c.getToken(credPath, policyName)
  136. }
  137. func (c *Client) getAzureCredentialPath(azIntegration *integrations.AzureIntegration) string {
  138. return fmt.Sprintf(
  139. "kv/data/secret/%s/%d/azure/%d",
  140. c.secretPrefix,
  141. azIntegration.ProjectID,
  142. azIntegration.ID,
  143. )
  144. }
  145. const readOnlyPolicyTemplate = `path "%s" {
  146. capabilities = ["read"]
  147. }`
  148. func (c *Client) getToken(credPath, policyName string) (string, error) {
  149. policy := fmt.Sprintf(readOnlyPolicyTemplate, credPath)
  150. policyReq := &CreatePolicyRequest{
  151. Policy: policy,
  152. }
  153. err := c.postRequest(fmt.Sprintf("/v1/sys/policy/%s", policyName), policyReq, nil)
  154. if err != nil {
  155. return "", err
  156. }
  157. tokenReq := &CreateTokenRequest{
  158. Policies: []string{policyName},
  159. Meta: Meta{
  160. User: policyName,
  161. },
  162. TTL: "6h",
  163. }
  164. tokenResp := &CreateTokenResponse{}
  165. err = c.postRequest("/v1/auth/token/create", tokenReq, tokenResp)
  166. if err != nil {
  167. return "", err
  168. }
  169. return tokenResp.Auth.Token, nil
  170. }
  171. func (c *Client) postRequest(path string, data interface{}, dst interface{}) error {
  172. return c.writeRequest("POST", path, data, dst)
  173. }
  174. func (c *Client) putRequest(path string, data interface{}, dst interface{}) error {
  175. return c.writeRequest("PUT", path, data, dst)
  176. }
  177. func (c *Client) deleteRequest(path string, data interface{}, dst interface{}) error {
  178. return c.writeRequest("DELETE", path, data, dst)
  179. }
  180. func (c *Client) getRequest(path string, dst interface{}) error {
  181. reqURL, err := url.Parse(c.serverURL)
  182. if err != nil {
  183. return nil
  184. }
  185. reqURL.Path = path
  186. req, err := http.NewRequest(
  187. "GET",
  188. reqURL.String(),
  189. nil,
  190. )
  191. if err != nil {
  192. return err
  193. }
  194. req.Header.Set("Content-Type", "application/json; charset=utf-8")
  195. req.Header.Set("Accept", "application/json; charset=utf-8")
  196. req.Header.Set("X-Vault-Token", c.apiKey)
  197. res, err := c.httpClient.Do(req)
  198. if err != nil {
  199. return err
  200. }
  201. defer res.Body.Close()
  202. if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
  203. resBytes, err := ioutil.ReadAll(res.Body)
  204. if err != nil {
  205. return fmt.Errorf("request failed with status code %d, but could not read body (%s)\n", res.StatusCode, err.Error())
  206. }
  207. return fmt.Errorf("request failed with status code %d: %s\n", res.StatusCode, string(resBytes))
  208. }
  209. if dst != nil {
  210. return json.NewDecoder(res.Body).Decode(dst)
  211. }
  212. return nil
  213. }
  214. func (c *Client) writeRequest(method, path string, data interface{}, dst interface{}) error {
  215. reqURL, err := url.Parse(c.serverURL)
  216. if err != nil {
  217. return nil
  218. }
  219. reqURL.Path = path
  220. var strData []byte
  221. if data != nil {
  222. strData, err = json.Marshal(data)
  223. if err != nil {
  224. return err
  225. }
  226. }
  227. req, err := http.NewRequest(
  228. method,
  229. reqURL.String(),
  230. strings.NewReader(string(strData)),
  231. )
  232. if err != nil {
  233. return err
  234. }
  235. req.Header.Set("Content-Type", "application/json; charset=utf-8")
  236. req.Header.Set("Accept", "application/json; charset=utf-8")
  237. req.Header.Set("X-Vault-Token", c.apiKey)
  238. res, err := c.httpClient.Do(req)
  239. if err != nil {
  240. return err
  241. }
  242. defer res.Body.Close()
  243. if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
  244. resBytes, err := ioutil.ReadAll(res.Body)
  245. if err != nil {
  246. return fmt.Errorf("request failed with status code %d, but could not read body (%s)\n", res.StatusCode, err.Error())
  247. }
  248. return fmt.Errorf("request failed with status code %d: %s\n", res.StatusCode, string(resBytes))
  249. }
  250. if dst != nil {
  251. return json.NewDecoder(res.Body).Decode(dst)
  252. }
  253. return nil
  254. }