未验证 提交 3ce4a0d7 编写于 作者: H hongming

token cache config

Signed-off-by: Nhongming <talonwan@yunify.com>
上级 17013d35
......@@ -8,7 +8,6 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
urlruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
unionauth "k8s.io/apiserver/pkg/authentication/request/union"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/klog"
......@@ -16,6 +15,7 @@ import (
"kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/jwttoken"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/request/anonymous"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/request/basictoken"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/request/bearertoken"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizerfactory"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/path"
......
......@@ -69,4 +69,5 @@ func (options *AuthenticationOptions) AddFlags(fs *pflag.FlagSet, s *Authenticat
fs.IntVar(&options.MaxAuthenticateRetries, "authenticate-max-retries", s.MaxAuthenticateRetries, "")
fs.BoolVar(&options.MultipleLogin, "multiple-login", s.MultipleLogin, "Allow multiple login with the same account, disable means only one user can login at the same time.")
fs.StringVar(&options.JwtSecret, "jwt-secret", s.JwtSecret, "Secret to sign jwt token, must not be empty.")
fs.DurationVar(&options.OAuthOptions.AccessTokenMaxAge, "access-token-max-age", s.OAuthOptions.AccessTokenMaxAge, "AccessTokenMaxAgeSeconds control the lifetime of access tokens, 0 means no expiration.")
}
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package bearertoken
import (
"errors"
"net/http"
"strings"
"k8s.io/apiserver/pkg/authentication/authenticator"
)
type Authenticator struct {
auth authenticator.Token
}
func New(auth authenticator.Token) *Authenticator {
return &Authenticator{auth}
}
var invalidToken = errors.New("invalid bearer token")
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
auth := strings.TrimSpace(req.Header.Get("Authorization"))
if auth == "" {
return nil, false, nil
}
parts := strings.Split(auth, " ")
if len(parts) < 2 || strings.ToLower(parts[0]) != "bearer" {
return nil, false, nil
}
token := parts[1]
// Empty bearer tokens aren't valid
if len(token) == 0 {
return nil, false, nil
}
resp, ok, err := a.auth.AuthenticateToken(req.Context(), token)
// If the token authenticator didn't error, provide a default error
if !ok && err == nil {
err = invalidToken
}
return resp, ok, err
}
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package bearertoken
import (
"context"
"errors"
"net/http"
"reflect"
"testing"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
)
func TestAuthenticateRequest(t *testing.T) {
auth := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
if token != "token" {
t.Errorf("unexpected token: %s", token)
}
return &authenticator.Response{User: &user.DefaultInfo{Name: "user"}}, true, nil
}))
resp, ok, err := auth.AuthenticateRequest(&http.Request{
Header: http.Header{"Authorization": []string{"Bearer token"}},
})
if !ok || resp == nil || err != nil {
t.Errorf("expected valid user")
}
}
func TestAuthenticateRequestTokenInvalid(t *testing.T) {
auth := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
return nil, false, nil
}))
resp, ok, err := auth.AuthenticateRequest(&http.Request{
Header: http.Header{"Authorization": []string{"Bearer token"}},
})
if ok || resp != nil {
t.Errorf("expected not authenticated user")
}
if err != invalidToken {
t.Errorf("expected invalidToken error, got %v", err)
}
}
func TestAuthenticateRequestTokenInvalidCustomError(t *testing.T) {
customError := errors.New("custom")
auth := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
return nil, false, customError
}))
resp, ok, err := auth.AuthenticateRequest(&http.Request{
Header: http.Header{"Authorization": []string{"Bearer token"}},
})
if ok || resp != nil {
t.Errorf("expected not authenticated user")
}
if err != customError {
t.Errorf("expected custom error, got %v", err)
}
}
func TestAuthenticateRequestTokenError(t *testing.T) {
auth := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
return nil, false, errors.New("error")
}))
resp, ok, err := auth.AuthenticateRequest(&http.Request{
Header: http.Header{"Authorization": []string{"Bearer token"}},
})
if ok || resp != nil || err == nil {
t.Errorf("expected error")
}
}
func TestAuthenticateRequestBadValue(t *testing.T) {
testCases := []struct {
Req *http.Request
}{
{Req: &http.Request{}},
{Req: &http.Request{Header: http.Header{"Authorization": []string{"Bearer"}}}},
{Req: &http.Request{Header: http.Header{"Authorization": []string{"bear token"}}}},
{Req: &http.Request{Header: http.Header{"Authorization": []string{"Bearer: token"}}}},
}
for i, testCase := range testCases {
auth := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
t.Errorf("authentication should not have been called")
return nil, false, nil
}))
user, ok, err := auth.AuthenticateRequest(testCase.Req)
if ok || user != nil || err != nil {
t.Errorf("%d: expected not authenticated (no token)", i)
}
}
}
func TestBearerToken(t *testing.T) {
tests := map[string]struct {
AuthorizationHeaders []string
TokenAuth authenticator.Token
ExpectedUserName string
ExpectedOK bool
ExpectedErr bool
ExpectedAuthorizationHeaders []string
}{
"no header": {
AuthorizationHeaders: nil,
ExpectedUserName: "",
ExpectedOK: false,
ExpectedErr: false,
ExpectedAuthorizationHeaders: nil,
},
"empty header": {
AuthorizationHeaders: []string{""},
ExpectedUserName: "",
ExpectedOK: false,
ExpectedErr: false,
ExpectedAuthorizationHeaders: []string{""},
},
"non-bearer header": {
AuthorizationHeaders: []string{"Basic 123"},
ExpectedUserName: "",
ExpectedOK: false,
ExpectedErr: false,
ExpectedAuthorizationHeaders: []string{"Basic 123"},
},
"empty bearer token": {
AuthorizationHeaders: []string{"Bearer "},
ExpectedUserName: "",
ExpectedOK: false,
ExpectedErr: false,
ExpectedAuthorizationHeaders: []string{"Bearer "},
},
"invalid bearer token": {
AuthorizationHeaders: []string{"Bearer 123"},
TokenAuth: authenticator.TokenFunc(func(ctx context.Context, t string) (*authenticator.Response, bool, error) { return nil, false, nil }),
ExpectedUserName: "",
ExpectedOK: false,
ExpectedErr: true,
ExpectedAuthorizationHeaders: []string{"Bearer 123"},
},
"error bearer token": {
AuthorizationHeaders: []string{"Bearer 123"},
TokenAuth: authenticator.TokenFunc(func(ctx context.Context, t string) (*authenticator.Response, bool, error) {
return nil, false, errors.New("error")
}),
ExpectedUserName: "",
ExpectedOK: false,
ExpectedErr: true,
ExpectedAuthorizationHeaders: []string{"Bearer 123"},
},
}
for k, tc := range tests {
req, _ := http.NewRequest("GET", "/", nil)
for _, h := range tc.AuthorizationHeaders {
req.Header.Add("Authorization", h)
}
bearerAuth := New(tc.TokenAuth)
resp, ok, err := bearerAuth.AuthenticateRequest(req)
if tc.ExpectedErr != (err != nil) {
t.Errorf("%s: Expected err=%v, got %v", k, tc.ExpectedErr, err)
continue
}
if ok != tc.ExpectedOK {
t.Errorf("%s: Expected ok=%v, got %v", k, tc.ExpectedOK, ok)
continue
}
if ok && resp.User.GetName() != tc.ExpectedUserName {
t.Errorf("%s: Expected username=%v, got %v", k, tc.ExpectedUserName, resp.User.GetName())
continue
}
if !reflect.DeepEqual(req.Header["Authorization"], tc.ExpectedAuthorizationHeaders) {
t.Errorf("%s: Expected headers=%#v, got %#v", k, tc.ExpectedAuthorizationHeaders, req.Header["Authorization"])
continue
}
}
}
......@@ -22,6 +22,7 @@ import (
"fmt"
"github.com/dgrijalva/jwt-go"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/server/errors"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
......@@ -53,20 +54,26 @@ func (s *jwtTokenIssuer) Verify(tokenString string) (User, error) {
if len(tokenString) == 0 {
return nil, errInvalidToken
}
_, err := s.cache.Get(tokenCacheKey(tokenString))
clm := &Claims{}
_, err := jwt.ParseWithClaims(tokenString, clm, s.keyFunc)
if err != nil {
if err == cache.ErrNoSuchKey {
return nil, errTokenExpired
}
return nil, err
}
clm := &Claims{}
// 0 means no expiration.
// validate token cache
if s.options.OAuthOptions.AccessTokenMaxAge > 0 {
_, err = s.cache.Get(tokenCacheKey(tokenString))
_, err = jwt.ParseWithClaims(tokenString, clm, s.keyFunc)
if err != nil {
return nil, err
if err != nil {
if err == cache.ErrNoSuchKey {
return nil, errTokenExpired
}
return nil, err
}
}
return &user.DefaultInfo{Name: clm.Username, UID: clm.UID}, nil
......@@ -92,16 +99,28 @@ func (s *jwtTokenIssuer) IssueTo(user User, expiresIn time.Duration) (string, er
tokenString, err := token.SignedString([]byte(s.options.JwtSecret))
if err != nil {
klog.Error(err)
return "", err
}
s.cache.Set(tokenCacheKey(tokenString), tokenString, expiresIn)
// 0 means no expiration.
// validate token cache
if s.options.OAuthOptions.AccessTokenMaxAge > 0 {
err = s.cache.Set(tokenCacheKey(tokenString), tokenString, s.options.OAuthOptions.AccessTokenMaxAge)
if err != nil {
klog.Error(err)
return "", err
}
}
return tokenString, nil
}
func (s *jwtTokenIssuer) Revoke(token string) error {
return s.cache.Del(tokenCacheKey(token))
if s.options.OAuthOptions.AccessTokenMaxAge > 0 {
return s.cache.Del(tokenCacheKey(token))
}
return nil
}
func NewJwtTokenIssuer(issuerName string, options *authoptions.AuthenticationOptions, cache cache.Interface) Issuer {
......
......@@ -21,6 +21,7 @@ package token
import (
"github.com/google/go-cmp/cmp"
"k8s.io/apiserver/pkg/authentication/user"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"testing"
......@@ -70,3 +71,39 @@ func TestJwtTokenIssuer(t *testing.T) {
})
}
}
func TestTokenVerifyWithoutCacheValidate(t *testing.T) {
options := authoptions.NewAuthenticateOptions()
// do not set token cache and disable token cache validate,
options.OAuthOptions = &oauth.Options{AccessTokenMaxAge: 0}
options.JwtSecret = "kubesphere"
issuer := NewJwtTokenIssuer(DefaultIssuerName, options, nil)
client, err := options.OAuthOptions.OAuthClient("default")
if err != nil {
t.Fatal(err)
}
user := &user.DefaultInfo{
Name: "admin",
UID: "admin",
}
tokenString, err := issuer.IssueTo(user, *client.AccessTokenMaxAge)
if err != nil {
t.Fatal(err)
}
got, err := issuer.Verify(tokenString)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(got, user); diff != "" {
t.Error("token validate failed")
}
}
......@@ -42,9 +42,6 @@ func WithAuthentication(handler http.Handler, auth authenticator.Request) http.H
return
}
// authorization header is not required anymore in case of a successful authentication.
req.Header.Del("Authorization")
req = req.WithContext(request.WithUser(req.Context(), resp.User))
handler.ServeHTTP(w, req)
})
......
......@@ -33,6 +33,9 @@ func WithKubeAPIServer(handler http.Handler, config *rest.Config, failed proxy.E
s.Host = kubernetes.Host
s.Scheme = kubernetes.Scheme
// Do not cover k8s client authorization header
req.Header.Del("Authorization")
httpProxy := proxy.NewUpgradeAwareHandler(&s, defaultTransport, true, false, failed)
httpProxy.ServeHTTP(w, req)
return
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册