client.go 15 KB

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