decoupling Jenkins and LDAP

Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
hongming
2021-03-29 16:11:33 +08:00
parent ac275b6e98
commit 3e8597cf76
11 changed files with 674 additions and 163 deletions

View File

@@ -23,6 +23,8 @@ import (
"reflect"
"time"
utilwait "k8s.io/apimachinery/pkg/util/wait"
"kubesphere.io/kubesphere/pkg/controller/utils/controller"
"golang.org/x/crypto/bcrypt"
@@ -65,7 +67,10 @@ const (
messageResourceSynced = "User synced successfully"
controllerName = "user-controller"
// user finalizer
finalizer = "finalizers.kubesphere.io/users"
finalizer = "finalizers.kubesphere.io/users"
interval = time.Second
timeout = 15 * time.Second
syncFailMessage = "Failed to sync: %s"
)
type userController struct {
@@ -165,44 +170,42 @@ func (c *userController) reconcile(key string) error {
// 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(context.Background(), user, metav1.UpdateOptions{}); err != nil {
klog.Error(err)
return err
}
}
} else {
// The object is being deleted
if sliceutil.HasString(user.ObjectMeta.Finalizers, finalizer) {
klog.V(4).Infof("delete user %s", key)
// we do not need to delete the user from ldapServer when ldapClient is nil
if c.ldapClient != nil {
if err = c.ldapClient.Delete(key); err != nil && err != ldapclient.ErrUserNotExists {
klog.Error(err)
return err
if err = c.waitForDeleteFromLDAP(key); err != nil {
// ignore timeout error
c.recorder.Event(user, corev1.EventTypeWarning, controller.FailedSynced, fmt.Sprintf(syncFailMessage, err))
}
}
if err = c.deleteRoleBindings(user); err != nil {
klog.Error(err)
c.recorder.Event(user, corev1.EventTypeWarning, controller.FailedSynced, fmt.Sprintf(syncFailMessage, err))
return err
}
if err = c.deleteGroupBindings(user); err != nil {
klog.Error(err)
c.recorder.Event(user, corev1.EventTypeWarning, controller.FailedSynced, fmt.Sprintf(syncFailMessage, err))
return err
}
if c.devopsClient != nil {
// unassign jenkins role, unassign multiple times is allowed
if err = c.unassignDevOpsAdminRole(user); err != nil {
klog.Error(err)
return err
if err = c.waitForUnassignDevOpsAdminRole(user); err != nil {
// ignore timeout error
c.recorder.Event(user, corev1.EventTypeWarning, controller.FailedSynced, fmt.Sprintf(syncFailMessage, err))
}
}
if err = c.deleteLoginRecords(user); err != nil {
klog.Error(err)
c.recorder.Event(user, corev1.EventTypeWarning, controller.FailedSynced, fmt.Sprintf(syncFailMessage, err))
return err
}
@@ -212,6 +215,7 @@ func (c *userController) reconcile(key string) error {
})
if user, err = c.ksClient.IamV1alpha2().Users().Update(context.Background(), user, metav1.UpdateOptions{}); err != nil {
klog.Error(err)
return err
}
}
@@ -222,9 +226,10 @@ func (c *userController) reconcile(key string) error {
// we do not need to sync ldap info when ldapClient is nil
if c.ldapClient != nil {
if err = c.ldapSync(user); err != nil {
klog.Error(err)
return err
// ignore errors if timeout
if err = c.waitForSyncToLDAP(user); err != nil {
// ignore timeout error
c.recorder.Event(user, corev1.EventTypeWarning, controller.FailedSynced, fmt.Sprintf(syncFailMessage, err))
}
}
@@ -242,6 +247,7 @@ func (c *userController) reconcile(key string) error {
// ensure user kubeconfig configmap is created
if err = c.kubeconfig.CreateKubeConfig(user); err != nil {
klog.Error(err)
c.recorder.Event(user, corev1.EventTypeWarning, controller.FailedSynced, fmt.Sprintf(syncFailMessage, err))
return err
}
}
@@ -249,16 +255,16 @@ func (c *userController) reconcile(key string) error {
if c.devopsClient != nil {
// assign jenkins role after user create, assign multiple times is allowed
// used as logged-in users can do anything
if err = c.assignDevOpsAdminRole(user); err != nil {
klog.Error(err)
return err
if err = c.waitForAssignDevOpsAdminRole(user); err != nil {
// ignore timeout error
c.recorder.Event(user, corev1.EventTypeWarning, controller.FailedSynced, fmt.Sprintf(syncFailMessage, err))
}
}
// synchronization through kubefed-controller when multi cluster is enabled
if c.multiClusterEnabled {
if err = c.multiClusterSync(user); err != nil {
klog.Error(err)
c.recorder.Event(user, corev1.EventTypeWarning, controller.FailedSynced, fmt.Sprintf(syncFailMessage, err))
return err
}
}
@@ -280,6 +286,7 @@ func (c *userController) encryptPassword(user *iamv1alpha2.User) (*iamv1alpha2.U
if user.Annotations == nil {
user.Annotations = make(map[string]string)
}
user.Annotations[iamv1alpha2.LastPasswordChangeTimeAnnotation] = time.Now().UTC().Format(time.RFC3339)
// ensure plain text password won't be kept anywhere
delete(user.Annotations, corev1.LastAppliedConfigAnnotation)
return c.ksClient.IamV1alpha2().Users().Update(context.Background(), user, metav1.UpdateOptions{})
@@ -303,7 +310,6 @@ func (c *userController) ensureNotControlledByKubefed(user *iamv1alpha2.User) er
}
func (c *userController) multiClusterSync(user *iamv1alpha2.User) error {
if err := c.ensureNotControlledByKubefed(user); err != nil {
klog.Error(err)
return err
@@ -399,44 +405,71 @@ func (c *userController) updateFederatedUser(fedUser *iamv1alpha2.FederatedUser)
if errors.IsNotFound(err) {
return nil
}
return err
}
return nil
}
func (c *userController) assignDevOpsAdminRole(user *iamv1alpha2.User) error {
if err := c.devopsClient.AssignGlobalRole(modelsdevops.JenkinsAdminRoleName, user.Name); err != nil {
klog.Errorf("%+v", err)
klog.Error(err)
return err
}
return nil
}
func (c *userController) unassignDevOpsAdminRole(user *iamv1alpha2.User) error {
if err := c.devopsClient.UnAssignGlobalRole(modelsdevops.JenkinsAdminRoleName, user.Name); err != nil {
klog.Errorf("%+v", err)
return err
}
return nil
func (c *userController) waitForAssignDevOpsAdminRole(user *iamv1alpha2.User) error {
err := utilwait.PollImmediate(interval, timeout, func() (done bool, err error) {
if err := c.devopsClient.AssignGlobalRole(modelsdevops.JenkinsAdminRoleName, user.Name); err != nil {
klog.Error(err)
return false, err
}
return true, nil
})
return err
}
func (c *userController) ldapSync(user *iamv1alpha2.User) error {
func (c *userController) waitForUnassignDevOpsAdminRole(user *iamv1alpha2.User) error {
err := utilwait.PollImmediate(interval, timeout, func() (done bool, err error) {
if err := c.devopsClient.UnAssignGlobalRole(modelsdevops.JenkinsAdminRoleName, user.Name); err != nil {
return false, err
}
return true, nil
})
return err
}
func (c *userController) waitForSyncToLDAP(user *iamv1alpha2.User) error {
if isEncrypted(user.Spec.EncryptedPassword) {
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)
err := utilwait.PollImmediate(interval, timeout, func() (done bool, err error) {
_, err = c.ldapClient.Get(user.Name)
if err != nil {
if err == ldapclient.ErrUserNotExists {
err = c.ldapClient.Create(user)
if err != nil {
klog.Error(err)
return false, err
}
return true, nil
}
klog.Error(err)
return false, err
}
klog.Error(err)
return err
} else {
klog.V(4).Infof("update user %s", user.Name)
return c.ldapClient.Update(user)
}
err = c.ldapClient.Update(user)
if err != nil {
klog.Error(err)
return false, err
}
return true, nil
})
return err
}
func (c *userController) waitForDeleteFromLDAP(username string) error {
err := utilwait.PollImmediate(interval, timeout, func() (done bool, err error) {
err = c.ldapClient.Delete(username)
if err != nil && err != ldapclient.ErrUserNotExists {
klog.Error(err)
return false, err
}
return true, nil
})
return err
}
func (c *userController) deleteGroupBindings(user *iamv1alpha2.User) error {
@@ -486,7 +519,6 @@ func (c *userController) deleteRoleBindings(user *iamv1alpha2.User) error {
}
}
}
return nil
}
@@ -504,45 +536,45 @@ func (c *userController) deleteLoginRecords(user *iamv1alpha2.User) error {
// syncUserStatus will reconcile user state based on user login records
func (c *userController) syncUserStatus(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
// disabled user, nothing to do
if user.Status.State != nil && *user.Status.State == iamv1alpha2.UserDisabled {
if user.Spec.EncryptedPassword == "" {
if user.Labels[iamv1alpha2.IdentifyProviderLabel] != "" {
// mapped user from other identity provider always active until disabled
if user.Status.State == nil || *user.Status.State != iamv1alpha2.UserActive {
expected := user.DeepCopy()
active := iamv1alpha2.UserActive
expected.Status = iamv1alpha2.UserStatus{
State: &active,
LastTransitionTime: &metav1.Time{Time: time.Now()},
}
return c.ksClient.IamV1alpha2().Users().Update(context.Background(), expected, metav1.UpdateOptions{})
}
} else {
// becomes disabled after setting a blank password
if user.Status.State == nil || *user.Status.State != iamv1alpha2.UserDisabled {
expected := user.DeepCopy()
disabled := iamv1alpha2.UserDisabled
expected.Status = iamv1alpha2.UserStatus{
State: &disabled,
LastTransitionTime: &metav1.Time{Time: time.Now()},
}
return c.ksClient.IamV1alpha2().Users().Update(context.Background(), expected, metav1.UpdateOptions{})
}
}
return user, nil
}
// mapped user from other identity provider always active until disabled
if user.Spec.EncryptedPassword == "" &&
user.Labels[iamv1alpha2.IdentifyProviderLabel] != "" &&
(user.Status.State == nil || *user.Status.State != iamv1alpha2.UserActive) {
expected := user.DeepCopy()
active := iamv1alpha2.UserActive
expected.Status = iamv1alpha2.UserStatus{
State: &active,
LastTransitionTime: &metav1.Time{Time: time.Now()},
}
return c.ksClient.IamV1alpha2().Users().UpdateStatus(context.Background(), expected, metav1.UpdateOptions{})
}
// becomes inactive after setting a blank password
if user.Spec.EncryptedPassword == "" &&
user.Labels[iamv1alpha2.IdentifyProviderLabel] == "" {
expected := user.DeepCopy()
expected.Status = iamv1alpha2.UserStatus{
State: nil,
LastTransitionTime: &metav1.Time{Time: time.Now()},
}
return c.ksClient.IamV1alpha2().Users().UpdateStatus(context.Background(), expected, metav1.UpdateOptions{})
}
// becomes active after password encrypted
if isEncrypted(user.Spec.EncryptedPassword) &&
user.Status.State == nil {
expected := user.DeepCopy()
active := iamv1alpha2.UserActive
expected.Status = iamv1alpha2.UserStatus{
State: &active,
LastTransitionTime: &metav1.Time{Time: time.Now()},
if isEncrypted(user.Spec.EncryptedPassword) {
if user.Status.State == nil || *user.Status.State == iamv1alpha2.UserDisabled {
expected := user.DeepCopy()
active := iamv1alpha2.UserActive
expected.Status = iamv1alpha2.UserStatus{
State: &active,
LastTransitionTime: &metav1.Time{Time: time.Now()},
}
return c.ksClient.IamV1alpha2().Users().Update(context.Background(), expected, metav1.UpdateOptions{})
}
return c.ksClient.IamV1alpha2().Users().UpdateStatus(context.Background(), expected, metav1.UpdateOptions{})
}
// blocked user, check if need to unblock user
@@ -556,7 +588,7 @@ func (c *userController) syncUserStatus(user *iamv1alpha2.User) (*iamv1alpha2.Us
State: &active,
LastTransitionTime: &metav1.Time{Time: time.Now()},
}
return c.ksClient.IamV1alpha2().Users().UpdateStatus(context.Background(), expected, metav1.UpdateOptions{})
return c.ksClient.IamV1alpha2().Users().Update(context.Background(), expected, metav1.UpdateOptions{})
}
}
@@ -588,7 +620,7 @@ func (c *userController) syncUserStatus(user *iamv1alpha2.User) (*iamv1alpha2.Us
}
// block user for AuthenticateRateLimiterDuration duration, after that put it back to the queue to unblock
c.Workqueue.AddAfter(user.Name, c.authenticationOptions.AuthenticateRateLimiterDuration)
return c.ksClient.IamV1alpha2().Users().UpdateStatus(context.Background(), expect, metav1.UpdateOptions{})
return c.ksClient.IamV1alpha2().Users().Update(context.Background(), expect, metav1.UpdateOptions{})
}
return user, nil