vault.go 12 KB

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