vault.go 9.1 KB

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