vault.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  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 addon
  13. import (
  14. "context"
  15. "crypto/rand"
  16. "crypto/rsa"
  17. "crypto/x509"
  18. "crypto/x509/pkix"
  19. "encoding/json"
  20. "encoding/pem"
  21. "fmt"
  22. "math/big"
  23. "net"
  24. "net/http"
  25. "os"
  26. "time"
  27. "github.com/golang-jwt/jwt/v4"
  28. vault "github.com/hashicorp/vault/api"
  29. // nolint
  30. . "github.com/onsi/ginkgo/v2"
  31. v1 "k8s.io/api/core/v1"
  32. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  33. "github.com/external-secrets/external-secrets/e2e/framework/util"
  34. )
  35. type Vault struct {
  36. chart *HelmChart
  37. Namespace string
  38. PodName string
  39. VaultClient *vault.Client
  40. VaultURL string
  41. RootToken string
  42. VaultServerCA []byte
  43. ServerCert []byte
  44. ServerKey []byte
  45. VaultClientCA []byte
  46. ClientCert []byte
  47. ClientKey []byte
  48. JWTPubkey []byte
  49. JWTPrivKey []byte
  50. JWTToken string
  51. JWTRole string
  52. JWTPath string
  53. JWTK8sPath string
  54. KubernetesAuthPath string
  55. KubernetesAuthRole string
  56. AppRoleSecret string
  57. AppRoleID string
  58. AppRolePath string
  59. }
  60. const privatePemType = "RSA PRIVATE KEY"
  61. func NewVault(namespace string) *Vault {
  62. repo := "hashicorp-" + namespace
  63. return &Vault{
  64. chart: &HelmChart{
  65. Namespace: namespace,
  66. ReleaseName: fmt.Sprintf("vault-%s", namespace), // avoid cluster role collision
  67. Chart: fmt.Sprintf("%s/vault", repo),
  68. ChartVersion: "0.11.0",
  69. Repo: ChartRepo{
  70. Name: repo,
  71. URL: "https://helm.releases.hashicorp.com",
  72. },
  73. Values: []string{"/k8s/vault.values.yaml"},
  74. },
  75. Namespace: namespace,
  76. }
  77. }
  78. type OperatorInitResponse struct {
  79. UnsealKeysB64 []string `json:"unseal_keys_b64"`
  80. RootToken string `json:"root_token"`
  81. }
  82. func (l *Vault) Install() error {
  83. By("Installing vault in " + l.Namespace)
  84. err := l.chart.Install()
  85. if err != nil {
  86. return err
  87. }
  88. err = l.initVault()
  89. if err != nil {
  90. return err
  91. }
  92. err = l.configureVault()
  93. if err != nil {
  94. return err
  95. }
  96. return nil
  97. }
  98. func (l *Vault) initVault() error {
  99. sec := &v1.Secret{
  100. ObjectMeta: metav1.ObjectMeta{
  101. Name: "vault-tls-config",
  102. Namespace: l.Namespace,
  103. },
  104. Data: map[string][]byte{},
  105. }
  106. // vault-config contains vault init config and policies
  107. files, err := os.ReadDir("/k8s/vault-config")
  108. if err != nil {
  109. return err
  110. }
  111. for _, f := range files {
  112. name := f.Name()
  113. data := mustReadFile(fmt.Sprintf("/k8s/vault-config/%s", name))
  114. sec.Data[name] = data
  115. }
  116. // gen certificates and put them into the secret
  117. serverRootPem, serverPem, serverKeyPem, clientRootPem, clientPem, clientKeyPem, err := genVaultCertificates(l.Namespace)
  118. if err != nil {
  119. return fmt.Errorf("unable to gen vault certs: %w", err)
  120. }
  121. jwtPrivkey, jwtPubkey, jwtToken, err := genVaultJWTKeys()
  122. if err != nil {
  123. return fmt.Errorf("unable to generate vault jwt keys: %w", err)
  124. }
  125. // pass certs to secret
  126. sec.Data["vault-server-ca.pem"] = serverRootPem
  127. sec.Data["server-cert.pem"] = serverPem
  128. sec.Data["server-cert-key.pem"] = serverKeyPem
  129. sec.Data["vault-client-ca.pem"] = clientRootPem
  130. sec.Data["es-client.pem"] = clientPem
  131. sec.Data["es-client-key.pem"] = clientKeyPem
  132. sec.Data["jwt-pubkey.pem"] = jwtPubkey
  133. // make certs available to the struct
  134. // so it can be used by the provider
  135. l.VaultServerCA = serverRootPem
  136. l.ServerCert = serverPem
  137. l.ServerKey = serverKeyPem
  138. l.VaultClientCA = clientRootPem
  139. l.ClientCert = clientPem
  140. l.ClientKey = clientKeyPem
  141. l.JWTPrivKey = jwtPrivkey
  142. l.JWTPubkey = jwtPubkey
  143. l.JWTToken = jwtToken
  144. l.JWTPath = "myjwt" // see configure-vault.sh
  145. l.JWTK8sPath = "myjwtk8s" // see configure-vault.sh
  146. l.JWTRole = "external-secrets-operator" // see configure-vault.sh
  147. l.KubernetesAuthPath = "mykubernetes" // see configure-vault.sh
  148. l.KubernetesAuthRole = "external-secrets-operator" // see configure-vault.sh
  149. By("Creating vault TLS secret")
  150. err = l.chart.config.CRClient.Create(context.Background(), sec)
  151. if err != nil {
  152. return err
  153. }
  154. By("Waiting for vault pods to be running")
  155. pl, err := util.WaitForPodsRunning(l.chart.config.KubeClientSet, 1, l.Namespace, metav1.ListOptions{
  156. LabelSelector: "app.kubernetes.io/name=vault",
  157. })
  158. if err != nil {
  159. return fmt.Errorf("error waiting for vault to be running: %w", err)
  160. }
  161. l.PodName = pl.Items[0].Name
  162. By("Initializing vault")
  163. out, err := util.ExecCmd(
  164. l.chart.config.KubeClientSet,
  165. l.chart.config.KubeConfig,
  166. l.PodName, l.Namespace, "vault operator init --format=json")
  167. if err != nil {
  168. return fmt.Errorf("error initializing vault: %w", err)
  169. }
  170. By("Parsing init response")
  171. var res OperatorInitResponse
  172. err = json.Unmarshal([]byte(out), &res)
  173. if err != nil {
  174. return err
  175. }
  176. l.RootToken = res.RootToken
  177. By("Unsealing vault")
  178. for _, k := range res.UnsealKeysB64 {
  179. _, err = util.ExecCmd(
  180. l.chart.config.KubeClientSet,
  181. l.chart.config.KubeConfig,
  182. l.PodName, l.Namespace, "vault operator unseal "+k)
  183. if err != nil {
  184. return fmt.Errorf("unable to unseal vault: %w", err)
  185. }
  186. }
  187. // vault becomes ready after it has been unsealed
  188. err = util.WaitForPodsReady(l.chart.config.KubeClientSet, 1, l.Namespace, metav1.ListOptions{
  189. LabelSelector: "app.kubernetes.io/name=vault",
  190. })
  191. if err != nil {
  192. return fmt.Errorf("error waiting for vault to be ready: %w", err)
  193. }
  194. serverCA := l.VaultServerCA
  195. caCertPool := x509.NewCertPool()
  196. ok := caCertPool.AppendCertsFromPEM(serverCA)
  197. if !ok {
  198. panic("unable to append server ca cert")
  199. }
  200. cfg := vault.DefaultConfig()
  201. l.VaultURL = fmt.Sprintf("https://vault-%s.%s.svc.cluster.local:8200", l.Namespace, l.Namespace)
  202. cfg.Address = l.VaultURL
  203. cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs = caCertPool
  204. l.VaultClient, err = vault.NewClient(cfg)
  205. if err != nil {
  206. return fmt.Errorf("unable to create vault client: %w", err)
  207. }
  208. l.VaultClient.SetToken(l.RootToken)
  209. return nil
  210. }
  211. func (l *Vault) configureVault() error {
  212. By("configuring vault")
  213. cmd := `sh /etc/vault-config/configure-vault.sh %s`
  214. _, err := util.ExecCmd(
  215. l.chart.config.KubeClientSet,
  216. l.chart.config.KubeConfig,
  217. l.PodName, l.Namespace, fmt.Sprintf(cmd, l.RootToken))
  218. if err != nil {
  219. return fmt.Errorf("unable to configure vault: %w", err)
  220. }
  221. // configure appRole
  222. l.AppRolePath = "myapprole"
  223. req := l.VaultClient.NewRequest(http.MethodGet, fmt.Sprintf("/v1/auth/%s/role/eso-e2e-role/role-id", l.AppRolePath))
  224. res, err := l.VaultClient.RawRequest(req) //nolint:staticcheck
  225. if err != nil {
  226. return err
  227. }
  228. defer res.Body.Close()
  229. sec, err := vault.ParseSecret(res.Body)
  230. if err != nil {
  231. return err
  232. }
  233. l.AppRoleID = sec.Data["role_id"].(string)
  234. // parse role id
  235. req = l.VaultClient.NewRequest(http.MethodPost, fmt.Sprintf("/v1/auth/%s/role/eso-e2e-role/secret-id", l.AppRolePath))
  236. res, err = l.VaultClient.RawRequest(req) //nolint:staticcheck
  237. if err != nil {
  238. return err
  239. }
  240. defer res.Body.Close()
  241. sec, err = vault.ParseSecret(res.Body)
  242. if err != nil {
  243. return err
  244. }
  245. l.AppRoleSecret = sec.Data["secret_id"].(string)
  246. return nil
  247. }
  248. func (l *Vault) Logs() error {
  249. return l.chart.Logs()
  250. }
  251. func (l *Vault) Uninstall() error {
  252. return l.chart.Uninstall()
  253. }
  254. func (l *Vault) Setup(cfg *Config) error {
  255. return l.chart.Setup(cfg)
  256. }
  257. // nolint:gocritic
  258. func genVaultCertificates(namespace string) ([]byte, []byte, []byte, []byte, []byte, []byte, error) {
  259. // gen server ca + certs
  260. serverRootCert, serverRootPem, serverRootKey, err := genCARoot()
  261. if err != nil {
  262. return nil, nil, nil, nil, nil, nil, fmt.Errorf("unable to generate ca cert: %w", err)
  263. }
  264. serverPem, serverKey, err := genPeerCert(serverRootCert, serverRootKey, "vault", []string{
  265. "localhost",
  266. "vault-" + namespace,
  267. fmt.Sprintf("vault-%s.%s.svc.cluster.local", namespace, namespace)})
  268. if err != nil {
  269. return nil, nil, nil, nil, nil, nil, fmt.Errorf("unable to generate vault server cert")
  270. }
  271. serverKeyPem := pem.EncodeToMemory(&pem.Block{
  272. Type: privatePemType,
  273. Bytes: x509.MarshalPKCS1PrivateKey(serverKey)},
  274. )
  275. // gen client ca + certs
  276. clientRootCert, clientRootPem, clientRootKey, err := genCARoot()
  277. if err != nil {
  278. return nil, nil, nil, nil, nil, nil, fmt.Errorf("unable to generate ca cert: %w", err)
  279. }
  280. clientPem, clientKey, err := genPeerCert(clientRootCert, clientRootKey, "vault-client", nil)
  281. if err != nil {
  282. return nil, nil, nil, nil, nil, nil, fmt.Errorf("unable to generate vault server cert")
  283. }
  284. clientKeyPem := pem.EncodeToMemory(&pem.Block{
  285. Type: privatePemType,
  286. Bytes: x509.MarshalPKCS1PrivateKey(clientKey)},
  287. )
  288. return serverRootPem, serverPem, serverKeyPem, clientRootPem, clientPem, clientKeyPem, err
  289. }
  290. func genVaultJWTKeys() ([]byte, []byte, string, error) {
  291. key, err := rsa.GenerateKey(rand.Reader, 2048)
  292. if err != nil {
  293. return nil, nil, "", err
  294. }
  295. privPem := pem.EncodeToMemory(&pem.Block{
  296. Type: privatePemType,
  297. Bytes: x509.MarshalPKCS1PrivateKey(key),
  298. })
  299. pk, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
  300. if err != nil {
  301. return nil, nil, "", err
  302. }
  303. pubPem := pem.EncodeToMemory(&pem.Block{
  304. Type: "RSA PUBLIC KEY",
  305. Bytes: pk,
  306. })
  307. token := jwt.NewWithClaims(jwt.SigningMethodPS256, jwt.MapClaims{
  308. "aud": "vault.client",
  309. "sub": "vault@example",
  310. "iss": "example.iss",
  311. "user": "eso",
  312. "exp": time.Now().Add(time.Hour).Unix(),
  313. "nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
  314. })
  315. // Sign and get the complete encoded token as a string using the secret
  316. tokenString, err := token.SignedString(key)
  317. if err != nil {
  318. return nil, nil, "", err
  319. }
  320. return privPem, pubPem, tokenString, nil
  321. }
  322. func genCARoot() (*x509.Certificate, []byte, *rsa.PrivateKey, error) {
  323. tpl := x509.Certificate{
  324. SerialNumber: big.NewInt(1),
  325. Subject: pkix.Name{
  326. Country: []string{"/dev/null"},
  327. Organization: []string{"External Secrets ACME"},
  328. CommonName: "External Secrets Vault CA",
  329. },
  330. NotBefore: time.Now(),
  331. NotAfter: time.Now().Add(time.Hour),
  332. KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
  333. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
  334. BasicConstraintsValid: true,
  335. IsCA: true,
  336. MaxPathLen: 2,
  337. }
  338. pkey, err := rsa.GenerateKey(rand.Reader, 2048)
  339. if err != nil {
  340. return nil, nil, nil, err
  341. }
  342. rootCert, rootPEM, err := genCert(&tpl, &tpl, &pkey.PublicKey, pkey)
  343. return rootCert, rootPEM, pkey, err
  344. }
  345. func genCert(template, parent *x509.Certificate, publicKey *rsa.PublicKey, privateKey *rsa.PrivateKey) (*x509.Certificate, []byte, error) {
  346. certBytes, err := x509.CreateCertificate(rand.Reader, template, parent, publicKey, privateKey)
  347. if err != nil {
  348. return nil, nil, fmt.Errorf("failed to create certificate: %w", err)
  349. }
  350. cert, err := x509.ParseCertificate(certBytes)
  351. if err != nil {
  352. return nil, nil, fmt.Errorf("failed to parse certificate: %w", err)
  353. }
  354. b := pem.Block{Type: "CERTIFICATE", Bytes: certBytes}
  355. certPEM := pem.EncodeToMemory(&b)
  356. return cert, certPEM, err
  357. }
  358. func genPeerCert(signingCert *x509.Certificate, signingKey *rsa.PrivateKey, cn string, dnsNames []string) ([]byte, *rsa.PrivateKey, error) {
  359. pkey, err := rsa.GenerateKey(rand.Reader, 2048)
  360. if err != nil {
  361. return nil, nil, err
  362. }
  363. tpl := x509.Certificate{
  364. Subject: pkix.Name{
  365. Country: []string{"/dev/null"},
  366. Organization: []string{"External Secrets ACME"},
  367. CommonName: cn,
  368. },
  369. SerialNumber: big.NewInt(1),
  370. NotBefore: time.Now(),
  371. NotAfter: time.Now().Add(time.Hour),
  372. KeyUsage: x509.KeyUsageCRLSign,
  373. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
  374. IsCA: false,
  375. MaxPathLenZero: true,
  376. IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
  377. DNSNames: dnsNames,
  378. }
  379. _, serverPEM, err := genCert(&tpl, signingCert, &pkey.PublicKey, signingKey)
  380. return serverPEM, pkey, err
  381. }
  382. func mustReadFile(path string) []byte {
  383. b, err := os.ReadFile(path)
  384. if err != nil {
  385. panic(err)
  386. }
  387. return b
  388. }