auth_iam.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  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 vault
  13. import (
  14. "context"
  15. "fmt"
  16. "os"
  17. "path/filepath"
  18. "github.com/aws/aws-sdk-go/aws"
  19. "github.com/aws/aws-sdk-go/aws/credentials"
  20. "github.com/aws/aws-sdk-go/aws/credentials/stscreds"
  21. "github.com/golang-jwt/jwt/v5"
  22. authaws "github.com/hashicorp/vault/api/auth/aws"
  23. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  24. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  25. "github.com/external-secrets/external-secrets/pkg/constants"
  26. "github.com/external-secrets/external-secrets/pkg/metrics"
  27. vaultiamauth "github.com/external-secrets/external-secrets/pkg/provider/vault/iamauth"
  28. "github.com/external-secrets/external-secrets/pkg/provider/vault/util"
  29. )
  30. const (
  31. defaultAWSRegion = "us-east-1"
  32. defaultAWSAuthMountPath = "aws"
  33. errIrsaTokenEnvVarNotFoundOnPod = "expected env variable: %s not found on controller's pod"
  34. errIrsaTokenFileNotFoundOnPod = "web ddentity token file not found at %s location: %w"
  35. errIrsaTokenFileNotReadable = "could not read the web identity token from the file %s: %w"
  36. errIrsaTokenNotValidJWT = "could not parse web identity token available at %s. not a valid jwt?: %w"
  37. errPodInfoNotFoundOnToken = "could not find pod identity info on token %s: %w"
  38. )
  39. func setIamAuthToken(ctx context.Context, v *client, jwtProvider util.JwtProviderFactory, assumeRoler vaultiamauth.STSProvider) (bool, error) {
  40. iamAuth := v.store.Auth.Iam
  41. isClusterKind := v.storeKind == esv1.ClusterSecretStoreKind
  42. if iamAuth != nil {
  43. err := v.requestTokenWithIamAuth(ctx, iamAuth, isClusterKind, v.kube, v.namespace, jwtProvider, assumeRoler)
  44. if err != nil {
  45. return true, err
  46. }
  47. return true, nil
  48. }
  49. return false, nil
  50. }
  51. func (c *client) requestTokenWithIamAuth(ctx context.Context, iamAuth *esv1.VaultIamAuth, isClusterKind bool, k kclient.Client, n string, jwtProvider util.JwtProviderFactory, assumeRoler vaultiamauth.STSProvider) error {
  52. jwtAuth := iamAuth.JWTAuth
  53. secretRefAuth := iamAuth.SecretRef
  54. regionAWS := defaultAWSRegion
  55. awsAuthMountPath := defaultAWSAuthMountPath
  56. if iamAuth.Region != "" {
  57. regionAWS = iamAuth.Region
  58. }
  59. if iamAuth.Path != "" {
  60. awsAuthMountPath = iamAuth.Path
  61. }
  62. var creds *credentials.Credentials
  63. var err error
  64. if jwtAuth != nil { // use credentials from a sa explicitly defined and referenced. Highest preference is given to this method/configuration.
  65. creds, err = vaultiamauth.CredsFromServiceAccount(ctx, *iamAuth, regionAWS, isClusterKind, k, n, jwtProvider)
  66. if err != nil {
  67. return err
  68. }
  69. } else if secretRefAuth != nil { // if jwtAuth is not defined, check if secretRef is defined. Second preference.
  70. logger.V(1).Info("using credentials from secretRef")
  71. creds, err = vaultiamauth.CredsFromSecretRef(ctx, *iamAuth, c.storeKind, k, n)
  72. if err != nil {
  73. return err
  74. }
  75. }
  76. // Neither of jwtAuth or secretRefAuth defined. Last preference.
  77. // Default to controller pod's identity
  78. if jwtAuth == nil && secretRefAuth == nil {
  79. // Checking if controller pod's service account is IRSA enabled and Web Identity token is available on pod
  80. tokenFile, ok := os.LookupEnv(vaultiamauth.AWSWebIdentityTokenFileEnvVar)
  81. if !ok {
  82. return fmt.Errorf(errIrsaTokenEnvVarNotFoundOnPod, vaultiamauth.AWSWebIdentityTokenFileEnvVar) // No Web Identity(IRSA) token found on pod
  83. }
  84. // IRSA enabled service account, let's check that the jwt token filemount and file exists
  85. if _, err := os.Stat(tokenFile); err != nil {
  86. return fmt.Errorf(errIrsaTokenFileNotFoundOnPod, tokenFile, err)
  87. }
  88. // everything looks good so far, let's fetch the jwt token from AWS_WEB_IDENTITY_TOKEN_FILE
  89. jwtByte, err := os.ReadFile(filepath.Clean(tokenFile))
  90. if err != nil {
  91. return fmt.Errorf(errIrsaTokenFileNotReadable, tokenFile, err)
  92. }
  93. // let's parse the jwt token
  94. parser := jwt.NewParser(jwt.WithoutClaimsValidation())
  95. token, _, err := parser.ParseUnverified(string(jwtByte), jwt.MapClaims{})
  96. if err != nil {
  97. return fmt.Errorf(errIrsaTokenNotValidJWT, tokenFile, err) // JWT token parser error
  98. }
  99. var ns string
  100. var sa string
  101. // let's fetch the namespace and serviceaccount from parsed jwt token
  102. if claims, ok := token.Claims.(jwt.MapClaims); ok {
  103. ns = claims["kubernetes.io"].(map[string]any)["namespace"].(string)
  104. sa = claims["kubernetes.io"].(map[string]any)["serviceaccount"].(map[string]any)["name"].(string)
  105. } else {
  106. return fmt.Errorf(errPodInfoNotFoundOnToken, tokenFile, err)
  107. }
  108. creds, err = vaultiamauth.CredsFromControllerServiceAccount(ctx, sa, ns, regionAWS, k, jwtProvider)
  109. if err != nil {
  110. return err
  111. }
  112. }
  113. config := aws.NewConfig().WithEndpointResolver(vaultiamauth.ResolveEndpoint())
  114. if creds != nil {
  115. config.WithCredentials(creds)
  116. }
  117. if regionAWS != "" {
  118. config.WithRegion(regionAWS)
  119. }
  120. sess, err := vaultiamauth.GetAWSSession(config)
  121. if err != nil {
  122. return err
  123. }
  124. if iamAuth.AWSIAMRole != "" {
  125. stsclient := assumeRoler(sess)
  126. if iamAuth.ExternalID != "" {
  127. var setExternalID = func(p *stscreds.AssumeRoleProvider) {
  128. p.ExternalID = aws.String(iamAuth.ExternalID)
  129. }
  130. sess.Config.WithCredentials(stscreds.NewCredentialsWithClient(stsclient, iamAuth.AWSIAMRole, setExternalID))
  131. } else {
  132. sess.Config.WithCredentials(stscreds.NewCredentialsWithClient(stsclient, iamAuth.AWSIAMRole))
  133. }
  134. }
  135. getCreds, err := sess.Config.Credentials.Get()
  136. if err != nil {
  137. return err
  138. }
  139. // Set environment variables. These would be fetched by Login
  140. _ = os.Setenv("AWS_ACCESS_KEY_ID", getCreds.AccessKeyID)
  141. _ = os.Setenv("AWS_SECRET_ACCESS_KEY", getCreds.SecretAccessKey)
  142. _ = os.Setenv("AWS_SESSION_TOKEN", getCreds.SessionToken)
  143. var awsAuthClient *authaws.AWSAuth
  144. if iamAuth.VaultAWSIAMServerID != "" {
  145. awsAuthClient, err = authaws.NewAWSAuth(authaws.WithRegion(regionAWS), authaws.WithIAMAuth(), authaws.WithRole(iamAuth.Role), authaws.WithMountPath(awsAuthMountPath), authaws.WithIAMServerIDHeader(iamAuth.VaultAWSIAMServerID))
  146. if err != nil {
  147. return err
  148. }
  149. } else {
  150. awsAuthClient, err = authaws.NewAWSAuth(authaws.WithRegion(regionAWS), authaws.WithIAMAuth(), authaws.WithRole(iamAuth.Role), authaws.WithMountPath(awsAuthMountPath))
  151. if err != nil {
  152. return err
  153. }
  154. }
  155. _, err = c.auth.Login(ctx, awsAuthClient)
  156. metrics.ObserveAPICall(constants.ProviderHCVault, constants.CallHCVaultLogin, err)
  157. if err != nil {
  158. return err
  159. }
  160. return nil
  161. }