mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-11-03 20:17:59 +00:00 
			
		
		
		
	Add Oracle Cloud auth to the Vault Agent (#19260)
* Add Oracle Cloud auth to the Vault Agent * Use ParseDurationSecond to parse credential_poll_interval * Use os.UserHomeDir()
This commit is contained in:
		
							
								
								
									
										3
									
								
								changelog/19260.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/19260.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
```release-note:feature
 | 
			
		||||
**agent/auto-auth:**: Add OCI (Oracle Cloud Infrastructure) auto-auth method
 | 
			
		||||
```
 | 
			
		||||
@@ -40,6 +40,7 @@ import (
 | 
			
		||||
	"github.com/hashicorp/vault/command/agent/auth/jwt"
 | 
			
		||||
	"github.com/hashicorp/vault/command/agent/auth/kerberos"
 | 
			
		||||
	"github.com/hashicorp/vault/command/agent/auth/kubernetes"
 | 
			
		||||
	"github.com/hashicorp/vault/command/agent/auth/oci"
 | 
			
		||||
	"github.com/hashicorp/vault/command/agent/cache"
 | 
			
		||||
	"github.com/hashicorp/vault/command/agent/cache/cacheboltdb"
 | 
			
		||||
	"github.com/hashicorp/vault/command/agent/cache/cachememdb"
 | 
			
		||||
@@ -370,6 +371,8 @@ func (c *AgentCommand) Run(args []string) int {
 | 
			
		||||
			method, err = kubernetes.NewKubernetesAuthMethod(authConfig)
 | 
			
		||||
		case "approle":
 | 
			
		||||
			method, err = approle.NewApproleAuthMethod(authConfig)
 | 
			
		||||
		case "oci":
 | 
			
		||||
			method, err = oci.NewOCIAuthMethod(authConfig, config.Vault.Address)
 | 
			
		||||
		case "token_file":
 | 
			
		||||
			method, err = token_file.NewTokenFileAuthMethod(authConfig)
 | 
			
		||||
		case "pcf": // Deprecated.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										262
									
								
								command/agent/auth/oci/oci.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								command/agent/auth/oci/oci.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,262 @@
 | 
			
		||||
package oci
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/hashicorp/go-secure-stdlib/parseutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/user"
 | 
			
		||||
	"path"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/hashicorp/go-hclog"
 | 
			
		||||
	"github.com/hashicorp/vault/api"
 | 
			
		||||
	"github.com/hashicorp/vault/command/agent/auth"
 | 
			
		||||
	"github.com/oracle/oci-go-sdk/common"
 | 
			
		||||
	ociAuth "github.com/oracle/oci-go-sdk/common/auth"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	typeAPIKey   = "apikey"
 | 
			
		||||
	typeInstance = "instance"
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
 | 
			
		||||
		IAM creds can be inferred from instance metadata or the container
 | 
			
		||||
		identity service, and those creds expire at varying intervals with
 | 
			
		||||
		new creds becoming available at likewise varying intervals. Let's
 | 
			
		||||
		default to polling once a minute so all changes can be picked up
 | 
			
		||||
		rather quickly. This is configurable, however.
 | 
			
		||||
 | 
			
		||||
	*/
 | 
			
		||||
	defaultCredCheckFreqSeconds = 60 * time.Second
 | 
			
		||||
 | 
			
		||||
	defaultConfigFileName    = "config"
 | 
			
		||||
	defaultConfigDirName     = ".oci"
 | 
			
		||||
	configFilePathEnvVarName = "OCI_CONFIG_FILE"
 | 
			
		||||
	secondaryConfigDirName   = ".oraclebmc"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewOCIAuthMethod(conf *auth.AuthConfig, vaultAddress string) (auth.AuthMethod, error) {
 | 
			
		||||
	if conf == nil {
 | 
			
		||||
		return nil, errors.New("empty config")
 | 
			
		||||
	}
 | 
			
		||||
	if conf.Config == nil {
 | 
			
		||||
		return nil, errors.New("empty config data")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a := &ociMethod{
 | 
			
		||||
		logger:       conf.Logger,
 | 
			
		||||
		vaultAddress: vaultAddress,
 | 
			
		||||
		mountPath:    conf.MountPath,
 | 
			
		||||
		credsFound:   make(chan struct{}),
 | 
			
		||||
		stopCh:       make(chan struct{}),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	typeRaw, ok := conf.Config["type"]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, errors.New("missing 'type' value")
 | 
			
		||||
	}
 | 
			
		||||
	authType, ok := typeRaw.(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, errors.New("could not convert 'type' config value to string")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	roleRaw, ok := conf.Config["role"]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, errors.New("missing 'role' value")
 | 
			
		||||
	}
 | 
			
		||||
	a.role, ok = roleRaw.(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, errors.New("could not convert 'role' config value to string")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check for an optional custom frequency at which we should poll for creds.
 | 
			
		||||
	credCheckFreqSec := defaultCredCheckFreqSeconds
 | 
			
		||||
	if checkFreqRaw, ok := conf.Config["credential_poll_interval"]; ok {
 | 
			
		||||
		checkFreq, err := parseutil.ParseDurationSecond(checkFreqRaw)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("could not parse credential_poll_interval: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		credCheckFreqSec = checkFreq
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	case a.role == "":
 | 
			
		||||
		return nil, errors.New("'role' value is empty")
 | 
			
		||||
	case authType == "":
 | 
			
		||||
		return nil, errors.New("'type' value is empty")
 | 
			
		||||
	case authType != typeAPIKey && authType != typeInstance:
 | 
			
		||||
		return nil, errors.New("'type' value is invalid")
 | 
			
		||||
	case authType == typeAPIKey:
 | 
			
		||||
		defaultConfigFile := getDefaultConfigFilePath()
 | 
			
		||||
		homeFolder := getHomeFolder()
 | 
			
		||||
		secondaryConfigFile := path.Join(homeFolder, secondaryConfigDirName, defaultConfigFileName)
 | 
			
		||||
 | 
			
		||||
		environmentProvider := common.ConfigurationProviderEnvironmentVariables("OCI", "")
 | 
			
		||||
		defaultFileProvider, _ := common.ConfigurationProviderFromFile(defaultConfigFile, "")
 | 
			
		||||
		secondaryFileProvider, _ := common.ConfigurationProviderFromFile(secondaryConfigFile, "")
 | 
			
		||||
 | 
			
		||||
		provider, _ := common.ComposingConfigurationProvider([]common.ConfigurationProvider{environmentProvider, defaultFileProvider, secondaryFileProvider})
 | 
			
		||||
		a.configurationProvider = provider
 | 
			
		||||
	case authType == typeInstance:
 | 
			
		||||
		configurationProvider, err := ociAuth.InstancePrincipalConfigurationProvider()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("failed to create instance principal configuration provider: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		a.configurationProvider = configurationProvider
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Do an initial population of the creds because we want to err right away if we can't
 | 
			
		||||
	// even get a first set.
 | 
			
		||||
	creds, err := a.configurationProvider.KeyID()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	a.lastCreds = creds
 | 
			
		||||
 | 
			
		||||
	go a.pollForCreds(credCheckFreqSec)
 | 
			
		||||
 | 
			
		||||
	return a, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ociMethod struct {
 | 
			
		||||
	logger       hclog.Logger
 | 
			
		||||
	vaultAddress string
 | 
			
		||||
	mountPath    string
 | 
			
		||||
 | 
			
		||||
	configurationProvider common.ConfigurationProvider
 | 
			
		||||
	role                  string
 | 
			
		||||
 | 
			
		||||
	// These are used to share the latest creds safely across goroutines.
 | 
			
		||||
	credLock  sync.Mutex
 | 
			
		||||
	lastCreds string
 | 
			
		||||
 | 
			
		||||
	// Notifies the outer environment that it should call Authenticate again.
 | 
			
		||||
	credsFound chan struct{}
 | 
			
		||||
 | 
			
		||||
	// Detects that the outer environment is closing.
 | 
			
		||||
	stopCh chan struct{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ociMethod) Authenticate(context.Context, *api.Client) (string, http.Header, map[string]interface{}, error) {
 | 
			
		||||
	a.credLock.Lock()
 | 
			
		||||
	defer a.credLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	a.logger.Trace("beginning authentication")
 | 
			
		||||
 | 
			
		||||
	requestPath := fmt.Sprintf("/v1/%s/login/%s", a.mountPath, a.role)
 | 
			
		||||
	requestURL := fmt.Sprintf("%s%s", a.vaultAddress, requestPath)
 | 
			
		||||
 | 
			
		||||
	request, err := http.NewRequest("GET", requestURL, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", nil, nil, fmt.Errorf("error creating authentication request: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	request.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
 | 
			
		||||
 | 
			
		||||
	signer := common.DefaultRequestSigner(a.configurationProvider)
 | 
			
		||||
 | 
			
		||||
	err = signer.Sign(request)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", nil, nil, fmt.Errorf("error signing authentication request: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	parsedVaultAddress, err := url.Parse(a.vaultAddress)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", nil, nil, fmt.Errorf("unable to parse vault address: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	request.Header.Set("Host", parsedVaultAddress.Host)
 | 
			
		||||
	request.Header.Set("(request-target)", fmt.Sprintf("%s %s", "get", requestPath))
 | 
			
		||||
 | 
			
		||||
	data := map[string]interface{}{
 | 
			
		||||
		"request_headers": request.Header,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s/login/%s", a.mountPath, a.role), nil, data, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ociMethod) NewCreds() chan struct{} {
 | 
			
		||||
	return a.credsFound
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ociMethod) CredSuccess() {}
 | 
			
		||||
 | 
			
		||||
func (a *ociMethod) Shutdown() {
 | 
			
		||||
	close(a.credsFound)
 | 
			
		||||
	close(a.stopCh)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ociMethod) pollForCreds(frequency time.Duration) {
 | 
			
		||||
	ticker := time.NewTicker(frequency)
 | 
			
		||||
	defer ticker.Stop()
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-a.stopCh:
 | 
			
		||||
			a.logger.Trace("shutdown triggered, stopping OCI auth handler")
 | 
			
		||||
			return
 | 
			
		||||
		case <-ticker.C:
 | 
			
		||||
			if err := a.checkCreds(); err != nil {
 | 
			
		||||
				a.logger.Warn("unable to retrieve current creds, retaining last creds", "error", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ociMethod) checkCreds() error {
 | 
			
		||||
	a.credLock.Lock()
 | 
			
		||||
	defer a.credLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	a.logger.Trace("checking for new credentials")
 | 
			
		||||
	currentCreds, err := a.configurationProvider.KeyID()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	// These will always have different pointers regardless of whether their
 | 
			
		||||
	// values are identical, hence the use of DeepEqual.
 | 
			
		||||
	if currentCreds == a.lastCreds {
 | 
			
		||||
		a.logger.Trace("credentials are unchanged")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	a.lastCreds = currentCreds
 | 
			
		||||
	a.logger.Trace("new credentials detected, triggering Authenticate")
 | 
			
		||||
	a.credsFound <- struct{}{}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getHomeFolder() string {
 | 
			
		||||
	current, e := user.Current()
 | 
			
		||||
	if e != nil {
 | 
			
		||||
		// Give up and try to return something sensible
 | 
			
		||||
		home, err := os.UserHomeDir()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return ""
 | 
			
		||||
		}
 | 
			
		||||
		return home
 | 
			
		||||
	}
 | 
			
		||||
	return current.HomeDir
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getDefaultConfigFilePath() string {
 | 
			
		||||
	homeFolder := getHomeFolder()
 | 
			
		||||
	defaultConfigFile := path.Join(homeFolder, defaultConfigDirName, defaultConfigFileName)
 | 
			
		||||
	if _, err := os.Stat(defaultConfigFile); err == nil {
 | 
			
		||||
		return defaultConfigFile
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Read configuration file path from OCI_CONFIG_FILE env var
 | 
			
		||||
	fallbackConfigFile, existed := os.LookupEnv(configFilePathEnvVarName)
 | 
			
		||||
	if !existed {
 | 
			
		||||
		return defaultConfigFile
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := os.Stat(fallbackConfigFile); os.IsNotExist(err) {
 | 
			
		||||
		return defaultConfigFile
 | 
			
		||||
	}
 | 
			
		||||
	return fallbackConfigFile
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										228
									
								
								command/agent/oci_end_to_end_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								command/agent/oci_end_to_end_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,228 @@
 | 
			
		||||
package agent
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	hclog "github.com/hashicorp/go-hclog"
 | 
			
		||||
	vaultoci "github.com/hashicorp/vault-plugin-auth-oci"
 | 
			
		||||
	"github.com/hashicorp/vault/api"
 | 
			
		||||
	"github.com/hashicorp/vault/command/agent/auth"
 | 
			
		||||
	agentoci "github.com/hashicorp/vault/command/agent/auth/oci"
 | 
			
		||||
	"github.com/hashicorp/vault/command/agent/sink"
 | 
			
		||||
	"github.com/hashicorp/vault/command/agent/sink/file"
 | 
			
		||||
	"github.com/hashicorp/vault/helper/testhelpers"
 | 
			
		||||
	vaulthttp "github.com/hashicorp/vault/http"
 | 
			
		||||
	"github.com/hashicorp/vault/sdk/helper/logging"
 | 
			
		||||
	"github.com/hashicorp/vault/sdk/logical"
 | 
			
		||||
	"github.com/hashicorp/vault/vault"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	envVarOCITestTenancyOCID    = "OCI_TEST_TENANCY_OCID"
 | 
			
		||||
	envVarOCITestUserOCID       = "OCI_TEST_USER_OCID"
 | 
			
		||||
	envVarOCITestFingerprint    = "OCI_TEST_FINGERPRINT"
 | 
			
		||||
	envVarOCITestPrivateKeyPath = "OCI_TEST_PRIVATE_KEY_PATH"
 | 
			
		||||
	envVAROCITestOCIDList       = "OCI_TEST_OCID_LIST"
 | 
			
		||||
 | 
			
		||||
	// The OCI SDK doesn't export its standard env vars so they're captured here.
 | 
			
		||||
	// These are used for the duration of the test to make sure the agent is able to
 | 
			
		||||
	// pick up creds from the env.
 | 
			
		||||
	//
 | 
			
		||||
	// To run this test, do not set these. Only the above ones need to be set.
 | 
			
		||||
	envVarOCITenancyOCID    = "OCI_tenancy_ocid"
 | 
			
		||||
	envVarOCIUserOCID       = "OCI_user_ocid"
 | 
			
		||||
	envVarOCIFingerprint    = "OCI_fingerprint"
 | 
			
		||||
	envVarOCIPrivateKeyPath = "OCI_private_key_path"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestOCIEndToEnd(t *testing.T) {
 | 
			
		||||
	if !runAcceptanceTests {
 | 
			
		||||
		t.SkipNow()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Ensure each cred is populated.
 | 
			
		||||
	credNames := []string{
 | 
			
		||||
		envVarOCITestTenancyOCID,
 | 
			
		||||
		envVarOCITestUserOCID,
 | 
			
		||||
		envVarOCITestFingerprint,
 | 
			
		||||
		envVarOCITestPrivateKeyPath,
 | 
			
		||||
		envVAROCITestOCIDList,
 | 
			
		||||
	}
 | 
			
		||||
	testhelpers.SkipUnlessEnvVarsSet(t, credNames)
 | 
			
		||||
 | 
			
		||||
	logger := logging.NewVaultLogger(hclog.Trace)
 | 
			
		||||
	coreConfig := &vault.CoreConfig{
 | 
			
		||||
		Logger: logger,
 | 
			
		||||
		CredentialBackends: map[string]logical.Factory{
 | 
			
		||||
			"oci": vaultoci.Factory,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
 | 
			
		||||
		HandlerFunc: vaulthttp.Handler,
 | 
			
		||||
	})
 | 
			
		||||
	cluster.Start()
 | 
			
		||||
	defer cluster.Cleanup()
 | 
			
		||||
 | 
			
		||||
	vault.TestWaitActive(t, cluster.Cores[0].Core)
 | 
			
		||||
	client := cluster.Cores[0].Client
 | 
			
		||||
 | 
			
		||||
	// Setup Vault
 | 
			
		||||
	if err := client.Sys().EnableAuthWithOptions("oci", &api.EnableAuthOptions{
 | 
			
		||||
		Type: "oci",
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := client.Logical().Write("auth/oci/config", map[string]interface{}{
 | 
			
		||||
		"home_tenancy_id": os.Getenv(envVarOCITestTenancyOCID),
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := client.Logical().Write("auth/oci/role/test", map[string]interface{}{
 | 
			
		||||
		"ocid_list": os.Getenv(envVAROCITestOCIDList),
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
 | 
			
		||||
 | 
			
		||||
	// We're going to feed oci auth creds via env variables.
 | 
			
		||||
	if err := setOCIEnvCreds(); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err := unsetOCIEnvCreds(); err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	vaultAddr := "http://" + cluster.Cores[0].Listeners[0].Addr().String()
 | 
			
		||||
 | 
			
		||||
	am, err := agentoci.NewOCIAuthMethod(&auth.AuthConfig{
 | 
			
		||||
		Logger:    logger.Named("auth.oci"),
 | 
			
		||||
		MountPath: "auth/oci",
 | 
			
		||||
		Config: map[string]interface{}{
 | 
			
		||||
			"type": "apikey",
 | 
			
		||||
			"role": "test",
 | 
			
		||||
		},
 | 
			
		||||
	}, vaultAddr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ahConfig := &auth.AuthHandlerConfig{
 | 
			
		||||
		Logger: logger.Named("auth.handler"),
 | 
			
		||||
		Client: client,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ah := auth.NewAuthHandler(ahConfig)
 | 
			
		||||
	errCh := make(chan error)
 | 
			
		||||
	go func() {
 | 
			
		||||
		errCh <- ah.Run(ctx, am)
 | 
			
		||||
	}()
 | 
			
		||||
	defer func() {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
		case err := <-errCh:
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	tmpFile, err := ioutil.TempFile("", "auth.tokensink.test.")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	tokenSinkFileName := tmpFile.Name()
 | 
			
		||||
	tmpFile.Close()
 | 
			
		||||
	os.Remove(tokenSinkFileName)
 | 
			
		||||
	t.Logf("output: %s", tokenSinkFileName)
 | 
			
		||||
 | 
			
		||||
	config := &sink.SinkConfig{
 | 
			
		||||
		Logger: logger.Named("sink.file"),
 | 
			
		||||
		Config: map[string]interface{}{
 | 
			
		||||
			"path": tokenSinkFileName,
 | 
			
		||||
		},
 | 
			
		||||
		WrapTTL: 10 * time.Second,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fs, err := file.NewFileSink(config)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	config.Sink = fs
 | 
			
		||||
 | 
			
		||||
	ss := sink.NewSinkServer(&sink.SinkServerConfig{
 | 
			
		||||
		Logger: logger.Named("sink.server"),
 | 
			
		||||
		Client: client,
 | 
			
		||||
	})
 | 
			
		||||
	go func() {
 | 
			
		||||
		errCh <- ss.Run(ctx, ah.OutputCh, []*sink.SinkConfig{config})
 | 
			
		||||
	}()
 | 
			
		||||
	defer func() {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
		case err := <-errCh:
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// This has to be after the other defers so it happens first. It allows
 | 
			
		||||
	// successful test runs to immediately cancel all of the runner goroutines
 | 
			
		||||
	// and unblock any of the blocking defer calls by the runner's DoneCh that
 | 
			
		||||
	// comes before this and avoid successful tests from taking the entire
 | 
			
		||||
	// timeout duration.
 | 
			
		||||
	defer cancel()
 | 
			
		||||
 | 
			
		||||
	if stat, err := os.Lstat(tokenSinkFileName); err == nil {
 | 
			
		||||
		t.Fatalf("expected err but got %s", stat)
 | 
			
		||||
	} else if !os.IsNotExist(err) {
 | 
			
		||||
		t.Fatal("expected notexist err")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Wait 2 seconds for the env variables to be detected and an auth to be generated.
 | 
			
		||||
	time.Sleep(time.Second * 2)
 | 
			
		||||
 | 
			
		||||
	token, err := readToken(tokenSinkFileName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if token.Token == "" {
 | 
			
		||||
		t.Fatal("expected token but didn't receive it")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setOCIEnvCreds() error {
 | 
			
		||||
	if err := os.Setenv(envVarOCITenancyOCID, os.Getenv(envVarOCITestTenancyOCID)); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := os.Setenv(envVarOCIUserOCID, os.Getenv(envVarOCITestUserOCID)); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := os.Setenv(envVarOCIFingerprint, os.Getenv(envVarOCITestFingerprint)); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return os.Setenv(envVarOCIPrivateKeyPath, os.Getenv(envVarOCITestPrivateKeyPath))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func unsetOCIEnvCreds() error {
 | 
			
		||||
	if err := os.Unsetenv(envVarOCITenancyOCID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := os.Unsetenv(envVarOCIUserOCID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := os.Unsetenv(envVarOCIFingerprint); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return os.Unsetenv(envVarOCIPrivateKeyPath)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								website/content/docs/agent/autoauth/methods/oci.mdx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								website/content/docs/agent/autoauth/methods/oci.mdx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
---
 | 
			
		||||
layout: docs
 | 
			
		||||
page_title: Vault Agent Auto-Auth OCI (Oracle Cloud Infrastructure) Method
 | 
			
		||||
description: OCI (Oracle Cloud Infrastructure) Method for Vault Agent Auto-Auth
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Vault Agent Auto-Auth OCI (Oracle Cloud Infrastructure) Method
 | 
			
		||||
 | 
			
		||||
The `oci` method performs authentication against the [OCI Auth
 | 
			
		||||
method](/vault/docs/auth/oci).
 | 
			
		||||
 | 
			
		||||
## Credentials
 | 
			
		||||
 | 
			
		||||
The method use to authenticate is set using the `type` parameter. Valid values are `apikey` to authenticate using
 | 
			
		||||
API Key credentials and `instance` for Instance Principal credentials.
 | 
			
		||||
 | 
			
		||||
If `apikey` is used, the Vault agent will use the first credential it can successfully obtain in the following order:
 | 
			
		||||
 | 
			
		||||
1. Environment variables:
 | 
			
		||||
  - `OCI_tenancy_ocid`
 | 
			
		||||
  - `OCI_user_ocid`
 | 
			
		||||
  - `OCI_fingerprint`
 | 
			
		||||
  - `OCI_private_key_path`
 | 
			
		||||
2. Configuration file in `$HOME/.oci/config`
 | 
			
		||||
3. Path to configuration file defined in the `OCI_CONFIG_FILE` environment variable
 | 
			
		||||
4. Configuration file in `$HOME/.obmcs/config`
 | 
			
		||||
 | 
			
		||||
Wherever possible, we recommend using instance principal for credentials. These are rotated automatically by OCI
 | 
			
		||||
and require no effort on your part to provision, making instance principal the most secure of the three methods. If
 | 
			
		||||
using instance principal _and_ a custom `credential_poll_interval`, be sure the frequency is set to a value that is less
 | 
			
		||||
than OCI's rotation frequency. This is currently documented as
 | 
			
		||||
[multiple times a day](https://docs.oracle.com/en-us/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm#faq),
 | 
			
		||||
but from experience, credentials are rotated every 10 to 15 minutes.
 | 
			
		||||
 | 
			
		||||
## Configuration
 | 
			
		||||
 | 
			
		||||
### General
 | 
			
		||||
 | 
			
		||||
- `type` `(string: required)` - The type of authentication to use. Valid values are `apikey` and `instance`.
 | 
			
		||||
 | 
			
		||||
- `role` `(string: required)` - The role to authenticate against on Vault.
 | 
			
		||||
 | 
			
		||||
- `credential_poll_interval` `(duration: "60s", optional)` - In seconds, how frequently the Vault agent should check for new credentials.
 | 
			
		||||
@@ -928,6 +928,10 @@
 | 
			
		||||
                "title": "Kubernetes",
 | 
			
		||||
                "path": "agent/autoauth/methods/kubernetes"
 | 
			
		||||
              },
 | 
			
		||||
              {
 | 
			
		||||
                "title": "Oracle Cloud Infrastructure",
 | 
			
		||||
                "path": "agent/autoauth/methods/oci"
 | 
			
		||||
              },
 | 
			
		||||
              {
 | 
			
		||||
                "title": "Token File",
 | 
			
		||||
                "path": "agent/autoauth/methods/token_file"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user