client.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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 bitwarden
  13. import (
  14. "context"
  15. "errors"
  16. "fmt"
  17. corev1 "k8s.io/api/core/v1"
  18. "k8s.io/kube-openapi/pkg/validation/strfmt"
  19. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  20. "github.com/external-secrets/external-secrets/pkg/utils"
  21. )
  22. var (
  23. errBadCertBundle = "caBundle failed to base64 decode: %w"
  24. )
  25. const (
  26. // NoteMetadataKey defines the note for the pushed secret.
  27. NoteMetadataKey = "note"
  28. )
  29. // PushSecret will write a single secret into the provider.
  30. // Note: We will refuse to overwrite ANY secrets, because we can never be completely
  31. // sure if it's the same secret we are trying to push. We only have the Name and the value
  32. // could be different. Therefore, we will always create a new secret. Except if, the value
  33. // the key, the note, and organization ID all match.
  34. // We only allow to push to a single project, because GET returns a single project ID
  35. // the secret belongs to even though technically Create allows multiple projects. This is
  36. // to ensure that we push to the same project always, and so we can determine reliably that
  37. // we don't need to push again.
  38. func (p *Provider) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1beta1.PushSecretData) error {
  39. spec := p.store.GetSpec()
  40. if spec == nil || spec.Provider == nil {
  41. return fmt.Errorf("store does not have a provider")
  42. }
  43. if data.GetSecretKey() == "" {
  44. return fmt.Errorf("pushing the whole secret is not yet implemented")
  45. }
  46. if data.GetRemoteKey() == "" {
  47. return fmt.Errorf("remote key must be defined")
  48. }
  49. value, ok := secret.Data[data.GetSecretKey()]
  50. if !ok {
  51. return fmt.Errorf("failed to find secret key in secret with key: %s", data.GetSecretKey())
  52. }
  53. note, err := utils.FetchValueFromMetadata(NoteMetadataKey, data.GetMetadata(), "")
  54. if err != nil {
  55. return fmt.Errorf("failed to fetch note from metadata: %w", err)
  56. }
  57. // ListAll Secrets for an organization. If the key matches our key, we GetSecret that and do a compare.
  58. remoteSecrets, err := p.bitwardenSdkClient.ListSecrets(ctx, spec.Provider.BitwardenSecretsManager.OrganizationID)
  59. if err != nil {
  60. return fmt.Errorf("failed to get all secrets: %w", err)
  61. }
  62. for _, d := range remoteSecrets.Data {
  63. if d.Key != data.GetRemoteKey() {
  64. continue
  65. }
  66. sec, err := p.bitwardenSdkClient.GetSecret(ctx, d.ID)
  67. if err != nil {
  68. return fmt.Errorf("failed to get secret: %w", err)
  69. }
  70. // If all pushed data matches, we won't push this secret.
  71. if sec.Key == data.GetRemoteKey() &&
  72. sec.Value == string(value) &&
  73. sec.Note == note &&
  74. sec.ProjectID != nil &&
  75. *sec.ProjectID == spec.Provider.BitwardenSecretsManager.ProjectID {
  76. // we have a complete match, skip pushing.
  77. return nil
  78. } else if sec.Key == data.GetRemoteKey() &&
  79. sec.Value != string(value) &&
  80. sec.Note == note &&
  81. sec.ProjectID != nil &&
  82. *sec.ProjectID == spec.Provider.BitwardenSecretsManager.ProjectID {
  83. // only the value is different, update the existing secret.
  84. _, err = p.bitwardenSdkClient.UpdateSecret(ctx, SecretPutRequest{
  85. ID: sec.ID,
  86. Key: data.GetRemoteKey(),
  87. Note: note,
  88. OrganizationID: spec.Provider.BitwardenSecretsManager.OrganizationID,
  89. ProjectIDS: []string{spec.Provider.BitwardenSecretsManager.ProjectID},
  90. Value: string(value),
  91. })
  92. return err
  93. }
  94. }
  95. // no matching secret found, let's create it
  96. _, err = p.bitwardenSdkClient.CreateSecret(ctx, SecretCreateRequest{
  97. Key: data.GetRemoteKey(),
  98. Note: note,
  99. OrganizationID: spec.Provider.BitwardenSecretsManager.OrganizationID,
  100. ProjectIDS: []string{spec.Provider.BitwardenSecretsManager.ProjectID},
  101. Value: string(value),
  102. })
  103. return err
  104. }
  105. // GetSecret returns a single secret from the provider.
  106. func (p *Provider) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  107. if strfmt.IsUUID(ref.Key) {
  108. resp, err := p.bitwardenSdkClient.GetSecret(ctx, ref.Key)
  109. if err != nil {
  110. return nil, fmt.Errorf("error getting secret: %w", err)
  111. }
  112. return []byte(resp.Value), nil
  113. }
  114. spec := p.store.GetSpec()
  115. if spec == nil || spec.Provider == nil {
  116. return nil, fmt.Errorf("store does not have a provider")
  117. }
  118. secret, err := p.findSecretByRef(ctx, ref.Key, spec.Provider.BitwardenSecretsManager.ProjectID)
  119. if err != nil {
  120. return nil, fmt.Errorf("error getting secret: %w", err)
  121. }
  122. // we found our secret, return the value for it
  123. return []byte(secret.Value), nil
  124. }
  125. func (p *Provider) DeleteSecret(ctx context.Context, ref esv1beta1.PushSecretRemoteRef) error {
  126. if strfmt.IsUUID(ref.GetRemoteKey()) {
  127. return p.deleteSecret(ctx, ref.GetRemoteKey())
  128. }
  129. spec := p.store.GetSpec()
  130. if spec == nil || spec.Provider == nil {
  131. return fmt.Errorf("store does not have a provider")
  132. }
  133. secret, err := p.findSecretByRef(ctx, ref.GetRemoteKey(), spec.Provider.BitwardenSecretsManager.ProjectID)
  134. if err != nil {
  135. return fmt.Errorf("error getting secret: %w", err)
  136. }
  137. return p.deleteSecret(ctx, secret.ID)
  138. }
  139. func (p *Provider) deleteSecret(ctx context.Context, id string) error {
  140. resp, err := p.bitwardenSdkClient.DeleteSecret(ctx, []string{id})
  141. if err != nil {
  142. return fmt.Errorf("error deleting secret: %w", err)
  143. }
  144. var errs error
  145. for _, data := range resp.Data {
  146. if data.Error != nil {
  147. errs = errors.Join(errs, fmt.Errorf("error deleting secret with id %s: %s", data.ID, *data.Error))
  148. }
  149. }
  150. if errs != nil {
  151. return fmt.Errorf("there were one or more errors deleting secrets: %w", errs)
  152. }
  153. return nil
  154. }
  155. func (p *Provider) SecretExists(ctx context.Context, ref esv1beta1.PushSecretRemoteRef) (bool, error) {
  156. if strfmt.IsUUID(ref.GetRemoteKey()) {
  157. _, err := p.bitwardenSdkClient.GetSecret(ctx, ref.GetRemoteKey())
  158. if err != nil {
  159. return false, fmt.Errorf("error getting secret: %w", err)
  160. }
  161. return true, nil
  162. }
  163. spec := p.store.GetSpec()
  164. if spec == nil || spec.Provider == nil {
  165. return false, fmt.Errorf("store does not have a provider")
  166. }
  167. if _, err := p.findSecretByRef(ctx, ref.GetRemoteKey(), spec.Provider.BitwardenSecretsManager.ProjectID); err != nil {
  168. return false, fmt.Errorf("error getting secret: %w", err)
  169. }
  170. return true, nil
  171. }
  172. // GetSecretMap returns multiple k/v pairs from the provider.
  173. func (p *Provider) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  174. return nil, fmt.Errorf("GetSecretMap() not implemented")
  175. }
  176. // GetAllSecrets gets multiple secrets from the provider and loads into a kubernetes secret.
  177. // First load all secrets from secretStore path configuration
  178. // Then, gets secrets from a matching name or matching custom_metadata.
  179. func (p *Provider) GetAllSecrets(ctx context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  180. spec := p.store.GetSpec()
  181. if spec == nil {
  182. return nil, fmt.Errorf("store does not have a provider")
  183. }
  184. secrets, err := p.bitwardenSdkClient.ListSecrets(ctx, spec.Provider.BitwardenSecretsManager.OrganizationID)
  185. if err != nil {
  186. return nil, fmt.Errorf("failed to get all secrets: %w", err)
  187. }
  188. result := map[string][]byte{}
  189. for _, d := range secrets.Data {
  190. sec, err := p.bitwardenSdkClient.GetSecret(ctx, d.ID)
  191. if err != nil {
  192. return nil, fmt.Errorf("failed to get secret: %w", err)
  193. }
  194. result[d.ID] = []byte(sec.Value)
  195. }
  196. return result, nil
  197. }
  198. // Validate validates the provider.
  199. func (p *Provider) Validate() (esv1beta1.ValidationResult, error) {
  200. return esv1beta1.ValidationResultReady, nil
  201. }
  202. // Close closes the provider.
  203. func (p *Provider) Close(_ context.Context) error {
  204. return nil
  205. }
  206. // getCABundle try retrieve the CA bundle from the provider CABundle.
  207. func (p *Provider) getCABundle(provider *esv1beta1.BitwardenSecretsManagerProvider) ([]byte, error) {
  208. certBytes, decodeErr := utils.Decode(esv1beta1.ExternalSecretDecodeBase64, []byte(provider.CABundle))
  209. if decodeErr != nil {
  210. return nil, fmt.Errorf(errBadCertBundle, decodeErr)
  211. }
  212. return certBytes, nil
  213. }
  214. func (p *Provider) findSecretByRef(ctx context.Context, key, projectID string) (*SecretResponse, error) {
  215. spec := p.store.GetSpec()
  216. if spec == nil || spec.Provider == nil {
  217. return nil, fmt.Errorf("store does not have a provider")
  218. }
  219. // ListAll Secrets for an organization. If the key matches our key, we GetSecret that and do a compare.
  220. secrets, err := p.bitwardenSdkClient.ListSecrets(ctx, spec.Provider.BitwardenSecretsManager.OrganizationID)
  221. if err != nil {
  222. return nil, fmt.Errorf("failed to get all secrets: %w", err)
  223. }
  224. var remoteSecret *SecretResponse
  225. for _, d := range secrets.Data {
  226. if d.Key != key {
  227. continue
  228. }
  229. sec, err := p.bitwardenSdkClient.GetSecret(ctx, d.ID)
  230. if err != nil {
  231. return nil, fmt.Errorf("failed to get secret: %w", err)
  232. }
  233. if sec.ProjectID != nil && *sec.ProjectID == projectID {
  234. if remoteSecret != nil {
  235. return nil, fmt.Errorf("more than one secret found for project %s with key %s", projectID, key)
  236. }
  237. // We don't break here because we WANT TO MAKE SURE that there is ONLY ONE
  238. // such secret.
  239. remoteSecret = sec
  240. }
  241. }
  242. if remoteSecret == nil {
  243. return nil, fmt.Errorf("no secret found for project id %s and name %s", projectID, key)
  244. }
  245. return remoteSecret, nil
  246. }