client.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package kubernetes
  13. import (
  14. "bytes"
  15. "context"
  16. "encoding/base64"
  17. "encoding/json"
  18. "fmt"
  19. "strings"
  20. "github.com/tidwall/gjson"
  21. v1 "k8s.io/api/core/v1"
  22. apierrors "k8s.io/apimachinery/pkg/api/errors"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. labels "k8s.io/apimachinery/pkg/labels"
  25. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  26. "github.com/external-secrets/external-secrets/pkg/constants"
  27. "github.com/external-secrets/external-secrets/pkg/find"
  28. "github.com/external-secrets/external-secrets/pkg/metrics"
  29. "github.com/external-secrets/external-secrets/pkg/utils"
  30. )
  31. const (
  32. metaLabels = "labels"
  33. metaAnnotations = "annotations"
  34. )
  35. func (c *Client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  36. secret, err := c.userSecretClient.Get(ctx, ref.Key, metav1.GetOptions{})
  37. if err != nil {
  38. return nil, err
  39. }
  40. var values map[string][]byte
  41. if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
  42. values, err = getSecretMetadata(secret)
  43. if err != nil {
  44. return nil, err
  45. }
  46. } else {
  47. values = secret.Data
  48. }
  49. byteArr, err := getSecretValues(values, ref.MetadataPolicy)
  50. if err != nil {
  51. return nil, err
  52. }
  53. if ref.Property != "" {
  54. jsonStr := string(byteArr)
  55. // We need to search if a given key with a . exists before using gjson operations.
  56. idx := strings.Index(ref.Property, ".")
  57. if idx > -1 {
  58. refProperty := strings.ReplaceAll(ref.Property, ".", "\\.")
  59. val := gjson.Get(jsonStr, refProperty)
  60. if val.Exists() {
  61. return []byte(val.String()), nil
  62. }
  63. }
  64. val := gjson.Get(jsonStr, ref.Property)
  65. if !val.Exists() {
  66. return nil, fmt.Errorf("property %s does not exist in key %s", ref.Property, ref.Key)
  67. }
  68. return []byte(val.String()), nil
  69. }
  70. return byteArr, nil
  71. }
  72. func getSecretValues(secretMap map[string][]byte, policy esv1beta1.ExternalSecretMetadataPolicy) ([]byte, error) {
  73. var byteArr []byte
  74. var err error
  75. if policy == esv1beta1.ExternalSecretMetadataPolicyFetch {
  76. data := make(map[string]json.RawMessage, len(secretMap))
  77. for k, v := range secretMap {
  78. data[k] = v
  79. }
  80. byteArr, err = json.Marshal(data)
  81. } else {
  82. strMap := make(map[string]string)
  83. for k, v := range secretMap {
  84. strMap[k] = string(v)
  85. }
  86. byteArr, err = json.Marshal(strMap)
  87. }
  88. if err != nil {
  89. return nil, fmt.Errorf("unabled to marshal json: %w", err)
  90. }
  91. return byteArr, nil
  92. }
  93. func (c *Client) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushRemoteRef) error {
  94. if remoteRef.GetProperty() == "" {
  95. return fmt.Errorf("requires property in RemoteRef to delete secret value")
  96. }
  97. extSecret, getErr := c.userSecretClient.Get(ctx, remoteRef.GetRemoteKey(), metav1.GetOptions{})
  98. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesGetSecret, getErr)
  99. if getErr != nil {
  100. if apierrors.IsNotFound(getErr) {
  101. // return gracefully if no secret exists
  102. return nil
  103. }
  104. return getErr
  105. }
  106. if _, ok := extSecret.Data[remoteRef.GetProperty()]; !ok {
  107. // return gracefully if specified secret does not contain the given property
  108. return nil
  109. }
  110. if len(extSecret.Data) > 1 {
  111. return c.removeProperty(ctx, extSecret, remoteRef)
  112. }
  113. return c.fullDelete(ctx, remoteRef.GetRemoteKey())
  114. }
  115. func (c *Client) PushSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
  116. if remoteRef.GetProperty() == "" {
  117. return fmt.Errorf("requires property in RemoteRef to push secret value")
  118. }
  119. extSecret, getErr := c.userSecretClient.Get(ctx, remoteRef.GetRemoteKey(), metav1.GetOptions{})
  120. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesGetSecret, getErr)
  121. if getErr != nil {
  122. // create if it not exists
  123. if apierrors.IsNotFound(getErr) {
  124. return c.createSecret(ctx, value, remoteRef)
  125. }
  126. return getErr
  127. }
  128. // return gracefully if data is already in sync
  129. if v, ok := extSecret.Data[remoteRef.GetProperty()]; ok && bytes.Equal(v, value) {
  130. return nil
  131. }
  132. // otherwise update remote property
  133. return c.updateProperty(ctx, extSecret, remoteRef, value)
  134. }
  135. func (c *Client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  136. secret, err := c.userSecretClient.Get(ctx, ref.Key, metav1.GetOptions{})
  137. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesGetSecret, err)
  138. if apierrors.IsNotFound(err) {
  139. return nil, esv1beta1.NoSecretError{}
  140. }
  141. if err != nil {
  142. return nil, err
  143. }
  144. var tmpMap map[string][]byte
  145. if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
  146. tmpMap, err = getSecretMetadata(secret)
  147. if err != nil {
  148. return nil, err
  149. }
  150. } else {
  151. tmpMap = secret.Data
  152. }
  153. if ref.Property != "" {
  154. retMap, err := getPropertyMap(ref.Key, ref.Property, tmpMap)
  155. if err != nil {
  156. return nil, err
  157. }
  158. return retMap, nil
  159. }
  160. return tmpMap, nil
  161. }
  162. func getPropertyMap(key, property string, tmpMap map[string][]byte) (map[string][]byte, error) {
  163. byteArr, err := json.Marshal(tmpMap)
  164. if err != nil {
  165. return nil, err
  166. }
  167. var retMap map[string][]byte
  168. jsonStr := string(byteArr)
  169. // We need to search if a given key with a . exists before using gjson operations.
  170. idx := strings.Index(property, ".")
  171. if idx > -1 {
  172. refProperty := strings.ReplaceAll(property, ".", "\\.")
  173. retMap, err = getMapFromValues(refProperty, jsonStr)
  174. if err != nil {
  175. return nil, err
  176. }
  177. if retMap != nil {
  178. return retMap, nil
  179. }
  180. }
  181. retMap, err = getMapFromValues(property, jsonStr)
  182. if err != nil {
  183. return nil, err
  184. }
  185. if retMap == nil {
  186. return nil, fmt.Errorf("property %s does not exist in key %s", property, key)
  187. }
  188. return retMap, nil
  189. }
  190. func getMapFromValues(property, jsonStr string) (map[string][]byte, error) {
  191. val := gjson.Get(jsonStr, property)
  192. if val.Exists() {
  193. retMap := make(map[string][]byte)
  194. var tmpMap map[string]interface{}
  195. decoded, err := base64.StdEncoding.DecodeString(val.String())
  196. if err != nil {
  197. return nil, err
  198. }
  199. err = json.Unmarshal(decoded, &tmpMap)
  200. if err != nil {
  201. return nil, err
  202. }
  203. for k, v := range tmpMap {
  204. b, err := json.Marshal(v)
  205. if err != nil {
  206. return nil, err
  207. }
  208. retMap[k] = b
  209. }
  210. return retMap, nil
  211. }
  212. return nil, nil
  213. }
  214. func getSecretMetadata(secret *v1.Secret) (map[string][]byte, error) {
  215. var err error
  216. tmpMap := make(map[string][]byte)
  217. tmpMap[metaLabels], err = json.Marshal(secret.ObjectMeta.Labels)
  218. if err != nil {
  219. return nil, err
  220. }
  221. tmpMap[metaAnnotations], err = json.Marshal(secret.ObjectMeta.Annotations)
  222. if err != nil {
  223. return nil, err
  224. }
  225. return tmpMap, nil
  226. }
  227. func (c *Client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  228. if ref.Tags != nil {
  229. return c.findByTags(ctx, ref)
  230. }
  231. if ref.Name != nil {
  232. return c.findByName(ctx, ref)
  233. }
  234. return nil, fmt.Errorf("unexpected find operator: %#v", ref)
  235. }
  236. func (c *Client) findByTags(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  237. // empty/nil tags = everything
  238. sel, err := labels.ValidatedSelectorFromSet(ref.Tags)
  239. if err != nil {
  240. return nil, fmt.Errorf("unable to validate selector tags: %w", err)
  241. }
  242. secrets, err := c.userSecretClient.List(ctx, metav1.ListOptions{LabelSelector: sel.String()})
  243. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesListSecrets, err)
  244. if err != nil {
  245. return nil, fmt.Errorf("unable to list secrets: %w", err)
  246. }
  247. data := make(map[string][]byte)
  248. for _, secret := range secrets.Items {
  249. jsonStr, err := json.Marshal(convertMap(secret.Data))
  250. if err != nil {
  251. return nil, err
  252. }
  253. data[secret.Name] = jsonStr
  254. }
  255. return utils.ConvertKeys(ref.ConversionStrategy, data)
  256. }
  257. func (c *Client) findByName(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  258. secrets, err := c.userSecretClient.List(ctx, metav1.ListOptions{})
  259. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesListSecrets, err)
  260. if err != nil {
  261. return nil, fmt.Errorf("unable to list secrets: %w", err)
  262. }
  263. matcher, err := find.New(*ref.Name)
  264. if err != nil {
  265. return nil, err
  266. }
  267. data := make(map[string][]byte)
  268. for _, secret := range secrets.Items {
  269. if !matcher.MatchName(secret.Name) {
  270. continue
  271. }
  272. jsonStr, err := json.Marshal(convertMap(secret.Data))
  273. if err != nil {
  274. return nil, err
  275. }
  276. data[secret.Name] = jsonStr
  277. }
  278. return utils.ConvertKeys(ref.ConversionStrategy, data)
  279. }
  280. func (c Client) Close(_ context.Context) error {
  281. return nil
  282. }
  283. func convertMap(in map[string][]byte) map[string]string {
  284. out := make(map[string]string)
  285. for k, v := range in {
  286. out[k] = string(v)
  287. }
  288. return out
  289. }
  290. func (c *Client) createSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
  291. s := v1.Secret{
  292. ObjectMeta: metav1.ObjectMeta{
  293. Name: remoteRef.GetRemoteKey(),
  294. Namespace: c.store.RemoteNamespace,
  295. },
  296. Data: map[string][]byte{remoteRef.GetProperty(): value},
  297. Type: "Opaque",
  298. }
  299. _, err := c.userSecretClient.Create(ctx, &s, metav1.CreateOptions{})
  300. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesCreateSecret, err)
  301. return err
  302. }
  303. // fullDelete removes remote secret completely.
  304. func (c *Client) fullDelete(ctx context.Context, secretName string) error {
  305. err := c.userSecretClient.Delete(ctx, secretName, metav1.DeleteOptions{})
  306. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesDeleteSecret, err)
  307. // gracefully return on not found
  308. if apierrors.IsNotFound(err) {
  309. return nil
  310. }
  311. return err
  312. }
  313. // removeProperty removes single data property from remote secret.
  314. func (c *Client) removeProperty(ctx context.Context, extSecret *v1.Secret, remoteRef esv1beta1.PushRemoteRef) error {
  315. delete(extSecret.Data, remoteRef.GetProperty())
  316. _, err := c.userSecretClient.Update(ctx, extSecret, metav1.UpdateOptions{})
  317. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesUpdateSecret, err)
  318. return err
  319. }
  320. func (c *Client) updateProperty(ctx context.Context, extSecret *v1.Secret, remoteRef esv1beta1.PushRemoteRef, value []byte) error {
  321. // otherwise update remote secret
  322. extSecret.Data[remoteRef.GetProperty()] = value
  323. _, uErr := c.userSecretClient.Update(ctx, extSecret, metav1.UpdateOptions{})
  324. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesUpdateSecret, uErr)
  325. return uErr
  326. }