secretsmanager.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  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. "strings"
  21. "github.com/aws/aws-sdk-go/aws"
  22. "github.com/aws/aws-sdk-go/aws/awserr"
  23. "github.com/aws/aws-sdk-go/aws/request"
  24. "github.com/aws/aws-sdk-go/aws/session"
  25. awssm "github.com/aws/aws-sdk-go/service/secretsmanager"
  26. "github.com/google/uuid"
  27. "github.com/tidwall/gjson"
  28. "github.com/tidwall/sjson"
  29. corev1 "k8s.io/api/core/v1"
  30. utilpointer "k8s.io/utils/ptr"
  31. ctrl "sigs.k8s.io/controller-runtime"
  32. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  33. "github.com/external-secrets/external-secrets/pkg/constants"
  34. "github.com/external-secrets/external-secrets/pkg/find"
  35. "github.com/external-secrets/external-secrets/pkg/metrics"
  36. "github.com/external-secrets/external-secrets/pkg/provider/aws/util"
  37. )
  38. // https://github.com/external-secrets/external-secrets/issues/644
  39. var _ esv1beta1.SecretsClient = &SecretsManager{}
  40. // SecretsManager is a provider for AWS SecretsManager.
  41. type SecretsManager struct {
  42. sess *session.Session
  43. client SMInterface
  44. referentAuth bool
  45. cache map[string]*awssm.GetSecretValueOutput
  46. config *esv1beta1.SecretsManager
  47. }
  48. // SMInterface is a subset of the smiface api.
  49. // see: https://docs.aws.amazon.com/sdk-for-go/api/service/secretsmanager/secretsmanageriface/
  50. type SMInterface interface {
  51. ListSecrets(*awssm.ListSecretsInput) (*awssm.ListSecretsOutput, error)
  52. GetSecretValue(*awssm.GetSecretValueInput) (*awssm.GetSecretValueOutput, error)
  53. CreateSecretWithContext(aws.Context, *awssm.CreateSecretInput, ...request.Option) (*awssm.CreateSecretOutput, error)
  54. GetSecretValueWithContext(aws.Context, *awssm.GetSecretValueInput, ...request.Option) (*awssm.GetSecretValueOutput, error)
  55. PutSecretValueWithContext(aws.Context, *awssm.PutSecretValueInput, ...request.Option) (*awssm.PutSecretValueOutput, error)
  56. DescribeSecretWithContext(aws.Context, *awssm.DescribeSecretInput, ...request.Option) (*awssm.DescribeSecretOutput, error)
  57. DeleteSecretWithContext(ctx aws.Context, input *awssm.DeleteSecretInput, opts ...request.Option) (*awssm.DeleteSecretOutput, error)
  58. }
  59. const (
  60. errUnexpectedFindOperator = "unexpected find operator"
  61. managedBy = "managed-by"
  62. externalSecrets = "external-secrets"
  63. initialVersion = "00000000-0000-0000-0000-000000000001"
  64. )
  65. var log = ctrl.Log.WithName("provider").WithName("aws").WithName("secretsmanager")
  66. // New creates a new SecretsManager client.
  67. func New(sess *session.Session, cfg *aws.Config, secretsManagerCfg *esv1beta1.SecretsManager, referentAuth bool) (*SecretsManager, error) {
  68. return &SecretsManager{
  69. sess: sess,
  70. client: awssm.New(sess, cfg),
  71. referentAuth: referentAuth,
  72. cache: make(map[string]*awssm.GetSecretValueOutput),
  73. config: secretsManagerCfg,
  74. }, nil
  75. }
  76. func (sm *SecretsManager) fetch(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (*awssm.GetSecretValueOutput, error) {
  77. ver := "AWSCURRENT"
  78. valueFrom := "SECRET"
  79. if ref.Version != "" {
  80. ver = ref.Version
  81. }
  82. if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
  83. valueFrom = "TAG"
  84. }
  85. log.Info("fetching secret value", "key", ref.Key, "version", ver, "value", valueFrom)
  86. cacheKey := fmt.Sprintf("%s#%s#%s", ref.Key, ver, valueFrom)
  87. if secretOut, found := sm.cache[cacheKey]; found {
  88. log.Info("found secret in cache", "key", ref.Key, "version", ver)
  89. return secretOut, nil
  90. }
  91. var secretOut *awssm.GetSecretValueOutput
  92. var err error
  93. if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
  94. describeSecretInput := &awssm.DescribeSecretInput{
  95. SecretId: &ref.Key,
  96. }
  97. descOutput, err := sm.client.DescribeSecretWithContext(ctx, describeSecretInput)
  98. if err != nil {
  99. return nil, err
  100. }
  101. log.Info("found metadata secret", "key", ref.Key, "output", descOutput)
  102. jsonTags, err := util.SecretTagsToJSONString(descOutput.Tags)
  103. if err != nil {
  104. return nil, err
  105. }
  106. secretOut = &awssm.GetSecretValueOutput{
  107. ARN: descOutput.ARN,
  108. CreatedDate: descOutput.CreatedDate,
  109. Name: descOutput.Name,
  110. SecretString: &jsonTags,
  111. VersionId: &ver,
  112. }
  113. } else {
  114. var getSecretValueInput *awssm.GetSecretValueInput
  115. if strings.HasPrefix(ver, "uuid/") {
  116. versionID := strings.TrimPrefix(ver, "uuid/")
  117. getSecretValueInput = &awssm.GetSecretValueInput{
  118. SecretId: &ref.Key,
  119. VersionId: &versionID,
  120. }
  121. } else {
  122. getSecretValueInput = &awssm.GetSecretValueInput{
  123. SecretId: &ref.Key,
  124. VersionStage: &ver,
  125. }
  126. }
  127. secretOut, err = sm.client.GetSecretValue(getSecretValueInput)
  128. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMGetSecretValue, err)
  129. var nf *awssm.ResourceNotFoundException
  130. if errors.As(err, &nf) {
  131. return nil, esv1beta1.NoSecretErr
  132. }
  133. if err != nil {
  134. return nil, err
  135. }
  136. }
  137. sm.cache[cacheKey] = secretOut
  138. return secretOut, nil
  139. }
  140. func (sm *SecretsManager) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushSecretRemoteRef) error {
  141. secretName := remoteRef.GetRemoteKey()
  142. secretValue := awssm.GetSecretValueInput{
  143. SecretId: &secretName,
  144. }
  145. secretInput := awssm.DescribeSecretInput{
  146. SecretId: &secretName,
  147. }
  148. awsSecret, err := sm.client.GetSecretValueWithContext(ctx, &secretValue)
  149. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMGetSecretValue, err)
  150. var aerr awserr.Error
  151. if err != nil {
  152. if ok := errors.As(err, &aerr); !ok {
  153. return err
  154. }
  155. if aerr.Code() == awssm.ErrCodeResourceNotFoundException {
  156. return nil
  157. }
  158. return err
  159. }
  160. data, err := sm.client.DescribeSecretWithContext(ctx, &secretInput)
  161. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMDescribeSecret, err)
  162. if err != nil {
  163. return err
  164. }
  165. if !isManagedByESO(data) {
  166. return nil
  167. }
  168. deleteInput := &awssm.DeleteSecretInput{
  169. SecretId: awsSecret.ARN,
  170. }
  171. if sm.config != nil && sm.config.ForceDeleteWithoutRecovery {
  172. deleteInput.ForceDeleteWithoutRecovery = &sm.config.ForceDeleteWithoutRecovery
  173. }
  174. if sm.config != nil && sm.config.RecoveryWindowInDays > 0 {
  175. deleteInput.RecoveryWindowInDays = &sm.config.RecoveryWindowInDays
  176. }
  177. err = util.ValidateDeleteSecretInput(*deleteInput)
  178. if err != nil {
  179. return err
  180. }
  181. _, err = sm.client.DeleteSecretWithContext(ctx, deleteInput)
  182. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMDeleteSecret, err)
  183. return err
  184. }
  185. func (sm *SecretsManager) PushSecret(ctx context.Context, secret *corev1.Secret, psd esv1beta1.PushSecretData) error {
  186. secretName := psd.GetRemoteKey()
  187. value := secret.Data[psd.GetSecretKey()]
  188. managedBy := managedBy
  189. externalSecrets := externalSecrets
  190. externalSecretsTag := []*awssm.Tag{
  191. {
  192. Key: &managedBy,
  193. Value: &externalSecrets,
  194. },
  195. }
  196. secretValue := awssm.GetSecretValueInput{
  197. SecretId: &secretName,
  198. }
  199. secretInput := awssm.DescribeSecretInput{
  200. SecretId: &secretName,
  201. }
  202. awsSecret, err := sm.client.GetSecretValueWithContext(ctx, &secretValue)
  203. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMGetSecretValue, err)
  204. if psd.GetProperty() != "" {
  205. currentSecret := sm.retrievePayload(awsSecret)
  206. if currentSecret != "" && !gjson.Valid(currentSecret) {
  207. return errors.New("PushSecret for aws secrets manager with a pushSecretData property requires a json secret")
  208. }
  209. value, _ = sjson.SetBytes([]byte(currentSecret), psd.GetProperty(), value)
  210. }
  211. var aerr awserr.Error
  212. if err != nil {
  213. if ok := errors.As(err, &aerr); !ok {
  214. return err
  215. }
  216. if aerr.Code() == awssm.ErrCodeResourceNotFoundException {
  217. secretVersion := initialVersion
  218. secretRequest := awssm.CreateSecretInput{
  219. Name: &secretName,
  220. SecretBinary: value,
  221. Tags: externalSecretsTag,
  222. ClientRequestToken: &secretVersion,
  223. }
  224. _, err = sm.client.CreateSecretWithContext(ctx, &secretRequest)
  225. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMCreateSecret, err)
  226. return err
  227. }
  228. return err
  229. }
  230. data, err := sm.client.DescribeSecretWithContext(ctx, &secretInput)
  231. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMDescribeSecret, err)
  232. if err != nil {
  233. return err
  234. }
  235. if !isManagedByESO(data) {
  236. return fmt.Errorf("secret not managed by external-secrets")
  237. }
  238. if awsSecret != nil && bytes.Equal(awsSecret.SecretBinary, value) {
  239. return nil
  240. }
  241. newVersionNumber, err := bumpVersionNumber(awsSecret.VersionId)
  242. if err != nil {
  243. return err
  244. }
  245. input := &awssm.PutSecretValueInput{
  246. SecretId: awsSecret.ARN,
  247. SecretBinary: value,
  248. ClientRequestToken: newVersionNumber,
  249. }
  250. _, err = sm.client.PutSecretValueWithContext(ctx, input)
  251. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMPutSecretValue, err)
  252. return err
  253. }
  254. func padOrTrim(b []byte) []byte {
  255. l := len(b)
  256. size := 16
  257. if l == size {
  258. return b
  259. }
  260. if l > size {
  261. return b[l-size:]
  262. }
  263. tmp := make([]byte, size)
  264. copy(tmp[size-l:], b)
  265. return tmp
  266. }
  267. func bumpVersionNumber(id *string) (*string, error) {
  268. if id == nil {
  269. output := initialVersion
  270. return &output, nil
  271. }
  272. n := new(big.Int)
  273. oldVersion, ok := n.SetString(strings.ReplaceAll(*id, "-", ""), 16)
  274. if !ok {
  275. return nil, fmt.Errorf("expected secret version in AWS SSM to be a UUID but got '%s'", *id)
  276. }
  277. newVersionRaw := oldVersion.Add(oldVersion, big.NewInt(1)).Bytes()
  278. newVersion, err := uuid.FromBytes(padOrTrim(newVersionRaw))
  279. if err != nil {
  280. return nil, err
  281. }
  282. s := newVersion.String()
  283. return &s, nil
  284. }
  285. func isManagedByESO(data *awssm.DescribeSecretOutput) bool {
  286. managedBy := managedBy
  287. externalSecrets := externalSecrets
  288. for _, tag := range data.Tags {
  289. if *tag.Key == managedBy && *tag.Value == externalSecrets {
  290. return true
  291. }
  292. }
  293. return false
  294. }
  295. // GetAllSecrets syncs multiple secrets from aws provider into a single Kubernetes Secret.
  296. func (sm *SecretsManager) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  297. if ref.Name != nil {
  298. return sm.findByName(ctx, ref)
  299. }
  300. if len(ref.Tags) > 0 {
  301. return sm.findByTags(ctx, ref)
  302. }
  303. return nil, errors.New(errUnexpectedFindOperator)
  304. }
  305. func (sm *SecretsManager) findByName(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  306. matcher, err := find.New(*ref.Name)
  307. if err != nil {
  308. return nil, err
  309. }
  310. filters := make([]*awssm.Filter, 0)
  311. if ref.Path != nil {
  312. filters = append(filters, &awssm.Filter{
  313. Key: utilpointer.To(awssm.FilterNameStringTypeName),
  314. Values: []*string{
  315. ref.Path,
  316. },
  317. })
  318. }
  319. data := make(map[string][]byte)
  320. var nextToken *string
  321. for {
  322. it, err := sm.client.ListSecrets(&awssm.ListSecretsInput{
  323. Filters: filters,
  324. NextToken: nextToken,
  325. })
  326. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMListSecrets, err)
  327. if err != nil {
  328. return nil, err
  329. }
  330. log.V(1).Info("aws sm findByName found", "secrets", len(it.SecretList))
  331. for _, secret := range it.SecretList {
  332. if !matcher.MatchName(*secret.Name) {
  333. continue
  334. }
  335. log.V(1).Info("aws sm findByName matches", "name", *secret.Name)
  336. err = sm.fetchAndSet(ctx, data, *secret.Name)
  337. if err != nil {
  338. return nil, err
  339. }
  340. }
  341. nextToken = it.NextToken
  342. if nextToken == nil {
  343. break
  344. }
  345. }
  346. return data, nil
  347. }
  348. func (sm *SecretsManager) findByTags(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  349. filters := make([]*awssm.Filter, 0)
  350. for k, v := range ref.Tags {
  351. filters = append(filters, &awssm.Filter{
  352. Key: utilpointer.To(awssm.FilterNameStringTypeTagKey),
  353. Values: []*string{
  354. utilpointer.To(k),
  355. },
  356. }, &awssm.Filter{
  357. Key: utilpointer.To(awssm.FilterNameStringTypeTagValue),
  358. Values: []*string{
  359. utilpointer.To(v),
  360. },
  361. })
  362. }
  363. if ref.Path != nil {
  364. filters = append(filters, &awssm.Filter{
  365. Key: utilpointer.To(awssm.FilterNameStringTypeName),
  366. Values: []*string{
  367. ref.Path,
  368. },
  369. })
  370. }
  371. data := make(map[string][]byte)
  372. var nextToken *string
  373. for {
  374. log.V(1).Info("aws sm findByTag", "nextToken", nextToken)
  375. it, err := sm.client.ListSecrets(&awssm.ListSecretsInput{
  376. Filters: filters,
  377. NextToken: nextToken,
  378. })
  379. metrics.ObserveAPICall(constants.ProviderAWSSM, constants.CallAWSSMListSecrets, err)
  380. if err != nil {
  381. return nil, err
  382. }
  383. log.V(1).Info("aws sm findByTag found", "secrets", len(it.SecretList))
  384. for _, secret := range it.SecretList {
  385. err = sm.fetchAndSet(ctx, data, *secret.Name)
  386. if err != nil {
  387. return nil, err
  388. }
  389. }
  390. nextToken = it.NextToken
  391. if nextToken == nil {
  392. break
  393. }
  394. }
  395. return data, nil
  396. }
  397. func (sm *SecretsManager) fetchAndSet(ctx context.Context, data map[string][]byte, name string) error {
  398. sec, err := sm.fetch(ctx, esv1beta1.ExternalSecretDataRemoteRef{
  399. Key: name,
  400. })
  401. if err != nil {
  402. return err
  403. }
  404. if sec.SecretString != nil {
  405. data[name] = []byte(*sec.SecretString)
  406. }
  407. if sec.SecretBinary != nil {
  408. data[name] = sec.SecretBinary
  409. }
  410. return nil
  411. }
  412. // GetSecret returns a single secret from the provider.
  413. func (sm *SecretsManager) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  414. secretOut, err := sm.fetch(ctx, ref)
  415. if errors.Is(err, esv1beta1.NoSecretErr) {
  416. return nil, err
  417. }
  418. if err != nil {
  419. return nil, util.SanitizeErr(err)
  420. }
  421. if ref.Property == "" {
  422. if secretOut.SecretString != nil {
  423. return []byte(*secretOut.SecretString), nil
  424. }
  425. if secretOut.SecretBinary != nil {
  426. return secretOut.SecretBinary, nil
  427. }
  428. return nil, fmt.Errorf("invalid secret received. no secret string nor binary for key: %s", ref.Key)
  429. }
  430. val := sm.mapSecretToGjson(secretOut, ref.Property)
  431. if !val.Exists() {
  432. return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
  433. }
  434. return []byte(val.String()), nil
  435. }
  436. func (sm *SecretsManager) mapSecretToGjson(secretOut *awssm.GetSecretValueOutput, property string) gjson.Result {
  437. payload := sm.retrievePayload(secretOut)
  438. refProperty := sm.escapeDotsIfRequired(property, payload)
  439. val := gjson.Get(payload, refProperty)
  440. return val
  441. }
  442. func (sm *SecretsManager) retrievePayload(secretOut *awssm.GetSecretValueOutput) string {
  443. var payload string
  444. if secretOut.SecretString != nil {
  445. payload = *secretOut.SecretString
  446. }
  447. if secretOut.SecretBinary != nil {
  448. payload = string(secretOut.SecretBinary)
  449. }
  450. return payload
  451. }
  452. func (sm *SecretsManager) escapeDotsIfRequired(currentRefProperty, payload string) string {
  453. // We need to search if a given key with a . exists before using gjson operations.
  454. idx := strings.Index(currentRefProperty, ".")
  455. refProperty := currentRefProperty
  456. if idx > -1 {
  457. refProperty = strings.ReplaceAll(currentRefProperty, ".", "\\.")
  458. val := gjson.Get(payload, refProperty)
  459. if !val.Exists() {
  460. refProperty = currentRefProperty
  461. }
  462. }
  463. return refProperty
  464. }
  465. // GetSecretMap returns multiple k/v pairs from the provider.
  466. func (sm *SecretsManager) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  467. log.Info("fetching secret map", "key", ref.Key)
  468. data, err := sm.GetSecret(ctx, ref)
  469. if err != nil {
  470. return nil, err
  471. }
  472. kv := make(map[string]json.RawMessage)
  473. err = json.Unmarshal(data, &kv)
  474. if err != nil {
  475. return nil, fmt.Errorf("unable to unmarshal secret %s: %w", ref.Key, err)
  476. }
  477. secretData := make(map[string][]byte)
  478. for k, v := range kv {
  479. var strVal string
  480. err = json.Unmarshal(v, &strVal)
  481. if err == nil {
  482. secretData[k] = []byte(strVal)
  483. } else {
  484. secretData[k] = v
  485. }
  486. }
  487. return secretData, nil
  488. }
  489. func (sm *SecretsManager) Close(_ context.Context) error {
  490. return nil
  491. }
  492. func (sm *SecretsManager) Validate() (esv1beta1.ValidationResult, error) {
  493. // skip validation stack because it depends on the namespace
  494. // of the ExternalSecret
  495. if sm.referentAuth {
  496. return esv1beta1.ValidationResultUnknown, nil
  497. }
  498. _, err := sm.sess.Config.Credentials.Get()
  499. if err != nil {
  500. return esv1beta1.ValidationResultError, util.SanitizeErr(err)
  501. }
  502. return esv1beta1.ValidationResultReady, nil
  503. }
  504. func (sm *SecretsManager) Capabilities() esv1beta1.SecretStoreCapabilities {
  505. return esv1beta1.SecretStoreReadWrite
  506. }