client_push.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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 vault
  13. import (
  14. "bytes"
  15. "context"
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. corev1 "k8s.io/api/core/v1"
  20. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  21. "github.com/external-secrets/external-secrets/pkg/constants"
  22. "github.com/external-secrets/external-secrets/pkg/metrics"
  23. "github.com/external-secrets/external-secrets/pkg/utils"
  24. )
  25. func (c *client) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1beta1.PushSecretData) error {
  26. var (
  27. value []byte
  28. err error
  29. )
  30. key := data.GetSecretKey()
  31. if key == "" {
  32. // Must convert secret values to string, otherwise data will be sent as base64 to Vault
  33. secretStringVal := make(map[string]string)
  34. for k, v := range secret.Data {
  35. secretStringVal[k] = string(v)
  36. }
  37. value, err = utils.JSONMarshal(secretStringVal)
  38. if err != nil {
  39. return fmt.Errorf("failed to serialize secret content as JSON: %w", err)
  40. }
  41. } else {
  42. value = secret.Data[key]
  43. }
  44. label := map[string]any{
  45. "custom_metadata": map[string]string{
  46. "managed-by": "external-secrets",
  47. },
  48. }
  49. secretVal := make(map[string]any)
  50. path := c.buildPath(data.GetRemoteKey())
  51. metaPath, err := c.buildMetadataPath(data.GetRemoteKey())
  52. if err != nil {
  53. return err
  54. }
  55. // Retrieve the secret map from vault and convert the secret value in string form.
  56. vaultSecret, err := c.readSecret(ctx, path, "")
  57. // If error is not of type secret not found, we should error
  58. if err != nil && !errors.Is(err, esv1beta1.NoSecretError{}) {
  59. return err
  60. }
  61. // If the secret exists (err == nil), we should check if it is managed by external-secrets
  62. if err == nil {
  63. metadata, err := c.readSecretMetadata(ctx, data.GetRemoteKey())
  64. if err != nil {
  65. return err
  66. }
  67. manager, ok := metadata["managed-by"]
  68. if !ok || manager != "external-secrets" {
  69. return fmt.Errorf("secret not managed by external-secrets")
  70. }
  71. }
  72. // Remove the metadata map to check the reconcile difference
  73. if c.store.Version == esv1beta1.VaultKVStoreV1 {
  74. delete(vaultSecret, "custom_metadata")
  75. }
  76. buf := &bytes.Buffer{}
  77. enc := json.NewEncoder(buf)
  78. enc.SetEscapeHTML(false)
  79. err = enc.Encode(vaultSecret)
  80. if err != nil {
  81. return fmt.Errorf("error encoding vault secret: %w", err)
  82. }
  83. vaultSecretValue := bytes.TrimSpace(buf.Bytes())
  84. if err != nil {
  85. return fmt.Errorf("error marshaling vault secret: %w", err)
  86. }
  87. if bytes.Equal(vaultSecretValue, value) {
  88. return nil
  89. }
  90. // If a Push of a property only, we should merge and add/update the property
  91. if data.GetProperty() != "" {
  92. if _, ok := vaultSecret[data.GetProperty()]; ok {
  93. d := vaultSecret[data.GetProperty()].(string)
  94. if err != nil {
  95. return fmt.Errorf("error marshaling vault secret: %w", err)
  96. }
  97. // If the property has the same value, don't update the secret
  98. if bytes.Equal([]byte(d), value) {
  99. return nil
  100. }
  101. }
  102. for k, v := range vaultSecret {
  103. secretVal[k] = v
  104. }
  105. // Secret got from vault is already on map[string]string format
  106. secretVal[data.GetProperty()] = string(value)
  107. } else {
  108. err = json.Unmarshal(value, &secretVal)
  109. if err != nil {
  110. return fmt.Errorf("error unmarshalling vault secret: %w", err)
  111. }
  112. }
  113. secretToPush := secretVal
  114. // Adding custom_metadata to the secret for KV v1
  115. if c.store.Version == esv1beta1.VaultKVStoreV1 {
  116. secretToPush["custom_metadata"] = label["custom_metadata"]
  117. }
  118. if c.store.Version == esv1beta1.VaultKVStoreV2 {
  119. secretToPush = map[string]any{
  120. "data": secretVal,
  121. }
  122. }
  123. if err != nil {
  124. return fmt.Errorf("failed to convert value to a valid JSON: %w", err)
  125. }
  126. // Secret metadata should be pushed separately only for KV2
  127. if c.store.Version == esv1beta1.VaultKVStoreV2 {
  128. _, err = c.logical.WriteWithContext(ctx, metaPath, label)
  129. metrics.ObserveAPICall(constants.ProviderHCVault, constants.CallHCVaultWriteSecretData, err)
  130. if err != nil {
  131. return err
  132. }
  133. }
  134. // Otherwise, create or update the version.
  135. _, err = c.logical.WriteWithContext(ctx, path, secretToPush)
  136. metrics.ObserveAPICall(constants.ProviderHCVault, constants.CallHCVaultWriteSecretData, err)
  137. return err
  138. }
  139. func (c *client) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushSecretRemoteRef) error {
  140. path := c.buildPath(remoteRef.GetRemoteKey())
  141. metaPath, err := c.buildMetadataPath(remoteRef.GetRemoteKey())
  142. if err != nil {
  143. return err
  144. }
  145. // Retrieve the secret map from vault and convert the secret value in string form.
  146. secretVal, err := c.readSecret(ctx, path, "")
  147. // If error is not of type secret not found, we should error
  148. if err != nil && errors.Is(err, esv1beta1.NoSecretError{}) {
  149. return nil
  150. }
  151. if err != nil {
  152. return err
  153. }
  154. metadata, err := c.readSecretMetadata(ctx, remoteRef.GetRemoteKey())
  155. if err != nil {
  156. return err
  157. }
  158. manager, ok := metadata["managed-by"]
  159. if !ok || manager != "external-secrets" {
  160. return nil
  161. }
  162. // If Push for a Property, we need to delete the property and update the secret
  163. if remoteRef.GetProperty() != "" {
  164. delete(secretVal, remoteRef.GetProperty())
  165. // If the only key left in the remote secret is the reference of the metadata.
  166. if c.store.Version == esv1beta1.VaultKVStoreV1 && len(secretVal) == 1 {
  167. delete(secretVal, "custom_metadata")
  168. }
  169. if len(secretVal) > 0 {
  170. secretToPush := secretVal
  171. if c.store.Version == esv1beta1.VaultKVStoreV2 {
  172. secretToPush = map[string]any{
  173. "data": secretVal,
  174. }
  175. }
  176. _, err = c.logical.WriteWithContext(ctx, path, secretToPush)
  177. metrics.ObserveAPICall(constants.ProviderHCVault, constants.CallHCVaultDeleteSecret, err)
  178. return err
  179. }
  180. }
  181. _, err = c.logical.DeleteWithContext(ctx, path)
  182. metrics.ObserveAPICall(constants.ProviderHCVault, constants.CallHCVaultDeleteSecret, err)
  183. if err != nil {
  184. return fmt.Errorf("could not delete secret %v: %w", remoteRef.GetRemoteKey(), err)
  185. }
  186. if c.store.Version == esv1beta1.VaultKVStoreV2 {
  187. _, err = c.logical.DeleteWithContext(ctx, metaPath)
  188. metrics.ObserveAPICall(constants.ProviderHCVault, constants.CallHCVaultDeleteSecret, err)
  189. if err != nil {
  190. return fmt.Errorf("could not delete secret metadata %v: %w", remoteRef.GetRemoteKey(), err)
  191. }
  192. }
  193. return nil
  194. }