| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package agentproxyshared |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "os" |
| "path/filepath" |
| "strings" |
| |
| log "github.com/hashicorp/go-hclog" |
| "github.com/hashicorp/vault/command/agentproxyshared/auth" |
| "github.com/hashicorp/vault/command/agentproxyshared/auth/alicloud" |
| "github.com/hashicorp/vault/command/agentproxyshared/auth/approle" |
| "github.com/hashicorp/vault/command/agentproxyshared/auth/aws" |
| "github.com/hashicorp/vault/command/agentproxyshared/auth/azure" |
| "github.com/hashicorp/vault/command/agentproxyshared/auth/cert" |
| "github.com/hashicorp/vault/command/agentproxyshared/auth/cf" |
| "github.com/hashicorp/vault/command/agentproxyshared/auth/gcp" |
| "github.com/hashicorp/vault/command/agentproxyshared/auth/jwt" |
| "github.com/hashicorp/vault/command/agentproxyshared/auth/kerberos" |
| "github.com/hashicorp/vault/command/agentproxyshared/auth/kubernetes" |
| "github.com/hashicorp/vault/command/agentproxyshared/auth/oci" |
| token_file "github.com/hashicorp/vault/command/agentproxyshared/auth/token-file" |
| "github.com/hashicorp/vault/command/agentproxyshared/cache" |
| "github.com/hashicorp/vault/command/agentproxyshared/cache/cacheboltdb" |
| "github.com/hashicorp/vault/command/agentproxyshared/cache/cachememdb" |
| "github.com/hashicorp/vault/command/agentproxyshared/cache/keymanager" |
| ) |
| |
| // GetAutoAuthMethodFromConfig Calls the appropriate NewAutoAuthMethod function, initializing |
| // the auto-auth method, based on the auto-auth method type. Returns an error if one happens or |
| // the method type is invalid. |
| func GetAutoAuthMethodFromConfig(autoAuthMethodType string, authConfig *auth.AuthConfig, vaultAddress string) (auth.AuthMethod, error) { |
| switch autoAuthMethodType { |
| case "alicloud": |
| return alicloud.NewAliCloudAuthMethod(authConfig) |
| case "aws": |
| return aws.NewAWSAuthMethod(authConfig) |
| case "azure": |
| return azure.NewAzureAuthMethod(authConfig) |
| case "cert": |
| return cert.NewCertAuthMethod(authConfig) |
| case "cf": |
| return cf.NewCFAuthMethod(authConfig) |
| case "gcp": |
| return gcp.NewGCPAuthMethod(authConfig) |
| case "jwt": |
| return jwt.NewJWTAuthMethod(authConfig) |
| case "kerberos": |
| return kerberos.NewKerberosAuthMethod(authConfig) |
| case "kubernetes": |
| return kubernetes.NewKubernetesAuthMethod(authConfig) |
| case "approle": |
| return approle.NewApproleAuthMethod(authConfig) |
| case "oci": |
| return oci.NewOCIAuthMethod(authConfig, vaultAddress) |
| case "token_file": |
| return token_file.NewTokenFileAuthMethod(authConfig) |
| case "pcf": // Deprecated. |
| return cf.NewCFAuthMethod(authConfig) |
| default: |
| return nil, errors.New(fmt.Sprintf("unknown auth method %q", autoAuthMethodType)) |
| } |
| } |
| |
| // PersistConfig contains configuration needed for persistent caching |
| type PersistConfig struct { |
| Type string |
| Path string `hcl:"path"` |
| KeepAfterImport bool `hcl:"keep_after_import"` |
| ExitOnErr bool `hcl:"exit_on_err"` |
| ServiceAccountTokenFile string `hcl:"service_account_token_file"` |
| } |
| |
| // AddPersistentStorageToLeaseCache adds persistence to a lease cache, based on a given PersistConfig |
| // Returns a close function to be deferred and the old token, if found, or an error |
| func AddPersistentStorageToLeaseCache(ctx context.Context, leaseCache *cache.LeaseCache, persistConfig *PersistConfig, logger log.Logger) (func() error, string, error) { |
| if persistConfig == nil { |
| return nil, "", errors.New("persist config was nil") |
| } |
| |
| if persistConfig.Path == "" { |
| return nil, "", errors.New("must specify persistent cache path") |
| } |
| |
| // Set AAD based on key protection type |
| var aad string |
| var err error |
| switch persistConfig.Type { |
| case "kubernetes": |
| aad, err = getServiceAccountJWT(persistConfig.ServiceAccountTokenFile) |
| if err != nil { |
| tokenFileName := persistConfig.ServiceAccountTokenFile |
| if len(tokenFileName) == 0 { |
| tokenFileName = "/var/run/secrets/kubernetes.io/serviceaccount/token" |
| } |
| return nil, "", fmt.Errorf("failed to read service account token from %s: %w", tokenFileName, err) |
| } |
| default: |
| return nil, "", fmt.Errorf("persistent key protection type %q not supported", persistConfig.Type) |
| } |
| |
| // Check if bolt file exists already |
| dbFileExists, err := cacheboltdb.DBFileExists(persistConfig.Path) |
| if err != nil { |
| return nil, "", fmt.Errorf("failed to check if bolt file exists at path %s: %w", persistConfig.Path, err) |
| } |
| if dbFileExists { |
| // Open the bolt file, but wait to setup Encryption |
| ps, err := cacheboltdb.NewBoltStorage(&cacheboltdb.BoltStorageConfig{ |
| Path: persistConfig.Path, |
| Logger: logger.Named("cacheboltdb"), |
| }) |
| if err != nil { |
| return nil, "", fmt.Errorf("error opening persistent cache %v", err) |
| } |
| |
| // Get the token from bolt for retrieving the encryption key, |
| // then setup encryption so that restore is possible |
| token, err := ps.GetRetrievalToken() |
| if err != nil { |
| return nil, "", fmt.Errorf("error getting retrieval token from persistent cache: %w", err) |
| } |
| |
| if err := ps.Close(); err != nil { |
| return nil, "", fmt.Errorf("failed to close persistent cache file after getting retrieval token: %w", err) |
| } |
| |
| km, err := keymanager.NewPassthroughKeyManager(ctx, token) |
| if err != nil { |
| return nil, "", fmt.Errorf("failed to configure persistence encryption for cache: %w", err) |
| } |
| |
| // Open the bolt file with the wrapper provided |
| ps, err = cacheboltdb.NewBoltStorage(&cacheboltdb.BoltStorageConfig{ |
| Path: persistConfig.Path, |
| Logger: logger.Named("cacheboltdb"), |
| Wrapper: km.Wrapper(), |
| AAD: aad, |
| }) |
| if err != nil { |
| return nil, "", fmt.Errorf("error opening persistent cache with wrapper: %w", err) |
| } |
| |
| // Restore anything in the persistent cache to the memory cache |
| if err := leaseCache.Restore(ctx, ps); err != nil { |
| logger.Error(fmt.Sprintf("error restoring in-memory cache from persisted file: %v", err)) |
| if persistConfig.ExitOnErr { |
| return nil, "", fmt.Errorf("exiting with error as exit_on_err is set to true") |
| } |
| } |
| logger.Info("loaded memcache from persistent storage") |
| |
| // Check for previous auto-auth token |
| oldTokenBytes, err := ps.GetAutoAuthToken(ctx) |
| if err != nil { |
| logger.Error(fmt.Sprintf("error in fetching previous auto-auth token: %v", err)) |
| if persistConfig.ExitOnErr { |
| return nil, "", fmt.Errorf("exiting with error as exit_on_err is set to true") |
| } |
| } |
| var previousToken string |
| if len(oldTokenBytes) > 0 { |
| oldToken, err := cachememdb.Deserialize(oldTokenBytes) |
| if err != nil { |
| logger.Error(fmt.Sprintf("error in deserializing previous auto-auth token cache entryn: %v", err)) |
| if persistConfig.ExitOnErr { |
| return nil, "", fmt.Errorf("exiting with error as exit_on_err is set to true") |
| } |
| } |
| previousToken = oldToken.Token |
| } |
| |
| // If keep_after_import true, set persistent storage layer in |
| // leaseCache, else remove db file |
| if persistConfig.KeepAfterImport { |
| leaseCache.SetPersistentStorage(ps) |
| return ps.Close, previousToken, nil |
| } else { |
| if err := ps.Close(); err != nil { |
| logger.Warn(fmt.Sprintf("failed to close persistent cache file: %s", err)) |
| } |
| dbFile := filepath.Join(persistConfig.Path, cacheboltdb.DatabaseFileName) |
| if err := os.Remove(dbFile); err != nil { |
| logger.Error(fmt.Sprintf("failed to remove persistent storage file %s: %v", dbFile, err)) |
| if persistConfig.ExitOnErr { |
| return nil, "", fmt.Errorf("exiting with error as exit_on_err is set to true") |
| } |
| } |
| return nil, previousToken, nil |
| } |
| } else { |
| km, err := keymanager.NewPassthroughKeyManager(ctx, nil) |
| if err != nil { |
| return nil, "", fmt.Errorf("failed to configure persistence encryption for cache: %w", err) |
| } |
| ps, err := cacheboltdb.NewBoltStorage(&cacheboltdb.BoltStorageConfig{ |
| Path: persistConfig.Path, |
| Logger: logger.Named("cacheboltdb"), |
| Wrapper: km.Wrapper(), |
| AAD: aad, |
| }) |
| if err != nil { |
| return nil, "", fmt.Errorf("error creating persistent cache: %w", err) |
| } |
| logger.Info("configured persistent storage", "path", persistConfig.Path) |
| |
| // Stash the key material in bolt |
| token, err := km.RetrievalToken(ctx) |
| if err != nil { |
| return nil, "", fmt.Errorf("error getting persistence key: %w", err) |
| } |
| if err := ps.StoreRetrievalToken(token); err != nil { |
| return nil, "", fmt.Errorf("error setting key in persistent cache: %w", err) |
| } |
| |
| leaseCache.SetPersistentStorage(ps) |
| return ps.Close, "", nil |
| } |
| } |
| |
| // getServiceAccountJWT attempts to read the service account JWT from the specified token file path. |
| // Defaults to using the Kubernetes default service account file path if token file path is empty. |
| func getServiceAccountJWT(tokenFile string) (string, error) { |
| if len(tokenFile) == 0 { |
| tokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" |
| } |
| token, err := os.ReadFile(tokenFile) |
| if err != nil { |
| return "", err |
| } |
| return strings.TrimSpace(string(token)), nil |
| } |