auth_gcp.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package vault
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. "os"
  19. authgcp "github.com/hashicorp/vault/api/auth/gcp"
  20. "golang.org/x/oauth2/google"
  21. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  22. gcpsm "github.com/external-secrets/external-secrets/providers/v1/gcp/secretmanager"
  23. "github.com/external-secrets/external-secrets/runtime/constants"
  24. "github.com/external-secrets/external-secrets/runtime/metrics"
  25. )
  26. const (
  27. defaultGCPAuthMountPath = "gcp"
  28. googleOAuthAccessTokenKey = "GOOGLE_OAUTH_ACCESS_TOKEN"
  29. )
  30. func setGcpAuthToken(ctx context.Context, v *client) (bool, error) {
  31. gcpAuth := v.store.Auth.GCP
  32. if gcpAuth == nil {
  33. return false, nil
  34. }
  35. // Only proceed with actual authentication if the auth client is available
  36. if v.auth == nil {
  37. return true, errors.New("vault auth client not initialized")
  38. }
  39. err := v.requestTokenWithGcpAuth(ctx, gcpAuth)
  40. if err != nil {
  41. return true, err
  42. }
  43. return true, nil
  44. }
  45. func (c *client) requestTokenWithGcpAuth(ctx context.Context, gcpAuth *esv1.VaultGCPAuth) error {
  46. authMountPath := c.getGCPAuthMountPathOrDefault(gcpAuth.Path)
  47. role := gcpAuth.Role
  48. // Set up GCP authentication using workload identity or service account key
  49. err := c.setupGCPAuth(ctx, gcpAuth)
  50. if err != nil {
  51. return fmt.Errorf("failed to set up GCP authentication: %w", err)
  52. }
  53. // Determine which GCP auth method to use based on available authentication
  54. var gcpAuthClient *authgcp.GCPAuth
  55. if gcpAuth.SecretRef != nil || gcpAuth.WorkloadIdentity != nil {
  56. // Use IAM auth method when we have explicit credentials (service account key or workload identity)
  57. gcpAuthClient, err = authgcp.NewGCPAuth(role,
  58. authgcp.WithMountPath(authMountPath),
  59. authgcp.WithIAMAuth(""), // Service account email will be determined automatically from credentials
  60. )
  61. } else {
  62. // Use GCE auth method for GCE instances (includes ServiceAccountRef and default ADC scenarios)
  63. gcpAuthClient, err = authgcp.NewGCPAuth(role,
  64. authgcp.WithMountPath(authMountPath),
  65. authgcp.WithGCEAuth(),
  66. )
  67. }
  68. if err != nil {
  69. return err
  70. }
  71. // Authenticate with Vault using GCP auth
  72. _, err = c.auth.Login(ctx, gcpAuthClient)
  73. metrics.ObserveAPICall(constants.ProviderHCVault, constants.CallHCVaultLogin, err)
  74. if err != nil {
  75. return err
  76. }
  77. return nil
  78. }
  79. func (c *client) setupGCPAuth(ctx context.Context, gcpAuth *esv1.VaultGCPAuth) error {
  80. // Priority order for GCP authentication methods:
  81. // 1. SecretRef: Service account key from Kubernetes secret (uses IAM auth method)
  82. // 2. WorkloadIdentity: GKE Workload Identity (uses IAM auth method)
  83. // 3. ServiceAccountRef: Pod's service account (uses GCE auth method)
  84. // 4. Default ADC: Application Default Credentials (uses GCE auth method)
  85. // First priority: Service account key from secret
  86. if gcpAuth.SecretRef != nil {
  87. return c.setupServiceAccountKeyAuth(ctx, gcpAuth)
  88. }
  89. // Second priority: Workload identity
  90. if gcpAuth.WorkloadIdentity != nil {
  91. return c.setupWorkloadIdentityAuth(ctx, gcpAuth)
  92. }
  93. // Third priority: Service account reference (for token creation)
  94. if gcpAuth.ServiceAccountRef != nil {
  95. return c.setupServiceAccountRefAuth(ctx, gcpAuth)
  96. }
  97. // Last resort: Default GCP authentication (ADC)
  98. return c.setupDefaultGCPAuth()
  99. }
  100. func (c *client) setupServiceAccountKeyAuth(ctx context.Context, gcpAuth *esv1.VaultGCPAuth) error {
  101. tokenSource, err := gcpsm.NewTokenSource(ctx, esv1.GCPSMAuth{
  102. SecretRef: gcpAuth.SecretRef,
  103. }, gcpAuth.ProjectID, c.storeKind, c.kube, c.namespace)
  104. if err != nil {
  105. return fmt.Errorf("failed to create token source from secret: %w", err)
  106. }
  107. token, err := tokenSource.Token()
  108. if err != nil {
  109. return fmt.Errorf("failed to retrieve token from secret: %w", err)
  110. }
  111. c.log.V(1).Info("Setting up GCP authentication using service account credentials from secret")
  112. return c.setGCPEnvironment(token.AccessToken)
  113. }
  114. func (c *client) setupWorkloadIdentityAuth(ctx context.Context, gcpAuth *esv1.VaultGCPAuth) error {
  115. tokenSource, err := gcpsm.NewTokenSource(ctx, esv1.GCPSMAuth{
  116. WorkloadIdentity: gcpAuth.WorkloadIdentity,
  117. }, gcpAuth.ProjectID, c.storeKind, c.kube, c.namespace)
  118. if err != nil {
  119. return fmt.Errorf("failed to create token source from workload identity: %w", err)
  120. }
  121. token, err := tokenSource.Token()
  122. if err != nil {
  123. return fmt.Errorf("failed to retrieve token from workload identity: %w", err)
  124. }
  125. c.log.V(1).Info("Setting up GCP authentication using workload identity")
  126. return c.setGCPEnvironment(token.AccessToken)
  127. }
  128. func (c *client) setupServiceAccountRefAuth(_ context.Context, _ *esv1.VaultGCPAuth) error {
  129. // When ServiceAccountRef is specified, we use the Kubernetes service account
  130. // The GCE auth method will automatically use the service account attached to the pod
  131. // This leverages GKE Workload Identity or service account key mounted in the pod
  132. c.log.V(1).Info("Setting up GCP authentication using service account reference with GCE auth method")
  133. // No explicit token setup needed - GCE auth method will use the pod's service account
  134. // This works with both Workload Identity and traditional service account keys
  135. return nil
  136. }
  137. func (c *client) setupDefaultGCPAuth() error {
  138. c.log.V(1).Info("Setting up default GCP authentication (ADC)")
  139. // Validate that ADC is available before proceeding
  140. ctx := context.Background()
  141. creds, err := google.FindDefaultCredentials(ctx)
  142. if err != nil {
  143. return fmt.Errorf("Application Default Credentials (ADC) not available: %w", err)
  144. }
  145. c.log.V(1).Info("ADC validation successful", "project_id", creds.ProjectID)
  146. // No explicit token setup needed - the Vault GCP auth method will use ADC automatically
  147. return nil
  148. }
  149. func (c *client) setGCPEnvironment(accessToken string) error {
  150. // The Vault GCP auth method will use this environment variable if set
  151. if err := c.setEnvVar(googleOAuthAccessTokenKey, accessToken); err != nil {
  152. return fmt.Errorf("failed to set GCP environment variable: %w", err)
  153. }
  154. return nil
  155. }
  156. func (c *client) setEnvVar(key, value string) error {
  157. if value == "" {
  158. return fmt.Errorf("empty value for environment variable %s", key)
  159. }
  160. if err := os.Setenv(key, value); err != nil {
  161. return fmt.Errorf("failed to set environment variable %s: %w", key, err)
  162. }
  163. c.log.V(1).Info("Set environment variable for GCP authentication", "key", key)
  164. return nil
  165. }
  166. func (c *client) getGCPAuthMountPathOrDefault(path string) string {
  167. if path != "" {
  168. return path
  169. }
  170. return defaultGCPAuthMountPath
  171. }