client.go 5.7 KB

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