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:
Martin 2018-10-01 19:33:21 +02:00 committed by Brian Kassouf
parent 36c20e8e2d
commit 03fb39033f
3 changed files with 147 additions and 5 deletions

View File

@ -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 {

View File

@ -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)

View File

@ -26,6 +26,7 @@ var StdAllowedHeaders = []string{
"X-Vault-Wrap-Format",
"X-Vault-Wrap-TTL",
"X-Vault-Policy-Override",
"Authorization",
consts.AuthHeaderName,
}