diff --git a/vaultfs/auth.go b/vaultfs/auth.go index 9e65c23f..ec73b90b 100644 --- a/vaultfs/auth.go +++ b/vaultfs/auth.go @@ -39,7 +39,7 @@ type withAPIAuthMethoder interface { // an optional interface that auth methods may implement to override the regular // token revocation type authLogouter interface { - Logout(client *api.Client) + Logout(ctx context.Context, client *api.Client) } // AuthMethod is an authentication method that vaultfs can use to acquire a diff --git a/vaultfs/vault.go b/vaultfs/vault.go index 5afee6fa..7707041f 100644 --- a/vaultfs/vault.go +++ b/vaultfs/vault.go @@ -328,7 +328,7 @@ func (f *vaultFile) Close() error { // the token auth method manages its own logout, to avoid revoking the // token, which shouldn't be managed here if lauth, ok := f.auth.(authLogouter); ok { - lauth.Logout(f.client.Client) + lauth.Logout(f.ctx, f.client.Client) } else { revokeToken(f.ctx, f.client.Client) } @@ -387,17 +387,16 @@ func (f *vaultFile) Stat() (fs.FileInfo, error) { resp, isV2, err := f.request(http.MethodGet) rerr := &api.ResponseError{} - if errors.As(err, &rerr) { - // if it's a 404 it might be a directory - let's try to LIST it instead - if rerr.StatusCode != http.StatusNotFound { - return nil, &fs.PathError{ - Op: "stat", Path: f.name, - Err: vaultFSError(err), - } + if errors.As(err, &rerr) && rerr.StatusCode != http.StatusNotFound { + return nil, &fs.PathError{ + Op: "stat", Path: f.name, + Err: vaultFSError(err), } } else if err != nil { - _, err = f.list() - if err != nil { + // if it's a 404 it might be a directory - let's try to LIST it instead + _, lerr := f.list() + if lerr != nil { + // return the original error, not the LIST error return nil, &fs.PathError{Op: "stat", Path: f.name, Err: err} } diff --git a/vaultfs/vaultauth/auth.go b/vaultfs/vaultauth/auth.go index 5ef4cf4c..aec2b4e4 100644 --- a/vaultfs/vaultauth/auth.go +++ b/vaultfs/vaultauth/auth.go @@ -60,3 +60,54 @@ func vaultFSError(err error) error { return err } + +// CompositeAuthMethod returns an AuthMethod that will try each of the given +// methods in order, until one succeeds. +func CompositeAuthMethod(methods ...api.AuthMethod) api.AuthMethod { + return &compositeAuthMethod{methods: methods} +} + +type compositeAuthMethod struct { + chosen api.AuthMethod + methods []api.AuthMethod +} + +func (m *compositeAuthMethod) Login(ctx context.Context, client *api.Client) (secret *api.Secret, err error) { + if m.chosen == nil { + for _, auth := range m.methods { + if auth == nil { + continue + } + + secret, err = auth.Login(ctx, client) + if err == nil { + m.chosen = auth + + break + } + } + } + + if m.chosen == nil { + return nil, fmt.Errorf("unable to authenticate with vault by any configured method. Last error was: %w", err) + } + + return secret, nil +} + +func (m *compositeAuthMethod) Logout(ctx context.Context, client *api.Client) { + if m.chosen == nil { + return + } + + // some auth methods (like the token method) manage their own logout, to + // avoid revoking the token, which shouldn't be managed here + if lauth, ok := m.chosen.(interface { + Logout(ctx context.Context, client *api.Client) + }); ok { + lauth.Logout(ctx, client) + } else { + _, _ = client.Logical().WriteWithContext(ctx, "auth/token/revoke-self", nil) + client.ClearToken() + } +} diff --git a/vaultfs/vaultauth/env.go b/vaultfs/vaultauth/env.go index c54780ef..a60ced44 100644 --- a/vaultfs/vaultauth/env.go +++ b/vaultfs/vaultauth/env.go @@ -1,8 +1,6 @@ package vaultauth import ( - "context" - "fmt" "os" "github.com/hashicorp/vault/api" @@ -41,43 +39,12 @@ import ( // to be heavily depended upon. It is recommended that you use the auth methods // directly, and configure them with the appropriate options. func EnvAuthMethod() api.AuthMethod { - return &envAuthMethod{ - // sorted in order of precedence - methods: []api.AuthMethod{ - envAppRoleAdapter(), - envGitHubAdapter(), - envUserPassAdapter(), - NewTokenAuth(""), - }, - } -} - -type envAuthMethod struct { - chosen api.AuthMethod - methods []api.AuthMethod -} - -func (m *envAuthMethod) Login(ctx context.Context, client *api.Client) (secret *api.Secret, err error) { - if m.chosen == nil { - for _, auth := range m.methods { - if auth == nil { - continue - } - - secret, err = auth.Login(ctx, client) - if err == nil { - m.chosen = auth - - break - } - } - } - - if m.chosen == nil { - return nil, fmt.Errorf("unable to authenticate with vault by any configured method. Last error was: %w", err) - } - - return secret, nil + return CompositeAuthMethod( + envAppRoleAdapter(), + envGitHubAdapter(), + envUserPassAdapter(), + NewTokenAuth(""), + ) } // envAppRoleAdapter builds an AppRoleAuth from environment variables, for use diff --git a/vaultfs/vaultauth/env_test.go b/vaultfs/vaultauth/env_test.go index 1f6d299a..642461ba 100644 --- a/vaultfs/vaultauth/env_test.go +++ b/vaultfs/vaultauth/env_test.go @@ -23,7 +23,7 @@ func TestEnvAuthLogin(t *testing.T) { s, err := m.Login(ctx, v) require.NoError(t, err) assert.Equal(t, "foo", s.Auth.ClientToken) - assert.NotNil(t, m.(*envAuthMethod).chosen) + assert.NotNil(t, m.(*compositeAuthMethod).chosen) } func fakeVaultServer(t *testing.T) *api.Client { diff --git a/vaultfs/vaultauth/token.go b/vaultfs/vaultauth/token.go index f7d8b0f0..f3e35852 100644 --- a/vaultfs/vaultauth/token.go +++ b/vaultfs/vaultauth/token.go @@ -58,7 +58,7 @@ func (m *tokenAuthMethod) Login(_ context.Context, _ *api.Client) (*api.Secret, // Logout implements the vaultfs.authLogouter interface because we need to keep // the token unmanaged. -func (m *tokenAuthMethod) Logout(client *api.Client) { +func (m *tokenAuthMethod) Logout(_ context.Context, client *api.Client) { // just clear the client's token, nothing else needs to be done here client.ClearToken() }