client.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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 bitwarden
  14. import (
  15. "bytes"
  16. "context"
  17. "encoding/json"
  18. "errors"
  19. "fmt"
  20. "gopkg.in/yaml.v3"
  21. corev1 "k8s.io/api/core/v1"
  22. "k8s.io/kube-openapi/pkg/validation/strfmt"
  23. "k8s.io/utils/ptr"
  24. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  25. "github.com/external-secrets/external-secrets/runtime/esutils"
  26. )
  27. const (
  28. // NoteMetadataKey defines the note for the pushed secret.
  29. NoteMetadataKey = "note"
  30. errNoProvider = "store does not have a provider"
  31. )
  32. var (
  33. errFailedToGetAllSecrets = "failed to get all secrets: %w"
  34. errFailedToGetSecret = "failed to get secret: %w"
  35. )
  36. // PushSecret will write a single secret into the provider.
  37. // Note: We will refuse to overwrite ANY secrets, because we can never be completely
  38. // sure if it's the same secret we are trying to push. We only have the Name and the value
  39. // could be different. Therefore, we will always create a new secret. Except if, the value
  40. // the key, the note, and organization ID all match.
  41. // We only allow to push to a single project, because GET returns a single project ID
  42. // the secret belongs to even though technically Create allows multiple projects. This is
  43. // to ensure that we push to the same project always, and so we can determine reliably that
  44. // we don't need to push again.
  45. func (p *Provider) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1.PushSecretData) error {
  46. spec := p.store.GetSpec()
  47. if spec == nil || spec.Provider == nil {
  48. return errors.New(errNoProvider)
  49. }
  50. if data.GetRemoteKey() == "" {
  51. return errors.New("remote key must be defined")
  52. }
  53. value, err := esutils.ExtractSecretData(data, secret)
  54. if err != nil {
  55. return fmt.Errorf("failed to extract secret data: %w", err)
  56. }
  57. note, err := esutils.FetchValueFromMetadata(NoteMetadataKey, data.GetMetadata(), "")
  58. if err != nil {
  59. return fmt.Errorf("failed to fetch note from metadata: %w", err)
  60. }
  61. // ListAll Secrets for an organization. If the key matches our key, we GetSecret that and do a compare.
  62. remoteSecrets, err := p.bitwardenSdkClient.ListSecrets(ctx, spec.Provider.BitwardenSecretsManager.OrganizationID)
  63. if err != nil {
  64. return fmt.Errorf(errFailedToGetAllSecrets, err)
  65. }
  66. for _, d := range remoteSecrets.Data {
  67. if d.Key != data.GetRemoteKey() {
  68. continue
  69. }
  70. sec, err := p.bitwardenSdkClient.GetSecret(ctx, d.ID)
  71. if err != nil {
  72. return fmt.Errorf("failed to get secret: %w", err)
  73. }
  74. // If all pushed data matches, we won't push this secret.
  75. if p.isExactlySameSecret(sec, data.GetRemoteKey(), note, spec.Provider.BitwardenSecretsManager.ProjectID, value) {
  76. // we have a complete match, skip pushing.
  77. return nil
  78. } else if p.isOnlyValueDifferent(sec, data.GetRemoteKey(), note, spec.Provider.BitwardenSecretsManager.ProjectID, value) {
  79. // only the value is different, update the existing secret.
  80. _, err = p.bitwardenSdkClient.UpdateSecret(ctx, SecretPutRequest{
  81. ID: sec.ID,
  82. Key: data.GetRemoteKey(),
  83. Note: note,
  84. OrganizationID: spec.Provider.BitwardenSecretsManager.OrganizationID,
  85. ProjectIDs: []string{spec.Provider.BitwardenSecretsManager.ProjectID},
  86. Value: string(value),
  87. })
  88. return err
  89. }
  90. }
  91. // no matching secret found, let's create it
  92. _, err = p.bitwardenSdkClient.CreateSecret(ctx, SecretCreateRequest{
  93. Key: data.GetRemoteKey(),
  94. Note: note,
  95. OrganizationID: spec.Provider.BitwardenSecretsManager.OrganizationID,
  96. ProjectIDs: []string{spec.Provider.BitwardenSecretsManager.ProjectID},
  97. Value: string(value),
  98. })
  99. return err
  100. }
  101. func (p *Provider) isExactlySameSecret(sec *SecretResponse, remoteKey, note, projectID string, value []byte) bool {
  102. return sec.Key == remoteKey &&
  103. sec.Value == string(value) &&
  104. sec.Note == note &&
  105. ptr.Deref(sec.ProjectID, "") == projectID
  106. }
  107. func (p *Provider) isOnlyValueDifferent(sec *SecretResponse, remoteKey, note, projectID string, value []byte) bool {
  108. return sec.Key == remoteKey &&
  109. sec.Value != string(value) &&
  110. sec.Note == note &&
  111. ptr.Deref(sec.ProjectID, "") == projectID
  112. }
  113. // GetSecret returns a single secret from the provider.
  114. func (p *Provider) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  115. if strfmt.IsUUID(ref.Key) {
  116. resp, err := p.bitwardenSdkClient.GetSecret(ctx, ref.Key)
  117. if err != nil {
  118. return nil, fmt.Errorf(errFailedToGetSecret, err)
  119. }
  120. return []byte(resp.Value), nil
  121. }
  122. spec := p.store.GetSpec()
  123. if spec == nil || spec.Provider == nil {
  124. return nil, errors.New(errNoProvider)
  125. }
  126. secret, err := p.findSecretByRef(ctx, ref.Key, spec.Provider.BitwardenSecretsManager.ProjectID)
  127. if err != nil {
  128. return nil, fmt.Errorf(errFailedToGetSecret, err)
  129. }
  130. if secret == nil {
  131. return nil, fmt.Errorf("no secret found for project id %s and name %s", spec.Provider.BitwardenSecretsManager.ProjectID, ref.Key)
  132. }
  133. // we found our secret, return the value for it
  134. return []byte(secret.Value), nil
  135. }
  136. // DeleteSecret deletes a secret from Bitwarden.
  137. func (p *Provider) DeleteSecret(ctx context.Context, ref esv1.PushSecretRemoteRef) error {
  138. if strfmt.IsUUID(ref.GetRemoteKey()) {
  139. return p.deleteSecret(ctx, ref.GetRemoteKey())
  140. }
  141. spec := p.store.GetSpec()
  142. if spec == nil || spec.Provider == nil {
  143. return errors.New(errNoProvider)
  144. }
  145. secret, err := p.findSecretByRef(ctx, ref.GetRemoteKey(), spec.Provider.BitwardenSecretsManager.ProjectID)
  146. if err != nil {
  147. return fmt.Errorf(errFailedToGetSecret, err)
  148. }
  149. if secret == nil {
  150. return fmt.Errorf("no secret found for project id %s and name %s", spec.Provider.BitwardenSecretsManager.ProjectID, ref.GetRemoteKey())
  151. }
  152. return p.deleteSecret(ctx, secret.ID)
  153. }
  154. func (p *Provider) deleteSecret(ctx context.Context, id string) error {
  155. resp, err := p.bitwardenSdkClient.DeleteSecret(ctx, []string{id})
  156. if err != nil {
  157. return fmt.Errorf("error deleting secret: %w", err)
  158. }
  159. var errs error
  160. for _, data := range resp.Data {
  161. if data.Error != nil {
  162. errs = errors.Join(errs, fmt.Errorf("error deleting secret with id %s: %s", data.ID, *data.Error))
  163. }
  164. }
  165. if errs != nil {
  166. return fmt.Errorf("there were one or more errors deleting secrets: %w", errs)
  167. }
  168. return nil
  169. }
  170. // SecretExists checks if a secret exists in Bitwarden.
  171. func (p *Provider) SecretExists(ctx context.Context, ref esv1.PushSecretRemoteRef) (bool, error) {
  172. if strfmt.IsUUID(ref.GetRemoteKey()) {
  173. _, err := p.bitwardenSdkClient.GetSecret(ctx, ref.GetRemoteKey())
  174. if err != nil {
  175. return false, fmt.Errorf(errFailedToGetSecret, err)
  176. }
  177. return true, nil
  178. }
  179. spec := p.store.GetSpec()
  180. if spec == nil || spec.Provider == nil {
  181. return false, errors.New(errNoProvider)
  182. }
  183. secret, err := p.findSecretByRef(ctx, ref.GetRemoteKey(), spec.Provider.BitwardenSecretsManager.ProjectID)
  184. if err != nil {
  185. return false, fmt.Errorf(errFailedToGetSecret, err)
  186. }
  187. if secret == nil {
  188. return false, nil
  189. }
  190. return true, nil
  191. }
  192. // GetSecretMap returns multiple k/v pairs from the provider.
  193. func (p *Provider) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  194. data, err := p.GetSecret(ctx, ref)
  195. if err != nil {
  196. return nil, err
  197. }
  198. if err := yaml.Unmarshal(data, map[string]any{}); err == nil {
  199. return p.parseYamlSecretData(data)
  200. }
  201. kv := make(map[string]json.RawMessage)
  202. if err := json.Unmarshal(data, &kv); err != nil {
  203. return nil, fmt.Errorf("error unmarshalling secret: %w", err)
  204. }
  205. secretData := make(map[string][]byte)
  206. for k, v := range kv {
  207. var strVal string
  208. err = json.Unmarshal(v, &strVal)
  209. if err == nil {
  210. secretData[k] = []byte(strVal)
  211. } else {
  212. secretData[k] = v
  213. }
  214. }
  215. return secretData, nil
  216. }
  217. func (p *Provider) parseYamlSecretData(data []byte) (map[string][]byte, error) {
  218. kv := make(map[string]any)
  219. if err := yaml.Unmarshal(data, &kv); err != nil {
  220. return nil, fmt.Errorf("error unmarshalling secret: %w", err)
  221. }
  222. secretData := make(map[string][]byte)
  223. for k, v := range kv {
  224. switch t := v.(type) {
  225. case string:
  226. secretData[k] = []byte(t)
  227. case []byte:
  228. secretData[k] = t
  229. case map[string]any:
  230. d, err := yaml.Marshal(t)
  231. if err != nil {
  232. return nil, fmt.Errorf("error marshaling secret: %w", err)
  233. }
  234. secretData[k] = bytes.TrimSpace(d)
  235. default:
  236. secretData[k] = fmt.Appendf(nil, "%v", t) // Convert to string and then []byte
  237. }
  238. }
  239. return secretData, nil
  240. }
  241. // GetAllSecrets gets multiple secrets from the provider and loads into a kubernetes secret.
  242. // First load all secrets from secretStore path configuration
  243. // Then, gets secrets from a matching name or matching custom_metadata.
  244. func (p *Provider) GetAllSecrets(ctx context.Context, _ esv1.ExternalSecretFind) (map[string][]byte, error) {
  245. spec := p.store.GetSpec()
  246. if spec == nil {
  247. return nil, errors.New(errNoProvider)
  248. }
  249. secrets, err := p.bitwardenSdkClient.ListSecrets(ctx, spec.Provider.BitwardenSecretsManager.OrganizationID)
  250. if err != nil {
  251. return nil, fmt.Errorf(errFailedToGetAllSecrets, err)
  252. }
  253. result := map[string][]byte{}
  254. for _, d := range secrets.Data {
  255. sec, err := p.bitwardenSdkClient.GetSecret(ctx, d.ID)
  256. if err != nil {
  257. return nil, fmt.Errorf(errFailedToGetSecret, err)
  258. }
  259. result[d.ID] = []byte(sec.Value)
  260. }
  261. return result, nil
  262. }
  263. // Validate validates the provider.
  264. func (p *Provider) Validate() (esv1.ValidationResult, error) {
  265. return esv1.ValidationResultReady, nil
  266. }
  267. // Close closes the provider.
  268. func (p *Provider) Close(_ context.Context) error {
  269. return nil
  270. }
  271. func (p *Provider) findSecretByRef(ctx context.Context, key, projectID string) (*SecretResponse, error) {
  272. spec := p.store.GetSpec()
  273. if spec == nil || spec.Provider == nil {
  274. return nil, errors.New(errNoProvider)
  275. }
  276. // ListAll Secrets for an organization. If the key matches our key, we GetSecret that and do a compare.
  277. secrets, err := p.bitwardenSdkClient.ListSecrets(ctx, spec.Provider.BitwardenSecretsManager.OrganizationID)
  278. if err != nil {
  279. return nil, fmt.Errorf(errFailedToGetAllSecrets, err)
  280. }
  281. var remoteSecret *SecretResponse
  282. for _, d := range secrets.Data {
  283. if d.Key != key {
  284. continue
  285. }
  286. sec, err := p.bitwardenSdkClient.GetSecret(ctx, d.ID)
  287. if err != nil {
  288. return nil, fmt.Errorf(errFailedToGetSecret, err)
  289. }
  290. if sec.ProjectID != nil && *sec.ProjectID == projectID {
  291. if remoteSecret != nil {
  292. return nil, fmt.Errorf("more than one secret found for project %s with key %s", projectID, key)
  293. }
  294. // We don't break here because we WANT TO MAKE SURE that there is ONLY ONE
  295. // such secret.
  296. remoteSecret = sec
  297. }
  298. }
  299. return remoteSecret, nil
  300. }