login record CRD (#2565)
* Signed-off-by: hongming <talonwan@yunify.com> support ldap identity provider Signed-off-by: hongming <talonwan@yunify.com> * add login record Signed-off-by: Jeff <zw0948@gmail.com> Co-authored-by: hongming <talonwan@yunify.com>
This commit is contained in:
@@ -29,18 +29,18 @@ import (
|
||||
// and group from user.AllUnauthenticated. This helps requests be passed along the handler chain,
|
||||
// because some resources are public accessible.
|
||||
type basicAuthenticator struct {
|
||||
im im.IdentityManagementInterface
|
||||
authenticator im.PasswordAuthenticator
|
||||
}
|
||||
|
||||
func NewBasicAuthenticator(im im.IdentityManagementInterface) authenticator.Password {
|
||||
func NewBasicAuthenticator(authenticator im.PasswordAuthenticator) authenticator.Password {
|
||||
return &basicAuthenticator{
|
||||
im: im,
|
||||
authenticator: authenticator,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *basicAuthenticator) AuthenticatePassword(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
|
||||
|
||||
providedUser, err := t.im.Authenticate(username, password)
|
||||
providedUser, err := t.authenticator.Authenticate(username, password)
|
||||
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
@@ -49,7 +49,7 @@ func (t *basicAuthenticator) AuthenticatePassword(ctx context.Context, username,
|
||||
return &authenticator.Response{
|
||||
User: &user.DefaultInfo{
|
||||
Name: providedUser.GetName(),
|
||||
UID: string(providedUser.GetUID()),
|
||||
UID: providedUser.GetUID(),
|
||||
Groups: []string{user.AllAuthenticated},
|
||||
},
|
||||
}, true, nil
|
||||
|
||||
@@ -20,7 +20,8 @@ import (
|
||||
"context"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
token2 "kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/models/iam/im"
|
||||
)
|
||||
|
||||
// TokenAuthenticator implements kubernetes token authenticate interface with our custom logic.
|
||||
@@ -29,18 +30,19 @@ import (
|
||||
// and group from user.AllUnauthenticated. This helps requests be passed along the handler chain,
|
||||
// because some resources are public accessible.
|
||||
type tokenAuthenticator struct {
|
||||
jwtTokenIssuer token2.Issuer
|
||||
tokenOperator im.TokenManagementInterface
|
||||
}
|
||||
|
||||
func NewTokenAuthenticator(issuer token2.Issuer) authenticator.Token {
|
||||
func NewTokenAuthenticator(tokenOperator im.TokenManagementInterface) authenticator.Token {
|
||||
return &tokenAuthenticator{
|
||||
jwtTokenIssuer: issuer,
|
||||
tokenOperator: tokenOperator,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
|
||||
providedUser, err := t.jwtTokenIssuer.Verify(token)
|
||||
providedUser, err := t.tokenOperator.Verify(token)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ type GithubIdentity struct {
|
||||
}
|
||||
|
||||
func init() {
|
||||
identityprovider.RegisterOAuthProviderCodec(&Github{})
|
||||
identityprovider.RegisterOAuthProvider(&Github{})
|
||||
}
|
||||
|
||||
func (g *Github) Type() string {
|
||||
|
||||
@@ -16,33 +16,7 @@ limitations under the License.
|
||||
|
||||
package identityprovider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorIdentityProviderNotFound = errors.New("the identity provider was not found")
|
||||
oauthProviders = make(map[string]OAuthProvider, 0)
|
||||
)
|
||||
|
||||
type OAuthProvider interface {
|
||||
Type() string
|
||||
Setup(options *oauth.DynamicOptions) (OAuthProvider, error)
|
||||
IdentityExchange(code string) (Identity, error)
|
||||
}
|
||||
type Identity interface {
|
||||
GetName() string
|
||||
GetEmail() string
|
||||
}
|
||||
|
||||
func GetOAuthProvider(providerType string, options *oauth.DynamicOptions) (OAuthProvider, error) {
|
||||
if provider, ok := oauthProviders[providerType]; ok {
|
||||
return provider.Setup(options)
|
||||
}
|
||||
return nil, ErrorIdentityProviderNotFound
|
||||
}
|
||||
|
||||
func RegisterOAuthProviderCodec(provider OAuthProvider) {
|
||||
oauthProviders[provider.Type()] = provider
|
||||
}
|
||||
|
||||
117
pkg/apiserver/authentication/identityprovider/ldap_provider.go
Normal file
117
pkg/apiserver/authentication/identityprovider/ldap_provider.go
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 The KubeSphere 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 identityprovider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-ldap/ldap"
|
||||
"gopkg.in/yaml.v3"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
|
||||
)
|
||||
|
||||
const LdapIdentityProvider = "LDAPIdentityProvider"
|
||||
|
||||
type LdapProvider interface {
|
||||
Authenticate(username string, password string) (*iamv1alpha2.User, error)
|
||||
}
|
||||
|
||||
type ldapOptions struct {
|
||||
Host string `json:"host" yaml:"host"`
|
||||
ManagerDN string `json:"managerDN" yaml:"managerDN"`
|
||||
ManagerPassword string `json:"-" yaml:"managerPassword"`
|
||||
UserSearchBase string `json:"userSearchBase" yaml:"userSearchBase"`
|
||||
//This is typically uid
|
||||
LoginAttribute string `json:"loginAttribute" yaml:"loginAttribute"`
|
||||
MailAttribute string `json:"mailAttribute" yaml:"mailAttribute"`
|
||||
DisplayNameAttribute string `json:"displayNameAttribute" yaml:"displayNameAttribute"`
|
||||
}
|
||||
|
||||
type ldapProvider struct {
|
||||
options ldapOptions
|
||||
}
|
||||
|
||||
func NewLdapProvider(options *oauth.DynamicOptions) (LdapProvider, error) {
|
||||
data, err := yaml.Marshal(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ldapOptions ldapOptions
|
||||
err = yaml.Unmarshal(data, &ldapOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ldapProvider{options: ldapOptions}, nil
|
||||
}
|
||||
|
||||
func (l ldapProvider) Authenticate(username string, password string) (*iamv1alpha2.User, error) {
|
||||
conn, err := ldap.Dial("tcp", l.options.Host)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = conn.Bind(l.options.ManagerDN, l.options.ManagerPassword)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter := fmt.Sprintf("(&(%s=%s))", l.options.LoginAttribute, username)
|
||||
|
||||
result, err := conn.Search(&ldap.SearchRequest{
|
||||
BaseDN: l.options.UserSearchBase,
|
||||
Scope: ldap.ScopeWholeSubtree,
|
||||
DerefAliases: ldap.NeverDerefAliases,
|
||||
SizeLimit: 1,
|
||||
TimeLimit: 0,
|
||||
TypesOnly: false,
|
||||
Filter: filter,
|
||||
Attributes: []string{l.options.LoginAttribute, l.options.MailAttribute, l.options.DisplayNameAttribute},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(result.Entries) == 1 {
|
||||
entry := result.Entries[0]
|
||||
err = conn.Bind(entry.DN, password)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &iamv1alpha2.User{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: username,
|
||||
},
|
||||
Spec: iamv1alpha2.UserSpec{
|
||||
Email: entry.GetAttributeValue(l.options.MailAttribute),
|
||||
DisplayName: entry.GetAttributeValue(l.options.DisplayNameAttribute),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, ldap.NewError(ldap.LDAPResultNoSuchObject, fmt.Errorf(" could not find user %s in LDAP directory", username))
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 The KubeSphere 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 identityprovider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
|
||||
)
|
||||
|
||||
var (
|
||||
oauthProviders = make(map[string]OAuthProvider, 0)
|
||||
ErrorIdentityProviderNotFound = errors.New("the identity provider was not found")
|
||||
)
|
||||
|
||||
type OAuthProvider interface {
|
||||
Type() string
|
||||
Setup(options *oauth.DynamicOptions) (OAuthProvider, error)
|
||||
IdentityExchange(code string) (Identity, error)
|
||||
}
|
||||
|
||||
func GetOAuthProvider(providerType string, options *oauth.DynamicOptions) (OAuthProvider, error) {
|
||||
if provider, ok := oauthProviders[providerType]; ok {
|
||||
return provider.Setup(options)
|
||||
}
|
||||
return nil, ErrorIdentityProviderNotFound
|
||||
}
|
||||
|
||||
func RegisterOAuthProvider(provider OAuthProvider) {
|
||||
oauthProviders[provider.Type()] = provider
|
||||
}
|
||||
@@ -46,6 +46,7 @@ const (
|
||||
|
||||
var (
|
||||
ErrorClientNotFound = errors.New("the OAuth client was not found")
|
||||
ErrorProviderNotFound = errors.New("the identity provider was not found")
|
||||
ErrorRedirectURLNotAllowed = errors.New("redirect URL is not allowed")
|
||||
)
|
||||
|
||||
@@ -92,7 +93,7 @@ type IdentityProviderOptions struct {
|
||||
Type string `json:"type" yaml:"type"`
|
||||
|
||||
// The options of identify provider
|
||||
Provider *DynamicOptions `json:"provider,omitempty" yaml:"provider"`
|
||||
Provider *DynamicOptions `json:"-" yaml:"provider"`
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
@@ -155,6 +156,7 @@ var (
|
||||
DefaultAccessTokenInactivityTimeout = time.Duration(0)
|
||||
DefaultClients = []Client{{
|
||||
Name: "default",
|
||||
Secret: "kubesphere",
|
||||
RespondWithChallenges: true,
|
||||
RedirectURIs: []string{AllowAllRedirectURI},
|
||||
GrantMethod: GrantHandlerAuto,
|
||||
@@ -177,13 +179,13 @@ func (o *Options) OAuthClient(name string) (Client, error) {
|
||||
}
|
||||
return Client{}, ErrorClientNotFound
|
||||
}
|
||||
func (o *Options) IdentityProviderOptions(name string) (IdentityProviderOptions, error) {
|
||||
func (o *Options) IdentityProviderOptions(name string) (*IdentityProviderOptions, error) {
|
||||
for _, found := range o.IdentityProviders {
|
||||
if found.Name == name {
|
||||
return found, nil
|
||||
return &found, nil
|
||||
}
|
||||
}
|
||||
return IdentityProviderOptions{}, ErrorClientNotFound
|
||||
return nil, ErrorProviderNotFound
|
||||
}
|
||||
|
||||
func (c Client) anyRedirectAbleURI() []string {
|
||||
@@ -224,7 +226,7 @@ func NewOptions() *Options {
|
||||
return &Options{
|
||||
IdentityProviders: make([]IdentityProviderOptions, 0),
|
||||
Clients: make([]Client, 0),
|
||||
AccessTokenMaxAge: time.Hour * 24,
|
||||
AccessTokenInactivityTimeout: 0,
|
||||
AccessTokenMaxAge: time.Hour * 2,
|
||||
AccessTokenInactivityTimeout: time.Hour * 2,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ func TestDefaultAuthOptions(t *testing.T) {
|
||||
expect := Client{
|
||||
Name: "default",
|
||||
RespondWithChallenges: true,
|
||||
Secret: "kubesphere",
|
||||
RedirectURIs: []string{AllowAllRedirectURI},
|
||||
GrantMethod: GrantHandlerAuto,
|
||||
ScopeRestrictions: []string{"full"},
|
||||
|
||||
@@ -25,9 +25,13 @@ import (
|
||||
)
|
||||
|
||||
type AuthenticationOptions struct {
|
||||
// authenticate rate limit will
|
||||
// authenticate rate limit
|
||||
AuthenticateRateLimiterMaxTries int `json:"authenticateRateLimiterMaxTries" yaml:"authenticateRateLimiterMaxTries"`
|
||||
AuthenticateRateLimiterDuration time.Duration `json:"authenticationRateLimiterDuration" yaml:"authenticationRateLimiterDuration"`
|
||||
AuthenticateRateLimiterDuration time.Duration `json:"authenticateRateLimiterDuration" yaml:"authenticateRateLimiterDuration"`
|
||||
// Token verification maximum time difference
|
||||
MaximumClockSkew time.Duration `json:"maximumClockSkew" yaml:"maximumClockSkew"`
|
||||
// retention login records
|
||||
RecordRetentionPeriod time.Duration `json:"recordRetentionPeriod" yaml:"recordRetentionPeriod"`
|
||||
// allow multiple users login at the same time
|
||||
MultipleLogin bool `json:"multipleLogin" yaml:"multipleLogin"`
|
||||
// secret to signed jwt token
|
||||
@@ -41,6 +45,8 @@ func NewAuthenticateOptions() *AuthenticationOptions {
|
||||
return &AuthenticationOptions{
|
||||
AuthenticateRateLimiterMaxTries: 5,
|
||||
AuthenticateRateLimiterDuration: time.Minute * 30,
|
||||
MaximumClockSkew: 10 * time.Second,
|
||||
RecordRetentionPeriod: time.Hour * 24 * 7,
|
||||
OAuthOptions: oauth.NewOptions(),
|
||||
MultipleLogin: false,
|
||||
JwtSecret: "",
|
||||
@@ -64,4 +70,5 @@ func (options *AuthenticationOptions) AddFlags(fs *pflag.FlagSet, s *Authenticat
|
||||
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.")
|
||||
fs.StringVar(&s.KubectlImage, "kubectl-image", s.KubectlImage, "Setup the image used by kubectl terminal pod")
|
||||
fs.DurationVar(&options.MaximumClockSkew, "maximum-clock-skew", s.MaximumClockSkew, "The maximum time difference between the system clocks of the ks-apiserver that issued a JWT and the ks-apiserver that verified the JWT.")
|
||||
}
|
||||
|
||||
@@ -16,16 +16,24 @@ limitations under the License.
|
||||
|
||||
package token
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
AccessToken TokenType = "access_token"
|
||||
RefreshToken TokenType = "refresh_token"
|
||||
StaticToken TokenType = "static_token"
|
||||
)
|
||||
|
||||
type TokenType string
|
||||
|
||||
// Issuer issues token to user, tokens are required to perform mutating requests to resources
|
||||
type Issuer interface {
|
||||
// IssueTo issues a token a User, return error if issuing process failed
|
||||
IssueTo(user User, expiresIn time.Duration) (string, error)
|
||||
IssueTo(user user.Info, tokenType TokenType, expiresIn time.Duration) (string, error)
|
||||
|
||||
// Verify verifies a token, and return a User if it's a valid token, otherwise return error
|
||||
Verify(string) (User, error)
|
||||
|
||||
// Revoke a token,
|
||||
Revoke(token string) error
|
||||
// Verify verifies a token, and return a user info if it's a valid token, otherwise return error
|
||||
Verify(string) (user.Info, TokenType, error)
|
||||
}
|
||||
|
||||
@@ -21,68 +21,50 @@ import (
|
||||
"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"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DefaultIssuerName = "kubesphere"
|
||||
|
||||
var (
|
||||
errInvalidToken = errors.New("invalid token")
|
||||
errTokenExpired = errors.New("expired token")
|
||||
const (
|
||||
DefaultIssuerName = "kubesphere"
|
||||
)
|
||||
|
||||
type Claims struct {
|
||||
Username string `json:"username"`
|
||||
UID string `json:"uid"`
|
||||
Username string `json:"username"`
|
||||
UID string `json:"uid"`
|
||||
TokenType TokenType `json:"token_type"`
|
||||
// Currently, we are not using any field in jwt.StandardClaims
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
type jwtTokenIssuer struct {
|
||||
name string
|
||||
options *authoptions.AuthenticationOptions
|
||||
cache cache.Interface
|
||||
keyFunc jwt.Keyfunc
|
||||
name string
|
||||
secret []byte
|
||||
// Maximum time difference
|
||||
maximumClockSkew time.Duration
|
||||
}
|
||||
|
||||
func (s *jwtTokenIssuer) Verify(tokenString string) (User, error) {
|
||||
if len(tokenString) == 0 {
|
||||
return nil, errInvalidToken
|
||||
}
|
||||
|
||||
func (s *jwtTokenIssuer) Verify(tokenString string) (user.Info, TokenType, error) {
|
||||
clm := &Claims{}
|
||||
// verify token signature and expiration time
|
||||
_, err := jwt.ParseWithClaims(tokenString, clm, s.keyFunc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
klog.Error(err)
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// accessTokenMaxAge = 0 or token without expiration time means that the token will not expire
|
||||
// do not validate token cache
|
||||
if s.options.OAuthOptions.AccessTokenMaxAge > 0 && clm.ExpiresAt > 0 {
|
||||
_, err = s.cache.Get(tokenCacheKey(tokenString))
|
||||
|
||||
if err != nil {
|
||||
if err == cache.ErrNoSuchKey {
|
||||
return nil, errTokenExpired
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &user.DefaultInfo{Name: clm.Username, UID: clm.UID}, nil
|
||||
return &user.DefaultInfo{Name: clm.Username, UID: clm.UID}, clm.TokenType, nil
|
||||
}
|
||||
|
||||
func (s *jwtTokenIssuer) IssueTo(user User, expiresIn time.Duration) (string, error) {
|
||||
func (s *jwtTokenIssuer) IssueTo(user user.Info, tokenType TokenType, expiresIn time.Duration) (string, error) {
|
||||
issueAt := time.Now().Unix() - int64(s.maximumClockSkew.Seconds())
|
||||
notBefore := issueAt
|
||||
clm := &Claims{
|
||||
Username: user.GetName(),
|
||||
UID: user.GetUID(),
|
||||
Username: user.GetName(),
|
||||
UID: user.GetUID(),
|
||||
TokenType: tokenType,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
IssuedAt: time.Now().Unix(),
|
||||
IssuedAt: issueAt,
|
||||
Issuer: s.name,
|
||||
NotBefore: time.Now().Unix(),
|
||||
NotBefore: notBefore,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -92,48 +74,28 @@ func (s *jwtTokenIssuer) IssueTo(user User, expiresIn time.Duration) (string, er
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, clm)
|
||||
|
||||
tokenString, err := token.SignedString([]byte(s.options.JwtSecret))
|
||||
tokenString, err := token.SignedString(s.secret)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if s.options.OAuthOptions.AccessTokenMaxAge > 0 {
|
||||
return s.cache.Del(tokenCacheKey(token))
|
||||
func (s *jwtTokenIssuer) keyFunc(token *jwt.Token) (i interface{}, err error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok {
|
||||
return s.secret, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("expect token signed with HMAC but got %v", token.Header["alg"])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewJwtTokenIssuer(issuerName string, options *authoptions.AuthenticationOptions, cache cache.Interface) Issuer {
|
||||
func NewTokenIssuer(secret string, maximumClockSkew time.Duration) Issuer {
|
||||
return &jwtTokenIssuer{
|
||||
name: issuerName,
|
||||
options: options,
|
||||
cache: cache,
|
||||
keyFunc: func(token *jwt.Token) (i interface{}, err error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok {
|
||||
return []byte(options.JwtSecret), nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("expect token signed with HMAC but got %v", token.Header["alg"])
|
||||
}
|
||||
},
|
||||
name: DefaultIssuerName,
|
||||
secret: []byte(secret),
|
||||
maximumClockSkew: maximumClockSkew,
|
||||
}
|
||||
}
|
||||
|
||||
func tokenCacheKey(token string) string {
|
||||
return fmt.Sprintf("kubesphere:tokens:%s", token)
|
||||
}
|
||||
|
||||
@@ -19,89 +19,30 @@ 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"
|
||||
)
|
||||
|
||||
func TestJwtTokenIssuer(t *testing.T) {
|
||||
options := authoptions.NewAuthenticateOptions()
|
||||
options.JwtSecret = "kubesphere"
|
||||
issuer := NewJwtTokenIssuer(DefaultIssuerName, options, cache.NewSimpleCache())
|
||||
|
||||
testCases := []struct {
|
||||
description string
|
||||
name string
|
||||
uid string
|
||||
email string
|
||||
}{
|
||||
{
|
||||
name: "admin",
|
||||
uid: "b8be6edd-2c92-4535-9b2a-df6326474458",
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
uid: "b8be6edd-2c92-4535-9b2a-df6326474452",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
user := &user.DefaultInfo{
|
||||
Name: testCase.name,
|
||||
UID: testCase.uid,
|
||||
}
|
||||
|
||||
t.Run(testCase.description, func(t *testing.T) {
|
||||
token, err := issuer.IssueTo(user, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := issuer.Verify(token)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(user, got); len(diff) != 0 {
|
||||
t.Errorf("%T differ (-got, +expected), %s", user, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
issuer := NewTokenIssuer("kubesphere", 0)
|
||||
|
||||
client, err := options.OAuthOptions.OAuthClient("default")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
user := &user.DefaultInfo{
|
||||
admin := &user.DefaultInfo{
|
||||
Name: "admin",
|
||||
UID: "admin",
|
||||
}
|
||||
|
||||
tokenString, err := issuer.IssueTo(user, *client.AccessTokenMaxAge)
|
||||
tokenString, err := issuer.IssueTo(admin, AccessToken, 0)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := issuer.Verify(tokenString)
|
||||
got, _, err := issuer.Verify(tokenString)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(got, user); diff != "" {
|
||||
if diff := cmp.Diff(got, admin); diff != "" {
|
||||
t.Error("token validate failed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 token
|
||||
|
||||
type User interface {
|
||||
// Name
|
||||
GetName() string
|
||||
|
||||
// UID
|
||||
GetUID() string
|
||||
}
|
||||
Reference in New Issue
Block a user