client.go 7.5 KB

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