vault.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  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 vault
  13. import (
  14. "context"
  15. "crypto/x509"
  16. "errors"
  17. "fmt"
  18. "io/ioutil"
  19. "net/http"
  20. "os"
  21. "strings"
  22. "github.com/go-logr/logr"
  23. vault "github.com/hashicorp/vault/api"
  24. corev1 "k8s.io/api/core/v1"
  25. "k8s.io/apimachinery/pkg/types"
  26. ctrl "sigs.k8s.io/controller-runtime"
  27. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  28. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  29. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  30. "github.com/external-secrets/external-secrets/pkg/provider"
  31. "github.com/external-secrets/external-secrets/pkg/provider/schema"
  32. )
  33. var (
  34. _ provider.Provider = &connector{}
  35. _ provider.SecretsClient = &client{}
  36. )
  37. const (
  38. serviceAccTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
  39. errVaultStore = "received invalid Vault SecretStore resource: %w"
  40. errVaultClient = "cannot setup new vault client: %w"
  41. errVaultCert = "cannot set Vault CA certificate: %w"
  42. errReadSecret = "cannot read secret data from Vault: %w"
  43. errAuthFormat = "cannot initialize Vault client: no valid auth method specified: %w"
  44. errVaultData = "cannot parse Vault response data: %w"
  45. errVaultToken = "cannot parse Vault authentication token: %w"
  46. errVaultReqParams = "cannot set Vault request parameters: %w"
  47. errVaultRequest = "error from Vault request: %w"
  48. errVaultResponse = "cannot parse Vault response: %w"
  49. errServiceAccount = "cannot read Kubernetes service account token from file system: %w"
  50. errGetKubeSecret = "cannot get Kubernetes secret %q: %w"
  51. errSecretKeyFmt = "cannot find secret data for key: %q"
  52. )
  53. type Client interface {
  54. NewRequest(method, requestPath string) *vault.Request
  55. RawRequestWithContext(ctx context.Context, r *vault.Request) (*vault.Response, error)
  56. SetToken(v string)
  57. SetNamespace(namespace string)
  58. }
  59. type client struct {
  60. kube kclient.Client
  61. store *esv1alpha1.VaultProvider
  62. log logr.Logger
  63. client Client
  64. namespace string
  65. storeKind string
  66. }
  67. func init() {
  68. schema.Register(&connector{
  69. newVaultClient: newVaultClient,
  70. }, &esv1alpha1.SecretStoreProvider{
  71. Vault: &esv1alpha1.VaultProvider{},
  72. })
  73. }
  74. func newVaultClient(c *vault.Config) (Client, error) {
  75. return vault.NewClient(c)
  76. }
  77. type connector struct {
  78. newVaultClient func(c *vault.Config) (Client, error)
  79. }
  80. func (c *connector) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
  81. storeSpec := store.GetSpec()
  82. if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Vault == nil {
  83. return nil, errors.New(errVaultStore)
  84. }
  85. vaultSpec := storeSpec.Provider.Vault
  86. vStore := &client{
  87. kube: kube,
  88. store: vaultSpec,
  89. log: ctrl.Log.WithName("provider").WithName("vault"),
  90. namespace: namespace,
  91. storeKind: store.GetObjectKind().GroupVersionKind().Kind,
  92. }
  93. cfg, err := vStore.newConfig()
  94. if err != nil {
  95. return nil, err
  96. }
  97. client, err := c.newVaultClient(cfg)
  98. if err != nil {
  99. return nil, fmt.Errorf(errVaultClient, err)
  100. }
  101. if vaultSpec.Namespace != nil {
  102. client.SetNamespace(*vaultSpec.Namespace)
  103. }
  104. if err := vStore.setAuth(ctx, client); err != nil {
  105. return nil, err
  106. }
  107. vStore.client = client
  108. return vStore, nil
  109. }
  110. func (v *client) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
  111. data, err := v.readSecret(ctx, ref.Key, ref.Version)
  112. if err != nil {
  113. return nil, err
  114. }
  115. value, exists := data[ref.Property]
  116. if !exists {
  117. return nil, fmt.Errorf(errSecretKeyFmt, ref.Property)
  118. }
  119. return value, nil
  120. }
  121. func (v *client) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  122. return v.readSecret(ctx, ref.Key, ref.Version)
  123. }
  124. func (v *client) readSecret(ctx context.Context, path, version string) (map[string][]byte, error) {
  125. kvPath := v.store.Path
  126. if v.store.Version == esv1alpha1.VaultKVStoreV2 {
  127. if !strings.HasSuffix(kvPath, "/data") {
  128. kvPath = fmt.Sprintf("%s/data", kvPath)
  129. }
  130. }
  131. // path formated according to vault docs for v1 and v2 API
  132. // v1: https://www.vaultproject.io/api-docs/secret/kv/kv-v1#read-secret
  133. // v2: https://www.vaultproject.io/api/secret/kv/kv-v2#read-secret-version
  134. req := v.client.NewRequest(http.MethodGet, fmt.Sprintf("/v1/%s/%s", kvPath, path))
  135. if version != "" {
  136. req.Params.Set("version", version)
  137. }
  138. resp, err := v.client.RawRequestWithContext(ctx, req)
  139. if err != nil {
  140. return nil, fmt.Errorf(errReadSecret, err)
  141. }
  142. vaultSecret, err := vault.ParseSecret(resp.Body)
  143. if err != nil {
  144. return nil, err
  145. }
  146. secretData := vaultSecret.Data
  147. if v.store.Version == esv1alpha1.VaultKVStoreV2 {
  148. // Vault KV2 has data embedded within sub-field
  149. // reference - https://www.vaultproject.io/api/secret/kv/kv-v2#read-secret-version
  150. dataInt, ok := vaultSecret.Data["data"]
  151. if !ok {
  152. return nil, errors.New(errVaultData)
  153. }
  154. secretData, ok = dataInt.(map[string]interface{})
  155. if !ok {
  156. return nil, errors.New(errVaultData)
  157. }
  158. }
  159. byteMap := make(map[string][]byte, len(secretData))
  160. for k, v := range secretData {
  161. switch t := v.(type) {
  162. case string:
  163. byteMap[k] = []byte(t)
  164. case []byte:
  165. byteMap[k] = t
  166. default:
  167. return nil, errors.New(errVaultData)
  168. }
  169. }
  170. return byteMap, nil
  171. }
  172. func (v *client) newConfig() (*vault.Config, error) {
  173. cfg := vault.DefaultConfig()
  174. cfg.Address = v.store.Server
  175. if len(v.store.CABundle) == 0 {
  176. return cfg, nil
  177. }
  178. caCertPool := x509.NewCertPool()
  179. ok := caCertPool.AppendCertsFromPEM(v.store.CABundle)
  180. if !ok {
  181. return nil, errors.New(errVaultCert)
  182. }
  183. if transport, ok := cfg.HttpClient.Transport.(*http.Transport); ok {
  184. transport.TLSClientConfig.RootCAs = caCertPool
  185. }
  186. return cfg, nil
  187. }
  188. func (v *client) setAuth(ctx context.Context, client Client) error {
  189. tokenRef := v.store.Auth.TokenSecretRef
  190. if tokenRef != nil {
  191. token, err := v.secretKeyRef(ctx, tokenRef)
  192. if err != nil {
  193. return err
  194. }
  195. client.SetToken(token)
  196. return nil
  197. }
  198. appRole := v.store.Auth.AppRole
  199. if appRole != nil {
  200. token, err := v.requestTokenWithAppRoleRef(ctx, client, appRole)
  201. if err != nil {
  202. return err
  203. }
  204. client.SetToken(token)
  205. return nil
  206. }
  207. kubernetesAuth := v.store.Auth.Kubernetes
  208. if kubernetesAuth != nil {
  209. token, err := v.requestTokenWithKubernetesAuth(ctx, client, kubernetesAuth)
  210. if err != nil {
  211. return err
  212. }
  213. client.SetToken(token)
  214. return nil
  215. }
  216. return errors.New(errAuthFormat)
  217. }
  218. func (v *client) secretKeyRef(ctx context.Context, secretRef *esmeta.SecretKeySelector) (string, error) {
  219. secret := &corev1.Secret{}
  220. ref := types.NamespacedName{
  221. Namespace: v.namespace,
  222. Name: secretRef.Name,
  223. }
  224. if (v.storeKind == esv1alpha1.ClusterSecretStoreKind) &&
  225. (secretRef.Namespace != nil) {
  226. ref.Namespace = *secretRef.Namespace
  227. }
  228. err := v.kube.Get(ctx, ref, secret)
  229. if err != nil {
  230. return "", fmt.Errorf(errGetKubeSecret, ref.Name, err)
  231. }
  232. keyBytes, ok := secret.Data[secretRef.Key]
  233. if !ok {
  234. return "", fmt.Errorf(errSecretKeyFmt, secretRef.Key)
  235. }
  236. value := string(keyBytes)
  237. valueStr := strings.TrimSpace(value)
  238. return valueStr, nil
  239. }
  240. // appRoleParameters creates the required body for Vault AppRole Auth.
  241. // Reference - https://www.vaultproject.io/api-docs/auth/approle#login-with-approle
  242. func appRoleParameters(role, secret string) map[string]string {
  243. return map[string]string{
  244. "role_id": role,
  245. "secret_id": secret,
  246. }
  247. }
  248. func (v *client) requestTokenWithAppRoleRef(ctx context.Context, client Client, appRole *esv1alpha1.VaultAppRole) (string, error) {
  249. roleID := strings.TrimSpace(appRole.RoleID)
  250. secretID, err := v.secretKeyRef(ctx, &appRole.SecretRef)
  251. if err != nil {
  252. return "", err
  253. }
  254. parameters := appRoleParameters(roleID, secretID)
  255. url := strings.Join([]string{"/v1", "auth", appRole.Path, "login"}, "/")
  256. request := client.NewRequest("POST", url)
  257. err = request.SetJSONBody(parameters)
  258. if err != nil {
  259. return "", fmt.Errorf(errVaultReqParams, err)
  260. }
  261. resp, err := client.RawRequestWithContext(ctx, request)
  262. if err != nil {
  263. return "", fmt.Errorf(errVaultRequest, err)
  264. }
  265. defer resp.Body.Close()
  266. vaultResult := vault.Secret{}
  267. if err = resp.DecodeJSON(&vaultResult); err != nil {
  268. return "", fmt.Errorf(errVaultResponse, err)
  269. }
  270. token, err := vaultResult.TokenID()
  271. if err != nil {
  272. return "", fmt.Errorf(errVaultToken, err)
  273. }
  274. return token, nil
  275. }
  276. // kubeParameters creates the required body for Vault Kubernetes auth.
  277. // Reference - https://www.vaultproject.io/api/auth/kubernetes#login
  278. func kubeParameters(role, jwt string) map[string]string {
  279. return map[string]string{
  280. "role": role,
  281. "jwt": jwt,
  282. }
  283. }
  284. func (v *client) requestTokenWithKubernetesAuth(ctx context.Context, client Client, kubernetesAuth *esv1alpha1.VaultKubernetesAuth) (string, error) {
  285. jwtString := ""
  286. if kubernetesAuth.SecretRef != nil {
  287. tokenRef := kubernetesAuth.SecretRef
  288. if tokenRef.Key == "" {
  289. tokenRef = kubernetesAuth.SecretRef.DeepCopy()
  290. tokenRef.Key = "token"
  291. }
  292. jwt, err := v.secretKeyRef(ctx, tokenRef)
  293. if err != nil {
  294. return "", err
  295. }
  296. jwtString = jwt
  297. } else {
  298. // Kubernetes authentication is specified, but without a referenced
  299. // Kubernetes secret. We check if the file path for in-cluster service account
  300. // exists and attempt to use the token for Vault Kubernetes auth.
  301. if _, err := os.Stat(serviceAccTokenPath); err != nil {
  302. return "", fmt.Errorf(errServiceAccount, err)
  303. }
  304. jwtByte, err := ioutil.ReadFile(serviceAccTokenPath)
  305. if err != nil {
  306. return "", fmt.Errorf(errServiceAccount, err)
  307. }
  308. jwtString = string(jwtByte)
  309. }
  310. parameters := kubeParameters(kubernetesAuth.Role, jwtString)
  311. url := strings.Join([]string{"/v1", "auth", kubernetesAuth.Path, "login"}, "/")
  312. request := client.NewRequest("POST", url)
  313. err := request.SetJSONBody(parameters)
  314. if err != nil {
  315. return "", fmt.Errorf(errVaultReqParams, err)
  316. }
  317. resp, err := client.RawRequestWithContext(ctx, request)
  318. if err != nil {
  319. return "", fmt.Errorf(errVaultRequest, err)
  320. }
  321. defer resp.Body.Close()
  322. vaultResult := vault.Secret{}
  323. err = resp.DecodeJSON(&vaultResult)
  324. if err != nil {
  325. return "", fmt.Errorf(errVaultResponse, err)
  326. }
  327. token, err := vaultResult.TokenID()
  328. if err != nil {
  329. return "", fmt.Errorf(errVaultToken, err)
  330. }
  331. return token, nil
  332. }