Files
kubesphere/pkg/models/iam/im.go
zryfish 0a07e5f652 refactor authentication (#1951)
* refactor authentication

* refactor authentication
2020-03-15 23:26:32 +08:00

221 lines
6.2 KiB
Go

/*
*
* 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 iam
import (
"fmt"
"github.com/pkg/errors"
"golang.org/x/oauth2"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api/iam"
"kubesphere.io/kubesphere/pkg/api/iam/token"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/server/params"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"kubesphere.io/kubesphere/pkg/simple/client/ldap"
"time"
)
type IdentityManagementInterface interface {
CreateUser(user *iam.User) (*iam.User, error)
DeleteUser(username string) error
DescribeUser(username string) (*iam.User, error)
Login(username, password, ip string) (*oauth2.Token, error)
ModifyUser(user *iam.User) (*iam.User, error)
ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
GetUserRoles(username string) ([]*rbacv1.Role, error)
GetUserRole(namespace string, username string) (*rbacv1.Role, error)
VerifyToken(token string) (*iam.User, error)
}
type imOperator struct {
authenticateOptions *iam.AuthenticationOptions
ldapClient ldap.Interface
cacheClient cache.Interface
issuer token.Issuer
}
var (
AuthRateLimitExceeded = errors.New("user auth rate limit exceeded")
UserAlreadyExists = errors.New("user already exists")
UserNotExists = errors.New("user not exists")
)
func NewIMOperator(ldapClient ldap.Interface, cacheClient cache.Interface, options *iam.AuthenticationOptions) *imOperator {
return &imOperator{
ldapClient: ldapClient,
cacheClient: cacheClient,
authenticateOptions: options,
issuer: token.NewJwtTokenIssuer(token.DefaultIssuerName, []byte(options.JwtSecret)),
}
}
func (im *imOperator) ModifyUser(user *iam.User) (*iam.User, error) {
err := im.ldapClient.Update(user)
if err != nil {
return nil, err
}
// clear auth failed record
if user.Password != "" {
records, err := im.cacheClient.Keys(authenticationFailedKeyForUsername(user.Username, "*"))
if err == nil {
im.cacheClient.Del(records...)
}
}
return im.ldapClient.Get(user.Username)
}
func (im *imOperator) Login(username, password, ip string) (*oauth2.Token, error) {
records, err := im.cacheClient.Keys(authenticationFailedKeyForUsername(username, "*"))
if err != nil {
return nil, err
}
if len(records) > im.authenticateOptions.MaxAuthenticateRetries {
return nil, AuthRateLimitExceeded
}
user, err := im.ldapClient.Get(username)
if err != nil {
return nil, err
}
err = im.ldapClient.Verify(user.Username, password)
if err != nil {
if err == ldap.ErrInvalidCredentials {
im.cacheClient.Set(authenticationFailedKeyForUsername(username, fmt.Sprintf("%d", time.Now().UnixNano())), "", 30*time.Minute)
}
return nil, err
}
issuedToken, err := im.issuer.IssueTo(user)
if err != nil {
return nil, err
}
// TODO: I think we should come up with a better strategy to prevent multiple login.
tokenKey := tokenKeyForUsername(user.Username, issuedToken)
if !im.authenticateOptions.MultipleLogin {
// multi login not allowed, remove the previous token
sessions, err := im.cacheClient.Keys(tokenKey)
if err != nil {
return nil, err
}
if len(sessions) > 0 {
klog.V(4).Infoln("revoke token", sessions)
err = im.cacheClient.Del(sessions...)
if err != nil {
return nil, err
}
}
}
// save token with expiration time
if err = im.cacheClient.Set(tokenKey, issuedToken, im.authenticateOptions.TokenExpiration); err != nil {
return nil, err
}
im.logLogin(user.Username, ip, time.Now())
return &oauth2.Token{AccessToken: issuedToken}, nil
}
func (im *imOperator) logLogin(username, ip string, loginTime time.Time) {
if ip != "" {
_ = im.cacheClient.Set(loginKeyForUsername(username, loginTime.UTC().Format("2006-01-02T15:04:05Z"), ip), "", 30*24*time.Hour)
}
}
func (im *imOperator) LoginHistory(username string) ([]string, error) {
keys, err := im.cacheClient.Keys(loginKeyForUsername(username, "*", "*"))
if err != nil {
return nil, err
}
return keys, nil
}
func (im *imOperator) ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
panic("implement me")
}
func (im *imOperator) DescribeUser(username string) (*iam.User, error) {
return im.ldapClient.Get(username)
}
func (im *imOperator) getLastLoginTime(username string) string {
return ""
}
func (im *imOperator) DeleteUser(username string) error {
return im.ldapClient.Delete(username)
}
func (im *imOperator) CreateUser(user *iam.User) (*iam.User, error) {
err := im.ldapClient.Create(user)
if err != nil {
return nil, err
}
return user, nil
}
func (im *imOperator) VerifyToken(tokenString string) (*iam.User, error) {
providedUser, err := im.issuer.Verify(tokenString)
if err != nil {
return nil, err
}
user, err := im.ldapClient.Get(providedUser.Name())
if err != nil {
return nil, err
}
return user, nil
}
func (im *imOperator) uidNumberNext() int {
// TODO fix me
return 0
}
func (im *imOperator) GetUserRoles(username string) ([]*rbacv1.Role, error) {
panic("implement me")
}
func (im *imOperator) GetUserRole(namespace string, username string) (*rbacv1.Role, error) {
panic("implement me")
}
func authenticationFailedKeyForUsername(username, failedTimestamp string) string {
return fmt.Sprintf("kubesphere:authfailed:%s:%s", username, failedTimestamp)
}
func tokenKeyForUsername(username, token string) string {
return fmt.Sprintf("kubesphere:users:%s:token:%s", username, token)
}
func loginKeyForUsername(username, loginTimestamp, ip string) string {
return fmt.Sprintf("kubesphere:users:%s:login-log:%s:%s", username, loginTimestamp, ip)
}