client.go 6.2 KB

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