secretsmanager.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  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 secretsmanager
  13. import (
  14. "bytes"
  15. "context"
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "math/big"
  20. "slices"
  21. "strings"
  22. "github.com/aws/aws-sdk-go-v2/aws"
  23. awssm "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
  24. "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types"
  25. "github.com/aws/smithy-go"
  26. "github.com/google/uuid"
  27. "github.com/tidwall/gjson"
  28. "github.com/tidwall/sjson"
  29. corev1 "k8s.io/api/core/v1"
  30. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  31. utilpointer "k8s.io/utils/ptr"
  32. ctrl "sigs.k8s.io/controller-runtime"
  33. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  34. "github.com/external-secrets/external-secrets/pkg/constants"
  35. "github.com/external-secrets/external-secrets/pkg/find"
  36. "github.com/external-secrets/external-secrets/pkg/metrics"
  37. "github.com/external-secrets/external-secrets/pkg/provider/aws/util"
  38. "github.com/external-secrets/external-secrets/pkg/utils"
  39. "github.com/external-secrets/external-secrets/pkg/utils/metadata"
  40. )
  41. type PushSecretMetadataSpec struct {
  42. Tags map[string]string `json:"tags,omitempty"`
  43. Description string `json:"description,omitempty"`
  44. SecretPushFormat string `json:"secretPushFormat,omitempty"`
  45. KMSKeyID string `json:"kmsKeyId,omitempty"`
  46. }
  47. // Declares metadata information for pushing secrets to AWS Secret Store.
  48. const (
  49. SecretPushFormatKey = "secretPushFormat"
  50. SecretPushFormatString = "string"
  51. SecretPushFormatBinary = "binary"
  52. ResourceNotFoundException = "ResourceNotFoundException"
  53. )
  54. // https://github.com/external-secrets/external-secrets/issues/644
  55. var _ esv1.SecretsClient = &SecretsManager{}
  56. // SecretsManager is a provider for AWS SecretsManager.
  57. type SecretsManager struct {
  58. cfg *aws.Config
  59. client SMInterface // Keep the interface
  60. referentAuth bool
  61. cache map[string]*awssm.GetSecretValueOutput
  62. config *esv1.SecretsManager
  63. prefix string
  64. }
  65. // SMInterface is a subset of the smiface api.
  66. // see: https://docs.aws.amazon.com/sdk-for-go/api/service/secretsmanager/secretsmanageriface/
  67. type SMInterface interface {
  68. BatchGetSecretValue(ctx context.Context, params *awssm.BatchGetSecretValueInput, optFuncs ...func(*awssm.Options)) (*awssm.BatchGetSecretValueOutput, error)
  69. ListSecrets(ctx context.Context, params *awssm.ListSecretsInput, optFuncs ...func(*awssm.Options)) (*awssm.ListSecretsOutput, error)
  70. GetSecretValue(ctx context.Context, params *awssm.GetSecretValueInput, optFuncs ...func(*awssm.Options)) (*awssm.GetSecretValueOutput, error)
  71. CreateSecret(ctx context.Context, params *awssm.CreateSecretInput, optFuncs ...func(*awssm.Options)) (*awssm.CreateSecretOutput, error)
  72. PutSecretValue(ctx context.Context, params *awssm.PutSecretValueInput, optFuncs ...func(*awssm.Options)) (*awssm.PutSecretValueOutput, error)
  73. DescribeSecret(ctx context.Context, params *awssm.DescribeSecretInput, optFuncs ...func(*awssm.Options)) (*awssm.DescribeSecretOutput, error)
  74. DeleteSecret(ctx context.Context, params *awssm.DeleteSecretInput, optFuncs ...func(*awssm.Options)) (*awssm.DeleteSecretOutput, error)
  75. }
  76. const (
  77. errUnexpectedFindOperator = "unexpected find operator"
  78. managedBy = "managed-by"
  79. externalSecrets = "external-secrets"
  80. initialVersion = "00000000-0000-0000-0000-000000000001"
  81. )
  82. var log = ctrl.Log.WithName("provider").WithName("aws").WithName("secretsmanager")
  83. // New creates a new SecretsManager client.
  84. func New(ctx context.Context, cfg *aws.Config, secretsManagerCfg *esv1.SecretsManager, prefix string, referentAuth bool) (*SecretsManager, error) {
  85. return &SecretsManager{
  86. cfg: cfg,
  87. client: awssm.NewFromConfig(*cfg, func(o *awssm.Options) {
  88. o.EndpointResolverV2 = customEndpointResolver{}
  89. }),
  90. referentAuth: referentAuth,
  91. cache: make(map[string]*awssm.GetSecretValueOutput),
  92. config: secretsManagerCfg,
  93. prefix: prefix,
  94. }, nil
  95. }
  96. func (sm *SecretsManager) fetch(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (*awssm.GetSecretValueOutput, error) {
  97. ver := "AWSCURRENT"
  98. valueFrom := "SECRET"
  99. if ref.Version != "" {
  100. ver = ref.Version
  101. }
  102. if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
  103. valueFrom = "TAG"
  104. }
  105. key := sm.prefix + ref.Key
  106. log.Info("fetching secret value", "key", key, "version", ver, "value", valueFrom)
  107. cacheKey := fmt.Sprintf("%s#%s#%s", key, ver, valueFrom)
  108. if secretOut, found := sm.cache[cacheKey]; found {
  109. log.Info("found secret in cache", "key", key, "version", ver)
  110. return secretOut, nil
  111. }
  112. secretOut, err := sm.constructSecretValue(ctx, key, ver, ref.MetadataPolicy)
  113. if err != nil {
  114. return nil, err
  115. }
  116. sm.cache[cacheKey] = secretOut
  117. return secretOut, nil
  118. }
  119. func (sm *SecretsManager) DeleteSecret(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) error {
  120. secretName := sm.prefix + remoteRef.GetRemoteKey()
  121. secretValue := awssm.GetSecretValueInput{
  122. SecretId: &secretName,
  123. }
  124. secretInput := awssm.DescribeSecretInput{
  125. SecretId: &secretName,
  126. }
  127. awsSecret, err := sm.client.GetSecretValue(ctx, &secretValue)
  128. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMGetSecretValue, err)
  129. var aerr smithy.APIError
  130. if err != nil {
  131. if ok := errors.As(err, &aerr); !ok {
  132. return err
  133. }
  134. if aerr.ErrorCode() == ResourceNotFoundException {
  135. return nil
  136. }
  137. return err
  138. }
  139. data, err := sm.client.DescribeSecret(ctx, &secretInput)
  140. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMDescribeSecret, err)
  141. if err != nil {
  142. return err
  143. }
  144. if !isManagedByESO(data) {
  145. return nil
  146. }
  147. deleteInput := &awssm.DeleteSecretInput{
  148. SecretId: awsSecret.ARN,
  149. }
  150. if sm.config != nil && sm.config.ForceDeleteWithoutRecovery {
  151. deleteInput.ForceDeleteWithoutRecovery = &sm.config.ForceDeleteWithoutRecovery
  152. }
  153. if sm.config != nil && sm.config.RecoveryWindowInDays > 0 {
  154. deleteInput.RecoveryWindowInDays = &sm.config.RecoveryWindowInDays
  155. }
  156. err = util.ValidateDeleteSecretInput(*deleteInput)
  157. if err != nil {
  158. return err
  159. }
  160. _, err = sm.client.DeleteSecret(ctx, deleteInput)
  161. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMDeleteSecret, err)
  162. return err
  163. }
  164. func (sm *SecretsManager) SecretExists(ctx context.Context, pushSecretRef esv1.PushSecretRemoteRef) (bool, error) {
  165. secretName := sm.prefix + pushSecretRef.GetRemoteKey()
  166. secretValue := awssm.GetSecretValueInput{
  167. SecretId: &secretName,
  168. }
  169. _, err := sm.client.GetSecretValue(ctx, &secretValue)
  170. if err != nil {
  171. return sm.handleSecretError(err)
  172. }
  173. return true, nil
  174. }
  175. func (sm *SecretsManager) handleSecretError(err error) (bool, error) {
  176. var aerr smithy.APIError
  177. if ok := errors.As(err, &aerr); !ok {
  178. return false, err
  179. }
  180. if aerr.ErrorCode() == ResourceNotFoundException {
  181. return false, nil
  182. }
  183. return false, err
  184. }
  185. func (sm *SecretsManager) PushSecret(ctx context.Context, secret *corev1.Secret, psd esv1.PushSecretData) error {
  186. value, err := utils.ExtractSecretData(psd, secret)
  187. if err != nil {
  188. return fmt.Errorf("failed to extract secret data: %w", err)
  189. }
  190. secretName := sm.prefix + psd.GetRemoteKey()
  191. secretValue := awssm.GetSecretValueInput{
  192. SecretId: &secretName,
  193. }
  194. secretInput := awssm.DescribeSecretInput{
  195. SecretId: &secretName,
  196. }
  197. awsSecret, err := sm.client.GetSecretValue(ctx, &secretValue)
  198. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMGetSecretValue, err)
  199. if psd.GetProperty() != "" {
  200. currentSecret := sm.retrievePayload(awsSecret)
  201. if currentSecret != "" && !gjson.Valid(currentSecret) {
  202. return errors.New("PushSecret for aws secrets manager with a pushSecretData property requires a json secret")
  203. }
  204. value, _ = sjson.SetBytes([]byte(currentSecret), psd.GetProperty(), value)
  205. }
  206. var aerr smithy.APIError
  207. if err != nil {
  208. if ok := errors.As(err, &aerr); !ok {
  209. return err
  210. }
  211. if aerr.ErrorCode() == ResourceNotFoundException {
  212. return sm.createSecretWithContext(ctx, secretName, psd, value)
  213. }
  214. return err
  215. }
  216. return sm.putSecretValueWithContext(ctx, secretInput, awsSecret, psd, value)
  217. }
  218. func padOrTrim(b []byte) []byte {
  219. l := len(b)
  220. size := 16
  221. if l == size {
  222. return b
  223. }
  224. if l > size {
  225. return b[l-size:]
  226. }
  227. tmp := make([]byte, size)
  228. copy(tmp[size-l:], b)
  229. return tmp
  230. }
  231. func bumpVersionNumber(id *string) (*string, error) {
  232. if id == nil {
  233. output := initialVersion
  234. return &output, nil
  235. }
  236. n := new(big.Int)
  237. oldVersion, ok := n.SetString(strings.ReplaceAll(*id, "-", ""), 16)
  238. if !ok {
  239. return nil, fmt.Errorf("expected secret version in AWS SSM to be a UUID but got '%s'", *id)
  240. }
  241. newVersionRaw := oldVersion.Add(oldVersion, big.NewInt(1)).Bytes()
  242. newVersion, err := uuid.FromBytes(padOrTrim(newVersionRaw))
  243. if err != nil {
  244. return nil, err
  245. }
  246. s := newVersion.String()
  247. return &s, nil
  248. }
  249. func isManagedByESO(data *awssm.DescribeSecretOutput) bool {
  250. managedBy := managedBy
  251. externalSecrets := externalSecrets
  252. for _, tag := range data.Tags {
  253. if *tag.Key == managedBy && *tag.Value == externalSecrets {
  254. return true
  255. }
  256. }
  257. return false
  258. }
  259. // GetAllSecrets syncs multiple secrets from aws provider into a single Kubernetes Secret.
  260. func (sm *SecretsManager) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  261. if ref.Name != nil {
  262. return sm.findByName(ctx, ref)
  263. }
  264. if len(ref.Tags) > 0 {
  265. return sm.findByTags(ctx, ref)
  266. }
  267. return nil, errors.New(errUnexpectedFindOperator)
  268. }
  269. func (sm *SecretsManager) findByName(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  270. matcher, err := find.New(*ref.Name)
  271. if err != nil {
  272. return nil, err
  273. }
  274. filters := make([]types.Filter, 0)
  275. if ref.Path != nil {
  276. filters = append(filters, types.Filter{
  277. Key: types.FilterNameStringTypeName,
  278. Values: []string{
  279. *ref.Path,
  280. },
  281. })
  282. return sm.fetchWithBatch(ctx, filters, matcher)
  283. }
  284. data := make(map[string][]byte)
  285. var nextToken *string
  286. for {
  287. // I put this into the for loop on purpose.
  288. log.V(0).Info("using ListSecret to fetch all secrets; this is a costly operations, please use batching by defining a _path_")
  289. it, err := sm.client.ListSecrets(ctx, &awssm.ListSecretsInput{
  290. Filters: filters,
  291. NextToken: nextToken,
  292. })
  293. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMListSecrets, err)
  294. if err != nil {
  295. return nil, err
  296. }
  297. log.V(1).Info("aws sm findByName found", "secrets", len(it.SecretList))
  298. for _, secret := range it.SecretList {
  299. if !matcher.MatchName(*secret.Name) {
  300. continue
  301. }
  302. log.V(1).Info("aws sm findByName matches", "name", *secret.Name)
  303. if err := sm.fetchAndSet(ctx, data, *secret.Name); err != nil {
  304. return nil, err
  305. }
  306. }
  307. nextToken = it.NextToken
  308. if nextToken == nil {
  309. break
  310. }
  311. }
  312. return data, nil
  313. }
  314. func (sm *SecretsManager) findByTags(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  315. filters := make([]types.Filter, 0)
  316. for k, v := range ref.Tags {
  317. filters = append(filters, types.Filter{
  318. Key: types.FilterNameStringTypeTagKey,
  319. Values: []string{
  320. k,
  321. },
  322. }, types.Filter{
  323. Key: types.FilterNameStringTypeTagValue,
  324. Values: []string{
  325. v,
  326. },
  327. })
  328. }
  329. if ref.Path != nil {
  330. filters = append(filters, types.Filter{
  331. Key: types.FilterNameStringTypeName,
  332. Values: []string{
  333. *ref.Path,
  334. },
  335. })
  336. }
  337. return sm.fetchWithBatch(ctx, filters, nil)
  338. }
  339. func (sm *SecretsManager) fetchAndSet(ctx context.Context, data map[string][]byte, name string) error {
  340. sec, err := sm.fetch(ctx, esv1.ExternalSecretDataRemoteRef{
  341. Key: name,
  342. })
  343. if err != nil {
  344. return err
  345. }
  346. if sec.SecretString != nil {
  347. data[name] = []byte(*sec.SecretString)
  348. }
  349. if sec.SecretBinary != nil {
  350. data[name] = sec.SecretBinary
  351. }
  352. return nil
  353. }
  354. // GetSecret returns a single secret from the provider.
  355. func (sm *SecretsManager) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  356. secretOut, err := sm.fetch(ctx, ref)
  357. if errors.Is(err, esv1.NoSecretErr) {
  358. return nil, err
  359. }
  360. if err != nil {
  361. return nil, util.SanitizeErr(err)
  362. }
  363. if ref.Property == "" {
  364. if secretOut.SecretString != nil {
  365. return []byte(*secretOut.SecretString), nil
  366. }
  367. if secretOut.SecretBinary != nil {
  368. return secretOut.SecretBinary, nil
  369. }
  370. return nil, fmt.Errorf("invalid secret received. no secret string nor binary for key: %s", ref.Key)
  371. }
  372. val := sm.mapSecretToGjson(secretOut, ref.Property)
  373. if !val.Exists() {
  374. return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
  375. }
  376. return []byte(val.String()), nil
  377. }
  378. func (sm *SecretsManager) mapSecretToGjson(secretOut *awssm.GetSecretValueOutput, property string) gjson.Result {
  379. payload := sm.retrievePayload(secretOut)
  380. refProperty := sm.escapeDotsIfRequired(property, payload)
  381. val := gjson.Get(payload, refProperty)
  382. return val
  383. }
  384. func (sm *SecretsManager) retrievePayload(secretOut *awssm.GetSecretValueOutput) string {
  385. if secretOut == nil {
  386. return ""
  387. }
  388. var payload string
  389. if secretOut.SecretString != nil {
  390. payload = *secretOut.SecretString
  391. }
  392. if secretOut.SecretBinary != nil {
  393. payload = string(secretOut.SecretBinary)
  394. }
  395. return payload
  396. }
  397. func (sm *SecretsManager) escapeDotsIfRequired(currentRefProperty, payload string) string {
  398. // We need to search if a given key with a . exists before using gjson operations.
  399. idx := strings.Index(currentRefProperty, ".")
  400. refProperty := currentRefProperty
  401. if idx > -1 {
  402. refProperty = strings.ReplaceAll(currentRefProperty, ".", "\\.")
  403. val := gjson.Get(payload, refProperty)
  404. if !val.Exists() {
  405. refProperty = currentRefProperty
  406. }
  407. }
  408. return refProperty
  409. }
  410. // GetSecretMap returns multiple k/v pairs from the provider.
  411. func (sm *SecretsManager) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  412. log.Info("fetching secret map", "key", ref.Key)
  413. data, err := sm.GetSecret(ctx, ref)
  414. if err != nil {
  415. return nil, err
  416. }
  417. kv := make(map[string]json.RawMessage)
  418. err = json.Unmarshal(data, &kv)
  419. if err != nil {
  420. return nil, fmt.Errorf("unable to unmarshal secret %s: %w", ref.Key, err)
  421. }
  422. secretData := make(map[string][]byte)
  423. for k, v := range kv {
  424. var strVal string
  425. err = json.Unmarshal(v, &strVal)
  426. if err == nil {
  427. secretData[k] = []byte(strVal)
  428. } else {
  429. secretData[k] = v
  430. }
  431. }
  432. return secretData, nil
  433. }
  434. func (sm *SecretsManager) Close(_ context.Context) error {
  435. return nil
  436. }
  437. func (sm *SecretsManager) Validate() (esv1.ValidationResult, error) {
  438. // skip validation stack because it depends on the namespace
  439. // of the ExternalSecret
  440. if sm.referentAuth {
  441. return esv1.ValidationResultUnknown, nil
  442. }
  443. _, err := sm.cfg.Credentials.Retrieve(context.Background())
  444. if err != nil {
  445. return esv1.ValidationResultError, util.SanitizeErr(err)
  446. }
  447. return esv1.ValidationResultReady, nil
  448. }
  449. func (sm *SecretsManager) Capabilities() esv1.SecretStoreCapabilities {
  450. return esv1.SecretStoreReadWrite
  451. }
  452. func (sm *SecretsManager) createSecretWithContext(ctx context.Context, secretName string, psd esv1.PushSecretData, value []byte) error {
  453. mdata, err := sm.constructMetadataWithDefaults(psd.GetMetadata())
  454. if err != nil {
  455. return fmt.Errorf("failed to parse push secret metadata: %w", err)
  456. }
  457. tags := []types.Tag{
  458. {
  459. Key: utilpointer.To(managedBy),
  460. Value: utilpointer.To(externalSecrets),
  461. },
  462. }
  463. for k, v := range mdata.Spec.Tags {
  464. tags = append(tags, types.Tag{
  465. Key: utilpointer.To(k),
  466. Value: utilpointer.To(v),
  467. })
  468. }
  469. input := &awssm.CreateSecretInput{
  470. Name: &secretName,
  471. SecretBinary: value,
  472. Tags: tags,
  473. Description: utilpointer.To(mdata.Spec.Description),
  474. ClientRequestToken: utilpointer.To(initialVersion),
  475. KmsKeyId: utilpointer.To(mdata.Spec.KMSKeyID),
  476. }
  477. if mdata.Spec.SecretPushFormat == SecretPushFormatString {
  478. input.SecretBinary = nil
  479. input.SecretString = aws.String(string(value))
  480. }
  481. _, err = sm.client.CreateSecret(ctx, input)
  482. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMCreateSecret, err)
  483. return err
  484. }
  485. func (sm *SecretsManager) putSecretValueWithContext(ctx context.Context, secretInput awssm.DescribeSecretInput, awsSecret *awssm.GetSecretValueOutput, psd esv1.PushSecretData, value []byte) error {
  486. data, err := sm.client.DescribeSecret(ctx, &secretInput)
  487. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMDescribeSecret, err)
  488. if err != nil {
  489. return err
  490. }
  491. if !isManagedByESO(data) {
  492. return errors.New("secret not managed by external-secrets")
  493. }
  494. if awsSecret != nil && bytes.Equal(awsSecret.SecretBinary, value) || utils.CompareStringAndByteSlices(awsSecret.SecretString, value) {
  495. return nil
  496. }
  497. newVersionNumber, err := bumpVersionNumber(awsSecret.VersionId)
  498. if err != nil {
  499. return err
  500. }
  501. input := &awssm.PutSecretValueInput{
  502. SecretId: awsSecret.ARN,
  503. SecretBinary: value,
  504. ClientRequestToken: newVersionNumber,
  505. }
  506. secretPushFormat, err := utils.FetchValueFromMetadata(SecretPushFormatKey, psd.GetMetadata(), SecretPushFormatBinary)
  507. if err != nil {
  508. return fmt.Errorf("failed to parse metadata: %w", err)
  509. }
  510. if secretPushFormat == SecretPushFormatString {
  511. input.SecretBinary = nil
  512. input.SecretString = aws.String(string(value))
  513. }
  514. _, err = sm.client.PutSecretValue(ctx, input)
  515. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMPutSecretValue, err)
  516. return err
  517. }
  518. func (sm *SecretsManager) fetchWithBatch(ctx context.Context, filters []types.Filter, matcher *find.Matcher) (map[string][]byte, error) {
  519. data := make(map[string][]byte)
  520. var nextToken *string
  521. for {
  522. it, err := sm.client.BatchGetSecretValue(ctx, &awssm.BatchGetSecretValueInput{
  523. Filters: filters,
  524. NextToken: nextToken,
  525. })
  526. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMBatchGetSecretValue, err)
  527. if err != nil {
  528. return nil, err
  529. }
  530. log.V(1).Info("aws sm findByName found", "secrets", len(it.SecretValues))
  531. for _, secret := range it.SecretValues {
  532. if matcher != nil && !matcher.MatchName(*secret.Name) {
  533. continue
  534. }
  535. log.V(1).Info("aws sm findByName matches", "name", *secret.Name)
  536. sm.setSecretValues(&secret, data)
  537. }
  538. nextToken = it.NextToken
  539. if nextToken == nil {
  540. break
  541. }
  542. }
  543. return data, nil
  544. }
  545. func (sm *SecretsManager) setSecretValues(secret *types.SecretValueEntry, data map[string][]byte) {
  546. if secret.SecretString != nil {
  547. data[*secret.Name] = []byte(*secret.SecretString)
  548. }
  549. if secret.SecretBinary != nil {
  550. data[*secret.Name] = secret.SecretBinary
  551. }
  552. }
  553. func (sm *SecretsManager) constructSecretValue(ctx context.Context, key, ver string, metadataPolicy esv1.ExternalSecretMetadataPolicy) (*awssm.GetSecretValueOutput, error) {
  554. if metadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
  555. describeSecretInput := &awssm.DescribeSecretInput{
  556. SecretId: &key,
  557. }
  558. descOutput, err := sm.client.DescribeSecret(ctx, describeSecretInput)
  559. if err != nil {
  560. return nil, err
  561. }
  562. log.Info("found metadata secret", "key", key, "output", descOutput)
  563. jsonTags, err := util.SecretTagsToJSONString(descOutput.Tags)
  564. if err != nil {
  565. return nil, err
  566. }
  567. return &awssm.GetSecretValueOutput{
  568. ARN: descOutput.ARN,
  569. CreatedDate: descOutput.CreatedDate,
  570. Name: descOutput.Name,
  571. SecretString: &jsonTags,
  572. VersionId: &ver,
  573. }, nil
  574. }
  575. var getSecretValueInput *awssm.GetSecretValueInput
  576. if strings.HasPrefix(ver, "uuid/") {
  577. versionID := strings.TrimPrefix(ver, "uuid/")
  578. getSecretValueInput = &awssm.GetSecretValueInput{
  579. SecretId: &key,
  580. VersionId: &versionID,
  581. }
  582. } else {
  583. getSecretValueInput = &awssm.GetSecretValueInput{
  584. SecretId: &key,
  585. VersionStage: &ver,
  586. }
  587. }
  588. secretOut, err := sm.client.GetSecretValue(ctx, getSecretValueInput)
  589. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMGetSecretValue, err)
  590. var (
  591. nf *types.ResourceNotFoundException
  592. ie *types.InvalidParameterException
  593. )
  594. if errors.As(err, &nf) {
  595. return nil, esv1.NoSecretErr
  596. }
  597. if errors.As(err, &ie) && strings.Contains(ie.Error(), "was marked for deletion") {
  598. return nil, esv1.NoSecretErr
  599. }
  600. return secretOut, err
  601. }
  602. func (sm *SecretsManager) constructMetadataWithDefaults(data *apiextensionsv1.JSON) (*metadata.PushSecretMetadata[PushSecretMetadataSpec], error) {
  603. var (
  604. meta *metadata.PushSecretMetadata[PushSecretMetadataSpec]
  605. err error
  606. )
  607. meta, err = metadata.ParseMetadataParameters[PushSecretMetadataSpec](data)
  608. if err != nil {
  609. return nil, fmt.Errorf("failed to parse metadata: %w", err)
  610. }
  611. if meta == nil {
  612. meta = &metadata.PushSecretMetadata[PushSecretMetadataSpec]{}
  613. }
  614. if meta.Spec.SecretPushFormat == "" {
  615. meta.Spec.SecretPushFormat = SecretPushFormatBinary
  616. } else if !slices.Contains([]string{SecretPushFormatBinary, SecretPushFormatString}, meta.Spec.SecretPushFormat) {
  617. return nil, fmt.Errorf("invalid secret push format: %s", meta.Spec.SecretPushFormat)
  618. }
  619. if meta.Spec.Description == "" {
  620. meta.Spec.Description = fmt.Sprintf("secret '%s:%s'", managedBy, externalSecrets)
  621. }
  622. if meta.Spec.KMSKeyID == "" {
  623. meta.Spec.KMSKeyID = "alias/aws/secretsmanager"
  624. }
  625. if len(meta.Spec.Tags) > 0 {
  626. if _, exists := meta.Spec.Tags[managedBy]; exists {
  627. return nil, fmt.Errorf("error parsing tags in metadata: Cannot specify a '%s' tag", managedBy)
  628. }
  629. }
  630. return meta, nil
  631. }