vault.go 12 KB

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