pushsecret_controller_v2_test.go 27 KB

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