221 lines
6.2 KiB
Go
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)
|
|
}
|