diff --git a/CHANGELOG.md b/CHANGELOG.md index 06928df7c19..6604481e8ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Features 1. [15151](https://github.com/influxdata/influxdb/pull/15151): Add jsonweb package for future JWT support +1. [15152](https://github.com/influxdata/influxdb/pull/15152): Add JWT support to http auth middleware ### UI Improvements diff --git a/auth.go b/auth.go index 296fac1be5a..3e3650aaf6d 100644 --- a/auth.go +++ b/auth.go @@ -8,13 +8,11 @@ import ( // AuthorizationKind is returned by (*Authorization).Kind(). const AuthorizationKind = "authorization" -var ( - // ErrUnableToCreateToken sanitized error message for all errors when a user cannot create a token - ErrUnableToCreateToken = &Error{ - Msg: "unable to create token", - Code: EInvalid, - } -) +// ErrUnableToCreateToken sanitized error message for all errors when a user cannot create a token +var ErrUnableToCreateToken = &Error{ + Msg: "unable to create token", + Code: EInvalid, +} // Authorization is an authorization. 🎉 type Authorization struct { diff --git a/http/authentication_middleware.go b/http/authentication_middleware.go index fe537ca1584..bd7e9bbebd5 100644 --- a/http/authentication_middleware.go +++ b/http/authentication_middleware.go @@ -8,6 +8,7 @@ import ( platform "github.com/influxdata/influxdb" platcontext "github.com/influxdata/influxdb/context" + "github.com/influxdata/influxdb/jsonweb" "github.com/julienschmidt/httprouter" "go.uber.org/zap" ) @@ -19,6 +20,7 @@ type AuthenticationHandler struct { AuthorizationService platform.AuthorizationService SessionService platform.SessionService + TokenParser *jsonweb.TokenParser SessionRenewDisabled bool // This is only really used for it's lookup method the specific http @@ -34,6 +36,7 @@ func NewAuthenticationHandler(h platform.HTTPErrorHandler) *AuthenticationHandle Logger: zap.NewNop(), HTTPErrorHandler: h, Handler: http.DefaultServeMux, + TokenParser: jsonweb.NewTokenParser(jsonweb.EmptyKeyStore), noAuthRouter: httprouter.New(), } } @@ -107,6 +110,19 @@ func (h *AuthenticationHandler) extractAuthorization(ctx context.Context, r *htt return ctx, err } + token, err := h.TokenParser.Parse(t) + if err == nil { + return platcontext.SetAuthorizer(ctx, token), nil + } + + // if the error returned signifies ths token is + // not a well formed JWT then use it as a lookup + // key for its associated authorization + // otherwise return the error + if !jsonweb.IsMalformedError(err) { + return ctx, err + } + a, err := h.AuthorizationService.FindAuthorizationByToken(ctx, t) if err != nil { return ctx, err diff --git a/http/authentication_test.go b/http/authentication_test.go index 51b947d0941..223cee06073 100644 --- a/http/authentication_test.go +++ b/http/authentication_test.go @@ -10,13 +10,17 @@ import ( platform "github.com/influxdata/influxdb" platformhttp "github.com/influxdata/influxdb/http" + "github.com/influxdata/influxdb/jsonweb" "github.com/influxdata/influxdb/mock" ) +const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjbG91ZDIuaW5mbHV4ZGF0YS5jb20iLCJhdWQiOiJnYXRld2F5LmluZmx1eGRhdGEuY29tIiwiaWF0IjoxNTY4NjI4OTgwLCJraWQiOiJzb21lLWtleSIsInBlcm1pc3Npb25zIjpbeyJhY3Rpb24iOiJ3cml0ZSIsInJlc291cmNlIjp7InR5cGUiOiJidWNrZXRzIiwiaWQiOiIwMDAwMDAwMDAwMDAwMDAxIiwib3JnSUQiOiIwMDAwMDAwMDAwMDAwMDAyIn19XX0.74vjbExiOd702VSIMmQWaDT_GFvUI0-_P-SfQ_OOHB0" + func TestAuthenticationHandler(t *testing.T) { type fields struct { AuthorizationService platform.AuthorizationService SessionService platform.SessionService + TokenParser *jsonweb.TokenParser } type args struct { token string @@ -114,6 +118,46 @@ func TestAuthenticationHandler(t *testing.T) { code: http.StatusUnauthorized, }, }, + { + name: "jwt provided", + fields: fields{ + AuthorizationService: &mock.AuthorizationService{ + FindAuthorizationByTokenFn: func(ctx context.Context, token string) (*platform.Authorization, error) { + return nil, fmt.Errorf("authorization not found") + }, + }, + SessionService: mock.NewSessionService(), + TokenParser: jsonweb.NewTokenParser(jsonweb.KeyStoreFunc(func(string) ([]byte, error) { + return []byte("correct-key"), nil + })), + }, + args: args{ + token: token, + }, + wants: wants{ + code: http.StatusOK, + }, + }, + { + name: "jwt provided - bad signature", + fields: fields{ + AuthorizationService: &mock.AuthorizationService{ + FindAuthorizationByTokenFn: func(ctx context.Context, token string) (*platform.Authorization, error) { + panic("token lookup attempted") + }, + }, + SessionService: mock.NewSessionService(), + TokenParser: jsonweb.NewTokenParser(jsonweb.KeyStoreFunc(func(string) ([]byte, error) { + return []byte("incorrect-key"), nil + })), + }, + args: args{ + token: token, + }, + wants: wants{ + code: http.StatusUnauthorized, + }, + }, } for _, tt := range tests { @@ -125,6 +169,9 @@ func TestAuthenticationHandler(t *testing.T) { h := platformhttp.NewAuthenticationHandler(platformhttp.ErrorHandler(0)) h.AuthorizationService = tt.fields.AuthorizationService h.SessionService = tt.fields.SessionService + if tt.fields.TokenParser != nil { + h.TokenParser = tt.fields.TokenParser + } h.Handler = handler w := httptest.NewRecorder() diff --git a/jsonweb/token.go b/jsonweb/token.go index e78382508f7..a64b292f96d 100644 --- a/jsonweb/token.go +++ b/jsonweb/token.go @@ -9,12 +9,16 @@ import ( const kind = "jwt" -// ErrKeyNotFound should be returned by a KeyStore when -// a key cannot be located for the provided key ID -var ErrKeyNotFound = errors.New("key not found") - -// ensure Token implements Authorizer -var _ influxdb.Authorizer = (*Token)(nil) +var ( + // ErrKeyNotFound should be returned by a KeyStore when + // a key cannot be located for the provided key ID + ErrKeyNotFound = errors.New("key not found") + + // EmptyKeyStore is a KeyStore implementation which contains no keys + EmptyKeyStore = KeyStoreFunc(func(string) ([]byte, error) { + return nil, ErrKeyNotFound + }) +) // KeyStore is a type which holds a set of keys accessed // via an id @@ -36,8 +40,8 @@ type TokenParser struct { // NewTokenParser returns a configured token parser used to // parse Token types from strings -func NewTokenParser(keyStore KeyStore) TokenParser { - return TokenParser{ +func NewTokenParser(keyStore KeyStore) *TokenParser { + return &TokenParser{ keyStore: keyStore, parser: &jwt.Parser{ ValidMethods: []string{jwt.SigningMethodHS256.Alg()}, @@ -70,6 +74,13 @@ func (t *TokenParser) Parse(v string) (*Token, error) { return token, nil } +// IsMalformedError returns true if the error returned represents +// a jwt malformed token error +func IsMalformedError(err error) bool { + verr, ok := err.(*jwt.ValidationError) + return ok && verr.Errors&jwt.ValidationErrorMalformed > 0 +} + // Token is a structure which is serialized as a json web token // It contains the necessary claims required to authorize type Token struct {