improve identity provider plugin

Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
hongming
2020-11-23 15:04:59 +08:00
parent 91c2e05616
commit dfaefa5ffb
63 changed files with 3656 additions and 1746 deletions

View File

@@ -18,22 +18,30 @@ package am
import (
"encoding/json"
"fmt"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
listersv1 "k8s.io/client-go/listers/core/v1"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
devopsv1alpha3 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha3"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
"kubesphere.io/kubesphere/pkg/apiserver/query"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
devopslisters "kubesphere.io/kubesphere/pkg/client/listers/devops/v1alpha3"
"kubesphere.io/kubesphere/pkg/informers"
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/clusterrole"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/clusterrolebinding"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/globalrole"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/globalrolebinding"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/role"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/rolebinding"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/workspacerole"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/workspacerolebinding"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
)
@@ -87,45 +95,55 @@ type AccessManagementInterface interface {
}
type amOperator struct {
resourceGetter *resourcev1alpha3.ResourceGetter
ksclient kubesphere.Interface
k8sclient kubernetes.Interface
globalRoleBindingGetter resourcev1alpha3.Interface
workspaceRoleBindingGetter resourcev1alpha3.Interface
clusterRoleBindingGetter resourcev1alpha3.Interface
roleBindingGetter resourcev1alpha3.Interface
globalRoleGetter resourcev1alpha3.Interface
workspaceRoleGetter resourcev1alpha3.Interface
clusterRoleGetter resourcev1alpha3.Interface
roleGetter resourcev1alpha3.Interface
devopsProjectLister devopslisters.DevOpsProjectLister
namespaceLister listersv1.NamespaceLister
ksclient kubesphere.Interface
k8sclient kubernetes.Interface
}
func NewReadOnlyOperator(factory informers.InformerFactory) AccessManagementInterface {
return &amOperator{
resourceGetter: resourcev1alpha3.NewResourceGetter(factory),
globalRoleBindingGetter: globalrolebinding.New(factory.KubeSphereSharedInformerFactory()),
workspaceRoleBindingGetter: workspacerolebinding.New(factory.KubeSphereSharedInformerFactory()),
clusterRoleBindingGetter: clusterrolebinding.New(factory.KubernetesSharedInformerFactory()),
roleBindingGetter: rolebinding.New(factory.KubernetesSharedInformerFactory()),
globalRoleGetter: globalrole.New(factory.KubeSphereSharedInformerFactory()),
workspaceRoleGetter: workspacerole.New(factory.KubeSphereSharedInformerFactory()),
clusterRoleGetter: clusterrole.New(factory.KubernetesSharedInformerFactory()),
roleGetter: role.New(factory.KubernetesSharedInformerFactory()),
devopsProjectLister: factory.KubeSphereSharedInformerFactory().Devops().V1alpha3().DevOpsProjects().Lister(),
namespaceLister: factory.KubernetesSharedInformerFactory().Core().V1().Namespaces().Lister(),
}
}
func NewOperator(factory informers.InformerFactory, ksclient kubesphere.Interface, k8sclient kubernetes.Interface) AccessManagementInterface {
return &amOperator{
resourceGetter: resourcev1alpha3.NewResourceGetter(factory),
ksclient: ksclient,
k8sclient: k8sclient,
}
func NewOperator(ksClient kubesphere.Interface, k8sClient kubernetes.Interface, factory informers.InformerFactory) AccessManagementInterface {
amOperator := NewReadOnlyOperator(factory).(*amOperator)
amOperator.ksclient = ksClient
amOperator.k8sclient = k8sClient
return amOperator
}
func (am *amOperator) GetGlobalRoleOfUser(username string) (*iamv1alpha2.GlobalRole, error) {
userRoleBindings, err := am.ListGlobalRoleBindings(username)
if len(userRoleBindings) > 0 {
role, err := am.GetGlobalRole(userRoleBindings[0].RoleRef.Name)
globalRoleBindings, err := am.ListGlobalRoleBindings(username)
if len(globalRoleBindings) > 0 {
// Usually, only one globalRoleBinding will be found which is created from ks-console.
if len(globalRoleBindings) > 1 {
klog.Warningf("conflict global role binding, username: %s", username)
}
globalRole, err := am.GetGlobalRole(globalRoleBindings[0].RoleRef.Name)
if err != nil {
klog.Error(err)
return nil, err
}
if len(userRoleBindings) > 1 {
klog.Warningf("conflict global role binding, username: %s", username)
}
out := role.DeepCopy()
if out.Annotations == nil {
out.Annotations = make(map[string]string, 0)
}
out.Annotations[iamv1alpha2.GlobalRoleAnnotation] = role.Name
return out, nil
return globalRole, nil
}
err = errors.NewNotFound(iamv1alpha2.Resource(iamv1alpha2.ResourcesSingularGlobalRoleBinding), username)
@@ -244,7 +262,7 @@ func (am *amOperator) GetClusterRoleOfUser(username string) (*rbacv1.ClusterRole
}
func (am *amOperator) ListWorkspaceRoleBindings(username string, groups []string, workspace string) ([]*iamv1alpha2.WorkspaceRoleBinding, error) {
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralWorkspaceRoleBinding, "", query.New())
roleBindings, err := am.workspaceRoleBindingGetter.List("", query.New())
if err != nil {
return nil, err
@@ -265,8 +283,7 @@ func (am *amOperator) ListWorkspaceRoleBindings(username string, groups []string
func (am *amOperator) ListClusterRoleBindings(username string) ([]*rbacv1.ClusterRoleBinding, error) {
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralClusterRoleBinding, "", query.New())
roleBindings, err := am.clusterRoleBindingGetter.List("", query.New())
if err != nil {
return nil, err
}
@@ -283,14 +300,12 @@ func (am *amOperator) ListClusterRoleBindings(username string) ([]*rbacv1.Cluste
}
func (am *amOperator) ListGlobalRoleBindings(username string) ([]*iamv1alpha2.GlobalRoleBinding, error) {
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralGlobalRoleBinding, "", query.New())
roleBindings, err := am.globalRoleBindingGetter.List("", query.New())
if err != nil {
return nil, err
}
result := make([]*iamv1alpha2.GlobalRoleBinding, 0)
for _, obj := range roleBindings.Items {
roleBinding := obj.(*iamv1alpha2.GlobalRoleBinding)
if contains(roleBinding.Subjects, username, nil) {
@@ -302,7 +317,7 @@ func (am *amOperator) ListGlobalRoleBindings(username string) ([]*iamv1alpha2.Gl
}
func (am *amOperator) ListRoleBindings(username string, groups []string, namespace string) ([]*rbacv1.RoleBinding, error) {
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralRoleBinding, namespace, query.New())
roleBindings, err := am.roleBindingGetter.List(namespace, query.New())
if err != nil {
klog.Error(err)
return nil, err
@@ -335,23 +350,23 @@ func contains(subjects []rbacv1.Subject, username string, groups []string) bool
}
func (am *amOperator) ListRoles(namespace string, query *query.Query) (*api.ListResult, error) {
return am.resourceGetter.List(iamv1alpha2.ResourcesPluralRole, namespace, query)
return am.roleGetter.List(namespace, query)
}
func (am *amOperator) ListClusterRoles(query *query.Query) (*api.ListResult, error) {
return am.resourceGetter.List(iamv1alpha2.ResourcesPluralClusterRole, "", query)
return am.clusterRoleGetter.List("", query)
}
func (am *amOperator) ListWorkspaceRoles(queryParam *query.Query) (*api.ListResult, error) {
return am.resourceGetter.List(iamv1alpha2.ResourcesPluralWorkspaceRole, "", queryParam)
return am.workspaceRoleGetter.List("", queryParam)
}
func (am *amOperator) ListGlobalRoles(query *query.Query) (*api.ListResult, error) {
return am.resourceGetter.List(iamv1alpha2.ResourcesPluralGlobalRole, "", query)
return am.globalRoleGetter.List("", query)
}
func (am *amOperator) GetGlobalRole(globalRole string) (*iamv1alpha2.GlobalRole, error) {
obj, err := am.resourceGetter.Get(iamv1alpha2.ResourcesPluralGlobalRole, "", globalRole)
obj, err := am.globalRoleGetter.Get("", globalRole)
if err != nil {
klog.Error(err)
return nil, err
@@ -938,7 +953,7 @@ func (am *amOperator) GetRoleReferenceRules(roleRef rbacv1.RoleRef, namespace st
}
func (am *amOperator) GetWorkspaceRole(workspace string, name string) (*iamv1alpha2.WorkspaceRole, error) {
obj, err := am.resourceGetter.Get(iamv1alpha2.ResourcesPluralWorkspaceRole, "", name)
obj, err := am.workspaceRoleGetter.Get("", name)
if err != nil {
klog.Error(err)
return nil, err
@@ -956,7 +971,7 @@ func (am *amOperator) GetWorkspaceRole(workspace string, name string) (*iamv1alp
}
func (am *amOperator) GetNamespaceRole(namespace string, name string) (*rbacv1.Role, error) {
obj, err := am.resourceGetter.Get(iamv1alpha2.ResourcesPluralRole, namespace, name)
obj, err := am.roleGetter.Get(namespace, name)
if err != nil {
klog.Error(err)
return nil, err
@@ -965,7 +980,7 @@ func (am *amOperator) GetNamespaceRole(namespace string, name string) (*rbacv1.R
}
func (am *amOperator) GetClusterRole(name string) (*rbacv1.ClusterRole, error) {
obj, err := am.resourceGetter.Get(iamv1alpha2.ResourcesPluralClusterRole, "", name)
obj, err := am.clusterRoleGetter.Get("", name)
if err != nil {
klog.Error(err)
return nil, err
@@ -973,28 +988,25 @@ func (am *amOperator) GetClusterRole(name string) (*rbacv1.ClusterRole, error) {
return obj.(*rbacv1.ClusterRole), nil
}
func (am *amOperator) GetDevOpsRelatedNamespace(devops string) (string, error) {
obj, err := am.resourceGetter.Get(devopsv1alpha3.ResourcePluralDevOpsProject, "", devops)
devopsProject, err := am.devopsProjectLister.Get(devops)
if err != nil {
klog.Error(err)
return "", err
}
devopsProject := obj.(*devopsv1alpha3.DevOpsProject)
return devopsProject.Status.AdminNamespace, nil
}
func (am *amOperator) GetDevOpsControlledWorkspace(devops string) (string, error) {
obj, err := am.resourceGetter.Get(devopsv1alpha3.ResourcePluralDevOpsProject, "", devops)
devopsProject, err := am.devopsProjectLister.Get(devops)
if err != nil {
klog.Error(err)
return "", err
}
devopsProject := obj.(*devopsv1alpha3.DevOpsProject)
return devopsProject.Labels[tenantv1alpha1.WorkspaceLabel], nil
}
func (am *amOperator) GetNamespaceControlledWorkspace(namespace string) (string, error) {
obj, err := am.resourceGetter.Get("namespaces", "", namespace)
ns, err := am.namespaceLister.Get(namespace)
if err != nil {
if errors.IsNotFound(err) {
return "", nil
@@ -1002,24 +1014,22 @@ func (am *amOperator) GetNamespaceControlledWorkspace(namespace string) (string,
klog.Error(err)
return "", err
}
ns := obj.(*corev1.Namespace)
return ns.Labels[tenantv1alpha1.WorkspaceLabel], nil
}
func (am *amOperator) ListGroupWorkspaceRoleBindings(workspace, group string) ([]*iamv1alpha2.WorkspaceRoleBinding, error) {
q := workspaceQuery(workspace)
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralWorkspaceRoleBinding, "", q)
queryParam := query.New()
queryParam.LabelSelector = labels.FormatLabels(map[string]string{tenantv1alpha1.WorkspaceLabel: workspace})
roleBindings, err := am.workspaceRoleBindingGetter.List("", queryParam)
if err != nil {
return nil, err
}
result := make([]*iamv1alpha2.WorkspaceRoleBinding, 0)
for _, obj := range roleBindings.Items {
roleBinding := obj.(*iamv1alpha2.WorkspaceRoleBinding)
inSpecifiedWorkspace := workspace == "" || roleBinding.Labels[tenantv1alpha1.WorkspaceLabel] == workspace
if containsgroup(roleBinding.Subjects, group) && inSpecifiedWorkspace {
if containsGroup(roleBinding.Subjects, group) && inSpecifiedWorkspace {
result = append(result, roleBinding)
}
}
@@ -1062,15 +1072,13 @@ func (am *amOperator) DeleteWorkspaceRoleBinding(workspaceName, name string) err
}
func (am *amOperator) ListGroupRoleBindings(workspace, group string) ([]*rbacv1.RoleBinding, error) {
q := workspaceQuery(workspace)
namespaces, err := am.resourceGetter.List("namespaces", "", q)
namespaces, err := am.namespaceLister.List(labels.SelectorFromSet(labels.Set{tenantv1alpha1.WorkspaceLabel: workspace}))
if err != nil {
return nil, err
}
result := make([]*rbacv1.RoleBinding, 0)
for _, ns := range namespaces.Items {
namespace := ns.(*corev1.Namespace)
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralRoleBinding, namespace.Name, query.New())
for _, namespace := range namespaces {
roleBindings, err := am.roleBindingGetter.List(namespace.Name, query.New())
if err != nil {
klog.Error(err)
return nil, err
@@ -1078,7 +1086,7 @@ func (am *amOperator) ListGroupRoleBindings(workspace, group string) ([]*rbacv1.
for _, obj := range roleBindings.Items {
roleBinding := obj.(*rbacv1.RoleBinding)
if containsgroup(roleBinding.Subjects, group) {
if containsGroup(roleBinding.Subjects, group) {
result = append(result, roleBinding)
}
}
@@ -1087,23 +1095,20 @@ func (am *amOperator) ListGroupRoleBindings(workspace, group string) ([]*rbacv1.
}
func (am *amOperator) ListGroupDevOpsRoleBindings(workspace, group string) ([]*rbacv1.RoleBinding, error) {
q := workspaceQuery(workspace)
namespaces, err := am.resourceGetter.List(devopsv1alpha3.ResourcePluralDevOpsProject, "", q)
devOpsProjects, err := am.devopsProjectLister.List(labels.SelectorFromSet(labels.Set{tenantv1alpha1.WorkspaceLabel: workspace}))
if err != nil {
return nil, err
}
result := make([]*rbacv1.RoleBinding, 0)
for _, ns := range namespaces.Items {
namespace := ns.(*devopsv1alpha3.DevOpsProject)
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralRoleBinding, namespace.Name, query.New())
for _, devOpsProject := range devOpsProjects {
roleBindings, err := am.roleBindingGetter.List(devOpsProject.Name, query.New())
if err != nil {
klog.Error(err)
return nil, err
}
for _, obj := range roleBindings.Items {
roleBinding := obj.(*rbacv1.RoleBinding)
if containsgroup(roleBinding.Subjects, group) {
if containsGroup(roleBinding.Subjects, group) {
result = append(result, roleBinding)
}
}
@@ -1143,7 +1148,7 @@ func (am *amOperator) DeleteRoleBinding(namespace, name string) error {
return am.k8sclient.RbacV1().RoleBindings(namespace).Delete(name, metav1.NewDeleteOptions(0))
}
func containsgroup(subjects []rbacv1.Subject, group string) bool {
func containsGroup(subjects []rbacv1.Subject, group string) bool {
for _, subject := range subjects {
if subject.Kind == rbacv1.GroupKind && subject.Name == group {
return true
@@ -1151,9 +1156,3 @@ func containsgroup(subjects []rbacv1.Subject, group string) bool {
}
return false
}
func workspaceQuery(workspace string) *query.Query {
q := query.New()
q.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", tenantv1alpha1.WorkspaceLabel, workspace))
return q
}

View File

@@ -1,175 +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 im
import (
"fmt"
"net/mail"
"github.com/go-ldap/ldap"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
authuser "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/constants"
)
var (
AuthRateLimitExceeded = fmt.Errorf("auth rate limit exceeded")
AuthFailedIncorrectPassword = fmt.Errorf("incorrect password")
AuthFailedAccountIsNotActive = fmt.Errorf("account is not active")
AuthFailedIdentityMappingNotMatch = fmt.Errorf("identity mapping not match")
)
type PasswordAuthenticator interface {
Authenticate(username, password string) (authuser.Info, error)
}
type passwordAuthenticator struct {
ksClient kubesphere.Interface
userLister iamv1alpha2listers.UserLister
options *authoptions.AuthenticationOptions
}
func NewPasswordAuthenticator(ksClient kubesphere.Interface,
userLister iamv1alpha2listers.UserLister,
options *authoptions.AuthenticationOptions) PasswordAuthenticator {
return &passwordAuthenticator{
ksClient: ksClient,
userLister: userLister,
options: options}
}
func (im *passwordAuthenticator) Authenticate(username, password string) (authuser.Info, error) {
user, err := im.searchUser(username)
if err != nil {
// internal error
if !errors.IsNotFound(err) {
klog.Error(err)
return nil, err
}
}
providerOptions, ldapProvider := im.getLdapProvider()
// no identity provider
// even auth failed, still return username to record login attempt
if user == nil && (providerOptions == nil || providerOptions.MappingMethod != oauth.MappingMethodAuto) {
return nil, AuthFailedIncorrectPassword
}
if user != nil && user.Status.State != iamv1alpha2.UserActive {
if user.Status.State == iamv1alpha2.UserAuthLimitExceeded {
klog.Errorf("%s, username: %s", AuthRateLimitExceeded, username)
return nil, AuthRateLimitExceeded
} else {
klog.Errorf("%s, username: %s", AuthFailedAccountIsNotActive, username)
return nil, AuthFailedAccountIsNotActive
}
}
// able to login using the locally principal admin account and password in case of a disruption of LDAP services.
if ldapProvider != nil && username != constants.AdminUserName {
if providerOptions.MappingMethod == oauth.MappingMethodLookup &&
(user == nil || user.Labels[iamv1alpha2.IdentifyProviderLabel] != providerOptions.Name) {
klog.Error(AuthFailedIdentityMappingNotMatch)
return nil, AuthFailedIdentityMappingNotMatch
}
if providerOptions.MappingMethod == oauth.MappingMethodAuto &&
user != nil && user.Labels[iamv1alpha2.IdentifyProviderLabel] != providerOptions.Name {
klog.Error(AuthFailedIdentityMappingNotMatch)
return nil, AuthFailedIdentityMappingNotMatch
}
authenticated, err := ldapProvider.Authenticate(username, password)
if err != nil {
klog.Error(err)
if ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials) || ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
return nil, AuthFailedIncorrectPassword
} else {
return nil, err
}
}
if authenticated != nil && user == nil {
authenticated.Labels = map[string]string{iamv1alpha2.IdentifyProviderLabel: providerOptions.Name}
if authenticated, err = im.ksClient.IamV1alpha2().Users().Create(authenticated); err != nil {
klog.Error(err)
return nil, err
}
}
if authenticated != nil {
return &authuser.DefaultInfo{
Name: authenticated.Name,
UID: string(authenticated.UID),
}, nil
}
}
if checkPasswordHash(password, user.Spec.EncryptedPassword) {
return &authuser.DefaultInfo{
Name: user.Name,
UID: string(user.UID),
Groups: user.Spec.Groups,
}, nil
}
return nil, AuthFailedIncorrectPassword
}
func (im *passwordAuthenticator) searchUser(username string) (*iamv1alpha2.User, error) {
if _, err := mail.ParseAddress(username); err != nil {
return im.userLister.Get(username)
} else {
users, err := im.userLister.List(labels.Everything())
if err != nil {
klog.Error(err)
return nil, err
}
for _, find := range users {
if find.Spec.Email == username {
return find, nil
}
}
}
return nil, errors.NewNotFound(iamv1alpha2.Resource("user"), username)
}
func (im *passwordAuthenticator) getLdapProvider() (*oauth.IdentityProviderOptions, identityprovider.LdapProvider) {
for _, options := range im.options.OAuthOptions.IdentityProviders {
if options.Type == identityprovider.LdapIdentityProvider {
if provider, err := identityprovider.NewLdapProvider(options.Provider); err != nil {
klog.Error(err)
} else {
return &options, provider
}
}
}
return nil, nil
}

View File

@@ -16,7 +16,7 @@ limitations under the License.
package im
import (
"golang.org/x/crypto/bcrypt"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
@@ -24,8 +24,8 @@ import (
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/query"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/informers"
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
"kubesphere.io/kubesphere/pkg/models/auth"
resources "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
)
type IdentityManagementInterface interface {
@@ -35,41 +35,40 @@ type IdentityManagementInterface interface {
UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error)
DescribeUser(username string) (*iamv1alpha2.User, error)
ModifyPassword(username string, password string) error
ListLoginRecords(query *query.Query) (*api.ListResult, error)
ListLoginRecords(username string, query *query.Query) (*api.ListResult, error)
PasswordVerify(username string, password string) error
}
func NewOperator(ksClient kubesphere.Interface, factory informers.InformerFactory, options *authoptions.AuthenticationOptions) IdentityManagementInterface {
im := &defaultIMOperator{
ksClient: ksClient,
resourceGetter: resourcev1alpha3.NewResourceGetter(factory),
options: options,
func NewOperator(ksClient kubesphere.Interface, userGetter resources.Interface, loginRecordGetter resources.Interface, options *authoptions.AuthenticationOptions) IdentityManagementInterface {
im := &imOperator{
ksClient: ksClient,
userGetter: userGetter,
loginRecordGetter: loginRecordGetter,
options: options,
}
return im
}
type defaultIMOperator struct {
ksClient kubesphere.Interface
resourceGetter *resourcev1alpha3.ResourceGetter
options *authoptions.AuthenticationOptions
type imOperator struct {
ksClient kubesphere.Interface
userGetter resources.Interface
loginRecordGetter resources.Interface
options *authoptions.AuthenticationOptions
}
func (im *defaultIMOperator) UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", user.Name)
// UpdateUser returns user information after update.
func (im *imOperator) UpdateUser(new *iamv1alpha2.User) (*iamv1alpha2.User, error) {
old, err := im.fetch(new.Name)
if err != nil {
klog.Error(err)
return nil, err
}
old := obj.(*iamv1alpha2.User).DeepCopy()
if user.Annotations == nil {
user.Annotations = make(map[string]string, 0)
if old.Annotations == nil {
old.Annotations = make(map[string]string, 0)
}
user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] = old.Annotations[iamv1alpha2.PasswordEncryptedAnnotation]
user.Spec.EncryptedPassword = old.Spec.EncryptedPassword
user.Status = old.Status
updated, err := im.ksClient.IamV1alpha2().Users().Update(user)
// keep encrypted password
new.Spec.EncryptedPassword = old.Spec.EncryptedPassword
updated, err := im.ksClient.IamV1alpha2().Users().Update(old)
if err != nil {
klog.Error(err)
return nil, err
@@ -77,18 +76,23 @@ func (im *defaultIMOperator) UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.Us
return ensurePasswordNotOutput(updated), nil
}
func (im *defaultIMOperator) ModifyPassword(username string, password string) error {
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", username)
func (im *imOperator) fetch(username string) (*iamv1alpha2.User, error) {
obj, err := im.userGetter.Get("", username)
if err != nil {
klog.Error(err)
return nil, err
}
user := obj.(*iamv1alpha2.User).DeepCopy()
return user, nil
}
func (im *imOperator) ModifyPassword(username string, password string) error {
user, err := im.fetch(username)
if err != nil {
klog.Error(err)
return err
}
user := obj.(*iamv1alpha2.User).DeepCopy()
delete(user.Annotations, iamv1alpha2.PasswordEncryptedAnnotation)
user.Spec.EncryptedPassword = password
_, err = im.ksClient.IamV1alpha2().Users().Update(user)
if err != nil {
klog.Error(err)
@@ -97,13 +101,12 @@ func (im *defaultIMOperator) ModifyPassword(username string, password string) er
return nil
}
func (im *defaultIMOperator) ListUsers(query *query.Query) (result *api.ListResult, err error) {
result, err = im.resourceGetter.List(iamv1alpha2.ResourcesPluralUser, "", query)
func (im *imOperator) ListUsers(query *query.Query) (result *api.ListResult, err error) {
result, err = im.userGetter.List("", query)
if err != nil {
klog.Error(err)
return nil, err
}
items := make([]interface{}, 0)
for _, item := range result.Items {
user := item.(*iamv1alpha2.User)
@@ -114,40 +117,34 @@ func (im *defaultIMOperator) ListUsers(query *query.Query) (result *api.ListResu
return result, nil
}
func (im *defaultIMOperator) PasswordVerify(username string, password string) error {
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", username)
func (im *imOperator) PasswordVerify(username string, password string) error {
obj, err := im.userGetter.Get("", username)
if err != nil {
klog.Error(err)
return err
}
user := obj.(*iamv1alpha2.User)
if checkPasswordHash(password, user.Spec.EncryptedPassword) {
return nil
if err = auth.PasswordVerify(user.Spec.EncryptedPassword, password); err != nil {
return err
}
return AuthFailedIncorrectPassword
return nil
}
func checkPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
func (im *defaultIMOperator) DescribeUser(username string) (*iamv1alpha2.User, error) {
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", username)
func (im *imOperator) DescribeUser(username string) (*iamv1alpha2.User, error) {
obj, err := im.userGetter.Get("", username)
if err != nil {
klog.Error(err)
return nil, err
}
user := obj.(*iamv1alpha2.User)
return ensurePasswordNotOutput(user), nil
}
func (im *defaultIMOperator) DeleteUser(username string) error {
func (im *imOperator) DeleteUser(username string) error {
return im.ksClient.IamV1alpha2().Users().Delete(username, metav1.NewDeleteOptions(0))
}
func (im *defaultIMOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
func (im *imOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
user, err := im.ksClient.IamV1alpha2().Users().Create(user)
if err != nil {
klog.Error(err)
@@ -156,8 +153,9 @@ func (im *defaultIMOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.Us
return user, nil
}
func (im *defaultIMOperator) ListLoginRecords(query *query.Query) (*api.ListResult, error) {
result, err := im.resourceGetter.List(iamv1alpha2.ResourcesPluralLoginRecord, "", query)
func (im *imOperator) ListLoginRecords(username string, q *query.Query) (*api.ListResult, error) {
q.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", iamv1alpha2.UserReferenceLabel, username))
result, err := im.loginRecordGetter.List("", q)
if err != nil {
klog.Error(err)
return nil, err

View File

@@ -15,25 +15,3 @@ limitations under the License.
*/
package im
import (
"golang.org/x/crypto/bcrypt"
"testing"
)
func TestEncryptPassword(t *testing.T) {
password := "P@88w0rd"
encryptedPassword, err := hashPassword(password)
if err != nil {
t.Fatal(err)
}
if !checkPasswordHash(password, encryptedPassword) {
t.Fatal(err)
}
t.Log(encryptedPassword)
}
func hashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
return string(bytes), err
}

View File

@@ -1,79 +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 im
import (
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/utils/net"
"net/http"
"strings"
)
type LoginRecorder interface {
RecordLogin(username string, loginType iamv1alpha2.LoginType, provider string, authErr error, req *http.Request) error
}
type loginRecorder struct {
ksClient kubesphere.Interface
}
func NewLoginRecorder(ksClient kubesphere.Interface) LoginRecorder {
return &loginRecorder{
ksClient: ksClient,
}
}
func (l *loginRecorder) RecordLogin(username string, loginType iamv1alpha2.LoginType, provider string, authErr error, req *http.Request) error {
// This is a temporary solution in case of user login with email,
// '@' is not allowed in Kubernetes object name.
username = strings.Replace(username, "@", "-", -1)
loginEntry := &iamv1alpha2.LoginRecord{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("%s-", username),
Labels: map[string]string{
iamv1alpha2.UserReferenceLabel: username,
},
},
Spec: iamv1alpha2.LoginRecordSpec{
Type: loginType,
Provider: provider,
Success: true,
Reason: iamv1alpha2.AuthenticatedSuccessfully,
SourceIP: net.GetRequestIP(req),
UserAgent: req.UserAgent(),
},
}
if authErr != nil {
loginEntry.Spec.Success = false
loginEntry.Spec.Reason = authErr.Error()
}
_, err := l.ksClient.IamV1alpha2().LoginRecords().Create(loginEntry)
if err != nil {
klog.Error(err)
return err
}
return nil
}

View File

@@ -1,146 +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 im
import (
"fmt"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"time"
)
type TokenManagementInterface interface {
// Verify verifies a token, and return a User if it's a valid token, otherwise return error
Verify(token string) (user.Info, error)
// IssueTo issues a token a User, return error if issuing process failed
IssueTo(user user.Info) (*oauth.Token, error)
}
type tokenOperator struct {
issuer token.Issuer
options *authoptions.AuthenticationOptions
cache cache.Interface
}
func NewTokenOperator(cache cache.Interface, options *authoptions.AuthenticationOptions) TokenManagementInterface {
operator := &tokenOperator{
issuer: token.NewTokenIssuer(options.JwtSecret, options.MaximumClockSkew),
options: options,
cache: cache,
}
return operator
}
func (t tokenOperator) Verify(tokenStr string) (user.Info, error) {
authenticated, tokenType, err := t.issuer.Verify(tokenStr)
if err != nil {
klog.Error(err)
return nil, err
}
if t.options.OAuthOptions.AccessTokenMaxAge == 0 ||
tokenType == token.StaticToken {
return authenticated, nil
}
if err := t.tokenCacheValidate(authenticated.GetName(), tokenStr); err != nil {
klog.Error(err)
return nil, err
}
return authenticated, nil
}
func (t tokenOperator) IssueTo(user user.Info) (*oauth.Token, error) {
accessTokenExpiresIn := t.options.OAuthOptions.AccessTokenMaxAge
refreshTokenExpiresIn := accessTokenExpiresIn + t.options.OAuthOptions.AccessTokenInactivityTimeout
accessToken, err := t.issuer.IssueTo(user, token.AccessToken, accessTokenExpiresIn)
if err != nil {
klog.Error(err)
return nil, err
}
refreshToken, err := t.issuer.IssueTo(user, token.RefreshToken, refreshTokenExpiresIn)
if err != nil {
klog.Error(err)
return nil, err
}
result := &oauth.Token{
AccessToken: accessToken,
TokenType: "Bearer",
RefreshToken: refreshToken,
ExpiresIn: int(accessTokenExpiresIn.Seconds()),
}
if !t.options.MultipleLogin {
if err = t.revokeAllUserTokens(user.GetName()); err != nil {
klog.Error(err)
return nil, err
}
}
if accessTokenExpiresIn > 0 {
if err = t.cacheToken(user.GetName(), accessToken, accessTokenExpiresIn); err != nil {
klog.Error(err)
return nil, err
}
if err = t.cacheToken(user.GetName(), refreshToken, refreshTokenExpiresIn); err != nil {
klog.Error(err)
return nil, err
}
}
return result, nil
}
func (t tokenOperator) revokeAllUserTokens(username string) error {
pattern := fmt.Sprintf("kubesphere:user:%s:token:*", username)
if keys, err := t.cache.Keys(pattern); err != nil {
klog.Error(err)
return err
} else if len(keys) > 0 {
if err := t.cache.Del(keys...); err != nil {
klog.Error(err)
return err
}
}
return nil
}
func (t tokenOperator) tokenCacheValidate(username, token string) error {
key := fmt.Sprintf("kubesphere:user:%s:token:%s", username, token)
if exist, err := t.cache.Exists(key); err != nil {
return err
} else if !exist {
return fmt.Errorf("token not found in cache")
}
return nil
}
func (t tokenOperator) cacheToken(username, token string, duration time.Duration) error {
key := fmt.Sprintf("kubesphere:user:%s:token:%s", username, token)
if err := t.cache.Set(key, token, duration); err != nil {
klog.Error(err)
return err
}
return nil
}