controller_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  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
  13. import (
  14. "context"
  15. "crypto/rand"
  16. "crypto/rsa"
  17. "crypto/tls"
  18. "crypto/x509"
  19. "crypto/x509/pkix"
  20. "encoding/pem"
  21. "math/big"
  22. "net"
  23. "testing"
  24. "time"
  25. "github.com/go-logr/logr"
  26. "github.com/prometheus/client_golang/prometheus"
  27. corev1 "k8s.io/api/core/v1"
  28. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  29. "k8s.io/apimachinery/pkg/runtime"
  30. utilruntime "k8s.io/apimachinery/pkg/util/runtime"
  31. clientgoscheme "k8s.io/client-go/kubernetes/scheme"
  32. "google.golang.org/grpc"
  33. "google.golang.org/grpc/codes"
  34. "google.golang.org/grpc/credentials"
  35. "google.golang.org/grpc/status"
  36. ctrl "sigs.k8s.io/controller-runtime"
  37. "sigs.k8s.io/controller-runtime/pkg/client"
  38. fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
  39. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  40. pb "github.com/external-secrets/external-secrets/proto/provider"
  41. )
  42. type recordingClusterProviderGRPCServer struct {
  43. pb.UnimplementedSecretStoreProviderServer
  44. validateRequest *pb.ValidateRequest
  45. capabilitiesRequest *pb.CapabilitiesRequest
  46. capabilitiesResp *pb.CapabilitiesResponse
  47. capabilitiesErr error
  48. }
  49. func (s *recordingClusterProviderGRPCServer) Validate(_ context.Context, req *pb.ValidateRequest) (*pb.ValidateResponse, error) {
  50. s.validateRequest = req
  51. return &pb.ValidateResponse{Valid: true}, nil
  52. }
  53. func (s *recordingClusterProviderGRPCServer) Capabilities(_ context.Context, req *pb.CapabilitiesRequest) (*pb.CapabilitiesResponse, error) {
  54. s.capabilitiesRequest = req
  55. if s.capabilitiesErr != nil {
  56. return nil, s.capabilitiesErr
  57. }
  58. if s.capabilitiesResp != nil {
  59. return s.capabilitiesResp, nil
  60. }
  61. return &pb.CapabilitiesResponse{Capabilities: pb.SecretStoreCapabilities_READ_WRITE}, nil
  62. }
  63. func TestValidateStoreAndGetCapabilitiesUsesCapabilitiesOnly(t *testing.T) {
  64. scheme := runtime.NewScheme()
  65. utilruntime.Must(clientgoscheme.AddToScheme(scheme))
  66. utilruntime.Must(esv1.AddToScheme(scheme))
  67. server, address, tlsSecret := newClusterProviderGRPCServer(t)
  68. store := &esv1.ClusterProvider{
  69. ObjectMeta: metav1.ObjectMeta{
  70. Name: "cluster-provider",
  71. },
  72. Spec: esv1.ClusterProviderSpec{
  73. Config: esv1.ProviderConfig{
  74. Address: address,
  75. ProviderRef: esv1.ProviderReference{
  76. APIVersion: "provider.external-secrets.io/v2alpha1",
  77. Kind: "Kubernetes",
  78. Name: "backend",
  79. Namespace: "config-ns",
  80. },
  81. },
  82. },
  83. }
  84. kubeClient := fakeclient.NewClientBuilder().
  85. WithScheme(scheme).
  86. WithObjects(store, &corev1.Secret{
  87. ObjectMeta: metav1.ObjectMeta{
  88. Name: "external-secrets-provider-tls",
  89. Namespace: "config-ns",
  90. },
  91. Data: tlsSecret,
  92. }).
  93. Build()
  94. r := &Reconciler{Client: kubeClient, Log: logr.Discard()}
  95. caps, err := r.validateStoreAndGetCapabilities(context.Background(), store)
  96. if err != nil {
  97. t.Fatalf("validateStoreAndGetCapabilities() error = %v", err)
  98. }
  99. if caps != esv1.ProviderReadWrite {
  100. t.Fatalf("expected ProviderReadWrite, got %q", caps)
  101. }
  102. if server.validateRequest != nil {
  103. t.Fatalf("expected Validate not to be called, got %#v", server.validateRequest)
  104. }
  105. assertClusterProviderReference(t, server.capabilitiesRequest.ProviderRef, store.Spec.Config.ProviderRef)
  106. if server.capabilitiesRequest.SourceNamespace != "" {
  107. t.Fatalf("expected empty source namespace, got %q", server.capabilitiesRequest.SourceNamespace)
  108. }
  109. }
  110. func TestValidateStoreAndGetCapabilitiesFallsBackToReadOnlyOnCapabilitiesError(t *testing.T) {
  111. scheme := runtime.NewScheme()
  112. utilruntime.Must(clientgoscheme.AddToScheme(scheme))
  113. utilruntime.Must(esv1.AddToScheme(scheme))
  114. server, address, tlsSecret := newClusterProviderGRPCServer(t)
  115. server.capabilitiesErr = status.Error(codes.Unavailable, "capabilities unavailable")
  116. store := &esv1.ClusterProvider{
  117. ObjectMeta: metav1.ObjectMeta{
  118. Name: "cluster-provider",
  119. },
  120. Spec: esv1.ClusterProviderSpec{
  121. Config: esv1.ProviderConfig{
  122. Address: address,
  123. ProviderRef: esv1.ProviderReference{
  124. APIVersion: "provider.external-secrets.io/v2alpha1",
  125. Kind: "Kubernetes",
  126. Name: "backend",
  127. Namespace: "config-ns",
  128. },
  129. },
  130. },
  131. }
  132. kubeClient := fakeclient.NewClientBuilder().
  133. WithScheme(scheme).
  134. WithObjects(store, &corev1.Secret{
  135. ObjectMeta: metav1.ObjectMeta{
  136. Name: "external-secrets-provider-tls",
  137. Namespace: "config-ns",
  138. },
  139. Data: tlsSecret,
  140. }).
  141. Build()
  142. r := &Reconciler{Client: kubeClient, Log: logr.Discard()}
  143. caps, err := r.validateStoreAndGetCapabilities(context.Background(), store)
  144. if err != nil {
  145. t.Fatalf("expected fallback to read-only, got error %v", err)
  146. }
  147. if caps != esv1.ProviderReadOnly {
  148. t.Fatalf("expected ProviderReadOnly, got %q", caps)
  149. }
  150. }
  151. func TestReconcileValidationFailureClearsStaleCapabilitiesAndUpdatesCondition(t *testing.T) {
  152. previousMetrics := gaugeVecMetrics
  153. gaugeVecMetrics = map[string]*prometheus.GaugeVec{
  154. ClusterProviderReconcileDurationKey: prometheus.NewGaugeVec(prometheus.GaugeOpts{
  155. Subsystem: ClusterProviderSubsystem,
  156. Name: ClusterProviderReconcileDurationKey,
  157. }, []string{"name"}),
  158. StatusConditionKey: prometheus.NewGaugeVec(prometheus.GaugeOpts{
  159. Subsystem: ClusterProviderSubsystem,
  160. Name: StatusConditionKey,
  161. }, []string{"name", "condition", "status"}),
  162. }
  163. t.Cleanup(func() {
  164. gaugeVecMetrics = previousMetrics
  165. })
  166. scheme := runtime.NewScheme()
  167. utilruntime.Must(clientgoscheme.AddToScheme(scheme))
  168. utilruntime.Must(esv1.AddToScheme(scheme))
  169. server, address, tlsSecret := newClusterProviderGRPCServer(t)
  170. server.capabilitiesErr = status.Error(codes.InvalidArgument, "invalid configuration")
  171. store := &esv1.ClusterProvider{
  172. ObjectMeta: metav1.ObjectMeta{
  173. Name: "cluster-provider",
  174. },
  175. Spec: esv1.ClusterProviderSpec{
  176. Config: esv1.ProviderConfig{
  177. Address: address,
  178. ProviderRef: esv1.ProviderReference{
  179. APIVersion: "provider.external-secrets.io/v2alpha1",
  180. Kind: "Kubernetes",
  181. Name: "backend",
  182. Namespace: "config-ns",
  183. },
  184. },
  185. },
  186. Status: esv1.ProviderStatus{
  187. Capabilities: esv1.ProviderReadWrite,
  188. },
  189. }
  190. kubeClient := fakeclient.NewClientBuilder().
  191. WithScheme(scheme).
  192. WithObjects(store, &corev1.Secret{
  193. ObjectMeta: metav1.ObjectMeta{
  194. Name: "external-secrets-provider-tls",
  195. Namespace: "config-ns",
  196. },
  197. Data: tlsSecret,
  198. }).
  199. WithStatusSubresource(store).
  200. Build()
  201. r := &Reconciler{
  202. Client: kubeClient,
  203. Log: logr.Discard(),
  204. RequeueInterval: 29 * time.Second,
  205. }
  206. result, err := r.Reconcile(context.Background(), ctrl.Request{
  207. NamespacedName: client.ObjectKey{Name: "cluster-provider"},
  208. })
  209. if err != nil {
  210. t.Fatalf("Reconcile() error = %v", err)
  211. }
  212. if result.RequeueAfter != 29*time.Second {
  213. t.Fatalf("expected requeue interval, got %#v", result)
  214. }
  215. var updated esv1.ClusterProvider
  216. if err := kubeClient.Get(context.Background(), client.ObjectKey{Name: "cluster-provider"}, &updated); err != nil {
  217. t.Fatalf("Get() error = %v", err)
  218. }
  219. if updated.Status.Capabilities != esv1.ProviderReadOnly {
  220. t.Fatalf("expected fallback read-only capabilities, got %q", updated.Status.Capabilities)
  221. }
  222. if len(updated.Status.Conditions) != 1 {
  223. t.Fatalf("expected a single condition, got %#v", updated.Status.Conditions)
  224. }
  225. condition := updated.Status.Conditions[0]
  226. if condition.Type != esv1.ProviderReady || condition.Status != metav1.ConditionTrue {
  227. t.Fatalf("unexpected condition: %#v", condition)
  228. }
  229. }
  230. func TestReconcileHardValidationFailureClearsStaleCapabilitiesAndUpdatesCondition(t *testing.T) {
  231. previousMetrics := gaugeVecMetrics
  232. gaugeVecMetrics = map[string]*prometheus.GaugeVec{
  233. ClusterProviderReconcileDurationKey: prometheus.NewGaugeVec(prometheus.GaugeOpts{
  234. Subsystem: ClusterProviderSubsystem,
  235. Name: ClusterProviderReconcileDurationKey,
  236. }, []string{"name"}),
  237. StatusConditionKey: prometheus.NewGaugeVec(prometheus.GaugeOpts{
  238. Subsystem: ClusterProviderSubsystem,
  239. Name: StatusConditionKey,
  240. }, []string{"name", "condition", "status"}),
  241. }
  242. t.Cleanup(func() {
  243. gaugeVecMetrics = previousMetrics
  244. })
  245. scheme := runtime.NewScheme()
  246. utilruntime.Must(clientgoscheme.AddToScheme(scheme))
  247. utilruntime.Must(esv1.AddToScheme(scheme))
  248. store := &esv1.ClusterProvider{
  249. ObjectMeta: metav1.ObjectMeta{
  250. Name: "cluster-provider",
  251. },
  252. Spec: esv1.ClusterProviderSpec{
  253. Config: esv1.ProviderConfig{
  254. ProviderRef: esv1.ProviderReference{
  255. APIVersion: "provider.external-secrets.io/v2alpha1",
  256. Kind: "Kubernetes",
  257. Name: "backend",
  258. Namespace: "config-ns",
  259. },
  260. },
  261. },
  262. Status: esv1.ProviderStatus{
  263. Capabilities: esv1.ProviderReadWrite,
  264. },
  265. }
  266. kubeClient := fakeclient.NewClientBuilder().
  267. WithScheme(scheme).
  268. WithObjects(store).
  269. WithStatusSubresource(store).
  270. Build()
  271. r := &Reconciler{
  272. Client: kubeClient,
  273. Log: logr.Discard(),
  274. RequeueInterval: 31 * time.Second,
  275. }
  276. result, err := r.Reconcile(context.Background(), ctrl.Request{
  277. NamespacedName: client.ObjectKey{Name: "cluster-provider"},
  278. })
  279. if err != nil {
  280. t.Fatalf("Reconcile() error = %v", err)
  281. }
  282. if result.RequeueAfter != 31*time.Second {
  283. t.Fatalf("expected requeue interval, got %#v", result)
  284. }
  285. var updated esv1.ClusterProvider
  286. if err := kubeClient.Get(context.Background(), client.ObjectKey{Name: "cluster-provider"}, &updated); err != nil {
  287. t.Fatalf("Get() error = %v", err)
  288. }
  289. if updated.Status.Capabilities != "" {
  290. t.Fatalf("expected capabilities to be cleared, got %q", updated.Status.Capabilities)
  291. }
  292. if len(updated.Status.Conditions) != 1 {
  293. t.Fatalf("expected a single condition, got %#v", updated.Status.Conditions)
  294. }
  295. condition := updated.Status.Conditions[0]
  296. if condition.Type != esv1.ProviderReady || condition.Status != metav1.ConditionFalse {
  297. t.Fatalf("unexpected condition: %#v", condition)
  298. }
  299. if condition.Reason != "ValidationFailed" {
  300. t.Fatalf("unexpected condition reason: %q", condition.Reason)
  301. }
  302. if condition.Message != "provider address is required" {
  303. t.Fatalf("unexpected condition message: %q", condition.Message)
  304. }
  305. }
  306. func TestSetNotReadyConditionUpdatesReasonAndMessageWithoutChangingTransitionTime(t *testing.T) {
  307. previousMetrics := gaugeVecMetrics
  308. gaugeVecMetrics = map[string]*prometheus.GaugeVec{
  309. StatusConditionKey: prometheus.NewGaugeVec(prometheus.GaugeOpts{
  310. Subsystem: ClusterProviderSubsystem,
  311. Name: StatusConditionKey,
  312. }, []string{"name", "condition", "status"}),
  313. }
  314. t.Cleanup(func() {
  315. gaugeVecMetrics = previousMetrics
  316. })
  317. previousTransition := metav1.NewTime(time.Unix(1700000000, 0))
  318. store := &esv1.ClusterProvider{
  319. ObjectMeta: metav1.ObjectMeta{
  320. Name: "cluster-provider",
  321. },
  322. Status: esv1.ProviderStatus{
  323. Conditions: []esv1.ProviderCondition{{
  324. Type: esv1.ProviderReady,
  325. Status: metav1.ConditionFalse,
  326. LastTransitionTime: previousTransition,
  327. Reason: "OldReason",
  328. Message: "old message",
  329. }},
  330. },
  331. }
  332. r := &Reconciler{Log: logr.Discard()}
  333. r.setNotReadyCondition(store, "ValidationFailed", "new message")
  334. if len(store.Status.Conditions) != 1 {
  335. t.Fatalf("expected a single condition, got %#v", store.Status.Conditions)
  336. }
  337. condition := store.Status.Conditions[0]
  338. if condition.Status != metav1.ConditionFalse {
  339. t.Fatalf("expected false status, got %q", condition.Status)
  340. }
  341. if condition.Reason != "ValidationFailed" {
  342. t.Fatalf("expected updated reason, got %q", condition.Reason)
  343. }
  344. if condition.Message != "new message" {
  345. t.Fatalf("expected updated message, got %q", condition.Message)
  346. }
  347. if !condition.LastTransitionTime.Equal(&previousTransition) {
  348. t.Fatalf("expected transition time to remain %v, got %v", previousTransition, condition.LastTransitionTime)
  349. }
  350. }
  351. func newClusterProviderGRPCServer(t *testing.T) (*recordingClusterProviderGRPCServer, string, map[string][]byte) {
  352. t.Helper()
  353. serverCert, serverKey, clientCert, clientKey, caCert := newClusterProviderTLSArtifacts(t, "127.0.0.1")
  354. caPool := x509.NewCertPool()
  355. if !caPool.AppendCertsFromPEM(caCert) {
  356. t.Fatal("failed to append CA cert")
  357. }
  358. tlsCert, err := tls.X509KeyPair(serverCert, serverKey)
  359. if err != nil {
  360. t.Fatalf("X509KeyPair() error = %v", err)
  361. }
  362. lis, err := net.Listen("tcp", "127.0.0.1:0")
  363. if err != nil {
  364. t.Fatalf("Listen() error = %v", err)
  365. }
  366. server := &recordingClusterProviderGRPCServer{}
  367. grpcServer := grpc.NewServer(grpc.Creds(credentials.NewTLS(&tls.Config{
  368. MinVersion: tls.VersionTLS12,
  369. Certificates: []tls.Certificate{tlsCert},
  370. ClientCAs: caPool,
  371. ClientAuth: tls.RequireAndVerifyClientCert,
  372. })))
  373. pb.RegisterSecretStoreProviderServer(grpcServer, server)
  374. go func() {
  375. _ = grpcServer.Serve(lis)
  376. }()
  377. t.Cleanup(func() {
  378. grpcServer.Stop()
  379. _ = lis.Close()
  380. })
  381. return server, lis.Addr().String(), map[string][]byte{
  382. "ca.crt": caCert,
  383. "client.crt": clientCert,
  384. "client.key": clientKey,
  385. }
  386. }
  387. func assertClusterProviderReference(t *testing.T, got *pb.ProviderReference, want esv1.ProviderReference) {
  388. t.Helper()
  389. if got == nil {
  390. t.Fatal("expected provider reference to be set")
  391. }
  392. if got.ApiVersion != want.APIVersion || got.Kind != want.Kind || got.Name != want.Name || got.Namespace != want.Namespace {
  393. t.Fatalf("unexpected provider ref: got=%#v want=%#v", got, want)
  394. }
  395. }
  396. func newClusterProviderTLSArtifacts(t *testing.T, host string) (serverCertPEM, serverKeyPEM, clientCertPEM, clientKeyPEM, caCertPEM []byte) {
  397. t.Helper()
  398. caKey, err := rsa.GenerateKey(rand.Reader, 2048)
  399. if err != nil {
  400. t.Fatalf("GenerateKey() error = %v", err)
  401. }
  402. caTemplate := &x509.Certificate{
  403. SerialNumber: big.NewInt(1),
  404. Subject: pkix.Name{CommonName: "cluster-provider-controller-test-ca"},
  405. NotBefore: time.Now().Add(-time.Hour),
  406. NotAfter: time.Now().Add(24 * time.Hour),
  407. KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
  408. BasicConstraintsValid: true,
  409. IsCA: true,
  410. }
  411. caDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey)
  412. if err != nil {
  413. t.Fatalf("CreateCertificate() error = %v", err)
  414. }
  415. caCert, err := x509.ParseCertificate(caDER)
  416. if err != nil {
  417. t.Fatalf("ParseCertificate() error = %v", err)
  418. }
  419. serverCertPEM, serverKeyPEM = newClusterProviderSignedTLSCert(t, caCert, caKey, 2, host, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth})
  420. clientCertPEM, clientKeyPEM = newClusterProviderSignedTLSCert(t, caCert, caKey, 3, host, []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth})
  421. caCertPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caDER})
  422. return serverCertPEM, serverKeyPEM, clientCertPEM, clientKeyPEM, caCertPEM
  423. }
  424. func newClusterProviderSignedTLSCert(t *testing.T, caCert *x509.Certificate, caKey *rsa.PrivateKey, serial int64, host string, usages []x509.ExtKeyUsage) ([]byte, []byte) {
  425. t.Helper()
  426. key, err := rsa.GenerateKey(rand.Reader, 2048)
  427. if err != nil {
  428. t.Fatalf("GenerateKey() error = %v", err)
  429. }
  430. template := &x509.Certificate{
  431. SerialNumber: big.NewInt(serial),
  432. Subject: pkix.Name{CommonName: host},
  433. NotBefore: time.Now().Add(-time.Hour),
  434. NotAfter: time.Now().Add(24 * time.Hour),
  435. KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
  436. ExtKeyUsage: usages,
  437. }
  438. if ip := net.ParseIP(host); ip != nil {
  439. template.IPAddresses = []net.IP{ip}
  440. } else {
  441. template.DNSNames = []string{host}
  442. }
  443. der, err := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey)
  444. if err != nil {
  445. t.Fatalf("CreateCertificate() error = %v", err)
  446. }
  447. return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}),
  448. pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
  449. }