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
|
// parseTokenInternal is used to parse the ?token query param or the X-Consul-Token header or
|
||||||
// optionally resolve proxy tokens to real ACL tokens. If no token is specified it will populate
|
// 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)
|
// 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) {
|
func (s *HTTPServer) parseTokenInternal(req *http.Request, token *string, resolveProxyToken bool) {
|
||||||
tok := ""
|
tok := ""
|
||||||
if other := req.URL.Query().Get("token"); other != "" {
|
if other := req.URL.Query().Get("token"); other != "" {
|
||||||
tok = other
|
tok = other
|
||||||
} else if other := req.Header.Get("X-Consul-Token"); other != "" {
|
} else if other := req.Header.Get("X-Consul-Token"); other != "" {
|
||||||
tok = 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 != "" {
|
if tok != "" {
|
||||||
|
@ -589,13 +610,14 @@ func (s *HTTPServer) parseTokenInternal(req *http.Request, token *string, resolv
|
||||||
*token = s.agent.tokens.UserToken()
|
*token = s.agent.tokens.UserToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseToken is used to parse the ?token query param or the X-Consul-Token header and
|
// parseToken is used to parse the ?token query param or the X-Consul-Token header or
|
||||||
// resolve proxy tokens to real ACL tokens
|
// Authorization Bearer token header (RFC6750) and resolve proxy tokens to real ACL tokens
|
||||||
func (s *HTTPServer) parseToken(req *http.Request, token *string) {
|
func (s *HTTPServer) parseToken(req *http.Request, token *string) {
|
||||||
s.parseTokenInternal(req, token, true)
|
s.parseTokenInternal(req, token, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseTokenWithoutResolvingProxyToken is used to parse the ?token query param or the X-Consul-Token header
|
// 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) {
|
func (s *HTTPServer) parseTokenWithoutResolvingProxyToken(req *http.Request, token *string) {
|
||||||
s.parseTokenInternal(req, token, false)
|
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, _ := http.NewRequest("GET", "/v1/catalog/nodes?token=baz", nil)
|
||||||
reqBothTokens.Header.Add("X-Consul-Token", "zap")
|
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(), "")
|
a := NewTestAgent(t.Name(), "")
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
|
|
||||||
|
@ -770,6 +804,58 @@ func TestACLResolution(t *testing.T) {
|
||||||
if token != "baz" {
|
if token != "baz" {
|
||||||
t.Fatalf("bad: %s", token)
|
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) {
|
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
|
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`
|
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
|
configuration option. However, the token can also be specified per-request
|
||||||
by using the `X-Consul-Token` request header or the `token` query string
|
by using the `X-Consul-Token` request header or Bearer header in Authorization
|
||||||
parameter. The request header takes precedence over the default token, and
|
header or the `token` query string parameter. The request header takes
|
||||||
the query string parameter takes precedence over everything.
|
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).
|
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
|
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`
|
[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
|
[CLI commands](/docs/commands/index.html) can accept tokens via the
|
||||||
`token` argument, or the `CONSUL_HTTP_TOKEN` environment variable.
|
`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`
|
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
|
query string parameter, or the `X-Consul-Token` request header, or Authorization
|
||||||
CLI commands via the `token` argument, or the `CONSUL_HTTP_TOKEN` environment
|
Bearer token header, or Consul's CLI commands via the `token` argument,
|
||||||
variable.
|
or the `CONSUL_HTTP_TOKEN` environment variable.
|
||||||
|
|
||||||
#### Agent Rules
|
#### Agent Rules
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue