api.go 6.7 KB

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