controller.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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 clusterprovider implements the controller for ClusterProvider resources.
  13. package clusterprovider
  14. import (
  15. "context"
  16. "fmt"
  17. "time"
  18. "github.com/go-logr/logr"
  19. corev1 "k8s.io/api/core/v1"
  20. apierrors "k8s.io/apimachinery/pkg/api/errors"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/runtime"
  23. ctrl "sigs.k8s.io/controller-runtime"
  24. "sigs.k8s.io/controller-runtime/pkg/client"
  25. "sigs.k8s.io/controller-runtime/pkg/controller"
  26. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  27. pb "github.com/external-secrets/external-secrets/proto/provider"
  28. "github.com/external-secrets/external-secrets/providers/v2/common/grpc"
  29. )
  30. // Reconciler reconciles a ClusterProvider object.
  31. type Reconciler struct {
  32. client.Client
  33. Log logr.Logger
  34. Scheme *runtime.Scheme
  35. RequeueInterval time.Duration
  36. }
  37. // Reconcile validates the ClusterProvider and updates its status.
  38. func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  39. log := r.Log.WithValues("ClusterProvider", req.NamespacedName)
  40. start := time.Now()
  41. defer func() {
  42. duration := time.Since(start)
  43. if gaugeVec := GetGaugeVec(ClusterProviderReconcileDurationKey); gaugeVec != nil {
  44. gaugeVec.WithLabelValues(req.Name).Set(duration.Seconds())
  45. }
  46. }()
  47. log.Info("reconciling ClusterProvider")
  48. var store esv1.ClusterProvider
  49. if err := r.Get(ctx, req.NamespacedName, &store); err != nil {
  50. if apierrors.IsNotFound(err) {
  51. RemoveMetrics(req.Name)
  52. return ctrl.Result{}, nil
  53. }
  54. log.Error(err, "unable to get ClusterProvider")
  55. return ctrl.Result{}, err
  56. }
  57. // Validate provider config and get capabilities
  58. capabilities, err := r.validateStoreAndGetCapabilities(ctx, &store)
  59. if err != nil {
  60. log.Error(err, "validation failed")
  61. r.setNotReadyCondition(&store, "ValidationFailed", err.Error())
  62. if updateErr := r.Status().Update(ctx, &store); updateErr != nil {
  63. log.Error(updateErr, "failed to update status")
  64. return ctrl.Result{}, updateErr
  65. }
  66. // Requeue after interval to retry
  67. return ctrl.Result{RequeueAfter: r.RequeueInterval}, nil
  68. }
  69. // Set ready condition and capabilities
  70. r.setReadyCondition(&store)
  71. store.Status.Capabilities = capabilities
  72. if err := r.Status().Update(ctx, &store); err != nil {
  73. log.Error(err, "failed to update status")
  74. return ctrl.Result{}, err
  75. }
  76. log.Info("ClusterProvider is ready", "capabilities", capabilities)
  77. return ctrl.Result{RequeueAfter: r.RequeueInterval}, nil
  78. }
  79. // validateStoreAndGetCapabilities validates the ClusterProvider configuration and retrieves capabilities by:
  80. // 1. Creating a gRPC client to the provider
  81. // 2. Calling Validate() on the provider with the ProviderReference
  82. // 3. Calling Capabilities() to get the provider's capabilities.
  83. func (r *Reconciler) validateStoreAndGetCapabilities(ctx context.Context, store *esv1.ClusterProvider) (esv1.ProviderCapabilities, error) {
  84. // Get provider address
  85. address := store.Spec.Config.Address
  86. if address == "" {
  87. return "", fmt.Errorf("provider address is required")
  88. }
  89. // Load TLS configuration
  90. tlsConfig, err := grpc.LoadClientTLSConfig(ctx, r.Client, store.Spec.Config.Address, "external-secrets-system")
  91. if err != nil {
  92. return "", fmt.Errorf("failed to load TLS config: %w", err)
  93. }
  94. // Create gRPC client with TLS
  95. client, err := grpc.NewClient(address, tlsConfig)
  96. if err != nil {
  97. return "", fmt.Errorf("failed to create gRPC client: %w", err)
  98. }
  99. defer func() { _ = client.Close(ctx) }()
  100. // Convert ProviderReference to protobuf format
  101. providerRef := &pb.ProviderReference{
  102. ApiVersion: store.Spec.Config.ProviderRef.APIVersion,
  103. Kind: store.Spec.Config.ProviderRef.Kind,
  104. Name: store.Spec.Config.ProviderRef.Name,
  105. Namespace: store.Spec.Config.ProviderRef.Namespace,
  106. }
  107. // For ClusterProvider validation, we need to be more lenient since:
  108. // - ManifestNamespace scope: we don't have a manifest namespace at validation time
  109. // - ProviderNamespace scope: validation namespace may not have RBAC set up yet
  110. // So we skip validation and rely on runtime errors instead
  111. // This is acceptable since the provider will be validated when actually used by an ExternalSecret
  112. // Get provider capabilities (using empty namespace for ClusterProvider)
  113. caps, err := client.Capabilities(ctx, providerRef, "")
  114. if err != nil {
  115. r.Log.Error(err, "failed to get capabilities")
  116. // Don't fail validation if capabilities check fails, just log and default to ReadOnly
  117. return esv1.ProviderReadOnly, nil
  118. }
  119. // Map gRPC capabilities to our API type
  120. var capabilities esv1.ProviderCapabilities
  121. switch caps {
  122. case pb.SecretStoreCapabilities_READ_ONLY:
  123. capabilities = esv1.ProviderReadOnly
  124. case pb.SecretStoreCapabilities_WRITE_ONLY:
  125. capabilities = esv1.ProviderWriteOnly
  126. case pb.SecretStoreCapabilities_READ_WRITE:
  127. capabilities = esv1.ProviderReadWrite
  128. default:
  129. capabilities = esv1.ProviderReadOnly
  130. }
  131. return capabilities, nil
  132. }
  133. // setReadyCondition sets the Ready condition to True.
  134. func (r *Reconciler) setReadyCondition(store *esv1.ClusterProvider) {
  135. condition := esv1.ProviderCondition{
  136. Type: esv1.ProviderReady,
  137. Status: metav1.ConditionTrue,
  138. LastTransitionTime: metav1.Now(),
  139. Reason: "Validated",
  140. Message: "ClusterProvider is ready",
  141. }
  142. r.setCondition(store, condition)
  143. }
  144. // setNotReadyCondition sets the Ready condition to False.
  145. func (r *Reconciler) setNotReadyCondition(store *esv1.ClusterProvider, reason, message string) {
  146. condition := esv1.ProviderCondition{
  147. Type: esv1.ProviderReady,
  148. Status: metav1.ConditionFalse,
  149. LastTransitionTime: metav1.Now(),
  150. Reason: reason,
  151. Message: message,
  152. }
  153. r.setCondition(store, condition)
  154. }
  155. // setCondition updates or adds a condition to the ClusterProvider status.
  156. func (r *Reconciler) setCondition(store *esv1.ClusterProvider, newCondition esv1.ProviderCondition) {
  157. // Find existing condition
  158. for i, condition := range store.Status.Conditions {
  159. if condition.Type == newCondition.Type {
  160. // Only update if status changed
  161. if condition.Status != newCondition.Status {
  162. store.Status.Conditions[i] = newCondition
  163. }
  164. // Update metrics
  165. UpdateStatusCondition(store, newCondition)
  166. return
  167. }
  168. }
  169. // Add new condition
  170. store.Status.Conditions = append(store.Status.Conditions, newCondition)
  171. // Update metrics
  172. UpdateStatusCondition(store, newCondition)
  173. }
  174. // SetupWithManager sets up the controller with the Manager.
  175. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
  176. return ctrl.NewControllerManagedBy(mgr).
  177. WithOptions(opts).
  178. For(&esv1.ClusterProvider{}).
  179. Owns(&corev1.Secret{}). // Watch secrets that might be used for auth
  180. Complete(r)
  181. }