Merge pull request #2270 from wansir/ldap

fix: synchronize users to LDAP
This commit is contained in:
KubeSphere CI Bot
2020-07-02 17:54:44 +08:00
committed by GitHub
9 changed files with 126 additions and 106 deletions

View File

@@ -43,6 +43,8 @@ import (
userlister "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models/kubeconfig"
ldapclient "kubesphere.io/kubesphere/pkg/simple/client/ldap"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"reflect"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"strconv"
@@ -67,6 +69,7 @@ type Controller struct {
cmSynced cache.InformerSynced
fedUserCache cache.Store
fedUserController cache.Controller
ldapClient ldapclient.Interface
// workqueue is a rate limited work queue. This is used to queue work to be
// processed instead of performing it as soon as a change happens. This
// means we can ensure we only process a fixed amount of resources at a
@@ -81,7 +84,7 @@ type Controller struct {
func NewController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface,
config *rest.Config, userInformer userinformer.UserInformer, fedUserCache cache.Store, fedUserController cache.Controller,
configMapInformer corev1informers.ConfigMapInformer, multiClusterEnabled bool) *Controller {
configMapInformer corev1informers.ConfigMapInformer, ldapClient ldapclient.Interface, multiClusterEnabled bool) *Controller {
// Create event broadcaster
// Add sample-controller types to the default Kubernetes Scheme so Events can be
// logged for sample-controller types.
@@ -106,6 +109,7 @@ func NewController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface
cmSynced: configMapInformer.Informer().HasSynced,
fedUserCache: fedUserCache,
fedUserController: fedUserController,
ldapClient: ldapClient,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Users"),
recorder: recorder,
multiClusterEnabled: multiClusterEnabled,
@@ -239,6 +243,48 @@ func (c *Controller) reconcile(key string) error {
return err
}
// name of your custom finalizer
finalizer := "finalizers.kubesphere.io/users"
if user.ObjectMeta.DeletionTimestamp.IsZero() {
// The object is not being deleted, so if it does not have our finalizer,
// then lets add the finalizer and update the object.
if !sliceutil.HasString(user.Finalizers, finalizer) {
user.ObjectMeta.Finalizers = append(user.ObjectMeta.Finalizers, finalizer)
if user, err = c.ksClient.IamV1alpha2().Users().Update(user); err != nil {
return err
}
}
} else {
// The object is being deleted
if sliceutil.HasString(user.ObjectMeta.Finalizers, finalizer) {
klog.V(4).Infof("delete user %s", key)
if err = c.ldapClient.Delete(key); err != nil && err != ldapclient.ErrUserNotExists {
klog.Error(err)
return err
}
// remove our finalizer from the list and update it.
user.Finalizers = sliceutil.RemoveString(user.ObjectMeta.Finalizers, func(item string) bool {
return item == finalizer
})
if _, err := c.ksClient.IamV1alpha2().Users().Update(user); err != nil {
return err
}
}
// Our finalizer has finished, so the reconciler can do nothing.
return nil
}
if err = c.ldapSync(user); err != nil {
klog.Error(err)
return err
}
if user, err = c.ensurePasswordIsEncrypted(user); err != nil {
klog.Error(err)
return err
@@ -269,9 +315,9 @@ func (c *Controller) Start(stopCh <-chan struct{}) error {
}
func (c *Controller) ensurePasswordIsEncrypted(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
encrypted, err := strconv.ParseBool(user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation])
encrypted, _ := strconv.ParseBool(user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation])
// password is not encrypted
if err != nil || !encrypted {
if !encrypted {
password, err := encrypt(user.Spec.EncryptedPassword)
if err != nil {
klog.Error(err)
@@ -282,7 +328,6 @@ func (c *Controller) ensurePasswordIsEncrypted(user *iamv1alpha2.User) (*iamv1al
if user.Annotations == nil {
user.Annotations = make(map[string]string, 0)
}
user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] = "true"
user.Status.State = iamv1alpha2.UserActive
return c.ksClient.IamV1alpha2().Users().Update(user)
@@ -419,6 +464,25 @@ func (c *Controller) updateFederatedUser(fedUser *iamv1alpha2.FederatedUser) err
return nil
}
func (c *Controller) ldapSync(user *iamv1alpha2.User) error {
encrypted, _ := strconv.ParseBool(user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation])
if encrypted {
return nil
}
_, err := c.ldapClient.Get(user.Name)
if err != nil {
if err == ldapclient.ErrUserNotExists {
klog.V(4).Infof("create user %s", user.Name)
return c.ldapClient.Create(user)
}
klog.Error(err)
return err
} else {
klog.V(4).Infof("update user %s", user.Name)
return c.ldapClient.Update(user)
}
}
func encrypt(password string) (string, error) {
// when user is already mapped to another identity, password is empty by default
// unable to log in directly until password reset

View File

@@ -29,6 +29,7 @@ import (
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
ksinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
ldapclient "kubesphere.io/kubesphere/pkg/simple/client/ldap"
"reflect"
"testing"
"time"
@@ -81,6 +82,7 @@ func newUser(name string) *iamv1alpha2.User {
func (f *fixture) newController() (*Controller, ksinformers.SharedInformerFactory, kubeinformers.SharedInformerFactory) {
f.ksclient = fake.NewSimpleClientset(f.objects...)
f.k8sclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
ldapClient := ldapclient.NewSimpleLdap()
ksinformers := ksinformers.NewSharedInformerFactory(f.ksclient, noResyncPeriodFunc())
k8sinformers := kubeinformers.NewSharedInformerFactory(f.k8sclient, noResyncPeriodFunc())
@@ -92,7 +94,7 @@ func (f *fixture) newController() (*Controller, ksinformers.SharedInformerFactor
}
}
c := NewController(f.k8sclient, f.ksclient, nil, ksinformers.Iam().V1alpha2().Users(), nil, nil, k8sinformers.Core().V1().ConfigMaps(), false)
c := NewController(f.k8sclient, f.ksclient, nil, ksinformers.Iam().V1alpha2().Users(), nil, nil, k8sinformers.Core().V1().ConfigMaps(), ldapClient, false)
c.userSynced = alwaysReady
c.recorder = &record.FakeRecorder{}
@@ -208,6 +210,7 @@ func filterInformerActions(actions []core.Action) []core.Action {
var ret []core.Action
for _, action := range actions {
if action.Matches("list", "users") ||
action.Matches("list", "configmaps") ||
action.Matches("watch", "users") {
continue
}
@@ -219,9 +222,14 @@ func filterInformerActions(actions []core.Action) []core.Action {
func (f *fixture) expectUpdateUserStatusAction(user *iamv1alpha2.User) {
expect := user.DeepCopy()
expect.Finalizers = []string{"finalizers.kubesphere.io/users"}
action := core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect)
f.actions = append(f.actions, action)
expect = expect.DeepCopy()
expect.Status.State = iamv1alpha2.UserActive
expect.Annotations = map[string]string{iamv1alpha2.PasswordEncryptedAnnotation: "true"}
action := core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect)
action = core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect)
f.actions = append(f.actions, action)
}