Add support for token passed Authorization Bearer header (#5397)
* Support Authorization Bearer as token header * add requestAuth test * remove spew debug output in test * Add Authorization in CORS Allowed headers * use const where applicable * use less allocations in bearer token checking * address PR comments on tests and apply last commit * reorder error checking in a TestHandler_requestAuth
This commit is contained in:
parent
36c20e8e2d
commit
03fb39033f
|
@ -567,18 +567,38 @@ func respondStandby(core *vault.Core, w http.ResponseWriter, reqURL *url.URL) {
|
|||
w.WriteHeader(307)
|
||||
}
|
||||
|
||||
// getTokenFromReq parse headers of the incoming request to extract token if present
|
||||
// it accepts Authorization Bearer (RFC6750) and X-Vault-Token header
|
||||
func getTokenFromReq(r *http.Request) (string, error) {
|
||||
if token := r.Header.Get(consts.AuthHeaderName); token != "" {
|
||||
return token, nil
|
||||
}
|
||||
if v := r.Header.Get("Authorization"); v != "" {
|
||||
// Reference for Authorization header format: https://tools.ietf.org/html/rfc7236#section-3
|
||||
|
||||
// If string does not start by Bearer, or contains any space after it. It is a formatting error
|
||||
if !strings.HasPrefix(v, "Bearer ") || strings.LastIndexByte(v, ' ') > 7 {
|
||||
return "", fmt.Errorf("the Authorization header provided is wrongly formatted. Please use \"Bearer <token>\"")
|
||||
}
|
||||
return v[7:], nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// requestAuth adds the token to the logical.Request if it exists.
|
||||
func requestAuth(core *vault.Core, r *http.Request, req *logical.Request) (*logical.Request, error) {
|
||||
// Attach the header value if we have it
|
||||
if v := r.Header.Get(consts.AuthHeaderName); v != "" {
|
||||
req.ClientToken = v
|
||||
if token, err := getTokenFromReq(r); err != nil {
|
||||
return req, err
|
||||
} else if token != "" {
|
||||
req.ClientToken = token
|
||||
|
||||
// Also attach the accessor if we have it. This doesn't fail if it
|
||||
// doesn't exist because the request may be to an unauthenticated
|
||||
// endpoint/login endpoint where a bad current token doesn't matter, or
|
||||
// a token from a Vault version pre-accessors.
|
||||
te, err := core.LookupToken(r.Context(), v)
|
||||
if err != nil && strings.Count(v, ".") != 2 {
|
||||
te, err := core.LookupToken(r.Context(), token)
|
||||
if err != nil && strings.Count(token, ".") != 2 {
|
||||
return req, err
|
||||
}
|
||||
if err == nil && te != nil {
|
||||
|
|
|
@ -11,6 +11,8 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/hashicorp/vault/helper/consts"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
|
@ -170,7 +172,7 @@ func TestHandler_cors(t *testing.T) {
|
|||
"Access-Control-Allow-Origin": addr,
|
||||
"Access-Control-Allow-Headers": strings.Join(vault.StdAllowedHeaders, ","),
|
||||
"Access-Control-Max-Age": "300",
|
||||
"Vary": "Origin",
|
||||
"Vary": "Origin",
|
||||
}
|
||||
|
||||
for expHeader, expected := range expHeaders {
|
||||
|
@ -521,6 +523,125 @@ func TestHandler_error(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHandler_requestAuth(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
|
||||
rootCtx := namespace.RootContext(nil)
|
||||
te, err := core.LookupToken(rootCtx, token)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
rWithAuthorization, err := http.NewRequest("GET", "v1/test/path", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
rWithAuthorization.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
rWithVault, err := http.NewRequest("GET", "v1/test/path", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
rWithVault.Header.Set(consts.AuthHeaderName, token)
|
||||
|
||||
for _, r := range []*http.Request{rWithVault, rWithAuthorization} {
|
||||
req := logical.TestRequest(t, logical.ReadOperation, "test/path")
|
||||
r = r.WithContext(rootCtx)
|
||||
req, err = requestAuth(core, r, req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if req.ClientToken != token {
|
||||
t.Fatalf("client token should be filled with %s, got %s", token, req.ClientToken)
|
||||
}
|
||||
if req.TokenEntry() == nil {
|
||||
t.Fatal("token entry should not be nil")
|
||||
}
|
||||
if !reflect.DeepEqual(req.TokenEntry(), te) {
|
||||
t.Fatalf("token entry should be the same as the core")
|
||||
}
|
||||
if req.ClientTokenAccessor == "" {
|
||||
t.Fatal("token accessor should not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
rInvalidScheme, err := http.NewRequest("GET", "v1/test/path", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
rInvalidScheme.Header.Set("Authorization", "invalid_scheme something")
|
||||
req := logical.TestRequest(t, logical.ReadOperation, "test/path")
|
||||
|
||||
_, err = requestAuth(core, rInvalidScheme, req)
|
||||
if err == nil {
|
||||
t.Fatalf("expected an error, got none")
|
||||
}
|
||||
|
||||
rNothing, err := http.NewRequest("GET", "v1/test/path", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
req = logical.TestRequest(t, logical.ReadOperation, "test/path")
|
||||
|
||||
req, err = requestAuth(core, rNothing, req)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %s", err)
|
||||
}
|
||||
if req.ClientToken != "" {
|
||||
t.Fatalf("client token should not be filled, got %s", req.ClientToken)
|
||||
}
|
||||
|
||||
rFragmentedHeader, err := http.NewRequest("GET", "v1/test/path", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
rFragmentedHeader.Header.Set("Authorization", "Bearer something somewhat")
|
||||
req = logical.TestRequest(t, logical.ReadOperation, "test/path")
|
||||
|
||||
_, err = requestAuth(core, rFragmentedHeader, req)
|
||||
if err == nil {
|
||||
t.Fatalf("expected an error, got none")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestHandler_getTokenFromReq(t *testing.T) {
|
||||
r := http.Request{Header: http.Header{}}
|
||||
|
||||
if tok, err := getTokenFromReq(&r); err != nil {
|
||||
t.Fatalf("expected no error, got %s", err)
|
||||
} else if tok != "" {
|
||||
t.Fatalf("expected '' as result, got '%s'", tok)
|
||||
}
|
||||
|
||||
r.Header.Set("Authorization", "Bearer TOKEN NOT_GOOD_TOKEN")
|
||||
if tok, err := getTokenFromReq(&r); err == nil {
|
||||
t.Fatalf("expected an error, got none")
|
||||
} else if tok != "" {
|
||||
t.Fatalf("expected '' as result, got '%s'", tok)
|
||||
}
|
||||
|
||||
r.Header.Set(consts.AuthHeaderName, "NEWTOKEN")
|
||||
if tok, err := getTokenFromReq(&r); err != nil {
|
||||
t.Fatalf("expected no error, got %s", err)
|
||||
} else if tok == "TOKEN" {
|
||||
t.Fatalf("%s header should be prioritized", consts.AuthHeaderName)
|
||||
} else if tok != "NEWTOKEN" {
|
||||
t.Fatalf("expected 'NEWTOKEN' as result, got '%s'", tok)
|
||||
}
|
||||
|
||||
r.Header = http.Header{}
|
||||
r.Header.Set("Authorization", "Basic TOKEN")
|
||||
if tok, err := getTokenFromReq(&r); err == nil {
|
||||
t.Fatal("expected error, got none")
|
||||
} else if tok != "" {
|
||||
t.Fatalf("expected '' as result, got '%s'", tok)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestHandler_nonPrintableChars(t *testing.T) {
|
||||
testNonPrintable(t, false)
|
||||
testNonPrintable(t, true)
|
||||
|
|
|
@ -26,6 +26,7 @@ var StdAllowedHeaders = []string{
|
|||
"X-Vault-Wrap-Format",
|
||||
"X-Vault-Wrap-TTL",
|
||||
"X-Vault-Policy-Override",
|
||||
"Authorization",
|
||||
consts.AuthHeaderName,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue