provider.go 10 KB

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