client.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  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 kubernetes
  14. import (
  15. "context"
  16. "encoding/base64"
  17. "encoding/json"
  18. "errors"
  19. "fmt"
  20. "maps"
  21. "strings"
  22. "github.com/tidwall/gjson"
  23. v1 "k8s.io/api/core/v1"
  24. "k8s.io/apimachinery/pkg/api/equality"
  25. apierrors "k8s.io/apimachinery/pkg/api/errors"
  26. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  27. "k8s.io/apimachinery/pkg/labels"
  28. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  29. "github.com/external-secrets/external-secrets/runtime/constants"
  30. "github.com/external-secrets/external-secrets/runtime/esutils"
  31. "github.com/external-secrets/external-secrets/runtime/esutils/metadata"
  32. "github.com/external-secrets/external-secrets/runtime/find"
  33. "github.com/external-secrets/external-secrets/runtime/metrics"
  34. )
  35. const (
  36. metaLabels = "labels"
  37. metaAnnotations = "annotations"
  38. )
  39. // GetSecret retrieves a secret from the Kubernetes API server by its key.
  40. func (c *Client) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  41. secret, err := c.userSecretClient.Get(ctx, ref.Key, metav1.GetOptions{})
  42. if err != nil {
  43. return nil, err
  44. }
  45. // if property is not defined, we will return the json-serialized secret
  46. if ref.Property == "" {
  47. if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
  48. m := map[string]map[string]string{}
  49. m[metaLabels] = secret.Labels
  50. m[metaAnnotations] = secret.Annotations
  51. j, err := esutils.JSONMarshal(m)
  52. if err != nil {
  53. return nil, err
  54. }
  55. return j, nil
  56. }
  57. m := map[string]string{}
  58. for key, val := range secret.Data {
  59. m[key] = string(val)
  60. }
  61. j, err := esutils.JSONMarshal(m)
  62. if err != nil {
  63. return nil, err
  64. }
  65. return j, nil
  66. }
  67. return getSecret(secret, ref)
  68. }
  69. // DeleteSecret removes a secret value from Kubernetes.
  70. // It requires a property to be specified in the RemoteRef.
  71. func (c *Client) DeleteSecret(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) error {
  72. extSecret, getErr := c.userSecretClient.Get(ctx, remoteRef.GetRemoteKey(), metav1.GetOptions{})
  73. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesGetSecret, getErr)
  74. if getErr != nil {
  75. if apierrors.IsNotFound(getErr) {
  76. // return gracefully if no secret exists
  77. return nil
  78. }
  79. return getErr
  80. }
  81. if remoteRef.GetProperty() != "" {
  82. if _, ok := extSecret.Data[remoteRef.GetProperty()]; !ok {
  83. // return gracefully if specified secret does not contain the given property
  84. return nil
  85. }
  86. if len(extSecret.Data) > 1 {
  87. return c.removeProperty(ctx, extSecret, remoteRef)
  88. }
  89. }
  90. return c.fullDelete(ctx, remoteRef.GetRemoteKey())
  91. }
  92. // SecretExists checks if a secret exists in Kubernetes.
  93. func (c *Client) SecretExists(ctx context.Context, ref esv1.PushSecretRemoteRef) (bool, error) {
  94. secret, err := c.userSecretClient.Get(ctx, ref.GetRemoteKey(), metav1.GetOptions{})
  95. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesGetSecret, err)
  96. if err != nil {
  97. if apierrors.IsNotFound(err) {
  98. return false, nil
  99. }
  100. return false, err
  101. }
  102. if ref.GetProperty() != "" {
  103. _, ok := secret.Data[ref.GetProperty()]
  104. return ok, nil
  105. }
  106. return true, nil
  107. }
  108. // PushSecret creates or updates a secret in Kubernetes.
  109. func (c *Client) PushSecret(ctx context.Context, secret *v1.Secret, data esv1.PushSecretData) error {
  110. if data.GetProperty() == "" && data.GetSecretKey() != "" {
  111. return errors.New("requires property in RemoteRef to push secret value if secret key is defined")
  112. }
  113. targetNamespace := c.store.RemoteNamespace
  114. pushMeta, err := metadata.ParseMetadataParameters[PushSecretMetadataSpec](data.GetMetadata())
  115. if err != nil {
  116. return fmt.Errorf("unable to parse metadata parameters: %w", err)
  117. }
  118. if pushMeta != nil && pushMeta.Spec.RemoteNamespace != "" {
  119. if c.storeKind != esv1.ClusterSecretStoreKind {
  120. return fmt.Errorf("remoteNamespace override is only supported with ClusterSecretStore")
  121. }
  122. targetNamespace = pushMeta.Spec.RemoteNamespace
  123. }
  124. secretClient := c.secretsClientFor(targetNamespace)
  125. remoteSecret := &v1.Secret{
  126. ObjectMeta: metav1.ObjectMeta{
  127. Namespace: targetNamespace,
  128. Name: data.GetRemoteKey(),
  129. },
  130. }
  131. return c.createOrUpdate(ctx, secretClient, remoteSecret, func() error {
  132. return c.mergePushSecretData(data, pushMeta, remoteSecret, secret)
  133. })
  134. }
  135. func (c *Client) mergePushSecretData(remoteRef esv1.PushSecretData, pushMeta *metadata.PushSecretMetadata[PushSecretMetadataSpec], remoteSecret, localSecret *v1.Secret) error {
  136. secretType := v1.SecretTypeOpaque
  137. if localSecret.Type != "" {
  138. secretType = localSecret.Type
  139. }
  140. remoteSecret.Type = secretType
  141. if remoteSecret.Data == nil {
  142. remoteSecret.Data = make(map[string][]byte)
  143. }
  144. var targetLabels, targetAnnotations map[string]string
  145. sourceLabels, sourceAnnotations, err := mergeSourceMetadata(localSecret, pushMeta)
  146. if err != nil {
  147. return fmt.Errorf("failed to merge source metadata: %w", err)
  148. }
  149. targetLabels, targetAnnotations, err = mergeTargetMetadata(remoteSecret, pushMeta, sourceLabels, sourceAnnotations)
  150. if err != nil {
  151. return fmt.Errorf("failed to merge target metadata: %w", err)
  152. }
  153. remoteSecret.ObjectMeta.Labels = targetLabels
  154. remoteSecret.ObjectMeta.Annotations = targetAnnotations
  155. // case 1: push the whole secret
  156. if remoteRef.GetProperty() == "" {
  157. maps.Copy(remoteSecret.Data, localSecret.Data)
  158. return nil
  159. }
  160. // cases 2a + 2b: push into a property.
  161. // if secret key is empty, we will marshal the whole secret and put it into
  162. // the property defined in the remoteRef.
  163. if remoteRef.GetSecretKey() == "" {
  164. value, err := c.marshalData(localSecret)
  165. if err != nil {
  166. return err
  167. }
  168. remoteSecret.Data[remoteRef.GetProperty()] = value
  169. } else {
  170. // if secret key is defined, we will push that key from the local secret
  171. remoteSecret.Data[remoteRef.GetProperty()] = localSecret.Data[remoteRef.GetSecretKey()]
  172. }
  173. return nil
  174. }
  175. func (c *Client) createOrUpdate(ctx context.Context, secretClient KClient, targetSecret *v1.Secret, f func() error) error {
  176. target, err := secretClient.Get(ctx, targetSecret.Name, metav1.GetOptions{})
  177. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesGetSecret, err)
  178. if err != nil {
  179. if !apierrors.IsNotFound(err) {
  180. return err
  181. }
  182. if err := f(); err != nil {
  183. return err
  184. }
  185. _, err := secretClient.Create(ctx, targetSecret, metav1.CreateOptions{})
  186. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesCreateSecret, err)
  187. if err != nil {
  188. return err
  189. }
  190. return nil
  191. }
  192. *targetSecret = *target
  193. existing := targetSecret.DeepCopyObject()
  194. if err := f(); err != nil {
  195. return err
  196. }
  197. if equality.Semantic.DeepEqual(existing, targetSecret) {
  198. return nil
  199. }
  200. _, err = secretClient.Update(ctx, targetSecret, metav1.UpdateOptions{})
  201. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesUpdateSecret, err)
  202. if err != nil {
  203. return err
  204. }
  205. return nil
  206. }
  207. func (c *Client) marshalData(secret *v1.Secret) ([]byte, error) {
  208. values := make(map[string]string)
  209. for k, v := range secret.Data {
  210. values[k] = string(v)
  211. }
  212. // marshal
  213. value, err := esutils.JSONMarshal(values)
  214. if err != nil {
  215. return nil, fmt.Errorf("failed to marshal secrets into a single property: %w", err)
  216. }
  217. return value, nil
  218. }
  219. // GetSecretMap retrieves a secret from Kubernetes and returns it as a map.
  220. // The secret data is converted to a map of key/value pairs.
  221. func (c *Client) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  222. secret, err := c.userSecretClient.Get(ctx, ref.Key, metav1.GetOptions{})
  223. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesGetSecret, err)
  224. if apierrors.IsNotFound(err) {
  225. return nil, esv1.NoSecretError{}
  226. }
  227. if err != nil {
  228. return nil, err
  229. }
  230. var tmpMap map[string][]byte
  231. if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
  232. tmpMap, err = getSecretMetadata(secret)
  233. if err != nil {
  234. return nil, err
  235. }
  236. } else {
  237. tmpMap = secret.Data
  238. }
  239. if ref.Property != "" {
  240. retMap, err := getPropertyMap(ref.Key, ref.Property, tmpMap)
  241. if err != nil {
  242. return nil, err
  243. }
  244. return retMap, nil
  245. }
  246. return tmpMap, nil
  247. }
  248. func getPropertyMap(key, property string, tmpMap map[string][]byte) (map[string][]byte, error) {
  249. byteArr, err := esutils.JSONMarshal(tmpMap)
  250. if err != nil {
  251. return nil, err
  252. }
  253. var retMap map[string][]byte
  254. jsonStr := string(byteArr)
  255. // We need to search if a given key with a . exists before using gjson operations.
  256. found := strings.Contains(property, ".")
  257. if found {
  258. refProperty := strings.ReplaceAll(property, ".", "\\.")
  259. retMap, err = getMapFromValues(refProperty, jsonStr)
  260. if err != nil {
  261. return nil, err
  262. }
  263. if retMap != nil {
  264. return retMap, nil
  265. }
  266. }
  267. retMap, err = getMapFromValues(property, jsonStr)
  268. if err != nil {
  269. return nil, err
  270. }
  271. if retMap == nil {
  272. return nil, fmt.Errorf("property %s does not exist in key %s", property, key)
  273. }
  274. return retMap, nil
  275. }
  276. func getMapFromValues(property, jsonStr string) (map[string][]byte, error) {
  277. val := gjson.Get(jsonStr, property)
  278. if val.Exists() {
  279. retMap := make(map[string][]byte)
  280. var tmpMap map[string]any
  281. decoded, err := base64.StdEncoding.DecodeString(val.String())
  282. if err != nil {
  283. return nil, err
  284. }
  285. err = json.Unmarshal(decoded, &tmpMap)
  286. if err != nil {
  287. // Do not return the raw error as json.Unmarshal errors may contain
  288. // sensitive secret data in the error message
  289. return nil, errors.New("failed to unmarshal secret: invalid JSON format")
  290. }
  291. for k, v := range tmpMap {
  292. b, err := esutils.JSONMarshal(v)
  293. if err != nil {
  294. return nil, err
  295. }
  296. retMap[k] = b
  297. }
  298. return retMap, nil
  299. }
  300. return nil, nil
  301. }
  302. func getSecretMetadata(secret *v1.Secret) (map[string][]byte, error) {
  303. var err error
  304. tmpMap := make(map[string][]byte)
  305. tmpMap[metaLabels], err = esutils.JSONMarshal(secret.ObjectMeta.Labels)
  306. if err != nil {
  307. return nil, err
  308. }
  309. tmpMap[metaAnnotations], err = esutils.JSONMarshal(secret.ObjectMeta.Annotations)
  310. if err != nil {
  311. return nil, err
  312. }
  313. return tmpMap, nil
  314. }
  315. // GetAllSecrets retrieves multiple secrets from Kubernetes based on the search criteria.
  316. func (c *Client) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  317. if ref.Tags != nil {
  318. return c.findByTags(ctx, ref)
  319. }
  320. if ref.Name != nil {
  321. return c.findByName(ctx, ref)
  322. }
  323. return nil, fmt.Errorf("unexpected find operator: %#v", ref)
  324. }
  325. func (c *Client) findByTags(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  326. // empty/nil tags = everything
  327. sel, err := labels.ValidatedSelectorFromSet(ref.Tags)
  328. if err != nil {
  329. return nil, fmt.Errorf("unable to validate selector tags: %w", err)
  330. }
  331. secrets, err := c.userSecretClient.List(ctx, metav1.ListOptions{LabelSelector: sel.String()})
  332. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesListSecrets, err)
  333. if err != nil {
  334. return nil, fmt.Errorf("unable to list secrets: %w", err)
  335. }
  336. data := make(map[string][]byte)
  337. for _, secret := range secrets.Items {
  338. jsonStr, err := esutils.JSONMarshal(convertMap(secret.Data))
  339. if err != nil {
  340. return nil, err
  341. }
  342. data[secret.Name] = jsonStr
  343. }
  344. return esutils.ConvertKeys(ref.ConversionStrategy, data)
  345. }
  346. func (c *Client) findByName(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  347. secrets, err := c.userSecretClient.List(ctx, metav1.ListOptions{})
  348. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesListSecrets, err)
  349. if err != nil {
  350. return nil, fmt.Errorf("unable to list secrets: %w", err)
  351. }
  352. matcher, err := find.New(*ref.Name)
  353. if err != nil {
  354. return nil, err
  355. }
  356. data := make(map[string][]byte)
  357. for _, secret := range secrets.Items {
  358. if !matcher.MatchName(secret.Name) {
  359. continue
  360. }
  361. jsonStr, err := esutils.JSONMarshal(convertMap(secret.Data))
  362. if err != nil {
  363. return nil, err
  364. }
  365. data[secret.Name] = jsonStr
  366. }
  367. return esutils.ConvertKeys(ref.ConversionStrategy, data)
  368. }
  369. // Close implements cleanup operations for the Kubernetes client.
  370. func (c *Client) Close(_ context.Context) error {
  371. return nil
  372. }
  373. func convertMap(in map[string][]byte) map[string]string {
  374. out := make(map[string]string)
  375. for k, v := range in {
  376. out[k] = string(v)
  377. }
  378. return out
  379. }
  380. // fullDelete removes remote secret completely.
  381. func (c *Client) fullDelete(ctx context.Context, secretName string) error {
  382. err := c.userSecretClient.Delete(ctx, secretName, metav1.DeleteOptions{})
  383. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesDeleteSecret, err)
  384. // gracefully return on not found
  385. if apierrors.IsNotFound(err) {
  386. return nil
  387. }
  388. return err
  389. }
  390. // removeProperty removes single data property from remote secret.
  391. func (c *Client) removeProperty(ctx context.Context, extSecret *v1.Secret, remoteRef esv1.PushSecretRemoteRef) error {
  392. delete(extSecret.Data, remoteRef.GetProperty())
  393. _, err := c.userSecretClient.Update(ctx, extSecret, metav1.UpdateOptions{})
  394. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesUpdateSecret, err)
  395. return err
  396. }
  397. func getSecret(secret *v1.Secret, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  398. if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
  399. s, found, err := getFromSecretMetadata(secret, ref)
  400. if err != nil {
  401. return nil, err
  402. }
  403. if !found {
  404. return nil, fmt.Errorf("property %s does not exist in metadata of secret %q", ref.Property, ref.Key)
  405. }
  406. return s, nil
  407. }
  408. s, found := getFromSecretData(secret, ref)
  409. if !found {
  410. return nil, fmt.Errorf("property %s does not exist in data of secret %q", ref.Property, ref.Key)
  411. }
  412. return s, nil
  413. }
  414. func getFromSecretData(secret *v1.Secret, ref esv1.ExternalSecretDataRemoteRef) ([]byte, bool) {
  415. // Check if a property with "." exists first such as file.png
  416. v, ok := secret.Data[ref.Property]
  417. if ok {
  418. return v, true
  419. }
  420. idx := strings.Index(ref.Property, ".")
  421. if idx == -1 || idx == 0 || idx == len(ref.Property)-1 {
  422. return nil, false
  423. }
  424. v, ok = secret.Data[ref.Property[:idx]]
  425. if !ok {
  426. return nil, false
  427. }
  428. val := gjson.Get(string(v), ref.Property[idx+1:])
  429. if !val.Exists() {
  430. return nil, false
  431. }
  432. return []byte(val.String()), true
  433. }
  434. func getFromSecretMetadata(secret *v1.Secret, ref esv1.ExternalSecretDataRemoteRef) ([]byte, bool, error) {
  435. path := strings.Split(ref.Property, ".")
  436. var metadata map[string]string
  437. switch path[0] {
  438. case metaLabels:
  439. metadata = secret.Labels
  440. case metaAnnotations:
  441. metadata = secret.Annotations
  442. default:
  443. return nil, false, nil
  444. }
  445. if len(path) == 1 {
  446. j, err := esutils.JSONMarshal(metadata)
  447. if err != nil {
  448. return nil, false, err
  449. }
  450. return j, true, nil
  451. }
  452. v, ok := metadata[path[1]]
  453. if !ok {
  454. return nil, false, nil
  455. }
  456. if len(path) == 2 {
  457. return []byte(v), true, nil
  458. }
  459. val := gjson.Get(v, strings.Join(path[2:], ""))
  460. if !val.Exists() {
  461. return nil, false, nil
  462. }
  463. return []byte(val.String()), true, nil
  464. }