Change auth helper interface to api.Secret. (#3263)

This allows us to properly handle wrapped responses.

Fixes #3217
This commit is contained in:
Jeff Mitchell 2017-08-31 16:57:00 -04:00 committed by GitHub
parent 9a159a597f
commit 223c4fc325
8 changed files with 262 additions and 49 deletions

View File

@ -69,7 +69,7 @@ func GenerateLoginData(accessKey, secretKey, sessionToken, headerValue string) (
return loginData, nil
}
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, error) {
mount, ok := m["mount"]
if !ok {
mount = "aws"
@ -87,23 +87,23 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
loginData, err := GenerateLoginData(m["aws_access_key_id"], m["aws_secret_access_key"], m["aws_security_token"], headerValue)
if err != nil {
return "", err
return nil, err
}
if loginData == nil {
return "", fmt.Errorf("got nil response from GenerateLoginData")
return nil, fmt.Errorf("got nil response from GenerateLoginData")
}
loginData["role"] = role
path := fmt.Sprintf("auth/%s/login", mount)
secret, err := c.Logical().Write(path, loginData)
if err != nil {
return "", err
return nil, err
}
if secret == nil {
return "", fmt.Errorf("empty response from credential provider")
return nil, fmt.Errorf("empty response from credential provider")
}
return secret.Auth.ClientToken, nil
return secret, nil
}
func (h *CLIHandler) Help() string {

View File

@ -10,13 +10,13 @@ import (
type CLIHandler struct{}
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, error) {
var data struct {
Mount string `mapstructure:"mount"`
Name string `mapstructure:"name"`
}
if err := mapstructure.WeakDecode(m, &data); err != nil {
return "", err
return nil, err
}
if data.Mount == "" {
@ -29,13 +29,13 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
path := fmt.Sprintf("auth/%s/login", data.Mount)
secret, err := c.Logical().Write(path, options)
if err != nil {
return "", err
return nil, err
}
if secret == nil {
return "", fmt.Errorf("empty response from credential provider")
return nil, fmt.Errorf("empty response from credential provider")
}
return secret.Auth.ClientToken, nil
return secret, nil
}
func (h *CLIHandler) Help() string {

View File

@ -10,7 +10,7 @@ import (
type CLIHandler struct{}
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, error) {
mount, ok := m["mount"]
if !ok {
mount = "github"
@ -19,7 +19,7 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
token, ok := m["token"]
if !ok {
if token = os.Getenv("VAULT_AUTH_GITHUB_TOKEN"); token == "" {
return "", fmt.Errorf("GitHub token should be provided either as 'value' for 'token' key,\nor via an env var VAULT_AUTH_GITHUB_TOKEN")
return nil, fmt.Errorf("GitHub token should be provided either as 'value' for 'token' key,\nor via an env var VAULT_AUTH_GITHUB_TOKEN")
}
}
@ -28,13 +28,13 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
"token": token,
})
if err != nil {
return "", err
return nil, err
}
if secret == nil {
return "", fmt.Errorf("empty response from credential provider")
return nil, fmt.Errorf("empty response from credential provider")
}
return secret.Auth.ClientToken, nil
return secret, nil
}
func (h *CLIHandler) Help() string {

View File

@ -11,7 +11,7 @@ import (
type CLIHandler struct{}
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, error) {
mount, ok := m["mount"]
if !ok {
mount = "ldap"
@ -21,7 +21,7 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
if !ok {
username = usernameFromEnv()
if username == "" {
return "", fmt.Errorf("'username' not supplied and neither 'LOGNAME' nor 'USER' env vars set")
return nil, fmt.Errorf("'username' not supplied and neither 'LOGNAME' nor 'USER' env vars set")
}
}
password, ok := m["password"]
@ -31,7 +31,7 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
password, err = pwd.Read(os.Stdin)
fmt.Println()
if err != nil {
return "", err
return nil, err
}
}
@ -51,13 +51,13 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
path := fmt.Sprintf("auth/%s/login/%s", mount, username)
secret, err := c.Logical().Write(path, data)
if err != nil {
return "", err
return nil, err
}
if secret == nil {
return "", fmt.Errorf("empty response from credential provider")
return nil, fmt.Errorf("empty response from credential provider")
}
return secret.Auth.ClientToken, nil
return secret, nil
}
func (h *CLIHandler) Help() string {

View File

@ -13,7 +13,7 @@ import (
type CLIHandler struct{}
// Auth cli method
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, error) {
mount, ok := m["mount"]
if !ok {
mount = "okta"
@ -21,7 +21,7 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
username, ok := m["username"]
if !ok {
return "", fmt.Errorf("'username' var must be set")
return nil, fmt.Errorf("'username' var must be set")
}
password, ok := m["password"]
if !ok {
@ -30,7 +30,7 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
password, err = pwd.Read(os.Stdin)
fmt.Println()
if err != nil {
return "", err
return nil, err
}
}
@ -41,13 +41,13 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
path := fmt.Sprintf("auth/%s/login/%s", mount, username)
secret, err := c.Logical().Write(path, data)
if err != nil {
return "", err
return nil, err
}
if secret == nil {
return "", fmt.Errorf("empty response from credential provider")
return nil, fmt.Errorf("empty response from credential provider")
}
return secret.Auth.ClientToken, nil
return secret, nil
}
// Help method for okta cli

View File

@ -14,7 +14,7 @@ type CLIHandler struct {
DefaultMount string
}
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, error) {
var data struct {
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
@ -23,18 +23,18 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
Passcode string `mapstructure:"passcode"`
}
if err := mapstructure.WeakDecode(m, &data); err != nil {
return "", err
return nil, err
}
if data.Username == "" {
return "", fmt.Errorf("'username' must be specified")
return nil, fmt.Errorf("'username' must be specified")
}
if data.Password == "" {
fmt.Printf("Password (will be hidden): ")
password, err := pwd.Read(os.Stdin)
fmt.Println()
if err != nil {
return "", err
return nil, err
}
data.Password = password
}
@ -55,13 +55,13 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
path := fmt.Sprintf("auth/%s/login/%s", data.Mount, data.Username)
secret, err := c.Logical().Write(path, options)
if err != nil {
return "", err
return nil, err
}
if secret == nil {
return "", fmt.Errorf("empty response from credential provider")
return nil, fmt.Errorf("empty response from credential provider")
}
return secret.Auth.ClientToken, nil
return secret, nil
}
func (h *CLIHandler) Help() string {

View File

@ -22,7 +22,7 @@ import (
// AuthHandler is the interface that any auth handlers must implement
// to enable auth via the CLI.
type AuthHandler interface {
Auth(*api.Client, map[string]string) (string, error)
Auth(*api.Client, map[string]string) (*api.Secret, error)
Help() string
}
@ -167,11 +167,52 @@ func (c *AuthCommand) Run(args []string) int {
}
// Authenticate
token, err := handler.Auth(client, vars)
secret, err := handler.Auth(client, vars)
if err != nil {
c.Ui.Error(err.Error())
return 1
}
if secret == nil {
c.Ui.Error("Empty response from auth helper")
return 1
}
// If we had requested a wrapped token, we want to unset that request
// before performing further functions
client.SetWrappingLookupFunc(func(string, string) string {
return ""
})
CHECK_TOKEN:
var token string
switch {
case secret == nil:
c.Ui.Error("Empty response from auth helper")
return 1
case secret.Auth != nil:
token = secret.Auth.ClientToken
case secret.WrapInfo != nil:
if secret.WrapInfo.WrappedAccessor == "" {
c.Ui.Error("Got a wrapped response from Vault but wrapped reply does not seem to contain a token")
return 1
}
if tokenOnly {
c.Ui.Output(secret.WrapInfo.Token)
return 0
}
if noStore {
return OutputSecret(c.Ui, "table", secret)
}
client.SetToken(secret.WrapInfo.Token)
secret, err = client.Logical().Unwrap("")
goto CHECK_TOKEN
default:
c.Ui.Error("No auth or wrapping info in auth helper response")
return 1
}
// Cache the previous token so that it can be restored if authentication fails
var previousToken string
@ -230,6 +271,9 @@ func (c *AuthCommand) Run(args []string) int {
}
return 1
}
client.SetWrappingLookupFunc(func(string, string) string {
return ""
})
// If in no-store mode it won't have read the token from a token-helper (or
// will read an old one) so set it explicitly
@ -238,7 +282,7 @@ func (c *AuthCommand) Run(args []string) int {
}
// Verify the token
secret, err := client.Auth().Token().LookupSelf()
secret, err = client.Auth().Token().LookupSelf()
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error validating token: %s", err))
@ -274,8 +318,8 @@ func (c *AuthCommand) Run(args []string) int {
// Get the policies we have
policiesRaw, ok := secret.Data["policies"]
if !ok {
policiesRaw = []string{"unknown"}
if !ok || policiesRaw == nil {
policiesRaw = []interface{}{"unknown"}
}
var policies []string
for _, v := range policiesRaw.([]interface{}) {
@ -307,6 +351,9 @@ func (c *AuthCommand) getMethods() (map[string]*api.AuthMount, error) {
if err != nil {
return nil, err
}
client.SetWrappingLookupFunc(func(string, string) string {
return ""
})
auth, err := client.Sys().ListAuth()
if err != nil {
@ -387,15 +434,21 @@ Usage: vault auth [options] [auth-information]
The value of the "-path" flag is supplied to auth providers as the "mount"
option in the payload to specify the mount point.
If response wrapping is used (via -wrap-ttl), the returned token will be
automatically unwrapped unless:
* -token-only is used, in which case the wrapping token will be output
* -no-store is used, in which case the details of the wrapping token
will be printed
General Options:
` + meta.GeneralOptionsUsage() + `
Auth Options:
-method=name Outputs help for the authentication method with the given
name for the remote server. If this authentication method
is not available, exit with code 1.
-method=name Use the method given here, which is a type of backend, not
the path. If this authentication method is not available,
exit with code 1.
-method-help If set, the help for the selected method will be shown.
@ -422,7 +475,7 @@ type tokenAuthHandler struct {
Token string
}
func (h *tokenAuthHandler) Auth(*api.Client, map[string]string) (string, error) {
func (h *tokenAuthHandler) Auth(*api.Client, map[string]string) (*api.Secret, error) {
token := h.Token
if token == "" {
var err error
@ -432,7 +485,7 @@ func (h *tokenAuthHandler) Auth(*api.Client, map[string]string) (string, error)
token, err = password.Read(os.Stdin)
fmt.Printf("\n")
if err != nil {
return "", fmt.Errorf(
return nil, fmt.Errorf(
"Error attempting to ask for token. The raw error message\n"+
"is shown below, but the most common reason for this error is\n"+
"that you attempted to pipe a value into auth. If you want to\n"+
@ -442,12 +495,16 @@ func (h *tokenAuthHandler) Auth(*api.Client, map[string]string) (string, error)
}
if token == "" {
return "", fmt.Errorf(
return nil, fmt.Errorf(
"A token must be passed to auth. Please view the help\n" +
"for more information.")
}
return token, nil
return &api.Secret{
Auth: &api.SecretAuth{
ClientToken: token,
},
}, nil
}
func (h *tokenAuthHandler) Help() string {

View File

@ -9,6 +9,9 @@ import (
"strings"
"testing"
credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/meta"
@ -84,6 +87,155 @@ func TestAuth_token(t *testing.T) {
}
}
func TestAuth_wrapping(t *testing.T) {
baseConfig := &vault.CoreConfig{
CredentialBackends: map[string]logical.Factory{
"userpass": credUserpass.Factory,
},
}
cluster := vault.NewTestCluster(t, baseConfig, &vault.TestClusterOptions{
HandlerFunc: http.Handler,
BaseListenAddress: "127.0.0.1:8200",
})
cluster.Start()
defer cluster.Cleanup()
testAuthInit(t)
client := cluster.Cores[0].Client
err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
Type: "userpass",
})
if err != nil {
t.Fatal(err)
}
_, err = client.Logical().Write("auth/userpass/users/foo", map[string]interface{}{
"password": "bar",
"policies": "zip,zap",
})
if err != nil {
t.Fatal(err)
}
ui := new(cli.MockUi)
c := &AuthCommand{
Meta: meta.Meta{
Ui: ui,
TokenHelper: DefaultTokenHelper,
},
Handlers: map[string]AuthHandler{
"userpass": &credUserpass.CLIHandler{DefaultMount: "userpass"},
},
}
args := []string{
"-address",
"https://127.0.0.1:8200",
"-tls-skip-verify",
"-method",
"userpass",
"username=foo",
"password=bar",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Test again with wrapping
ui = new(cli.MockUi)
c = &AuthCommand{
Meta: meta.Meta{
Ui: ui,
TokenHelper: DefaultTokenHelper,
},
Handlers: map[string]AuthHandler{
"userpass": &credUserpass.CLIHandler{DefaultMount: "userpass"},
},
}
args = []string{
"-address",
"https://127.0.0.1:8200",
"-tls-skip-verify",
"-wrap-ttl",
"5m",
"-method",
"userpass",
"username=foo",
"password=bar",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Test again with no-store
ui = new(cli.MockUi)
c = &AuthCommand{
Meta: meta.Meta{
Ui: ui,
TokenHelper: DefaultTokenHelper,
},
Handlers: map[string]AuthHandler{
"userpass": &credUserpass.CLIHandler{DefaultMount: "userpass"},
},
}
args = []string{
"-address",
"https://127.0.0.1:8200",
"-tls-skip-verify",
"-wrap-ttl",
"5m",
"-no-store",
"-method",
"userpass",
"username=foo",
"password=bar",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Test again with wrapping and token-only
ui = new(cli.MockUi)
c = &AuthCommand{
Meta: meta.Meta{
Ui: ui,
TokenHelper: DefaultTokenHelper,
},
Handlers: map[string]AuthHandler{
"userpass": &credUserpass.CLIHandler{DefaultMount: "userpass"},
},
}
args = []string{
"-address",
"https://127.0.0.1:8200",
"-tls-skip-verify",
"-wrap-ttl",
"5m",
"-token-only",
"-method",
"userpass",
"username=foo",
"password=bar",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
token := strings.TrimSpace(ui.OutputWriter.String())
if token == "" {
t.Fatal("expected to find token in output")
}
secret, err := client.Logical().Unwrap(token)
if err != nil {
t.Fatal(err)
}
if secret.Auth.ClientToken == "" {
t.Fatal("no client token found")
}
}
func TestAuth_token_nostore(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := http.TestServer(t, core)
@ -237,8 +389,12 @@ func testAuthInit(t *testing.T) {
type testAuthHandler struct{}
func (h *testAuthHandler) Auth(c *api.Client, m map[string]string) (string, error) {
return m["foo"], nil
func (h *testAuthHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, error) {
return &api.Secret{
Auth: &api.SecretAuth{
ClientToken: m["foo"],
},
}, nil
}
func (h *testAuthHandler) Help() string { return "" }