secretsmanager.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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. "context"
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "regexp"
  19. utilpointer "k8s.io/utils/pointer"
  20. "github.com/aws/aws-sdk-go/aws/session"
  21. awssm "github.com/aws/aws-sdk-go/service/secretsmanager"
  22. "github.com/tidwall/gjson"
  23. ctrl "sigs.k8s.io/controller-runtime"
  24. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  25. "github.com/external-secrets/external-secrets/pkg/find"
  26. "github.com/external-secrets/external-secrets/pkg/provider/aws/util"
  27. )
  28. // SecretsManager is a provider for AWS SecretsManager.
  29. type SecretsManager struct {
  30. sess *session.Session
  31. client SMInterface
  32. cache map[string]*awssm.GetSecretValueOutput
  33. }
  34. // SMInterface is a subset of the smiface api.
  35. // see: https://docs.aws.amazon.com/sdk-for-go/api/service/secretsmanager/secretsmanageriface/
  36. type SMInterface interface {
  37. GetSecretValue(*awssm.GetSecretValueInput) (*awssm.GetSecretValueOutput, error)
  38. ListSecrets(*awssm.ListSecretsInput) (*awssm.ListSecretsOutput, error)
  39. }
  40. const (
  41. errUnexpectedFindOperator = "unexpected find operator"
  42. errDuplicateKey = "duplicate key mapping at %s"
  43. )
  44. var log = ctrl.Log.WithName("provider").WithName("aws").WithName("secretsmanager")
  45. // New creates a new SecretsManager client.
  46. func New(sess *session.Session) (*SecretsManager, error) {
  47. return &SecretsManager{
  48. sess: sess,
  49. client: awssm.New(sess),
  50. cache: make(map[string]*awssm.GetSecretValueOutput),
  51. }, nil
  52. }
  53. func (sm *SecretsManager) fetch(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (*awssm.GetSecretValueOutput, error) {
  54. ver := "AWSCURRENT"
  55. if ref.Version != "" {
  56. ver = ref.Version
  57. }
  58. log.Info("fetching secret value", "key", ref.Key, "version", ver)
  59. cacheKey := fmt.Sprintf("%s#%s", ref.Key, ver)
  60. if secretOut, found := sm.cache[cacheKey]; found {
  61. log.Info("found secret in cache", "key", ref.Key, "version", ver)
  62. return secretOut, nil
  63. }
  64. secretOut, err := sm.client.GetSecretValue(&awssm.GetSecretValueInput{
  65. SecretId: &ref.Key,
  66. VersionStage: &ver,
  67. })
  68. if err != nil {
  69. return nil, err
  70. }
  71. sm.cache[cacheKey] = secretOut
  72. return secretOut, nil
  73. }
  74. // Empty GetAllSecrets.
  75. func (sm *SecretsManager) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  76. if ref.Name != nil {
  77. return sm.findByName(ctx, ref)
  78. }
  79. if len(ref.Tags) > 0 {
  80. return sm.findByTags(ctx, ref)
  81. }
  82. return nil, errors.New(errUnexpectedFindOperator)
  83. }
  84. func (sm *SecretsManager) findByName(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  85. matcher, err := find.New(*ref.Name)
  86. if err != nil {
  87. return nil, err
  88. }
  89. data := make(map[string][]byte)
  90. var nextToken *string
  91. for {
  92. ctrl.Log.Info("aws sm findByName", "nextToken", nextToken)
  93. it, err := sm.client.ListSecrets(&awssm.ListSecretsInput{
  94. NextToken: nextToken,
  95. })
  96. if err != nil {
  97. return nil, err
  98. }
  99. ctrl.Log.Info("aws sm findByName found", "secrets", len(it.SecretList))
  100. for _, secret := range it.SecretList {
  101. if !matcher.MatchName(*secret.Name) {
  102. continue
  103. }
  104. ctrl.Log.Info("aws sm findByName matches", "name", *secret.Name)
  105. err = sm.fetchAndSet(ctx, data, *secret.Name)
  106. if err != nil {
  107. return nil, err
  108. }
  109. }
  110. nextToken = it.NextToken
  111. if nextToken == nil {
  112. break
  113. }
  114. }
  115. return data, nil
  116. }
  117. func (sm *SecretsManager) findByTags(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  118. filters := make([]*awssm.Filter, len(ref.Tags)*2)
  119. for k, v := range ref.Tags {
  120. filters = append(filters, &awssm.Filter{
  121. Key: utilpointer.StringPtr(awssm.FilterNameStringTypeTagKey),
  122. Values: []*string{
  123. utilpointer.StringPtr(k),
  124. },
  125. }, &awssm.Filter{
  126. Key: utilpointer.StringPtr(awssm.FilterNameStringTypeTagValue),
  127. Values: []*string{
  128. utilpointer.StringPtr(v),
  129. },
  130. })
  131. }
  132. data := make(map[string][]byte)
  133. var nextToken *string
  134. for {
  135. ctrl.Log.Info("aws sm findByTag", "nextToken", nextToken)
  136. it, err := sm.client.ListSecrets(&awssm.ListSecretsInput{
  137. Filters: filters,
  138. NextToken: nextToken,
  139. })
  140. if err != nil {
  141. return nil, err
  142. }
  143. ctrl.Log.Info("aws sm findByTag found", "secrets", len(it.SecretList))
  144. for _, secret := range it.SecretList {
  145. err = sm.fetchAndSet(ctx, data, *secret.Name)
  146. if err != nil {
  147. return nil, err
  148. }
  149. }
  150. nextToken = it.NextToken
  151. if nextToken == nil {
  152. break
  153. }
  154. }
  155. return data, nil
  156. }
  157. func (sm *SecretsManager) fetchAndSet(ctx context.Context, data map[string][]byte, name string) error {
  158. ctrl.Log.Info("aws sm fetchAndSet fetch", "name", name)
  159. sec, err := sm.fetch(ctx, esv1beta1.ExternalSecretDataRemoteRef{
  160. Key: name,
  161. // Right now we only support AWSCURRENT as version
  162. // There is no intent to support specific versions
  163. // or specific aliases like AWSPREVIOUS or AWSPENDING
  164. Version: "AWSCURRENT",
  165. })
  166. if err != nil {
  167. return err
  168. }
  169. // Note: multiple key names can collide:
  170. // foo/bar and foo$bar would result in the same key
  171. // foo_bar being mapped.
  172. key := mapSecretKey(name)
  173. if _, exist := data[key]; exist {
  174. return fmt.Errorf(errDuplicateKey, key)
  175. }
  176. if sec.SecretString != nil {
  177. data[key] = []byte(*sec.SecretString)
  178. }
  179. if sec.SecretBinary != nil {
  180. data[key] = sec.SecretBinary
  181. }
  182. return nil
  183. }
  184. var keyChars = regexp.MustCompile(`[^A-Za-z0-9_\-.]+`)
  185. func mapSecretKey(key string) string {
  186. return keyChars.ReplaceAllString(key, "_")
  187. }
  188. // GetSecret returns a single secret from the provider.
  189. func (sm *SecretsManager) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  190. secretOut, err := sm.fetch(ctx, ref)
  191. if err != nil {
  192. return nil, util.SanitizeErr(err)
  193. }
  194. if ref.Property == "" {
  195. if secretOut.SecretString != nil {
  196. return []byte(*secretOut.SecretString), nil
  197. }
  198. if secretOut.SecretBinary != nil {
  199. return secretOut.SecretBinary, nil
  200. }
  201. return nil, fmt.Errorf("invalid secret received. no secret string nor binary for key: %s", ref.Key)
  202. }
  203. var payload string
  204. if secretOut.SecretString != nil {
  205. payload = *secretOut.SecretString
  206. }
  207. if secretOut.SecretBinary != nil {
  208. payload = string(secretOut.SecretBinary)
  209. }
  210. val := gjson.Get(payload, ref.Property)
  211. if !val.Exists() {
  212. return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
  213. }
  214. return []byte(val.String()), nil
  215. }
  216. // GetSecretMap returns multiple k/v pairs from the provider.
  217. func (sm *SecretsManager) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  218. log.Info("fetching secret map", "key", ref.Key)
  219. data, err := sm.GetSecret(ctx, ref)
  220. if err != nil {
  221. return nil, err
  222. }
  223. kv := make(map[string]json.RawMessage)
  224. err = json.Unmarshal(data, &kv)
  225. if err != nil {
  226. return nil, fmt.Errorf("unable to unmarshal secret %s: %w", ref.Key, err)
  227. }
  228. secretData := make(map[string][]byte)
  229. for k, v := range kv {
  230. var strVal string
  231. err = json.Unmarshal(v, &strVal)
  232. if err == nil {
  233. secretData[k] = []byte(strVal)
  234. } else {
  235. secretData[k] = v
  236. }
  237. }
  238. return secretData, nil
  239. }
  240. func (sm *SecretsManager) Close(ctx context.Context) error {
  241. return nil
  242. }
  243. func (sm *SecretsManager) Validate() error {
  244. _, err := sm.sess.Config.Credentials.Get()
  245. return err
  246. }