auth.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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 auth
  13. import (
  14. "context"
  15. "fmt"
  16. "github.com/aws/aws-sdk-go/aws"
  17. "github.com/aws/aws-sdk-go/aws/credentials"
  18. "github.com/aws/aws-sdk-go/aws/credentials/stscreds"
  19. "github.com/aws/aws-sdk-go/aws/defaults"
  20. "github.com/aws/aws-sdk-go/aws/request"
  21. "github.com/aws/aws-sdk-go/aws/session"
  22. "github.com/aws/aws-sdk-go/service/sts"
  23. "github.com/aws/aws-sdk-go/service/sts/stsiface"
  24. v1 "k8s.io/api/core/v1"
  25. "k8s.io/apimachinery/pkg/types"
  26. "k8s.io/client-go/kubernetes"
  27. ctrl "sigs.k8s.io/controller-runtime"
  28. "sigs.k8s.io/controller-runtime/pkg/client"
  29. ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
  30. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  31. "github.com/external-secrets/external-secrets/pkg/provider/aws/util"
  32. )
  33. // Config contains configuration to create a new AWS provider.
  34. type Config struct {
  35. AssumeRole string
  36. Region string
  37. APIRetries int
  38. }
  39. var log = ctrl.Log.WithName("provider").WithName("aws")
  40. const (
  41. roleARNAnnotation = "eks.amazonaws.com/role-arn"
  42. errInvalidClusterStoreMissingAKIDNamespace = "invalid ClusterSecretStore: missing AWS AccessKeyID Namespace"
  43. errInvalidClusterStoreMissingSAKNamespace = "invalid ClusterSecretStore: missing AWS SecretAccessKey Namespace"
  44. errFetchAKIDSecret = "could not fetch accessKeyID secret: %w"
  45. errFetchSAKSecret = "could not fetch SecretAccessKey secret: %w"
  46. errMissingSAK = "missing SecretAccessKey"
  47. errMissingAKID = "missing AccessKeyID"
  48. )
  49. // New creates a new aws session based on the provided store
  50. // it uses the following authentication mechanisms in order:
  51. // * service-account token authentication via AssumeRoleWithWebIdentity
  52. // * static credentials from a Kind=Secret, optionally with doing a AssumeRole.
  53. // * sdk default provider chain, see: https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default
  54. func New(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string, assumeRoler STSProvider, jwtProvider jwtProviderFactory) (*session.Session, error) {
  55. prov, err := util.GetAWSProvider(store)
  56. if err != nil {
  57. return nil, err
  58. }
  59. var creds *credentials.Credentials
  60. // use credentials via service account token
  61. jwtAuth := prov.Auth.JWTAuth
  62. if jwtAuth != nil {
  63. creds, err = sessionFromServiceAccount(ctx, prov, store, kube, namespace, jwtProvider)
  64. if err != nil {
  65. return nil, err
  66. }
  67. }
  68. // use credentials from sercretRef
  69. secretRef := prov.Auth.SecretRef
  70. if secretRef != nil {
  71. log.V(1).Info("using credentials from secretRef")
  72. creds, err = sessionFromSecretRef(ctx, prov, store, kube, namespace)
  73. if err != nil {
  74. return nil, err
  75. }
  76. }
  77. config := aws.NewConfig().WithEndpointResolver(ResolveEndpoint())
  78. if creds != nil {
  79. config.WithCredentials(creds)
  80. }
  81. if prov.Region != "" {
  82. config.WithRegion(prov.Region)
  83. }
  84. handlers := defaults.Handlers()
  85. handlers.Build.PushBack(request.WithAppendUserAgent("external-secrets"))
  86. sess, err := session.NewSessionWithOptions(session.Options{
  87. Config: *config,
  88. Handlers: handlers,
  89. SharedConfigState: session.SharedConfigDisable,
  90. })
  91. if err != nil {
  92. return nil, err
  93. }
  94. if prov.Role != "" {
  95. stsclient := assumeRoler(sess)
  96. sess.Config.WithCredentials(stscreds.NewCredentialsWithClient(stsclient, prov.Role))
  97. }
  98. log.Info("using aws session", "region", *sess.Config.Region, "credentials", creds)
  99. return sess, nil
  100. }
  101. func sessionFromSecretRef(ctx context.Context, prov *esv1alpha1.AWSProvider, store esv1alpha1.GenericStore, kube client.Client, namespace string) (*credentials.Credentials, error) {
  102. ke := client.ObjectKey{
  103. Name: prov.Auth.SecretRef.AccessKeyID.Name,
  104. Namespace: namespace, // default to ExternalSecret namespace
  105. }
  106. // only ClusterStore is allowed to set namespace (and then it's required)
  107. if store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
  108. if prov.Auth.SecretRef.AccessKeyID.Namespace == nil {
  109. return nil, fmt.Errorf(errInvalidClusterStoreMissingAKIDNamespace)
  110. }
  111. ke.Namespace = *prov.Auth.SecretRef.AccessKeyID.Namespace
  112. }
  113. akSecret := v1.Secret{}
  114. err := kube.Get(ctx, ke, &akSecret)
  115. if err != nil {
  116. return nil, fmt.Errorf(errFetchAKIDSecret, err)
  117. }
  118. ke = client.ObjectKey{
  119. Name: prov.Auth.SecretRef.SecretAccessKey.Name,
  120. Namespace: namespace, // default to ExternalSecret namespace
  121. }
  122. // only ClusterStore is allowed to set namespace (and then it's required)
  123. if store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
  124. if prov.Auth.SecretRef.SecretAccessKey.Namespace == nil {
  125. return nil, fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
  126. }
  127. ke.Namespace = *prov.Auth.SecretRef.SecretAccessKey.Namespace
  128. }
  129. sakSecret := v1.Secret{}
  130. err = kube.Get(ctx, ke, &sakSecret)
  131. if err != nil {
  132. return nil, fmt.Errorf(errFetchSAKSecret, err)
  133. }
  134. sak := string(sakSecret.Data[prov.Auth.SecretRef.SecretAccessKey.Key])
  135. aks := string(akSecret.Data[prov.Auth.SecretRef.AccessKeyID.Key])
  136. if sak == "" {
  137. return nil, fmt.Errorf(errMissingSAK)
  138. }
  139. if aks == "" {
  140. return nil, fmt.Errorf(errMissingAKID)
  141. }
  142. return credentials.NewStaticCredentials(aks, sak, ""), err
  143. }
  144. func sessionFromServiceAccount(ctx context.Context, prov *esv1alpha1.AWSProvider, store esv1alpha1.GenericStore, kube client.Client, namespace string, jwtProvider jwtProviderFactory) (*credentials.Credentials, error) {
  145. if store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
  146. namespace = *prov.Auth.JWTAuth.ServiceAccountRef.Namespace
  147. }
  148. name := prov.Auth.JWTAuth.ServiceAccountRef.Name
  149. sa := v1.ServiceAccount{}
  150. err := kube.Get(ctx, types.NamespacedName{
  151. Name: name,
  152. Namespace: namespace,
  153. }, &sa)
  154. if err != nil {
  155. return nil, err
  156. }
  157. // the service account is expected to have a well-known annotation
  158. // this is used as input to assumeRoleWithWebIdentity
  159. roleArn := sa.Annotations[roleARNAnnotation]
  160. if roleArn == "" {
  161. return nil, fmt.Errorf("an IAM role must be associated with service account %s (namespace: %s)", name, namespace)
  162. }
  163. jwtProv, err := jwtProvider(name, namespace, roleArn, prov.Region)
  164. if err != nil {
  165. return nil, err
  166. }
  167. log.V(1).Info("using credentials via service account", "role", roleArn, "region", prov.Region)
  168. return credentials.NewCredentials(jwtProv), nil
  169. }
  170. type jwtProviderFactory func(name, namespace, roleArn, region string) (credentials.Provider, error)
  171. // DefaultJWTProvider returns a credentials.Provider that calls the AssumeRoleWithWebidentity
  172. // controller-runtime/client does not support TokenRequest or other subresource APIs
  173. // so we need to construct our own client and use it to fetch tokens.
  174. func DefaultJWTProvider(name, namespace, roleArn, region string) (credentials.Provider, error) {
  175. cfg, err := ctrlcfg.GetConfig()
  176. if err != nil {
  177. return nil, err
  178. }
  179. clientset, err := kubernetes.NewForConfig(cfg)
  180. if err != nil {
  181. return nil, err
  182. }
  183. handlers := defaults.Handlers()
  184. handlers.Build.PushBack(request.WithAppendUserAgent("external-secrets"))
  185. awscfg := aws.NewConfig().WithEndpointResolver(ResolveEndpoint())
  186. if region != "" {
  187. awscfg.WithRegion(region)
  188. }
  189. sess, err := session.NewSessionWithOptions(session.Options{
  190. Config: *awscfg,
  191. SharedConfigState: session.SharedConfigDisable,
  192. Handlers: handlers,
  193. })
  194. if err != nil {
  195. return nil, err
  196. }
  197. tokenFetcher := &authTokenFetcher{
  198. Namespace: namespace,
  199. ServiceAccount: name,
  200. k8sClient: clientset.CoreV1(),
  201. }
  202. return stscreds.NewWebIdentityRoleProviderWithToken(
  203. sts.New(sess), roleArn, "external-secrets-provider-aws", tokenFetcher), nil
  204. }
  205. type STSProvider func(*session.Session) stsiface.STSAPI
  206. func DefaultSTSProvider(sess *session.Session) stsiface.STSAPI {
  207. return sts.New(sess)
  208. }