k8s_rest_config.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package esutils
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. authenticationv1 "k8s.io/api/authentication/v1"
  19. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
  21. "k8s.io/client-go/rest"
  22. "k8s.io/client-go/tools/clientcmd"
  23. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  24. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  25. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  26. "github.com/external-secrets/external-secrets/runtime/esutils/resolvers"
  27. )
  28. const errUnableCreateK8sSAToken = "cannot create service account token: %q"
  29. // ErrMultipleAuthMethods is returned when more than one inline auth method is configured.
  30. var ErrMultipleAuthMethods = errors.New("multiple auth methods provided: set exactly one of Token, ServiceAccount, or Cert")
  31. // ErrAuthRefWithInlineAuth is returned when both authRef and inline auth are set.
  32. var ErrAuthRefWithInlineAuth = errors.New("authRef and inline auth cannot both be set")
  33. // BuildRESTConfigFromKubernetesConnection builds a *rest.Config from the same
  34. // server/auth fields used by the Kubernetes SecretStore provider. It is shared
  35. // by the kubernetes and CRD providers.
  36. func BuildRESTConfigFromKubernetesConnection(
  37. ctx context.Context,
  38. ctrlClient kclient.Client,
  39. coreV1 typedcorev1.CoreV1Interface,
  40. storeKind, esNamespace string,
  41. server esv1.KubernetesServer,
  42. auth *esv1.KubernetesAuth,
  43. authRef *esmeta.SecretKeySelector,
  44. ) (*rest.Config, error) {
  45. if authRef != nil {
  46. if auth != nil {
  47. return nil, ErrAuthRefWithInlineAuth
  48. }
  49. cfg, err := fetchKubernetesSecretKey(ctx, ctrlClient, storeKind, esNamespace, *authRef)
  50. if err != nil {
  51. return nil, err
  52. }
  53. return clientcmd.RESTConfigFromKubeConfig(cfg)
  54. }
  55. if auth == nil {
  56. return nil, errors.New("no auth provider given")
  57. }
  58. if server.URL == "" {
  59. return nil, errors.New("no server URL provided")
  60. }
  61. cfg := &rest.Config{
  62. Host: server.URL,
  63. }
  64. ca, err := FetchCACertFromSource(ctx, CreateCertOpts{
  65. CABundle: server.CABundle,
  66. CAProvider: server.CAProvider,
  67. StoreKind: storeKind,
  68. Namespace: esNamespace,
  69. Client: ctrlClient,
  70. })
  71. if err != nil {
  72. return nil, err
  73. }
  74. cfg.TLSClientConfig = rest.TLSClientConfig{
  75. Insecure: false,
  76. CAData: ca,
  77. }
  78. if err := applyInlineAuth(ctx, ctrlClient, coreV1, storeKind, esNamespace, auth, cfg); err != nil {
  79. return nil, err
  80. }
  81. return cfg, nil
  82. }
  83. // applyInlineAuth validates that exactly one auth method is set and populates cfg accordingly.
  84. func applyInlineAuth(
  85. ctx context.Context,
  86. ctrlClient kclient.Client,
  87. coreV1 typedcorev1.CoreV1Interface,
  88. storeKind, esNamespace string,
  89. auth *esv1.KubernetesAuth,
  90. cfg *rest.Config,
  91. ) error {
  92. set := 0
  93. if auth.Token != nil {
  94. set++
  95. }
  96. if auth.ServiceAccount != nil {
  97. set++
  98. }
  99. if auth.Cert != nil {
  100. set++
  101. }
  102. if set > 1 {
  103. return ErrMultipleAuthMethods
  104. }
  105. switch {
  106. case auth.Token != nil:
  107. token, err := fetchKubernetesSecretKey(ctx, ctrlClient, storeKind, esNamespace, auth.Token.BearerToken)
  108. if err != nil {
  109. return fmt.Errorf("could not fetch Auth.Token.BearerToken: %w", err)
  110. }
  111. cfg.BearerToken = string(token)
  112. case auth.ServiceAccount != nil:
  113. token, err := serviceAccountToken(ctx, coreV1, storeKind, esNamespace, auth.ServiceAccount)
  114. if err != nil {
  115. return fmt.Errorf("could not fetch Auth.ServiceAccount: %w", err)
  116. }
  117. cfg.BearerToken = string(token)
  118. case auth.Cert != nil:
  119. key, cert, err := clientCertKeyFromSecrets(ctx, ctrlClient, storeKind, esNamespace, auth.Cert)
  120. if err != nil {
  121. return fmt.Errorf("could not fetch client key and cert: %w", err)
  122. }
  123. cfg.TLSClientConfig.KeyData = key
  124. cfg.TLSClientConfig.CertData = cert
  125. default:
  126. return errors.New("no auth provider given")
  127. }
  128. return nil
  129. }
  130. // fetchKubernetesSecretKey resolves a SecretKeySelector and returns its value as bytes.
  131. func fetchKubernetesSecretKey(ctx context.Context, ctrlClient kclient.Client, storeKind, esNamespace string, ref esmeta.SecretKeySelector) ([]byte, error) {
  132. secret, err := resolvers.SecretKeyRef(
  133. ctx,
  134. ctrlClient,
  135. storeKind,
  136. esNamespace,
  137. &ref,
  138. )
  139. if err != nil {
  140. return nil, err
  141. }
  142. return []byte(secret), nil
  143. }
  144. // serviceAccountToken creates a short-lived token for the referenced service account.
  145. // For ClusterSecretStore, serviceAccountRef.namespace overrides the provided namespace.
  146. func serviceAccountToken(ctx context.Context, coreV1 typedcorev1.CoreV1Interface, storeKind, namespace string, serviceAccountRef *esmeta.ServiceAccountSelector) ([]byte, error) {
  147. if storeKind == esv1.ClusterSecretStoreKind && serviceAccountRef.Namespace != nil {
  148. namespace = *serviceAccountRef.Namespace
  149. }
  150. expirationSeconds := int64(3600)
  151. tr, err := coreV1.ServiceAccounts(namespace).CreateToken(ctx, serviceAccountRef.Name, &authenticationv1.TokenRequest{
  152. Spec: authenticationv1.TokenRequestSpec{
  153. Audiences: serviceAccountRef.Audiences,
  154. ExpirationSeconds: &expirationSeconds,
  155. },
  156. }, metav1.CreateOptions{})
  157. if err != nil {
  158. return nil, fmt.Errorf(errUnableCreateK8sSAToken, err)
  159. }
  160. return []byte(tr.Status.Token), nil
  161. }
  162. // clientCertKeyFromSecrets fetches client certificate and key material from Secrets.
  163. // It returns key bytes first, then certificate bytes to match rest.TLSClientConfig fields.
  164. func clientCertKeyFromSecrets(ctx context.Context, ctrlClient kclient.Client, storeKind, esNamespace string, cert *esv1.CertAuth) ([]byte, []byte, error) {
  165. certPEM, err := fetchKubernetesSecretKey(ctx, ctrlClient, storeKind, esNamespace, cert.ClientCert)
  166. if err != nil {
  167. return nil, nil, fmt.Errorf("unable to fetch client certificate: %w", err)
  168. }
  169. keyPEM, err := fetchKubernetesSecretKey(ctx, ctrlClient, storeKind, esNamespace, cert.ClientKey)
  170. if err != nil {
  171. return nil, nil, fmt.Errorf("unable to fetch client key: %w", err)
  172. }
  173. return keyPEM, certPEM, nil
  174. }