secretsmanager.go 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125
  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 secretsmanager
  14. import (
  15. "bytes"
  16. "context"
  17. "encoding/json"
  18. "errors"
  19. "fmt"
  20. "reflect"
  21. "slices"
  22. "strings"
  23. "github.com/aws/aws-sdk-go-v2/aws"
  24. awssm "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
  25. "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types"
  26. "github.com/aws/smithy-go"
  27. "github.com/google/uuid"
  28. "github.com/tidwall/gjson"
  29. "github.com/tidwall/sjson"
  30. corev1 "k8s.io/api/core/v1"
  31. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  32. ctrl "sigs.k8s.io/controller-runtime"
  33. "sigs.k8s.io/controller-runtime/pkg/client"
  34. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  35. awsutil "github.com/external-secrets/external-secrets/providers/v1/aws/util"
  36. "github.com/external-secrets/external-secrets/runtime/constants"
  37. "github.com/external-secrets/external-secrets/runtime/esutils"
  38. "github.com/external-secrets/external-secrets/runtime/esutils/metadata"
  39. "github.com/external-secrets/external-secrets/runtime/find"
  40. "github.com/external-secrets/external-secrets/runtime/metrics"
  41. )
  42. // PushSecretMetadataSpec contains metadata information for pushing secrets to AWS Secret Manager.
  43. type PushSecretMetadataSpec struct {
  44. Tags map[string]string `json:"tags,omitempty"`
  45. Description string `json:"description,omitempty"`
  46. SecretPushFormat string `json:"secretPushFormat,omitempty"`
  47. KMSKeyID string `json:"kmsKeyId,omitempty"`
  48. ResourcePolicy *ResourcePolicySpec `json:"resourcePolicy,omitempty"`
  49. // ReplicationLocations defines one or more user-managed replication
  50. // locations for the secret. This is useful for High Availability across
  51. // regions.
  52. ReplicationLocations []string `json:"replicationLocations,omitempty"`
  53. }
  54. // ResourcePolicySpec defines the resource policy configuration using PolicySourceRef for AWS Secrets Manager.
  55. type ResourcePolicySpec struct {
  56. BlockPublicPolicy *bool `json:"blockPublicPolicy,omitempty"`
  57. PolicySourceRef *PolicySourceRef `json:"policySourceRef,omitempty"`
  58. }
  59. // PolicySourceRef defines the source reference for the resource policy.
  60. type PolicySourceRef struct {
  61. Kind string `json:"kind"`
  62. Name string `json:"name"`
  63. Key string `json:"key"`
  64. }
  65. // Declares metadata information for pushing secrets to AWS Secret Store.
  66. const (
  67. SecretPushFormatKey = "secretPushFormat"
  68. SecretPushFormatString = "string"
  69. SecretPushFormatBinary = "binary"
  70. ResourceNotFoundException = "ResourceNotFoundException"
  71. )
  72. // https://github.com/external-secrets/external-secrets/issues/644
  73. var _ esv1.SecretsClient = &SecretsManager{}
  74. // SecretsManager is a provider for AWS SecretsManager.
  75. type SecretsManager struct {
  76. cfg *aws.Config
  77. client SMInterface // Keep the interface
  78. referentAuth bool
  79. cache map[string]*awssm.GetSecretValueOutput
  80. config *esv1.SecretsManager
  81. prefix string
  82. newUUID func() string
  83. kube client.Client
  84. namespace string
  85. }
  86. // SMInterface is a subset of the smiface api.
  87. // see: https://docs.aws.amazon.com/sdk-for-go/api/service/secretsmanager/secretsmanageriface/
  88. type SMInterface interface {
  89. BatchGetSecretValue(ctx context.Context, params *awssm.BatchGetSecretValueInput, optFuncs ...func(*awssm.Options)) (*awssm.BatchGetSecretValueOutput, error)
  90. ListSecrets(ctx context.Context, params *awssm.ListSecretsInput, optFuncs ...func(*awssm.Options)) (*awssm.ListSecretsOutput, error)
  91. GetSecretValue(ctx context.Context, params *awssm.GetSecretValueInput, optFuncs ...func(*awssm.Options)) (*awssm.GetSecretValueOutput, error)
  92. CreateSecret(ctx context.Context, params *awssm.CreateSecretInput, optFuncs ...func(*awssm.Options)) (*awssm.CreateSecretOutput, error)
  93. PutSecretValue(ctx context.Context, params *awssm.PutSecretValueInput, optFuncs ...func(*awssm.Options)) (*awssm.PutSecretValueOutput, error)
  94. DescribeSecret(ctx context.Context, params *awssm.DescribeSecretInput, optFuncs ...func(*awssm.Options)) (*awssm.DescribeSecretOutput, error)
  95. DeleteSecret(ctx context.Context, params *awssm.DeleteSecretInput, optFuncs ...func(*awssm.Options)) (*awssm.DeleteSecretOutput, error)
  96. TagResource(ctx context.Context, params *awssm.TagResourceInput, optFuncs ...func(*awssm.Options)) (*awssm.TagResourceOutput, error)
  97. UntagResource(ctx context.Context, params *awssm.UntagResourceInput, optFuncs ...func(*awssm.Options)) (*awssm.UntagResourceOutput, error)
  98. PutResourcePolicy(ctx context.Context, params *awssm.PutResourcePolicyInput, optFuncs ...func(*awssm.Options)) (*awssm.PutResourcePolicyOutput, error)
  99. GetResourcePolicy(ctx context.Context, params *awssm.GetResourcePolicyInput, optFuncs ...func(*awssm.Options)) (*awssm.GetResourcePolicyOutput, error)
  100. DeleteResourcePolicy(ctx context.Context, params *awssm.DeleteResourcePolicyInput, optFuncs ...func(*awssm.Options)) (*awssm.DeleteResourcePolicyOutput, error)
  101. ReplicateSecretToRegions(ctx context.Context, params *awssm.ReplicateSecretToRegionsInput, optFuncs ...func(*awssm.Options)) (*awssm.ReplicateSecretToRegionsOutput, error)
  102. RemoveRegionsFromReplication(ctx context.Context, params *awssm.RemoveRegionsFromReplicationInput, optFuncs ...func(*awssm.Options)) (*awssm.RemoveRegionsFromReplicationOutput, error)
  103. }
  104. const (
  105. errUnexpectedFindOperator = "unexpected find operator"
  106. managedBy = "managed-by"
  107. externalSecrets = "external-secrets"
  108. initialVersion = "00000000-0000-0000-0000-000000000001"
  109. )
  110. var log = ctrl.Log.WithName("provider").WithName("aws").WithName("secretsmanager")
  111. // New creates a new SecretsManager client.
  112. func New(_ context.Context, cfg *aws.Config, secretsManagerCfg *esv1.SecretsManager, prefix string, referentAuth bool, kube client.Client, namespace string) (*SecretsManager, error) {
  113. return &SecretsManager{
  114. cfg: cfg,
  115. client: awssm.NewFromConfig(*cfg, func(o *awssm.Options) {
  116. o.EndpointResolverV2 = customEndpointResolver{}
  117. }),
  118. referentAuth: referentAuth,
  119. cache: make(map[string]*awssm.GetSecretValueOutput),
  120. config: secretsManagerCfg,
  121. prefix: prefix,
  122. kube: kube,
  123. namespace: namespace,
  124. }, nil
  125. }
  126. func (sm *SecretsManager) fetch(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (*awssm.GetSecretValueOutput, error) {
  127. ver := "AWSCURRENT"
  128. valueFrom := "SECRET"
  129. if ref.Version != "" {
  130. ver = ref.Version
  131. }
  132. if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
  133. valueFrom = "TAG"
  134. }
  135. key := sm.prefix + ref.Key
  136. log.Info("fetching secret value", "key", key, "version", ver, "value", valueFrom)
  137. cacheKey := fmt.Sprintf("%s#%s#%s", key, ver, valueFrom)
  138. if secretOut, found := sm.cache[cacheKey]; found {
  139. log.Info("found secret in cache", "key", key, "version", ver)
  140. return secretOut, nil
  141. }
  142. secretOut, err := sm.constructSecretValue(ctx, key, ver, ref.MetadataPolicy)
  143. if err != nil {
  144. return nil, err
  145. }
  146. sm.cache[cacheKey] = secretOut
  147. return secretOut, nil
  148. }
  149. // DeleteSecret deletes a secret from AWS Secrets Manager.
  150. func (sm *SecretsManager) DeleteSecret(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) error {
  151. secretName := sm.prefix + remoteRef.GetRemoteKey()
  152. secretValue := awssm.GetSecretValueInput{
  153. SecretId: &secretName,
  154. }
  155. secretInput := awssm.DescribeSecretInput{
  156. SecretId: &secretName,
  157. }
  158. awsSecret, err := sm.client.GetSecretValue(ctx, &secretValue)
  159. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMGetSecretValue, err)
  160. var aerr smithy.APIError
  161. if err != nil {
  162. if ok := errors.As(err, &aerr); !ok {
  163. return err
  164. }
  165. if aerr.ErrorCode() == ResourceNotFoundException {
  166. return nil
  167. }
  168. return err
  169. }
  170. data, err := sm.client.DescribeSecret(ctx, &secretInput)
  171. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMDescribeSecret, err)
  172. if err != nil {
  173. return err
  174. }
  175. if !isManagedByESO(data) {
  176. return nil
  177. }
  178. if len(data.ReplicationStatus) > 0 {
  179. regions := make([]string, 0, len(data.ReplicationStatus))
  180. for _, replicationStatus := range data.ReplicationStatus {
  181. regions = append(regions, aws.ToString(replicationStatus.Region))
  182. }
  183. if err := sm.removeRegionsFromReplication(ctx, aws.String(secretName), regions); err != nil {
  184. return err
  185. }
  186. }
  187. deleteInput := &awssm.DeleteSecretInput{
  188. SecretId: awsSecret.ARN,
  189. }
  190. if sm.config != nil && sm.config.ForceDeleteWithoutRecovery {
  191. deleteInput.ForceDeleteWithoutRecovery = &sm.config.ForceDeleteWithoutRecovery
  192. }
  193. if sm.config != nil && sm.config.RecoveryWindowInDays > 0 {
  194. deleteInput.RecoveryWindowInDays = &sm.config.RecoveryWindowInDays
  195. }
  196. err = awsutil.ValidateDeleteSecretInput(*deleteInput)
  197. if err != nil {
  198. return err
  199. }
  200. _, err = sm.client.DeleteSecret(ctx, deleteInput)
  201. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMDeleteSecret, err)
  202. return err
  203. }
  204. // SecretExists checks if a secret exists in AWS Secrets Manager.
  205. func (sm *SecretsManager) SecretExists(ctx context.Context, pushSecretRef esv1.PushSecretRemoteRef) (bool, error) {
  206. secretName := sm.prefix + pushSecretRef.GetRemoteKey()
  207. secretValue := awssm.GetSecretValueInput{
  208. SecretId: &secretName,
  209. }
  210. _, err := sm.client.GetSecretValue(ctx, &secretValue)
  211. if err != nil {
  212. return sm.handleSecretError(err)
  213. }
  214. return true, nil
  215. }
  216. func (sm *SecretsManager) handleSecretError(err error) (bool, error) {
  217. var aerr smithy.APIError
  218. if ok := errors.As(err, &aerr); !ok {
  219. return false, err
  220. }
  221. if aerr.ErrorCode() == ResourceNotFoundException {
  222. return false, nil
  223. }
  224. return false, err
  225. }
  226. // PushSecret pushes a secret to AWS Secrets Manager.
  227. func (sm *SecretsManager) PushSecret(ctx context.Context, secret *corev1.Secret, psd esv1.PushSecretData) error {
  228. value, err := esutils.ExtractSecretData(psd, secret)
  229. if err != nil {
  230. return fmt.Errorf("failed to extract secret data: %w", err)
  231. }
  232. secretName := sm.prefix + psd.GetRemoteKey()
  233. describeSecretInput := awssm.DescribeSecretInput{SecretId: &secretName}
  234. describeSecretOutput, err := sm.client.DescribeSecret(ctx, &describeSecretInput)
  235. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMDescribeSecret, err)
  236. var aerr smithy.APIError
  237. if err != nil {
  238. if ok := errors.As(err, &aerr); !ok {
  239. return err
  240. }
  241. if aerr.ErrorCode() == ResourceNotFoundException {
  242. finalValue, err := sm.getNewSecretValue(value, psd.GetProperty(), nil)
  243. if err != nil {
  244. return err
  245. }
  246. return sm.createSecretWithContext(ctx, secretName, psd, finalValue)
  247. }
  248. return err
  249. } else if !isManagedByESO(describeSecretOutput) {
  250. return errors.New("secret not managed by external-secrets")
  251. }
  252. if len(describeSecretOutput.VersionIdsToStages) == 0 {
  253. finalValue, err := sm.getNewSecretValue(value, psd.GetProperty(), nil)
  254. if err != nil {
  255. return err
  256. }
  257. return sm.putSecretValueWithContext(ctx, secretName, nil, psd, finalValue, describeSecretOutput)
  258. }
  259. getSecretValueInput := awssm.GetSecretValueInput{SecretId: &secretName}
  260. getSecretValueOutput, err := sm.client.GetSecretValue(ctx, &getSecretValueInput)
  261. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMGetSecretValue, err)
  262. if err != nil {
  263. return err
  264. }
  265. finalValue, err := sm.getNewSecretValue(value, psd.GetProperty(), getSecretValueOutput)
  266. if err != nil {
  267. return err
  268. }
  269. return sm.putSecretValueWithContext(ctx, secretName, getSecretValueOutput, psd, finalValue, describeSecretOutput)
  270. }
  271. func (sm *SecretsManager) getNewSecretValue(value []byte, property string, existingSecret *awssm.GetSecretValueOutput) ([]byte, error) {
  272. if property == "" {
  273. return value, nil
  274. }
  275. if existingSecret == nil {
  276. value, _ = sjson.SetBytes([]byte{}, property, value)
  277. return value, nil
  278. }
  279. currentSecret := sm.retrievePayload(existingSecret)
  280. if currentSecret != "" && !gjson.Valid(currentSecret) {
  281. return nil, errors.New("PushSecret for aws secrets manager with a pushSecretData property requires a json secret")
  282. }
  283. value, _ = sjson.SetBytes([]byte(currentSecret), property, value)
  284. return value, nil
  285. }
  286. func isManagedByESO(data *awssm.DescribeSecretOutput) bool {
  287. managedBy := managedBy
  288. externalSecrets := externalSecrets
  289. for _, tag := range data.Tags {
  290. if *tag.Key == managedBy && *tag.Value == externalSecrets {
  291. return true
  292. }
  293. }
  294. return false
  295. }
  296. // GetAllSecrets syncs multiple secrets from aws provider into a single Kubernetes Secret.
  297. func (sm *SecretsManager) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  298. hasName := ref.Name != nil
  299. hasTags := len(ref.Tags) > 0
  300. filters := make([]types.Filter, 0)
  301. switch {
  302. case !hasName && !hasTags:
  303. return nil, errors.New(errUnexpectedFindOperator)
  304. case hasName && !hasTags:
  305. return sm.findByName(ctx, ref, filters)
  306. case !hasName && hasTags:
  307. return sm.findByTags(ctx, ref)
  308. case hasName && hasTags:
  309. return sm.findByNameAndTags(ctx, ref, filters)
  310. default:
  311. return nil, errors.New(errUnexpectedFindOperator)
  312. }
  313. }
  314. func (sm *SecretsManager) findByNameAndTags(ctx context.Context, ref esv1.ExternalSecretFind, filters []types.Filter) (map[string][]byte, error) {
  315. for k, v := range ref.Tags {
  316. filters = append(filters, types.Filter{
  317. Key: types.FilterNameStringTypeTagKey,
  318. Values: []string{
  319. k,
  320. },
  321. }, types.Filter{
  322. Key: types.FilterNameStringTypeTagValue,
  323. Values: []string{
  324. v,
  325. },
  326. })
  327. }
  328. return sm.findByName(ctx, ref, filters)
  329. }
  330. func (sm *SecretsManager) findByName(ctx context.Context, ref esv1.ExternalSecretFind, filters []types.Filter) (map[string][]byte, error) {
  331. matcher, err := find.New(*ref.Name)
  332. if err != nil {
  333. return nil, err
  334. }
  335. if ref.Path != nil {
  336. filters = append(filters, types.Filter{
  337. Key: types.FilterNameStringTypeName,
  338. Values: []string{
  339. *ref.Path,
  340. },
  341. })
  342. return sm.fetchWithBatch(ctx, filters, matcher)
  343. }
  344. data := make(map[string][]byte)
  345. var nextToken *string
  346. for {
  347. // I put this into the for loop on purpose.
  348. log.V(0).Info("using ListSecret to fetch all secrets; this is a costly operations, please use batching by defining a _path_")
  349. it, err := sm.client.ListSecrets(ctx, &awssm.ListSecretsInput{
  350. Filters: filters,
  351. NextToken: nextToken,
  352. })
  353. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMListSecrets, err)
  354. if err != nil {
  355. return nil, err
  356. }
  357. log.V(1).Info("aws sm findByName found", "secrets", len(it.SecretList))
  358. for _, secret := range it.SecretList {
  359. if !matcher.MatchName(*secret.Name) {
  360. continue
  361. }
  362. log.V(1).Info("aws sm findByName matches", "name", *secret.Name)
  363. if err := sm.fetchAndSet(ctx, data, *secret.Name); err != nil {
  364. return nil, err
  365. }
  366. }
  367. nextToken = it.NextToken
  368. if nextToken == nil {
  369. break
  370. }
  371. }
  372. return data, nil
  373. }
  374. func (sm *SecretsManager) findByTags(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  375. filters := make([]types.Filter, 0)
  376. for k, v := range ref.Tags {
  377. filters = append(filters, types.Filter{
  378. Key: types.FilterNameStringTypeTagKey,
  379. Values: []string{
  380. k,
  381. },
  382. }, types.Filter{
  383. Key: types.FilterNameStringTypeTagValue,
  384. Values: []string{
  385. v,
  386. },
  387. })
  388. }
  389. if ref.Path != nil {
  390. filters = append(filters, types.Filter{
  391. Key: types.FilterNameStringTypeName,
  392. Values: []string{
  393. *ref.Path,
  394. },
  395. })
  396. }
  397. return sm.fetchWithBatch(ctx, filters, nil)
  398. }
  399. func (sm *SecretsManager) fetchAndSet(ctx context.Context, data map[string][]byte, name string) error {
  400. sec, err := sm.fetch(ctx, esv1.ExternalSecretDataRemoteRef{
  401. Key: name,
  402. })
  403. if err != nil {
  404. return err
  405. }
  406. if sec.SecretString != nil {
  407. data[name] = []byte(*sec.SecretString)
  408. }
  409. if sec.SecretBinary != nil {
  410. data[name] = sec.SecretBinary
  411. }
  412. return nil
  413. }
  414. // GetSecret returns a single secret from the provider.
  415. func (sm *SecretsManager) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  416. secretOut, err := sm.fetch(ctx, ref)
  417. if errors.Is(err, esv1.NoSecretErr) {
  418. return nil, err
  419. }
  420. if err != nil {
  421. return nil, awsutil.SanitizeErr(err)
  422. }
  423. if ref.Property == "" {
  424. if secretOut.SecretString != nil {
  425. return []byte(*secretOut.SecretString), nil
  426. }
  427. if secretOut.SecretBinary != nil {
  428. return secretOut.SecretBinary, nil
  429. }
  430. return nil, fmt.Errorf("invalid secret received. no secret string nor binary for key: %s", ref.Key)
  431. }
  432. val := sm.mapSecretToGjson(secretOut, ref.Property)
  433. if !val.Exists() {
  434. return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
  435. }
  436. return []byte(val.String()), nil
  437. }
  438. func (sm *SecretsManager) mapSecretToGjson(secretOut *awssm.GetSecretValueOutput, property string) gjson.Result {
  439. payload := sm.retrievePayload(secretOut)
  440. refProperty := sm.escapeDotsIfRequired(property, payload)
  441. val := gjson.Get(payload, refProperty)
  442. return val
  443. }
  444. func (sm *SecretsManager) retrievePayload(secretOut *awssm.GetSecretValueOutput) string {
  445. if secretOut == nil {
  446. return ""
  447. }
  448. var payload string
  449. if secretOut.SecretString != nil {
  450. payload = *secretOut.SecretString
  451. }
  452. if secretOut.SecretBinary != nil {
  453. payload = string(secretOut.SecretBinary)
  454. }
  455. return payload
  456. }
  457. func (sm *SecretsManager) escapeDotsIfRequired(currentRefProperty, payload string) string {
  458. // We need to search if a given key with a . exists before using gjson operations.
  459. found := strings.Contains(currentRefProperty, ".")
  460. refProperty := currentRefProperty
  461. if found {
  462. refProperty = strings.ReplaceAll(currentRefProperty, ".", "\\.")
  463. val := gjson.Get(payload, refProperty)
  464. if !val.Exists() {
  465. refProperty = currentRefProperty
  466. }
  467. }
  468. return refProperty
  469. }
  470. // GetSecretMap returns multiple k/v pairs from the provider.
  471. func (sm *SecretsManager) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  472. log.Info("fetching secret map", "key", ref.Key)
  473. data, err := sm.GetSecret(ctx, ref)
  474. if err != nil {
  475. return nil, err
  476. }
  477. kv := make(map[string]json.RawMessage)
  478. err = json.Unmarshal(data, &kv)
  479. if err != nil {
  480. return nil, fmt.Errorf("unable to unmarshal secret %s: %w", ref.Key, err)
  481. }
  482. secretData := make(map[string][]byte)
  483. for k, v := range kv {
  484. var strVal string
  485. err = json.Unmarshal(v, &strVal)
  486. if err == nil {
  487. secretData[k] = []byte(strVal)
  488. } else {
  489. secretData[k] = v
  490. }
  491. }
  492. return secretData, nil
  493. }
  494. // Close closes the provider client connection.
  495. func (sm *SecretsManager) Close(_ context.Context) error {
  496. return nil
  497. }
  498. // Validate validates the provider configuration.
  499. func (sm *SecretsManager) Validate() (esv1.ValidationResult, error) {
  500. // skip validation stack because it depends on the namespace
  501. // of the ExternalSecret
  502. if sm.referentAuth {
  503. return esv1.ValidationResultUnknown, nil
  504. }
  505. _, err := sm.cfg.Credentials.Retrieve(context.Background())
  506. if err != nil {
  507. return esv1.ValidationResultError, awsutil.SanitizeErr(err)
  508. }
  509. return esv1.ValidationResultReady, nil
  510. }
  511. // Capabilities returns the provider's esv1.SecretStoreCapabilities.
  512. func (sm *SecretsManager) Capabilities() esv1.SecretStoreCapabilities {
  513. return esv1.SecretStoreReadWrite
  514. }
  515. func (sm *SecretsManager) createSecretWithContext(ctx context.Context, secretName string, psd esv1.PushSecretData, value []byte) error {
  516. mdata, err := sm.constructMetadataWithDefaults(psd.GetMetadata())
  517. if err != nil {
  518. return fmt.Errorf("failed to parse push secret metadata: %w", err)
  519. }
  520. tags := make([]types.Tag, 0)
  521. for k, v := range mdata.Spec.Tags {
  522. tags = append(tags, types.Tag{
  523. Key: new(k),
  524. Value: new(v),
  525. })
  526. }
  527. kmsKeyID := aws.String(mdata.Spec.KMSKeyID)
  528. input := &awssm.CreateSecretInput{
  529. Name: &secretName,
  530. SecretBinary: value,
  531. Tags: tags,
  532. Description: new(mdata.Spec.Description),
  533. ClientRequestToken: new(initialVersion),
  534. KmsKeyId: kmsKeyID,
  535. AddReplicaRegions: buildReplicationRegionType(mdata.Spec.ReplicationLocations, kmsKeyID),
  536. }
  537. if mdata.Spec.SecretPushFormat == SecretPushFormatString {
  538. input.SecretBinary = nil
  539. input.SecretString = aws.String(string(value))
  540. }
  541. createOutput, err := sm.client.CreateSecret(ctx, input)
  542. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMCreateSecret, err)
  543. if err != nil {
  544. return err
  545. }
  546. // Apply resource policy if specified
  547. if mdata.Spec.ResourcePolicy != nil && mdata.Spec.ResourcePolicy.PolicySourceRef != nil {
  548. policyJSON, err := sm.resolveResourcePolicy(ctx, mdata.Spec.ResourcePolicy.PolicySourceRef)
  549. if err != nil {
  550. return fmt.Errorf("failed to resolve resource policy: %w", err)
  551. }
  552. putPolicyInput := &awssm.PutResourcePolicyInput{
  553. SecretId: createOutput.ARN,
  554. ResourcePolicy: aws.String(policyJSON),
  555. }
  556. if mdata.Spec.ResourcePolicy.BlockPublicPolicy != nil {
  557. putPolicyInput.BlockPublicPolicy = mdata.Spec.ResourcePolicy.BlockPublicPolicy
  558. }
  559. _, err = sm.client.PutResourcePolicy(ctx, putPolicyInput)
  560. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMPutResourcePolicy, err)
  561. if err != nil {
  562. return fmt.Errorf("failed to put resource policy: %w", err)
  563. }
  564. }
  565. return nil
  566. }
  567. func (sm *SecretsManager) putSecretValueWithContext(
  568. ctx context.Context,
  569. secretArn string,
  570. awsSecret *awssm.GetSecretValueOutput,
  571. psd esv1.PushSecretData,
  572. value []byte,
  573. describeSecret *awssm.DescribeSecretOutput,
  574. ) error {
  575. currentTags := make(map[string]string, len(describeSecret.Tags))
  576. for _, tag := range describeSecret.Tags {
  577. currentTags[*tag.Key] = *tag.Value
  578. }
  579. if err := sm.patchTags(ctx, psd.GetMetadata(), &secretArn, currentTags); err != nil {
  580. return err
  581. }
  582. if err := sm.manageResourcePolicy(ctx, psd.GetMetadata(), &secretArn); err != nil {
  583. return err
  584. }
  585. if err := sm.manageRegionReplication(ctx, psd.GetMetadata(), &secretArn, describeSecret.KmsKeyId, describeSecret.ReplicationStatus); err != nil {
  586. return err
  587. }
  588. if awsSecret != nil && (bytes.Equal(awsSecret.SecretBinary, value) || esutils.CompareStringAndByteSlices(awsSecret.SecretString, value)) {
  589. return nil
  590. }
  591. newVersionNumber := initialVersion
  592. if awsSecret != nil {
  593. if sm.newUUID == nil {
  594. newVersionNumber = uuid.NewString()
  595. } else {
  596. newVersionNumber = sm.newUUID()
  597. }
  598. }
  599. input := &awssm.PutSecretValueInput{
  600. SecretId: &secretArn,
  601. SecretBinary: value,
  602. ClientRequestToken: aws.String(newVersionNumber),
  603. }
  604. secretPushFormat, err := esutils.FetchValueFromMetadata(SecretPushFormatKey, psd.GetMetadata(), SecretPushFormatBinary)
  605. if err != nil {
  606. return fmt.Errorf("failed to parse metadata: %w", err)
  607. }
  608. if secretPushFormat == SecretPushFormatString {
  609. input.SecretBinary = nil
  610. input.SecretString = aws.String(string(value))
  611. }
  612. _, err = sm.client.PutSecretValue(ctx, input)
  613. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMPutSecretValue, err)
  614. return err
  615. }
  616. func (sm *SecretsManager) patchTags(ctx context.Context, rawMetadata *apiextensionsv1.JSON, secretID *string, tags map[string]string) error {
  617. rawMeta, err := metadata.ParseMetadataParameters[PushSecretMetadataSpec](rawMetadata)
  618. if err != nil {
  619. return err
  620. }
  621. if rawMeta == nil || len(rawMeta.Spec.Tags) == 0 {
  622. return nil
  623. }
  624. meta, err := sm.constructMetadataWithDefaults(rawMetadata)
  625. if err != nil {
  626. return err
  627. }
  628. tagKeysToRemove := awsutil.FindTagKeysToRemove(tags, meta.Spec.Tags)
  629. if len(tagKeysToRemove) > 0 {
  630. _, err = sm.client.UntagResource(ctx, &awssm.UntagResourceInput{
  631. SecretId: secretID,
  632. TagKeys: tagKeysToRemove,
  633. })
  634. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMUntagResource, err)
  635. if err != nil {
  636. return err
  637. }
  638. }
  639. tagsToUpdate, isModified := computeTagsToUpdate(tags, meta.Spec.Tags)
  640. if isModified {
  641. _, err = sm.client.TagResource(ctx, &awssm.TagResourceInput{
  642. SecretId: secretID,
  643. Tags: tagsToUpdate,
  644. })
  645. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMTagResource, err)
  646. if err != nil {
  647. return err
  648. }
  649. }
  650. return nil
  651. }
  652. func (sm *SecretsManager) fetchWithBatch(ctx context.Context, filters []types.Filter, matcher *find.Matcher) (map[string][]byte, error) {
  653. data := make(map[string][]byte)
  654. var nextToken *string
  655. for {
  656. it, err := sm.client.BatchGetSecretValue(ctx, &awssm.BatchGetSecretValueInput{
  657. Filters: filters,
  658. NextToken: nextToken,
  659. })
  660. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMBatchGetSecretValue, err)
  661. if err != nil {
  662. return nil, err
  663. }
  664. log.V(1).Info("aws sm findByName found", "secrets", len(it.SecretValues))
  665. for _, secret := range it.SecretValues {
  666. if matcher != nil && !matcher.MatchName(*secret.Name) {
  667. continue
  668. }
  669. log.V(1).Info("aws sm findByName matches", "name", *secret.Name)
  670. sm.setSecretValues(&secret, data)
  671. }
  672. nextToken = it.NextToken
  673. if nextToken == nil {
  674. break
  675. }
  676. }
  677. return data, nil
  678. }
  679. func (sm *SecretsManager) setSecretValues(secret *types.SecretValueEntry, data map[string][]byte) {
  680. if secret.SecretString != nil {
  681. data[*secret.Name] = []byte(*secret.SecretString)
  682. }
  683. if secret.SecretBinary != nil {
  684. data[*secret.Name] = secret.SecretBinary
  685. }
  686. }
  687. func (sm *SecretsManager) constructSecretValue(ctx context.Context, key, ver string, metadataPolicy esv1.ExternalSecretMetadataPolicy) (*awssm.GetSecretValueOutput, error) {
  688. if metadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
  689. describeSecretInput := &awssm.DescribeSecretInput{
  690. SecretId: &key,
  691. }
  692. descOutput, err := sm.client.DescribeSecret(ctx, describeSecretInput)
  693. if err != nil {
  694. return nil, err
  695. }
  696. log.Info("found metadata secret", "key", key, "output", descOutput)
  697. jsonTags, err := awsutil.SecretTagsToJSONString(descOutput.Tags)
  698. if err != nil {
  699. return nil, err
  700. }
  701. return &awssm.GetSecretValueOutput{
  702. ARN: descOutput.ARN,
  703. CreatedDate: descOutput.CreatedDate,
  704. Name: descOutput.Name,
  705. SecretString: &jsonTags,
  706. VersionId: &ver,
  707. }, nil
  708. }
  709. var getSecretValueInput *awssm.GetSecretValueInput
  710. if after, ok := strings.CutPrefix(ver, "uuid/"); ok {
  711. versionID := after
  712. getSecretValueInput = &awssm.GetSecretValueInput{
  713. SecretId: &key,
  714. VersionId: &versionID,
  715. }
  716. } else {
  717. getSecretValueInput = &awssm.GetSecretValueInput{
  718. SecretId: &key,
  719. VersionStage: &ver,
  720. }
  721. }
  722. secretOut, err := sm.client.GetSecretValue(ctx, getSecretValueInput)
  723. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMGetSecretValue, err)
  724. var (
  725. nf *types.ResourceNotFoundException
  726. ie *types.InvalidParameterException
  727. )
  728. if errors.As(err, &nf) {
  729. return nil, esv1.NoSecretErr
  730. }
  731. if errors.As(err, &ie) && strings.Contains(ie.Error(), "was marked for deletion") {
  732. return nil, esv1.NoSecretErr
  733. }
  734. return secretOut, err
  735. }
  736. func (sm *SecretsManager) constructMetadataWithDefaults(data *apiextensionsv1.JSON) (*metadata.PushSecretMetadata[PushSecretMetadataSpec], error) {
  737. var (
  738. meta *metadata.PushSecretMetadata[PushSecretMetadataSpec]
  739. err error
  740. )
  741. meta, err = metadata.ParseMetadataParameters[PushSecretMetadataSpec](data)
  742. if err != nil {
  743. return nil, fmt.Errorf("failed to parse metadata: %w", err)
  744. }
  745. if meta == nil {
  746. meta = &metadata.PushSecretMetadata[PushSecretMetadataSpec]{}
  747. }
  748. if meta.Spec.SecretPushFormat == "" {
  749. meta.Spec.SecretPushFormat = SecretPushFormatBinary
  750. } else if !slices.Contains([]string{SecretPushFormatBinary, SecretPushFormatString}, meta.Spec.SecretPushFormat) {
  751. return nil, fmt.Errorf("invalid secret push format: %s", meta.Spec.SecretPushFormat)
  752. }
  753. if meta.Spec.Description == "" {
  754. meta.Spec.Description = fmt.Sprintf("secret '%s:%s'", managedBy, externalSecrets)
  755. }
  756. if meta.Spec.KMSKeyID == "" {
  757. meta.Spec.KMSKeyID = "alias/aws/secretsmanager"
  758. }
  759. if len(meta.Spec.Tags) > 0 {
  760. if _, exists := meta.Spec.Tags[managedBy]; exists {
  761. return nil, fmt.Errorf("error parsing tags in metadata: Cannot specify a '%s' tag", managedBy)
  762. }
  763. } else {
  764. meta.Spec.Tags = make(map[string]string)
  765. }
  766. meta.Spec.Tags[managedBy] = externalSecrets
  767. return meta, nil
  768. }
  769. // resolveResourcePolicy resolves the policy JSON from the PolicySourceRef.
  770. func (sm *SecretsManager) resolveResourcePolicy(ctx context.Context, policyRef *PolicySourceRef) (string, error) {
  771. if policyRef == nil {
  772. return "", errors.New("policySourceRef is nil")
  773. }
  774. switch policyRef.Kind {
  775. case "ConfigMap":
  776. cm := &corev1.ConfigMap{}
  777. if err := sm.kube.Get(ctx, client.ObjectKey{
  778. Namespace: sm.namespace,
  779. Name: policyRef.Name,
  780. }, cm); err != nil {
  781. return "", fmt.Errorf("failed to get ConfigMap %s/%s: %w", sm.namespace, policyRef.Name, err)
  782. }
  783. policy, ok := cm.Data[policyRef.Key]
  784. if !ok {
  785. return "", fmt.Errorf("key %s not found in ConfigMap %s/%s", policyRef.Key, sm.namespace, policyRef.Name)
  786. }
  787. return policy, nil
  788. case "Secret":
  789. secret := &corev1.Secret{}
  790. if err := sm.kube.Get(ctx, client.ObjectKey{
  791. Namespace: sm.namespace,
  792. Name: policyRef.Name,
  793. }, secret); err != nil {
  794. return "", fmt.Errorf("failed to get Secret %s/%s: %w", sm.namespace, policyRef.Name, err)
  795. }
  796. policyBytes, ok := secret.Data[policyRef.Key]
  797. if !ok {
  798. return "", fmt.Errorf("key %s not found in Secret %s/%s", policyRef.Key, sm.namespace, policyRef.Name)
  799. }
  800. return string(policyBytes), nil
  801. default:
  802. return "", fmt.Errorf("unsupported PolicySourceRef kind: %s (must be ConfigMap or Secret)", policyRef.Kind)
  803. }
  804. }
  805. // unmarshalPolicyJSON parses a JSON policy string into a map.
  806. // Returns nil map for empty input, allowing comparison with a populated policy.
  807. func unmarshalPolicyJSON(policy string) (map[string]any, error) {
  808. if policy == "" {
  809. return nil, nil
  810. }
  811. var m map[string]any
  812. if err := json.Unmarshal([]byte(policy), &m); err != nil {
  813. return nil, err
  814. }
  815. return m, nil
  816. }
  817. func (sm *SecretsManager) deleteResourcePolicy(ctx context.Context, secretID *string) error {
  818. deletePolicyInput := &awssm.DeleteResourcePolicyInput{
  819. SecretId: secretID,
  820. }
  821. _, err := sm.client.DeleteResourcePolicy(ctx, deletePolicyInput)
  822. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMDeleteResourcePolicy, err)
  823. var nf *types.ResourceNotFoundException
  824. if err != nil && !errors.As(err, &nf) {
  825. return fmt.Errorf("failed to delete resource policy: %w", err)
  826. }
  827. return nil
  828. }
  829. // manageResourcePolicy applies or removes the resource policy based on metadata.
  830. func (sm *SecretsManager) manageResourcePolicy(ctx context.Context, metadata *apiextensionsv1.JSON, secretID *string) error {
  831. meta, err := sm.constructMetadataWithDefaults(metadata)
  832. if err != nil {
  833. return err
  834. }
  835. // Delete policy if policyRef is nil and the policy exists.
  836. if meta.Spec.ResourcePolicy == nil {
  837. return sm.deleteResourcePolicy(ctx, secretID)
  838. }
  839. // Normal flow, is to create the policy.
  840. policyJSON, err := sm.resolveResourcePolicy(ctx, meta.Spec.ResourcePolicy.PolicySourceRef)
  841. if err != nil {
  842. return fmt.Errorf("failed to resolve resource policy: %w", err)
  843. }
  844. if policyJSON == "" {
  845. return sm.deleteResourcePolicy(ctx, secretID)
  846. }
  847. getCurrentPolicyInput := &awssm.GetResourcePolicyInput{
  848. SecretId: secretID,
  849. }
  850. currentPolicyOutput, err := sm.client.GetResourcePolicy(ctx, getCurrentPolicyInput)
  851. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMGetResourcePolicy, err)
  852. var nf *types.ResourceNotFoundException
  853. if err != nil && !errors.As(err, &nf) {
  854. return fmt.Errorf("failed to get current resource policy: %w", err)
  855. }
  856. currentPolicy := ""
  857. if currentPolicyOutput != nil && currentPolicyOutput.ResourcePolicy != nil {
  858. currentPolicy = *currentPolicyOutput.ResourcePolicy
  859. }
  860. currentPolicyMap, err := unmarshalPolicyJSON(currentPolicy)
  861. if err != nil {
  862. return fmt.Errorf("failed to unmarshal current resource policy: %w", err)
  863. }
  864. desiredPolicyMap, err := unmarshalPolicyJSON(policyJSON)
  865. if err != nil {
  866. return fmt.Errorf("failed to unmarshal desired resource policy: %w", err)
  867. }
  868. if reflect.DeepEqual(currentPolicyMap, desiredPolicyMap) {
  869. return nil
  870. }
  871. putPolicyInput := &awssm.PutResourcePolicyInput{
  872. SecretId: secretID,
  873. ResourcePolicy: aws.String(policyJSON),
  874. }
  875. if meta.Spec.ResourcePolicy.BlockPublicPolicy != nil {
  876. putPolicyInput.BlockPublicPolicy = meta.Spec.ResourcePolicy.BlockPublicPolicy
  877. }
  878. _, err = sm.client.PutResourcePolicy(ctx, putPolicyInput)
  879. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMPutResourcePolicy, err)
  880. if err != nil {
  881. return fmt.Errorf("failed to put resource policy: %w", err)
  882. }
  883. return nil
  884. }
  885. // computeTagsToUpdate compares the current tags with the desired metaTags and returns a slice of ssmTypes.Tag
  886. // that should be set on the resource. It also returns a boolean indicating if any tag was added or modified.
  887. func computeTagsToUpdate(tags, metaTags map[string]string) ([]types.Tag, bool) {
  888. result := make([]types.Tag, 0, len(metaTags))
  889. modified := false
  890. for k, v := range metaTags {
  891. if _, exists := tags[k]; !exists || tags[k] != v {
  892. if k != managedBy {
  893. modified = true
  894. }
  895. }
  896. result = append(result, types.Tag{
  897. Key: new(k),
  898. Value: new(v),
  899. })
  900. }
  901. return result, modified
  902. }
  903. // manageRegionReplication add or remove regions for secret replication based on
  904. // desired and live state.
  905. func (sm *SecretsManager) manageRegionReplication(ctx context.Context, metadata *apiextensionsv1.JSON, secretARN, kmsKeyID *string, existingReplicationStatusType []types.ReplicationStatusType) error {
  906. meta, err := sm.constructMetadataWithDefaults(metadata)
  907. if err != nil {
  908. return err
  909. }
  910. // NOTE: skip replication completely unless explicitly set in the desired state
  911. if meta.Spec.ReplicationLocations == nil {
  912. return nil
  913. }
  914. existingReplicationRegions := buildExistingReplicationRegionsSlice(existingReplicationStatusType)
  915. requiresRegionReplicationRemoval, regionsToBeRemovedFromReplication := sm.getReplicationRegionToBeRemoved(meta.Spec.ReplicationLocations, existingReplicationRegions)
  916. if requiresRegionReplicationRemoval {
  917. if err := sm.removeRegionsFromReplication(ctx, secretARN, regionsToBeRemovedFromReplication); err != nil {
  918. return err
  919. }
  920. }
  921. requiresNewRegionReplication, regionsToReplicate := sm.getReplicationRegionsToBeAdded(meta.Spec.ReplicationLocations, existingReplicationRegions)
  922. if requiresNewRegionReplication {
  923. if err := sm.replicateExistingSecretToRegions(ctx, secretARN, kmsKeyID, regionsToReplicate); err != nil {
  924. return err
  925. }
  926. }
  927. return nil
  928. }
  929. func (sm *SecretsManager) replicateExistingSecretToRegions(ctx context.Context, secretID, kmsKeyID *string, regionsToReplicate []string) error {
  930. replicateSecretToRegionsInput := &awssm.ReplicateSecretToRegionsInput{
  931. AddReplicaRegions: buildReplicationRegionType(regionsToReplicate, kmsKeyID),
  932. SecretId: secretID,
  933. }
  934. _, err := sm.client.ReplicateSecretToRegions(ctx, replicateSecretToRegionsInput)
  935. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMReplicateSecretToRegions, err)
  936. if err != nil {
  937. return fmt.Errorf("failed to replicate existing secret to regions: %w", err)
  938. }
  939. return nil
  940. }
  941. func (sm *SecretsManager) removeRegionsFromReplication(ctx context.Context, secretID *string, replicationRegionsToBeRemoved []string) error {
  942. removeRegionsFromReplicationInput := &awssm.RemoveRegionsFromReplicationInput{
  943. RemoveReplicaRegions: replicationRegionsToBeRemoved,
  944. SecretId: secretID,
  945. }
  946. _, err := sm.client.RemoveRegionsFromReplication(ctx, removeRegionsFromReplicationInput)
  947. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMRemoveRegionsFromReplication, err)
  948. if err != nil {
  949. return fmt.Errorf("failed to remove regions from secret replication: %w", err)
  950. }
  951. return nil
  952. }
  953. func (sm *SecretsManager) getReplicationRegionToBeRemoved(desiredReplicationRegions, existingReplicationRegions []string) (bool, []string) {
  954. regionsDifference := getDifferenceFromSlices(existingReplicationRegions, desiredReplicationRegions)
  955. return len(regionsDifference) > 0, regionsDifference
  956. }
  957. func (sm *SecretsManager) getReplicationRegionsToBeAdded(desiredReplicationRegions, existingReplicationRegions []string) (bool, []string) {
  958. regionsDifference := getDifferenceFromSlices(desiredReplicationRegions, existingReplicationRegions)
  959. return len(regionsDifference) > 0, regionsDifference
  960. }
  961. func buildExistingReplicationRegionsSlice(existingReplicationRegions []types.ReplicationStatusType) []string {
  962. replicationRegions := make([]string, 0, len(existingReplicationRegions))
  963. for _, replicationStatusType := range existingReplicationRegions {
  964. replicationRegions = append(replicationRegions, aws.ToString(replicationStatusType.Region))
  965. }
  966. return replicationRegions
  967. }
  968. func buildReplicationRegionType(regions []string, kmsKeyID *string) []types.ReplicaRegionType {
  969. if len(regions) == 0 {
  970. return nil
  971. }
  972. replicationRegionsType := make([]types.ReplicaRegionType, 0, len(regions))
  973. for _, region := range regions {
  974. replicationRegionType := types.ReplicaRegionType{
  975. Region: aws.String(region),
  976. KmsKeyId: kmsKeyID,
  977. }
  978. replicationRegionsType = append(replicationRegionsType, replicationRegionType)
  979. }
  980. return replicationRegionsType
  981. }
  982. func getDifferenceFromSlices[T comparable](source, other []T) []T {
  983. otherSet := make(map[T]struct{}, len(other))
  984. result := make([]T, 0, len(source))
  985. for _, key := range other {
  986. otherSet[key] = struct{}{}
  987. }
  988. for _, key := range source {
  989. if _, ok := otherSet[key]; !ok {
  990. result = append(result, key)
  991. }
  992. }
  993. return result
  994. }