client_get.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  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 conjur
  13. import (
  14. "context"
  15. "encoding/json"
  16. "fmt"
  17. "strings"
  18. "github.com/cyberark/conjur-api-go/conjurapi"
  19. "github.com/tidwall/gjson"
  20. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  21. "github.com/external-secrets/external-secrets/pkg/find"
  22. )
  23. type conjurResource map[string]interface{}
  24. // resourceFilterFunc is a function that filters resources.
  25. // It takes a resource as input and returns the name of the resource if it should be included.
  26. // If the resource should not be included, it returns an empty string.
  27. // If an error occurs, it returns an empty string and the error.
  28. type resourceFilterFunc func(candidate conjurResource) (name string, err error)
  29. // GetSecret returns a single secret from the provider.
  30. func (c *Client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  31. conjurClient, getConjurClientError := c.GetConjurClient(ctx)
  32. if getConjurClientError != nil {
  33. return nil, getConjurClientError
  34. }
  35. secretValue, err := conjurClient.RetrieveSecret(ref.Key)
  36. if err != nil {
  37. return nil, err
  38. }
  39. // If no property is specified, return the secret value as is
  40. if ref.Property == "" {
  41. return secretValue, nil
  42. }
  43. // If a property is specified, parse the secret value as JSON and return the property value
  44. val := gjson.Get(string(secretValue), ref.Property)
  45. if !val.Exists() {
  46. return nil, fmt.Errorf(errSecretKeyFmt, ref.Property)
  47. }
  48. return []byte(val.String()), nil
  49. }
  50. // GetSecretMap returns multiple k/v pairs from the provider.
  51. func (c *Client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  52. // Gets a secret as normal, expecting secret value to be a json object
  53. data, err := c.GetSecret(ctx, ref)
  54. if err != nil {
  55. return nil, fmt.Errorf("error getting secret %s: %w", ref.Key, err)
  56. }
  57. // Maps the json data to a string:string map
  58. kv := make(map[string]string)
  59. err = json.Unmarshal(data, &kv)
  60. if err != nil {
  61. return nil, fmt.Errorf("unable to unmarshal secret %s: %w", ref.Key, err)
  62. }
  63. // Converts values in K:V pairs into bytes, while leaving keys as strings
  64. secretData := make(map[string][]byte)
  65. for k, v := range kv {
  66. secretData[k] = []byte(v)
  67. }
  68. return secretData, nil
  69. }
  70. // GetAllSecrets gets multiple secrets from the provider and loads into a kubernetes secret.
  71. // First load all secrets from secretStore path configuration
  72. // Then, gets secrets from a matching name or matching custom_metadata.
  73. func (c *Client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  74. if ref.Name != nil {
  75. return c.findSecretsFromName(ctx, *ref.Name)
  76. }
  77. return c.findSecretsFromTags(ctx, ref.Tags)
  78. }
  79. func (c *Client) findSecretsFromName(ctx context.Context, ref esv1beta1.FindName) (map[string][]byte, error) {
  80. matcher, err := find.New(ref)
  81. if err != nil {
  82. return nil, err
  83. }
  84. var resourceFilterFunc = func(candidate conjurResource) (string, error) {
  85. name := trimConjurResourceName(candidate["id"].(string))
  86. isMatch := matcher.MatchName(name)
  87. if !isMatch {
  88. return "", nil
  89. }
  90. return name, nil
  91. }
  92. return c.listSecrets(ctx, resourceFilterFunc)
  93. }
  94. func (c *Client) findSecretsFromTags(ctx context.Context, tags map[string]string) (map[string][]byte, error) {
  95. var resourceFilterFunc = func(candidate conjurResource) (string, error) {
  96. name := trimConjurResourceName(candidate["id"].(string))
  97. annotations, ok := candidate["annotations"].([]interface{})
  98. if !ok {
  99. // No annotations, skip
  100. return "", nil
  101. }
  102. formattedAnnotations, err := formatAnnotations(annotations)
  103. if err != nil {
  104. return "", err
  105. }
  106. // Check if all tags match
  107. for tk, tv := range tags {
  108. p, ok := formattedAnnotations[tk]
  109. if !ok || p != tv {
  110. return "", nil
  111. }
  112. }
  113. return name, nil
  114. }
  115. return c.listSecrets(ctx, resourceFilterFunc)
  116. }
  117. func (c *Client) listSecrets(ctx context.Context, filterFunc resourceFilterFunc) (map[string][]byte, error) {
  118. conjurClient, getConjurClientError := c.GetConjurClient(ctx)
  119. if getConjurClientError != nil {
  120. return nil, getConjurClientError
  121. }
  122. filteredResourceNames := []string{}
  123. // Loop through all secrets in the Conjur account.
  124. // Ideally this will be only a small list, but we need to handle pagination in the
  125. // case that there are a lot of secrets. To limit load on Conjur and memory usage
  126. // in ESO, we will only load 100 secrets at a time. We will then filter these secrets,
  127. // discarding any that do not match the filterFunc. We will then repeat this process
  128. // until we have loaded all secrets.
  129. for offset := 0; ; offset += 100 {
  130. resFilter := &conjurapi.ResourceFilter{
  131. Kind: "variable",
  132. Limit: 100,
  133. Offset: offset,
  134. }
  135. resources, err := conjurClient.Resources(resFilter)
  136. if err != nil {
  137. return nil, err
  138. }
  139. for _, candidate := range resources {
  140. name, err := filterFunc(candidate)
  141. if err != nil {
  142. return nil, err
  143. }
  144. if name != "" {
  145. filteredResourceNames = append(filteredResourceNames, name)
  146. }
  147. }
  148. // If we have less than 100 resources, we reached the last page
  149. if len(resources) < 100 {
  150. break
  151. }
  152. }
  153. filteredResources, err := c.client.RetrieveBatchSecrets(filteredResourceNames)
  154. if err != nil {
  155. return nil, err
  156. }
  157. // Trim the resource names to just the last part of the ID
  158. return trimConjurResourceNames(filteredResources), nil
  159. }
  160. // trimConjurResourceNames trims the Conjur resource names to the last part of the ID.
  161. // It iterates over a map of secrets and returns a new map with the trimmed names.
  162. func trimConjurResourceNames(resources map[string][]byte) map[string][]byte {
  163. trimmedResources := make(map[string][]byte)
  164. for k, v := range resources {
  165. trimmedResources[trimConjurResourceName(k)] = v
  166. }
  167. return trimmedResources
  168. }
  169. // trimConjurResourceName trims the Conjur resource name to the last part of the ID.
  170. // For example, if the ID is "account:variable:secret", the function will return
  171. // "secret".
  172. func trimConjurResourceName(id string) string {
  173. tokens := strings.SplitN(id, ":", 3)
  174. return tokens[len(tokens)-1]
  175. }
  176. // Convert annotations from objects with "name", "policy", "value" keys (as returned by the Conjur API)
  177. // to a key/value map for easier comparison in code.
  178. func formatAnnotations(annotations []interface{}) (map[string]string, error) {
  179. formattedAnnotations := make(map[string]string)
  180. for _, annot := range annotations {
  181. annot, ok := annot.(map[string]interface{})
  182. if !ok {
  183. return nil, fmt.Errorf("could not parse annotation: %v", annot)
  184. }
  185. name := annot["name"].(string)
  186. value := annot["value"].(string)
  187. formattedAnnotations[name] = value
  188. }
  189. return formattedAnnotations, nil
  190. }