provider_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  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 openbao_test
  14. import (
  15. "crypto/x509"
  16. "encoding/json"
  17. "fmt"
  18. "net/http"
  19. "os"
  20. "os/exec"
  21. "path/filepath"
  22. "regexp"
  23. "strings"
  24. "sync/atomic"
  25. "testing"
  26. "time"
  27. "github.com/go-viper/mapstructure/v2"
  28. "gopkg.in/dnaeon/go-vcr.v4/pkg/cassette"
  29. "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder"
  30. corev1 "k8s.io/api/core/v1"
  31. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  32. "sigs.k8s.io/controller-runtime/pkg/client"
  33. clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
  34. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  35. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  36. "github.com/external-secrets/external-secrets/providers/v1/openbao"
  37. "github.com/external-secrets/external-secrets/providers/v1/openbao/internal/auth"
  38. . "github.com/onsi/gomega"
  39. )
  40. const recordDir = "testdata/http"
  41. const fakeToken = "s.fakeTOKEN123"
  42. var (
  43. requestIdReg = regexp.MustCompile(`id":"[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}"`)
  44. timeReg = regexp.MustCompile(`_time":"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+Z"`)
  45. namedAccessorReg = regexp.MustCompile(`accessor":"([a-z]+)_[0-9a-f]+"`)
  46. accessorReg = regexp.MustCompile(`accessor":"([A-Za-z0-9]+)"`)
  47. tokenReg = regexp.MustCompile(`client_token":"s\.([A-Za-z0-9]+)"`)
  48. )
  49. func getRecorder(t *testing.T) *recorder.Recorder {
  50. // this hook makes the "git diff" of a rerecord smaller by removing unneeded metadata
  51. cleanupHook := recorder.WithHook(func(i *cassette.Interaction) error {
  52. delete(i.Response.Headers, "Date")
  53. i.Response.Duration = 0
  54. i.Response.Body = requestIdReg.ReplaceAllString(i.Response.Body, `id":"00000000-0000-0000-0000-000000000000"`)
  55. i.Response.Body = timeReg.ReplaceAllString(i.Response.Body, `_time":"2099-09-09T09:09:09.09Z"`)
  56. i.Response.Body = namedAccessorReg.ReplaceAllString(i.Response.Body, `accessor":"${1}_01234567"`)
  57. i.Response.Body = accessorReg.ReplaceAllString(i.Response.Body, `accessor":"AbCdEfGHiJk123"`)
  58. i.Response.Body = tokenReg.ReplaceAllString(i.Response.Body, `client_token":"`+fakeToken+`"`)
  59. token := i.Request.Headers.Get("X-Vault-Token")
  60. if token != "" && token != "root" {
  61. i.Request.Headers.Set("X-Vault-Token", fakeToken)
  62. }
  63. var body map[string]any
  64. err := json.Unmarshal([]byte(i.Response.Body), &body)
  65. if err != nil {
  66. return err
  67. }
  68. indentedBody, err := json.MarshalIndent(body, "", " ")
  69. if err != nil {
  70. return err
  71. }
  72. i.Response.Body = string(indentedBody)
  73. return nil
  74. }, recorder.BeforeSaveHook)
  75. r, err := recorder.New(filepath.Join(recordDir, strings.ReplaceAll(t.Name(), "/", "_")), cleanupHook)
  76. Expect(err).NotTo(HaveOccurred())
  77. t.Cleanup(func() {
  78. if err := r.Stop(); err != nil {
  79. t.Log(err)
  80. }
  81. })
  82. return r
  83. }
  84. func makeValidSecretStoreWithVersion(v esv1.OpenBaoKVStoreVersion) *esv1.SecretStore {
  85. path := "secret"
  86. if v == esv1.OpenBaoKVStoreV1 {
  87. path = "secret_v1"
  88. }
  89. return &esv1.SecretStore{
  90. ObjectMeta: metav1.ObjectMeta{
  91. Name: "openbao-store",
  92. Namespace: "default",
  93. },
  94. Spec: esv1.SecretStoreSpec{
  95. Provider: &esv1.SecretStoreProvider{
  96. OpenBao: &esv1.OpenBaoProvider{
  97. Server: "http://localhost:8200",
  98. Path: &path,
  99. Version: v,
  100. Auth: &esv1.OpenBaoAuth{
  101. TokenSecretRef: &esmeta.SecretKeySelector{
  102. Name: "bao-token",
  103. Key: "token",
  104. },
  105. },
  106. },
  107. },
  108. },
  109. }
  110. }
  111. func TestMain(m *testing.M) {
  112. record := os.Getenv("ESO_PROVIDER_OPENBAO_RERECORD") == "true"
  113. if record {
  114. bao := exec.Command("bao", "server", "-dev", "-dev-root-token-id=root")
  115. bao.Stderr = os.Stderr
  116. bao.Stdout = os.Stdin
  117. err := bao.Start()
  118. if err != nil {
  119. panic(err)
  120. }
  121. defer func() {
  122. err := bao.Process.Signal(os.Interrupt)
  123. if err != nil {
  124. panic(err)
  125. }
  126. err = bao.Wait()
  127. if err != nil {
  128. panic(err)
  129. }
  130. }()
  131. err = os.RemoveAll(recordDir)
  132. if err != nil {
  133. panic(err)
  134. }
  135. time.Sleep(time.Second)
  136. initBao := exec.Command("./testdata/init-bao.sh")
  137. initBao.Stderr = os.Stderr
  138. initBao.Stdout = os.Stdin
  139. err = initBao.Run()
  140. if err != nil {
  141. panic(err)
  142. }
  143. fmt.Println("started bao, running test")
  144. }
  145. m.Run()
  146. }
  147. func TestProvider_NewClient_TokenNotFound(t *testing.T) {
  148. RegisterTestingT(t)
  149. kube := clientfake.NewClientBuilder().Build()
  150. provider := openbao.NewProvider()
  151. client, err := provider.NewClient(t.Context(), makeValidSecretStoreWithVersion(esv1.OpenBaoKVStoreV2), kube, "default")
  152. Expect(err).To(MatchError(ContainSubstring(`secrets "bao-token" not found`)))
  153. Expect(client).To(BeNil())
  154. }
  155. func TestProvider_KVv2(t *testing.T) {
  156. v := esv1.OpenBaoKVStoreV2
  157. t.Run("GetSecret_Property", func(t *testing.T) {
  158. RegisterTestingT(t)
  159. client := setupClient(t, v)
  160. data, err := client.GetSecret(t.Context(), esv1.ExternalSecretDataRemoteRef{
  161. Key: "foo",
  162. Property: "bar",
  163. })
  164. Expect(err).NotTo(HaveOccurred())
  165. Expect(data).To(BeEquivalentTo("bazz"))
  166. data, err = client.GetSecret(t.Context(), esv1.ExternalSecretDataRemoteRef{
  167. Key: "foo",
  168. Property: "does-not-exist",
  169. })
  170. Expect(err).To(MatchError(`cannot find secret data for key: "does-not-exist"`))
  171. Expect(data).To(BeNil())
  172. })
  173. t.Run("GetSecret_Versioned", func(t *testing.T) {
  174. RegisterTestingT(t)
  175. client := setupClient(t, v)
  176. data, err := client.GetSecret(t.Context(), esv1.ExternalSecretDataRemoteRef{
  177. Key: "foo",
  178. Property: "bar",
  179. Version: "1",
  180. })
  181. Expect(err).NotTo(HaveOccurred())
  182. Expect(data).To(BeEquivalentTo("old_bazz"))
  183. data, err = client.GetSecret(t.Context(), esv1.ExternalSecretDataRemoteRef{
  184. Key: "foo",
  185. Property: "bar",
  186. Version: "invalid",
  187. })
  188. Expect(err).To(MatchError(`invalid Ref.Version: strconv.Atoi: parsing "invalid": invalid syntax`))
  189. Expect(data).To(BeNil())
  190. })
  191. t.Run("GetSecret_Full", func(t *testing.T) {
  192. RegisterTestingT(t)
  193. client := setupClient(t, v)
  194. data, err := client.GetSecret(t.Context(), esv1.ExternalSecretDataRemoteRef{
  195. Key: "foo",
  196. })
  197. Expect(err).NotTo(HaveOccurred())
  198. Expect(data).To(MatchJSON(`{
  199. "bar": "bazz",
  200. "lorem": "ipsum"
  201. }`))
  202. })
  203. t.Run("GetSecretMap", func(t *testing.T) {
  204. RegisterTestingT(t)
  205. client := setupClient(t, v)
  206. data, err := client.GetSecretMap(t.Context(), esv1.ExternalSecretDataRemoteRef{
  207. Key: "foo",
  208. })
  209. Expect(err).NotTo(HaveOccurred())
  210. Expect(data).To(Equal(map[string][]byte{
  211. "bar": []byte("bazz"),
  212. "lorem": []byte("ipsum"),
  213. }))
  214. })
  215. t.Run("GetAllSecrets", func(t *testing.T) {
  216. RegisterTestingT(t)
  217. client := setupClient(t, v)
  218. allData, err := client.GetAllSecrets(t.Context(), esv1.ExternalSecretFind{
  219. Name: &esv1.FindName{
  220. RegExp: "fo+",
  221. },
  222. })
  223. Expect(err).NotTo(HaveOccurred())
  224. Expect(allData).To(HaveLen(1))
  225. Expect(allData).To(HaveKeyWithValue("foo", MatchJSON(`{
  226. "bar": "bazz",
  227. "lorem": "ipsum"
  228. }`)))
  229. })
  230. t.Run("GetAllSecrets_NoMatch", func(t *testing.T) {
  231. RegisterTestingT(t)
  232. client := setupClient(t, v)
  233. allData, err := client.GetAllSecrets(t.Context(), esv1.ExternalSecretFind{
  234. Path: new("empty"),
  235. Name: &esv1.FindName{
  236. RegExp: "nomatch",
  237. },
  238. })
  239. Expect(err).NotTo(HaveOccurred())
  240. Expect(allData).To(HaveLen(0))
  241. })
  242. }
  243. func TestProvider_KVv1(t *testing.T) {
  244. v := esv1.OpenBaoKVStoreV1
  245. t.Run("GetSecret_Property", func(t *testing.T) {
  246. RegisterTestingT(t)
  247. client := setupClient(t, v)
  248. data, err := client.GetSecret(t.Context(), esv1.ExternalSecretDataRemoteRef{
  249. Key: "foo",
  250. Property: "bar",
  251. })
  252. Expect(err).NotTo(HaveOccurred())
  253. Expect(data).To(BeEquivalentTo("bazz_v1"))
  254. })
  255. t.Run("GetSecret_Versioned", func(t *testing.T) {
  256. RegisterTestingT(t)
  257. client := setupClient(t, v)
  258. data, err := client.GetSecret(t.Context(), esv1.ExternalSecretDataRemoteRef{
  259. Key: "foo",
  260. Property: "bar",
  261. Version: "1",
  262. })
  263. Expect(err).To(MatchError("OpenBao KVv1 secrets do not support versioning (use KVv2)"))
  264. Expect(data).To(BeNil())
  265. })
  266. t.Run("GetSecret_Full", func(t *testing.T) {
  267. RegisterTestingT(t)
  268. client := setupClient(t, v)
  269. data, err := client.GetSecret(t.Context(), esv1.ExternalSecretDataRemoteRef{
  270. Key: "foo",
  271. })
  272. Expect(err).NotTo(HaveOccurred())
  273. Expect(data).To(MatchJSON(`{
  274. "bar": "bazz_v1",
  275. "lorem": "ipsum_v1"
  276. }`))
  277. })
  278. t.Run("GetAllSecrets", func(t *testing.T) {
  279. RegisterTestingT(t)
  280. client := setupClient(t, v)
  281. allData, err := client.GetAllSecrets(t.Context(), esv1.ExternalSecretFind{
  282. Name: &esv1.FindName{
  283. RegExp: "fo+",
  284. },
  285. })
  286. Expect(err).NotTo(HaveOccurred())
  287. Expect(allData).To(HaveLen(1))
  288. Expect(allData).To(HaveKeyWithValue("foo", MatchJSON(`{
  289. "bar": "bazz_v1",
  290. "lorem": "ipsum_v1"
  291. }`)))
  292. })
  293. t.Run("GetAllSecrets_NoMatch", func(t *testing.T) {
  294. RegisterTestingT(t)
  295. client := setupClient(t, v)
  296. allData, err := client.GetAllSecrets(t.Context(), esv1.ExternalSecretFind{
  297. Path: new("empty"),
  298. Name: &esv1.FindName{
  299. RegExp: "nomatch",
  300. },
  301. })
  302. Expect(err).NotTo(HaveOccurred())
  303. Expect(allData).To(HaveLen(0))
  304. })
  305. }
  306. func TestProvider_Auth(t *testing.T) {
  307. RegisterTestingT(t)
  308. kube := clientfake.NewClientBuilder().WithObjects(&corev1.Secret{
  309. ObjectMeta: metav1.ObjectMeta{
  310. Name: "shared-secret",
  311. Namespace: "default",
  312. },
  313. Data: map[string][]byte{
  314. "approle-id": []byte("dynamic-roleid"),
  315. "approle-secret": []byte("the-secret"),
  316. "userpass-password": []byte("the-password"),
  317. },
  318. }).Build()
  319. provider := openbao.NewProvider().(*openbao.Provider)
  320. cases := []struct {
  321. name string
  322. auth *esv1.OpenBaoAuth
  323. expectedCalls []string
  324. }{{
  325. name: "userpass",
  326. auth: &esv1.OpenBaoAuth{
  327. UserPass: &esv1.OpenBaoUserPassAuth{
  328. Path: "userpasspath",
  329. Username: "the-user",
  330. SecretRef: esmeta.SecretKeySelector{
  331. Name: "shared-secret",
  332. Key: "userpass-password",
  333. },
  334. },
  335. },
  336. expectedCalls: []string{`UserPass("the-user", "the-password", "userpasspath")`},
  337. }, {
  338. name: "approle static",
  339. auth: &esv1.OpenBaoAuth{
  340. AppRole: &esv1.OpenBaoAppRole{
  341. Path: "approlepath",
  342. RoleID: "static-roleid",
  343. SecretRef: esmeta.SecretKeySelector{
  344. Name: "shared-secret",
  345. Key: "approle-secret",
  346. },
  347. },
  348. },
  349. expectedCalls: []string{`AppRole("static-roleid", "the-secret", "approlepath")`},
  350. }, {
  351. name: "approle dynamic",
  352. auth: &esv1.OpenBaoAuth{
  353. AppRole: &esv1.OpenBaoAppRole{
  354. Path: "approlepath",
  355. RoleRef: &esmeta.SecretKeySelector{
  356. Name: "shared-secret",
  357. Key: "approle-id",
  358. },
  359. SecretRef: esmeta.SecretKeySelector{
  360. Name: "shared-secret",
  361. Key: "approle-secret",
  362. },
  363. },
  364. },
  365. expectedCalls: []string{`AppRole("dynamic-roleid", "the-secret", "approlepath")`},
  366. }}
  367. for _, tc := range cases {
  368. t.Run(tc.name, func(t *testing.T) {
  369. RegisterTestingT(t)
  370. factory := &auth.MockFactory{}
  371. provider.AuthMethodFactory = factory
  372. store := makeValidSecretStoreWithVersion(esv1.OpenBaoKVStoreV2)
  373. store.Spec.Provider.OpenBao.Auth = tc.auth
  374. client, err := provider.NewClient(t.Context(), store, kube, "default")
  375. Expect(err).NotTo(HaveOccurred())
  376. Expect(client).NotTo(BeNil())
  377. t.Cleanup(func() {
  378. client.Close(t.Context())
  379. })
  380. Expect(factory.GetCalls()).To(Equal(tc.expectedCalls))
  381. })
  382. }
  383. }
  384. func TestProvider_Auth_UserPass(t *testing.T) {
  385. RegisterTestingT(t)
  386. kube, provider := setupProvider(t, &corev1.Secret{
  387. ObjectMeta: metav1.ObjectMeta{
  388. Name: "password-of-alice",
  389. Namespace: "default",
  390. },
  391. Data: map[string][]byte{
  392. "pw": []byte("bob4ever"),
  393. },
  394. })
  395. store := makeValidSecretStoreWithVersion(esv1.OpenBaoKVStoreV2)
  396. store.Spec.Provider.OpenBao.Auth = &esv1.OpenBaoAuth{
  397. UserPass: &esv1.OpenBaoUserPassAuth{
  398. Path: "customuserpasspath",
  399. Username: "alice",
  400. SecretRef: esmeta.SecretKeySelector{
  401. Name: "password-of-alice",
  402. Key: "pw",
  403. },
  404. },
  405. }
  406. client, err := provider.NewClient(t.Context(), store, kube, "default")
  407. Expect(err).NotTo(HaveOccurred())
  408. Expect(client).NotTo(BeNil())
  409. t.Cleanup(func() {
  410. client.Close(t.Context())
  411. })
  412. data, err := client.GetSecret(t.Context(), esv1.ExternalSecretDataRemoteRef{
  413. Key: "foo",
  414. Property: "bar",
  415. })
  416. Expect(err).NotTo(HaveOccurred())
  417. Expect(data).To(BeEquivalentTo("bazz"))
  418. }
  419. func TestProvider_BaoNamespaces(t *testing.T) {
  420. v := esv1.OpenBaoKVStoreV2
  421. kube, provider := setupProvider(t, &corev1.Secret{
  422. ObjectMeta: metav1.ObjectMeta{
  423. Name: "auth",
  424. Namespace: "default",
  425. },
  426. Data: map[string][]byte{
  427. "token": []byte("root"),
  428. "password": []byte("bob4ever"),
  429. },
  430. })
  431. store := makeValidSecretStoreWithVersion(v)
  432. store.Spec.Provider.OpenBao.Namespace = new("my-namespace")
  433. store.Spec.Provider.OpenBao.Path = nil
  434. store.Spec.Provider.OpenBao.Auth.TokenSecretRef.Name = "auth"
  435. t.Run("WithToken", func(t *testing.T) {
  436. RegisterTestingT(t)
  437. client, err := provider.NewClient(t.Context(), store, kube, "default")
  438. Expect(err).NotTo(HaveOccurred())
  439. Expect(client).NotTo(BeNil())
  440. t.Cleanup(func() {
  441. client.Close(t.Context())
  442. })
  443. data, err := client.GetSecret(t.Context(), esv1.ExternalSecretDataRemoteRef{
  444. Key: "foo",
  445. Property: "namespaced-bar",
  446. })
  447. Expect(err).NotTo(HaveOccurred())
  448. Expect(data).To(BeEquivalentTo("namespaced-bazz"))
  449. })
  450. store.Spec.Provider.OpenBao.Auth = &esv1.OpenBaoAuth{
  451. UserPass: &esv1.OpenBaoUserPassAuth{
  452. Path: "customuserpasspath",
  453. Username: "alice",
  454. SecretRef: esmeta.SecretKeySelector{
  455. Name: "auth",
  456. Key: "password",
  457. },
  458. },
  459. Namespace: new(""),
  460. }
  461. t.Run("WithUserPass", func(t *testing.T) {
  462. RegisterTestingT(t)
  463. client, err := provider.NewClient(t.Context(), store, kube, "default")
  464. Expect(err).NotTo(HaveOccurred())
  465. Expect(client).NotTo(BeNil())
  466. t.Cleanup(func() {
  467. client.Close(t.Context())
  468. })
  469. data, err := client.GetSecret(t.Context(), esv1.ExternalSecretDataRemoteRef{
  470. Key: "foo",
  471. Property: "namespaced-bar",
  472. })
  473. Expect(err).NotTo(HaveOccurred())
  474. Expect(data).To(BeEquivalentTo("namespaced-bazz"))
  475. })
  476. }
  477. func TestProvider_Validate(t *testing.T) {
  478. RegisterTestingT(t)
  479. kube, provider := setupProvider(t, &corev1.Secret{
  480. ObjectMeta: metav1.ObjectMeta{
  481. Name: "bao-token",
  482. Namespace: "default",
  483. },
  484. Data: map[string][]byte{
  485. "token": []byte("root"),
  486. },
  487. })
  488. store := makeValidSecretStoreWithVersion(esv1.OpenBaoKVStoreV1)
  489. client, err := provider.NewClient(t.Context(), store, kube, "default")
  490. Expect(err).NotTo(HaveOccurred())
  491. Expect(client).NotTo(BeNil())
  492. Expect(client.Validate()).To(Equal(esv1.ValidationResultReady))
  493. // make version it invalid
  494. store.Spec.Provider.OpenBao.Version = esv1.OpenBaoKVStoreV2
  495. client, err = provider.NewClient(t.Context(), store, kube, "default")
  496. Expect(err).NotTo(HaveOccurred())
  497. Expect(client).NotTo(BeNil())
  498. result, err := client.Validate()
  499. Expect(err).To(MatchError("expected kv engine version 2 found version 1"))
  500. Expect(result).To(Equal(esv1.ValidationResultError))
  501. // make engine it invalid
  502. store.Spec.Provider.OpenBao.Path = new("sys")
  503. client, err = provider.NewClient(t.Context(), store, kube, "default")
  504. Expect(err).NotTo(HaveOccurred())
  505. Expect(client).NotTo(BeNil())
  506. result, err = client.Validate()
  507. Expect(err).To(MatchError(`expected mount type "kv" found "system"`))
  508. Expect(result).To(Equal(esv1.ValidationResultError))
  509. // make it a cluster store
  510. clusterStore := &esv1.ClusterSecretStore{}
  511. Expect(mapstructure.Decode(store, clusterStore)).NotTo(HaveOccurred())
  512. client, err = provider.NewClient(t.Context(), clusterStore, kube, "")
  513. Expect(err).NotTo(HaveOccurred())
  514. Expect(client).NotTo(BeNil())
  515. Expect(client.Validate()).To(Equal(esv1.ValidationResultUnknown))
  516. }
  517. var dummyCA = []byte(`-----BEGIN CERTIFICATE-----
  518. MIIBgDCCATKgAwIBAgIRAOzjpCdp42oW5MoccLpRXpAwBQYDK2VwMBIxEDAOBgNV
  519. BAMTB3Jvb3QtY2EwHhcNMjIwMjA5MTAyNTMxWhcNMzIwMjA3MTAyNTMxWjAaMRgw
  520. FgYDVQQDEw9pbnRlcm1lZGlhdGUtY2EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
  521. AATekdyX6cZe0Ajmme363TQoWnrQwXnARzeWEf4FRQE8BGWgf8z7wljjpb4M4S4f
  522. +CJAYYY/6x38UnlsxXEeBTofo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/
  523. BAgwBgEB/wIBADAdBgNVHQ4EFgQUIuDzQn9tkFs535jz5X3iXnEzbMQwHwYDVR0j
  524. BBgwFoAUa2fUac2OZ3pzE6EydVq7UvwiQa0wBQYDK2VwA0EA4gntaGs/3ME6q1y9
  525. gO4ntri2qwoC25l3q7q9BiFBmeBmvS6I1w9HCZHtB3JnVC/IYDTCYDNTbpGWEOjl
  526. aCKLCA==
  527. -----END CERTIFICATE-----`)
  528. func TestProvider_CustomCA(t *testing.T) {
  529. cases := []struct {
  530. name string
  531. spec esv1.OpenBaoProvider
  532. k8sObjects []client.Object
  533. expectedError string
  534. }{
  535. {
  536. name: "CABundle",
  537. spec: esv1.OpenBaoProvider{
  538. CABundle: dummyCA,
  539. },
  540. },
  541. {
  542. name: "CABundle_invalid",
  543. spec: esv1.OpenBaoProvider{
  544. CABundle: []byte("invalid"),
  545. },
  546. expectedError: "cannot set OpenBao CA certificate: failed to decode ca bundle: failed to parse the new certificate, not valid pem data",
  547. },
  548. {
  549. name: "CAProvider",
  550. spec: esv1.OpenBaoProvider{
  551. CAProvider: &esv1.CAProvider{
  552. Type: esv1.CAProviderTypeSecret,
  553. Name: "dummy-ca",
  554. Key: "ca.pem",
  555. },
  556. },
  557. k8sObjects: []client.Object{&corev1.Secret{
  558. ObjectMeta: metav1.ObjectMeta{
  559. Name: "dummy-ca",
  560. Namespace: "default",
  561. },
  562. Data: map[string][]byte{
  563. "ca.pem": dummyCA,
  564. },
  565. }},
  566. },
  567. {
  568. name: "CAProvider_not_found",
  569. spec: esv1.OpenBaoProvider{
  570. CAProvider: &esv1.CAProvider{
  571. Type: esv1.CAProviderTypeSecret,
  572. Name: "dummy-ca",
  573. Key: "ca.pem",
  574. },
  575. },
  576. expectedError: `cannot set OpenBao CA certificate: failed to get cert from secret: failed to resolve secret key ref: cannot get Kubernetes secret "dummy-ca" from namespace "default": secrets "dummy-ca" not found`,
  577. },
  578. }
  579. for _, tc := range cases {
  580. t.Run(tc.name, func(t *testing.T) {
  581. RegisterTestingT(t)
  582. kube := clientfake.NewClientBuilder().WithObjects(tc.k8sObjects...).Build()
  583. provider := openbao.NewProvider().(*openbao.Provider)
  584. originalFactory := provider.HTTPClientFactory
  585. var httpClient atomic.Pointer[http.Client]
  586. var factoryCallCount atomic.Int64
  587. provider.HTTPClientFactory = func() *http.Client {
  588. c := originalFactory()
  589. httpClient.Store(c)
  590. factoryCallCount.Add(1)
  591. return c
  592. }
  593. store := makeValidSecretStoreWithVersion(esv1.OpenBaoKVStoreV2)
  594. store.Spec.Provider.OpenBao = &tc.spec
  595. client, err := provider.NewClient(t.Context(), store, kube, "default")
  596. if tc.expectedError != "" {
  597. Expect(err).To(MatchError(tc.expectedError))
  598. Expect(client).To(BeNil())
  599. } else {
  600. Expect(err).NotTo(HaveOccurred())
  601. Expect(client).NotTo(BeNil())
  602. expectedPool := x509.NewCertPool()
  603. Expect(expectedPool.AppendCertsFromPEM(dummyCA)).To(BeTrue())
  604. Expect(factoryCallCount.Load()).To(BeEquivalentTo(1))
  605. transport := httpClient.Load().Transport
  606. Expect(transport).To(BeAssignableToTypeOf(&http.Transport{}))
  607. tls := transport.(*http.Transport).TLSClientConfig
  608. Expect(tls.RootCAs.Equal(expectedPool)).To(BeTrueBecause("root CAs should equal expected cert pool"))
  609. client.Close(t.Context())
  610. }
  611. })
  612. }
  613. }
  614. func setupClient(t *testing.T, v esv1.OpenBaoKVStoreVersion) esv1.SecretsClient {
  615. kube, provider := setupProvider(t, &corev1.Secret{
  616. ObjectMeta: metav1.ObjectMeta{
  617. Name: "bao-token",
  618. Namespace: "default",
  619. },
  620. Data: map[string][]byte{
  621. "token": []byte("root"),
  622. },
  623. })
  624. client, err := provider.NewClient(t.Context(), makeValidSecretStoreWithVersion(v), kube, "default")
  625. Expect(err).NotTo(HaveOccurred())
  626. Expect(client).NotTo(BeNil())
  627. t.Cleanup(func() {
  628. client.Close(t.Context())
  629. })
  630. return client
  631. }
  632. func setupProvider(t *testing.T, objects ...client.Object) (client.WithWatch, *openbao.Provider) {
  633. r := getRecorder(t)
  634. kube := clientfake.NewClientBuilder().WithObjects(objects...).Build()
  635. provider := openbao.NewProvider().(*openbao.Provider)
  636. provider.HTTPClientFactory = r.GetDefaultClient
  637. return kube, provider
  638. }