vault.go 12 KB

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