api.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliec.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package api
  13. import (
  14. "bytes"
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "net/http"
  19. "net/url"
  20. "time"
  21. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  22. "github.com/external-secrets/external-secrets/pkg/metrics"
  23. "github.com/external-secrets/external-secrets/pkg/provider/infisical/constants"
  24. )
  25. type InfisicalClient struct {
  26. BaseURL *url.URL
  27. client *http.Client
  28. token string
  29. }
  30. type InfisicalApis interface {
  31. MachineIdentityLoginViaUniversalAuth(data MachineIdentityUniversalAuthLoginRequest) (*MachineIdentityDetailsResponse, error)
  32. GetSecretsV3(data GetSecretsV3Request) (map[string]string, error)
  33. GetSecretByKeyV3(data GetSecretByKeyV3Request) (string, error)
  34. RevokeAccessToken() error
  35. }
  36. const UserAgentName = "k8-external-secrets-operator"
  37. const errJSONSecretUnmarshal = "unable to unmarshal secret: %w"
  38. func NewAPIClient(baseURL string) (*InfisicalClient, error) {
  39. baseParsedURL, err := url.Parse(baseURL)
  40. if err != nil {
  41. return nil, err
  42. }
  43. api := &InfisicalClient{
  44. BaseURL: baseParsedURL,
  45. client: &http.Client{
  46. Timeout: time.Second * 15,
  47. },
  48. }
  49. return api, nil
  50. }
  51. func (a *InfisicalClient) SetTokenViaMachineIdentity(clientID, clientSecret string) error {
  52. if a.token != "" {
  53. return nil
  54. }
  55. loginResponse, err := a.MachineIdentityLoginViaUniversalAuth(MachineIdentityUniversalAuthLoginRequest{
  56. ClientID: clientID,
  57. ClientSecret: clientSecret,
  58. })
  59. if err != nil {
  60. return err
  61. }
  62. a.token = loginResponse.AccessToken
  63. return nil
  64. }
  65. func (a *InfisicalClient) RevokeAccessToken() error {
  66. if a.token == "" {
  67. return nil
  68. }
  69. if _, err := a.RevokeMachineIdentityAccessToken(RevokeMachineIdentityAccessTokenRequest{AccessToken: a.token}); err != nil {
  70. return err
  71. }
  72. a.token = ""
  73. return nil
  74. }
  75. func (a *InfisicalClient) resolveEndpoint(path string) string {
  76. return a.BaseURL.ResolveReference(&url.URL{Path: path}).String()
  77. }
  78. func (a *InfisicalClient) do(r *http.Request) (*http.Response, error) {
  79. if a.token != "" {
  80. r.Header.Add("Authorization", "Bearer "+a.token)
  81. }
  82. r.Header.Add("User-Agent", UserAgentName)
  83. r.Header.Add("Content-Type", "application/json")
  84. return a.client.Do(r)
  85. }
  86. func (a *InfisicalClient) MachineIdentityLoginViaUniversalAuth(data MachineIdentityUniversalAuthLoginRequest) (*MachineIdentityDetailsResponse, error) {
  87. endpointURL := a.resolveEndpoint("api/v1/auth/universal-auth/login")
  88. body, err := MarshalReqBody(data)
  89. if err != nil {
  90. return nil, err
  91. }
  92. req, err := http.NewRequest(http.MethodPost, endpointURL, body)
  93. metrics.ObserveAPICall(constants.ProviderName, "MachineIdentityLoginViaUniversalAuth", err)
  94. if err != nil {
  95. return nil, err
  96. }
  97. rawRes, err := a.do(req)
  98. if err != nil {
  99. return nil, err
  100. }
  101. var res MachineIdentityDetailsResponse
  102. err = ReadAndUnmarshal(rawRes, &res)
  103. if err != nil {
  104. return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
  105. }
  106. return &res, nil
  107. }
  108. func (a *InfisicalClient) RevokeMachineIdentityAccessToken(data RevokeMachineIdentityAccessTokenRequest) (*RevokeMachineIdentityAccessTokenResponse, error) {
  109. endpointURL := a.resolveEndpoint("api/v1/auth/token/revoke")
  110. body, err := MarshalReqBody(data)
  111. if err != nil {
  112. return nil, err
  113. }
  114. req, err := http.NewRequest(http.MethodPost, endpointURL, body)
  115. metrics.ObserveAPICall(constants.ProviderName, "RevokeMachineIdentityAccessToken", err)
  116. if err != nil {
  117. return nil, err
  118. }
  119. rawRes, err := a.do(req)
  120. if err != nil {
  121. return nil, err
  122. }
  123. var res RevokeMachineIdentityAccessTokenResponse
  124. err = ReadAndUnmarshal(rawRes, &res)
  125. if err != nil {
  126. return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
  127. }
  128. return &res, nil
  129. }
  130. func (a *InfisicalClient) GetSecretsV3(data GetSecretsV3Request) (map[string]string, error) {
  131. endpointURL := a.resolveEndpoint("api/v3/secrets/raw")
  132. req, err := http.NewRequest(http.MethodGet, endpointURL, http.NoBody)
  133. metrics.ObserveAPICall(constants.ProviderName, "GetSecretsV3", err)
  134. if err != nil {
  135. return nil, err
  136. }
  137. q := req.URL.Query()
  138. q.Add("workspaceSlug", data.ProjectSlug)
  139. q.Add("environment", data.EnvironmentSlug)
  140. q.Add("secretPath", data.SecretPath)
  141. q.Add("include_imports", "true")
  142. q.Add("expandSecretReferences", "true")
  143. req.URL.RawQuery = q.Encode()
  144. rawRes, err := a.do(req)
  145. if err != nil {
  146. return nil, err
  147. }
  148. var res GetSecretsV3Response
  149. err = ReadAndUnmarshal(rawRes, &res)
  150. if err != nil {
  151. return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
  152. }
  153. secrets := make(map[string]string)
  154. for _, s := range res.ImportedSecrets {
  155. for _, el := range s.Secrets {
  156. secrets[el.SecretKey] = el.SecretValue
  157. }
  158. }
  159. for _, el := range res.Secrets {
  160. secrets[el.SecretKey] = el.SecretValue
  161. }
  162. return secrets, nil
  163. }
  164. func (a *InfisicalClient) GetSecretByKeyV3(data GetSecretByKeyV3Request) (string, error) {
  165. endpointURL := a.resolveEndpoint(fmt.Sprintf("api/v3/secrets/raw/%s", data.SecretKey))
  166. req, err := http.NewRequest(http.MethodGet, endpointURL, http.NoBody)
  167. metrics.ObserveAPICall(constants.ProviderName, "GetSecretByKeyV3", err)
  168. if err != nil {
  169. return "", err
  170. }
  171. q := req.URL.Query()
  172. q.Add("workspaceSlug", data.ProjectSlug)
  173. q.Add("environment", data.EnvironmentSlug)
  174. q.Add("secretPath", data.SecretPath)
  175. q.Add("include_imports", "true")
  176. req.URL.RawQuery = q.Encode()
  177. rawRes, err := a.do(req)
  178. if err != nil {
  179. return "", err
  180. }
  181. if rawRes.StatusCode == 400 {
  182. var errRes InfisicalAPIErrorResponse
  183. err = ReadAndUnmarshal(rawRes, &errRes)
  184. if err != nil {
  185. return "", fmt.Errorf(errJSONSecretUnmarshal, err)
  186. }
  187. if errRes.Message == "Secret not found" {
  188. return "", esv1beta1.NoSecretError{}
  189. }
  190. return "", errors.New(errRes.Message)
  191. }
  192. var res GetSecretByKeyV3Response
  193. err = ReadAndUnmarshal(rawRes, &res)
  194. if err != nil {
  195. return "", fmt.Errorf(errJSONSecretUnmarshal, err)
  196. }
  197. return res.Secret.SecretValue, nil
  198. }
  199. func MarshalReqBody(data any) (*bytes.Reader, error) {
  200. body, err := json.Marshal(data)
  201. if err != nil {
  202. return nil, err
  203. }
  204. return bytes.NewReader(body), nil
  205. }
  206. func ReadAndUnmarshal(resp *http.Response, target any) error {
  207. var buf bytes.Buffer
  208. defer resp.Body.Close()
  209. _, err := buf.ReadFrom(resp.Body)
  210. if err != nil {
  211. return err
  212. }
  213. return json.Unmarshal(buf.Bytes(), target)
  214. }