push_secret.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  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 secretmanager
  14. import (
  15. "bytes"
  16. "errors"
  17. "fmt"
  18. "maps"
  19. "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
  20. "github.com/tidwall/sjson"
  21. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  22. "github.com/external-secrets/external-secrets/runtime/esutils/metadata"
  23. )
  24. // PushSecretMetadataMergePolicy defines how metadata should be merged when pushing secrets.
  25. type PushSecretMetadataMergePolicy string
  26. const (
  27. // PushSecretMetadataMergePolicyReplace indicates that metadata should be replaced entirely.
  28. PushSecretMetadataMergePolicyReplace PushSecretMetadataMergePolicy = "Replace"
  29. // PushSecretMetadataMergePolicyMerge indicates that metadata should be merged.
  30. PushSecretMetadataMergePolicyMerge PushSecretMetadataMergePolicy = "Merge"
  31. )
  32. // PushSecretMetadataSpec defines the metadata specification for pushed secrets.
  33. type PushSecretMetadataSpec struct {
  34. Annotations map[string]string `json:"annotations,omitempty"`
  35. Labels map[string]string `json:"labels,omitempty"`
  36. Topics []string `json:"topics,omitempty"`
  37. MergePolicy PushSecretMetadataMergePolicy `json:"mergePolicy,omitempty"`
  38. CMEKKeyName string `json:"cmekKeyName,omitempty"`
  39. ReplicationLocation string `json:"replicationLocation,omitempty"`
  40. }
  41. func newPushSecretBuilder(payload []byte, data esv1.PushSecretData) (pushSecretBuilder, error) {
  42. if data.GetProperty() == "" {
  43. return &psBuilder{
  44. payload: payload,
  45. pushSecretData: data,
  46. }, nil
  47. }
  48. if data.GetMetadata() != nil {
  49. return nil, errors.New("cannot specify metadata and property at the same time")
  50. }
  51. return &propertyPSBuilder{
  52. payload: payload,
  53. pushSecretData: data,
  54. }, nil
  55. }
  56. type pushSecretBuilder interface {
  57. buildMetadata(annotations, labels map[string]string, topics []*secretmanagerpb.Topic) (map[string]string, map[string]string, []string, error)
  58. needUpdate(original []byte) bool
  59. buildData(original []byte) ([]byte, error)
  60. }
  61. type psBuilder struct {
  62. payload []byte
  63. pushSecretData esv1.PushSecretData
  64. }
  65. func (b *psBuilder) buildMetadata(_, labels map[string]string, _ []*secretmanagerpb.Topic) (map[string]string, map[string]string, []string, error) {
  66. if manager, ok := labels[managedByKey]; !ok || manager != managedByValue {
  67. return nil, nil, nil, fmt.Errorf("secret %v is not managed by external secrets", b.pushSecretData.GetRemoteKey())
  68. }
  69. var meta *metadata.PushSecretMetadata[PushSecretMetadataSpec]
  70. if b.pushSecretData.GetMetadata() != nil {
  71. var err error
  72. meta, err = metadata.ParseMetadataParameters[PushSecretMetadataSpec](b.pushSecretData.GetMetadata())
  73. if err != nil {
  74. return nil, nil, nil, fmt.Errorf("failed to parse PushSecret metadata: %w", err)
  75. }
  76. }
  77. var spec PushSecretMetadataSpec
  78. if meta != nil {
  79. spec = meta.Spec
  80. }
  81. newLabels := map[string]string{}
  82. maps.Copy(newLabels, spec.Labels)
  83. if spec.MergePolicy == PushSecretMetadataMergePolicyMerge {
  84. // Keep labels from the existing GCP Secret Manager Secret
  85. maps.Copy(newLabels, labels)
  86. }
  87. newLabels[managedByKey] = managedByValue
  88. return spec.Annotations, newLabels, spec.Topics, nil
  89. }
  90. func (b *psBuilder) needUpdate(original []byte) bool {
  91. if original == nil {
  92. return true
  93. }
  94. return !bytes.Equal(b.payload, original)
  95. }
  96. func (b *psBuilder) buildData(_ []byte) ([]byte, error) {
  97. return b.payload, nil
  98. }
  99. type propertyPSBuilder struct {
  100. payload []byte
  101. pushSecretData esv1.PushSecretData
  102. }
  103. func (b *propertyPSBuilder) buildMetadata(annotations, labels map[string]string, topics []*secretmanagerpb.Topic) (map[string]string, map[string]string, []string, error) {
  104. newAnnotations := map[string]string{}
  105. newLabels := map[string]string{}
  106. if annotations != nil {
  107. newAnnotations = annotations
  108. }
  109. if labels != nil {
  110. newLabels = labels
  111. }
  112. newLabels[managedByKey] = managedByValue
  113. result := make([]string, 0, len(topics))
  114. for _, t := range topics {
  115. result = append(result, t.Name)
  116. }
  117. return newAnnotations, newLabels, result, nil
  118. }
  119. func (b *propertyPSBuilder) needUpdate(original []byte) bool {
  120. if original == nil {
  121. return true
  122. }
  123. val, _ := getDataByProperty(original, b.pushSecretData.GetProperty())
  124. return !val.Exists() || val.String() != string(b.payload)
  125. }
  126. func (b *propertyPSBuilder) buildData(original []byte) ([]byte, error) {
  127. var base []byte
  128. if original != nil {
  129. base = original
  130. }
  131. return sjson.SetBytes(base, b.pushSecretData.GetProperty(), b.payload)
  132. }