client_get.go 7.1 KB

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