pushsecret_controller_v2.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  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 pushsecret
  13. import (
  14. "context"
  15. "fmt"
  16. "strings"
  17. corev1 "k8s.io/api/core/v1"
  18. "k8s.io/apimachinery/pkg/types"
  19. esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  20. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  21. pb "github.com/external-secrets/external-secrets/proto/provider"
  22. "github.com/external-secrets/external-secrets/providers/v2/common/grpc"
  23. )
  24. // isV2SecretStore checks if the referenced SecretStore is a v2 API version.
  25. func (r *Reconciler) isV2SecretStore(ctx context.Context, storeRef esv1alpha1.PushSecretStoreRef, namespace string) bool {
  26. // Check the apiVersion field first if specified
  27. if storeRef.APIVersion != "" {
  28. return storeRef.APIVersion == "external-secrets.io/v1"
  29. }
  30. // For backwards compatibility, try to fetch as v2 Provider
  31. var store esapi.Provider
  32. storeKey := types.NamespacedName{
  33. Name: storeRef.Name,
  34. Namespace: namespace,
  35. }
  36. err := r.Client.Get(ctx, storeKey, &store)
  37. return err == nil
  38. }
  39. // pushSecretToProviderV2 pushes a secret to a v2 provider via gRPC.
  40. func (r *Reconciler) pushSecretToProviderV2(ctx context.Context, storeRef esv1alpha1.PushSecretStoreRef, ps esv1alpha1.PushSecret, secret *corev1.Secret) (map[string]esv1alpha1.PushSecretData, error) {
  41. // Get the v2 Provider
  42. var store esapi.Provider
  43. storeKey := types.NamespacedName{
  44. Name: storeRef.Name,
  45. Namespace: ps.Namespace,
  46. }
  47. if err := r.Client.Get(ctx, storeKey, &store); err != nil {
  48. return nil, fmt.Errorf("failed to get Provider: %w", err)
  49. }
  50. // Get provider address
  51. address := store.Spec.Config.Address
  52. if address == "" {
  53. return nil, fmt.Errorf("provider address is required in Provider")
  54. }
  55. // Load TLS configuration
  56. tlsConfig, err := grpc.LoadClientTLSConfig(ctx, r.Client, store.Spec.Config.Address, "external-secrets-system")
  57. if err != nil {
  58. return nil, fmt.Errorf("failed to load TLS config: %w", err)
  59. }
  60. // Create gRPC client with TLS
  61. grpcClient, err := grpc.NewClient(address, tlsConfig)
  62. if err != nil {
  63. return nil, fmt.Errorf("failed to create gRPC client: %w", err)
  64. }
  65. defer func() { _ = grpcClient.Close(ctx) }()
  66. // Convert ProviderReference to protobuf format
  67. providerRef := &pb.ProviderReference{
  68. ApiVersion: store.Spec.Config.ProviderRef.APIVersion,
  69. Kind: store.Spec.Config.ProviderRef.Kind,
  70. Name: store.Spec.Config.ProviderRef.Name,
  71. Namespace: store.Spec.Config.ProviderRef.Namespace,
  72. }
  73. // Push secrets
  74. syncedSecrets := make(map[string]esv1alpha1.PushSecretData)
  75. // Convert secret data to map[string][]byte
  76. secretData := make(map[string][]byte)
  77. for k, v := range secret.Data {
  78. secretData[k] = v
  79. }
  80. // Push each data item
  81. for _, data := range ps.Spec.Data {
  82. // Prepare push secret data
  83. var metadataJSON []byte
  84. if data.Metadata != nil {
  85. metadataJSON = data.Metadata.Raw
  86. }
  87. pushData := &pb.PushSecretData{
  88. Metadata: metadataJSON,
  89. SecretKey: data.Match.SecretKey,
  90. RemoteKey: data.Match.RemoteRef.RemoteKey,
  91. Property: data.Match.RemoteRef.Property,
  92. }
  93. remoteRef := &pb.PushSecretRemoteRef{
  94. RemoteKey: data.Match.RemoteRef.RemoteKey,
  95. Property: data.Match.RemoteRef.Property,
  96. }
  97. // Handle UpdatePolicy
  98. switch ps.Spec.UpdatePolicy {
  99. case esv1alpha1.PushSecretUpdatePolicyIfNotExists:
  100. // Check if secret already exists
  101. exists, err := grpcClient.SecretExists(ctx, remoteRef, providerRef, ps.Namespace)
  102. if err != nil {
  103. return syncedSecrets, fmt.Errorf("could not verify if secret exists: %w", err)
  104. }
  105. if exists {
  106. // Secret exists, skip push but record it as synced
  107. syncedSecrets[data.Match.RemoteRef.RemoteKey] = data
  108. continue
  109. }
  110. case esv1alpha1.PushSecretUpdatePolicyReplace:
  111. // Always push (replace existing)
  112. default:
  113. // Default to replace
  114. }
  115. // Push the secret
  116. if err := grpcClient.PushSecret(ctx, secretData, pushData, providerRef, ps.Namespace); err != nil {
  117. return syncedSecrets, fmt.Errorf("failed to push secret for key %s: %w", data.Match.RemoteRef.RemoteKey, err)
  118. }
  119. // Record successful push
  120. syncedSecrets[data.Match.RemoteRef.RemoteKey] = data
  121. }
  122. return syncedSecrets, nil
  123. }
  124. // GetSecretStoresV2 retrieves both v1 and v2 Providers.
  125. func (r *Reconciler) GetSecretStoresV2(ctx context.Context, ps esv1alpha1.PushSecret) (map[esv1alpha1.PushSecretStoreRef]interface{}, error) {
  126. stores := make(map[esv1alpha1.PushSecretStoreRef]interface{})
  127. for _, refStore := range ps.Spec.SecretStoreRefs {
  128. // Check if this is a v2 Provider
  129. if r.isV2SecretStore(ctx, refStore, ps.Namespace) {
  130. // Get v2 Provider
  131. var store esapi.Provider
  132. storeKey := types.NamespacedName{
  133. Name: refStore.Name,
  134. Namespace: ps.Namespace,
  135. }
  136. if err := r.Client.Get(ctx, storeKey, &store); err != nil {
  137. return nil, fmt.Errorf("failed to get v2 Provider %s: %w", refStore.Name, err)
  138. }
  139. stores[refStore] = &store
  140. } else {
  141. // Get v1 SecretStore (existing implementation)
  142. store, err := r.getSecretStoreFromName(ctx, refStore, ps.Namespace)
  143. if err != nil {
  144. return nil, err
  145. }
  146. stores[refStore] = store
  147. }
  148. }
  149. return stores, nil
  150. }
  151. // handleV2Push determines if a store is v2 and pushes accordingly.
  152. func (r *Reconciler) handleV2Push(ctx context.Context, storeRef esv1alpha1.PushSecretStoreRef, store interface{}, ps esv1alpha1.PushSecret, secret *corev1.Secret) (map[string]esv1alpha1.PushSecretData, error) {
  153. // Check if this is a v2 store
  154. if _, ok := store.(*esapi.Provider); ok {
  155. // Use v2 push
  156. return r.pushSecretToProviderV2(ctx, storeRef, ps, secret)
  157. }
  158. // Not a v2 store, return nil to indicate v1 handling should be used
  159. return nil, nil
  160. }
  161. // DeleteSecretFromProvidersV2 removes secrets from v2 providers when they're no longer needed.
  162. func (r *Reconciler) DeleteSecretFromProvidersV2(ctx context.Context, ps *esv1alpha1.PushSecret, newMap esv1alpha1.SyncedPushSecretsMap, stores map[esv1alpha1.PushSecretStoreRef]interface{}) (esv1alpha1.SyncedPushSecretsMap, error) {
  163. out := mergeSecretState(newMap, ps.Status.SyncedPushSecrets)
  164. for storeName, oldData := range ps.Status.SyncedPushSecrets {
  165. // Parse store name format "Kind/Name"
  166. parts := strings.Split(storeName, "/")
  167. if len(parts) != 2 {
  168. continue
  169. }
  170. storeKind := parts[0]
  171. storeNameOnly := parts[1]
  172. // Find the matching store
  173. var matchingStore interface{}
  174. var found bool
  175. for ref, store := range stores {
  176. if ref.Kind == storeKind && ref.Name == storeNameOnly {
  177. matchingStore = store
  178. found = true
  179. break
  180. }
  181. }
  182. if !found {
  183. // Store no longer referenced, skip deletion
  184. continue
  185. }
  186. // Check if it's a v2 store
  187. if v2Store, ok := matchingStore.(*esapi.Provider); ok {
  188. // Create gRPC client
  189. tlsConfig, err := grpc.LoadClientTLSConfig(ctx, r.Client, v2Store.Spec.Config.Address, "external-secrets-system")
  190. if err != nil {
  191. return out, fmt.Errorf("failed to load TLS config: %w", err)
  192. }
  193. grpcClient, err := grpc.NewClient(v2Store.Spec.Config.Address, tlsConfig)
  194. if err != nil {
  195. return out, fmt.Errorf("failed to create gRPC client: %w", err)
  196. }
  197. defer func() { _ = grpcClient.Close(ctx) }()
  198. // Convert ProviderReference to protobuf format
  199. providerRef := &pb.ProviderReference{
  200. ApiVersion: v2Store.Spec.Config.ProviderRef.APIVersion,
  201. Kind: v2Store.Spec.Config.ProviderRef.Kind,
  202. Name: v2Store.Spec.Config.ProviderRef.Name,
  203. Namespace: v2Store.Spec.Config.ProviderRef.Namespace,
  204. }
  205. // Check if store still exists in newMap
  206. newData, ok := newMap[storeName]
  207. if !ok {
  208. // Store removed entirely, delete all secrets
  209. for _, oldRef := range oldData {
  210. remoteRef := &pb.PushSecretRemoteRef{
  211. RemoteKey: oldRef.Match.RemoteRef.RemoteKey,
  212. Property: oldRef.Match.RemoteRef.Property,
  213. }
  214. if err := grpcClient.DeleteSecret(ctx, remoteRef, providerRef, ps.Namespace); err != nil {
  215. return out, fmt.Errorf("failed to delete secret %s: %w", oldRef.Match.RemoteRef.RemoteKey, err)
  216. }
  217. }
  218. delete(out, storeName)
  219. continue
  220. }
  221. // Delete individual secrets that are no longer in the new data
  222. for oldEntry, oldRef := range oldData {
  223. if _, stillExists := newData[oldEntry]; !stillExists {
  224. remoteRef := &pb.PushSecretRemoteRef{
  225. RemoteKey: oldRef.Match.RemoteRef.RemoteKey,
  226. Property: oldRef.Match.RemoteRef.Property,
  227. }
  228. if err := grpcClient.DeleteSecret(ctx, remoteRef, providerRef, ps.Namespace); err != nil {
  229. return out, fmt.Errorf("failed to delete secret %s: %w", oldRef.Match.RemoteRef.RemoteKey, err)
  230. }
  231. delete(out[storeName], oldRef.Match.RemoteRef.RemoteKey)
  232. }
  233. }
  234. }
  235. // If not v2, the v1 deletion logic will handle it
  236. }
  237. return out, nil
  238. }