vault.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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,
  62. ) error {
  63. reqData := &CreateVaultSecretRequest{
  64. Data: data,
  65. }
  66. return c.postRequest(fmt.Sprintf("/v1/%s", c.getGCPCredentialPath(gcpIntegration)), reqData, nil)
  67. }
  68. func (c *Client) GetGCPCredential(gcpIntegration *integrations.GCPIntegration) (*credentials.GCPCredential, error) {
  69. resp := &GetGCPCredentialResponse{}
  70. err := c.getRequest(fmt.Sprintf("/v1/%s", c.getGCPCredentialPath(gcpIntegration)), resp)
  71. if err != nil {
  72. return nil, err
  73. }
  74. return resp.Data.Data, nil
  75. }
  76. func (c *Client) CreateGCPToken(gcpIntegration *integrations.GCPIntegration) (string, error) {
  77. credPath := c.getGCPCredentialPath(gcpIntegration)
  78. policyName := fmt.Sprintf("access-%d-gcp-%d", gcpIntegration.ProjectID, gcpIntegration.ID)
  79. return c.getToken(credPath, policyName)
  80. }
  81. func (c *Client) getGCPCredentialPath(gcpIntegration *integrations.GCPIntegration) string {
  82. return fmt.Sprintf(
  83. "kv/data/secret/%s/%d/gcp/%d",
  84. c.secretPrefix,
  85. gcpIntegration.ProjectID,
  86. gcpIntegration.ID,
  87. )
  88. }
  89. func (c *Client) WriteAWSCredential(
  90. awsIntegration *integrations.AWSIntegration,
  91. data *credentials.AWSCredential,
  92. ) error {
  93. reqData := &CreateVaultSecretRequest{
  94. Data: data,
  95. }
  96. return c.postRequest(fmt.Sprintf("/v1/%s", c.getAWSCredentialPath(awsIntegration)), reqData, nil)
  97. }
  98. func (c *Client) GetAWSCredential(awsIntegration *integrations.AWSIntegration) (*credentials.AWSCredential, error) {
  99. resp := &GetAWSCredentialResponse{}
  100. err := c.getRequest(fmt.Sprintf("/v1/%s", c.getAWSCredentialPath(awsIntegration)), resp)
  101. if err != nil {
  102. return nil, err
  103. }
  104. return resp.Data.Data, nil
  105. }
  106. func (c *Client) CreateAWSToken(awsIntegration *integrations.AWSIntegration) (string, error) {
  107. credPath := c.getAWSCredentialPath(awsIntegration)
  108. policyName := fmt.Sprintf("access-%d-aws-%d", awsIntegration.ProjectID, awsIntegration.ID)
  109. return c.getToken(credPath, policyName)
  110. }
  111. func (c *Client) getAWSCredentialPath(awsIntegration *integrations.AWSIntegration) string {
  112. return fmt.Sprintf(
  113. "kv/data/secret/%s/%d/aws/%d",
  114. c.secretPrefix,
  115. awsIntegration.ProjectID,
  116. awsIntegration.ID,
  117. )
  118. }
  119. func (c *Client) WriteAzureCredential(
  120. azIntegration *integrations.AzureIntegration,
  121. data *credentials.AzureCredential,
  122. ) error {
  123. reqData := &CreateVaultSecretRequest{
  124. Data: data,
  125. }
  126. return c.postRequest(fmt.Sprintf("/v1/%s", c.getAzureCredentialPath(azIntegration)), reqData, nil)
  127. }
  128. func (c *Client) GetAzureCredential(azIntegration *integrations.AzureIntegration) (*credentials.AzureCredential, error) {
  129. resp := &GetAzureCredentialResponse{}
  130. err := c.getRequest(fmt.Sprintf("/v1/%s", c.getAzureCredentialPath(azIntegration)), resp)
  131. if err != nil {
  132. return nil, err
  133. }
  134. return resp.Data.Data, nil
  135. }
  136. func (c *Client) CreateAzureToken(azIntegration *integrations.AzureIntegration) (string, error) {
  137. credPath := c.getAzureCredentialPath(azIntegration)
  138. policyName := fmt.Sprintf("access-%d-azure-%d", azIntegration.ProjectID, azIntegration.ID)
  139. return c.getToken(credPath, policyName)
  140. }
  141. func (c *Client) getAzureCredentialPath(azIntegration *integrations.AzureIntegration) string {
  142. return fmt.Sprintf(
  143. "kv/data/secret/%s/%d/azure/%d",
  144. c.secretPrefix,
  145. azIntegration.ProjectID,
  146. azIntegration.ID,
  147. )
  148. }
  149. func (c *Client) WriteGitlabCredential(giIntegration *integrations.GitlabIntegration, data *credentials.GitlabCredential) error {
  150. reqData := &CreateVaultSecretRequest{
  151. Data: data,
  152. }
  153. return c.postRequest(fmt.Sprintf("/v1/%s", c.getGitlabCredentialPath(giIntegration)), reqData, nil)
  154. }
  155. func (c *Client) GetGitlabCredential(
  156. giIntegration *integrations.GitlabIntegration,
  157. ) (*credentials.GitlabCredential, error) {
  158. resp := &GetGitlabCredentialResponse{}
  159. err := c.getRequest(fmt.Sprintf("/v1/%s", c.getGitlabCredentialPath(giIntegration)), resp)
  160. if err != nil {
  161. return nil, err
  162. }
  163. return resp.Data.Data, nil
  164. }
  165. func (c *Client) CreateGitlabToken(giIntegration *integrations.GitlabIntegration) (string, error) {
  166. panic("not implemented")
  167. }
  168. func (c *Client) getGitlabCredentialPath(giIntegration *integrations.GitlabIntegration) string {
  169. return fmt.Sprintf(
  170. "kv/data/secret/%s/%d/gitlab/%d",
  171. c.secretPrefix,
  172. giIntegration.ProjectID,
  173. giIntegration.ID,
  174. )
  175. }
  176. const readOnlyPolicyTemplate = `path "%s" {
  177. capabilities = ["read"]
  178. }`
  179. func (c *Client) getToken(credPath, policyName string) (string, error) {
  180. policy := fmt.Sprintf(readOnlyPolicyTemplate, credPath)
  181. policyReq := &CreatePolicyRequest{
  182. Policy: policy,
  183. }
  184. err := c.postRequest(fmt.Sprintf("/v1/sys/policy/%s", policyName), policyReq, nil)
  185. if err != nil {
  186. return "", err
  187. }
  188. tokenReq := &CreateTokenRequest{
  189. Policies: []string{policyName},
  190. Meta: Meta{
  191. User: policyName,
  192. },
  193. TTL: "6h",
  194. }
  195. tokenResp := &CreateTokenResponse{}
  196. err = c.postRequest("/v1/auth/token/create", tokenReq, tokenResp)
  197. if err != nil {
  198. return "", err
  199. }
  200. return tokenResp.Auth.Token, nil
  201. }
  202. func (c *Client) postRequest(path string, data interface{}, dst interface{}) error {
  203. return c.writeRequest("POST", path, data, dst)
  204. }
  205. func (c *Client) putRequest(path string, data interface{}, dst interface{}) error {
  206. return c.writeRequest("PUT", path, data, dst)
  207. }
  208. func (c *Client) deleteRequest(path string, data interface{}, dst interface{}) error {
  209. return c.writeRequest("DELETE", path, data, dst)
  210. }
  211. func (c *Client) getRequest(path string, dst interface{}) error {
  212. reqURL, err := url.Parse(c.serverURL)
  213. if err != nil {
  214. return nil
  215. }
  216. reqURL.Path = path
  217. req, err := http.NewRequest(
  218. "GET",
  219. reqURL.String(),
  220. nil,
  221. )
  222. if err != nil {
  223. return err
  224. }
  225. req.Header.Set("Content-Type", "application/json; charset=utf-8")
  226. req.Header.Set("Accept", "application/json; charset=utf-8")
  227. req.Header.Set("X-Vault-Token", c.apiKey)
  228. res, err := c.httpClient.Do(req)
  229. if err != nil {
  230. return err
  231. }
  232. defer res.Body.Close()
  233. if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
  234. resBytes, err := ioutil.ReadAll(res.Body)
  235. if err != nil {
  236. return fmt.Errorf("request failed with status code %d, but could not read body (%s)\n", res.StatusCode, err.Error())
  237. }
  238. return fmt.Errorf("request failed with status code %d: %s\n", res.StatusCode, string(resBytes))
  239. }
  240. if dst != nil {
  241. return json.NewDecoder(res.Body).Decode(dst)
  242. }
  243. return nil
  244. }
  245. func (c *Client) writeRequest(method, path string, data interface{}, dst interface{}) error {
  246. reqURL, err := url.Parse(c.serverURL)
  247. if err != nil {
  248. return nil
  249. }
  250. reqURL.Path = path
  251. var strData []byte
  252. if data != nil {
  253. strData, err = json.Marshal(data)
  254. if err != nil {
  255. return err
  256. }
  257. }
  258. req, err := http.NewRequest(
  259. method,
  260. reqURL.String(),
  261. strings.NewReader(string(strData)),
  262. )
  263. if err != nil {
  264. return err
  265. }
  266. req.Header.Set("Content-Type", "application/json; charset=utf-8")
  267. req.Header.Set("Accept", "application/json; charset=utf-8")
  268. req.Header.Set("X-Vault-Token", c.apiKey)
  269. res, err := c.httpClient.Do(req)
  270. if err != nil {
  271. return err
  272. }
  273. defer res.Body.Close()
  274. if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
  275. resBytes, err := ioutil.ReadAll(res.Body)
  276. if err != nil {
  277. return fmt.Errorf("request failed with status code %d, but could not read body (%s)\n", res.StatusCode, err.Error())
  278. }
  279. return fmt.Errorf("request failed with status code %d: %s\n", res.StatusCode, string(resBytes))
  280. }
  281. if dst != nil {
  282. return json.NewDecoder(res.Body).Decode(dst)
  283. }
  284. return nil
  285. }