client.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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 barbican client implementation.
  14. package barbican
  15. import (
  16. "context"
  17. "encoding/json"
  18. "errors"
  19. "fmt"
  20. "regexp"
  21. "strings"
  22. "github.com/gophercloud/gophercloud/v2"
  23. "github.com/gophercloud/gophercloud/v2/openstack/keymanager/v1/secrets"
  24. corev1 "k8s.io/api/core/v1"
  25. esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  26. )
  27. const (
  28. errClientGeneric = "barbican client: %w"
  29. errClientMissingField = "barbican client: missing field %w"
  30. errClientListAllSecrets = "barbican client: failed to list all secrets: %w"
  31. errClientExtractSecrets = "barbican client: failed to extract secrets: %w"
  32. errClientGetSecretPayload = "barbican client: failed to get secret payload: %w"
  33. errClientGetSecretPayloadProperty = "barbican client: failed to get secret payload property: %w"
  34. errClientJSONUnmarshal = "barbican client: failed to unmarshal json: %w"
  35. )
  36. var _ esapi.SecretsClient = &Client{}
  37. // Client is a Barbican secrets client.
  38. type Client struct {
  39. keyManager *gophercloud.ServiceClient
  40. }
  41. // GetAllSecrets retrieves all secrets matching the given name.
  42. func (c *Client) GetAllSecrets(ctx context.Context, ref esapi.ExternalSecretFind) (map[string][]byte, error) {
  43. if ref.Name == nil || ref.Name.RegExp == "" {
  44. return nil, fmt.Errorf(errClientMissingField, errors.New("name and/or regexp"))
  45. }
  46. // Barbican's list API only supports exact-name matching, so we can't push
  47. // the regexp down to the server. List everything and match client-side, the
  48. // same way the other ESO providers treat find.name.regexp.
  49. nameMatcher, err := regexp.Compile(ref.Name.RegExp)
  50. if err != nil {
  51. return nil, fmt.Errorf(errClientGeneric, fmt.Errorf("invalid name regexp %q: %w", ref.Name.RegExp, err))
  52. }
  53. allPages, err := secrets.List(c.keyManager, secrets.ListOpts{}).AllPages(ctx)
  54. if err != nil {
  55. return nil, fmt.Errorf(errClientListAllSecrets, err)
  56. }
  57. allSecrets, err := secrets.ExtractSecrets(allPages)
  58. if err != nil {
  59. return nil, fmt.Errorf(errClientExtractSecrets, err)
  60. }
  61. var secretsMap = make(map[string][]byte)
  62. for _, secret := range allSecrets {
  63. if !nameMatcher.MatchString(secret.Name) {
  64. continue
  65. }
  66. secretUUID := extractUUIDFromRef(secret.SecretRef)
  67. secretsMap[secretUUID], err = secrets.GetPayload(ctx, c.keyManager, secretUUID, nil).Extract()
  68. if err != nil {
  69. return nil, fmt.Errorf(errClientGetSecretPayload, fmt.Errorf("failed to get secret payload for secret %s: %w", secretUUID, err))
  70. }
  71. }
  72. if len(secretsMap) == 0 {
  73. return nil, fmt.Errorf(errClientGeneric, errors.New("no secrets found"))
  74. }
  75. return secretsMap, nil
  76. }
  77. // GetSecret retrieves a secret from Barbican.
  78. func (c *Client) GetSecret(ctx context.Context, ref esapi.ExternalSecretDataRemoteRef) ([]byte, error) {
  79. payload, err := secrets.GetPayload(ctx, c.keyManager, ref.Key, nil).Extract()
  80. if err != nil {
  81. return nil, fmt.Errorf(errClientGetSecretPayload, err)
  82. }
  83. if ref.Property == "" {
  84. return payload, nil
  85. }
  86. propertyValue, err := getSecretPayloadProperty(payload, ref.Property)
  87. if err != nil {
  88. return nil, fmt.Errorf(errClientGetSecretPayloadProperty, fmt.Errorf("failed to get property %s from secret payload: %w", ref.Property, err))
  89. }
  90. return propertyValue, nil
  91. }
  92. // GetSecretMap retrieves a secret and parses it as a JSON object.
  93. func (c *Client) GetSecretMap(ctx context.Context, ref esapi.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  94. payload, err := c.GetSecret(ctx, ref)
  95. if err != nil {
  96. return nil, fmt.Errorf(errClientGeneric, err)
  97. }
  98. var rawJSON map[string]json.RawMessage
  99. if err := json.Unmarshal(payload, &rawJSON); err != nil {
  100. return nil, fmt.Errorf(errClientJSONUnmarshal, err)
  101. }
  102. secretMap := make(map[string][]byte, len(rawJSON))
  103. for k, v := range rawJSON {
  104. secretMap[k] = []byte(v)
  105. }
  106. return secretMap, nil
  107. }
  108. // PushSecret is not implemented right now for Barbican.
  109. func (c *Client) PushSecret(_ context.Context, _ *corev1.Secret, _ esapi.PushSecretData) error {
  110. return fmt.Errorf("barbican provider does not support pushing secrets")
  111. }
  112. // SecretExists is not implemented right now for Barbican.
  113. func (c *Client) SecretExists(_ context.Context, _ esapi.PushSecretRemoteRef) (bool, error) {
  114. return false, errors.New("barbican provider does not support checking secret existence (read-only)")
  115. }
  116. // DeleteSecret is not implemented right now for Barbican.
  117. func (c *Client) DeleteSecret(_ context.Context, _ esapi.PushSecretRemoteRef) error {
  118. return fmt.Errorf("barbican provider does not support deleting secrets (delete policy Delete)")
  119. }
  120. // Validate checks if the client is properly configured.
  121. func (c *Client) Validate() (esapi.ValidationResult, error) {
  122. return esapi.ValidationResultUnknown, nil
  123. }
  124. // Close closes the client and any underlying connections.
  125. func (c *Client) Close(_ context.Context) error {
  126. return nil
  127. }
  128. // getSecretPayloadProperty extracts a property from a JSON payload.
  129. func getSecretPayloadProperty(payload []byte, property string) ([]byte, error) {
  130. if property == "" {
  131. return payload, nil
  132. }
  133. var rawJSON map[string]json.RawMessage
  134. if err := json.Unmarshal(payload, &rawJSON); err != nil {
  135. return nil, fmt.Errorf(errClientJSONUnmarshal, err)
  136. }
  137. value, ok := rawJSON[property]
  138. if !ok {
  139. return nil, fmt.Errorf(errClientGeneric, fmt.Errorf("property %s not found in secret payload", property))
  140. }
  141. return value, nil
  142. }
  143. // extractUUIDFromRef extracts the UUID from a Barbican secret reference URL.
  144. func extractUUIDFromRef(secretRef string) string {
  145. // Barbican secret refs are usually of the form: https://<endpoint>/v1/secrets/<uuid>
  146. // We'll just take the last part after the last '/'
  147. // If there's a trailing slash, the UUID part would be empty, so return empty string
  148. lastSlash := strings.LastIndex(secretRef, "/")
  149. if lastSlash > -1 {
  150. return secretRef[lastSlash+1:] // <- will not result in overflow even if it's the last `/`
  151. }
  152. return ""
  153. }