vault.go 9.1 KB

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