diff --git a/cmd/controller-manager/app/controllers.go b/cmd/controller-manager/app/controllers.go index 374f2d88d..fc606d7f4 100644 --- a/cmd/controller-manager/app/controllers.go +++ b/cmd/controller-manager/app/controllers.go @@ -221,7 +221,7 @@ func addControllers( client.Kubernetes(), client.KubeSphere(), kubesphereInformer.Iam().V1alpha2().LoginRecords(), - authenticationOptions) + authenticationOptions.LoginHistoryRetentionPeriod) csrController := certificatesigningrequest.NewController(client.Kubernetes(), kubernetesInformer.Certificates().V1beta1().CertificateSigningRequests(), diff --git a/config/crds/iam.kubesphere.io_federatedusers.yaml b/config/crds/iam.kubesphere.io_federatedusers.yaml index d6dc4a18a..568e392bd 100644 --- a/config/crds/iam.kubesphere.io_federatedusers.yaml +++ b/config/crds/iam.kubesphere.io_federatedusers.yaml @@ -84,6 +84,10 @@ spec: status: description: UserStatus defines the observed state of User properties: + lastLoginTime: + description: Last login attempt timestamp + format: date-time + type: string lastTransitionTime: format: date-time type: string diff --git a/config/crds/iam.kubesphere.io_users.yaml b/config/crds/iam.kubesphere.io_users.yaml index 423ffaee6..07084d0ac 100644 --- a/config/crds/iam.kubesphere.io_users.yaml +++ b/config/crds/iam.kubesphere.io_users.yaml @@ -69,6 +69,10 @@ spec: status: description: UserStatus defines the observed state of User properties: + lastLoginTime: + description: Last login attempt timestamp + format: date-time + type: string lastTransitionTime: format: date-time type: string diff --git a/hack/generate_certs.sh b/hack/generate_certs.sh new file mode 100755 index 000000000..87d433cf9 --- /dev/null +++ b/hack/generate_certs.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +set -e + +usage() { + cat < c.authenticationOptions.RecordRetentionPeriod { + if loginRecord.CreationTimestamp.Add(c.loginHistoryRetentionPeriod).Before(now) { // login record beyonds retention period if err = c.ksClient.IamV1alpha2().LoginRecords().Delete(loginRecord.Name, metav1.NewDeleteOptions(0)); err != nil { klog.Error(err) return err } + } else { // put item back into the queue + c.workqueue.AddAfter(key, loginRecord.CreationTimestamp.Add(c.loginHistoryRetentionPeriod).Sub(now)) } c.recorder.Event(loginRecord, corev1.EventTypeNormal, successSynced, messageResourceSynced) return nil diff --git a/pkg/controller/user/user_controller.go b/pkg/controller/user/user_controller.go index c8e60edaf..039d5686d 100644 --- a/pkg/controller/user/user_controller.go +++ b/pkg/controller/user/user_controller.go @@ -99,12 +99,9 @@ func NewUserController(k8sClient kubernetes.Interface, ksClient kubesphere.Inter ldapClient ldapclient.Interface, authenticationOptions *authoptions.AuthenticationOptions, multiClusterEnabled bool) *Controller { - // Create event broadcaster - // Add sample-controller types to the default Kubernetes Scheme so Events can be - // logged for sample-controller types. utilruntime.Must(kubespherescheme.AddToScheme(scheme.Scheme)) - klog.V(4).Info("Creating event broadcaster") + eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(klog.Infof) eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: k8sClient.CoreV1().Events("")}) @@ -132,6 +129,7 @@ func NewUserController(k8sClient kubernetes.Interface, ksClient kubesphere.Inter multiClusterEnabled: multiClusterEnabled, authenticationOptions: authenticationOptions, } + klog.Info("Setting up event handlers") userInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: ctl.enqueueUser, @@ -140,16 +138,11 @@ func NewUserController(k8sClient kubernetes.Interface, ksClient kubesphere.Inter }, DeleteFunc: ctl.enqueueUser, }) + loginRecordInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(new interface{}) { - if username := new.(*iamv1alpha2.LoginRecord).Labels[iamv1alpha2.UserReferenceLabel]; username != "" { - ctl.workqueue.Add(username) - } - }, - DeleteFunc: func(obj interface{}) { - if username := obj.(*iamv1alpha2.LoginRecord).Labels[iamv1alpha2.UserReferenceLabel]; username != "" { - ctl.workqueue.Add(username) - } + err := ctl.enqueueLogin(new) + klog.Errorf("Failed to enqueue login object, error: %v", err) }, }) return ctl @@ -235,9 +228,32 @@ func (c *Controller) processNextWorkItem() bool { return true } -// syncHandler compares the actual state with the desired, and attempts to -// converge the two. It then updates the Status block of the Foo resource -// with the current status of the resource. +// enqueueLogin accepts a login object and set user lastLoginTime field +func (c *Controller) enqueueLogin(object interface{}) error { + login := object.(*iamv1alpha2.LoginRecord) + username, ok := login.Labels[iamv1alpha2.UserReferenceLabel] + + if !ok || len(username) == 0 { + return fmt.Errorf("login doesn't belong to any user") + } + + user, err := c.userLister.Get(username) + if err != nil { + if errors.IsNotFound(err) { + return fmt.Errorf("user %s doesn't exist any more, login record will be deleted later", username) + } + return err + } + + if user.Status.LastLoginTime == nil || user.Status.LastLoginTime.Before(&login.CreationTimestamp) { + user.Status.LastLoginTime = &login.CreationTimestamp + user, err = c.ksClient.IamV1alpha2().Users().Update(user) + return err + } + + return nil +} + func (c *Controller) reconcile(key string) error { // Get the user with this name