mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 19:47:54 +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/jwt"
|
||||||
"github.com/hashicorp/vault/command/agent/auth/kerberos"
|
"github.com/hashicorp/vault/command/agent/auth/kerberos"
|
||||||
"github.com/hashicorp/vault/command/agent/auth/kubernetes"
|
"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"
|
||||||
"github.com/hashicorp/vault/command/agent/cache/cacheboltdb"
|
"github.com/hashicorp/vault/command/agent/cache/cacheboltdb"
|
||||||
"github.com/hashicorp/vault/command/agent/cache/cachememdb"
|
"github.com/hashicorp/vault/command/agent/cache/cachememdb"
|
||||||
@@ -370,6 +371,8 @@ func (c *AgentCommand) Run(args []string) int {
|
|||||||
method, err = kubernetes.NewKubernetesAuthMethod(authConfig)
|
method, err = kubernetes.NewKubernetesAuthMethod(authConfig)
|
||||||
case "approle":
|
case "approle":
|
||||||
method, err = approle.NewApproleAuthMethod(authConfig)
|
method, err = approle.NewApproleAuthMethod(authConfig)
|
||||||
|
case "oci":
|
||||||
|
method, err = oci.NewOCIAuthMethod(authConfig, config.Vault.Address)
|
||||||
case "token_file":
|
case "token_file":
|
||||||
method, err = token_file.NewTokenFileAuthMethod(authConfig)
|
method, err = token_file.NewTokenFileAuthMethod(authConfig)
|
||||||
case "pcf": // Deprecated.
|
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",
|
"title": "Kubernetes",
|
||||||
"path": "agent/autoauth/methods/kubernetes"
|
"path": "agent/autoauth/methods/kubernetes"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Oracle Cloud Infrastructure",
|
||||||
|
"path": "agent/autoauth/methods/oci"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Token File",
|
"title": "Token File",
|
||||||
"path": "agent/autoauth/methods/token_file"
|
"path": "agent/autoauth/methods/token_file"
|
||||||
|
|||||||
Reference in New Issue
Block a user