client_push.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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. "bytes"
  16. "context"
  17. "fmt"
  18. "strings"
  19. "github.com/cyberark/conjur-api-go/conjurapi"
  20. "github.com/doodlesbykumbi/conjur-policy-go/pkg/conjurpolicy"
  21. "gopkg.in/yaml.v3"
  22. corev1 "k8s.io/api/core/v1"
  23. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  24. "github.com/external-secrets/external-secrets/runtime/esutils"
  25. )
  26. const (
  27. // AnnotationManagedByKey is the key for the annotation used to denote that a conjur resource is managed by external-secrets.
  28. AnnotationManagedByKey = "managed-by"
  29. // AnnotationManagedByValue is the value for the annotation used to denote that a conjur resource is managed by external-secrets.
  30. AnnotationManagedByValue = "external-secrets"
  31. )
  32. func conjurPolicy(name string, vars []string) ([]byte, error) {
  33. pvars := make([]conjurpolicy.Resource, len(vars))
  34. permits := make([]conjurpolicy.Resource, len(vars))
  35. for i, v := range vars {
  36. pvars[i] = conjurpolicy.Variable{
  37. Id: v,
  38. Annotations: map[string]any{
  39. AnnotationManagedByKey: AnnotationManagedByValue,
  40. },
  41. }
  42. permits[i] = conjurpolicy.Permit{
  43. Resources: conjurpolicy.VariableRef(v),
  44. Role: conjurpolicy.GroupRef("delegation/consumers"),
  45. Privileges: []conjurpolicy.Privilege{conjurpolicy.PrivilegeRead, conjurpolicy.PrivilegeExecute},
  46. }
  47. }
  48. p := conjurpolicy.Policy{
  49. Id: name,
  50. Body: []conjurpolicy.Resource{
  51. conjurpolicy.Group{
  52. Id: "delegation/consumers",
  53. Annotations: map[string]any{
  54. AnnotationManagedByKey: AnnotationManagedByValue,
  55. // Allow authorized users to manage the members of this group
  56. // https://docs.cyberark.com/secrets-manager-sh/12.7/en/content/operations/policy/annotations-conjur.htm#Editableannotationongroupsandlayers
  57. "editable": "true",
  58. },
  59. },
  60. },
  61. }
  62. p.Body = append(p.Body, pvars...)
  63. p.Body = append(p.Body, permits...)
  64. policy, err := yaml.Marshal(conjurpolicy.PolicyStatements{p})
  65. if err != nil {
  66. return nil, err
  67. }
  68. return policy, nil
  69. }
  70. // PushSecret writes a single secret into the provider.
  71. func (c *Client) PushSecret(ctx context.Context, secret *corev1.Secret, ref esv1.PushSecretData) error {
  72. conjurClient, getConjurClientError := c.GetConjurClient(ctx)
  73. if getConjurClientError != nil {
  74. return getConjurClientError
  75. }
  76. values := map[string]string{}
  77. vars := []string{}
  78. key := ref.GetSecretKey()
  79. if key == "" {
  80. for k, v := range secret.Data {
  81. values[k] = string(v)
  82. vars = append(vars, k)
  83. }
  84. } else {
  85. value, ok := secret.Data[key]
  86. if !ok {
  87. return fmt.Errorf("key %q not found in source secret", key)
  88. }
  89. values[key] = string(value)
  90. vars = append(vars, key)
  91. }
  92. fqSecretName := ref.GetRemoteKey()
  93. // if property is empty, we should create multiple variables for each key of the secret
  94. property := ref.GetProperty()
  95. i := strings.LastIndex(fqSecretName, "/")
  96. if i == -1 {
  97. return fmt.Errorf("expected RemoteKey (%q) to contain a '/'", fqSecretName)
  98. }
  99. if property != "" {
  100. vars = []string{property}
  101. }
  102. parentPolicy := fqSecretName[0:i]
  103. policyName := fqSecretName[i+1:]
  104. // Before we apply policy, we should check any existing secrets to make sure that if they exist, they have the "managed-by" annotation
  105. // If they don't, we should leave them alone.
  106. // Also, any value that hasn't changed should be removed to avoid spurious updates
  107. updateVars, err := checkSecrets(conjurClient, fqSecretName, vars, values, property, key)
  108. if err != nil {
  109. return fmt.Errorf("failed to check remote secrets: %w", err)
  110. }
  111. // Nothing to update
  112. if len(updateVars) == 0 {
  113. return nil
  114. }
  115. policy, err := conjurPolicy(policyName, updateVars)
  116. if err != nil {
  117. return fmt.Errorf("failed to generate policy: %w", err)
  118. }
  119. _, err = conjurClient.LoadPolicy(conjurapi.PolicyModePost, parentPolicy, bytes.NewReader(policy))
  120. if err != nil {
  121. return fmt.Errorf("failed to load policy: %w", err)
  122. }
  123. // if we're not given a property, store all the secrets under the k8s secret key
  124. if property == "" {
  125. for _, k := range updateVars {
  126. err = conjurClient.AddSecret(fmt.Sprintf("%s/%s", fqSecretName, k), values[k])
  127. if err != nil {
  128. return fmt.Errorf("failed to store secret: %w", err)
  129. }
  130. }
  131. }
  132. // if we have a property and a single k8s secret key, store it "as is"
  133. if property != "" && key != "" {
  134. err = conjurClient.AddSecret(fmt.Sprintf("%s/%s", fqSecretName, property), values[key])
  135. if err != nil {
  136. return fmt.Errorf("failed to store secret: %w", err)
  137. }
  138. } else if property != "" && key == "" {
  139. // if we have a property, and all the k8s secret fields, store it as a json obj.
  140. value, err := esutils.JSONMarshal(values)
  141. if err != nil {
  142. return fmt.Errorf("failed to json encode secret: %w", err)
  143. }
  144. err = conjurClient.AddSecret(fmt.Sprintf("%s/%s", fqSecretName, property), string(value))
  145. if err != nil {
  146. return fmt.Errorf("failed to store secret: %w", err)
  147. }
  148. }
  149. return nil
  150. }
  151. // checkSecrets checks if secrets exists, if they are managed by eso, and if the values are different.
  152. // Returns the set of secrets that we should update/create.
  153. func checkSecrets(conjurClient SecretsClient, conjurSecretName string, conjurVars []string, secretData map[string]string, property, key string) ([]string, error) {
  154. updateVars := []string{}
  155. for _, v := range conjurVars {
  156. n := fmt.Sprintf("%s/%s", conjurSecretName, v)
  157. resp, err := conjurClient.GetStaticSecretDetails(n)
  158. if err != nil {
  159. // assume doesn't exist, so we should create it
  160. // Could also be no permission - but that just looks like not found, and then it should fail when we try to create it
  161. updateVars = append(updateVars, v)
  162. continue
  163. }
  164. found := false
  165. for ak, av := range resp.Annotations {
  166. if ak == AnnotationManagedByKey && av == AnnotationManagedByValue {
  167. found = true
  168. break
  169. }
  170. }
  171. if found == false {
  172. continue
  173. }
  174. secret, err := conjurClient.RetrieveSecret(n)
  175. // if we can't read the secret value, assume it's out of our control, don't update it.
  176. if err != nil {
  177. continue
  178. }
  179. secretValue := string(secret)
  180. if property != "" {
  181. // if property and key are present, just a value check
  182. if key != "" {
  183. if secretValue == secretData[key] {
  184. continue
  185. }
  186. } else {
  187. value, err := esutils.JSONMarshal(secretData)
  188. if err != nil {
  189. return nil, err
  190. }
  191. if secretValue == string(value) {
  192. continue
  193. }
  194. }
  195. } else {
  196. if secretValue == secretData[v] {
  197. continue
  198. }
  199. }
  200. updateVars = append(updateVars, v)
  201. }
  202. return updateVars, nil
  203. }