client_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  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 store
  14. import (
  15. "bytes"
  16. "context"
  17. "errors"
  18. "testing"
  19. corev1 "k8s.io/api/core/v1"
  20. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  23. pb "github.com/external-secrets/external-secrets/proto/provider"
  24. )
  25. const (
  26. testProperty = "property"
  27. testSourceNamespace = "tenant-a"
  28. testValue = "value"
  29. )
  30. type fakeV2Provider struct {
  31. getSecretResponse []byte
  32. getSecretErr error
  33. getSecretRef esv1.ExternalSecretDataRemoteRef
  34. getSecretProviderRef *pb.ProviderReference
  35. getSecretNamespace string
  36. getSecretMapResponse map[string][]byte
  37. getSecretMapErr error
  38. getSecretMapRef esv1.ExternalSecretDataRemoteRef
  39. getAllSecretsResponse map[string][]byte
  40. getAllSecretsErr error
  41. getAllSecretsFind esv1.ExternalSecretFind
  42. pushSecretErr error
  43. pushSecretData map[string][]byte
  44. pushSecretSecret *corev1.Secret
  45. pushSecretPayload *pb.PushSecretData
  46. pushSecretProviderRef *pb.ProviderReference
  47. pushSecretNamespace string
  48. deleteSecretErr error
  49. deleteSecretRemoteRef *pb.PushSecretRemoteRef
  50. deleteSecretProviderRef *pb.ProviderReference
  51. deleteSecretNamespace string
  52. secretExistsResponse bool
  53. secretExistsErr error
  54. secretExistsRemoteRef *pb.PushSecretRemoteRef
  55. secretExistsProviderRef *pb.ProviderReference
  56. secretExistsNamespace string
  57. validateErr error
  58. validateProviderRef *pb.ProviderReference
  59. validateNamespace string
  60. closeErr error
  61. closeCalled bool
  62. }
  63. func (f *fakeV2Provider) GetSecret(_ context.Context, ref esv1.ExternalSecretDataRemoteRef, providerRef *pb.ProviderReference, sourceNamespace string) ([]byte, error) {
  64. f.getSecretRef = ref
  65. f.getSecretProviderRef = providerRef
  66. f.getSecretNamespace = sourceNamespace
  67. return f.getSecretResponse, f.getSecretErr
  68. }
  69. func (f *fakeV2Provider) GetSecretMap(_ context.Context, ref esv1.ExternalSecretDataRemoteRef, providerRef *pb.ProviderReference, sourceNamespace string) (map[string][]byte, error) {
  70. f.getSecretMapRef = ref
  71. f.getSecretProviderRef = providerRef
  72. f.getSecretNamespace = sourceNamespace
  73. return f.getSecretMapResponse, f.getSecretMapErr
  74. }
  75. func (f *fakeV2Provider) GetAllSecrets(_ context.Context, find esv1.ExternalSecretFind, providerRef *pb.ProviderReference, sourceNamespace string) (map[string][]byte, error) {
  76. f.getAllSecretsFind = find
  77. f.getSecretProviderRef = providerRef
  78. f.getSecretNamespace = sourceNamespace
  79. return f.getAllSecretsResponse, f.getAllSecretsErr
  80. }
  81. func (f *fakeV2Provider) PushSecret(_ context.Context, secret *corev1.Secret, pushSecretData *pb.PushSecretData, providerRef *pb.ProviderReference, sourceNamespace string) error {
  82. f.pushSecretData = secret.Data
  83. f.pushSecretSecret = secret.DeepCopy()
  84. f.pushSecretPayload = pushSecretData
  85. f.pushSecretProviderRef = providerRef
  86. f.pushSecretNamespace = sourceNamespace
  87. return f.pushSecretErr
  88. }
  89. func (f *fakeV2Provider) DeleteSecret(_ context.Context, remoteRef *pb.PushSecretRemoteRef, providerRef *pb.ProviderReference, sourceNamespace string) error {
  90. f.deleteSecretRemoteRef = remoteRef
  91. f.deleteSecretProviderRef = providerRef
  92. f.deleteSecretNamespace = sourceNamespace
  93. return f.deleteSecretErr
  94. }
  95. func (f *fakeV2Provider) SecretExists(_ context.Context, remoteRef *pb.PushSecretRemoteRef, providerRef *pb.ProviderReference, sourceNamespace string) (bool, error) {
  96. f.secretExistsRemoteRef = remoteRef
  97. f.secretExistsProviderRef = providerRef
  98. f.secretExistsNamespace = sourceNamespace
  99. return f.secretExistsResponse, f.secretExistsErr
  100. }
  101. func (f *fakeV2Provider) Validate(_ context.Context, providerRef *pb.ProviderReference, sourceNamespace string) error {
  102. f.validateProviderRef = providerRef
  103. f.validateNamespace = sourceNamespace
  104. return f.validateErr
  105. }
  106. func (f *fakeV2Provider) Capabilities(context.Context, *pb.ProviderReference, string) (pb.SecretStoreCapabilities, error) {
  107. return pb.SecretStoreCapabilities_READ_WRITE, nil
  108. }
  109. func (f *fakeV2Provider) Close(context.Context) error {
  110. f.closeCalled = true
  111. return f.closeErr
  112. }
  113. type fakePushSecretData struct {
  114. property string
  115. secretKey string
  116. remoteKey string
  117. metadata *apiextensionsv1.JSON
  118. }
  119. func (f fakePushSecretData) GetProperty() string {
  120. return f.property
  121. }
  122. func (f fakePushSecretData) GetSecretKey() string {
  123. return f.secretKey
  124. }
  125. func (f fakePushSecretData) GetRemoteKey() string {
  126. return f.remoteKey
  127. }
  128. func (f fakePushSecretData) GetMetadata() *apiextensionsv1.JSON {
  129. return f.metadata
  130. }
  131. type fakePushSecretRemoteRef struct {
  132. remoteKey string
  133. property string
  134. }
  135. func (f fakePushSecretRemoteRef) GetRemoteKey() string {
  136. return f.remoteKey
  137. }
  138. func (f fakePushSecretRemoteRef) GetProperty() string {
  139. return f.property
  140. }
  141. func TestClientGetSecretDelegatesProviderReferenceAndNamespace(t *testing.T) {
  142. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
  143. provider := &fakeV2Provider{getSecretResponse: []byte("secret-value")}
  144. client := NewClient(provider, providerRef, testSourceNamespace)
  145. ref := esv1.ExternalSecretDataRemoteRef{Key: "sample", Version: "v1", Property: "password"}
  146. value, err := client.GetSecret(context.Background(), ref)
  147. if err != nil {
  148. t.Fatalf("GetSecret() error = %v", err)
  149. }
  150. if string(value) != "secret-value" {
  151. t.Fatalf("expected secret-value, got %q", string(value))
  152. }
  153. if provider.getSecretRef != ref {
  154. t.Fatalf("unexpected ref: %#v", provider.getSecretRef)
  155. }
  156. if provider.getSecretProviderRef != providerRef {
  157. t.Fatalf("unexpected provider ref: %#v", provider.getSecretProviderRef)
  158. }
  159. if provider.getSecretNamespace != testSourceNamespace {
  160. t.Fatalf("unexpected source namespace: %q", provider.getSecretNamespace)
  161. }
  162. }
  163. func TestClientGetSecretMapDelegatesProviderReferenceAndNamespace(t *testing.T) {
  164. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
  165. expected := map[string][]byte{
  166. "foo": []byte("bar"),
  167. "baz": []byte("qux"),
  168. }
  169. provider := &fakeV2Provider{getSecretMapResponse: expected}
  170. client := NewClient(provider, providerRef, testSourceNamespace)
  171. ref := esv1.ExternalSecretDataRemoteRef{Key: "sample"}
  172. secretMap, err := client.GetSecretMap(context.Background(), ref)
  173. if err != nil {
  174. t.Fatalf("GetSecretMap() error = %v", err)
  175. }
  176. if string(secretMap["foo"]) != "bar" || string(secretMap["baz"]) != "qux" {
  177. t.Fatalf("unexpected secret map: %#v", secretMap)
  178. }
  179. if provider.getSecretMapRef != ref {
  180. t.Fatalf("unexpected ref: %#v", provider.getSecretMapRef)
  181. }
  182. if provider.getSecretProviderRef != providerRef {
  183. t.Fatalf("unexpected provider ref: %#v", provider.getSecretProviderRef)
  184. }
  185. if provider.getSecretNamespace != testSourceNamespace {
  186. t.Fatalf("unexpected source namespace: %q", provider.getSecretNamespace)
  187. }
  188. }
  189. func TestClientGetAllSecretsDelegatesFindCriteria(t *testing.T) {
  190. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
  191. path := "/team-a"
  192. expected := map[string][]byte{"db-password": []byte(testValue)}
  193. provider := &fakeV2Provider{getAllSecretsResponse: expected}
  194. client := NewClient(provider, providerRef, testSourceNamespace)
  195. find := esv1.ExternalSecretFind{
  196. Tags: map[string]string{
  197. "team": "a",
  198. },
  199. Path: &path,
  200. Name: &esv1.FindName{RegExp: "db-.*"},
  201. }
  202. secrets, err := client.GetAllSecrets(context.Background(), find)
  203. if err != nil {
  204. t.Fatalf("GetAllSecrets() error = %v", err)
  205. }
  206. if string(secrets["db-password"]) != testValue {
  207. t.Fatalf("unexpected secret value: %#v", secrets)
  208. }
  209. if provider.getAllSecretsFind.Tags["team"] != "a" {
  210. t.Fatalf("unexpected find tags: %#v", provider.getAllSecretsFind)
  211. }
  212. if provider.getAllSecretsFind.Path == nil || *provider.getAllSecretsFind.Path != path {
  213. t.Fatalf("unexpected find path: %#v", provider.getAllSecretsFind.Path)
  214. }
  215. if provider.getAllSecretsFind.Name == nil || provider.getAllSecretsFind.Name.RegExp != "db-.*" {
  216. t.Fatalf("unexpected find name: %#v", provider.getAllSecretsFind.Name)
  217. }
  218. if provider.getSecretProviderRef != providerRef {
  219. t.Fatalf("unexpected provider ref: %#v", provider.getSecretProviderRef)
  220. }
  221. if provider.getSecretNamespace != testSourceNamespace {
  222. t.Fatalf("unexpected source namespace: %q", provider.getSecretNamespace)
  223. }
  224. }
  225. func TestClientPushSecretConvertsPayloadAndMetadata(t *testing.T) {
  226. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
  227. provider := &fakeV2Provider{}
  228. client := NewClient(provider, providerRef, testSourceNamespace)
  229. metadata := []byte(`{"owner":"eso"}`)
  230. secret := &corev1.Secret{
  231. Data: map[string][]byte{
  232. "token": []byte(testValue),
  233. },
  234. }
  235. pushData := fakePushSecretData{
  236. property: testProperty,
  237. secretKey: "token",
  238. remoteKey: serverTestRemoteKey,
  239. metadata: &apiextensionsv1.JSON{Raw: metadata},
  240. }
  241. err := client.PushSecret(context.Background(), secret, pushData)
  242. if err != nil {
  243. t.Fatalf("PushSecret() error = %v", err)
  244. }
  245. if string(provider.pushSecretData["token"]) != testValue {
  246. t.Fatalf("unexpected secret data: %#v", provider.pushSecretData)
  247. }
  248. if provider.pushSecretPayload == nil {
  249. t.Fatal("expected push payload to be recorded")
  250. }
  251. if provider.pushSecretPayload.RemoteKey != serverTestRemoteKey || provider.pushSecretPayload.SecretKey != "token" || provider.pushSecretPayload.Property != testProperty {
  252. t.Fatalf("unexpected push payload: %#v", provider.pushSecretPayload)
  253. }
  254. if !bytes.Equal(provider.pushSecretPayload.Metadata, metadata) {
  255. t.Fatalf("unexpected metadata: %q", string(provider.pushSecretPayload.Metadata))
  256. }
  257. if provider.pushSecretProviderRef != providerRef {
  258. t.Fatalf("unexpected provider ref: %#v", provider.pushSecretProviderRef)
  259. }
  260. if provider.pushSecretNamespace != testSourceNamespace {
  261. t.Fatalf("unexpected source namespace: %q", provider.pushSecretNamespace)
  262. }
  263. }
  264. func TestClientPushSecretForwardsKubernetesSecretShape(t *testing.T) {
  265. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
  266. provider := &fakeV2Provider{}
  267. client := NewClient(provider, providerRef, testSourceNamespace)
  268. metadata := []byte(`{"mergePolicy":"replace"}`)
  269. secret := &corev1.Secret{
  270. Type: corev1.SecretTypeDockerConfigJson,
  271. ObjectMeta: metav1.ObjectMeta{
  272. Labels: map[string]string{"team": "platform"},
  273. Annotations: map[string]string{"owner": "app-team"},
  274. },
  275. Data: map[string][]byte{
  276. ".dockerconfigjson": []byte("payload"),
  277. },
  278. }
  279. pushData := fakePushSecretData{
  280. property: testProperty,
  281. secretKey: ".dockerconfigjson",
  282. remoteKey: serverTestRemoteKey,
  283. metadata: &apiextensionsv1.JSON{Raw: metadata},
  284. }
  285. err := client.PushSecret(context.Background(), secret, pushData)
  286. if err != nil {
  287. t.Fatalf("PushSecret() error = %v", err)
  288. }
  289. if provider.pushSecretSecret == nil {
  290. t.Fatal("expected pushed secret to be recorded")
  291. }
  292. if provider.pushSecretSecret.Type != corev1.SecretTypeDockerConfigJson {
  293. t.Errorf("expected secret type %q, got %q", corev1.SecretTypeDockerConfigJson, provider.pushSecretSecret.Type)
  294. }
  295. if got, want := provider.pushSecretSecret.Labels["team"], "platform"; got != want {
  296. t.Errorf("expected secret label team=%q, got %q", want, got)
  297. }
  298. if got, want := provider.pushSecretSecret.Annotations["owner"], "app-team"; got != want {
  299. t.Errorf("expected secret annotation owner=%q, got %q", want, got)
  300. }
  301. if got, want := string(provider.pushSecretSecret.Data[".dockerconfigjson"]), "payload"; got != want {
  302. t.Errorf("expected secret payload %q, got %q", want, got)
  303. }
  304. if provider.pushSecretPayload == nil {
  305. t.Fatal("expected push payload to be recorded")
  306. }
  307. if !bytes.Equal(provider.pushSecretPayload.Metadata, metadata) {
  308. t.Fatalf("unexpected metadata: %q", string(provider.pushSecretPayload.Metadata))
  309. }
  310. }
  311. func TestClientDeleteSecretConvertsRemoteRef(t *testing.T) {
  312. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
  313. provider := &fakeV2Provider{}
  314. client := NewClient(provider, providerRef, testSourceNamespace)
  315. err := client.DeleteSecret(context.Background(), fakePushSecretRemoteRef{
  316. remoteKey: serverTestRemoteKey,
  317. property: testProperty,
  318. })
  319. if err != nil {
  320. t.Fatalf("DeleteSecret() error = %v", err)
  321. }
  322. if provider.deleteSecretRemoteRef == nil {
  323. t.Fatal("expected delete remote ref to be recorded")
  324. }
  325. if provider.deleteSecretRemoteRef.RemoteKey != serverTestRemoteKey || provider.deleteSecretRemoteRef.Property != testProperty {
  326. t.Fatalf("unexpected remote ref: %#v", provider.deleteSecretRemoteRef)
  327. }
  328. if provider.deleteSecretProviderRef != providerRef {
  329. t.Fatalf("unexpected provider ref: %#v", provider.deleteSecretProviderRef)
  330. }
  331. if provider.deleteSecretNamespace != testSourceNamespace {
  332. t.Fatalf("unexpected source namespace: %q", provider.deleteSecretNamespace)
  333. }
  334. }
  335. func TestClientSecretExistsConvertsRemoteRef(t *testing.T) {
  336. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
  337. provider := &fakeV2Provider{secretExistsResponse: true}
  338. client := NewClient(provider, providerRef, testSourceNamespace)
  339. exists, err := client.SecretExists(context.Background(), fakePushSecretRemoteRef{
  340. remoteKey: serverTestRemoteKey,
  341. property: testProperty,
  342. })
  343. if err != nil {
  344. t.Fatalf("SecretExists() error = %v", err)
  345. }
  346. if !exists {
  347. t.Fatal("expected secret to exist")
  348. }
  349. if provider.secretExistsRemoteRef == nil {
  350. t.Fatal("expected exists remote ref to be recorded")
  351. }
  352. if provider.secretExistsRemoteRef.RemoteKey != serverTestRemoteKey || provider.secretExistsRemoteRef.Property != testProperty {
  353. t.Fatalf("unexpected remote ref: %#v", provider.secretExistsRemoteRef)
  354. }
  355. if provider.secretExistsProviderRef != providerRef {
  356. t.Fatalf("unexpected provider ref: %#v", provider.secretExistsProviderRef)
  357. }
  358. if provider.secretExistsNamespace != testSourceNamespace {
  359. t.Fatalf("unexpected source namespace: %q", provider.secretExistsNamespace)
  360. }
  361. }
  362. func TestClientValidateMapsProviderErrors(t *testing.T) {
  363. t.Run("success", func(t *testing.T) {
  364. providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
  365. provider := &fakeV2Provider{}
  366. client := NewClient(provider, providerRef, testSourceNamespace)
  367. result, err := client.Validate()
  368. if err != nil {
  369. t.Fatalf("Validate() error = %v", err)
  370. }
  371. if result != esv1.ValidationResultReady {
  372. t.Fatalf("expected ValidationResultReady, got %q", result)
  373. }
  374. if provider.validateProviderRef != providerRef {
  375. t.Fatalf("unexpected provider ref: %#v", provider.validateProviderRef)
  376. }
  377. if provider.validateNamespace != testSourceNamespace {
  378. t.Fatalf("unexpected source namespace: %q", provider.validateNamespace)
  379. }
  380. })
  381. t.Run("error", func(t *testing.T) {
  382. validateErr := errors.New("invalid credentials")
  383. provider := &fakeV2Provider{validateErr: validateErr}
  384. client := NewClient(provider, &pb.ProviderReference{Name: "provider"}, testSourceNamespace)
  385. result, err := client.Validate()
  386. if !errors.Is(err, validateErr) {
  387. t.Fatalf("expected %v, got %v", validateErr, err)
  388. }
  389. if result != esv1.ValidationResultError {
  390. t.Fatalf("expected ValidationResultError, got %q", result)
  391. }
  392. })
  393. }
  394. func TestClientCloseDelegates(t *testing.T) {
  395. closeErr := errors.New("close failed")
  396. provider := &fakeV2Provider{closeErr: closeErr}
  397. client := NewClient(provider, &pb.ProviderReference{Name: "provider"}, testSourceNamespace)
  398. err := client.Close(context.Background())
  399. if !errors.Is(err, closeErr) {
  400. t.Fatalf("expected %v, got %v", closeErr, err)
  401. }
  402. if !provider.closeCalled {
  403. t.Fatal("expected provider close to be called")
  404. }
  405. }