client.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  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 openbao
  14. import (
  15. "context"
  16. "crypto/tls"
  17. "crypto/x509"
  18. "encoding/json"
  19. "errors"
  20. "fmt"
  21. "net/http"
  22. "strconv"
  23. "time"
  24. "github.com/openbao/openbao/api/v2"
  25. authv1 "k8s.io/api/authentication/v1"
  26. v1 "k8s.io/api/core/v1"
  27. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  28. k8sClient "sigs.k8s.io/controller-runtime/pkg/client"
  29. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  30. "github.com/external-secrets/external-secrets/runtime/esutils"
  31. "github.com/external-secrets/external-secrets/runtime/esutils/resolvers"
  32. "github.com/external-secrets/external-secrets/runtime/find"
  33. )
  34. var (
  35. _ esv1.SecretsClient = &client{}
  36. )
  37. const (
  38. errInvalidRevVersion = "invalid Ref.Version: %w"
  39. errSecretKeyNotFound = "cannot find secret data for key: %q"
  40. errFetchMount = "error while validating %q: %w"
  41. errInvalidMountType = `expected mount type "kv" found %q`
  42. errInvalidMountVersion = "expected kv engine version %s found version %s"
  43. errKVv1VersionUnsupported = "OpenBao KVv1 secrets do not support versioning (use KVv2)"
  44. errCustomCA = "cannot set OpenBao CA certificate: %w"
  45. )
  46. type client struct {
  47. client *api.Client
  48. httpClient *http.Client
  49. store *esv1.OpenBaoProvider
  50. storeKind string
  51. }
  52. func (c *client) setup(ctx context.Context, kube k8sClient.Client, namespace string, provider *Provider) error {
  53. c.httpClient = provider.HTTPClientFactory()
  54. config := api.DefaultConfig()
  55. config.HttpClient = c.httpClient
  56. config.Address = c.store.Server
  57. if len(c.store.CABundle) != 0 || c.store.CAProvider != nil {
  58. caCertPool := x509.NewCertPool()
  59. ca, err := esutils.FetchCACertFromSource(ctx, esutils.CreateCertOpts{
  60. CABundle: c.store.CABundle,
  61. CAProvider: c.store.CAProvider,
  62. StoreKind: c.storeKind,
  63. Namespace: namespace,
  64. Client: kube,
  65. })
  66. if err != nil {
  67. return fmt.Errorf(errCustomCA, err)
  68. }
  69. ok := caCertPool.AppendCertsFromPEM(ca)
  70. if !ok {
  71. return fmt.Errorf(errCustomCA, errors.New("failed add certificate to CertPool"))
  72. }
  73. if transport, ok := config.HttpClient.Transport.(*http.Transport); ok {
  74. transport = transport.Clone()
  75. if transport.TLSClientConfig == nil {
  76. transport.TLSClientConfig = &tls.Config{}
  77. }
  78. transport.TLSClientConfig.RootCAs = caCertPool
  79. config.HttpClient.Transport = transport
  80. }
  81. }
  82. client, err := api.NewClient(config)
  83. if err != nil {
  84. return err
  85. }
  86. if c.store.Namespace != nil {
  87. client.SetNamespace(*c.store.Namespace)
  88. }
  89. c.client = client
  90. return c.setupAuth(ctx, kube, namespace, provider)
  91. }
  92. func (c *client) setupAuth(ctx context.Context, kube k8sClient.Client, namespace string, provider *Provider) error {
  93. if c.store.Auth == nil {
  94. return nil
  95. }
  96. if c.store.Auth.TokenSecretRef != nil {
  97. token, err := resolvers.SecretKeyRef(ctx, kube, c.storeKind, namespace, c.store.Auth.TokenSecretRef)
  98. if err != nil {
  99. return err
  100. }
  101. c.client.SetToken(token)
  102. return nil
  103. }
  104. var auth api.AuthMethod
  105. switch {
  106. case c.store.Auth.UserPass != nil:
  107. userPass := c.store.Auth.UserPass
  108. password, err := resolvers.SecretKeyRef(ctx, kube, c.storeKind, namespace, &userPass.SecretRef)
  109. if err != nil {
  110. return err
  111. }
  112. auth, err = provider.AuthMethodFactory.UserPass(userPass.Username, password, userPass.Path)
  113. if err != nil {
  114. return err
  115. }
  116. case c.store.Auth.AppRole != nil:
  117. appRole := c.store.Auth.AppRole
  118. secret, err := resolvers.SecretKeyRef(ctx, kube, c.storeKind, namespace, &appRole.SecretRef)
  119. if err != nil {
  120. return err
  121. }
  122. roleID := appRole.RoleID
  123. if appRole.RoleRef != nil { // RoleID and RoleRef are mutually exclusive (enforced by CRD validation)
  124. roleID, err = resolvers.SecretKeyRef(ctx, kube, c.storeKind, namespace, appRole.RoleRef)
  125. if err != nil {
  126. return err
  127. }
  128. }
  129. auth, err = provider.AuthMethodFactory.AppRole(roleID, secret, appRole.Path)
  130. if err != nil {
  131. return err
  132. }
  133. case c.store.Auth.Kubernetes != nil:
  134. kubernetes := c.store.Auth.Kubernetes
  135. jwt, err := c.getJwt(ctx, kube, namespace)
  136. if err != nil {
  137. return err
  138. }
  139. auth, err = provider.AuthMethodFactory.Kubernetes(kubernetes.Role, jwt, kubernetes.Path)
  140. if err != nil {
  141. return err
  142. }
  143. default:
  144. return fmt.Errorf("unsupported auth method") // this should not happen, because of CRD validation (unless a case is missing above)
  145. }
  146. authClient := c.client
  147. if c.store.Auth.Namespace != nil {
  148. authClient = authClient.WithNamespace(*c.store.Auth.Namespace)
  149. }
  150. _, err := authClient.Auth().Login(ctx, auth)
  151. if err != nil {
  152. return err
  153. }
  154. c.client.SetToken(authClient.Token())
  155. return nil
  156. }
  157. func (c *client) Close(_ context.Context) error {
  158. if c.httpClient != nil {
  159. c.httpClient.CloseIdleConnections()
  160. c.httpClient = nil
  161. }
  162. c.client = nil
  163. c.store = nil
  164. return nil
  165. }
  166. func (c *client) DeleteSecret(_ context.Context, _ esv1.PushSecretRemoteRef) error {
  167. return errors.New("delete secret is not supported (the OpenBao provider is currently read only)")
  168. }
  169. func (c *client) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  170. if ref.Tags != nil {
  171. return nil, errors.New("tag based search is not implemented")
  172. }
  173. listPath := ""
  174. if ref.Path != nil {
  175. listPath = *ref.Path
  176. }
  177. var list func(ctx context.Context, secretPath string) (*api.KVList, error)
  178. if c.useV1() {
  179. list = c.client.KVv1(c.path()).List
  180. } else {
  181. list = c.client.KVv2(c.path()).List
  182. }
  183. meta, err := list(ctx, listPath)
  184. if err != nil {
  185. return nil, err
  186. }
  187. if meta == nil {
  188. return nil, nil
  189. }
  190. return c.findSecretsFromName(ctx, meta.Keys, *ref.Name)
  191. }
  192. func (c *client) findSecretsFromName(ctx context.Context, candidates []string, ref esv1.FindName) (map[string][]byte, error) {
  193. secrets := make(map[string][]byte)
  194. matcher, err := find.New(ref)
  195. if err != nil {
  196. return nil, err
  197. }
  198. for _, name := range candidates {
  199. ok := matcher.MatchName(name)
  200. if ok {
  201. secret, err := c.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: name})
  202. if errors.Is(err, esv1.NoSecretError{}) {
  203. continue
  204. }
  205. if err != nil {
  206. return nil, err
  207. }
  208. if secret != nil {
  209. secrets[name] = secret
  210. }
  211. }
  212. }
  213. return secrets, nil
  214. }
  215. func (c *client) useV1() bool {
  216. return c.store.Version == esv1.OpenBaoKVStoreV1
  217. }
  218. func (c *client) path() string {
  219. if c.store.Path != nil {
  220. return *c.store.Path
  221. }
  222. return "kv"
  223. }
  224. func (c *client) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  225. var data *api.KVSecret
  226. var err error
  227. if c.useV1() {
  228. if ref.Version != "" {
  229. return nil, errors.New(errKVv1VersionUnsupported)
  230. }
  231. kv := c.client.KVv1(c.path())
  232. data, err = kv.Get(ctx, ref.Key)
  233. if err != nil {
  234. return nil, err
  235. }
  236. } else {
  237. kv := c.client.KVv2(c.path())
  238. if ref.Version != "" {
  239. version, err := strconv.Atoi(ref.Version)
  240. if err != nil {
  241. return nil, fmt.Errorf(errInvalidRevVersion, err)
  242. }
  243. data, err = kv.GetVersion(ctx, ref.Key, version)
  244. if err != nil {
  245. return nil, err
  246. }
  247. } else {
  248. data, err = kv.Get(ctx, ref.Key)
  249. if err != nil {
  250. return nil, err
  251. }
  252. }
  253. }
  254. if ref.Property == "" {
  255. return json.Marshal(data.Data)
  256. }
  257. property, ok := data.Data[ref.Property]
  258. if !ok {
  259. return nil, fmt.Errorf(errSecretKeyNotFound, ref.Property)
  260. }
  261. return esutils.GetByteValue(property)
  262. }
  263. func (c *client) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  264. data, err := c.GetSecret(ctx, ref)
  265. if err != nil {
  266. return nil, err
  267. }
  268. var secretData map[string]any
  269. err = json.Unmarshal(data, &secretData)
  270. if err != nil {
  271. return nil, err
  272. }
  273. byteMap := make(map[string][]byte, len(secretData))
  274. for k, v := range secretData {
  275. byteMap[k], err = esutils.GetByteValue(v)
  276. if err != nil {
  277. return nil, err
  278. }
  279. }
  280. return byteMap, nil
  281. }
  282. func (c *client) PushSecret(_ context.Context, _ *v1.Secret, _ esv1.PushSecretData) error {
  283. return errors.New("push secret is not supported (the OpenBao provider is currently read only)")
  284. }
  285. func (c *client) SecretExists(_ context.Context, _ esv1.PushSecretRemoteRef) (bool, error) {
  286. return false, errors.New("not implemented")
  287. }
  288. func (c *client) Validate() (esv1.ValidationResult, error) {
  289. // when using referent namespace we can not validate the token
  290. // because the namespace is not known yet when Validate() is called
  291. // from the SecretStore controller.
  292. if c.storeKind == esv1.ClusterSecretStoreKind && isReferentSpec(c.store) {
  293. return esv1.ValidationResultUnknown, nil
  294. }
  295. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  296. defer cancel()
  297. mount, err := c.client.Sys().MountInfoWithContext(ctx, c.path())
  298. if err != nil {
  299. return esv1.ValidationResultError, fmt.Errorf(errFetchMount, c.store.Server, err)
  300. }
  301. if mount.Type != "kv" {
  302. return esv1.ValidationResultError, fmt.Errorf(errInvalidMountType, mount.Type)
  303. }
  304. actualVersion := mount.Options["version"]
  305. expectedVersion := string(c.store.Version[1:]) // drop the "v" prefix
  306. if expectedVersion != actualVersion {
  307. return esv1.ValidationResultError, fmt.Errorf(errInvalidMountVersion, expectedVersion, actualVersion)
  308. }
  309. return esv1.ValidationResultReady, nil
  310. }
  311. // getJwt retrieves a JWT token from the given Kubernetes ServiceAccount (`serviceAccountRef`) or Kubernetes secret (`secretRef`).
  312. func (c *client) getJwt(ctx context.Context, kube k8sClient.Client, namespace string) (string, error) {
  313. kubernetesAuth := c.store.Auth.Kubernetes
  314. if kubernetesAuth.ServiceAccountRef != nil {
  315. var expirationSeconds int64 = 600
  316. saNamespace := namespace
  317. if c.storeKind == esv1.ClusterSecretStoreKind && kubernetesAuth.ServiceAccountRef.Namespace != nil {
  318. saNamespace = *kubernetesAuth.ServiceAccountRef.Namespace
  319. }
  320. sa := &v1.ServiceAccount{
  321. ObjectMeta: metav1.ObjectMeta{
  322. Name: kubernetesAuth.ServiceAccountRef.Name,
  323. Namespace: saNamespace,
  324. },
  325. }
  326. tokenRequest := &authv1.TokenRequest{
  327. Spec: authv1.TokenRequestSpec{
  328. Audiences: kubernetesAuth.ServiceAccountRef.Audiences,
  329. ExpirationSeconds: &expirationSeconds,
  330. },
  331. }
  332. if err := kube.SubResource("token").Create(ctx, sa, tokenRequest); err != nil {
  333. return "", fmt.Errorf("cannot request Kubernetes service account token for service account %q: %w", kubernetesAuth.ServiceAccountRef.Name, err)
  334. }
  335. return tokenRequest.Status.Token, nil
  336. }
  337. if kubernetesAuth.SecretRef != nil {
  338. tokenRef := kubernetesAuth.SecretRef
  339. if tokenRef.Key == "" {
  340. tokenRef = kubernetesAuth.SecretRef.DeepCopy()
  341. tokenRef.Key = "token"
  342. }
  343. jwt, err := resolvers.SecretKeyRef(ctx, kube, c.storeKind, namespace, tokenRef)
  344. if err != nil {
  345. return "", err
  346. }
  347. return jwt, nil
  348. }
  349. return "", fmt.Errorf("serviceAccountRef or secretRef was not set. Unable to get a jwt")
  350. }