238 lines
8.7 KiB
Go
238 lines
8.7 KiB
Go
|
// 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
|
||
|
}
|