client.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package alibaba
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. "net/http"
  19. "net/url"
  20. "runtime"
  21. "strings"
  22. "time"
  23. openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
  24. kms "github.com/alibabacloud-go/kms-20160120/v3/client"
  25. openapiutil "github.com/alibabacloud-go/openapi-util/service"
  26. util "github.com/alibabacloud-go/tea-utils/v2/service"
  27. "github.com/alibabacloud-go/tea/tea"
  28. "github.com/hashicorp/go-retryablehttp"
  29. "github.com/external-secrets/external-secrets/pkg/utils"
  30. )
  31. const (
  32. kmsAPIVersion = "2016-01-20"
  33. )
  34. type SecretsManagerClient interface {
  35. GetSecretValue(
  36. ctx context.Context,
  37. request *kms.GetSecretValueRequest,
  38. ) (*kms.GetSecretValueResponseBody, error)
  39. Endpoint() string
  40. }
  41. type secretsManagerClient struct {
  42. config *openapi.Config
  43. options *util.RuntimeOptions
  44. endpoint string
  45. client *http.Client
  46. }
  47. var _ SecretsManagerClient = (*secretsManagerClient)(nil)
  48. func newClient(config *openapi.Config, options *util.RuntimeOptions) (*secretsManagerClient, error) {
  49. kmsClient, err := kms.NewClient(config)
  50. if err != nil {
  51. return nil, fmt.Errorf("failed to create Alibaba KMS client: %w", err)
  52. }
  53. endpoint, err := kmsClient.GetEndpoint(tea.String("kms"), kmsClient.RegionId, kmsClient.EndpointRule, kmsClient.Network, kmsClient.Suffix, kmsClient.EndpointMap, kmsClient.Endpoint)
  54. if err != nil {
  55. return nil, fmt.Errorf("failed to get KMS endpoint: %w", err)
  56. }
  57. if utils.Deref(endpoint) == "" {
  58. return nil, errors.New("error KMS endpoint is missing")
  59. }
  60. const (
  61. connectTimeoutSec = 30
  62. readWriteTimeoutSec = 60
  63. )
  64. retryClient := retryablehttp.NewClient()
  65. retryClient.CheckRetry = retryablehttp.ErrorPropagatedRetryPolicy
  66. retryClient.Backoff = retryablehttp.DefaultBackoff
  67. retryClient.Logger = log
  68. retryClient.HTTPClient = &http.Client{
  69. Timeout: time.Second * time.Duration(readWriteTimeoutSec),
  70. }
  71. const defaultRetryAttempts = 3
  72. if utils.Deref(options.Autoretry) {
  73. if options.MaxAttempts != nil {
  74. retryClient.RetryMax = utils.Deref(options.MaxAttempts)
  75. } else {
  76. retryClient.RetryMax = defaultRetryAttempts
  77. }
  78. }
  79. return &secretsManagerClient{
  80. config: config,
  81. options: options,
  82. endpoint: utils.Deref(endpoint),
  83. client: retryClient.StandardClient(),
  84. }, nil
  85. }
  86. func (s *secretsManagerClient) Endpoint() string {
  87. return s.endpoint
  88. }
  89. func (s *secretsManagerClient) GetSecretValue(
  90. ctx context.Context,
  91. request *kms.GetSecretValueRequest,
  92. ) (*kms.GetSecretValueResponseBody, error) {
  93. resp, err := s.doAPICall(ctx, "GetSecretValue", request)
  94. if err != nil {
  95. return nil, fmt.Errorf("error getting secret [%s] latest value: %w", utils.Deref(request.SecretName), err)
  96. }
  97. body, err := utils.ConvertToType[kms.GetSecretValueResponseBody](resp)
  98. if err != nil {
  99. return nil, fmt.Errorf("error converting body: %w", err)
  100. }
  101. return &body, nil
  102. }
  103. func (s *secretsManagerClient) doAPICall(ctx context.Context,
  104. action string,
  105. request any) (any, error) {
  106. creds, err := s.config.Credential.GetCredential()
  107. if err != nil {
  108. return nil, fmt.Errorf("could not get credentials: %w", err)
  109. }
  110. apiRequest := newOpenAPIRequest(s.endpoint, action, methodTypeGET, request)
  111. apiRequest.query["AccessKeyId"] = creds.AccessKeyId
  112. if utils.Deref(creds.SecurityToken) != "" {
  113. apiRequest.query["SecurityToken"] = creds.SecurityToken
  114. }
  115. apiRequest.query["Signature"] = openapiutil.GetRPCSignature(apiRequest.query, utils.Ptr(apiRequest.method.String()), creds.AccessKeySecret)
  116. httpReq, err := newHTTPRequestWithContext(ctx, apiRequest)
  117. if err != nil {
  118. return nil, fmt.Errorf("error creating http request: %w", err)
  119. }
  120. resp, err := s.client.Do(httpReq)
  121. if err != nil {
  122. return nil, fmt.Errorf("error invoking http request: %w", err)
  123. }
  124. defer func() {
  125. _ = resp.Body.Close()
  126. }()
  127. return s.parseResponse(resp)
  128. }
  129. func (s *secretsManagerClient) parseResponse(resp *http.Response) (map[string]any, error) {
  130. statusCode := utils.Ptr(resp.StatusCode)
  131. if utils.Deref(util.Is4xx(statusCode)) || utils.Deref(util.Is5xx(statusCode)) {
  132. return nil, s.parseErrorResponse(resp)
  133. }
  134. obj, err := util.ReadAsJSON(resp.Body)
  135. if err != nil {
  136. return nil, err
  137. }
  138. res, err := util.AssertAsMap(obj)
  139. if err != nil {
  140. return nil, err
  141. }
  142. return res, nil
  143. }
  144. func (s *secretsManagerClient) parseErrorResponse(resp *http.Response) error {
  145. res, err := util.ReadAsJSON(resp.Body)
  146. if err != nil {
  147. return err
  148. }
  149. errorMap, err := util.AssertAsMap(res)
  150. if err != nil {
  151. return err
  152. }
  153. errorMap["statusCode"] = utils.Ptr(resp.StatusCode)
  154. err = tea.NewSDKError(map[string]any{
  155. "code": tea.ToString(defaultAny(errorMap["Code"], errorMap["code"])),
  156. "message": fmt.Sprintf("code: %s, %s", tea.ToString(resp.StatusCode), tea.ToString(defaultAny(errorMap["Message"], errorMap["message"]))),
  157. "data": errorMap,
  158. "description": tea.ToString(defaultAny(errorMap["Description"], errorMap["description"])),
  159. "accessDeniedDetail": errorMap["AccessDeniedDetail"],
  160. })
  161. return err
  162. }
  163. type methodType string
  164. const (
  165. methodTypeGET = "GET"
  166. )
  167. func (m methodType) String() string {
  168. return string(m)
  169. }
  170. type openAPIRequest struct {
  171. endpoint string
  172. method methodType
  173. headers map[string]*string
  174. query map[string]*string
  175. }
  176. func newOpenAPIRequest(endpoint string,
  177. action string,
  178. method methodType,
  179. request any,
  180. ) *openAPIRequest {
  181. req := &openAPIRequest{
  182. endpoint: endpoint,
  183. method: method,
  184. headers: map[string]*string{
  185. "host": &endpoint,
  186. "x-acs-version": utils.Ptr(kmsAPIVersion),
  187. "x-acs-action": &action,
  188. "user-agent": utils.Ptr(fmt.Sprintf("AlibabaCloud (%s; %s) Golang/%s Core/%s TeaDSL/1", runtime.GOOS, runtime.GOARCH, strings.Trim(runtime.Version(), "go"), "0.01")),
  189. },
  190. query: map[string]*string{
  191. "Action": &action,
  192. "Format": utils.Ptr("json"),
  193. "Version": utils.Ptr(kmsAPIVersion),
  194. "Timestamp": openapiutil.GetTimestamp(),
  195. "SignatureNonce": util.GetNonce(),
  196. "SignatureMethod": utils.Ptr("HMAC-SHA1"),
  197. "SignatureVersion": utils.Ptr("1.0"),
  198. },
  199. }
  200. req.query = tea.Merge(req.query, openapiutil.Query(request))
  201. return req
  202. }
  203. func newHTTPRequestWithContext(ctx context.Context,
  204. req *openAPIRequest) (*http.Request, error) {
  205. query := url.Values{}
  206. for k, v := range req.query {
  207. query.Add(k, utils.Deref(v))
  208. }
  209. httpReq, err := http.NewRequestWithContext(ctx, req.method.String(), fmt.Sprintf("https://%s/?%s", url.PathEscape(req.endpoint), query.Encode()), http.NoBody)
  210. if err != nil {
  211. return nil, fmt.Errorf("error converting OpenAPI request to http request: %w", err)
  212. }
  213. for k, v := range req.headers {
  214. httpReq.Header.Add(k, utils.Deref(v))
  215. }
  216. return httpReq, nil
  217. }
  218. func defaultAny(inputValue, defaultValue any) any {
  219. if utils.Deref(util.IsUnset(inputValue)) {
  220. return defaultValue
  221. }
  222. return inputValue
  223. }