client_test.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  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 grpc
  14. import (
  15. "bytes"
  16. "context"
  17. "net"
  18. "testing"
  19. "time"
  20. "google.golang.org/grpc"
  21. "google.golang.org/grpc/credentials/insecure"
  22. "google.golang.org/grpc/test/bufconn"
  23. "google.golang.org/protobuf/reflect/protoreflect"
  24. corev1 "k8s.io/api/core/v1"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  27. pb "github.com/external-secrets/external-secrets/proto/provider"
  28. v2 "github.com/external-secrets/external-secrets/providers/v2/common"
  29. )
  30. const (
  31. bufSize = 1024 * 1024
  32. testCompatibilityStoreName = "compat-store"
  33. testSourceNamespace = "tenant-a"
  34. )
  35. type mockServer struct {
  36. pb.UnimplementedSecretStoreProviderServer
  37. getSecretResponse *pb.GetSecretResponse
  38. getSecretRequest *pb.GetSecretRequest
  39. getSecretMapResponse *pb.GetSecretMapResponse
  40. getSecretMapRequest *pb.GetSecretMapRequest
  41. getAllSecretsResponse *pb.GetAllSecretsResponse
  42. getAllSecretsRequest *pb.GetAllSecretsRequest
  43. pushSecretRequest *pb.PushSecretRequest
  44. deleteRequest *pb.DeleteSecretRequest
  45. existsRequest *pb.SecretExistsRequest
  46. existsResponse *pb.SecretExistsResponse
  47. validateResponse *pb.ValidateResponse
  48. validateRequest *pb.ValidateRequest
  49. capabilitiesResponse *pb.CapabilitiesResponse
  50. capabilitiesRequest *pb.CapabilitiesRequest
  51. getAllSecretsDeadline time.Duration
  52. }
  53. func (m *mockServer) GetSecret(_ context.Context, req *pb.GetSecretRequest) (*pb.GetSecretResponse, error) {
  54. m.getSecretRequest = req
  55. if m.getSecretResponse != nil {
  56. return m.getSecretResponse, nil
  57. }
  58. return &pb.GetSecretResponse{Value: []byte("test-secret-value")}, nil
  59. }
  60. func (m *mockServer) GetSecretMap(_ context.Context, req *pb.GetSecretMapRequest) (*pb.GetSecretMapResponse, error) {
  61. m.getSecretMapRequest = req
  62. if m.getSecretMapResponse != nil {
  63. return m.getSecretMapResponse, nil
  64. }
  65. return &pb.GetSecretMapResponse{
  66. Secrets: map[string][]byte{"foo": []byte("bar")},
  67. }, nil
  68. }
  69. func (m *mockServer) GetAllSecrets(ctx context.Context, req *pb.GetAllSecretsRequest) (*pb.GetAllSecretsResponse, error) {
  70. m.getAllSecretsRequest = req
  71. if deadline, ok := ctx.Deadline(); ok {
  72. m.getAllSecretsDeadline = time.Until(deadline)
  73. }
  74. if m.getAllSecretsResponse != nil {
  75. return m.getAllSecretsResponse, nil
  76. }
  77. return &pb.GetAllSecretsResponse{
  78. Secrets: map[string][]byte{"db-password": []byte("value")},
  79. }, nil
  80. }
  81. func (m *mockServer) PushSecret(_ context.Context, req *pb.PushSecretRequest) (*pb.PushSecretResponse, error) {
  82. m.pushSecretRequest = req
  83. return &pb.PushSecretResponse{}, nil
  84. }
  85. func (m *mockServer) DeleteSecret(_ context.Context, req *pb.DeleteSecretRequest) (*pb.DeleteSecretResponse, error) {
  86. m.deleteRequest = req
  87. return &pb.DeleteSecretResponse{}, nil
  88. }
  89. func (m *mockServer) SecretExists(_ context.Context, req *pb.SecretExistsRequest) (*pb.SecretExistsResponse, error) {
  90. m.existsRequest = req
  91. if m.existsResponse != nil {
  92. return m.existsResponse, nil
  93. }
  94. return &pb.SecretExistsResponse{Exists: true}, nil
  95. }
  96. func (m *mockServer) Validate(_ context.Context, req *pb.ValidateRequest) (*pb.ValidateResponse, error) {
  97. m.validateRequest = req
  98. if m.validateResponse != nil {
  99. return m.validateResponse, nil
  100. }
  101. return &pb.ValidateResponse{Valid: true}, nil
  102. }
  103. func (m *mockServer) Capabilities(_ context.Context, req *pb.CapabilitiesRequest) (*pb.CapabilitiesResponse, error) {
  104. m.capabilitiesRequest = req
  105. if m.capabilitiesResponse != nil {
  106. return m.capabilitiesResponse, nil
  107. }
  108. return &pb.CapabilitiesResponse{Capabilities: pb.SecretStoreCapabilities_READ_WRITE}, nil
  109. }
  110. func setupTestServer(t *testing.T, mock *mockServer) (*grpc.ClientConn, func()) {
  111. t.Helper()
  112. lis := bufconn.Listen(bufSize)
  113. baseServer := grpc.NewServer()
  114. pb.RegisterSecretStoreProviderServer(baseServer, mock)
  115. go func() {
  116. if err := baseServer.Serve(lis); err != nil {
  117. t.Logf("Server exited with error: %v", err)
  118. }
  119. }()
  120. conn, err := grpc.DialContext(context.Background(), "",
  121. grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
  122. return lis.Dial()
  123. }),
  124. grpc.WithTransportCredentials(insecure.NewCredentials()))
  125. if err != nil {
  126. t.Fatalf("Failed to dial bufnet: %v", err)
  127. }
  128. cleanup := func() {
  129. _ = conn.Close()
  130. baseServer.Stop()
  131. _ = lis.Close()
  132. }
  133. return conn, cleanup
  134. }
  135. func TestClientGetSecretSendsProviderReferenceAndNamespace(t *testing.T) {
  136. mock := &mockServer{}
  137. conn, cleanup := setupTestServer(t, mock)
  138. defer cleanup()
  139. client := NewClientWithConn(conn)
  140. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderStoreKindStr}
  141. ref := esv1.ExternalSecretDataRemoteRef{
  142. Key: "test-key",
  143. Version: "v1",
  144. Property: "password",
  145. DecodingStrategy: esv1.ExternalSecretDecodeBase64,
  146. MetadataPolicy: esv1.ExternalSecretMetadataPolicyFetch,
  147. }
  148. value, err := client.GetSecret(context.Background(), ref, providerRef, nil, testSourceNamespace)
  149. if err != nil {
  150. t.Fatalf("GetSecret failed: %v", err)
  151. }
  152. if string(value) != "test-secret-value" {
  153. t.Fatalf("expected test-secret-value, got %q", string(value))
  154. }
  155. if mock.getSecretRequest == nil {
  156. t.Fatal("expected get secret request to be recorded")
  157. }
  158. assertProviderRefEqual(t, mock.getSecretRequest.ProviderRef, providerRef)
  159. if mock.getSecretRequest.SourceNamespace != testSourceNamespace {
  160. t.Fatalf("unexpected source namespace: %q", mock.getSecretRequest.SourceNamespace)
  161. }
  162. if mock.getSecretRequest.RemoteRef.Key != "test-key" || mock.getSecretRequest.RemoteRef.Version != "v1" || mock.getSecretRequest.RemoteRef.Property != "password" {
  163. t.Fatalf("unexpected remote ref: %#v", mock.getSecretRequest.RemoteRef)
  164. }
  165. if mock.getSecretRequest.RemoteRef.DecodingStrategy != string(esv1.ExternalSecretDecodeBase64) {
  166. t.Fatalf("unexpected decoding strategy: %q", mock.getSecretRequest.RemoteRef.DecodingStrategy)
  167. }
  168. if mock.getSecretRequest.RemoteRef.MetadataPolicy != string(esv1.ExternalSecretMetadataPolicyFetch) {
  169. t.Fatalf("unexpected metadata policy: %q", mock.getSecretRequest.RemoteRef.MetadataPolicy)
  170. }
  171. }
  172. func TestClientGetSecretMapSendsProviderReferenceAndNamespace(t *testing.T) {
  173. mock := &mockServer{
  174. getSecretMapResponse: &pb.GetSecretMapResponse{
  175. Secrets: map[string][]byte{"a": []byte("b")},
  176. },
  177. }
  178. conn, cleanup := setupTestServer(t, mock)
  179. defer cleanup()
  180. client := NewClientWithConn(conn)
  181. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderStoreKindStr}
  182. value, err := client.GetSecretMap(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "test-key"}, providerRef, nil, testSourceNamespace)
  183. if err != nil {
  184. t.Fatalf("GetSecretMap failed: %v", err)
  185. }
  186. if string(value["a"]) != "b" {
  187. t.Fatalf("expected map[a]=b, got %#v", value)
  188. }
  189. if mock.getSecretMapRequest == nil {
  190. t.Fatal("expected get secret map request to be recorded")
  191. }
  192. if mock.getSecretMapRequest.SourceNamespace != testSourceNamespace {
  193. t.Fatalf("unexpected request: %#v", mock.getSecretMapRequest)
  194. }
  195. assertProviderRefEqual(t, mock.getSecretMapRequest.ProviderRef, providerRef)
  196. }
  197. func TestClientGetAllSecretsSendsFindCriteria(t *testing.T) {
  198. mock := &mockServer{}
  199. conn, cleanup := setupTestServer(t, mock)
  200. defer cleanup()
  201. client := NewClientWithConn(conn)
  202. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderStoreKindStr}
  203. path := "/team-a"
  204. secrets, err := client.GetAllSecrets(context.Background(), esv1.ExternalSecretFind{
  205. Tags: map[string]string{"team": "a"},
  206. Path: &path,
  207. Name: &esv1.FindName{RegExp: "db-.*"},
  208. }, providerRef, nil, testSourceNamespace)
  209. if err != nil {
  210. t.Fatalf("GetAllSecrets failed: %v", err)
  211. }
  212. if string(secrets["db-password"]) != "value" {
  213. t.Fatalf("unexpected secrets: %#v", secrets)
  214. }
  215. if mock.getAllSecretsRequest == nil {
  216. t.Fatal("expected get all secrets request to be recorded")
  217. }
  218. if mock.getAllSecretsRequest.SourceNamespace != testSourceNamespace {
  219. t.Fatalf("unexpected request: %#v", mock.getAllSecretsRequest)
  220. }
  221. assertProviderRefEqual(t, mock.getAllSecretsRequest.ProviderRef, providerRef)
  222. if mock.getAllSecretsRequest.Find.Path != "/team-a" {
  223. t.Fatalf("unexpected path: %q", mock.getAllSecretsRequest.Find.Path)
  224. }
  225. if mock.getAllSecretsRequest.Find.Name == nil || mock.getAllSecretsRequest.Find.Name.Regexp != "db-.*" {
  226. t.Fatalf("unexpected name matcher: %#v", mock.getAllSecretsRequest.Find.Name)
  227. }
  228. }
  229. func TestClientGetAllSecretsUsesExtendedTimeout(t *testing.T) {
  230. mock := &mockServer{}
  231. conn, cleanup := setupTestServer(t, mock)
  232. defer cleanup()
  233. client := NewClientWithConn(conn)
  234. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderStoreKindStr}
  235. _, err := client.GetAllSecrets(context.Background(), esv1.ExternalSecretFind{
  236. Name: &esv1.FindName{RegExp: "db-.*"},
  237. }, providerRef, nil, testSourceNamespace)
  238. if err != nil {
  239. t.Fatalf("GetAllSecrets failed: %v", err)
  240. }
  241. if mock.getAllSecretsDeadline <= defaultTimeout {
  242. t.Fatalf("expected GetAllSecrets timeout to exceed default timeout %s, got %s", defaultTimeout, mock.getAllSecretsDeadline)
  243. }
  244. }
  245. func TestClientGetSecretSendsCompatibilityStore(t *testing.T) {
  246. mock := &mockServer{}
  247. conn, cleanup := setupTestServer(t, mock)
  248. defer cleanup()
  249. client := NewClientWithConn(conn)
  250. compatibilityStore := &pb.CompatibilityStore{
  251. StoreName: testCompatibilityStoreName,
  252. StoreNamespace: "config-ns",
  253. StoreKind: esv1.SecretStoreKind,
  254. StoreUid: "uid-1",
  255. StoreGeneration: 7,
  256. StoreSpecJson: []byte(`{"provider":{"fake":{"data":[{"key":"test-key","value":"test-secret-value"}]}}}`),
  257. }
  258. value, err := client.GetSecret(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "test-key"}, nil, compatibilityStore, testSourceNamespace)
  259. if err != nil {
  260. t.Fatalf("GetSecret failed: %v", err)
  261. }
  262. if string(value) != "test-secret-value" {
  263. t.Fatalf("expected test-secret-value, got %q", string(value))
  264. }
  265. if mock.getSecretRequest == nil {
  266. t.Fatal("expected get secret request to be recorded")
  267. }
  268. if mock.getSecretRequest.ProviderRef != nil {
  269. t.Fatalf("expected provider ref to be omitted, got %#v", mock.getSecretRequest.ProviderRef)
  270. }
  271. if mock.getSecretRequest.CompatibilityStore == nil {
  272. t.Fatal("expected compatibility store to be recorded")
  273. }
  274. if mock.getSecretRequest.CompatibilityStore.StoreName != testCompatibilityStoreName {
  275. t.Fatalf("unexpected compatibility store: %#v", mock.getSecretRequest.CompatibilityStore)
  276. }
  277. }
  278. func TestClientGetSecretMapSendsCompatibilityStore(t *testing.T) {
  279. mock := &mockServer{}
  280. conn, cleanup := setupTestServer(t, mock)
  281. defer cleanup()
  282. client := NewClientWithConn(conn)
  283. compatibilityStore := &pb.CompatibilityStore{
  284. StoreName: testCompatibilityStoreName,
  285. StoreNamespace: "config-ns",
  286. StoreKind: esv1.SecretStoreKind,
  287. StoreUid: "uid-1",
  288. StoreGeneration: 7,
  289. StoreSpecJson: []byte(`{"provider":{"fake":{"data":[{"key":"test-key","value":"test-secret-value"}]}}}`),
  290. }
  291. _, err := client.GetSecretMap(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "test-key"}, nil, compatibilityStore, testSourceNamespace)
  292. if err != nil {
  293. t.Fatalf("GetSecretMap failed: %v", err)
  294. }
  295. if mock.getSecretMapRequest == nil {
  296. t.Fatal("expected get secret map request to be recorded")
  297. }
  298. if mock.getSecretMapRequest.ProviderRef != nil {
  299. t.Fatalf("expected provider ref to be omitted, got %#v", mock.getSecretMapRequest.ProviderRef)
  300. }
  301. if mock.getSecretMapRequest.CompatibilityStore == nil {
  302. t.Fatal("expected compatibility store to be recorded")
  303. }
  304. if mock.getSecretMapRequest.CompatibilityStore.StoreName != testCompatibilityStoreName {
  305. t.Fatalf("unexpected compatibility store: %#v", mock.getSecretMapRequest.CompatibilityStore)
  306. }
  307. }
  308. func TestClientGetAllSecretsSendsCompatibilityStore(t *testing.T) {
  309. mock := &mockServer{}
  310. conn, cleanup := setupTestServer(t, mock)
  311. defer cleanup()
  312. client := NewClientWithConn(conn)
  313. compatibilityStore := &pb.CompatibilityStore{
  314. StoreName: testCompatibilityStoreName,
  315. StoreNamespace: "config-ns",
  316. StoreKind: esv1.SecretStoreKind,
  317. StoreUid: "uid-1",
  318. StoreGeneration: 7,
  319. StoreSpecJson: []byte(`{"provider":{"fake":{"data":[{"key":"test-key","value":"test-secret-value"}]}}}`),
  320. }
  321. _, err := client.GetAllSecrets(context.Background(), esv1.ExternalSecretFind{}, nil, compatibilityStore, testSourceNamespace)
  322. if err != nil {
  323. t.Fatalf("GetAllSecrets failed: %v", err)
  324. }
  325. if mock.getAllSecretsRequest == nil {
  326. t.Fatal("expected get all secrets request to be recorded")
  327. }
  328. if mock.getAllSecretsRequest.ProviderRef != nil {
  329. t.Fatalf("expected provider ref to be omitted, got %#v", mock.getAllSecretsRequest.ProviderRef)
  330. }
  331. if mock.getAllSecretsRequest.CompatibilityStore == nil {
  332. t.Fatal("expected compatibility store to be recorded")
  333. }
  334. if mock.getAllSecretsRequest.CompatibilityStore.StoreName != testCompatibilityStoreName {
  335. t.Fatalf("unexpected compatibility store: %#v", mock.getAllSecretsRequest.CompatibilityStore)
  336. }
  337. }
  338. func TestClientValidateSendsCompatibilityStore(t *testing.T) {
  339. mock := &mockServer{}
  340. conn, cleanup := setupTestServer(t, mock)
  341. defer cleanup()
  342. client := NewClientWithConn(conn)
  343. compatibilityStore := &pb.CompatibilityStore{
  344. StoreName: testCompatibilityStoreName,
  345. StoreNamespace: "config-ns",
  346. StoreKind: esv1.SecretStoreKind,
  347. StoreUid: "uid-1",
  348. StoreGeneration: 7,
  349. StoreSpecJson: []byte(`{"provider":{"fake":{"data":[{"key":"test-key","value":"test-secret-value"}]}}}`),
  350. }
  351. err := client.Validate(context.Background(), nil, compatibilityStore, testSourceNamespace)
  352. if err != nil {
  353. t.Fatalf("Validate failed: %v", err)
  354. }
  355. if mock.validateRequest == nil {
  356. t.Fatal("expected validate request to be recorded")
  357. }
  358. if mock.validateRequest.ProviderRef != nil {
  359. t.Fatalf("expected provider ref to be omitted, got %#v", mock.validateRequest.ProviderRef)
  360. }
  361. if mock.validateRequest.CompatibilityStore == nil {
  362. t.Fatal("expected compatibility store to be recorded")
  363. }
  364. }
  365. func TestClientPushSecretSendsCompatibilityStore(t *testing.T) {
  366. mock := &mockServer{}
  367. conn, cleanup := setupTestServer(t, mock)
  368. defer cleanup()
  369. client := NewClientWithConn(conn)
  370. compatibilityStore := &pb.CompatibilityStore{
  371. StoreName: testCompatibilityStoreName,
  372. StoreNamespace: "config-ns",
  373. StoreKind: esv1.SecretStoreKind,
  374. StoreUid: "uid-1",
  375. StoreGeneration: 7,
  376. StoreSpecJson: []byte(`{"provider":{"fake":{"data":[{"key":"test-key","value":"test-secret-value"}]}}}`),
  377. }
  378. err := client.PushSecret(context.Background(), &corev1.Secret{
  379. Data: map[string][]byte{"token": []byte("value")},
  380. }, &pb.PushSecretData{RemoteKey: "remote", SecretKey: "token"}, nil, compatibilityStore, testSourceNamespace)
  381. if err != nil {
  382. t.Fatalf("PushSecret failed: %v", err)
  383. }
  384. if mock.pushSecretRequest == nil {
  385. t.Fatal("expected push secret request to be recorded")
  386. }
  387. if mock.pushSecretRequest.ProviderRef != nil {
  388. t.Fatalf("expected provider ref to be omitted, got %#v", mock.pushSecretRequest.ProviderRef)
  389. }
  390. if mock.pushSecretRequest.CompatibilityStore == nil {
  391. t.Fatal("expected compatibility store to be recorded")
  392. }
  393. }
  394. func TestClientDeleteSecretSendsCompatibilityStore(t *testing.T) {
  395. mock := &mockServer{}
  396. conn, cleanup := setupTestServer(t, mock)
  397. defer cleanup()
  398. client := NewClientWithConn(conn)
  399. compatibilityStore := &pb.CompatibilityStore{
  400. StoreName: testCompatibilityStoreName,
  401. StoreNamespace: "config-ns",
  402. StoreKind: esv1.SecretStoreKind,
  403. StoreUid: "uid-1",
  404. StoreGeneration: 7,
  405. StoreSpecJson: []byte(`{"provider":{"fake":{"data":[{"key":"test-key","value":"test-secret-value"}]}}}`),
  406. }
  407. err := client.DeleteSecret(context.Background(), &pb.PushSecretRemoteRef{RemoteKey: "remote"}, nil, compatibilityStore, testSourceNamespace)
  408. if err != nil {
  409. t.Fatalf("DeleteSecret failed: %v", err)
  410. }
  411. if mock.deleteRequest == nil {
  412. t.Fatal("expected delete secret request to be recorded")
  413. }
  414. if mock.deleteRequest.ProviderRef != nil {
  415. t.Fatalf("expected provider ref to be omitted, got %#v", mock.deleteRequest.ProviderRef)
  416. }
  417. if mock.deleteRequest.CompatibilityStore == nil {
  418. t.Fatal("expected compatibility store to be recorded")
  419. }
  420. }
  421. func TestClientSecretExistsSendsCompatibilityStore(t *testing.T) {
  422. mock := &mockServer{}
  423. conn, cleanup := setupTestServer(t, mock)
  424. defer cleanup()
  425. client := NewClientWithConn(conn)
  426. compatibilityStore := &pb.CompatibilityStore{
  427. StoreName: testCompatibilityStoreName,
  428. StoreNamespace: "config-ns",
  429. StoreKind: esv1.SecretStoreKind,
  430. StoreUid: "uid-1",
  431. StoreGeneration: 7,
  432. StoreSpecJson: []byte(`{"provider":{"fake":{"data":[{"key":"test-key","value":"test-secret-value"}]}}}`),
  433. }
  434. exists, err := client.SecretExists(context.Background(), &pb.PushSecretRemoteRef{RemoteKey: "remote"}, nil, compatibilityStore, testSourceNamespace)
  435. if err != nil {
  436. t.Fatalf("SecretExists failed: %v", err)
  437. }
  438. if !exists {
  439. t.Fatal("expected secret to exist")
  440. }
  441. if mock.existsRequest == nil {
  442. t.Fatal("expected secret exists request to be recorded")
  443. }
  444. if mock.existsRequest.ProviderRef != nil {
  445. t.Fatalf("expected provider ref to be omitted, got %#v", mock.existsRequest.ProviderRef)
  446. }
  447. if mock.existsRequest.CompatibilityStore == nil {
  448. t.Fatal("expected compatibility store to be recorded")
  449. }
  450. }
  451. func TestReadRequestIdentityValidationRejectsMissingReadIdentity(t *testing.T) {
  452. testCases := []struct {
  453. name string
  454. call func(client v2.Provider) error
  455. }{
  456. {
  457. name: "get secret",
  458. call: func(client v2.Provider) error {
  459. _, err := client.GetSecret(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "sample"}, nil, nil, testSourceNamespace)
  460. return err
  461. },
  462. },
  463. {
  464. name: "get secret map",
  465. call: func(client v2.Provider) error {
  466. _, err := client.GetSecretMap(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "sample"}, nil, nil, testSourceNamespace)
  467. return err
  468. },
  469. },
  470. {
  471. name: "get all secrets",
  472. call: func(client v2.Provider) error {
  473. _, err := client.GetAllSecrets(context.Background(), esv1.ExternalSecretFind{}, nil, nil, testSourceNamespace)
  474. return err
  475. },
  476. },
  477. }
  478. for _, tc := range testCases {
  479. t.Run(tc.name, func(t *testing.T) {
  480. mock := &mockServer{}
  481. conn, cleanup := setupTestServer(t, mock)
  482. defer cleanup()
  483. err := tc.call(NewClientWithConn(conn))
  484. if err == nil || err.Error() != "provider reference or compatibility store is required for read operations" {
  485. t.Fatalf("unexpected error: %v", err)
  486. }
  487. })
  488. }
  489. }
  490. func TestCompatibilityStoreLogFieldsRedactSpecPayload(t *testing.T) {
  491. specPayload := []byte(`{"provider":{"fake":{"data":[{"key":"db","value":"secret-value"}]}}}`)
  492. store := &pb.CompatibilityStore{
  493. StoreName: testCompatibilityStoreName,
  494. StoreNamespace: "team-a",
  495. StoreKind: esv1.SecretStoreKind,
  496. StoreUid: "uid-1",
  497. StoreGeneration: 7,
  498. StoreSpecJson: specPayload,
  499. }
  500. fields := compatibilityStoreLogFields(store)
  501. if len(fields) == 0 {
  502. t.Fatal("expected compatibility store log fields")
  503. }
  504. found := map[string]bool{}
  505. for i := 0; i < len(fields); i += 2 {
  506. key, ok := fields[i].(string)
  507. if !ok {
  508. t.Fatalf("expected string log key, got %T", fields[i])
  509. }
  510. found[key] = true
  511. if bytes.Equal([]byte(key), specPayload) {
  512. t.Fatalf("unexpected spec payload key: %q", key)
  513. }
  514. value := fields[i+1]
  515. if value == store {
  516. t.Fatalf("unexpected raw compatibility store in log fields")
  517. }
  518. if payload, ok := value.([]byte); ok && bytes.Equal(payload, specPayload) {
  519. t.Fatalf("unexpected raw spec payload in log fields")
  520. }
  521. if text, ok := value.(string); ok && text == string(specPayload) {
  522. t.Fatalf("unexpected raw spec payload string in log fields")
  523. }
  524. }
  525. for _, key := range []string{
  526. "compatibilityStoreKind",
  527. "compatibilityStoreName",
  528. "compatibilityStoreNamespace",
  529. "compatibilityStoreUID",
  530. "compatibilityStoreGeneration",
  531. "compatibilityStoreSpecBytes",
  532. } {
  533. if !found[key] {
  534. t.Fatalf("expected log field %q, got %#v", key, fields)
  535. }
  536. }
  537. }
  538. func TestClientPushDeleteExistsAndCapabilitiesSendProviderReferenceAndNamespace(t *testing.T) {
  539. mock := &mockServer{}
  540. conn, cleanup := setupTestServer(t, mock)
  541. defer cleanup()
  542. client := NewClientWithConn(conn)
  543. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderStoreKindStr}
  544. err := client.PushSecret(context.Background(), &corev1.Secret{
  545. Data: map[string][]byte{"token": []byte("value")},
  546. }, &pb.PushSecretData{
  547. RemoteKey: "remote/path",
  548. SecretKey: "token",
  549. Property: "property",
  550. Metadata: []byte(`{"mergePolicy":"replace"}`),
  551. }, providerRef, nil, testSourceNamespace)
  552. if err != nil {
  553. t.Fatalf("PushSecret failed: %v", err)
  554. }
  555. if mock.pushSecretRequest == nil {
  556. t.Fatal("expected push secret request to be recorded")
  557. }
  558. if mock.pushSecretRequest.SourceNamespace != testSourceNamespace {
  559. t.Fatalf("unexpected push request: %#v", mock.pushSecretRequest)
  560. }
  561. assertProviderRefEqual(t, mock.pushSecretRequest.ProviderRef, providerRef)
  562. if string(mock.pushSecretRequest.SecretData["token"]) != "value" {
  563. t.Fatalf("unexpected pushed secret data: %#v", mock.pushSecretRequest.SecretData)
  564. }
  565. err = client.DeleteSecret(context.Background(), &pb.PushSecretRemoteRef{
  566. RemoteKey: "remote/path",
  567. Property: "property",
  568. }, providerRef, nil, testSourceNamespace)
  569. if err != nil {
  570. t.Fatalf("DeleteSecret failed: %v", err)
  571. }
  572. if mock.deleteRequest == nil || mock.deleteRequest.SourceNamespace != testSourceNamespace {
  573. t.Fatalf("unexpected delete request: %#v", mock.deleteRequest)
  574. }
  575. assertProviderRefEqual(t, mock.deleteRequest.ProviderRef, providerRef)
  576. exists, err := client.SecretExists(context.Background(), &pb.PushSecretRemoteRef{
  577. RemoteKey: "remote/path",
  578. Property: "property",
  579. }, providerRef, nil, testSourceNamespace)
  580. if err != nil {
  581. t.Fatalf("SecretExists failed: %v", err)
  582. }
  583. if !exists {
  584. t.Fatal("expected exists to be true")
  585. }
  586. if mock.existsRequest == nil || mock.existsRequest.SourceNamespace != testSourceNamespace {
  587. t.Fatalf("unexpected exists request: %#v", mock.existsRequest)
  588. }
  589. assertProviderRefEqual(t, mock.existsRequest.ProviderRef, providerRef)
  590. caps, err := client.Capabilities(context.Background(), providerRef, testSourceNamespace)
  591. if err != nil {
  592. t.Fatalf("Capabilities failed: %v", err)
  593. }
  594. if caps != pb.SecretStoreCapabilities_READ_WRITE {
  595. t.Fatalf("expected READ_WRITE, got %v", caps)
  596. }
  597. if mock.capabilitiesRequest == nil || mock.capabilitiesRequest.SourceNamespace != testSourceNamespace {
  598. t.Fatalf("unexpected capabilities request: %#v", mock.capabilitiesRequest)
  599. }
  600. assertProviderRefEqual(t, mock.capabilitiesRequest.ProviderRef, providerRef)
  601. }
  602. func TestClientPushSecretSendsExpandedKubernetesSecretFields(t *testing.T) {
  603. mock := &mockServer{}
  604. conn, cleanup := setupTestServer(t, mock)
  605. defer cleanup()
  606. client := NewClientWithConn(conn)
  607. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ClusterProviderStoreKindStr}
  608. err := client.PushSecret(context.Background(), &corev1.Secret{
  609. Type: corev1.SecretTypeDockerConfigJson,
  610. ObjectMeta: metav1.ObjectMeta{
  611. Labels: map[string]string{"team": "platform"},
  612. Annotations: map[string]string{"owner": "app-team"},
  613. },
  614. Data: map[string][]byte{
  615. ".dockerconfigjson": []byte("payload"),
  616. },
  617. }, &pb.PushSecretData{
  618. RemoteKey: "remote/path",
  619. SecretKey: ".dockerconfigjson",
  620. Property: "property",
  621. Metadata: []byte(`{"mergePolicy":"replace"}`),
  622. }, providerRef, nil, testSourceNamespace)
  623. if err != nil {
  624. t.Fatalf("PushSecret failed: %v", err)
  625. }
  626. if mock.pushSecretRequest == nil {
  627. t.Fatal("expected push secret request to be recorded")
  628. }
  629. if got, want := string(mock.pushSecretRequest.SecretData[".dockerconfigjson"]), "payload"; got != want {
  630. t.Errorf("expected request secret data %q, got %q", want, got)
  631. }
  632. assertProviderRefEqual(t, mock.pushSecretRequest.ProviderRef, providerRef)
  633. if got, want := mock.pushSecretRequest.SourceNamespace, testSourceNamespace; got != want {
  634. t.Errorf("expected source namespace %q, got %q", want, got)
  635. }
  636. if got, want := mock.pushSecretRequest.SecretType, string(corev1.SecretTypeDockerConfigJson); got != want {
  637. t.Errorf("expected secret_type=%q, got %q", want, got)
  638. }
  639. if got, want := mock.pushSecretRequest.SecretLabels["team"], "platform"; got != want {
  640. t.Errorf("expected secret_labels.team=%q, got %q", want, got)
  641. }
  642. if got, want := mock.pushSecretRequest.SecretAnnotations["owner"], "app-team"; got != want {
  643. t.Errorf("expected secret_annotations.owner=%q, got %q", want, got)
  644. }
  645. if got, want := string(mock.pushSecretRequest.PushSecretData.Metadata), `{"mergePolicy":"replace"}`; got != want {
  646. t.Errorf("expected metadata=%q, got %q", want, got)
  647. }
  648. }
  649. func TestClientValidate(t *testing.T) {
  650. t.Run("success", func(t *testing.T) {
  651. mock := &mockServer{}
  652. conn, cleanup := setupTestServer(t, mock)
  653. defer cleanup()
  654. client := NewClientWithConn(conn)
  655. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderStoreKindStr}
  656. err := client.Validate(context.Background(), providerRef, nil, testSourceNamespace)
  657. if err != nil {
  658. t.Fatalf("Validate failed: %v", err)
  659. }
  660. if mock.validateRequest == nil {
  661. t.Fatal("expected validate request to be recorded")
  662. }
  663. if mock.validateRequest.SourceNamespace != testSourceNamespace {
  664. t.Fatalf("unexpected validate request: %#v", mock.validateRequest)
  665. }
  666. assertProviderRefEqual(t, mock.validateRequest.ProviderRef, providerRef)
  667. })
  668. t.Run("validation_error", func(t *testing.T) {
  669. mock := &mockServer{
  670. validateResponse: &pb.ValidateResponse{
  671. Valid: false,
  672. Error: "invalid credentials",
  673. },
  674. }
  675. conn, cleanup := setupTestServer(t, mock)
  676. defer cleanup()
  677. client := NewClientWithConn(conn)
  678. err := client.Validate(context.Background(), &pb.ProviderReference{Name: "provider"}, nil, testSourceNamespace)
  679. if err == nil {
  680. t.Fatal("Expected validation to fail, but it succeeded")
  681. }
  682. if err.Error() != "provider validation failed: invalid credentials" {
  683. t.Fatalf("unexpected error message: %v", err)
  684. }
  685. })
  686. }
  687. func TestClientClose(t *testing.T) {
  688. mock := &mockServer{}
  689. conn, cleanup := setupTestServer(t, mock)
  690. defer cleanup()
  691. client := NewClientWithConn(conn)
  692. if err := client.Close(context.Background()); err != nil {
  693. t.Fatalf("Close failed: %v", err)
  694. }
  695. }
  696. func TestNewClientInvalidAddress(t *testing.T) {
  697. _, err := NewClient("", nil)
  698. if err == nil {
  699. t.Fatal("expected error for empty address, got nil")
  700. }
  701. }
  702. func assertProviderRefEqual(t *testing.T, got, want *pb.ProviderReference) {
  703. t.Helper()
  704. if got == nil || want == nil {
  705. t.Fatalf("provider refs must not be nil: got=%#v want=%#v", got, want)
  706. }
  707. if got.ApiVersion != want.ApiVersion || got.Kind != want.Kind || got.Name != want.Name || got.Namespace != want.Namespace || got.StoreRefKind != want.StoreRefKind {
  708. t.Fatalf("unexpected provider ref: got=%#v want=%#v", got, want)
  709. }
  710. }
  711. func TestProtoCompatibilityRequestsExposeCompatibilityStoreField(t *testing.T) {
  712. cases := []struct {
  713. name string
  714. msg protoreflect.ProtoMessage
  715. }{
  716. {name: "GetSecretRequest", msg: &pb.GetSecretRequest{}},
  717. {name: "GetSecretMapRequest", msg: &pb.GetSecretMapRequest{}},
  718. {name: "GetAllSecretsRequest", msg: &pb.GetAllSecretsRequest{}},
  719. {name: "ValidateRequest", msg: &pb.ValidateRequest{}},
  720. {name: "PushSecretRequest", msg: &pb.PushSecretRequest{}},
  721. {name: "DeleteSecretRequest", msg: &pb.DeleteSecretRequest{}},
  722. {name: "SecretExistsRequest", msg: &pb.SecretExistsRequest{}},
  723. }
  724. for _, tc := range cases {
  725. t.Run(tc.name, func(t *testing.T) {
  726. fields := tc.msg.ProtoReflect().Descriptor().Fields()
  727. if fields.ByName("compatibility_store") == nil {
  728. t.Fatalf("expected %s to have field compatibility_store", tc.name)
  729. }
  730. })
  731. }
  732. }