secretsmanager.go 37 KB

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