provider.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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. "errors"
  16. "fmt"
  17. "time"
  18. vault "github.com/hashicorp/vault/api"
  19. "github.com/spf13/pflag"
  20. "k8s.io/client-go/kubernetes"
  21. typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
  22. ctrl "sigs.k8s.io/controller-runtime"
  23. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  24. ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
  25. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  26. "github.com/external-secrets/external-secrets/pkg/cache"
  27. "github.com/external-secrets/external-secrets/pkg/feature"
  28. "github.com/external-secrets/external-secrets/pkg/provider/vault/util"
  29. "github.com/external-secrets/external-secrets/pkg/utils/resolvers"
  30. )
  31. var (
  32. _ esv1beta1.Provider = &Provider{}
  33. enableCache bool
  34. logger = ctrl.Log.WithName("provider").WithName("vault")
  35. clientCache *cache.Cache[util.Client]
  36. )
  37. const (
  38. errVaultStore = "received invalid Vault SecretStore resource: %w"
  39. errVaultClient = "cannot setup new vault client: %w"
  40. errVaultCert = "cannot set Vault CA certificate: %w"
  41. errConfigMapFmt = "cannot find config map data for key: %q"
  42. errClientTLSAuth = "error from Client TLS Auth: %q"
  43. errUnknownCAProvider = "unknown caProvider type given"
  44. errCANamespace = "cannot read secret for CAProvider due to missing namespace on kind ClusterSecretStore"
  45. )
  46. type Provider struct {
  47. // NewVaultClient is a function that returns a new Vault client.
  48. // This is used for testing to inject a fake client.
  49. NewVaultClient func(config *vault.Config) (util.Client, error)
  50. }
  51. // NewVaultClient returns a new Vault client.
  52. func NewVaultClient(config *vault.Config) (util.Client, error) {
  53. vaultClient, err := vault.NewClient(config)
  54. if err != nil {
  55. return nil, err
  56. }
  57. return &util.VaultClient{
  58. SetTokenFunc: vaultClient.SetToken,
  59. TokenFunc: vaultClient.Token,
  60. ClearTokenFunc: vaultClient.ClearToken,
  61. AuthField: vaultClient.Auth(),
  62. AuthTokenField: vaultClient.Auth().Token(),
  63. LogicalField: vaultClient.Logical(),
  64. SetNamespaceFunc: vaultClient.SetNamespace,
  65. AddHeaderFunc: vaultClient.AddHeader,
  66. }, nil
  67. }
  68. // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
  69. func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
  70. return esv1beta1.SecretStoreReadWrite
  71. }
  72. // NewClient implements the Client interface.
  73. func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
  74. // controller-runtime/client does not support TokenRequest or other subresource APIs
  75. // so we need to construct our own client and use it to fetch tokens
  76. // (for Kubernetes service account token auth)
  77. restCfg, err := ctrlcfg.GetConfig()
  78. if err != nil {
  79. return nil, err
  80. }
  81. clientset, err := kubernetes.NewForConfig(restCfg)
  82. if err != nil {
  83. return nil, err
  84. }
  85. return p.newClient(ctx, store, kube, clientset.CoreV1(), namespace)
  86. }
  87. func (p *Provider) NewGeneratorClient(ctx context.Context, kube kclient.Client, corev1 typedcorev1.CoreV1Interface, vaultSpec *esv1beta1.VaultProvider, namespace string) (util.Client, error) {
  88. vStore, cfg, err := p.prepareConfig(ctx, kube, corev1, vaultSpec, nil, namespace, resolvers.EmptyStoreKind)
  89. if err != nil {
  90. return nil, err
  91. }
  92. client, err := p.NewVaultClient(cfg)
  93. if err != nil {
  94. return nil, err
  95. }
  96. _, err = p.initClient(ctx, vStore, client, cfg, vaultSpec)
  97. if err != nil {
  98. return nil, err
  99. }
  100. return client, nil
  101. }
  102. func (p *Provider) newClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, corev1 typedcorev1.CoreV1Interface, namespace string) (esv1beta1.SecretsClient, error) {
  103. storeSpec := store.GetSpec()
  104. if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Vault == nil {
  105. return nil, errors.New(errVaultStore)
  106. }
  107. vaultSpec := storeSpec.Provider.Vault
  108. vStore, cfg, err := p.prepareConfig(
  109. ctx,
  110. kube,
  111. corev1,
  112. vaultSpec,
  113. storeSpec.RetrySettings,
  114. namespace,
  115. store.GetObjectKind().GroupVersionKind().Kind)
  116. if err != nil {
  117. return nil, err
  118. }
  119. client, err := getVaultClient(p, store, cfg)
  120. if err != nil {
  121. return nil, fmt.Errorf(errVaultClient, err)
  122. }
  123. return p.initClient(ctx, vStore, client, cfg, vaultSpec)
  124. }
  125. func (p *Provider) initClient(ctx context.Context, c *client, client util.Client, cfg *vault.Config, vaultSpec *esv1beta1.VaultProvider) (esv1beta1.SecretsClient, error) {
  126. if vaultSpec.Namespace != nil {
  127. client.SetNamespace(*vaultSpec.Namespace)
  128. }
  129. if vaultSpec.ReadYourWrites && vaultSpec.ForwardInconsistent {
  130. client.AddHeader("X-Vault-Inconsistent", "forward-active-node")
  131. }
  132. c.client = client
  133. c.auth = client.Auth()
  134. c.logical = client.Logical()
  135. c.token = client.AuthToken()
  136. // allow SecretStore controller validation to pass
  137. // when using referent namespace.
  138. if c.storeKind == esv1beta1.ClusterSecretStoreKind && c.namespace == "" && isReferentSpec(vaultSpec) {
  139. return c, nil
  140. }
  141. if err := c.setAuth(ctx, cfg); err != nil {
  142. return nil, err
  143. }
  144. return c, nil
  145. }
  146. func (p *Provider) prepareConfig(ctx context.Context, kube kclient.Client, corev1 typedcorev1.CoreV1Interface, vaultSpec *esv1beta1.VaultProvider, retrySettings *esv1beta1.SecretStoreRetrySettings, namespace, storeKind string) (*client, *vault.Config, error) {
  147. c := &client{
  148. kube: kube,
  149. corev1: corev1,
  150. store: vaultSpec,
  151. log: logger,
  152. namespace: namespace,
  153. storeKind: storeKind,
  154. }
  155. cfg, err := c.newConfig(ctx)
  156. if err != nil {
  157. return nil, nil, err
  158. }
  159. // Setup retry options if present
  160. if retrySettings != nil {
  161. if retrySettings.MaxRetries != nil {
  162. cfg.MaxRetries = int(*retrySettings.MaxRetries)
  163. } else {
  164. // By default we rely only on the reconciliation process for retrying
  165. cfg.MaxRetries = 0
  166. }
  167. if retrySettings.RetryInterval != nil {
  168. retryWait, err := time.ParseDuration(*retrySettings.RetryInterval)
  169. if err != nil {
  170. return nil, nil, err
  171. }
  172. cfg.MinRetryWait = retryWait
  173. cfg.MaxRetryWait = retryWait
  174. }
  175. }
  176. return c, cfg, nil
  177. }
  178. func getVaultClient(p *Provider, store esv1beta1.GenericStore, cfg *vault.Config) (util.Client, error) {
  179. isStaticToken := store.GetSpec().Provider.Vault.Auth.TokenSecretRef != nil
  180. useCache := enableCache && !isStaticToken
  181. key := cache.Key{
  182. Name: store.GetObjectMeta().Name,
  183. Namespace: store.GetObjectMeta().Namespace,
  184. Kind: store.GetTypeMeta().Kind,
  185. }
  186. if useCache {
  187. client, ok := clientCache.Get(store.GetObjectMeta().ResourceVersion, key)
  188. if ok {
  189. return client, nil
  190. }
  191. }
  192. client, err := p.NewVaultClient(cfg)
  193. if err != nil {
  194. return nil, fmt.Errorf(errVaultClient, err)
  195. }
  196. if useCache && !clientCache.Contains(key) {
  197. clientCache.Add(store.GetObjectMeta().ResourceVersion, key, client)
  198. }
  199. return client, nil
  200. }
  201. func isReferentSpec(prov *esv1beta1.VaultProvider) bool {
  202. if prov.Auth.TokenSecretRef != nil && prov.Auth.TokenSecretRef.Namespace == nil {
  203. return true
  204. }
  205. if prov.Auth.AppRole != nil && prov.Auth.AppRole.SecretRef.Namespace == nil {
  206. return true
  207. }
  208. if prov.Auth.Kubernetes != nil && prov.Auth.Kubernetes.SecretRef != nil && prov.Auth.Kubernetes.SecretRef.Namespace == nil {
  209. return true
  210. }
  211. if prov.Auth.Kubernetes != nil && prov.Auth.Kubernetes.ServiceAccountRef != nil && prov.Auth.Kubernetes.ServiceAccountRef.Namespace == nil {
  212. return true
  213. }
  214. if prov.Auth.Ldap != nil && prov.Auth.Ldap.SecretRef.Namespace == nil {
  215. return true
  216. }
  217. if prov.Auth.UserPass != nil && prov.Auth.UserPass.SecretRef.Namespace == nil {
  218. return true
  219. }
  220. if prov.Auth.Jwt != nil && prov.Auth.Jwt.SecretRef != nil && prov.Auth.Jwt.SecretRef.Namespace == nil {
  221. return true
  222. }
  223. if prov.Auth.Jwt != nil && prov.Auth.Jwt.KubernetesServiceAccountToken != nil && prov.Auth.Jwt.KubernetesServiceAccountToken.ServiceAccountRef.Namespace == nil {
  224. return true
  225. }
  226. if prov.Auth.Cert != nil && prov.Auth.Cert.SecretRef.Namespace == nil {
  227. return true
  228. }
  229. if prov.Auth.Iam != nil && prov.Auth.Iam.JWTAuth != nil && prov.Auth.Iam.JWTAuth.ServiceAccountRef != nil && prov.Auth.Iam.JWTAuth.ServiceAccountRef.Namespace == nil {
  230. return true
  231. }
  232. if prov.Auth.Iam != nil && prov.Auth.Iam.SecretRef != nil &&
  233. (prov.Auth.Iam.SecretRef.AccessKeyID.Namespace == nil ||
  234. prov.Auth.Iam.SecretRef.SecretAccessKey.Namespace == nil ||
  235. (prov.Auth.Iam.SecretRef.SessionToken != nil && prov.Auth.Iam.SecretRef.SessionToken.Namespace == nil)) {
  236. return true
  237. }
  238. return false
  239. }
  240. func init() {
  241. var vaultTokenCacheSize int
  242. fs := pflag.NewFlagSet("vault", pflag.ExitOnError)
  243. fs.BoolVar(&enableCache, "experimental-enable-vault-token-cache", false, "Enable experimental Vault token cache. External secrets will reuse the Vault token without creating a new one on each request.")
  244. // max. 265k vault leases with 30bytes each ~= 7MB
  245. fs.IntVar(&vaultTokenCacheSize, "experimental-vault-token-cache-size", 2<<17, "Maximum size of Vault token cache. When more tokens than Only used if --experimental-enable-vault-token-cache is set.")
  246. lateInit := func() {
  247. logger.Info("initializing vault cache", "size", vaultTokenCacheSize)
  248. clientCache = cache.Must(vaultTokenCacheSize, func(client util.Client) {
  249. err := revokeTokenIfValid(context.Background(), client)
  250. if err != nil {
  251. logger.Error(err, "unable to revoke cached token on eviction")
  252. }
  253. })
  254. }
  255. feature.Register(feature.Feature{
  256. Flags: fs,
  257. Initialize: lateInit,
  258. })
  259. esv1beta1.Register(&Provider{
  260. NewVaultClient: NewVaultClient,
  261. }, &esv1beta1.SecretStoreProvider{
  262. Vault: &esv1beta1.VaultProvider{},
  263. })
  264. }