Merge pull request #1776 from influxdata/feature/authorization-influx
Update influx Authorization Headers for write and query pathpull/10616/head
commit
040e72b78d
|
@ -7,6 +7,7 @@
|
|||
1. [#1751](https://github.com/influxdata/chronograf/pull/1751): Fix typo that may have affected PagerDuty node creation in Kapacitor
|
||||
1. [#1756](https://github.com/influxdata/chronograf/pull/1756): Prevent 'auto' GROUP BY as option in Kapacitor rule builder when applying a function to a field
|
||||
1. [#1773](https://github.com/influxdata/chronograf/pull/1773): Prevent clipped buttons in Rule Builder, Data Explorer, and Configuration pages
|
||||
1. [#1776](https://github.com/influxdata/chronograf/pull/1776): Fix JWT for the write path
|
||||
|
||||
### Features
|
||||
1. [#1717](https://github.com/influxdata/chronograf/pull/1717): View server generated TICKscripts
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
package influx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
// Authorizer adds optional authorization header to request
|
||||
type Authorizer interface {
|
||||
// Set may manipulate the request by adding the Authorization header
|
||||
Set(req *http.Request) error
|
||||
}
|
||||
|
||||
// NoAuthorization does not add any authorization headers
|
||||
type NoAuthorization struct{}
|
||||
|
||||
// Set does not add authorization
|
||||
func (n *NoAuthorization) Set(req *http.Request) error { return nil }
|
||||
|
||||
// DefaultAuthorization creates either a shared JWT builder, basic auth or Noop
|
||||
func DefaultAuthorization(src *chronograf.Source) Authorizer {
|
||||
// Optionally, add the shared secret JWT token creation
|
||||
if src.Username != "" && src.SharedSecret != "" {
|
||||
return &BearerJWT{
|
||||
Username: src.Username,
|
||||
SharedSecret: src.SharedSecret,
|
||||
}
|
||||
} else if src.Username != "" && src.Password != "" {
|
||||
return &BasicAuth{
|
||||
Username: src.Username,
|
||||
Password: src.Password,
|
||||
}
|
||||
}
|
||||
return &NoAuthorization{}
|
||||
}
|
||||
|
||||
// BasicAuth adds Authorization: Basic to the request header
|
||||
type BasicAuth struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// Set adds the basic auth headers to the request
|
||||
func (b *BasicAuth) Set(r *http.Request) error {
|
||||
r.SetBasicAuth(b.Username, b.Password)
|
||||
return nil
|
||||
}
|
||||
|
||||
// BearerJWT is the default Bearer for InfluxDB
|
||||
type BearerJWT struct {
|
||||
Username string
|
||||
SharedSecret string
|
||||
}
|
||||
|
||||
// Set adds an Authorization Bearer to the request if has a shared secret
|
||||
func (b *BearerJWT) Set(r *http.Request) error {
|
||||
if b.SharedSecret != "" && b.Username != "" {
|
||||
token, err := b.Token(b.Username)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create token")
|
||||
}
|
||||
r.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Token returns the expected InfluxDB JWT signed with the sharedSecret
|
||||
func (b *BearerJWT) Token(username string) (string, error) {
|
||||
return JWT(username, b.SharedSecret, time.Now)
|
||||
}
|
||||
|
||||
// Now returns the current time
|
||||
type Now func() time.Time
|
||||
|
||||
// JWT returns a token string accepted by InfluxDB using the sharedSecret as an Authorization: Bearer header
|
||||
func JWT(username, sharedSecret string, now Now) (string, error) {
|
||||
token := &jwt.Token{
|
||||
Header: map[string]interface{}{
|
||||
"typ": "JWT",
|
||||
"alg": jwt.SigningMethodHS512.Alg(),
|
||||
},
|
||||
Claims: jwt.MapClaims{
|
||||
"username": username,
|
||||
"exp": now().Add(time.Minute).Unix(),
|
||||
},
|
||||
Method: jwt.SigningMethodHS512,
|
||||
}
|
||||
return token.SignedString([]byte(sharedSecret))
|
||||
}
|
|
@ -28,7 +28,7 @@ var (
|
|||
// Client is a device for retrieving time series data from an InfluxDB instance
|
||||
type Client struct {
|
||||
URL *url.URL
|
||||
Bearer Bearer
|
||||
Authorizer Authorizer
|
||||
InsecureSkipVerify bool
|
||||
Logger chronograf.Logger
|
||||
}
|
||||
|
@ -72,13 +72,11 @@ func (c *Client) query(u *url.URL, q chronograf.Query) (chronograf.Response, err
|
|||
params.Set("epoch", "ms") // TODO(timraymond): set this based on analysis
|
||||
req.URL.RawQuery = params.Encode()
|
||||
|
||||
if c.Bearer != nil && u.User != nil {
|
||||
token, err := c.Bearer.Token(u.User.Username())
|
||||
if err != nil {
|
||||
logs.Error("Error creating token", err)
|
||||
return nil, fmt.Errorf("Unable to create token")
|
||||
if c.Authorizer != nil {
|
||||
if err := c.Authorizer.Set(req); err != nil {
|
||||
logs.Error("Error setting authorization header ", err)
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
|
||||
hc := &http.Client{}
|
||||
|
@ -156,22 +154,13 @@ func (c *Client) Connect(ctx context.Context, src *chronograf.Source) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.User = url.UserPassword(src.Username, src.Password)
|
||||
c.Authorizer = DefaultAuthorization(src)
|
||||
// Only allow acceptance of all certs if the scheme is https AND the user opted into to the setting.
|
||||
if u.Scheme == "https" && src.InsecureSkipVerify {
|
||||
c.InsecureSkipVerify = src.InsecureSkipVerify
|
||||
}
|
||||
c.URL = u
|
||||
|
||||
// Optionally, add the shared secret JWT token creation
|
||||
if src.Username != "" && src.SharedSecret != "" {
|
||||
c.Bearer = &BearerJWT{
|
||||
src.SharedSecret,
|
||||
}
|
||||
} else {
|
||||
// Clear out the bearer if not needed
|
||||
c.Bearer = nil
|
||||
}
|
||||
c.URL = u
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -62,38 +62,52 @@ func Test_Influx_MakesRequestsToQueryEndpoint(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type MockBearer struct {
|
||||
type MockAuthorization struct {
|
||||
Bearer string
|
||||
Error error
|
||||
}
|
||||
|
||||
func (m *MockBearer) Token(username string) (string, error) {
|
||||
return m.Bearer, m.Error
|
||||
func (m *MockAuthorization) Set(req *http.Request) error {
|
||||
return m.Error
|
||||
}
|
||||
func Test_Influx_AuthorizationBearer(t *testing.T) {
|
||||
t.Parallel()
|
||||
want := "Bearer ********"
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.Write([]byte(`{}`))
|
||||
got := r.Header.Get("Authorization")
|
||||
if got != want {
|
||||
t.Errorf("Test_Influx_AuthorizationBearer got %s want %s", got, want)
|
||||
auth := r.Header.Get("Authorization")
|
||||
tokenString := strings.Split(auth, " ")[1]
|
||||
token, err := gojwt.Parse(tokenString, func(token *gojwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*gojwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte("42"), nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Invalid token %v", err)
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(gojwt.MapClaims); ok && token.Valid {
|
||||
got := claims["username"]
|
||||
want := "AzureDiamond"
|
||||
if got != want {
|
||||
t.Errorf("Test_Influx_AuthorizationBearer got %s want %s", got, want)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Errorf("Invalid token %v", token)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
bearer := &MockBearer{
|
||||
Bearer: "********",
|
||||
src := &chronograf.Source{
|
||||
Username: "AzureDiamond",
|
||||
URL: ts.URL,
|
||||
SharedSecret: "42",
|
||||
}
|
||||
|
||||
u, _ := url.Parse(ts.URL)
|
||||
u.User = url.UserPassword("AzureDiamond", "hunter2")
|
||||
series := &influx.Client{
|
||||
URL: u,
|
||||
Bearer: bearer,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
}
|
||||
series.Connect(context.Background(), src)
|
||||
|
||||
query := chronograf.Query{
|
||||
Command: "show databases",
|
||||
|
@ -161,16 +175,16 @@ func Test_Influx_AuthorizationBearerCtx(t *testing.T) {
|
|||
|
||||
func Test_Influx_AuthorizationBearerFailure(t *testing.T) {
|
||||
t.Parallel()
|
||||
bearer := &MockBearer{
|
||||
bearer := &MockAuthorization{
|
||||
Error: fmt.Errorf("cracked1337"),
|
||||
}
|
||||
|
||||
u, _ := url.Parse("http://haxored.net")
|
||||
u.User = url.UserPassword("AzureDiamond", "hunter2")
|
||||
series := &influx.Client{
|
||||
URL: u,
|
||||
Bearer: bearer,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
URL: u,
|
||||
Authorizer: bearer,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
}
|
||||
|
||||
query := chronograf.Query{
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
package influx
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
// Bearer generates tokens for Authorization: Bearer
|
||||
type Bearer interface {
|
||||
Token(username string) (string, error)
|
||||
}
|
||||
|
||||
// BearerJWT is the default Bearer for InfluxDB
|
||||
type BearerJWT struct {
|
||||
SharedSecret string
|
||||
}
|
||||
|
||||
// Token returns the expected InfluxDB JWT signed with the sharedSecret
|
||||
func (b *BearerJWT) Token(username string) (string, error) {
|
||||
return JWT(username, b.SharedSecret, time.Now)
|
||||
}
|
||||
|
||||
// Now returns the current time
|
||||
type Now func() time.Time
|
||||
|
||||
// JWT returns a token string accepted by InfluxDB using the sharedSecret as an Authorization: Bearer header
|
||||
func JWT(username, sharedSecret string, now Now) (string, error) {
|
||||
token := &jwt.Token{
|
||||
Header: map[string]interface{}{
|
||||
"typ": "JWT",
|
||||
"alg": jwt.SigningMethodHS512.Alg(),
|
||||
},
|
||||
Claims: jwt.MapClaims{
|
||||
"username": username,
|
||||
"exp": now().Add(time.Minute).Unix(),
|
||||
},
|
||||
Method: jwt.SigningMethodHS512,
|
||||
}
|
||||
return token.SignedString([]byte(sharedSecret))
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"net/url"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/influx"
|
||||
)
|
||||
|
||||
// ValidInfluxRequest checks if queries specify a command.
|
||||
|
@ -106,10 +107,9 @@ func (h *Service) Write(w http.ResponseWriter, r *http.Request) {
|
|||
req.Host = u.Host
|
||||
req.URL = u
|
||||
// Because we are acting as a proxy, influxdb needs to have the
|
||||
// basic auth information set as a header directly
|
||||
if src.Username != "" && src.Password != "" {
|
||||
req.SetBasicAuth(src.Username, src.Password)
|
||||
}
|
||||
// basic auth or bearer token information set as a header directly
|
||||
auth := influx.DefaultAuthorization(&src)
|
||||
auth.Set(req)
|
||||
}
|
||||
proxy := &httputil.ReverseProxy{
|
||||
Director: director,
|
||||
|
|
Loading…
Reference in New Issue