pushsecret_controller_v2_test.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911
  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 pushsecret
  14. import (
  15. "context"
  16. "crypto/rand"
  17. "crypto/rsa"
  18. "crypto/tls"
  19. "crypto/x509"
  20. "crypto/x509/pkix"
  21. "encoding/pem"
  22. "math/big"
  23. "net"
  24. "testing"
  25. "time"
  26. "github.com/go-logr/logr"
  27. "google.golang.org/grpc"
  28. "google.golang.org/grpc/credentials"
  29. corev1 "k8s.io/api/core/v1"
  30. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  31. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  32. "k8s.io/apimachinery/pkg/runtime"
  33. utilruntime "k8s.io/apimachinery/pkg/util/runtime"
  34. clientgoscheme "k8s.io/client-go/kubernetes/scheme"
  35. fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
  36. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  37. esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  38. pb "github.com/external-secrets/external-secrets/proto/provider"
  39. "github.com/external-secrets/external-secrets/runtime/clientmanager"
  40. )
  41. type pushsecretRecordingProviderServer struct {
  42. pb.UnimplementedSecretStoreProviderServer
  43. pushRequest *pb.PushSecretRequest
  44. deleteRequest *pb.DeleteSecretRequest
  45. }
  46. func (s *pushsecretRecordingProviderServer) PushSecret(_ context.Context, req *pb.PushSecretRequest) (*pb.PushSecretResponse, error) {
  47. s.pushRequest = req
  48. return &pb.PushSecretResponse{}, nil
  49. }
  50. func (s *pushsecretRecordingProviderServer) DeleteSecret(_ context.Context, req *pb.DeleteSecretRequest) (*pb.DeleteSecretResponse, error) {
  51. s.deleteRequest = req
  52. return &pb.DeleteSecretResponse{}, nil
  53. }
  54. func (s *pushsecretRecordingProviderServer) SecretExists(_ context.Context, _ *pb.SecretExistsRequest) (*pb.SecretExistsResponse, error) {
  55. return &pb.SecretExistsResponse{Exists: false}, nil
  56. }
  57. func TestResolvedStoreInfoSupportsProviderKinds(t *testing.T) {
  58. providerInfo, ok := resolvedStoreInfo(esapi.PushSecretStoreRef{
  59. Name: "provider",
  60. Kind: esv1.ProviderKindStr,
  61. }, &esv1.Provider{
  62. ObjectMeta: metav1.ObjectMeta{
  63. Name: "provider",
  64. Labels: map[string]string{"team": "a"},
  65. },
  66. })
  67. if !ok {
  68. t.Fatal("expected provider store info to resolve")
  69. }
  70. if providerInfo.Name != "provider" || providerInfo.Kind != esv1.ProviderKindStr || providerInfo.Labels["team"] != "a" {
  71. t.Fatalf("unexpected provider info: %#v", providerInfo)
  72. }
  73. clusterProviderInfo, ok := resolvedStoreInfo(esapi.PushSecretStoreRef{
  74. Name: "cluster-provider",
  75. Kind: esv1.ClusterProviderKindStr,
  76. }, &esv1.ClusterProvider{
  77. ObjectMeta: metav1.ObjectMeta{
  78. Name: "cluster-provider",
  79. Labels: map[string]string{"scope": "cluster"},
  80. },
  81. })
  82. if !ok {
  83. t.Fatal("expected cluster provider store info to resolve")
  84. }
  85. if clusterProviderInfo.Name != "cluster-provider" || clusterProviderInfo.Kind != esv1.ClusterProviderKindStr || clusterProviderInfo.Labels["scope"] != "cluster" {
  86. t.Fatalf("unexpected cluster provider info: %#v", clusterProviderInfo)
  87. }
  88. }
  89. func TestValidateDataToMatchesResolvedStoresSupportsProviderKinds(t *testing.T) {
  90. err := validateDataToMatchesResolvedStores([]esapi.PushSecretDataTo{
  91. {
  92. StoreRef: &esapi.PushSecretStoreRef{
  93. Kind: esv1.ProviderKindStr,
  94. LabelSelector: &metav1.LabelSelector{
  95. MatchLabels: map[string]string{"team": "a"},
  96. },
  97. },
  98. RemoteKey: "bundle",
  99. },
  100. }, []storeInfo{
  101. {Name: "provider", Kind: esv1.ProviderKindStr, Labels: map[string]string{"team": "a"}},
  102. })
  103. if err != nil {
  104. t.Fatalf("expected provider label selector to match, got %v", err)
  105. }
  106. err = validateDataToMatchesResolvedStores([]esapi.PushSecretDataTo{
  107. {
  108. StoreRef: &esapi.PushSecretStoreRef{
  109. Kind: esv1.ClusterProviderKindStr,
  110. LabelSelector: &metav1.LabelSelector{
  111. MatchLabels: map[string]string{"scope": "missing"},
  112. },
  113. },
  114. RemoteKey: "bundle",
  115. },
  116. }, []storeInfo{
  117. {Name: "cluster-provider", Kind: esv1.ClusterProviderKindStr, Labels: map[string]string{"scope": "cluster"}},
  118. })
  119. if err == nil || err.Error() != "dataTo[0]: labelSelector does not match any store in secretStoreRefs" {
  120. t.Fatalf("unexpected error: %v", err)
  121. }
  122. }
  123. func TestPushSecretToProvidersV2UsesProviderPath(t *testing.T) {
  124. previous := clientmanager.V2ProvidersEnabled()
  125. clientmanager.SetV2ProvidersEnabled(true)
  126. t.Cleanup(func() {
  127. clientmanager.SetV2ProvidersEnabled(previous)
  128. })
  129. scheme := newPushSecretTestScheme(t)
  130. server, address, tlsSecret := newPushSecretProviderServer(t)
  131. provider := &esv1.Provider{
  132. ObjectMeta: metav1.ObjectMeta{
  133. Name: "provider",
  134. Namespace: "tenant-a",
  135. Labels: map[string]string{"team": "a"},
  136. },
  137. Spec: esv1.ProviderSpec{
  138. Config: esv1.ProviderConfig{
  139. Address: address,
  140. ProviderRef: esv1.ProviderReference{
  141. APIVersion: "provider.external-secrets.io/v2alpha1",
  142. Kind: "Kubernetes",
  143. Name: "backend",
  144. },
  145. },
  146. },
  147. }
  148. kubeClient := fakeclient.NewClientBuilder().
  149. WithScheme(scheme).
  150. WithObjects(provider, &corev1.Secret{
  151. ObjectMeta: metav1.ObjectMeta{
  152. Name: "external-secrets-provider-tls",
  153. Namespace: "tenant-a",
  154. },
  155. Data: tlsSecret,
  156. }).
  157. Build()
  158. r := &Reconciler{Client: kubeClient, Log: logr.Discard()}
  159. mgr := clientmanager.NewManager(kubeClient, "", false)
  160. defer func() {
  161. _ = mgr.Close(context.Background())
  162. }()
  163. ps := esapi.PushSecret{
  164. ObjectMeta: metav1.ObjectMeta{
  165. Name: "pushsecret",
  166. Namespace: "tenant-a",
  167. },
  168. Spec: esapi.PushSecretSpec{
  169. SecretStoreRefs: []esapi.PushSecretStoreRef{{
  170. Name: "provider",
  171. Kind: esv1.ProviderKindStr,
  172. }},
  173. Data: []esapi.PushSecretData{{
  174. Match: esapi.PushSecretMatch{
  175. SecretKey: "token",
  176. RemoteRef: esapi.PushSecretRemoteRef{
  177. RemoteKey: "remote/path",
  178. Property: "property",
  179. },
  180. },
  181. Metadata: &apiextensionsv1.JSON{Raw: []byte(`{"owner":"eso"}`)},
  182. }},
  183. },
  184. }
  185. secret := &corev1.Secret{
  186. Data: map[string][]byte{"token": []byte("value")},
  187. }
  188. synced, err := r.PushSecretToProvidersV2(context.Background(), map[esapi.PushSecretStoreRef]interface{}{
  189. {Name: "provider", Kind: esv1.ProviderKindStr}: provider,
  190. }, ps, secret, mgr)
  191. if err != nil {
  192. t.Fatalf("PushSecretToProvidersV2() error = %v", err)
  193. }
  194. if server.pushRequest == nil {
  195. t.Fatal("expected push request to be recorded")
  196. }
  197. if server.pushRequest.SourceNamespace != "tenant-a" {
  198. t.Fatalf("unexpected source namespace: %q", server.pushRequest.SourceNamespace)
  199. }
  200. if server.pushRequest.ProviderRef == nil || server.pushRequest.ProviderRef.Name != "backend" {
  201. t.Fatalf("unexpected provider ref: %#v", server.pushRequest.ProviderRef)
  202. }
  203. if string(server.pushRequest.SecretData["token"]) != "value" {
  204. t.Fatalf("unexpected secret data: %#v", server.pushRequest.SecretData)
  205. }
  206. if server.pushRequest.PushSecretData == nil || server.pushRequest.PushSecretData.RemoteKey != "remote/path" || server.pushRequest.PushSecretData.Property != "property" {
  207. t.Fatalf("unexpected push payload: %#v", server.pushRequest.PushSecretData)
  208. }
  209. if string(server.pushRequest.PushSecretData.Metadata) != `{"owner":"eso"}` {
  210. t.Fatalf("unexpected metadata: %q", string(server.pushRequest.PushSecretData.Metadata))
  211. }
  212. if synced["Provider/provider"]["remote/path/property"].Match.SecretKey != "token" {
  213. t.Fatalf("unexpected synced map: %#v", synced)
  214. }
  215. }
  216. func TestPushSecretToProvidersV2UsesProviderNamespaceAuthScope(t *testing.T) {
  217. previous := clientmanager.V2ProvidersEnabled()
  218. clientmanager.SetV2ProvidersEnabled(true)
  219. t.Cleanup(func() {
  220. clientmanager.SetV2ProvidersEnabled(previous)
  221. })
  222. scheme := newPushSecretTestScheme(t)
  223. server, address, tlsSecret := newPushSecretProviderServer(t)
  224. const manifestNamespace = "tenant-a"
  225. const providerNamespace = "provider-config-ns"
  226. clusterProvider := &esv1.ClusterProvider{
  227. ObjectMeta: metav1.ObjectMeta{
  228. Name: "cluster-provider",
  229. Labels: map[string]string{"scope": "cluster"},
  230. },
  231. Spec: esv1.ClusterProviderSpec{
  232. Config: esv1.ProviderConfig{
  233. Address: address,
  234. ProviderRef: esv1.ProviderReference{
  235. APIVersion: "provider.external-secrets.io/v2alpha1",
  236. Kind: "Kubernetes",
  237. Name: "backend",
  238. Namespace: providerNamespace,
  239. },
  240. },
  241. AuthenticationScope: esv1.AuthenticationScopeProviderNamespace,
  242. Conditions: []esv1.ClusterSecretStoreCondition{{
  243. Namespaces: []string{manifestNamespace},
  244. }},
  245. },
  246. }
  247. kubeClient := fakeclient.NewClientBuilder().
  248. WithScheme(scheme).
  249. WithObjects(
  250. clusterProvider,
  251. &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
  252. Name: manifestNamespace,
  253. Labels: map[string]string{
  254. "kubernetes.io/metadata.name": manifestNamespace,
  255. },
  256. }},
  257. &corev1.Secret{
  258. ObjectMeta: metav1.ObjectMeta{
  259. Name: "external-secrets-provider-tls",
  260. Namespace: providerNamespace,
  261. },
  262. Data: tlsSecret,
  263. },
  264. ).
  265. Build()
  266. r := &Reconciler{Client: kubeClient, Log: logr.Discard()}
  267. mgr := clientmanager.NewManager(kubeClient, "", false)
  268. defer func() {
  269. _ = mgr.Close(context.Background())
  270. }()
  271. ps := esapi.PushSecret{
  272. ObjectMeta: metav1.ObjectMeta{
  273. Name: "pushsecret",
  274. Namespace: manifestNamespace,
  275. },
  276. Spec: esapi.PushSecretSpec{
  277. SecretStoreRefs: []esapi.PushSecretStoreRef{{
  278. Name: "cluster-provider",
  279. Kind: esv1.ClusterProviderKindStr,
  280. }},
  281. Data: []esapi.PushSecretData{{
  282. Match: esapi.PushSecretMatch{
  283. SecretKey: "token",
  284. RemoteRef: esapi.PushSecretRemoteRef{
  285. RemoteKey: "remote/path",
  286. Property: "property",
  287. },
  288. },
  289. Metadata: &apiextensionsv1.JSON{Raw: []byte(`{"owner":"eso"}`)},
  290. }},
  291. },
  292. }
  293. secret := &corev1.Secret{
  294. Data: map[string][]byte{"token": []byte("value")},
  295. }
  296. synced, err := r.PushSecretToProvidersV2(context.Background(), map[esapi.PushSecretStoreRef]interface{}{
  297. {Name: "cluster-provider", Kind: esv1.ClusterProviderKindStr}: clusterProvider,
  298. }, ps, secret, mgr)
  299. if err != nil {
  300. t.Fatalf("PushSecretToProvidersV2() error = %v", err)
  301. }
  302. if server.pushRequest == nil {
  303. t.Fatal("expected push request to be recorded")
  304. }
  305. if server.pushRequest.SourceNamespace != providerNamespace {
  306. t.Fatalf("unexpected source namespace: %q", server.pushRequest.SourceNamespace)
  307. }
  308. if server.pushRequest.ProviderRef == nil || server.pushRequest.ProviderRef.Name != "backend" || server.pushRequest.ProviderRef.Namespace != providerNamespace {
  309. t.Fatalf("unexpected provider ref: %#v", server.pushRequest.ProviderRef)
  310. }
  311. if synced["ClusterProvider/cluster-provider"]["remote/path/property"].Match.SecretKey != "token" {
  312. t.Fatalf("unexpected synced map: %#v", synced)
  313. }
  314. }
  315. func TestDeleteSecretFromProvidersV2UsesClusterProviderPath(t *testing.T) {
  316. previous := clientmanager.V2ProvidersEnabled()
  317. clientmanager.SetV2ProvidersEnabled(true)
  318. t.Cleanup(func() {
  319. clientmanager.SetV2ProvidersEnabled(previous)
  320. })
  321. scheme := newPushSecretTestScheme(t)
  322. server, address, tlsSecret := newPushSecretProviderServer(t)
  323. clusterProvider := &esv1.ClusterProvider{
  324. ObjectMeta: metav1.ObjectMeta{
  325. Name: "cluster-provider",
  326. Labels: map[string]string{"scope": "cluster"},
  327. },
  328. Spec: esv1.ClusterProviderSpec{
  329. Config: esv1.ProviderConfig{
  330. Address: address,
  331. ProviderRef: esv1.ProviderReference{
  332. APIVersion: "provider.external-secrets.io/v2alpha1",
  333. Kind: "Kubernetes",
  334. Name: "backend",
  335. },
  336. },
  337. AuthenticationScope: esv1.AuthenticationScopeManifestNamespace,
  338. Conditions: []esv1.ClusterSecretStoreCondition{{
  339. Namespaces: []string{"tenant-a"},
  340. }},
  341. },
  342. }
  343. kubeClient := fakeclient.NewClientBuilder().
  344. WithScheme(scheme).
  345. WithObjects(
  346. clusterProvider,
  347. &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
  348. Name: "tenant-a",
  349. Labels: map[string]string{
  350. "kubernetes.io/metadata.name": "tenant-a",
  351. },
  352. }},
  353. &corev1.Secret{
  354. ObjectMeta: metav1.ObjectMeta{
  355. Name: "external-secrets-provider-tls",
  356. Namespace: "tenant-a",
  357. },
  358. Data: tlsSecret,
  359. },
  360. ).
  361. Build()
  362. r := &Reconciler{Client: kubeClient, Log: logr.Discard()}
  363. ps := &esapi.PushSecret{
  364. ObjectMeta: metav1.ObjectMeta{
  365. Name: "pushsecret",
  366. Namespace: "tenant-a",
  367. },
  368. Status: esapi.PushSecretStatus{
  369. SyncedPushSecrets: esapi.SyncedPushSecretsMap{
  370. "ClusterProvider/cluster-provider": {
  371. "remote/path": {
  372. Match: esapi.PushSecretMatch{
  373. SecretKey: "token",
  374. RemoteRef: esapi.PushSecretRemoteRef{
  375. RemoteKey: "remote/path",
  376. Property: "property",
  377. },
  378. },
  379. },
  380. },
  381. },
  382. },
  383. }
  384. result, err := r.DeleteSecretFromProvidersV2(context.Background(), ps, esapi.SyncedPushSecretsMap{}, map[esapi.PushSecretStoreRef]interface{}{
  385. {Name: "cluster-provider", Kind: esv1.ClusterProviderKindStr}: clusterProvider,
  386. })
  387. if err != nil {
  388. t.Fatalf("DeleteSecretFromProvidersV2() error = %v", err)
  389. }
  390. if server.deleteRequest == nil {
  391. t.Fatal("expected delete request to be recorded")
  392. }
  393. if server.deleteRequest.SourceNamespace != "tenant-a" {
  394. t.Fatalf("unexpected source namespace: %q", server.deleteRequest.SourceNamespace)
  395. }
  396. if server.deleteRequest.RemoteRef == nil || server.deleteRequest.RemoteRef.RemoteKey != "remote/path" || server.deleteRequest.RemoteRef.Property != "property" {
  397. t.Fatalf("unexpected delete ref: %#v", server.deleteRequest.RemoteRef)
  398. }
  399. if _, ok := result["ClusterProvider/cluster-provider"]; ok {
  400. t.Fatalf("expected synced state to be cleaned up, got %#v", result)
  401. }
  402. }
  403. func TestDeleteSecretFromProvidersV2UsesProviderNamespaceAuthScope(t *testing.T) {
  404. previous := clientmanager.V2ProvidersEnabled()
  405. clientmanager.SetV2ProvidersEnabled(true)
  406. t.Cleanup(func() {
  407. clientmanager.SetV2ProvidersEnabled(previous)
  408. })
  409. scheme := newPushSecretTestScheme(t)
  410. server, address, tlsSecret := newPushSecretProviderServer(t)
  411. const manifestNamespace = "tenant-a"
  412. const providerNamespace = "provider-config-ns"
  413. clusterProvider := &esv1.ClusterProvider{
  414. ObjectMeta: metav1.ObjectMeta{
  415. Name: "cluster-provider",
  416. Labels: map[string]string{"scope": "cluster"},
  417. },
  418. Spec: esv1.ClusterProviderSpec{
  419. Config: esv1.ProviderConfig{
  420. Address: address,
  421. ProviderRef: esv1.ProviderReference{
  422. APIVersion: "provider.external-secrets.io/v2alpha1",
  423. Kind: "Kubernetes",
  424. Name: "backend",
  425. Namespace: providerNamespace,
  426. },
  427. },
  428. AuthenticationScope: esv1.AuthenticationScopeProviderNamespace,
  429. Conditions: []esv1.ClusterSecretStoreCondition{{
  430. Namespaces: []string{manifestNamespace},
  431. }},
  432. },
  433. }
  434. kubeClient := fakeclient.NewClientBuilder().
  435. WithScheme(scheme).
  436. WithObjects(
  437. clusterProvider,
  438. &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
  439. Name: manifestNamespace,
  440. Labels: map[string]string{
  441. "kubernetes.io/metadata.name": manifestNamespace,
  442. },
  443. }},
  444. &corev1.Secret{
  445. ObjectMeta: metav1.ObjectMeta{
  446. Name: "external-secrets-provider-tls",
  447. Namespace: providerNamespace,
  448. },
  449. Data: tlsSecret,
  450. },
  451. ).
  452. Build()
  453. r := &Reconciler{Client: kubeClient, Log: logr.Discard()}
  454. ps := &esapi.PushSecret{
  455. ObjectMeta: metav1.ObjectMeta{
  456. Name: "pushsecret",
  457. Namespace: manifestNamespace,
  458. },
  459. Status: esapi.PushSecretStatus{
  460. SyncedPushSecrets: esapi.SyncedPushSecretsMap{
  461. "ClusterProvider/cluster-provider": {
  462. "remote/path": {
  463. Match: esapi.PushSecretMatch{
  464. SecretKey: "token",
  465. RemoteRef: esapi.PushSecretRemoteRef{
  466. RemoteKey: "remote/path",
  467. Property: "property",
  468. },
  469. },
  470. },
  471. },
  472. },
  473. },
  474. }
  475. result, err := r.DeleteSecretFromProvidersV2(context.Background(), ps, esapi.SyncedPushSecretsMap{}, map[esapi.PushSecretStoreRef]interface{}{
  476. {Name: "cluster-provider", Kind: esv1.ClusterProviderKindStr}: clusterProvider,
  477. })
  478. if err != nil {
  479. t.Fatalf("DeleteSecretFromProvidersV2() error = %v", err)
  480. }
  481. if server.deleteRequest == nil {
  482. t.Fatal("expected delete request to be recorded")
  483. }
  484. if server.deleteRequest.SourceNamespace != providerNamespace {
  485. t.Fatalf("unexpected source namespace: %q", server.deleteRequest.SourceNamespace)
  486. }
  487. if server.deleteRequest.ProviderRef == nil || server.deleteRequest.ProviderRef.Namespace != providerNamespace {
  488. t.Fatalf("unexpected provider ref: %#v", server.deleteRequest.ProviderRef)
  489. }
  490. if _, ok := result["ClusterProvider/cluster-provider"]; ok {
  491. t.Fatalf("expected synced state to be cleaned up, got %#v", result)
  492. }
  493. }
  494. func TestDeleteSecretFromProvidersV2DeletesOnlyRemovedEntriesForManifestScope(t *testing.T) {
  495. previous := clientmanager.V2ProvidersEnabled()
  496. clientmanager.SetV2ProvidersEnabled(true)
  497. t.Cleanup(func() {
  498. clientmanager.SetV2ProvidersEnabled(previous)
  499. })
  500. scheme := newPushSecretTestScheme(t)
  501. server, address, tlsSecret := newPushSecretProviderServer(t)
  502. clusterProvider := &esv1.ClusterProvider{
  503. ObjectMeta: metav1.ObjectMeta{
  504. Name: "cluster-provider",
  505. },
  506. Spec: esv1.ClusterProviderSpec{
  507. Config: esv1.ProviderConfig{
  508. Address: address,
  509. ProviderRef: esv1.ProviderReference{
  510. APIVersion: "provider.external-secrets.io/v2alpha1",
  511. Kind: "Kubernetes",
  512. Name: "backend",
  513. },
  514. },
  515. AuthenticationScope: esv1.AuthenticationScopeManifestNamespace,
  516. Conditions: []esv1.ClusterSecretStoreCondition{{
  517. Namespaces: []string{"tenant-a"},
  518. }},
  519. },
  520. }
  521. kubeClient := fakeclient.NewClientBuilder().
  522. WithScheme(scheme).
  523. WithObjects(
  524. clusterProvider,
  525. &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
  526. Name: "tenant-a",
  527. Labels: map[string]string{
  528. "kubernetes.io/metadata.name": "tenant-a",
  529. },
  530. }},
  531. &corev1.Secret{
  532. ObjectMeta: metav1.ObjectMeta{
  533. Name: "external-secrets-provider-tls",
  534. Namespace: "tenant-a",
  535. },
  536. Data: tlsSecret,
  537. },
  538. ).
  539. Build()
  540. r := &Reconciler{Client: kubeClient, Log: logr.Discard()}
  541. ps := &esapi.PushSecret{
  542. ObjectMeta: metav1.ObjectMeta{
  543. Name: "pushsecret",
  544. Namespace: "tenant-a",
  545. },
  546. Status: esapi.PushSecretStatus{
  547. SyncedPushSecrets: esapi.SyncedPushSecretsMap{
  548. "ClusterProvider/cluster-provider": {
  549. "remote/keep/property": {
  550. Match: esapi.PushSecretMatch{
  551. SecretKey: "keep",
  552. RemoteRef: esapi.PushSecretRemoteRef{
  553. RemoteKey: "remote/keep",
  554. Property: "property",
  555. },
  556. },
  557. },
  558. "remote/delete/property": {
  559. Match: esapi.PushSecretMatch{
  560. SecretKey: "delete",
  561. RemoteRef: esapi.PushSecretRemoteRef{
  562. RemoteKey: "remote/delete",
  563. Property: "property",
  564. },
  565. },
  566. },
  567. },
  568. },
  569. },
  570. }
  571. newMap := esapi.SyncedPushSecretsMap{
  572. "ClusterProvider/cluster-provider": {
  573. "remote/keep/property": ps.Status.SyncedPushSecrets["ClusterProvider/cluster-provider"]["remote/keep/property"],
  574. },
  575. }
  576. result, err := r.DeleteSecretFromProvidersV2(context.Background(), ps, newMap, map[esapi.PushSecretStoreRef]interface{}{
  577. {Name: "cluster-provider", Kind: esv1.ClusterProviderKindStr}: clusterProvider,
  578. })
  579. if err != nil {
  580. t.Fatalf("DeleteSecretFromProvidersV2() error = %v", err)
  581. }
  582. if server.deleteRequest == nil {
  583. t.Fatal("expected delete request to be recorded")
  584. }
  585. if server.deleteRequest.SourceNamespace != "tenant-a" {
  586. t.Fatalf("unexpected source namespace: %q", server.deleteRequest.SourceNamespace)
  587. }
  588. if server.deleteRequest.RemoteRef == nil || server.deleteRequest.RemoteRef.RemoteKey != "remote/delete" || server.deleteRequest.RemoteRef.Property != "property" {
  589. t.Fatalf("unexpected delete ref: %#v", server.deleteRequest.RemoteRef)
  590. }
  591. storeState, ok := result["ClusterProvider/cluster-provider"]
  592. if !ok {
  593. t.Fatalf("expected synced state for cluster provider, got %#v", result)
  594. }
  595. if len(storeState) != 1 {
  596. t.Fatalf("expected one remaining synced entry, got %#v", storeState)
  597. }
  598. if _, ok := storeState["remote/keep/property"]; !ok {
  599. t.Fatalf("expected keep entry to remain, got %#v", storeState)
  600. }
  601. if _, ok := storeState["remote/delete/property"]; ok {
  602. t.Fatalf("expected delete entry to be removed, got %#v", storeState)
  603. }
  604. }
  605. func TestDeleteSecretFromProvidersV2DeletesOnlyRemovedEntriesForProviderNamespaceScope(t *testing.T) {
  606. previous := clientmanager.V2ProvidersEnabled()
  607. clientmanager.SetV2ProvidersEnabled(true)
  608. t.Cleanup(func() {
  609. clientmanager.SetV2ProvidersEnabled(previous)
  610. })
  611. scheme := newPushSecretTestScheme(t)
  612. server, address, tlsSecret := newPushSecretProviderServer(t)
  613. const manifestNamespace = "tenant-a"
  614. const providerNamespace = "provider-config-ns"
  615. clusterProvider := &esv1.ClusterProvider{
  616. ObjectMeta: metav1.ObjectMeta{
  617. Name: "cluster-provider",
  618. },
  619. Spec: esv1.ClusterProviderSpec{
  620. Config: esv1.ProviderConfig{
  621. Address: address,
  622. ProviderRef: esv1.ProviderReference{
  623. APIVersion: "provider.external-secrets.io/v2alpha1",
  624. Kind: "Kubernetes",
  625. Name: "backend",
  626. Namespace: providerNamespace,
  627. },
  628. },
  629. AuthenticationScope: esv1.AuthenticationScopeProviderNamespace,
  630. Conditions: []esv1.ClusterSecretStoreCondition{{
  631. Namespaces: []string{manifestNamespace},
  632. }},
  633. },
  634. }
  635. kubeClient := fakeclient.NewClientBuilder().
  636. WithScheme(scheme).
  637. WithObjects(
  638. clusterProvider,
  639. &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
  640. Name: manifestNamespace,
  641. Labels: map[string]string{
  642. "kubernetes.io/metadata.name": manifestNamespace,
  643. },
  644. }},
  645. &corev1.Secret{
  646. ObjectMeta: metav1.ObjectMeta{
  647. Name: "external-secrets-provider-tls",
  648. Namespace: providerNamespace,
  649. },
  650. Data: tlsSecret,
  651. },
  652. ).
  653. Build()
  654. r := &Reconciler{Client: kubeClient, Log: logr.Discard()}
  655. ps := &esapi.PushSecret{
  656. ObjectMeta: metav1.ObjectMeta{
  657. Name: "pushsecret",
  658. Namespace: manifestNamespace,
  659. },
  660. Status: esapi.PushSecretStatus{
  661. SyncedPushSecrets: esapi.SyncedPushSecretsMap{
  662. "ClusterProvider/cluster-provider": {
  663. "remote/keep/property": {
  664. Match: esapi.PushSecretMatch{
  665. SecretKey: "keep",
  666. RemoteRef: esapi.PushSecretRemoteRef{
  667. RemoteKey: "remote/keep",
  668. Property: "property",
  669. },
  670. },
  671. },
  672. "remote/delete/property": {
  673. Match: esapi.PushSecretMatch{
  674. SecretKey: "delete",
  675. RemoteRef: esapi.PushSecretRemoteRef{
  676. RemoteKey: "remote/delete",
  677. Property: "property",
  678. },
  679. },
  680. },
  681. },
  682. },
  683. },
  684. }
  685. newMap := esapi.SyncedPushSecretsMap{
  686. "ClusterProvider/cluster-provider": {
  687. "remote/keep/property": ps.Status.SyncedPushSecrets["ClusterProvider/cluster-provider"]["remote/keep/property"],
  688. },
  689. }
  690. result, err := r.DeleteSecretFromProvidersV2(context.Background(), ps, newMap, map[esapi.PushSecretStoreRef]interface{}{
  691. {Name: "cluster-provider", Kind: esv1.ClusterProviderKindStr}: clusterProvider,
  692. })
  693. if err != nil {
  694. t.Fatalf("DeleteSecretFromProvidersV2() error = %v", err)
  695. }
  696. if server.deleteRequest == nil {
  697. t.Fatal("expected delete request to be recorded")
  698. }
  699. if server.deleteRequest.SourceNamespace != providerNamespace {
  700. t.Fatalf("unexpected source namespace: %q", server.deleteRequest.SourceNamespace)
  701. }
  702. if server.deleteRequest.ProviderRef == nil || server.deleteRequest.ProviderRef.Namespace != providerNamespace {
  703. t.Fatalf("unexpected provider ref: %#v", server.deleteRequest.ProviderRef)
  704. }
  705. if server.deleteRequest.RemoteRef == nil || server.deleteRequest.RemoteRef.RemoteKey != "remote/delete" || server.deleteRequest.RemoteRef.Property != "property" {
  706. t.Fatalf("unexpected delete ref: %#v", server.deleteRequest.RemoteRef)
  707. }
  708. storeState, ok := result["ClusterProvider/cluster-provider"]
  709. if !ok {
  710. t.Fatalf("expected synced state for cluster provider, got %#v", result)
  711. }
  712. if len(storeState) != 1 {
  713. t.Fatalf("expected one remaining synced entry, got %#v", storeState)
  714. }
  715. if _, ok := storeState["remote/keep/property"]; !ok {
  716. t.Fatalf("expected keep entry to remain, got %#v", storeState)
  717. }
  718. if _, ok := storeState["remote/delete/property"]; ok {
  719. t.Fatalf("expected delete entry to be removed, got %#v", storeState)
  720. }
  721. }
  722. func newPushSecretTestScheme(t *testing.T) *runtime.Scheme {
  723. t.Helper()
  724. scheme := runtime.NewScheme()
  725. utilruntime.Must(clientgoscheme.AddToScheme(scheme))
  726. utilruntime.Must(esv1.AddToScheme(scheme))
  727. utilruntime.Must(esapi.AddToScheme(scheme))
  728. return scheme
  729. }
  730. func newPushSecretProviderServer(t *testing.T) (*pushsecretRecordingProviderServer, string, map[string][]byte) {
  731. t.Helper()
  732. serverCert, serverKey, clientCert, clientKey, caCert := newPushSecretTLSArtifacts(t, "127.0.0.1")
  733. caPool := x509.NewCertPool()
  734. if !caPool.AppendCertsFromPEM(caCert) {
  735. t.Fatal("failed to append CA cert")
  736. }
  737. tlsCert, err := tls.X509KeyPair(serverCert, serverKey)
  738. if err != nil {
  739. t.Fatalf("X509KeyPair() error = %v", err)
  740. }
  741. lis, err := net.Listen("tcp", "127.0.0.1:0")
  742. if err != nil {
  743. t.Fatalf("Listen() error = %v", err)
  744. }
  745. server := &pushsecretRecordingProviderServer{}
  746. grpcServer := grpc.NewServer(grpc.Creds(credentials.NewTLS(&tls.Config{
  747. MinVersion: tls.VersionTLS12,
  748. Certificates: []tls.Certificate{tlsCert},
  749. ClientCAs: caPool,
  750. ClientAuth: tls.RequireAndVerifyClientCert,
  751. })))
  752. pb.RegisterSecretStoreProviderServer(grpcServer, server)
  753. go func() {
  754. _ = grpcServer.Serve(lis)
  755. }()
  756. t.Cleanup(func() {
  757. grpcServer.Stop()
  758. _ = lis.Close()
  759. })
  760. return server, lis.Addr().String(), map[string][]byte{
  761. "ca.crt": caCert,
  762. "client.crt": clientCert,
  763. "client.key": clientKey,
  764. }
  765. }
  766. func newPushSecretTLSArtifacts(t *testing.T, host string) (serverCertPEM, serverKeyPEM, clientCertPEM, clientKeyPEM, caCertPEM []byte) {
  767. t.Helper()
  768. caKey, err := rsa.GenerateKey(rand.Reader, 2048)
  769. if err != nil {
  770. t.Fatalf("GenerateKey() error = %v", err)
  771. }
  772. caTemplate := &x509.Certificate{
  773. SerialNumber: big.NewInt(1),
  774. Subject: pkix.Name{
  775. CommonName: "pushsecret-test-ca",
  776. },
  777. NotBefore: time.Now().Add(-time.Hour),
  778. NotAfter: time.Now().Add(24 * time.Hour),
  779. KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
  780. BasicConstraintsValid: true,
  781. IsCA: true,
  782. }
  783. caDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey)
  784. if err != nil {
  785. t.Fatalf("CreateCertificate() error = %v", err)
  786. }
  787. caCert, err := x509.ParseCertificate(caDER)
  788. if err != nil {
  789. t.Fatalf("ParseCertificate() error = %v", err)
  790. }
  791. serverCertPEM, serverKeyPEM = newPushSecretSignedTLSCert(t, caCert, caKey, 2, host, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth})
  792. clientCertPEM, clientKeyPEM = newPushSecretSignedTLSCert(t, caCert, caKey, 3, host, []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth})
  793. caCertPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caDER})
  794. return serverCertPEM, serverKeyPEM, clientCertPEM, clientKeyPEM, caCertPEM
  795. }
  796. func newPushSecretSignedTLSCert(t *testing.T, caCert *x509.Certificate, caKey *rsa.PrivateKey, serial int64, host string, usages []x509.ExtKeyUsage) ([]byte, []byte) {
  797. t.Helper()
  798. key, err := rsa.GenerateKey(rand.Reader, 2048)
  799. if err != nil {
  800. t.Fatalf("GenerateKey() error = %v", err)
  801. }
  802. template := &x509.Certificate{
  803. SerialNumber: big.NewInt(serial),
  804. Subject: pkix.Name{
  805. CommonName: host,
  806. },
  807. NotBefore: time.Now().Add(-time.Hour),
  808. NotAfter: time.Now().Add(24 * time.Hour),
  809. KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
  810. ExtKeyUsage: usages,
  811. }
  812. if ip := net.ParseIP(host); ip != nil {
  813. template.IPAddresses = []net.IP{ip}
  814. } else {
  815. template.DNSNames = []string{host}
  816. }
  817. der, err := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey)
  818. if err != nil {
  819. t.Fatalf("CreateCertificate() error = %v", err)
  820. }
  821. return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}),
  822. pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
  823. }