client_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  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. "context"
  16. "net"
  17. "testing"
  18. "google.golang.org/grpc"
  19. "google.golang.org/grpc/credentials/insecure"
  20. "google.golang.org/grpc/test/bufconn"
  21. corev1 "k8s.io/api/core/v1"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  24. pb "github.com/external-secrets/external-secrets/proto/provider"
  25. )
  26. const (
  27. bufSize = 1024 * 1024
  28. testSourceNamespace = "tenant-a"
  29. )
  30. type mockServer struct {
  31. pb.UnimplementedSecretStoreProviderServer
  32. getSecretResponse *pb.GetSecretResponse
  33. getSecretRequest *pb.GetSecretRequest
  34. getSecretMapResponse *pb.GetSecretMapResponse
  35. getSecretMapRequest *pb.GetSecretMapRequest
  36. getAllSecretsResponse *pb.GetAllSecretsResponse
  37. getAllSecretsRequest *pb.GetAllSecretsRequest
  38. pushSecretRequest *pb.PushSecretRequest
  39. deleteRequest *pb.DeleteSecretRequest
  40. existsRequest *pb.SecretExistsRequest
  41. existsResponse *pb.SecretExistsResponse
  42. validateResponse *pb.ValidateResponse
  43. validateRequest *pb.ValidateRequest
  44. capabilitiesResponse *pb.CapabilitiesResponse
  45. capabilitiesRequest *pb.CapabilitiesRequest
  46. }
  47. func (m *mockServer) GetSecret(_ context.Context, req *pb.GetSecretRequest) (*pb.GetSecretResponse, error) {
  48. m.getSecretRequest = req
  49. if m.getSecretResponse != nil {
  50. return m.getSecretResponse, nil
  51. }
  52. return &pb.GetSecretResponse{Value: []byte("test-secret-value")}, nil
  53. }
  54. func (m *mockServer) GetSecretMap(_ context.Context, req *pb.GetSecretMapRequest) (*pb.GetSecretMapResponse, error) {
  55. m.getSecretMapRequest = req
  56. if m.getSecretMapResponse != nil {
  57. return m.getSecretMapResponse, nil
  58. }
  59. return &pb.GetSecretMapResponse{
  60. Secrets: map[string][]byte{"foo": []byte("bar")},
  61. }, nil
  62. }
  63. func (m *mockServer) GetAllSecrets(_ context.Context, req *pb.GetAllSecretsRequest) (*pb.GetAllSecretsResponse, error) {
  64. m.getAllSecretsRequest = req
  65. if m.getAllSecretsResponse != nil {
  66. return m.getAllSecretsResponse, nil
  67. }
  68. return &pb.GetAllSecretsResponse{
  69. Secrets: map[string][]byte{"db-password": []byte("value")},
  70. }, nil
  71. }
  72. func (m *mockServer) PushSecret(_ context.Context, req *pb.PushSecretRequest) (*pb.PushSecretResponse, error) {
  73. m.pushSecretRequest = req
  74. return &pb.PushSecretResponse{}, nil
  75. }
  76. func (m *mockServer) DeleteSecret(_ context.Context, req *pb.DeleteSecretRequest) (*pb.DeleteSecretResponse, error) {
  77. m.deleteRequest = req
  78. return &pb.DeleteSecretResponse{}, nil
  79. }
  80. func (m *mockServer) SecretExists(_ context.Context, req *pb.SecretExistsRequest) (*pb.SecretExistsResponse, error) {
  81. m.existsRequest = req
  82. if m.existsResponse != nil {
  83. return m.existsResponse, nil
  84. }
  85. return &pb.SecretExistsResponse{Exists: true}, nil
  86. }
  87. func (m *mockServer) Validate(_ context.Context, req *pb.ValidateRequest) (*pb.ValidateResponse, error) {
  88. m.validateRequest = req
  89. if m.validateResponse != nil {
  90. return m.validateResponse, nil
  91. }
  92. return &pb.ValidateResponse{Valid: true}, nil
  93. }
  94. func (m *mockServer) Capabilities(_ context.Context, req *pb.CapabilitiesRequest) (*pb.CapabilitiesResponse, error) {
  95. m.capabilitiesRequest = req
  96. if m.capabilitiesResponse != nil {
  97. return m.capabilitiesResponse, nil
  98. }
  99. return &pb.CapabilitiesResponse{Capabilities: pb.SecretStoreCapabilities_READ_WRITE}, nil
  100. }
  101. func setupTestServer(t *testing.T, mock *mockServer) (*grpc.ClientConn, func()) {
  102. t.Helper()
  103. lis := bufconn.Listen(bufSize)
  104. baseServer := grpc.NewServer()
  105. pb.RegisterSecretStoreProviderServer(baseServer, mock)
  106. go func() {
  107. if err := baseServer.Serve(lis); err != nil {
  108. t.Logf("Server exited with error: %v", err)
  109. }
  110. }()
  111. conn, err := grpc.DialContext(context.Background(), "",
  112. grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
  113. return lis.Dial()
  114. }),
  115. grpc.WithTransportCredentials(insecure.NewCredentials()))
  116. if err != nil {
  117. t.Fatalf("Failed to dial bufnet: %v", err)
  118. }
  119. cleanup := func() {
  120. _ = conn.Close()
  121. baseServer.Stop()
  122. _ = lis.Close()
  123. }
  124. return conn, cleanup
  125. }
  126. func TestClientGetSecretSendsProviderReferenceAndNamespace(t *testing.T) {
  127. mock := &mockServer{}
  128. conn, cleanup := setupTestServer(t, mock)
  129. defer cleanup()
  130. client := NewClientWithConn(conn)
  131. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderKindStr}
  132. ref := esv1.ExternalSecretDataRemoteRef{
  133. Key: "test-key",
  134. Version: "v1",
  135. Property: "password",
  136. DecodingStrategy: esv1.ExternalSecretDecodeBase64,
  137. MetadataPolicy: esv1.ExternalSecretMetadataPolicyFetch,
  138. }
  139. value, err := client.GetSecret(context.Background(), ref, providerRef, testSourceNamespace)
  140. if err != nil {
  141. t.Fatalf("GetSecret failed: %v", err)
  142. }
  143. if string(value) != "test-secret-value" {
  144. t.Fatalf("expected test-secret-value, got %q", string(value))
  145. }
  146. if mock.getSecretRequest == nil {
  147. t.Fatal("expected get secret request to be recorded")
  148. }
  149. assertProviderRefEqual(t, mock.getSecretRequest.ProviderRef, providerRef)
  150. if mock.getSecretRequest.SourceNamespace != testSourceNamespace {
  151. t.Fatalf("unexpected source namespace: %q", mock.getSecretRequest.SourceNamespace)
  152. }
  153. if mock.getSecretRequest.RemoteRef.Key != "test-key" || mock.getSecretRequest.RemoteRef.Version != "v1" || mock.getSecretRequest.RemoteRef.Property != "password" {
  154. t.Fatalf("unexpected remote ref: %#v", mock.getSecretRequest.RemoteRef)
  155. }
  156. if mock.getSecretRequest.RemoteRef.DecodingStrategy != string(esv1.ExternalSecretDecodeBase64) {
  157. t.Fatalf("unexpected decoding strategy: %q", mock.getSecretRequest.RemoteRef.DecodingStrategy)
  158. }
  159. if mock.getSecretRequest.RemoteRef.MetadataPolicy != string(esv1.ExternalSecretMetadataPolicyFetch) {
  160. t.Fatalf("unexpected metadata policy: %q", mock.getSecretRequest.RemoteRef.MetadataPolicy)
  161. }
  162. }
  163. func TestClientGetSecretMapSendsProviderReferenceAndNamespace(t *testing.T) {
  164. mock := &mockServer{
  165. getSecretMapResponse: &pb.GetSecretMapResponse{
  166. Secrets: map[string][]byte{"a": []byte("b")},
  167. },
  168. }
  169. conn, cleanup := setupTestServer(t, mock)
  170. defer cleanup()
  171. client := NewClientWithConn(conn)
  172. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderKindStr}
  173. value, err := client.GetSecretMap(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "test-key"}, providerRef, testSourceNamespace)
  174. if err != nil {
  175. t.Fatalf("GetSecretMap failed: %v", err)
  176. }
  177. if string(value["a"]) != "b" {
  178. t.Fatalf("expected map[a]=b, got %#v", value)
  179. }
  180. if mock.getSecretMapRequest == nil {
  181. t.Fatal("expected get secret map request to be recorded")
  182. }
  183. if mock.getSecretMapRequest.SourceNamespace != testSourceNamespace {
  184. t.Fatalf("unexpected request: %#v", mock.getSecretMapRequest)
  185. }
  186. assertProviderRefEqual(t, mock.getSecretMapRequest.ProviderRef, providerRef)
  187. }
  188. func TestClientGetAllSecretsSendsFindCriteria(t *testing.T) {
  189. mock := &mockServer{}
  190. conn, cleanup := setupTestServer(t, mock)
  191. defer cleanup()
  192. client := NewClientWithConn(conn)
  193. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderKindStr}
  194. path := "/team-a"
  195. secrets, err := client.GetAllSecrets(context.Background(), esv1.ExternalSecretFind{
  196. Tags: map[string]string{"team": "a"},
  197. Path: &path,
  198. Name: &esv1.FindName{RegExp: "db-.*"},
  199. }, providerRef, testSourceNamespace)
  200. if err != nil {
  201. t.Fatalf("GetAllSecrets failed: %v", err)
  202. }
  203. if string(secrets["db-password"]) != "value" {
  204. t.Fatalf("unexpected secrets: %#v", secrets)
  205. }
  206. if mock.getAllSecretsRequest == nil {
  207. t.Fatal("expected get all secrets request to be recorded")
  208. }
  209. if mock.getAllSecretsRequest.SourceNamespace != testSourceNamespace {
  210. t.Fatalf("unexpected request: %#v", mock.getAllSecretsRequest)
  211. }
  212. assertProviderRefEqual(t, mock.getAllSecretsRequest.ProviderRef, providerRef)
  213. if mock.getAllSecretsRequest.Find.Path != "/team-a" {
  214. t.Fatalf("unexpected path: %q", mock.getAllSecretsRequest.Find.Path)
  215. }
  216. if mock.getAllSecretsRequest.Find.Name == nil || mock.getAllSecretsRequest.Find.Name.Regexp != "db-.*" {
  217. t.Fatalf("unexpected name matcher: %#v", mock.getAllSecretsRequest.Find.Name)
  218. }
  219. }
  220. func TestClientPushDeleteExistsAndCapabilitiesSendProviderReferenceAndNamespace(t *testing.T) {
  221. mock := &mockServer{}
  222. conn, cleanup := setupTestServer(t, mock)
  223. defer cleanup()
  224. client := NewClientWithConn(conn)
  225. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderKindStr}
  226. err := client.PushSecret(context.Background(), &corev1.Secret{
  227. Data: map[string][]byte{"token": []byte("value")},
  228. }, &pb.PushSecretData{
  229. RemoteKey: "remote/path",
  230. SecretKey: "token",
  231. Property: "property",
  232. Metadata: []byte(`{"mergePolicy":"replace"}`),
  233. }, providerRef, testSourceNamespace)
  234. if err != nil {
  235. t.Fatalf("PushSecret failed: %v", err)
  236. }
  237. if mock.pushSecretRequest == nil {
  238. t.Fatal("expected push secret request to be recorded")
  239. }
  240. if mock.pushSecretRequest.SourceNamespace != testSourceNamespace {
  241. t.Fatalf("unexpected push request: %#v", mock.pushSecretRequest)
  242. }
  243. assertProviderRefEqual(t, mock.pushSecretRequest.ProviderRef, providerRef)
  244. if string(mock.pushSecretRequest.SecretData["token"]) != "value" {
  245. t.Fatalf("unexpected pushed secret data: %#v", mock.pushSecretRequest.SecretData)
  246. }
  247. err = client.DeleteSecret(context.Background(), &pb.PushSecretRemoteRef{
  248. RemoteKey: "remote/path",
  249. Property: "property",
  250. }, providerRef, testSourceNamespace)
  251. if err != nil {
  252. t.Fatalf("DeleteSecret failed: %v", err)
  253. }
  254. if mock.deleteRequest == nil || mock.deleteRequest.SourceNamespace != testSourceNamespace {
  255. t.Fatalf("unexpected delete request: %#v", mock.deleteRequest)
  256. }
  257. assertProviderRefEqual(t, mock.deleteRequest.ProviderRef, providerRef)
  258. exists, err := client.SecretExists(context.Background(), &pb.PushSecretRemoteRef{
  259. RemoteKey: "remote/path",
  260. Property: "property",
  261. }, providerRef, testSourceNamespace)
  262. if err != nil {
  263. t.Fatalf("SecretExists failed: %v", err)
  264. }
  265. if !exists {
  266. t.Fatal("expected exists to be true")
  267. }
  268. if mock.existsRequest == nil || mock.existsRequest.SourceNamespace != testSourceNamespace {
  269. t.Fatalf("unexpected exists request: %#v", mock.existsRequest)
  270. }
  271. assertProviderRefEqual(t, mock.existsRequest.ProviderRef, providerRef)
  272. caps, err := client.Capabilities(context.Background(), providerRef, testSourceNamespace)
  273. if err != nil {
  274. t.Fatalf("Capabilities failed: %v", err)
  275. }
  276. if caps != pb.SecretStoreCapabilities_READ_WRITE {
  277. t.Fatalf("expected READ_WRITE, got %v", caps)
  278. }
  279. if mock.capabilitiesRequest == nil || mock.capabilitiesRequest.SourceNamespace != testSourceNamespace {
  280. t.Fatalf("unexpected capabilities request: %#v", mock.capabilitiesRequest)
  281. }
  282. assertProviderRefEqual(t, mock.capabilitiesRequest.ProviderRef, providerRef)
  283. }
  284. func TestClientPushSecretSendsExpandedKubernetesSecretFields(t *testing.T) {
  285. mock := &mockServer{}
  286. conn, cleanup := setupTestServer(t, mock)
  287. defer cleanup()
  288. client := NewClientWithConn(conn)
  289. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ClusterProviderKindStr}
  290. err := client.PushSecret(context.Background(), &corev1.Secret{
  291. Type: corev1.SecretTypeDockerConfigJson,
  292. ObjectMeta: metav1.ObjectMeta{
  293. Labels: map[string]string{"team": "platform"},
  294. Annotations: map[string]string{"owner": "app-team"},
  295. },
  296. Data: map[string][]byte{
  297. ".dockerconfigjson": []byte("payload"),
  298. },
  299. }, &pb.PushSecretData{
  300. RemoteKey: "remote/path",
  301. SecretKey: ".dockerconfigjson",
  302. Property: "property",
  303. Metadata: []byte(`{"mergePolicy":"replace"}`),
  304. }, providerRef, testSourceNamespace)
  305. if err != nil {
  306. t.Fatalf("PushSecret failed: %v", err)
  307. }
  308. if mock.pushSecretRequest == nil {
  309. t.Fatal("expected push secret request to be recorded")
  310. }
  311. if got, want := string(mock.pushSecretRequest.SecretData[".dockerconfigjson"]), "payload"; got != want {
  312. t.Errorf("expected request secret data %q, got %q", want, got)
  313. }
  314. assertProviderRefEqual(t, mock.pushSecretRequest.ProviderRef, providerRef)
  315. if got, want := mock.pushSecretRequest.SourceNamespace, testSourceNamespace; got != want {
  316. t.Errorf("expected source namespace %q, got %q", want, got)
  317. }
  318. if got, want := mock.pushSecretRequest.SecretType, string(corev1.SecretTypeDockerConfigJson); got != want {
  319. t.Errorf("expected secret_type=%q, got %q", want, got)
  320. }
  321. if got, want := mock.pushSecretRequest.SecretLabels["team"], "platform"; got != want {
  322. t.Errorf("expected secret_labels.team=%q, got %q", want, got)
  323. }
  324. if got, want := mock.pushSecretRequest.SecretAnnotations["owner"], "app-team"; got != want {
  325. t.Errorf("expected secret_annotations.owner=%q, got %q", want, got)
  326. }
  327. if got, want := string(mock.pushSecretRequest.PushSecretData.Metadata), `{"mergePolicy":"replace"}`; got != want {
  328. t.Errorf("expected metadata=%q, got %q", want, got)
  329. }
  330. }
  331. func TestClientValidate(t *testing.T) {
  332. t.Run("success", func(t *testing.T) {
  333. mock := &mockServer{}
  334. conn, cleanup := setupTestServer(t, mock)
  335. defer cleanup()
  336. client := NewClientWithConn(conn)
  337. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderKindStr}
  338. err := client.Validate(context.Background(), providerRef, testSourceNamespace)
  339. if err != nil {
  340. t.Fatalf("Validate failed: %v", err)
  341. }
  342. if mock.validateRequest == nil {
  343. t.Fatal("expected validate request to be recorded")
  344. }
  345. if mock.validateRequest.SourceNamespace != testSourceNamespace {
  346. t.Fatalf("unexpected validate request: %#v", mock.validateRequest)
  347. }
  348. assertProviderRefEqual(t, mock.validateRequest.ProviderRef, providerRef)
  349. })
  350. t.Run("validation_error", func(t *testing.T) {
  351. mock := &mockServer{
  352. validateResponse: &pb.ValidateResponse{
  353. Valid: false,
  354. Error: "invalid credentials",
  355. },
  356. }
  357. conn, cleanup := setupTestServer(t, mock)
  358. defer cleanup()
  359. client := NewClientWithConn(conn)
  360. err := client.Validate(context.Background(), &pb.ProviderReference{Name: "provider"}, testSourceNamespace)
  361. if err == nil {
  362. t.Fatal("Expected validation to fail, but it succeeded")
  363. }
  364. if err.Error() != "provider validation failed: invalid credentials" {
  365. t.Fatalf("unexpected error message: %v", err)
  366. }
  367. })
  368. }
  369. func TestClientClose(t *testing.T) {
  370. mock := &mockServer{}
  371. conn, cleanup := setupTestServer(t, mock)
  372. defer cleanup()
  373. client := NewClientWithConn(conn)
  374. if err := client.Close(context.Background()); err != nil {
  375. t.Fatalf("Close failed: %v", err)
  376. }
  377. }
  378. func TestNewClientInvalidAddress(t *testing.T) {
  379. _, err := NewClient("", nil)
  380. if err == nil {
  381. t.Fatal("expected error for empty address, got nil")
  382. }
  383. }
  384. func assertProviderRefEqual(t *testing.T, got, want *pb.ProviderReference) {
  385. t.Helper()
  386. if got == nil || want == nil {
  387. t.Fatalf("provider refs must not be nil: got=%#v want=%#v", got, want)
  388. }
  389. if got.ApiVersion != want.ApiVersion || got.Kind != want.Kind || got.Name != want.Name || got.Namespace != want.Namespace || got.StoreRefKind != want.StoreRefKind {
  390. t.Fatalf("unexpected provider ref: got=%#v want=%#v", got, want)
  391. }
  392. }