lockbox_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package lockbox
  13. import (
  14. "context"
  15. b64 "encoding/base64"
  16. "encoding/json"
  17. "testing"
  18. tassert "github.com/stretchr/testify/assert"
  19. "github.com/yandex-cloud/go-genproto/yandex/cloud/lockbox/v1"
  20. "github.com/yandex-cloud/go-sdk/iamkey"
  21. corev1 "k8s.io/api/core/v1"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "sigs.k8s.io/controller-runtime/pkg/client"
  24. clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
  25. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  26. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  27. "github.com/external-secrets/external-secrets/pkg/provider/schema"
  28. "github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client/fake"
  29. )
  30. func TestNewClient(t *testing.T) {
  31. ctx := context.Background()
  32. const namespace = "namespace"
  33. store := &esv1alpha1.SecretStore{
  34. ObjectMeta: metav1.ObjectMeta{
  35. Namespace: namespace,
  36. },
  37. Spec: esv1alpha1.SecretStoreSpec{
  38. Provider: &esv1alpha1.SecretStoreProvider{
  39. YandexLockbox: &esv1alpha1.YandexLockboxProvider{},
  40. },
  41. },
  42. }
  43. provider, err := schema.GetProvider(store)
  44. tassert.Nil(t, err)
  45. k8sClient := clientfake.NewClientBuilder().Build()
  46. secretClient, err := provider.NewClient(context.Background(), store, k8sClient, namespace)
  47. tassert.EqualError(t, err, "invalid Yandex Lockbox SecretStore resource: missing AuthorizedKey Name")
  48. tassert.Nil(t, secretClient)
  49. store.Spec.Provider.YandexLockbox.Auth = esv1alpha1.YandexLockboxAuth{}
  50. secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
  51. tassert.EqualError(t, err, "invalid Yandex Lockbox SecretStore resource: missing AuthorizedKey Name")
  52. tassert.Nil(t, secretClient)
  53. store.Spec.Provider.YandexLockbox.Auth.AuthorizedKey = esmeta.SecretKeySelector{}
  54. secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
  55. tassert.EqualError(t, err, "invalid Yandex Lockbox SecretStore resource: missing AuthorizedKey Name")
  56. tassert.Nil(t, secretClient)
  57. const authorizedKeySecretName = "authorizedKeySecretName"
  58. const authorizedKeySecretKey = "authorizedKeySecretKey"
  59. store.Spec.Provider.YandexLockbox.Auth.AuthorizedKey.Name = authorizedKeySecretName
  60. store.Spec.Provider.YandexLockbox.Auth.AuthorizedKey.Key = authorizedKeySecretKey
  61. secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
  62. tassert.EqualError(t, err, "could not fetch AuthorizedKey secret: secrets \"authorizedKeySecretName\" not found")
  63. tassert.Nil(t, secretClient)
  64. err = createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, newFakeAuthorizedKey("0"))
  65. tassert.Nil(t, err)
  66. secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
  67. tassert.EqualError(t, err, "failed to create Yandex.Cloud SDK: private key parsing failed: Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
  68. tassert.Nil(t, secretClient)
  69. }
  70. func TestGetSecretForAllEntries(t *testing.T) {
  71. ctx := context.Background()
  72. const namespace = "namespace"
  73. authorizedKey := newFakeAuthorizedKey("0")
  74. lockboxBackend := fake.NewLockboxBackend()
  75. k1, v1 := "k1", "v1"
  76. k2, v2 := "k2", []byte("v2")
  77. secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
  78. textEntry(k1, v1),
  79. binaryEntry(k2, v2),
  80. )
  81. k8sClient := clientfake.NewClientBuilder().Build()
  82. const authorizedKeySecretName = "authorizedKeySecretName"
  83. const authorizedKeySecretKey = "authorizedKeySecretKey"
  84. err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
  85. tassert.Nil(t, err)
  86. store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
  87. provider := &lockboxProvider{&fake.LockboxClientCreator{
  88. Backend: lockboxBackend,
  89. }}
  90. secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
  91. tassert.Nil(t, err)
  92. data, err := secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID})
  93. tassert.Nil(t, err)
  94. tassert.Equal(
  95. t,
  96. map[string]string{
  97. k1: v1,
  98. k2: base64(v2),
  99. },
  100. unmarshalStringMap(t, data),
  101. )
  102. }
  103. func TestGetSecretForTextEntry(t *testing.T) {
  104. ctx := context.Background()
  105. const namespace = "namespace"
  106. authorizedKey := newFakeAuthorizedKey("0")
  107. lockboxBackend := fake.NewLockboxBackend()
  108. k1, v1 := "k1", "v1"
  109. k2, v2 := "k2", []byte("v2")
  110. secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
  111. textEntry(k1, v1),
  112. binaryEntry(k2, v2),
  113. )
  114. k8sClient := clientfake.NewClientBuilder().Build()
  115. const authorizedKeySecretName = "authorizedKeySecretName"
  116. const authorizedKeySecretKey = "authorizedKeySecretKey"
  117. err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
  118. tassert.Nil(t, err)
  119. store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
  120. provider := &lockboxProvider{&fake.LockboxClientCreator{
  121. Backend: lockboxBackend,
  122. }}
  123. secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
  124. tassert.Nil(t, err)
  125. data, err := secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Property: k1})
  126. tassert.Nil(t, err)
  127. tassert.Equal(t, v1, string(data))
  128. }
  129. func TestGetSecretForBinaryEntry(t *testing.T) {
  130. ctx := context.Background()
  131. const namespace = "namespace"
  132. authorizedKey := newFakeAuthorizedKey("0")
  133. lockboxBackend := fake.NewLockboxBackend()
  134. k1, v1 := "k1", "v1"
  135. k2, v2 := "k2", []byte("v2")
  136. secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
  137. textEntry(k1, v1),
  138. binaryEntry(k2, v2),
  139. )
  140. k8sClient := clientfake.NewClientBuilder().Build()
  141. const authorizedKeySecretName = "authorizedKeySecretName"
  142. const authorizedKeySecretKey = "authorizedKeySecretKey"
  143. err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
  144. tassert.Nil(t, err)
  145. store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
  146. provider := &lockboxProvider{&fake.LockboxClientCreator{
  147. Backend: lockboxBackend,
  148. }}
  149. secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
  150. tassert.Nil(t, err)
  151. data, err := secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Property: k2})
  152. tassert.Nil(t, err)
  153. tassert.Equal(t, v2, data)
  154. }
  155. func TestGetSecretByVersionID(t *testing.T) {
  156. ctx := context.Background()
  157. const namespace = "namespace"
  158. authorizedKey := newFakeAuthorizedKey("0")
  159. lockboxBackend := fake.NewLockboxBackend()
  160. oldKey, oldVal := "oldKey", "oldVal"
  161. secretID, oldVersionID := lockboxBackend.CreateSecret(authorizedKey,
  162. textEntry(oldKey, oldVal),
  163. )
  164. k8sClient := clientfake.NewClientBuilder().Build()
  165. const authorizedKeySecretName = "authorizedKeySecretName"
  166. const authorizedKeySecretKey = "authorizedKeySecretKey"
  167. err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
  168. tassert.Nil(t, err)
  169. store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
  170. provider := &lockboxProvider{&fake.LockboxClientCreator{
  171. Backend: lockboxBackend,
  172. }}
  173. secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
  174. tassert.Nil(t, err)
  175. data, err := secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: oldVersionID})
  176. tassert.Nil(t, err)
  177. tassert.Equal(t, map[string]string{oldKey: oldVal}, unmarshalStringMap(t, data))
  178. newKey, newVal := "newKey", "newVal"
  179. newVersionID := lockboxBackend.AddVersion(secretID,
  180. textEntry(newKey, newVal),
  181. )
  182. data, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: oldVersionID})
  183. tassert.Nil(t, err)
  184. tassert.Equal(t, map[string]string{oldKey: oldVal}, unmarshalStringMap(t, data))
  185. data, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: newVersionID})
  186. tassert.Nil(t, err)
  187. tassert.Equal(t, map[string]string{newKey: newVal}, unmarshalStringMap(t, data))
  188. }
  189. func TestGetSecretUnauthorized(t *testing.T) {
  190. ctx := context.Background()
  191. const namespace = "namespace"
  192. authorizedKeyA := newFakeAuthorizedKey("A")
  193. authorizedKeyB := newFakeAuthorizedKey("B")
  194. lockboxBackend := fake.NewLockboxBackend()
  195. secretID, _ := lockboxBackend.CreateSecret(authorizedKeyA,
  196. textEntry("k1", "v1"),
  197. )
  198. k8sClient := clientfake.NewClientBuilder().Build()
  199. const authorizedKeySecretName = "authorizedKeySecretName"
  200. const authorizedKeySecretKey = "authorizedKeySecretKey"
  201. err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKeyB)
  202. tassert.Nil(t, err)
  203. store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
  204. provider := &lockboxProvider{&fake.LockboxClientCreator{
  205. Backend: lockboxBackend,
  206. }}
  207. secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
  208. tassert.Nil(t, err)
  209. _, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID})
  210. tassert.EqualError(t, err, "unable to request secret payload to get secret: permission denied")
  211. }
  212. func TestGetSecretNotFound(t *testing.T) {
  213. ctx := context.Background()
  214. const namespace = "namespace"
  215. authorizedKey := newFakeAuthorizedKey("0")
  216. lockboxBackend := fake.NewLockboxBackend()
  217. k8sClient := clientfake.NewClientBuilder().Build()
  218. const authorizedKeySecretName = "authorizedKeySecretName"
  219. const authorizedKeySecretKey = "authorizedKeySecretKey"
  220. err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
  221. tassert.Nil(t, err)
  222. store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
  223. provider := &lockboxProvider{&fake.LockboxClientCreator{
  224. Backend: lockboxBackend,
  225. }}
  226. secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
  227. tassert.Nil(t, err)
  228. _, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: "no-secret-with-this-id"})
  229. tassert.EqualError(t, err, "unable to request secret payload to get secret: secret not found")
  230. secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
  231. textEntry("k1", "v1"),
  232. )
  233. _, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: "no-version-with-this-id"})
  234. tassert.EqualError(t, err, "unable to request secret payload to get secret: version not found")
  235. }
  236. func TestGetSecretMap(t *testing.T) {
  237. ctx := context.Background()
  238. const namespace = "namespace"
  239. authorizedKey := newFakeAuthorizedKey("0")
  240. lockboxBackend := fake.NewLockboxBackend()
  241. k1, v1 := "k1", "v1"
  242. k2, v2 := "k2", []byte("v2")
  243. secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
  244. textEntry(k1, v1),
  245. binaryEntry(k2, v2),
  246. )
  247. k8sClient := clientfake.NewClientBuilder().Build()
  248. const authorizedKeySecretName = "authorizedKeySecretName"
  249. const authorizedKeySecretKey = "authorizedKeySecretKey"
  250. err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
  251. tassert.Nil(t, err)
  252. store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
  253. provider := &lockboxProvider{&fake.LockboxClientCreator{
  254. Backend: lockboxBackend,
  255. }}
  256. secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
  257. tassert.Nil(t, err)
  258. data, err := secretsClient.GetSecretMap(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID})
  259. tassert.Nil(t, err)
  260. tassert.Equal(
  261. t,
  262. map[string][]byte{
  263. k1: []byte(v1),
  264. k2: v2,
  265. },
  266. data,
  267. )
  268. }
  269. func TestGetSecretMapByVersionID(t *testing.T) {
  270. ctx := context.Background()
  271. const namespace = "namespace"
  272. authorizedKey := newFakeAuthorizedKey("0")
  273. lockboxBackend := fake.NewLockboxBackend()
  274. oldKey, oldVal := "oldKey", "oldVal"
  275. secretID, oldVersionID := lockboxBackend.CreateSecret(authorizedKey,
  276. textEntry(oldKey, oldVal),
  277. )
  278. k8sClient := clientfake.NewClientBuilder().Build()
  279. const authorizedKeySecretName = "authorizedKeySecretName"
  280. const authorizedKeySecretKey = "authorizedKeySecretKey"
  281. err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
  282. tassert.Nil(t, err)
  283. store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
  284. provider := &lockboxProvider{&fake.LockboxClientCreator{
  285. Backend: lockboxBackend,
  286. }}
  287. secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
  288. tassert.Nil(t, err)
  289. data, err := secretsClient.GetSecretMap(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: oldVersionID})
  290. tassert.Nil(t, err)
  291. tassert.Equal(t, map[string][]byte{oldKey: []byte(oldVal)}, data)
  292. newKey, newVal := "newKey", "newVal"
  293. newVersionID := lockboxBackend.AddVersion(secretID,
  294. textEntry(newKey, newVal),
  295. )
  296. data, err = secretsClient.GetSecretMap(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: oldVersionID})
  297. tassert.Nil(t, err)
  298. tassert.Equal(t, map[string][]byte{oldKey: []byte(oldVal)}, data)
  299. data, err = secretsClient.GetSecretMap(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: newVersionID})
  300. tassert.Nil(t, err)
  301. tassert.Equal(t, map[string][]byte{newKey: []byte(newVal)}, data)
  302. }
  303. // helper functions
  304. func newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey string) esv1alpha1.GenericStore {
  305. return &esv1alpha1.SecretStore{
  306. ObjectMeta: metav1.ObjectMeta{
  307. Namespace: namespace,
  308. },
  309. Spec: esv1alpha1.SecretStoreSpec{
  310. Provider: &esv1alpha1.SecretStoreProvider{
  311. YandexLockbox: &esv1alpha1.YandexLockboxProvider{
  312. Auth: esv1alpha1.YandexLockboxAuth{
  313. AuthorizedKey: esmeta.SecretKeySelector{
  314. Name: authorizedKeySecretName,
  315. Key: authorizedKeySecretKey,
  316. },
  317. },
  318. },
  319. },
  320. },
  321. }
  322. }
  323. func createK8sSecret(ctx context.Context, k8sClient client.Client, namespace, secretName, secretKey string, secretContent interface{}) error {
  324. data, err := json.Marshal(secretContent)
  325. if err != nil {
  326. return err
  327. }
  328. err = k8sClient.Create(ctx, &corev1.Secret{
  329. ObjectMeta: metav1.ObjectMeta{
  330. Namespace: namespace,
  331. Name: secretName,
  332. },
  333. Data: map[string][]byte{secretKey: data},
  334. })
  335. if err != nil {
  336. return err
  337. }
  338. return nil
  339. }
  340. func newFakeAuthorizedKey(uniqueLabel string) *iamkey.Key {
  341. return &iamkey.Key{
  342. Id: uniqueLabel,
  343. Subject: &iamkey.Key_ServiceAccountId{
  344. ServiceAccountId: uniqueLabel,
  345. },
  346. PrivateKey: uniqueLabel,
  347. }
  348. }
  349. func textEntry(key, value string) *lockbox.Payload_Entry {
  350. return &lockbox.Payload_Entry{
  351. Key: key,
  352. Value: &lockbox.Payload_Entry_TextValue{
  353. TextValue: value,
  354. },
  355. }
  356. }
  357. func binaryEntry(key string, value []byte) *lockbox.Payload_Entry {
  358. return &lockbox.Payload_Entry{
  359. Key: key,
  360. Value: &lockbox.Payload_Entry_BinaryValue{
  361. BinaryValue: value,
  362. },
  363. }
  364. }
  365. func unmarshalStringMap(t *testing.T, data []byte) map[string]string {
  366. stringMap := make(map[string]string)
  367. err := json.Unmarshal(data, &stringMap)
  368. tassert.Nil(t, err)
  369. return stringMap
  370. }
  371. func base64(data []byte) string {
  372. return b64.StdEncoding.EncodeToString(data)
  373. }