auth_iam.go 6.7 KB

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