| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- /*
- Copyright © The ESO Authors
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- https://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package vault
- import (
- "context"
- "errors"
- "fmt"
- "os"
- authgcp "github.com/hashicorp/vault/api/auth/gcp"
- "golang.org/x/oauth2/google"
- esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
- gcpsm "github.com/external-secrets/external-secrets/providers/v1/gcp/secretmanager"
- "github.com/external-secrets/external-secrets/runtime/constants"
- "github.com/external-secrets/external-secrets/runtime/metrics"
- )
- const (
- defaultGCPAuthMountPath = "gcp"
- googleOAuthAccessTokenKey = "GOOGLE_OAUTH_ACCESS_TOKEN"
- )
- func setGcpAuthToken(ctx context.Context, v *client) (bool, error) {
- gcpAuth := v.store.Auth.GCP
- if gcpAuth == nil {
- return false, nil
- }
- // Only proceed with actual authentication if the auth client is available
- if v.auth == nil {
- return true, errors.New("vault auth client not initialized")
- }
- err := v.requestTokenWithGcpAuth(ctx, gcpAuth)
- if err != nil {
- return true, err
- }
- return true, nil
- }
- func (c *client) requestTokenWithGcpAuth(ctx context.Context, gcpAuth *esv1.VaultGCPAuth) error {
- authMountPath := c.getGCPAuthMountPathOrDefault(gcpAuth.Path)
- role := gcpAuth.Role
- // Set up GCP authentication using workload identity or service account key
- err := c.setupGCPAuth(ctx, gcpAuth)
- if err != nil {
- return fmt.Errorf("failed to set up GCP authentication: %w", err)
- }
- // Determine which GCP auth method to use based on available authentication
- var gcpAuthClient *authgcp.GCPAuth
- if gcpAuth.SecretRef != nil || gcpAuth.WorkloadIdentity != nil {
- // Use IAM auth method when we have explicit credentials (service account key or workload identity)
- gcpAuthClient, err = authgcp.NewGCPAuth(role,
- authgcp.WithMountPath(authMountPath),
- authgcp.WithIAMAuth(""), // Service account email will be determined automatically from credentials
- )
- } else {
- // Use GCE auth method for GCE instances (includes ServiceAccountRef and default ADC scenarios)
- gcpAuthClient, err = authgcp.NewGCPAuth(role,
- authgcp.WithMountPath(authMountPath),
- authgcp.WithGCEAuth(),
- )
- }
- if err != nil {
- return err
- }
- // Authenticate with Vault using GCP auth
- _, err = c.auth.Login(ctx, gcpAuthClient)
- metrics.ObserveAPICall(constants.ProviderHCVault, constants.CallHCVaultLogin, err)
- if err != nil {
- return err
- }
- return nil
- }
- func (c *client) setupGCPAuth(ctx context.Context, gcpAuth *esv1.VaultGCPAuth) error {
- // Priority order for GCP authentication methods:
- // 1. SecretRef: Service account key from Kubernetes secret (uses IAM auth method)
- // 2. WorkloadIdentity: GKE Workload Identity (uses IAM auth method)
- // 3. ServiceAccountRef: Pod's service account (uses GCE auth method)
- // 4. Default ADC: Application Default Credentials (uses GCE auth method)
- // First priority: Service account key from secret
- if gcpAuth.SecretRef != nil {
- return c.setupServiceAccountKeyAuth(ctx, gcpAuth)
- }
- // Second priority: Workload identity
- if gcpAuth.WorkloadIdentity != nil {
- return c.setupWorkloadIdentityAuth(ctx, gcpAuth)
- }
- // Third priority: Service account reference (for token creation)
- if gcpAuth.ServiceAccountRef != nil {
- return c.setupServiceAccountRefAuth(ctx, gcpAuth)
- }
- // Last resort: Default GCP authentication (ADC)
- return c.setupDefaultGCPAuth()
- }
- func (c *client) setupServiceAccountKeyAuth(ctx context.Context, gcpAuth *esv1.VaultGCPAuth) error {
- tokenSource, err := gcpsm.NewTokenSource(ctx, esv1.GCPSMAuth{
- SecretRef: gcpAuth.SecretRef,
- }, gcpAuth.ProjectID, c.storeKind, c.kube, c.namespace)
- if err != nil {
- return fmt.Errorf("failed to create token source from secret: %w", err)
- }
- token, err := tokenSource.Token()
- if err != nil {
- return fmt.Errorf("failed to retrieve token from secret: %w", err)
- }
- c.log.V(1).Info("Setting up GCP authentication using service account credentials from secret")
- return c.setGCPEnvironment(token.AccessToken)
- }
- func (c *client) setupWorkloadIdentityAuth(ctx context.Context, gcpAuth *esv1.VaultGCPAuth) error {
- tokenSource, err := gcpsm.NewTokenSource(ctx, esv1.GCPSMAuth{
- WorkloadIdentity: gcpAuth.WorkloadIdentity,
- }, gcpAuth.ProjectID, c.storeKind, c.kube, c.namespace)
- if err != nil {
- return fmt.Errorf("failed to create token source from workload identity: %w", err)
- }
- token, err := tokenSource.Token()
- if err != nil {
- return fmt.Errorf("failed to retrieve token from workload identity: %w", err)
- }
- c.log.V(1).Info("Setting up GCP authentication using workload identity")
- return c.setGCPEnvironment(token.AccessToken)
- }
- func (c *client) setupServiceAccountRefAuth(_ context.Context, _ *esv1.VaultGCPAuth) error {
- // When ServiceAccountRef is specified, we use the Kubernetes service account
- // The GCE auth method will automatically use the service account attached to the pod
- // This leverages GKE Workload Identity or service account key mounted in the pod
- c.log.V(1).Info("Setting up GCP authentication using service account reference with GCE auth method")
- // No explicit token setup needed - GCE auth method will use the pod's service account
- // This works with both Workload Identity and traditional service account keys
- return nil
- }
- func (c *client) setupDefaultGCPAuth() error {
- c.log.V(1).Info("Setting up default GCP authentication (ADC)")
- // Validate that ADC is available before proceeding
- ctx := context.Background()
- creds, err := google.FindDefaultCredentials(ctx)
- if err != nil {
- return fmt.Errorf("Application Default Credentials (ADC) not available: %w", err)
- }
- c.log.V(1).Info("ADC validation successful", "project_id", creds.ProjectID)
- // No explicit token setup needed - the Vault GCP auth method will use ADC automatically
- return nil
- }
- func (c *client) setGCPEnvironment(accessToken string) error {
- // The Vault GCP auth method will use this environment variable if set
- if err := c.setEnvVar(googleOAuthAccessTokenKey, accessToken); err != nil {
- return fmt.Errorf("failed to set GCP environment variable: %w", err)
- }
- return nil
- }
- func (c *client) setEnvVar(key, value string) error {
- if value == "" {
- return fmt.Errorf("empty value for environment variable %s", key)
- }
- if err := os.Setenv(key, value); err != nil {
- return fmt.Errorf("failed to set environment variable %s: %w", key, err)
- }
- c.log.V(1).Info("Set environment variable for GCP authentication", "key", key)
- return nil
- }
- func (c *client) getGCPAuthMountPathOrDefault(path string) string {
- if path != "" {
- return path
- }
- return defaultGCPAuthMountPath
- }
|