client.go 9.2 KB

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