keyvault_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  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 keyvault
  13. import (
  14. "context"
  15. "encoding/json"
  16. "fmt"
  17. "reflect"
  18. "testing"
  19. "github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault"
  20. tassert "github.com/stretchr/testify/assert"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/utils/pointer"
  23. clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
  24. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  25. v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
  26. fake "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault/fake"
  27. "github.com/external-secrets/external-secrets/pkg/provider/schema"
  28. utils "github.com/external-secrets/external-secrets/pkg/utils"
  29. )
  30. type secretManagerTestCase struct {
  31. mockClient *fake.AzureMockClient
  32. secretName string
  33. secretVersion string
  34. serviceURL string
  35. ref *esv1alpha1.ExternalSecretDataRemoteRef
  36. apiErr error
  37. secretOutput keyvault.SecretBundle
  38. keyOutput keyvault.KeyBundle
  39. certOutput keyvault.CertificateBundle
  40. expectError string
  41. expectedSecret string
  42. // for testing secretmap
  43. expectedData map[string][]byte
  44. }
  45. func makeValidSecretManagerTestCase() *secretManagerTestCase {
  46. secretString := "Hello World!"
  47. smtc := secretManagerTestCase{
  48. mockClient: &fake.AzureMockClient{},
  49. secretName: "MySecret",
  50. secretVersion: "",
  51. ref: makeValidRef(),
  52. secretOutput: keyvault.SecretBundle{Value: &secretString},
  53. serviceURL: "",
  54. apiErr: nil,
  55. expectError: "",
  56. expectedSecret: secretString,
  57. expectedData: map[string][]byte{},
  58. }
  59. smtc.mockClient.WithValue(smtc.serviceURL, smtc.secretName, smtc.secretVersion, smtc.secretOutput, smtc.apiErr)
  60. return &smtc
  61. }
  62. func makeValidSecretManagerTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase {
  63. smtc := makeValidSecretManagerTestCase()
  64. for _, fn := range tweaks {
  65. fn(smtc)
  66. }
  67. smtc.mockClient.WithValue(smtc.serviceURL, smtc.secretName, smtc.secretVersion, smtc.secretOutput, smtc.apiErr)
  68. smtc.mockClient.WithKey(smtc.serviceURL, smtc.secretName, smtc.secretVersion, smtc.keyOutput, smtc.apiErr)
  69. smtc.mockClient.WithCertificate(smtc.serviceURL, smtc.secretName, smtc.secretVersion, smtc.certOutput, smtc.apiErr)
  70. return smtc
  71. }
  72. func TestNewClientManagedIdentityNoNeedForCredentials(t *testing.T) {
  73. namespace := "internal"
  74. vaultURL := "https://local.vault.url"
  75. identityID := "1234"
  76. authType := esv1alpha1.ManagedIdentity
  77. store := esv1alpha1.SecretStore{
  78. ObjectMeta: metav1.ObjectMeta{
  79. Namespace: namespace,
  80. },
  81. Spec: esv1alpha1.SecretStoreSpec{Provider: &esv1alpha1.SecretStoreProvider{AzureKV: &esv1alpha1.AzureKVProvider{
  82. AuthType: &authType,
  83. IdentityID: &identityID,
  84. VaultURL: &vaultURL,
  85. }}},
  86. }
  87. provider, err := schema.GetProvider(&store)
  88. tassert.Nil(t, err, "the return err should be nil")
  89. k8sClient := clientfake.NewClientBuilder().Build()
  90. secretClient, err := provider.NewClient(context.Background(), &store, k8sClient, namespace)
  91. if err != nil {
  92. // On non Azure environment, MSI auth not available, so this error should be returned
  93. tassert.EqualError(t, err, "failed to get oauth token from MSI: MSI not available")
  94. } else {
  95. // On Azure (where GitHub Actions are running) a secretClient is returned, as only an Authorizer is configured, but no token is requested for MI
  96. tassert.NotNil(t, secretClient)
  97. }
  98. }
  99. func TestNewClientNoCreds(t *testing.T) {
  100. namespace := "internal"
  101. vaultURL := "https://local.vault.url"
  102. tenantID := "1234"
  103. authType := esv1alpha1.ServicePrincipal
  104. store := esv1alpha1.SecretStore{
  105. ObjectMeta: metav1.ObjectMeta{
  106. Namespace: namespace,
  107. },
  108. Spec: esv1alpha1.SecretStoreSpec{Provider: &esv1alpha1.SecretStoreProvider{AzureKV: &esv1alpha1.AzureKVProvider{
  109. AuthType: &authType,
  110. VaultURL: &vaultURL,
  111. TenantID: &tenantID,
  112. }}},
  113. }
  114. provider, err := schema.GetProvider(&store)
  115. tassert.Nil(t, err, "the return err should be nil")
  116. k8sClient := clientfake.NewClientBuilder().Build()
  117. _, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
  118. tassert.EqualError(t, err, "missing secretRef in provider config")
  119. store.Spec.Provider.AzureKV.AuthSecretRef = &esv1alpha1.AzureKVAuth{}
  120. _, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
  121. tassert.EqualError(t, err, "missing accessKeyID/secretAccessKey in store config")
  122. store.Spec.Provider.AzureKV.AuthSecretRef.ClientID = &v1.SecretKeySelector{Name: "user"}
  123. _, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
  124. tassert.EqualError(t, err, "missing accessKeyID/secretAccessKey in store config")
  125. store.Spec.Provider.AzureKV.AuthSecretRef.ClientSecret = &v1.SecretKeySelector{Name: "password"}
  126. _, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
  127. tassert.EqualError(t, err, "could not find secret internal/user: secrets \"user\" not found")
  128. store.TypeMeta.Kind = esv1alpha1.ClusterSecretStoreKind
  129. store.TypeMeta.APIVersion = esv1alpha1.ClusterSecretStoreKindAPIVersion
  130. ns := "default"
  131. store.Spec.Provider.AzureKV.AuthSecretRef.ClientID.Namespace = &ns
  132. store.Spec.Provider.AzureKV.AuthSecretRef.ClientSecret.Namespace = &ns
  133. _, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
  134. tassert.EqualError(t, err, "could not find secret default/user: secrets \"user\" not found")
  135. }
  136. const (
  137. jwkPubRSA = `{"kid":"ex","kty":"RSA","key_ops":["sign","verify","wrapKey","unwrapKey","encrypt","decrypt"],"n":"p2VQo8qCfWAZmdWBVaYuYb-a-tWWm78K6Sr9poCvNcmv8rUPSLACxitQWR8gZaSH1DklVkqz-Ed8Cdlf8lkDg4Ex5tkB64jRdC1Uvn4CDpOH6cp-N2s8hTFLqy9_YaDmyQS7HiqthOi9oVjil1VMeWfaAbClGtFt6UnKD0Vb_DvLoWYQSqlhgBArFJi966b4E1pOq5Ad02K8pHBDThlIIx7unibLehhDU6q3DCwNH_OOLx6bgNtmvGYJDd1cywpkLQ3YzNCUPWnfMBJRP3iQP_WI21uP6cvo0DqBPBM4wvVzHbCT0vnIflwkbgEWkq1FprqAitZlop9KjLqzjp9vyQ","e":"AQAB"}`
  138. jwkPubEC = `{"kid":"https://example.vault.azure.net/keys/ec-p-521/e3d0e9c179b54988860c69c6ae172c65","kty":"EC","key_ops":["sign","verify"],"crv":"P-521","x":"AedOAtb7H7Oz1C_cPKI_R4CN_eai5nteY6KFW07FOoaqgQfVCSkQDK22fCOiMT_28c8LZYJRsiIFz_IIbQUW7bXj","y":"AOnchHnmBphIWXvanmMAmcCDkaED6ycW8GsAl9fQ43BMVZTqcTkJYn6vGnhn7MObizmkNSmgZYTwG-vZkIg03HHs"}`
  139. jsonTestString = `{"Name": "External", "LastName": "Secret", "Address": { "Street": "Myroad st.", "CP": "J4K4T4" } }`
  140. jsonSingleTestString = `{"Name": "External", "LastName": "Secret" }`
  141. keyName = "key/keyname"
  142. certName = "cert/certname"
  143. )
  144. func newKVJWK(b []byte) *keyvault.JSONWebKey {
  145. var key keyvault.JSONWebKey
  146. err := json.Unmarshal(b, &key)
  147. if err != nil {
  148. panic(err)
  149. }
  150. return &key
  151. }
  152. // test the sm<->azurekv interface
  153. // make sure correct values are passed and errors are handled accordingly.
  154. func TestAzureKeyVaultSecretManagerGetSecret(t *testing.T) {
  155. secretString := "changedvalue"
  156. secretCertificate := "certificate_value"
  157. // good case
  158. setSecretString := func(smtc *secretManagerTestCase) {
  159. smtc.expectedSecret = secretString
  160. smtc.secretOutput = keyvault.SecretBundle{
  161. Value: &secretString,
  162. }
  163. }
  164. setSecretStringWithVersion := func(smtc *secretManagerTestCase) {
  165. smtc.expectedSecret = secretString
  166. smtc.secretOutput = keyvault.SecretBundle{
  167. Value: &secretString,
  168. }
  169. smtc.ref.Version = "v1"
  170. smtc.secretVersion = smtc.ref.Version
  171. }
  172. setSecretWithProperty := func(smtc *secretManagerTestCase) {
  173. jsonString := jsonTestString
  174. smtc.expectedSecret = "External"
  175. smtc.secretOutput = keyvault.SecretBundle{
  176. Value: &jsonString,
  177. }
  178. smtc.ref.Property = "Name"
  179. }
  180. badSecretWithProperty := func(smtc *secretManagerTestCase) {
  181. jsonString := jsonTestString
  182. smtc.expectedSecret = ""
  183. smtc.secretOutput = keyvault.SecretBundle{
  184. Value: &jsonString,
  185. }
  186. smtc.ref.Property = "Age"
  187. smtc.expectError = fmt.Sprintf("property %s does not exist in key %s", smtc.ref.Property, smtc.ref.Key)
  188. smtc.apiErr = fmt.Errorf(smtc.expectError)
  189. }
  190. // // good case: key set
  191. setPubRSAKey := func(smtc *secretManagerTestCase) {
  192. smtc.secretName = keyName
  193. smtc.expectedSecret = jwkPubRSA
  194. smtc.keyOutput = keyvault.KeyBundle{
  195. Key: newKVJWK([]byte(jwkPubRSA)),
  196. }
  197. smtc.ref.Key = smtc.secretName
  198. }
  199. // // good case: key set
  200. setPubECKey := func(smtc *secretManagerTestCase) {
  201. smtc.secretName = keyName
  202. smtc.expectedSecret = jwkPubEC
  203. smtc.keyOutput = keyvault.KeyBundle{
  204. Key: newKVJWK([]byte(jwkPubEC)),
  205. }
  206. smtc.ref.Key = smtc.secretName
  207. }
  208. // // good case: key set
  209. setCertificate := func(smtc *secretManagerTestCase) {
  210. byteArrString := []byte(secretCertificate)
  211. smtc.secretName = certName
  212. smtc.expectedSecret = secretCertificate
  213. smtc.certOutput = keyvault.CertificateBundle{
  214. Cer: &byteArrString,
  215. }
  216. smtc.ref.Key = smtc.secretName
  217. }
  218. badSecretType := func(smtc *secretManagerTestCase) {
  219. smtc.secretName = "name"
  220. smtc.expectedSecret = ""
  221. smtc.expectError = fmt.Sprintf("unknown Azure Keyvault object Type for %s", smtc.secretName)
  222. smtc.ref.Key = fmt.Sprintf("dummy/%s", smtc.secretName)
  223. }
  224. successCases := []*secretManagerTestCase{
  225. makeValidSecretManagerTestCase(),
  226. makeValidSecretManagerTestCaseCustom(setSecretString),
  227. makeValidSecretManagerTestCaseCustom(setSecretStringWithVersion),
  228. makeValidSecretManagerTestCaseCustom(setSecretWithProperty),
  229. makeValidSecretManagerTestCaseCustom(badSecretWithProperty),
  230. makeValidSecretManagerTestCaseCustom(setPubRSAKey),
  231. makeValidSecretManagerTestCaseCustom(setPubECKey),
  232. makeValidSecretManagerTestCaseCustom(setCertificate),
  233. makeValidSecretManagerTestCaseCustom(badSecretType),
  234. }
  235. sm := Azure{
  236. provider: &esv1alpha1.AzureKVProvider{VaultURL: pointer.StringPtr("noop")},
  237. }
  238. for k, v := range successCases {
  239. sm.baseClient = v.mockClient
  240. out, err := sm.GetSecret(context.Background(), *v.ref)
  241. if !utils.ErrorContains(err, v.expectError) {
  242. t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
  243. }
  244. if string(out) != v.expectedSecret {
  245. t.Errorf("[%d] unexpected secret: expected %s, got %s", k, v.expectedSecret, string(out))
  246. }
  247. }
  248. }
  249. func TestAzureKeyVaultSecretManagerGetSecretMap(t *testing.T) {
  250. secretString := "changedvalue"
  251. secretCertificate := "certificate_value"
  252. badSecretString := func(smtc *secretManagerTestCase) {
  253. smtc.expectedSecret = secretString
  254. smtc.secretOutput = keyvault.SecretBundle{
  255. Value: &secretString,
  256. }
  257. smtc.expectError = "error unmarshalling json data: invalid character 'c' looking for beginning of value"
  258. }
  259. setSecretJSON := func(smtc *secretManagerTestCase) {
  260. jsonString := jsonSingleTestString
  261. smtc.secretOutput = keyvault.SecretBundle{
  262. Value: &jsonString,
  263. }
  264. smtc.expectedData["Name"] = []byte("External")
  265. smtc.expectedData["LastName"] = []byte("Secret")
  266. }
  267. setSecretJSONWithProperty := func(smtc *secretManagerTestCase) {
  268. jsonString := jsonTestString
  269. smtc.secretOutput = keyvault.SecretBundle{
  270. Value: &jsonString,
  271. }
  272. smtc.ref.Property = "Address"
  273. smtc.expectedData["Street"] = []byte("Myroad st.")
  274. smtc.expectedData["CP"] = []byte("J4K4T4")
  275. }
  276. badSecretWithProperty := func(smtc *secretManagerTestCase) {
  277. jsonString := jsonTestString
  278. smtc.expectedSecret = ""
  279. smtc.secretOutput = keyvault.SecretBundle{
  280. Value: &jsonString,
  281. }
  282. smtc.ref.Property = "Age"
  283. smtc.expectError = fmt.Sprintf("property %s does not exist in key %s", smtc.ref.Property, smtc.ref.Key)
  284. smtc.apiErr = fmt.Errorf(smtc.expectError)
  285. }
  286. badPubRSAKey := func(smtc *secretManagerTestCase) {
  287. smtc.secretName = keyName
  288. smtc.expectedSecret = jwkPubRSA
  289. smtc.keyOutput = keyvault.KeyBundle{
  290. Key: newKVJWK([]byte(jwkPubRSA)),
  291. }
  292. smtc.ref.Key = smtc.secretName
  293. smtc.expectError = "cannot get use dataFrom to get key secret"
  294. }
  295. badCertificate := func(smtc *secretManagerTestCase) {
  296. byteArrString := []byte(secretCertificate)
  297. smtc.secretName = certName
  298. smtc.expectedSecret = secretCertificate
  299. smtc.certOutput = keyvault.CertificateBundle{
  300. Cer: &byteArrString,
  301. }
  302. smtc.ref.Key = smtc.secretName
  303. smtc.expectError = "cannot get use dataFrom to get certificate secret"
  304. }
  305. badSecretType := func(smtc *secretManagerTestCase) {
  306. smtc.secretName = "name"
  307. smtc.expectedSecret = ""
  308. smtc.expectError = fmt.Sprintf("unknown Azure Keyvault object Type for %s", smtc.secretName)
  309. smtc.ref.Key = fmt.Sprintf("dummy/%s", smtc.secretName)
  310. }
  311. successCases := []*secretManagerTestCase{
  312. makeValidSecretManagerTestCaseCustom(badSecretString),
  313. makeValidSecretManagerTestCaseCustom(setSecretJSON),
  314. makeValidSecretManagerTestCaseCustom(setSecretJSONWithProperty),
  315. makeValidSecretManagerTestCaseCustom(badSecretWithProperty),
  316. makeValidSecretManagerTestCaseCustom(badPubRSAKey),
  317. makeValidSecretManagerTestCaseCustom(badCertificate),
  318. makeValidSecretManagerTestCaseCustom(badSecretType),
  319. }
  320. sm := Azure{
  321. provider: &esv1alpha1.AzureKVProvider{VaultURL: pointer.StringPtr("noop")},
  322. }
  323. for k, v := range successCases {
  324. sm.baseClient = v.mockClient
  325. out, err := sm.GetSecretMap(context.Background(), *v.ref)
  326. if !utils.ErrorContains(err, v.expectError) {
  327. t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
  328. }
  329. if err == nil && !reflect.DeepEqual(out, v.expectedData) {
  330. t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", k, v.expectedData, out)
  331. }
  332. }
  333. }
  334. func makeValidRef() *esv1alpha1.ExternalSecretDataRemoteRef {
  335. return &esv1alpha1.ExternalSecretDataRemoteRef{
  336. Key: "test-secret",
  337. Version: "default",
  338. }
  339. }