| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- /*
- Copyright © 2025 ESO Maintainer Team
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- https://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- // Package alibaba provides an implementation to interact with the Alibaba Cloud KMS and Secrets Manager.
- package alibaba
- import (
- "context"
- "errors"
- "fmt"
- "net/http"
- "net/url"
- "runtime"
- "strings"
- "time"
- openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
- kms "github.com/alibabacloud-go/kms-20160120/v3/client"
- openapiutil "github.com/alibabacloud-go/openapi-util/service"
- util "github.com/alibabacloud-go/tea-utils/v2/service"
- "github.com/alibabacloud-go/tea/tea"
- "github.com/hashicorp/go-retryablehttp"
- "github.com/external-secrets/external-secrets/runtime/esutils"
- )
- const (
- kmsAPIVersion = "2016-01-20"
- )
- // SecretsManagerClient defines the interface for interacting with the Alibaba Cloud Secrets Manager service.
- type SecretsManagerClient interface {
- GetSecretValue(
- ctx context.Context,
- request *kms.GetSecretValueRequest,
- ) (*kms.GetSecretValueResponseBody, error)
- Endpoint() string
- }
- type secretsManagerClient struct {
- config *openapi.Config
- options *util.RuntimeOptions
- endpoint string
- client *http.Client
- }
- var _ SecretsManagerClient = (*secretsManagerClient)(nil)
- func newClient(config *openapi.Config, options *util.RuntimeOptions) (*secretsManagerClient, error) {
- kmsClient, err := kms.NewClient(config)
- if err != nil {
- return nil, fmt.Errorf("failed to create Alibaba KMS client: %w", err)
- }
- endpoint, err := kmsClient.GetEndpoint(tea.String("kms"), kmsClient.RegionId, kmsClient.EndpointRule, kmsClient.Network, kmsClient.Suffix, kmsClient.EndpointMap, kmsClient.Endpoint)
- if err != nil {
- return nil, fmt.Errorf("failed to get KMS endpoint: %w", err)
- }
- if esutils.Deref(endpoint) == "" {
- return nil, errors.New("error KMS endpoint is missing")
- }
- const readWriteTimeoutSec = 60
- retryClient := retryablehttp.NewClient()
- retryClient.CheckRetry = retryablehttp.ErrorPropagatedRetryPolicy
- retryClient.Backoff = retryablehttp.DefaultBackoff
- retryClient.Logger = log
- retryClient.HTTPClient = &http.Client{
- Timeout: time.Second * time.Duration(readWriteTimeoutSec),
- }
- const defaultRetryAttempts = 3
- if esutils.Deref(options.Autoretry) {
- if options.MaxAttempts != nil {
- retryClient.RetryMax = esutils.Deref(options.MaxAttempts)
- } else {
- retryClient.RetryMax = defaultRetryAttempts
- }
- }
- return &secretsManagerClient{
- config: config,
- options: options,
- endpoint: esutils.Deref(endpoint),
- client: retryClient.StandardClient(),
- }, nil
- }
- func (s *secretsManagerClient) Endpoint() string {
- return s.endpoint
- }
- func (s *secretsManagerClient) GetSecretValue(
- ctx context.Context,
- request *kms.GetSecretValueRequest,
- ) (*kms.GetSecretValueResponseBody, error) {
- resp, err := s.doAPICall(ctx, "GetSecretValue", request)
- if err != nil {
- return nil, fmt.Errorf("error getting secret [%s] latest value: %w", esutils.Deref(request.SecretName), err)
- }
- body, err := esutils.ConvertToType[kms.GetSecretValueResponseBody](resp)
- if err != nil {
- return nil, fmt.Errorf("error converting body: %w", err)
- }
- return &body, nil
- }
- func (s *secretsManagerClient) doAPICall(ctx context.Context,
- action string,
- request any) (any, error) {
- creds, err := s.config.Credential.GetCredential()
- if err != nil {
- return nil, fmt.Errorf("could not get credentials: %w", err)
- }
- apiRequest := newOpenAPIRequest(s.endpoint, action, methodTypeGET, request)
- apiRequest.query["AccessKeyId"] = creds.AccessKeyId
- if esutils.Deref(creds.SecurityToken) != "" {
- apiRequest.query["SecurityToken"] = creds.SecurityToken
- }
- apiRequest.query["Signature"] = openapiutil.GetRPCSignature(apiRequest.query, esutils.Ptr(apiRequest.method.String()), creds.AccessKeySecret)
- httpReq, err := newHTTPRequestWithContext(ctx, apiRequest)
- if err != nil {
- return nil, fmt.Errorf("error creating http request: %w", err)
- }
- resp, err := s.client.Do(httpReq)
- if err != nil {
- return nil, fmt.Errorf("error invoking http request: %w", err)
- }
- defer func() {
- _ = resp.Body.Close()
- }()
- return s.parseResponse(resp)
- }
- func (s *secretsManagerClient) parseResponse(resp *http.Response) (map[string]any, error) {
- statusCode := esutils.Ptr(resp.StatusCode)
- if esutils.Deref(util.Is4xx(statusCode)) || esutils.Deref(util.Is5xx(statusCode)) {
- return nil, s.parseErrorResponse(resp)
- }
- obj, err := util.ReadAsJSON(resp.Body)
- if err != nil {
- return nil, err
- }
- res, err := util.AssertAsMap(obj)
- if err != nil {
- return nil, err
- }
- return res, nil
- }
- func (s *secretsManagerClient) parseErrorResponse(resp *http.Response) error {
- res, err := util.ReadAsJSON(resp.Body)
- if err != nil {
- return err
- }
- errorMap, err := util.AssertAsMap(res)
- if err != nil {
- return err
- }
- errorMap["statusCode"] = esutils.Ptr(resp.StatusCode)
- err = tea.NewSDKError(map[string]any{
- "code": tea.ToString(defaultAny(errorMap["Code"], errorMap["code"])),
- "message": fmt.Sprintf("code: %s, %s", tea.ToString(resp.StatusCode), tea.ToString(defaultAny(errorMap["Message"], errorMap["message"]))),
- "data": errorMap,
- "description": tea.ToString(defaultAny(errorMap["Description"], errorMap["description"])),
- "accessDeniedDetail": errorMap["AccessDeniedDetail"],
- })
- return err
- }
- type methodType string
- const (
- methodTypeGET = "GET"
- )
- func (m methodType) String() string {
- return string(m)
- }
- type openAPIRequest struct {
- endpoint string
- method methodType
- headers map[string]*string
- query map[string]*string
- }
- func newOpenAPIRequest(endpoint string,
- action string,
- method methodType,
- request any,
- ) *openAPIRequest {
- req := &openAPIRequest{
- endpoint: endpoint,
- method: method,
- headers: map[string]*string{
- "host": &endpoint,
- "x-acs-version": esutils.Ptr(kmsAPIVersion),
- "x-acs-action": &action,
- "user-agent": esutils.Ptr(fmt.Sprintf("AlibabaCloud (%s; %s) Golang/%s Core/%s TeaDSL/1", runtime.GOOS, runtime.GOARCH, strings.Trim(runtime.Version(), "go"), "0.01")),
- },
- query: map[string]*string{
- "Action": &action,
- "Format": esutils.Ptr("json"),
- "Version": esutils.Ptr(kmsAPIVersion),
- "Timestamp": openapiutil.GetTimestamp(),
- "SignatureNonce": util.GetNonce(),
- "SignatureMethod": esutils.Ptr("HMAC-SHA1"),
- "SignatureVersion": esutils.Ptr("1.0"),
- },
- }
- req.query = tea.Merge(req.query, openapiutil.Query(request))
- return req
- }
- func newHTTPRequestWithContext(ctx context.Context,
- req *openAPIRequest) (*http.Request, error) {
- query := url.Values{}
- for k, v := range req.query {
- query.Add(k, esutils.Deref(v))
- }
- httpReq, err := http.NewRequestWithContext(ctx, req.method.String(), fmt.Sprintf("https://%s/?%s", url.PathEscape(req.endpoint), query.Encode()), http.NoBody)
- if err != nil {
- return nil, fmt.Errorf("error converting OpenAPI request to http request: %w", err)
- }
- for k, v := range req.headers {
- httpReq.Header.Add(k, esutils.Deref(v))
- }
- return httpReq, nil
- }
- func defaultAny(inputValue, defaultValue any) any {
- if esutils.Deref(util.IsUnset(inputValue)) {
- return defaultValue
- }
- return inputValue
- }
|