client.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  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. "unicode/utf8"
  21. "github.com/tidwall/gjson"
  22. v1 "k8s.io/api/core/v1"
  23. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  24. apierrors "k8s.io/apimachinery/pkg/api/errors"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. labels "k8s.io/apimachinery/pkg/labels"
  27. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  28. "github.com/external-secrets/external-secrets/pkg/constants"
  29. "github.com/external-secrets/external-secrets/pkg/find"
  30. "github.com/external-secrets/external-secrets/pkg/metrics"
  31. "github.com/external-secrets/external-secrets/pkg/utils"
  32. )
  33. const (
  34. metaLabels = "labels"
  35. metaAnnotations = "annotations"
  36. )
  37. func (c *Client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  38. secret, err := c.userSecretClient.Get(ctx, ref.Key, metav1.GetOptions{})
  39. if err != nil {
  40. return nil, err
  41. }
  42. serializedSecret, err := serializeSecret(secret, ref)
  43. if err != nil {
  44. return nil, err
  45. }
  46. // if property is not defined, we will return the json-serialized secret
  47. if ref.Property == "" {
  48. return serializedSecret, nil
  49. }
  50. jsonStr := string(serializedSecret)
  51. // We need to search if a given key with a . exists before using gjson operations.
  52. idx := strings.Index(ref.Property, ".")
  53. if idx > -1 {
  54. refProperty := strings.ReplaceAll(ref.Property, ".", "\\.")
  55. val := gjson.Get(jsonStr, refProperty)
  56. if val.Exists() {
  57. return []byte(val.Str), nil
  58. }
  59. }
  60. val := gjson.Get(jsonStr, ref.Property)
  61. if !val.Exists() {
  62. return nil, fmt.Errorf("property %s does not exist in key %s", ref.Property, ref.Key)
  63. }
  64. return []byte(val.String()), nil
  65. }
  66. // serializeSecret serializes a secret map[string][]byte into a flat []byte.
  67. func serializeSecret(secret *v1.Secret, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  68. // metadata is treated differently, because it
  69. // contains nested maps which can be queried with `ref.Property`
  70. if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
  71. values, err := getSecretMetadata(secret)
  72. if err != nil {
  73. return nil, err
  74. }
  75. data := make(map[string]json.RawMessage, len(values))
  76. for k, v := range values {
  77. data[k] = encodeBinaryData(v)
  78. }
  79. return jsonMarshal(data)
  80. }
  81. strMap := make(map[string]string)
  82. for k, v := range secret.Data {
  83. strMap[k] = string(encodeBinaryData(v))
  84. }
  85. return jsonMarshal(strMap)
  86. }
  87. // encode binary data encodes non UTF-8 data
  88. // as base64. This is needed to support proper json serialization.
  89. // if binary data would not be encoded, it would be utf-8 escaped: `\uffed`.
  90. func encodeBinaryData(input []byte) []byte {
  91. if utf8.Valid(input) {
  92. return input
  93. }
  94. return []byte(base64.StdEncoding.EncodeToString(input))
  95. }
  96. func jsonMarshal(t interface{}) ([]byte, error) {
  97. buffer := &bytes.Buffer{}
  98. encoder := json.NewEncoder(buffer)
  99. encoder.SetEscapeHTML(false)
  100. err := encoder.Encode(t)
  101. return bytes.TrimRight(buffer.Bytes(), "\n"), err
  102. }
  103. func (c *Client) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushRemoteRef) error {
  104. if remoteRef.GetProperty() == "" {
  105. return fmt.Errorf("requires property in RemoteRef to delete secret value")
  106. }
  107. extSecret, getErr := c.userSecretClient.Get(ctx, remoteRef.GetRemoteKey(), metav1.GetOptions{})
  108. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesGetSecret, getErr)
  109. if getErr != nil {
  110. if apierrors.IsNotFound(getErr) {
  111. // return gracefully if no secret exists
  112. return nil
  113. }
  114. return getErr
  115. }
  116. if _, ok := extSecret.Data[remoteRef.GetProperty()]; !ok {
  117. // return gracefully if specified secret does not contain the given property
  118. return nil
  119. }
  120. if len(extSecret.Data) > 1 {
  121. return c.removeProperty(ctx, extSecret, remoteRef)
  122. }
  123. return c.fullDelete(ctx, remoteRef.GetRemoteKey())
  124. }
  125. func (c *Client) PushSecret(ctx context.Context, value []byte, _ *apiextensionsv1.JSON, remoteRef esv1beta1.PushRemoteRef) error {
  126. if remoteRef.GetProperty() == "" {
  127. return fmt.Errorf("requires property in RemoteRef to push secret value")
  128. }
  129. extSecret, getErr := c.userSecretClient.Get(ctx, remoteRef.GetRemoteKey(), metav1.GetOptions{})
  130. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesGetSecret, getErr)
  131. if getErr != nil {
  132. // create if it not exists
  133. if apierrors.IsNotFound(getErr) {
  134. return c.createSecret(ctx, value, remoteRef)
  135. }
  136. return getErr
  137. }
  138. // return gracefully if data is already in sync
  139. if v, ok := extSecret.Data[remoteRef.GetProperty()]; ok && bytes.Equal(v, value) {
  140. return nil
  141. }
  142. // otherwise update remote property
  143. return c.updateProperty(ctx, extSecret, remoteRef, value)
  144. }
  145. func (c *Client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  146. secret, err := c.userSecretClient.Get(ctx, ref.Key, metav1.GetOptions{})
  147. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesGetSecret, err)
  148. if apierrors.IsNotFound(err) {
  149. return nil, esv1beta1.NoSecretError{}
  150. }
  151. if err != nil {
  152. return nil, err
  153. }
  154. var tmpMap map[string][]byte
  155. if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
  156. tmpMap, err = getSecretMetadata(secret)
  157. if err != nil {
  158. return nil, err
  159. }
  160. } else {
  161. tmpMap = secret.Data
  162. }
  163. if ref.Property != "" {
  164. retMap, err := getPropertyMap(ref.Key, ref.Property, tmpMap)
  165. if err != nil {
  166. return nil, err
  167. }
  168. return retMap, nil
  169. }
  170. return tmpMap, nil
  171. }
  172. func getPropertyMap(key, property string, tmpMap map[string][]byte) (map[string][]byte, error) {
  173. byteArr, err := jsonMarshal(tmpMap)
  174. if err != nil {
  175. return nil, err
  176. }
  177. var retMap map[string][]byte
  178. jsonStr := string(byteArr)
  179. // We need to search if a given key with a . exists before using gjson operations.
  180. idx := strings.Index(property, ".")
  181. if idx > -1 {
  182. refProperty := strings.ReplaceAll(property, ".", "\\.")
  183. retMap, err = getMapFromValues(refProperty, jsonStr)
  184. if err != nil {
  185. return nil, err
  186. }
  187. if retMap != nil {
  188. return retMap, nil
  189. }
  190. }
  191. retMap, err = getMapFromValues(property, jsonStr)
  192. if err != nil {
  193. return nil, err
  194. }
  195. if retMap == nil {
  196. return nil, fmt.Errorf("property %s does not exist in key %s", property, key)
  197. }
  198. return retMap, nil
  199. }
  200. func getMapFromValues(property, jsonStr string) (map[string][]byte, error) {
  201. val := gjson.Get(jsonStr, property)
  202. if val.Exists() {
  203. retMap := make(map[string][]byte)
  204. var tmpMap map[string]interface{}
  205. decoded, err := base64.StdEncoding.DecodeString(val.String())
  206. if err != nil {
  207. return nil, err
  208. }
  209. err = json.Unmarshal(decoded, &tmpMap)
  210. if err != nil {
  211. return nil, err
  212. }
  213. for k, v := range tmpMap {
  214. b, err := jsonMarshal(v)
  215. if err != nil {
  216. return nil, err
  217. }
  218. retMap[k] = b
  219. }
  220. return retMap, nil
  221. }
  222. return nil, nil
  223. }
  224. func getSecretMetadata(secret *v1.Secret) (map[string][]byte, error) {
  225. var err error
  226. tmpMap := make(map[string][]byte)
  227. tmpMap[metaLabels], err = jsonMarshal(secret.ObjectMeta.Labels)
  228. if err != nil {
  229. return nil, err
  230. }
  231. tmpMap[metaAnnotations], err = jsonMarshal(secret.ObjectMeta.Annotations)
  232. if err != nil {
  233. return nil, err
  234. }
  235. return tmpMap, nil
  236. }
  237. func (c *Client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  238. if ref.Tags != nil {
  239. return c.findByTags(ctx, ref)
  240. }
  241. if ref.Name != nil {
  242. return c.findByName(ctx, ref)
  243. }
  244. return nil, fmt.Errorf("unexpected find operator: %#v", ref)
  245. }
  246. func (c *Client) findByTags(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  247. // empty/nil tags = everything
  248. sel, err := labels.ValidatedSelectorFromSet(ref.Tags)
  249. if err != nil {
  250. return nil, fmt.Errorf("unable to validate selector tags: %w", err)
  251. }
  252. secrets, err := c.userSecretClient.List(ctx, metav1.ListOptions{LabelSelector: sel.String()})
  253. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesListSecrets, err)
  254. if err != nil {
  255. return nil, fmt.Errorf("unable to list secrets: %w", err)
  256. }
  257. data := make(map[string][]byte)
  258. for _, secret := range secrets.Items {
  259. jsonStr, err := jsonMarshal(convertMap(secret.Data))
  260. if err != nil {
  261. return nil, err
  262. }
  263. data[secret.Name] = jsonStr
  264. }
  265. return utils.ConvertKeys(ref.ConversionStrategy, data)
  266. }
  267. func (c *Client) findByName(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  268. secrets, err := c.userSecretClient.List(ctx, metav1.ListOptions{})
  269. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesListSecrets, err)
  270. if err != nil {
  271. return nil, fmt.Errorf("unable to list secrets: %w", err)
  272. }
  273. matcher, err := find.New(*ref.Name)
  274. if err != nil {
  275. return nil, err
  276. }
  277. data := make(map[string][]byte)
  278. for _, secret := range secrets.Items {
  279. if !matcher.MatchName(secret.Name) {
  280. continue
  281. }
  282. jsonStr, err := jsonMarshal(convertMap(secret.Data))
  283. if err != nil {
  284. return nil, err
  285. }
  286. data[secret.Name] = jsonStr
  287. }
  288. return utils.ConvertKeys(ref.ConversionStrategy, data)
  289. }
  290. func (c Client) Close(_ context.Context) error {
  291. return nil
  292. }
  293. func convertMap(in map[string][]byte) map[string]string {
  294. out := make(map[string]string)
  295. for k, v := range in {
  296. out[k] = string(v)
  297. }
  298. return out
  299. }
  300. func (c *Client) createSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
  301. s := v1.Secret{
  302. ObjectMeta: metav1.ObjectMeta{
  303. Name: remoteRef.GetRemoteKey(),
  304. Namespace: c.store.RemoteNamespace,
  305. },
  306. Data: map[string][]byte{remoteRef.GetProperty(): value},
  307. Type: "Opaque",
  308. }
  309. _, err := c.userSecretClient.Create(ctx, &s, metav1.CreateOptions{})
  310. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesCreateSecret, err)
  311. return err
  312. }
  313. // fullDelete removes remote secret completely.
  314. func (c *Client) fullDelete(ctx context.Context, secretName string) error {
  315. err := c.userSecretClient.Delete(ctx, secretName, metav1.DeleteOptions{})
  316. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesDeleteSecret, err)
  317. // gracefully return on not found
  318. if apierrors.IsNotFound(err) {
  319. return nil
  320. }
  321. return err
  322. }
  323. // removeProperty removes single data property from remote secret.
  324. func (c *Client) removeProperty(ctx context.Context, extSecret *v1.Secret, remoteRef esv1beta1.PushRemoteRef) error {
  325. delete(extSecret.Data, remoteRef.GetProperty())
  326. _, err := c.userSecretClient.Update(ctx, extSecret, metav1.UpdateOptions{})
  327. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesUpdateSecret, err)
  328. return err
  329. }
  330. func (c *Client) updateProperty(ctx context.Context, extSecret *v1.Secret, remoteRef esv1beta1.PushRemoteRef, value []byte) error {
  331. // otherwise update remote secret
  332. extSecret.Data[remoteRef.GetProperty()] = value
  333. _, uErr := c.userSecretClient.Update(ctx, extSecret, metav1.UpdateOptions{})
  334. metrics.ObserveAPICall(constants.ProviderKubernetes, constants.CallKubernetesUpdateSecret, uErr)
  335. return uErr
  336. }