parameterstore.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  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 parameterstore
  13. import (
  14. "context"
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "strings"
  19. "github.com/aws/aws-sdk-go-v2/aws"
  20. "github.com/aws/aws-sdk-go-v2/service/ssm"
  21. ssmTypes "github.com/aws/aws-sdk-go-v2/service/ssm/types"
  22. "github.com/aws/smithy-go"
  23. "github.com/tidwall/gjson"
  24. corev1 "k8s.io/api/core/v1"
  25. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  26. "k8s.io/utils/ptr"
  27. ctrl "sigs.k8s.io/controller-runtime"
  28. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  29. "github.com/external-secrets/external-secrets/pkg/constants"
  30. "github.com/external-secrets/external-secrets/pkg/find"
  31. "github.com/external-secrets/external-secrets/pkg/metrics"
  32. "github.com/external-secrets/external-secrets/pkg/provider/aws/util"
  33. "github.com/external-secrets/external-secrets/pkg/utils"
  34. "github.com/external-secrets/external-secrets/pkg/utils/metadata"
  35. )
  36. // Tier defines policy details for PushSecret.
  37. type Tier struct {
  38. Type ssmTypes.ParameterTier `json:"type"`
  39. Policies *apiextensionsv1.JSON `json:"policies"`
  40. }
  41. // PushSecretMetadataSpec defines the spec for the metadata for PushSecret.
  42. type PushSecretMetadataSpec struct {
  43. SecretType ssmTypes.ParameterType `json:"secretType,omitempty"`
  44. KMSKeyID string `json:"kmsKeyID,omitempty"`
  45. Tier Tier `json:"tier,omitempty"`
  46. EncodeAsDecoded bool `json:"encodeAsDecoded,omitempty"`
  47. Tags map[string]string `json:"tags,omitempty"`
  48. Description string `json:"description,omitempty"`
  49. }
  50. // https://github.com/external-secrets/external-secrets/issues/644
  51. var (
  52. _ esv1.SecretsClient = &ParameterStore{}
  53. managedBy = "managed-by"
  54. externalSecrets = "external-secrets"
  55. logger = ctrl.Log.WithName("provider").WithName("parameterstore")
  56. )
  57. // ParameterStore is a provider for AWS ParameterStore.
  58. type ParameterStore struct {
  59. cfg *aws.Config
  60. client PMInterface
  61. referentAuth bool
  62. prefix string
  63. }
  64. // PMInterface is a subset of the parameterstore api.
  65. // see: https://docs.aws.amazon.com/sdk-for-go/api/service/ssm/ssmiface/
  66. type PMInterface interface {
  67. GetParameter(ctx context.Context, input *ssm.GetParameterInput, opts ...func(*ssm.Options)) (*ssm.GetParameterOutput, error)
  68. GetParametersByPath(ctx context.Context, input *ssm.GetParametersByPathInput, opts ...func(*ssm.Options)) (*ssm.GetParametersByPathOutput, error)
  69. PutParameter(ctx context.Context, input *ssm.PutParameterInput, opts ...func(*ssm.Options)) (*ssm.PutParameterOutput, error)
  70. DescribeParameters(ctx context.Context, input *ssm.DescribeParametersInput, opts ...func(*ssm.Options)) (*ssm.DescribeParametersOutput, error)
  71. ListTagsForResource(ctx context.Context, input *ssm.ListTagsForResourceInput, opts ...func(*ssm.Options)) (*ssm.ListTagsForResourceOutput, error)
  72. RemoveTagsFromResource(ctx context.Context, params *ssm.RemoveTagsFromResourceInput, optFns ...func(*ssm.Options)) (*ssm.RemoveTagsFromResourceOutput, error)
  73. AddTagsToResource(ctx context.Context, params *ssm.AddTagsToResourceInput, optFns ...func(*ssm.Options)) (*ssm.AddTagsToResourceOutput, error)
  74. DeleteParameter(ctx context.Context, input *ssm.DeleteParameterInput, opts ...func(*ssm.Options)) (*ssm.DeleteParameterOutput, error)
  75. }
  76. const (
  77. errUnexpectedFindOperator = "unexpected find operator"
  78. errCodeAccessDeniedException = "AccessDeniedException"
  79. )
  80. // New constructs a ParameterStore Provider that is specific to a store.
  81. func New(_ context.Context, cfg *aws.Config, prefix string, referentAuth bool) (*ParameterStore, error) {
  82. return &ParameterStore{
  83. cfg: cfg,
  84. referentAuth: referentAuth,
  85. client: ssm.NewFromConfig(*cfg, func(o *ssm.Options) {
  86. o.EndpointResolverV2 = customEndpointResolver{}
  87. }),
  88. prefix: prefix,
  89. }, nil
  90. }
  91. func (pm *ParameterStore) getTagsByName(ctx context.Context, ref *ssm.GetParameterOutput) (map[string]string, error) {
  92. parameterType := "Parameter"
  93. parameterTags := ssm.ListTagsForResourceInput{
  94. ResourceId: ref.Parameter.Name,
  95. ResourceType: ssmTypes.ResourceTypeForTagging(parameterType),
  96. }
  97. data, err := pm.client.ListTagsForResource(ctx, &parameterTags)
  98. metrics.ObserveAPICall(constants.ProviderAWSPS, constants.CallAWSPSListTagsForResource, err)
  99. if err != nil {
  100. return nil, fmt.Errorf("error listing tags %w", err)
  101. }
  102. tags := map[string]string{}
  103. for _, tag := range data.TagList {
  104. tags[*tag.Key] = *tag.Value
  105. }
  106. return tags, nil
  107. }
  108. func (pm *ParameterStore) DeleteSecret(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) error {
  109. secretName := pm.prefix + remoteRef.GetRemoteKey()
  110. secretValue := ssm.GetParameterInput{
  111. Name: &secretName,
  112. }
  113. existing, err := pm.client.GetParameter(ctx, &secretValue)
  114. metrics.ObserveAPICall(constants.ProviderAWSPS, constants.CallAWSPSGetParameter, err)
  115. var parameterNotFoundErr *ssmTypes.ParameterNotFound
  116. ok := errors.As(err, &parameterNotFoundErr)
  117. if err != nil && !ok {
  118. return fmt.Errorf("unexpected error getting parameter %v: %w", secretName, err)
  119. }
  120. if existing != nil && existing.Parameter != nil {
  121. tags, err := pm.getTagsByName(ctx, existing)
  122. if err != nil {
  123. return fmt.Errorf("error getting the existing tags for the parameter %v: %w", secretName, err)
  124. }
  125. isManaged := isManagedByESO(tags)
  126. if !isManaged {
  127. // If the secret is not managed by external-secrets, it is "deleted" effectively by all means
  128. return nil
  129. }
  130. deleteInput := &ssm.DeleteParameterInput{
  131. Name: &secretName,
  132. }
  133. _, err = pm.client.DeleteParameter(ctx, deleteInput)
  134. metrics.ObserveAPICall(constants.ProviderAWSPS, constants.CallAWSPSDeleteParameter, err)
  135. if err != nil {
  136. return fmt.Errorf("could not delete parameter %v: %w", secretName, err)
  137. }
  138. }
  139. return nil
  140. }
  141. func (pm *ParameterStore) SecretExists(ctx context.Context, pushSecretRef esv1.PushSecretRemoteRef) (bool, error) {
  142. secretName := pm.prefix + pushSecretRef.GetRemoteKey()
  143. secretValue := ssm.GetParameterInput{
  144. Name: &secretName,
  145. }
  146. _, err := pm.client.GetParameter(ctx, &secretValue)
  147. var resourceNotFoundErr *ssmTypes.ResourceNotFoundException
  148. var parameterNotFoundErr *ssmTypes.ParameterNotFound
  149. if err != nil {
  150. if errors.As(err, &resourceNotFoundErr) {
  151. return false, nil
  152. }
  153. if errors.As(err, &parameterNotFoundErr) {
  154. return false, nil
  155. }
  156. return false, err
  157. }
  158. return true, nil
  159. }
  160. func (pm *ParameterStore) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1.PushSecretData) error {
  161. var (
  162. value []byte
  163. err error
  164. )
  165. meta, err := pm.constructMetadataWithDefaults(data.GetMetadata())
  166. if err != nil {
  167. return err
  168. }
  169. key := data.GetSecretKey()
  170. if key == "" {
  171. value, err = pm.encodeSecretData(meta.Spec.EncodeAsDecoded, secret.Data)
  172. if err != nil {
  173. return fmt.Errorf("failed to serialize secret content as JSON: %w", err)
  174. }
  175. } else {
  176. value = secret.Data[key]
  177. }
  178. tags := make([]ssmTypes.Tag, 0, len(meta.Spec.Tags))
  179. for k, v := range meta.Spec.Tags {
  180. tags = append(tags, ssmTypes.Tag{
  181. Key: ptr.To(k),
  182. Value: ptr.To(v),
  183. })
  184. }
  185. secretName := pm.prefix + data.GetRemoteKey()
  186. secretRequest := ssm.PutParameterInput{
  187. Name: ptr.To(pm.prefix + data.GetRemoteKey()),
  188. Value: ptr.To(string(value)),
  189. Type: meta.Spec.SecretType,
  190. Overwrite: ptr.To(true),
  191. Description: ptr.To(meta.Spec.Description),
  192. }
  193. if meta.Spec.SecretType == "SecureString" {
  194. secretRequest.KeyId = &meta.Spec.KMSKeyID
  195. }
  196. if meta.Spec.Tier.Type == ssmTypes.ParameterTierAdvanced {
  197. secretRequest.Tier = meta.Spec.Tier.Type
  198. if meta.Spec.Tier.Policies != nil {
  199. secretRequest.Policies = ptr.To(string(meta.Spec.Tier.Policies.Raw))
  200. }
  201. }
  202. secretValue := ssm.GetParameterInput{
  203. Name: &secretName,
  204. WithDecryption: aws.Bool(true),
  205. }
  206. existing, err := pm.client.GetParameter(ctx, &secretValue)
  207. metrics.ObserveAPICall(constants.ProviderAWSPS, constants.CallAWSPSGetParameter, err)
  208. var parameterNotFoundErr *ssmTypes.ParameterNotFound
  209. ok := errors.As(err, &parameterNotFoundErr)
  210. if err != nil && !ok {
  211. return fmt.Errorf("unexpected error getting parameter %v: %w", secretName, err)
  212. }
  213. // If we have a valid parameter returned to us, check its tags
  214. if existing != nil && existing.Parameter != nil {
  215. return pm.setExisting(ctx, existing, secretName, value, secretRequest, meta.Spec.Tags)
  216. }
  217. // let's set the secret
  218. // Do we need to delete the existing parameter on the remote?
  219. return pm.setManagedRemoteParameter(ctx, secretRequest, tags, true)
  220. }
  221. func (pm *ParameterStore) encodeSecretData(encodeAsDecoded bool, data map[string][]byte) ([]byte, error) {
  222. if encodeAsDecoded {
  223. // This will result in map byte slices not being base64 encoded by json.Marshal.
  224. return utils.JSONMarshal(convertMap(data))
  225. }
  226. return utils.JSONMarshal(data)
  227. }
  228. func convertMap(in map[string][]byte) map[string]string {
  229. m := make(map[string]string)
  230. for k, v := range in {
  231. m[k] = string(v)
  232. }
  233. return m
  234. }
  235. func (pm *ParameterStore) setExisting(ctx context.Context, existing *ssm.GetParameterOutput, secretName string, value []byte, secretRequest ssm.PutParameterInput, metaTags map[string]string) error {
  236. tags, err := pm.getTagsByName(ctx, existing)
  237. if err != nil {
  238. return fmt.Errorf("error getting the existing tags for the parameter %v: %w", secretName, err)
  239. }
  240. isManaged := isManagedByESO(tags)
  241. if !isManaged {
  242. return errors.New("secret not managed by external-secrets")
  243. }
  244. // When fetching a remote SecureString parameter without decrypting, the default value will always be 'sensitive'
  245. // in this case, no updates will be pushed remotely
  246. if existing.Parameter.Value != nil && *existing.Parameter.Value == "sensitive" {
  247. return errors.New("unable to compare 'sensitive' result, ensure to request a decrypted value")
  248. }
  249. if existing.Parameter.Value != nil && *existing.Parameter.Value == string(value) {
  250. return nil
  251. }
  252. err = pm.setManagedRemoteParameter(ctx, secretRequest, []ssmTypes.Tag{}, false)
  253. if err != nil {
  254. return err
  255. }
  256. tagKeysToRemove := findTagKeysToRemove(tags, metaTags)
  257. if len(tagKeysToRemove) > 0 {
  258. _, err = pm.client.RemoveTagsFromResource(ctx, &ssm.RemoveTagsFromResourceInput{
  259. ResourceId: existing.Parameter.Name,
  260. ResourceType: ssmTypes.ResourceTypeForTaggingParameter,
  261. TagKeys: tagKeysToRemove,
  262. })
  263. metrics.ObserveAPICall(constants.ProviderAWSPS, constants.CallAWSPSRemoveTagsParameter, err)
  264. if err != nil {
  265. return err
  266. }
  267. }
  268. tagsToUpdate, isModified := computeTagsToUpdate(tags, metaTags)
  269. if isModified {
  270. _, err = pm.client.AddTagsToResource(ctx, &ssm.AddTagsToResourceInput{
  271. ResourceId: existing.Parameter.Name,
  272. ResourceType: ssmTypes.ResourceTypeForTaggingParameter,
  273. Tags: tagsToUpdate,
  274. })
  275. metrics.ObserveAPICall(constants.ProviderAWSPS, constants.CallAWSPSAddTagsParameter, err)
  276. if err != nil {
  277. return err
  278. }
  279. }
  280. return nil
  281. }
  282. func isManagedByESO(tags map[string]string) bool {
  283. return tags[managedBy] == externalSecrets
  284. }
  285. func (pm *ParameterStore) setManagedRemoteParameter(ctx context.Context, secretRequest ssm.PutParameterInput, tags []ssmTypes.Tag, createManagedByTags bool) error {
  286. overwrite := true
  287. secretRequest.Overwrite = &overwrite
  288. if createManagedByTags {
  289. secretRequest.Tags = append(secretRequest.Tags, tags...)
  290. overwrite = false
  291. }
  292. _, err := pm.client.PutParameter(ctx, &secretRequest)
  293. metrics.ObserveAPICall(constants.ProviderAWSPS, constants.CallAWSPSPutParameter, err)
  294. if err != nil {
  295. return fmt.Errorf("unexpected error pushing parameter %v: %w", secretRequest.Name, err)
  296. }
  297. return nil
  298. }
  299. // GetAllSecrets fetches information from multiple secrets into a single kubernetes secret.
  300. func (pm *ParameterStore) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  301. if ref.Name != nil {
  302. return pm.findByName(ctx, ref)
  303. }
  304. if ref.Tags != nil {
  305. return pm.findByTags(ctx, ref)
  306. }
  307. return nil, errors.New(errUnexpectedFindOperator)
  308. }
  309. // findByName requires `ssm:GetParametersByPath` IAM permission, but the `Resource` scope can be limited.
  310. func (pm *ParameterStore) findByName(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  311. matcher, err := find.New(*ref.Name)
  312. if err != nil {
  313. return nil, err
  314. }
  315. if ref.Path == nil {
  316. ref.Path = aws.String("/")
  317. }
  318. data := make(map[string][]byte)
  319. var nextToken *string
  320. for {
  321. it, err := pm.client.GetParametersByPath(
  322. ctx,
  323. &ssm.GetParametersByPathInput{
  324. NextToken: nextToken,
  325. Path: ref.Path,
  326. Recursive: aws.Bool(true),
  327. WithDecryption: aws.Bool(true),
  328. })
  329. metrics.ObserveAPICall(constants.ProviderAWSPS, constants.CallAWSPSGetParametersByPath, err)
  330. if err != nil {
  331. var apiErr smithy.APIError
  332. if errors.As(err, &apiErr) && apiErr.ErrorCode() == errCodeAccessDeniedException {
  333. logger.Info("GetParametersByPath: access denied. using fallback to describe parameters. It is recommended to add ssm:GetParametersByPath permissions", "path", ref.Path)
  334. return pm.fallbackFindByName(ctx, ref)
  335. }
  336. return nil, fmt.Errorf("fetching parameters by path %s: %w", *ref.Path, err)
  337. }
  338. for _, param := range it.Parameters {
  339. if !matcher.MatchName(*param.Name) {
  340. continue
  341. }
  342. data[*param.Name] = []byte(*param.Value)
  343. }
  344. nextToken = it.NextToken
  345. if nextToken == nil {
  346. break
  347. }
  348. }
  349. return data, nil
  350. }
  351. // fallbackFindByName requires `ssm:DescribeParameters` IAM permission on `"Resource": "*"`.
  352. func (pm *ParameterStore) fallbackFindByName(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  353. matcher, err := find.New(*ref.Name)
  354. if err != nil {
  355. return nil, err
  356. }
  357. pathFilter := make([]ssmTypes.ParameterStringFilter, 0)
  358. if ref.Path != nil {
  359. pathFilter = append(pathFilter, ssmTypes.ParameterStringFilter{
  360. Key: aws.String("Path"),
  361. Option: aws.String("Recursive"),
  362. Values: []string{*ref.Path},
  363. })
  364. }
  365. data := make(map[string][]byte)
  366. var nextToken *string
  367. for {
  368. it, err := pm.client.DescribeParameters(
  369. ctx,
  370. &ssm.DescribeParametersInput{
  371. NextToken: nextToken,
  372. ParameterFilters: pathFilter,
  373. })
  374. metrics.ObserveAPICall(constants.ProviderAWSPS, constants.CallAWSPSDescribeParameter, err)
  375. if err != nil {
  376. return nil, err
  377. }
  378. for _, param := range it.Parameters {
  379. if !matcher.MatchName(*param.Name) {
  380. continue
  381. }
  382. err = pm.fetchAndSet(ctx, data, *param.Name)
  383. if err != nil {
  384. return nil, err
  385. }
  386. }
  387. nextToken = it.NextToken
  388. if nextToken == nil {
  389. break
  390. }
  391. }
  392. return data, nil
  393. }
  394. // findByTags requires ssm:DescribeParameters,tag:GetResources IAM permission on `"Resource": "*"`.
  395. func (pm *ParameterStore) findByTags(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  396. filters := make([]ssmTypes.ParameterStringFilter, 0)
  397. for k, v := range ref.Tags {
  398. filters = append(filters, ssmTypes.ParameterStringFilter{
  399. Key: ptr.To(fmt.Sprintf("tag:%s", k)),
  400. Values: []string{v},
  401. Option: ptr.To("Equals"),
  402. })
  403. }
  404. if ref.Path != nil {
  405. filters = append(filters, ssmTypes.ParameterStringFilter{
  406. Key: aws.String("Path"),
  407. Option: aws.String("Recursive"),
  408. Values: []string{*ref.Path},
  409. })
  410. }
  411. data := make(map[string][]byte)
  412. var nextToken *string
  413. for {
  414. it, err := pm.client.DescribeParameters(
  415. ctx,
  416. &ssm.DescribeParametersInput{
  417. ParameterFilters: filters,
  418. NextToken: nextToken,
  419. })
  420. metrics.ObserveAPICall(constants.ProviderAWSPS, constants.CallAWSPSDescribeParameter, err)
  421. if err != nil {
  422. return nil, err
  423. }
  424. for _, param := range it.Parameters {
  425. err = pm.fetchAndSet(ctx, data, *param.Name)
  426. if err != nil {
  427. return nil, err
  428. }
  429. }
  430. nextToken = it.NextToken
  431. if nextToken == nil {
  432. break
  433. }
  434. }
  435. return data, nil
  436. }
  437. func (pm *ParameterStore) fetchAndSet(ctx context.Context, data map[string][]byte, name string) error {
  438. out, err := pm.client.GetParameter(ctx, &ssm.GetParameterInput{
  439. Name: ptr.To(name),
  440. WithDecryption: aws.Bool(true),
  441. })
  442. metrics.ObserveAPICall(constants.ProviderAWSPS, constants.CallAWSPSGetParameter, err)
  443. if err != nil {
  444. return util.SanitizeErr(err)
  445. }
  446. data[name] = []byte(*out.Parameter.Value)
  447. return nil
  448. }
  449. // GetSecret returns a single secret from the provider.
  450. func (pm *ParameterStore) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  451. var out *ssm.GetParameterOutput
  452. var err error
  453. if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
  454. out, err = pm.getParameterTags(ctx, ref)
  455. } else {
  456. out, err = pm.getParameterValue(ctx, ref)
  457. }
  458. metrics.ObserveAPICall(constants.ProviderAWSPS, constants.CallAWSPSGetParameter, err)
  459. nsf := esv1.NoSecretError{}
  460. var nf *ssmTypes.ParameterNotFound
  461. if errors.As(err, &nf) || errors.As(err, &nsf) {
  462. return nil, esv1.NoSecretErr
  463. }
  464. if err != nil {
  465. return nil, util.SanitizeErr(err)
  466. }
  467. if ref.Property == "" {
  468. if out.Parameter.Value != nil {
  469. return []byte(*out.Parameter.Value), nil
  470. }
  471. return nil, fmt.Errorf("invalid secret received. parameter value is nil for key: %s", ref.Key)
  472. }
  473. idx := strings.Index(ref.Property, ".")
  474. if idx > -1 {
  475. refProperty := strings.ReplaceAll(ref.Property, ".", "\\.")
  476. val := gjson.Get(*out.Parameter.Value, refProperty)
  477. if val.Exists() {
  478. return []byte(val.String()), nil
  479. }
  480. }
  481. val := gjson.Get(*out.Parameter.Value, ref.Property)
  482. if !val.Exists() {
  483. return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
  484. }
  485. return []byte(val.String()), nil
  486. }
  487. func (pm *ParameterStore) getParameterTags(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (*ssm.GetParameterOutput, error) {
  488. param := ssm.GetParameterOutput{
  489. Parameter: &ssmTypes.Parameter{
  490. Name: pm.parameterNameWithVersion(ref),
  491. },
  492. }
  493. tags, err := pm.getTagsByName(ctx, &param)
  494. if err != nil {
  495. return nil, err
  496. }
  497. json, err := util.ParameterTagsToJSONString(tags)
  498. if err != nil {
  499. return nil, err
  500. }
  501. out := &ssm.GetParameterOutput{
  502. Parameter: &ssmTypes.Parameter{
  503. Value: &json,
  504. },
  505. }
  506. return out, nil
  507. }
  508. func (pm *ParameterStore) getParameterValue(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (*ssm.GetParameterOutput, error) {
  509. out, err := pm.client.GetParameter(ctx, &ssm.GetParameterInput{
  510. Name: pm.parameterNameWithVersion(ref),
  511. WithDecryption: aws.Bool(true),
  512. })
  513. return out, err
  514. }
  515. // GetSecretMap returns multiple k/v pairs from the provider.
  516. func (pm *ParameterStore) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  517. data, err := pm.GetSecret(ctx, ref)
  518. if err != nil {
  519. return nil, err
  520. }
  521. kv := make(map[string]json.RawMessage)
  522. err = json.Unmarshal(data, &kv)
  523. if err != nil {
  524. return nil, fmt.Errorf("unable to unmarshal secret %s: %w", ref.Key, err)
  525. }
  526. secretData := make(map[string][]byte)
  527. for k, v := range kv {
  528. var strVal string
  529. err = json.Unmarshal(v, &strVal)
  530. if err == nil {
  531. secretData[k] = []byte(strVal)
  532. } else {
  533. secretData[k] = v
  534. }
  535. }
  536. return secretData, nil
  537. }
  538. func (pm *ParameterStore) parameterNameWithVersion(ref esv1.ExternalSecretDataRemoteRef) *string {
  539. name := pm.prefix + ref.Key
  540. if ref.Version != "" {
  541. // see docs: https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-versions.html#reference-parameter-version
  542. name += ":" + ref.Version
  543. }
  544. return &name
  545. }
  546. func (pm *ParameterStore) Close(_ context.Context) error {
  547. return nil
  548. }
  549. func (pm *ParameterStore) Validate() (esv1.ValidationResult, error) {
  550. // skip validation stack because it depends on the namespace
  551. // of the ExternalSecret
  552. if pm.referentAuth {
  553. return esv1.ValidationResultUnknown, nil
  554. }
  555. _, err := pm.cfg.Credentials.Retrieve(context.Background())
  556. if err != nil {
  557. return esv1.ValidationResultError, err
  558. }
  559. return esv1.ValidationResultReady, nil
  560. }
  561. func (pm *ParameterStore) constructMetadataWithDefaults(data *apiextensionsv1.JSON) (*metadata.PushSecretMetadata[PushSecretMetadataSpec], error) {
  562. var (
  563. meta *metadata.PushSecretMetadata[PushSecretMetadataSpec]
  564. err error
  565. )
  566. meta, err = metadata.ParseMetadataParameters[PushSecretMetadataSpec](data)
  567. if err != nil {
  568. return nil, fmt.Errorf("failed to parse metadata: %w", err)
  569. }
  570. if meta == nil {
  571. meta = &metadata.PushSecretMetadata[PushSecretMetadataSpec]{}
  572. }
  573. if meta.Spec.Description == "" {
  574. meta.Spec.Description = fmt.Sprintf("secret '%s:%s'", managedBy, externalSecrets)
  575. }
  576. if meta.Spec.Tier.Type == "" {
  577. meta.Spec.Tier.Type = "Standard"
  578. }
  579. if meta.Spec.SecretType == "" {
  580. meta.Spec.SecretType = "String"
  581. }
  582. if meta.Spec.KMSKeyID == "" {
  583. meta.Spec.KMSKeyID = "alias/aws/ssm"
  584. }
  585. if len(meta.Spec.Tags) > 0 {
  586. if _, exists := meta.Spec.Tags[managedBy]; exists {
  587. return nil, fmt.Errorf("error parsing tags in metadata: Cannot specify a '%s' tag", managedBy)
  588. }
  589. } else {
  590. meta.Spec.Tags = make(map[string]string)
  591. }
  592. // always add the managedBy tag
  593. meta.Spec.Tags[managedBy] = externalSecrets
  594. return meta, nil
  595. }
  596. // findTagKeysToRemove returns a slice of tag keys that exist in the current tags
  597. // but are not present in the desired metaTags. These keys should be removed to
  598. // synchronize the tags with the desired state.
  599. func findTagKeysToRemove(tags, metaTags map[string]string) []string {
  600. var diff []string
  601. for key, _ := range tags {
  602. if _, ok := metaTags[key]; !ok {
  603. diff = append(diff, key)
  604. }
  605. }
  606. return diff
  607. }
  608. // computeTagsToUpdate compares the current tags with the desired metaTags and returns a slice of ssmTypes.Tag
  609. // that should be set on the resource. It also returns a boolean indicating if any tag was added or modified.
  610. func computeTagsToUpdate(tags, metaTags map[string]string) ([]ssmTypes.Tag, bool) {
  611. result := make([]ssmTypes.Tag, 0, len(metaTags))
  612. modified := false
  613. for k, v := range metaTags {
  614. if _, exists := tags[k]; !exists || tags[k] != v {
  615. modified = true
  616. }
  617. result = append(result, ssmTypes.Tag{
  618. Key: ptr.To(k),
  619. Value: ptr.To(v),
  620. })
  621. }
  622. return result, modified
  623. }