Added Authorization Bearer token support as per RFC6750 * appended Authorization header token parsing after X-Consul-Token * added test cases * updated website documentation to mention Authorization header * improve tests, improve Bearer parsing
This commit is contained in:
parent
89194ee16c
commit
8f7e87439a
|
@ -563,15 +563,36 @@ func (s *HTTPServer) parseDC(req *http.Request, dc *string) {
|
|||
}
|
||||
}
|
||||
|
||||
// parseTokenInternal is used to parse the ?token query param or the X-Consul-Token header and
|
||||
// optionally resolve proxy tokens to real ACL tokens. If no token is specified it will populate
|
||||
// parseTokenInternal is used to parse the ?token query param or the X-Consul-Token header or
|
||||
// Authorization Bearer token (RFC6750) and
|
||||
// optionally resolve proxy tokens to real ACL tokens. If the token is invalid or not specified it will populate
|
||||
// the token with the agents UserToken (acl_token in the consul configuration)
|
||||
// Parsing has the following priority: ?token, X-Consul-Token and last "Authorization: Bearer "
|
||||
func (s *HTTPServer) parseTokenInternal(req *http.Request, token *string, resolveProxyToken bool) {
|
||||
tok := ""
|
||||
if other := req.URL.Query().Get("token"); other != "" {
|
||||
tok = other
|
||||
} else if other := req.Header.Get("X-Consul-Token"); other != "" {
|
||||
tok = other
|
||||
} else if other := req.Header.Get("Authorization"); other != "" {
|
||||
// HTTP Authorization headers are in the format: <Scheme>[SPACE]<Value>
|
||||
// Ref. https://tools.ietf.org/html/rfc7236#section-3
|
||||
parts := strings.Split(other, " ")
|
||||
|
||||
// Authorization Header is invalid if containing 1 or 0 parts, e.g.:
|
||||
// "" || "<Scheme><Value>" || "<Scheme>" || "<Value>"
|
||||
if len(parts) > 1 {
|
||||
scheme := parts[0]
|
||||
// Everything after "<Scheme>" is "<Value>", trimmed
|
||||
value := strings.TrimSpace(strings.Join(parts[1:], " "))
|
||||
|
||||
// <Scheme> must be "Bearer"
|
||||
if scheme == "Bearer" {
|
||||
// Since Bearer tokens shouldnt contain spaces (rfc6750#section-2.1)
|
||||
// "value" is tokenized, only the first item is used
|
||||
tok = strings.TrimSpace(strings.Split(value, " ")[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if tok != "" {
|
||||
|
@ -589,13 +610,14 @@ func (s *HTTPServer) parseTokenInternal(req *http.Request, token *string, resolv
|
|||
*token = s.agent.tokens.UserToken()
|
||||
}
|
||||
|
||||
// parseToken is used to parse the ?token query param or the X-Consul-Token header and
|
||||
// resolve proxy tokens to real ACL tokens
|
||||
// parseToken is used to parse the ?token query param or the X-Consul-Token header or
|
||||
// Authorization Bearer token header (RFC6750) and resolve proxy tokens to real ACL tokens
|
||||
func (s *HTTPServer) parseToken(req *http.Request, token *string) {
|
||||
s.parseTokenInternal(req, token, true)
|
||||
}
|
||||
|
||||
// parseTokenWithoutResolvingProxyToken is used to parse the ?token query param or the X-Consul-Token header
|
||||
// or Authorization Bearer header token (RFC6750) and
|
||||
func (s *HTTPServer) parseTokenWithoutResolvingProxyToken(req *http.Request, token *string) {
|
||||
s.parseTokenInternal(req, token, false)
|
||||
}
|
||||
|
|
|
@ -736,6 +736,40 @@ func TestACLResolution(t *testing.T) {
|
|||
reqBothTokens, _ := http.NewRequest("GET", "/v1/catalog/nodes?token=baz", nil)
|
||||
reqBothTokens.Header.Add("X-Consul-Token", "zap")
|
||||
|
||||
// Request with Authorization Bearer token
|
||||
reqAuthBearerToken, _ := http.NewRequest("GET", "/v1/catalog/nodes", nil)
|
||||
reqAuthBearerToken.Header.Add("Authorization", "Bearer bearer-token")
|
||||
|
||||
// Request with invalid Authorization scheme
|
||||
reqAuthBearerInvalidScheme, _ := http.NewRequest("GET", "/v1/catalog/nodes", nil)
|
||||
reqAuthBearerInvalidScheme.Header.Add("Authorization", "Beer")
|
||||
|
||||
// Request with empty Authorization Bearer token
|
||||
reqAuthBearerTokenEmpty, _ := http.NewRequest("GET", "/v1/catalog/nodes", nil)
|
||||
reqAuthBearerTokenEmpty.Header.Add("Authorization", "Bearer")
|
||||
|
||||
// Request with empty Authorization Bearer token
|
||||
reqAuthBearerTokenInvalid, _ := http.NewRequest("GET", "/v1/catalog/nodes", nil)
|
||||
reqAuthBearerTokenInvalid.Header.Add("Authorization", "Bearertoken")
|
||||
|
||||
// Request with more than one space between Bearer and token
|
||||
reqAuthBearerTokenMultiSpaces, _ := http.NewRequest("GET", "/v1/catalog/nodes", nil)
|
||||
reqAuthBearerTokenMultiSpaces.Header.Add("Authorization", "Bearer bearer-token")
|
||||
|
||||
// Request with Authorization Bearer token containing spaces
|
||||
reqAuthBearerTokenSpaces, _ := http.NewRequest("GET", "/v1/catalog/nodes", nil)
|
||||
reqAuthBearerTokenSpaces.Header.Add("Authorization", "Bearer bearer-token "+
|
||||
" the rest is discarded ")
|
||||
|
||||
// Request with Authorization Bearer and querystring token
|
||||
reqAuthBearerAndQsToken, _ := http.NewRequest("GET", "/v1/catalog/nodes?token=qstoken", nil)
|
||||
reqAuthBearerAndQsToken.Header.Add("Authorization", "Bearer bearer-token")
|
||||
|
||||
// Request with Authorization Bearer and X-Consul-Token header token
|
||||
reqAuthBearerAndXToken, _ := http.NewRequest("GET", "/v1/catalog/nodes", nil)
|
||||
reqAuthBearerAndXToken.Header.Add("X-Consul-Token", "xtoken")
|
||||
reqAuthBearerAndXToken.Header.Add("Authorization", "Bearer bearer-token")
|
||||
|
||||
a := NewTestAgent(t.Name(), "")
|
||||
defer a.Shutdown()
|
||||
|
||||
|
@ -770,6 +804,58 @@ func TestACLResolution(t *testing.T) {
|
|||
if token != "baz" {
|
||||
t.Fatalf("bad: %s", token)
|
||||
}
|
||||
|
||||
//
|
||||
// Authorization Bearer token tests
|
||||
//
|
||||
|
||||
// Check if Authorization bearer token header is parsed correctly
|
||||
a.srv.parseToken(reqAuthBearerToken, &token)
|
||||
if token != "bearer-token" {
|
||||
t.Fatalf("bad: %s", token)
|
||||
}
|
||||
|
||||
// Check Authorization Bearer scheme invalid
|
||||
a.srv.parseToken(reqAuthBearerInvalidScheme, &token)
|
||||
if token != "agent" {
|
||||
t.Fatalf("bad: %s", token)
|
||||
}
|
||||
|
||||
// Check if Authorization Bearer token is empty
|
||||
a.srv.parseToken(reqAuthBearerTokenEmpty, &token)
|
||||
if token != "agent" {
|
||||
t.Fatalf("bad: %s", token)
|
||||
}
|
||||
|
||||
// Check if the Authorization Bearer token is invalid
|
||||
a.srv.parseToken(reqAuthBearerTokenInvalid, &token)
|
||||
if token != "agent" {
|
||||
t.Fatalf("bad: %s", token)
|
||||
}
|
||||
|
||||
// Check multi spaces between Authorization Bearer and token value
|
||||
a.srv.parseToken(reqAuthBearerTokenMultiSpaces, &token)
|
||||
if token != "bearer-token" {
|
||||
t.Fatalf("bad: %s", token)
|
||||
}
|
||||
|
||||
// Check if Authorization Bearer token with spaces is parsed correctly
|
||||
a.srv.parseToken(reqAuthBearerTokenSpaces, &token)
|
||||
if token != "bearer-token" {
|
||||
t.Fatalf("bad: %s", token)
|
||||
}
|
||||
|
||||
// Check if explicit token has precedence over Authorization bearer token
|
||||
a.srv.parseToken(reqAuthBearerAndQsToken, &token)
|
||||
if token != "qstoken" {
|
||||
t.Fatalf("bad: %s", token)
|
||||
}
|
||||
|
||||
// Check if X-Consul-Token has precedence over Authorization bearer token
|
||||
a.srv.parseToken(reqAuthBearerAndXToken, &token)
|
||||
if token != "xtoken" {
|
||||
t.Fatalf("bad: %s", token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnableWebUI(t *testing.T) {
|
||||
|
|
|
@ -21,9 +21,10 @@ All API routes are prefixed with `/v1/`. This documentation is only for the v1 A
|
|||
Several endpoints in Consul use or require ACL tokens to operate. An agent
|
||||
can be configured to use a default token in requests using the `acl_token`
|
||||
configuration option. However, the token can also be specified per-request
|
||||
by using the `X-Consul-Token` request header or the `token` query string
|
||||
parameter. The request header takes precedence over the default token, and
|
||||
the query string parameter takes precedence over everything.
|
||||
by using the `X-Consul-Token` request header or Bearer header in Authorization
|
||||
header or the `token` query string parameter. The request header takes
|
||||
precedence over the default token, and the query string parameter takes
|
||||
precedence over everything.
|
||||
|
||||
For more details about ACLs, please see the [ACL Guide](/docs/guides/acl.html).
|
||||
|
||||
|
|
|
@ -32,7 +32,8 @@ The type is either "client" (meaning the token cannot modify ACL rules) or "mana
|
|||
|
||||
The token ID is passed along with each RPC request to the servers. Consul's
|
||||
[HTTP endpoints](/api/index.html) can accept tokens via the `token`
|
||||
query string parameter, or the `X-Consul-Token` request header. Consul's
|
||||
query string parameter, or the `X-Consul-Token` request header, or Authorization Bearer
|
||||
token [RFC6750](https://tools.ietf.org/html/rfc6750). Consul's
|
||||
[CLI commands](/docs/commands/index.html) can accept tokens via the
|
||||
`token` argument, or the `CONSUL_HTTP_TOKEN` environment variable.
|
||||
|
||||
|
@ -612,9 +613,9 @@ On success, the token ID is returned:
|
|||
```
|
||||
|
||||
This token ID can then be passed into Consul's HTTP APIs via the `token`
|
||||
query string parameter, or the `X-Consul-Token` request header, or Consul's
|
||||
CLI commands via the `token` argument, or the `CONSUL_HTTP_TOKEN` environment
|
||||
variable.
|
||||
query string parameter, or the `X-Consul-Token` request header, or Authorization
|
||||
Bearer token header, or Consul's CLI commands via the `token` argument,
|
||||
or the `CONSUL_HTTP_TOKEN` environment variable.
|
||||
|
||||
#### Agent Rules
|
||||
|
||||
|
|
Loading…
Reference in New Issue