vault.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  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"
  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. serverRootPem, serverPem, serverKeyPem, clientRootPem, clientPem, clientKeyPem, 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"] = serverRootPem
  125. sec.Data["server-cert.pem"] = serverPem
  126. sec.Data["server-cert-key.pem"] = serverKeyPem
  127. sec.Data["vault-client-ca.pem"] = clientRootPem
  128. sec.Data["es-client.pem"] = clientPem
  129. sec.Data["es-client-key.pem"] = 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 = serverRootPem
  134. l.ServerCert = serverPem
  135. l.ServerKey = serverKeyPem
  136. l.VaultClientCA = clientRootPem
  137. l.ClientCert = clientPem
  138. l.ClientKey = 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. // nolint:gocritic
  254. func genVaultCertificates(namespace string) ([]byte, []byte, []byte, []byte, []byte, []byte, error) {
  255. // gen server ca + certs
  256. serverRootCert, serverRootPem, serverRootKey, err := genCARoot()
  257. if err != nil {
  258. return nil, nil, nil, nil, nil, nil, fmt.Errorf("unable to generate ca cert: %w", err)
  259. }
  260. serverPem, serverKey, err := genPeerCert(serverRootCert, serverRootKey, "vault", []string{
  261. "localhost",
  262. "vault-" + namespace,
  263. fmt.Sprintf("vault-%s.%s.svc.cluster.local", namespace, namespace)})
  264. if err != nil {
  265. return nil, nil, nil, nil, nil, nil, fmt.Errorf("unable to generate vault server cert")
  266. }
  267. serverKeyPem := pem.EncodeToMemory(&pem.Block{
  268. Type: privatePemType,
  269. Bytes: x509.MarshalPKCS1PrivateKey(serverKey)},
  270. )
  271. // gen client ca + certs
  272. clientRootCert, clientRootPem, clientRootKey, err := genCARoot()
  273. if err != nil {
  274. return nil, nil, nil, nil, nil, nil, fmt.Errorf("unable to generate ca cert: %w", err)
  275. }
  276. clientPem, clientKey, err := genPeerCert(clientRootCert, clientRootKey, "vault-client", nil)
  277. if err != nil {
  278. return nil, nil, nil, nil, nil, nil, fmt.Errorf("unable to generate vault server cert")
  279. }
  280. clientKeyPem := pem.EncodeToMemory(&pem.Block{
  281. Type: privatePemType,
  282. Bytes: x509.MarshalPKCS1PrivateKey(clientKey)},
  283. )
  284. return serverRootPem, serverPem, serverKeyPem, clientRootPem, clientPem, clientKeyPem, err
  285. }
  286. func genVaultJWTKeys() ([]byte, []byte, string, error) {
  287. key, err := rsa.GenerateKey(rand.Reader, 2048)
  288. if err != nil {
  289. return nil, nil, "", err
  290. }
  291. privPem := pem.EncodeToMemory(&pem.Block{
  292. Type: privatePemType,
  293. Bytes: x509.MarshalPKCS1PrivateKey(key),
  294. })
  295. pk, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
  296. if err != nil {
  297. return nil, nil, "", err
  298. }
  299. pubPem := pem.EncodeToMemory(&pem.Block{
  300. Type: "RSA PUBLIC KEY",
  301. Bytes: pk,
  302. })
  303. token := jwt.NewWithClaims(jwt.SigningMethodPS256, jwt.MapClaims{
  304. "aud": "vault.client",
  305. "sub": "vault@example",
  306. "iss": "example.iss",
  307. "user": "eso",
  308. "exp": time.Now().Add(time.Hour).Unix(),
  309. "nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
  310. })
  311. // Sign and get the complete encoded token as a string using the secret
  312. tokenString, err := token.SignedString(key)
  313. if err != nil {
  314. return nil, nil, "", err
  315. }
  316. return privPem, pubPem, tokenString, nil
  317. }
  318. func genCARoot() (*x509.Certificate, []byte, *rsa.PrivateKey, error) {
  319. tpl := x509.Certificate{
  320. SerialNumber: big.NewInt(1),
  321. Subject: pkix.Name{
  322. Country: []string{"/dev/null"},
  323. Organization: []string{"External Secrets ACME"},
  324. CommonName: "External Secrets Vault CA",
  325. },
  326. NotBefore: time.Now(),
  327. NotAfter: time.Now().Add(time.Hour),
  328. KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
  329. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
  330. BasicConstraintsValid: true,
  331. IsCA: true,
  332. MaxPathLen: 2,
  333. }
  334. pkey, err := rsa.GenerateKey(rand.Reader, 2048)
  335. if err != nil {
  336. return nil, nil, nil, err
  337. }
  338. rootCert, rootPEM, err := genCert(&tpl, &tpl, &pkey.PublicKey, pkey)
  339. return rootCert, rootPEM, pkey, err
  340. }
  341. func genCert(template, parent *x509.Certificate, publicKey *rsa.PublicKey, privateKey *rsa.PrivateKey) (*x509.Certificate, []byte, error) {
  342. certBytes, err := x509.CreateCertificate(rand.Reader, template, parent, publicKey, privateKey)
  343. if err != nil {
  344. return nil, nil, fmt.Errorf("failed to create certificate: %w", err)
  345. }
  346. cert, err := x509.ParseCertificate(certBytes)
  347. if err != nil {
  348. return nil, nil, fmt.Errorf("failed to parse certificate: %w", err)
  349. }
  350. b := pem.Block{Type: "CERTIFICATE", Bytes: certBytes}
  351. certPEM := pem.EncodeToMemory(&b)
  352. return cert, certPEM, err
  353. }
  354. func genPeerCert(signingCert *x509.Certificate, signingKey *rsa.PrivateKey, cn string, dnsNames []string) ([]byte, *rsa.PrivateKey, error) {
  355. pkey, err := rsa.GenerateKey(rand.Reader, 2048)
  356. if err != nil {
  357. return nil, nil, err
  358. }
  359. tpl := x509.Certificate{
  360. Subject: pkix.Name{
  361. Country: []string{"/dev/null"},
  362. Organization: []string{"External Secrets ACME"},
  363. CommonName: cn,
  364. },
  365. SerialNumber: big.NewInt(1),
  366. NotBefore: time.Now(),
  367. NotAfter: time.Now().Add(time.Hour),
  368. KeyUsage: x509.KeyUsageCRLSign,
  369. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
  370. IsCA: false,
  371. MaxPathLenZero: true,
  372. IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
  373. DNSNames: dnsNames,
  374. }
  375. _, serverPEM, err := genCert(&tpl, signingCert, &pkey.PublicKey, signingKey)
  376. return serverPEM, pkey, err
  377. }
  378. func mustReadFile(path string) []byte {
  379. b, err := os.ReadFile(path)
  380. if err != nil {
  381. panic(err)
  382. }
  383. return b
  384. }