client.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  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 secretmanager
  14. import (
  15. "context"
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "strconv"
  20. "strings"
  21. smsV2 "github.com/cloudru-tech/secret-manager-sdk/api/v2"
  22. "github.com/google/uuid"
  23. "github.com/tidwall/gjson"
  24. corev1 "k8s.io/api/core/v1"
  25. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  26. "github.com/external-secrets/external-secrets/pkg/provider/cloudru/secretmanager/adapter"
  27. "github.com/external-secrets/external-secrets/pkg/utils"
  28. )
  29. var (
  30. // ErrInvalidSecretVersion represents the error, when trying to access the secret with non-numeric version.
  31. ErrInvalidSecretVersion = errors.New("invalid secret version: should be a valid int32 value or 'latest' keyword")
  32. )
  33. // SecretProvider is an API client for the Cloud.ru Secret Manager.
  34. type SecretProvider interface {
  35. // ListSecrets lists secrets by the given request.
  36. ListSecrets(ctx context.Context, req *adapter.ListSecretsRequest) ([]*smsV2.Secret, error)
  37. // AccessSecretVersionByPath gets the secret by the given path.
  38. AccessSecretVersionByPath(ctx context.Context, projectID, path string, version *int32) ([]byte, error)
  39. // AccessSecretVersion gets the secret by the given request.
  40. AccessSecretVersion(ctx context.Context, id, version string) ([]byte, error)
  41. }
  42. // Client is a client for the Cloud.ru Secret Manager.
  43. type Client struct {
  44. apiClient SecretProvider
  45. projectID string
  46. }
  47. // GetSecret gets the secret by the remote reference.
  48. func (c *Client) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  49. secret, err := c.accessSecret(ctx, ref.Key, ref.Version)
  50. if err != nil {
  51. return nil, err
  52. }
  53. prop := strings.TrimSpace(ref.Property)
  54. if prop == "" {
  55. return secret, nil
  56. }
  57. // For more obvious behavior, we return an error if we are dealing with invalid JSON
  58. // this is needed, because the gjson library works fine with value for `key`, for example:
  59. //
  60. // {"key": "value", another: "value"}
  61. //
  62. // but it will return "" when accessing to a property `another` (no quotes)
  63. if err = json.Unmarshal(secret, &map[string]interface{}{}); err != nil {
  64. return nil, fmt.Errorf("expecting the secret %q in JSON format, could not access property %q", ref.Key, ref.Property)
  65. }
  66. result := gjson.Parse(string(secret)).Get(prop)
  67. if !result.Exists() {
  68. return nil, fmt.Errorf("the requested property %q does not exist in secret %q", prop, ref.Key)
  69. }
  70. return []byte(result.Str), nil
  71. }
  72. func (c *Client) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  73. secret, err := c.accessSecret(ctx, ref.Key, ref.Version)
  74. if err != nil {
  75. return nil, err
  76. }
  77. secretMap := make(map[string]json.RawMessage)
  78. if err = json.Unmarshal(secret, &secretMap); err != nil {
  79. return nil, fmt.Errorf("expecting the secret %q in JSON format", ref.Key)
  80. }
  81. out := make(map[string][]byte)
  82. for k, v := range secretMap {
  83. out[k] = []byte(strings.Trim(string(v), "\""))
  84. }
  85. return out, nil
  86. }
  87. // GetAllSecrets gets all secrets by the remote reference.
  88. func (c *Client) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  89. if len(ref.Tags) == 0 && ref.Name == nil {
  90. return nil, fmt.Errorf("at least one of the following fields must be set: tags, name")
  91. }
  92. var nameFilter string
  93. if ref.Name != nil {
  94. nameFilter = ref.Name.RegExp
  95. }
  96. searchReq := &adapter.ListSecretsRequest{
  97. ProjectID: c.projectID,
  98. Labels: ref.Tags,
  99. NameRegex: nameFilter,
  100. }
  101. secrets, err := c.apiClient.ListSecrets(ctx, searchReq)
  102. if err != nil {
  103. return nil, fmt.Errorf("failed to list secrets: %w", err)
  104. }
  105. out := make(map[string][]byte)
  106. for _, s := range secrets {
  107. secret, accessErr := c.accessSecret(ctx, s.GetId(), "latest")
  108. if accessErr != nil {
  109. return nil, accessErr
  110. }
  111. out[s.GetPath()] = secret
  112. }
  113. return utils.ConvertKeys(ref.ConversionStrategy, out)
  114. }
  115. func (c *Client) accessSecret(ctx context.Context, key, version string) ([]byte, error) {
  116. // check if the secret key is UUID
  117. // The uuid value means that the provided `key` is a secret identifier.
  118. // if not, then it is a secret name, and we need to get the secret by
  119. // name before accessing the version.
  120. if _, err := uuid.Parse(key); err != nil {
  121. var versionNum *int32
  122. if version != "" && version != "latest" {
  123. num, parseErr := strconv.ParseInt(version, 10, 32)
  124. if parseErr != nil {
  125. return nil, ErrInvalidSecretVersion
  126. }
  127. versionNum = &[]int32{int32(num)}[0]
  128. }
  129. return c.apiClient.AccessSecretVersionByPath(ctx, c.projectID, key, versionNum)
  130. }
  131. return c.apiClient.AccessSecretVersion(ctx, key, version)
  132. }
  133. func (c *Client) PushSecret(context.Context, *corev1.Secret, esv1.PushSecretData) error {
  134. return fmt.Errorf("push secret is not supported")
  135. }
  136. func (c *Client) DeleteSecret(context.Context, esv1.PushSecretRemoteRef) error {
  137. return fmt.Errorf("delete secret is not supported")
  138. }
  139. func (c *Client) SecretExists(context.Context, esv1.PushSecretRemoteRef) (bool, error) {
  140. return false, fmt.Errorf("secret exists is not supported")
  141. }
  142. // Validate validates the client.
  143. func (c *Client) Validate() (esv1.ValidationResult, error) {
  144. return esv1.ValidationResultUnknown, nil
  145. }
  146. // Close closes the client.
  147. func (c *Client) Close(_ context.Context) error { return nil }