secretsmanager.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  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. "strings"
  20. "github.com/aws/aws-sdk-go/aws"
  21. "github.com/aws/aws-sdk-go/aws/awserr"
  22. "github.com/aws/aws-sdk-go/aws/request"
  23. "github.com/aws/aws-sdk-go/aws/session"
  24. awssm "github.com/aws/aws-sdk-go/service/secretsmanager"
  25. "github.com/tidwall/gjson"
  26. utilpointer "k8s.io/utils/pointer"
  27. ctrl "sigs.k8s.io/controller-runtime"
  28. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  29. "github.com/external-secrets/external-secrets/pkg/find"
  30. "github.com/external-secrets/external-secrets/pkg/provider/aws/util"
  31. )
  32. // https://github.com/external-secrets/external-secrets/issues/644
  33. var _ esv1beta1.SecretsClient = &SecretsManager{}
  34. // SecretsManager is a provider for AWS SecretsManager.
  35. type SecretsManager struct {
  36. sess *session.Session
  37. client SMInterface
  38. referentAuth bool
  39. cache map[string]*awssm.GetSecretValueOutput
  40. }
  41. // SMInterface is a subset of the smiface api.
  42. // see: https://docs.aws.amazon.com/sdk-for-go/api/service/secretsmanager/secretsmanageriface/
  43. type SMInterface interface {
  44. ListSecrets(*awssm.ListSecretsInput) (*awssm.ListSecretsOutput, error)
  45. GetSecretValue(*awssm.GetSecretValueInput) (*awssm.GetSecretValueOutput, error)
  46. CreateSecretWithContext(aws.Context, *awssm.CreateSecretInput, ...request.Option) (*awssm.CreateSecretOutput, error)
  47. GetSecretValueWithContext(aws.Context, *awssm.GetSecretValueInput, ...request.Option) (*awssm.GetSecretValueOutput, error)
  48. PutSecretValueWithContext(aws.Context, *awssm.PutSecretValueInput, ...request.Option) (*awssm.PutSecretValueOutput, error)
  49. DescribeSecretWithContext(aws.Context, *awssm.DescribeSecretInput, ...request.Option) (*awssm.DescribeSecretOutput, error)
  50. DeleteSecretWithContext(ctx aws.Context, input *awssm.DeleteSecretInput, opts ...request.Option) (*awssm.DeleteSecretOutput, error)
  51. }
  52. const (
  53. errUnexpectedFindOperator = "unexpected find operator"
  54. managedBy = "managed-by"
  55. externalSecrets = "external-secrets"
  56. )
  57. var log = ctrl.Log.WithName("provider").WithName("aws").WithName("secretsmanager")
  58. // New creates a new SecretsManager client.
  59. func New(sess *session.Session, cfg *aws.Config, referentAuth bool) (*SecretsManager, error) {
  60. return &SecretsManager{
  61. sess: sess,
  62. client: awssm.New(sess, cfg),
  63. referentAuth: referentAuth,
  64. cache: make(map[string]*awssm.GetSecretValueOutput),
  65. }, nil
  66. }
  67. func (sm *SecretsManager) fetch(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (*awssm.GetSecretValueOutput, error) {
  68. ver := "AWSCURRENT"
  69. if ref.Version != "" {
  70. ver = ref.Version
  71. }
  72. log.Info("fetching secret value", "key", ref.Key, "version", ver)
  73. cacheKey := fmt.Sprintf("%s#%s", ref.Key, ver)
  74. if secretOut, found := sm.cache[cacheKey]; found {
  75. log.Info("found secret in cache", "key", ref.Key, "version", ver)
  76. return secretOut, nil
  77. }
  78. var getSecretValueInput *awssm.GetSecretValueInput
  79. if strings.HasPrefix(ver, "uuid/") {
  80. versionID := strings.TrimPrefix(ver, "uuid/")
  81. getSecretValueInput = &awssm.GetSecretValueInput{
  82. SecretId: &ref.Key,
  83. VersionId: &versionID,
  84. }
  85. } else {
  86. getSecretValueInput = &awssm.GetSecretValueInput{
  87. SecretId: &ref.Key,
  88. VersionStage: &ver,
  89. }
  90. }
  91. secretOut, err := sm.client.GetSecretValue(getSecretValueInput)
  92. var nf *awssm.ResourceNotFoundException
  93. if errors.As(err, &nf) {
  94. return nil, esv1beta1.NoSecretErr
  95. }
  96. if err != nil {
  97. return nil, err
  98. }
  99. sm.cache[cacheKey] = secretOut
  100. return secretOut, nil
  101. }
  102. func (sm *SecretsManager) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushRemoteRef) error {
  103. secretName := remoteRef.GetRemoteKey()
  104. secretValue := awssm.GetSecretValueInput{
  105. SecretId: &secretName,
  106. }
  107. secretInput := awssm.DescribeSecretInput{
  108. SecretId: &secretName,
  109. }
  110. awsSecret, err := sm.client.GetSecretValueWithContext(ctx, &secretValue)
  111. var aerr awserr.Error
  112. if err != nil {
  113. if ok := errors.As(err, &aerr); !ok {
  114. return err
  115. }
  116. if aerr.Code() == awssm.ErrCodeResourceNotFoundException {
  117. return nil
  118. }
  119. return err
  120. }
  121. data, err := sm.client.DescribeSecretWithContext(ctx, &secretInput)
  122. if err != nil {
  123. return err
  124. }
  125. if !isManagedByESO(data) {
  126. return nil
  127. }
  128. deleteInput := &awssm.DeleteSecretInput{
  129. SecretId: awsSecret.ARN,
  130. }
  131. _, err = sm.client.DeleteSecretWithContext(ctx, deleteInput)
  132. return err
  133. }
  134. func (sm *SecretsManager) PushSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
  135. secretName := remoteRef.GetRemoteKey()
  136. managedBy := managedBy
  137. externalSecrets := externalSecrets
  138. externalSecretsTag := []*awssm.Tag{
  139. {
  140. Key: &managedBy,
  141. Value: &externalSecrets,
  142. },
  143. }
  144. secretRequest := awssm.CreateSecretInput{
  145. Name: &secretName,
  146. SecretBinary: value,
  147. Tags: externalSecretsTag,
  148. }
  149. secretValue := awssm.GetSecretValueInput{
  150. SecretId: &secretName,
  151. }
  152. secretInput := awssm.DescribeSecretInput{
  153. SecretId: &secretName,
  154. }
  155. awsSecret, err := sm.client.GetSecretValueWithContext(ctx, &secretValue)
  156. var aerr awserr.Error
  157. if err != nil {
  158. if ok := errors.As(err, &aerr); !ok {
  159. return err
  160. }
  161. if aerr.Code() == awssm.ErrCodeResourceNotFoundException {
  162. _, err = sm.client.CreateSecretWithContext(ctx, &secretRequest)
  163. return err
  164. }
  165. return err
  166. }
  167. data, err := sm.client.DescribeSecretWithContext(ctx, &secretInput)
  168. if err != nil {
  169. return err
  170. }
  171. if !isManagedByESO(data) {
  172. return fmt.Errorf("secret not managed by external-secrets")
  173. }
  174. if awsSecret != nil && bytes.Equal(awsSecret.SecretBinary, value) {
  175. return nil
  176. }
  177. input := &awssm.PutSecretValueInput{
  178. SecretId: awsSecret.ARN,
  179. SecretBinary: value,
  180. }
  181. _, err = sm.client.PutSecretValueWithContext(ctx, input)
  182. return err
  183. }
  184. func isManagedByESO(data *awssm.DescribeSecretOutput) bool {
  185. managedBy := managedBy
  186. externalSecrets := externalSecrets
  187. for _, tag := range data.Tags {
  188. if *tag.Key == managedBy && *tag.Value == externalSecrets {
  189. return true
  190. }
  191. }
  192. return false
  193. }
  194. // GetAllSecrets syncs multiple secrets from aws provider into a single Kubernetes Secret.
  195. func (sm *SecretsManager) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  196. if ref.Name != nil {
  197. return sm.findByName(ctx, ref)
  198. }
  199. if len(ref.Tags) > 0 {
  200. return sm.findByTags(ctx, ref)
  201. }
  202. return nil, errors.New(errUnexpectedFindOperator)
  203. }
  204. func (sm *SecretsManager) findByName(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  205. matcher, err := find.New(*ref.Name)
  206. if err != nil {
  207. return nil, err
  208. }
  209. filters := make([]*awssm.Filter, 0)
  210. if ref.Path != nil {
  211. filters = append(filters, &awssm.Filter{
  212. Key: utilpointer.String(awssm.FilterNameStringTypeName),
  213. Values: []*string{
  214. ref.Path,
  215. },
  216. })
  217. }
  218. data := make(map[string][]byte)
  219. var nextToken *string
  220. for {
  221. it, err := sm.client.ListSecrets(&awssm.ListSecretsInput{
  222. Filters: filters,
  223. NextToken: nextToken,
  224. })
  225. if err != nil {
  226. return nil, err
  227. }
  228. log.V(1).Info("aws sm findByName found", "secrets", len(it.SecretList))
  229. for _, secret := range it.SecretList {
  230. if !matcher.MatchName(*secret.Name) {
  231. continue
  232. }
  233. log.V(1).Info("aws sm findByName matches", "name", *secret.Name)
  234. err = sm.fetchAndSet(ctx, data, *secret.Name)
  235. if err != nil {
  236. return nil, err
  237. }
  238. }
  239. nextToken = it.NextToken
  240. if nextToken == nil {
  241. break
  242. }
  243. }
  244. return data, nil
  245. }
  246. func (sm *SecretsManager) findByTags(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  247. filters := make([]*awssm.Filter, 0)
  248. for k, v := range ref.Tags {
  249. filters = append(filters, &awssm.Filter{
  250. Key: utilpointer.String(awssm.FilterNameStringTypeTagKey),
  251. Values: []*string{
  252. utilpointer.String(k),
  253. },
  254. }, &awssm.Filter{
  255. Key: utilpointer.String(awssm.FilterNameStringTypeTagValue),
  256. Values: []*string{
  257. utilpointer.String(v),
  258. },
  259. })
  260. }
  261. if ref.Path != nil {
  262. filters = append(filters, &awssm.Filter{
  263. Key: utilpointer.String(awssm.FilterNameStringTypeName),
  264. Values: []*string{
  265. ref.Path,
  266. },
  267. })
  268. }
  269. data := make(map[string][]byte)
  270. var nextToken *string
  271. for {
  272. log.V(1).Info("aws sm findByTag", "nextToken", nextToken)
  273. it, err := sm.client.ListSecrets(&awssm.ListSecretsInput{
  274. Filters: filters,
  275. NextToken: nextToken,
  276. })
  277. if err != nil {
  278. return nil, err
  279. }
  280. log.V(1).Info("aws sm findByTag found", "secrets", len(it.SecretList))
  281. for _, secret := range it.SecretList {
  282. err = sm.fetchAndSet(ctx, data, *secret.Name)
  283. if err != nil {
  284. return nil, err
  285. }
  286. }
  287. nextToken = it.NextToken
  288. if nextToken == nil {
  289. break
  290. }
  291. }
  292. return data, nil
  293. }
  294. func (sm *SecretsManager) fetchAndSet(ctx context.Context, data map[string][]byte, name string) error {
  295. sec, err := sm.fetch(ctx, esv1beta1.ExternalSecretDataRemoteRef{
  296. Key: name,
  297. })
  298. if err != nil {
  299. return err
  300. }
  301. if sec.SecretString != nil {
  302. data[name] = []byte(*sec.SecretString)
  303. }
  304. if sec.SecretBinary != nil {
  305. data[name] = sec.SecretBinary
  306. }
  307. return nil
  308. }
  309. // GetSecret returns a single secret from the provider.
  310. func (sm *SecretsManager) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  311. secretOut, err := sm.fetch(ctx, ref)
  312. if errors.Is(err, esv1beta1.NoSecretErr) {
  313. return nil, err
  314. }
  315. if err != nil {
  316. return nil, util.SanitizeErr(err)
  317. }
  318. if ref.Property == "" {
  319. if secretOut.SecretString != nil {
  320. return []byte(*secretOut.SecretString), nil
  321. }
  322. if secretOut.SecretBinary != nil {
  323. return secretOut.SecretBinary, nil
  324. }
  325. return nil, fmt.Errorf("invalid secret received. no secret string nor binary for key: %s", ref.Key)
  326. }
  327. var payload string
  328. if secretOut.SecretString != nil {
  329. payload = *secretOut.SecretString
  330. }
  331. if secretOut.SecretBinary != nil {
  332. payload = string(secretOut.SecretBinary)
  333. }
  334. // We need to search if a given key with a . exists before using gjson operations.
  335. idx := strings.Index(ref.Property, ".")
  336. if idx > -1 {
  337. refProperty := strings.ReplaceAll(ref.Property, ".", "\\.")
  338. val := gjson.Get(payload, refProperty)
  339. if val.Exists() {
  340. return []byte(val.String()), nil
  341. }
  342. }
  343. val := gjson.Get(payload, ref.Property)
  344. if !val.Exists() {
  345. return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
  346. }
  347. return []byte(val.String()), nil
  348. }
  349. // GetSecretMap returns multiple k/v pairs from the provider.
  350. func (sm *SecretsManager) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  351. log.Info("fetching secret map", "key", ref.Key)
  352. data, err := sm.GetSecret(ctx, ref)
  353. if err != nil {
  354. return nil, err
  355. }
  356. kv := make(map[string]json.RawMessage)
  357. err = json.Unmarshal(data, &kv)
  358. if err != nil {
  359. return nil, fmt.Errorf("unable to unmarshal secret %s: %w", ref.Key, err)
  360. }
  361. secretData := make(map[string][]byte)
  362. for k, v := range kv {
  363. var strVal string
  364. err = json.Unmarshal(v, &strVal)
  365. if err == nil {
  366. secretData[k] = []byte(strVal)
  367. } else {
  368. secretData[k] = v
  369. }
  370. }
  371. return secretData, nil
  372. }
  373. func (sm *SecretsManager) Close(ctx context.Context) error {
  374. return nil
  375. }
  376. func (sm *SecretsManager) Validate() (esv1beta1.ValidationResult, error) {
  377. // skip validation stack because it depends on the namespace
  378. // of the ExternalSecret
  379. if sm.referentAuth {
  380. return esv1beta1.ValidationResultUnknown, nil
  381. }
  382. _, err := sm.sess.Config.Credentials.Get()
  383. if err != nil {
  384. return esv1beta1.ValidationResultError, err
  385. }
  386. return esv1beta1.ValidationResultReady, nil
  387. }
  388. func (sm *SecretsManager) Capabilities() esv1beta1.SecretStoreCapabilities {
  389. return esv1beta1.SecretStoreReadWrite
  390. }