add last login time for user (#2679)
Signed-off-by: Jeff <zw0948@gmail.com>
This commit is contained in:
@@ -221,7 +221,7 @@ func addControllers(
|
|||||||
client.Kubernetes(),
|
client.Kubernetes(),
|
||||||
client.KubeSphere(),
|
client.KubeSphere(),
|
||||||
kubesphereInformer.Iam().V1alpha2().LoginRecords(),
|
kubesphereInformer.Iam().V1alpha2().LoginRecords(),
|
||||||
authenticationOptions)
|
authenticationOptions.LoginHistoryRetentionPeriod)
|
||||||
|
|
||||||
csrController := certificatesigningrequest.NewController(client.Kubernetes(),
|
csrController := certificatesigningrequest.NewController(client.Kubernetes(),
|
||||||
kubernetesInformer.Certificates().V1beta1().CertificateSigningRequests(),
|
kubernetesInformer.Certificates().V1beta1().CertificateSigningRequests(),
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ spec:
|
|||||||
status:
|
status:
|
||||||
description: UserStatus defines the observed state of User
|
description: UserStatus defines the observed state of User
|
||||||
properties:
|
properties:
|
||||||
|
lastLoginTime:
|
||||||
|
description: Last login attempt timestamp
|
||||||
|
format: date-time
|
||||||
|
type: string
|
||||||
lastTransitionTime:
|
lastTransitionTime:
|
||||||
format: date-time
|
format: date-time
|
||||||
type: string
|
type: string
|
||||||
|
|||||||
4
config/crds/iam.kubesphere.io_users.yaml
generated
4
config/crds/iam.kubesphere.io_users.yaml
generated
@@ -69,6 +69,10 @@ spec:
|
|||||||
status:
|
status:
|
||||||
description: UserStatus defines the observed state of User
|
description: UserStatus defines the observed state of User
|
||||||
properties:
|
properties:
|
||||||
|
lastLoginTime:
|
||||||
|
description: Last login attempt timestamp
|
||||||
|
format: date-time
|
||||||
|
type: string
|
||||||
lastTransitionTime:
|
lastTransitionTime:
|
||||||
format: date-time
|
format: date-time
|
||||||
type: string
|
type: string
|
||||||
|
|||||||
67
hack/generate_certs.sh
Executable file
67
hack/generate_certs.sh
Executable file
@@ -0,0 +1,67 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Generate certificate suitable for use with an sidecar-injector webhook service.
|
||||||
|
This script uses k8s' CertificateSigningRequest API to a generate a
|
||||||
|
certificate signed by k8s CA suitable for use with sidecar-injector webhook
|
||||||
|
services. This requires permissions to create and approve CSR. See
|
||||||
|
https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster for
|
||||||
|
detailed explantion and additional instructions.
|
||||||
|
The server key/cert k8s CA cert are stored in a k8s secret.
|
||||||
|
usage: ${0} [OPTIONS]
|
||||||
|
The following flags are required.
|
||||||
|
--service Service name of webhook.
|
||||||
|
--namespace Namespace where webhook service and secret reside.
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case ${1} in
|
||||||
|
--service)
|
||||||
|
service="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--namespace)
|
||||||
|
namespace="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -z ${service} ] && service=webhook-service
|
||||||
|
[ -z ${namespace} ] && namespace=default
|
||||||
|
|
||||||
|
if [ ! -x "$(command -v openssl)" ]; then
|
||||||
|
echo "openssl not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
csrName=${service}.${namespace}
|
||||||
|
CERTSDIR="config/certs"
|
||||||
|
|
||||||
|
if [ ! -d ${CERTSDIR} ]; then
|
||||||
|
mkdir ${CERTSDIR}
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "creating certs in certsdir ${CERTSDIR} "
|
||||||
|
|
||||||
|
# create cakey
|
||||||
|
openssl genrsa -out ${CERTSDIR}/ca.key 2048
|
||||||
|
|
||||||
|
# create ca.crt
|
||||||
|
openssl req -x509 -new -nodes -key ${CERTSDIR}/ca.key -subj "/C=CN/ST=HB/O=QC/CN=${service}" -sha256 -days 10000 -out ${CERTSDIR}/ca.crt
|
||||||
|
|
||||||
|
# create server.key
|
||||||
|
openssl genrsa -out ${CERTSDIR}/server.key 2048
|
||||||
|
|
||||||
|
# create server.crt
|
||||||
|
openssl req -new -sha256 -key ${CERTSDIR}/server.key -subj "/C=CN/ST=HB/O=QC/CN=${service}.${namespace}.svc" -out ${CERTSDIR}/server.csr
|
||||||
|
openssl x509 -req -in ${CERTSDIR}/server.csr -CA ${CERTSDIR}/ca.crt -CAkey ${CERTSDIR}/ca.key -CAcreateserial -out ${CERTSDIR}/server.crt -days 10000 -sha256
|
||||||
@@ -137,6 +137,9 @@ type UserStatus struct {
|
|||||||
Reason string `json:"reason,omitempty"`
|
Reason string `json:"reason,omitempty"`
|
||||||
// +optional
|
// +optional
|
||||||
LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"`
|
LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"`
|
||||||
|
// Last login attempt timestamp
|
||||||
|
// +optional
|
||||||
|
LastLoginTime *metav1.Time `json:"lastLoginTime,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|||||||
4
pkg/apis/iam/v1alpha2/zz_generated.deepcopy.go
generated
4
pkg/apis/iam/v1alpha2/zz_generated.deepcopy.go
generated
@@ -577,6 +577,10 @@ func (in *UserStatus) DeepCopyInto(out *UserStatus) {
|
|||||||
in, out := &in.LastTransitionTime, &out.LastTransitionTime
|
in, out := &in.LastTransitionTime, &out.LastTransitionTime
|
||||||
*out = (*in).DeepCopy()
|
*out = (*in).DeepCopy()
|
||||||
}
|
}
|
||||||
|
if in.LastLoginTime != nil {
|
||||||
|
in, out := &in.LastLoginTime, &out.LastLoginTime
|
||||||
|
*out = (*in).DeepCopy()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserStatus.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserStatus.
|
||||||
|
|||||||
@@ -25,20 +25,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type AuthenticationOptions struct {
|
type AuthenticationOptions struct {
|
||||||
// authenticate rate limit
|
// AuthenticateRateLimiter defines under which circumstances we will block user.
|
||||||
|
// A user will be blocked if his/her failed login attempt reaches AuthenticateRateLimiterMaxTries in
|
||||||
|
// AuthenticateRateLimiterDuration for about AuthenticateRateLimiterDuration. For example,
|
||||||
|
// AuthenticateRateLimiterMaxTries: 5
|
||||||
|
// AuthenticateRateLimiterDuration: 10m
|
||||||
|
// A user will be blocked for 10m if he/she logins with incorrect credentials for at least 5 times in 10m.
|
||||||
AuthenticateRateLimiterMaxTries int `json:"authenticateRateLimiterMaxTries" yaml:"authenticateRateLimiterMaxTries"`
|
AuthenticateRateLimiterMaxTries int `json:"authenticateRateLimiterMaxTries" yaml:"authenticateRateLimiterMaxTries"`
|
||||||
AuthenticateRateLimiterDuration time.Duration `json:"authenticateRateLimiterDuration" yaml:"authenticateRateLimiterDuration"`
|
AuthenticateRateLimiterDuration time.Duration `json:"authenticateRateLimiterDuration" yaml:"authenticateRateLimiterDuration"`
|
||||||
// Token verification maximum time difference
|
// Token verification maximum time difference
|
||||||
MaximumClockSkew time.Duration `json:"maximumClockSkew" yaml:"maximumClockSkew"`
|
MaximumClockSkew time.Duration `json:"maximumClockSkew" yaml:"maximumClockSkew"`
|
||||||
// retention login records
|
// retention login history, records beyond this amount will be deleted
|
||||||
RecordRetentionPeriod time.Duration `json:"recordRetentionPeriod" yaml:"recordRetentionPeriod"`
|
LoginHistoryRetentionPeriod time.Duration `json:"loginHistoryRetentionPeriod" yaml:"loginHistoryRetentionPeriod"`
|
||||||
// allow multiple users login at the same time
|
// allow multiple users login from different location at the same time
|
||||||
MultipleLogin bool `json:"multipleLogin" yaml:"multipleLogin"`
|
MultipleLogin bool `json:"multipleLogin" yaml:"multipleLogin"`
|
||||||
// secret to signed jwt token
|
// secret to sign jwt token
|
||||||
JwtSecret string `json:"-" yaml:"jwtSecret"`
|
JwtSecret string `json:"-" yaml:"jwtSecret"`
|
||||||
// oauth options
|
// OAuthOptions defines options needed for integrated oauth plugins
|
||||||
OAuthOptions *oauth.Options `json:"oauthOptions" yaml:"oauthOptions"`
|
OAuthOptions *oauth.Options `json:"oauthOptions" yaml:"oauthOptions"`
|
||||||
KubectlImage string `json:"kubectlImage" yaml:"kubectlImage"`
|
// KubectlImage is the image address we use to create kubectl pod for users who have admin access to the cluster.
|
||||||
|
KubectlImage string `json:"kubectlImage" yaml:"kubectlImage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthenticateOptions() *AuthenticationOptions {
|
func NewAuthenticateOptions() *AuthenticationOptions {
|
||||||
@@ -46,7 +52,7 @@ func NewAuthenticateOptions() *AuthenticationOptions {
|
|||||||
AuthenticateRateLimiterMaxTries: 5,
|
AuthenticateRateLimiterMaxTries: 5,
|
||||||
AuthenticateRateLimiterDuration: time.Minute * 30,
|
AuthenticateRateLimiterDuration: time.Minute * 30,
|
||||||
MaximumClockSkew: 10 * time.Second,
|
MaximumClockSkew: 10 * time.Second,
|
||||||
RecordRetentionPeriod: time.Hour * 24 * 7,
|
LoginHistoryRetentionPeriod: time.Hour * 24 * 7,
|
||||||
OAuthOptions: oauth.NewOptions(),
|
OAuthOptions: oauth.NewOptions(),
|
||||||
MultipleLogin: false,
|
MultipleLogin: false,
|
||||||
JwtSecret: "",
|
JwtSecret: "",
|
||||||
@@ -68,7 +74,8 @@ func (options *AuthenticationOptions) AddFlags(fs *pflag.FlagSet, s *Authenticat
|
|||||||
fs.DurationVar(&options.AuthenticateRateLimiterDuration, "authenticate-rate-limiter-duration", s.AuthenticateRateLimiterDuration, "")
|
fs.DurationVar(&options.AuthenticateRateLimiterDuration, "authenticate-rate-limiter-duration", s.AuthenticateRateLimiterDuration, "")
|
||||||
fs.BoolVar(&options.MultipleLogin, "multiple-login", s.MultipleLogin, "Allow multiple login with the same account, disable means only one user can login at the same time.")
|
fs.BoolVar(&options.MultipleLogin, "multiple-login", s.MultipleLogin, "Allow multiple login with the same account, disable means only one user can login at the same time.")
|
||||||
fs.StringVar(&options.JwtSecret, "jwt-secret", s.JwtSecret, "Secret to sign jwt token, must not be empty.")
|
fs.StringVar(&options.JwtSecret, "jwt-secret", s.JwtSecret, "Secret to sign jwt token, must not be empty.")
|
||||||
fs.DurationVar(&options.OAuthOptions.AccessTokenMaxAge, "access-token-max-age", s.OAuthOptions.AccessTokenMaxAge, "AccessTokenMaxAgeSeconds control the lifetime of access tokens, 0 means no expiration.")
|
fs.DurationVar(&options.LoginHistoryRetentionPeriod, "login-history-retention-period", s.LoginHistoryRetentionPeriod, "login-history-retention-period defines how long login history should be kept.")
|
||||||
|
fs.DurationVar(&options.OAuthOptions.AccessTokenMaxAge, "access-token-max-age", s.OAuthOptions.AccessTokenMaxAge, "access-token-max-age control the lifetime of access tokens, 0 means no expiration.")
|
||||||
fs.StringVar(&s.KubectlImage, "kubectl-image", s.KubectlImage, "Setup the image used by kubectl terminal pod")
|
fs.StringVar(&s.KubectlImage, "kubectl-image", s.KubectlImage, "Setup the image used by kubectl terminal pod")
|
||||||
fs.DurationVar(&options.MaximumClockSkew, "maximum-clock-skew", s.MaximumClockSkew, "The maximum time difference between the system clocks of the ks-apiserver that issued a JWT and the ks-apiserver that verified the JWT.")
|
fs.DurationVar(&options.MaximumClockSkew, "maximum-clock-skew", s.MaximumClockSkew, "The maximum time difference between the system clocks of the ks-apiserver that issued a JWT and the ks-apiserver that verified the JWT.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import (
|
|||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
"k8s.io/client-go/util/workqueue"
|
"k8s.io/client-go/util/workqueue"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
|
|
||||||
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||||
iamv1alpha2informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam/v1alpha2"
|
iamv1alpha2informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam/v1alpha2"
|
||||||
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
|
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
|
||||||
@@ -54,13 +53,13 @@ type LoginRecordController struct {
|
|||||||
workqueue workqueue.RateLimitingInterface
|
workqueue workqueue.RateLimitingInterface
|
||||||
// recorder is an event recorder for recording Event resources to the
|
// recorder is an event recorder for recording Event resources to the
|
||||||
// Kubernetes API.
|
// Kubernetes API.
|
||||||
recorder record.EventRecorder
|
recorder record.EventRecorder
|
||||||
authenticationOptions *authoptions.AuthenticationOptions
|
loginHistoryRetentionPeriod time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLoginRecordController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface,
|
func NewLoginRecordController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface,
|
||||||
loginRecordInformer iamv1alpha2informers.LoginRecordInformer,
|
loginRecordInformer iamv1alpha2informers.LoginRecordInformer,
|
||||||
authenticationOptions *authoptions.AuthenticationOptions) *LoginRecordController {
|
loginHistoryRetentionPeriod time.Duration) *LoginRecordController {
|
||||||
|
|
||||||
klog.V(4).Info("Creating event broadcaster")
|
klog.V(4).Info("Creating event broadcaster")
|
||||||
eventBroadcaster := record.NewBroadcaster()
|
eventBroadcaster := record.NewBroadcaster()
|
||||||
@@ -68,17 +67,15 @@ func NewLoginRecordController(k8sClient kubernetes.Interface, ksClient kubespher
|
|||||||
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: k8sClient.CoreV1().Events("")})
|
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: k8sClient.CoreV1().Events("")})
|
||||||
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "loginrecord-controller"})
|
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "loginrecord-controller"})
|
||||||
ctl := &LoginRecordController{
|
ctl := &LoginRecordController{
|
||||||
k8sClient: k8sClient,
|
k8sClient: k8sClient,
|
||||||
ksClient: ksClient,
|
ksClient: ksClient,
|
||||||
loginRecordInformer: loginRecordInformer,
|
loginRecordInformer: loginRecordInformer,
|
||||||
loginRecordLister: loginRecordInformer.Lister(),
|
loginRecordLister: loginRecordInformer.Lister(),
|
||||||
loginRecordSynced: loginRecordInformer.Informer().HasSynced,
|
loginRecordSynced: loginRecordInformer.Informer().HasSynced,
|
||||||
authenticationOptions: authenticationOptions,
|
loginHistoryRetentionPeriod: loginHistoryRetentionPeriod,
|
||||||
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "WorkspaceTemplate"),
|
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "loginrecord"),
|
||||||
recorder: recorder,
|
recorder: recorder,
|
||||||
}
|
}
|
||||||
klog.Error(authenticationOptions.RecordRetentionPeriod)
|
|
||||||
klog.Info("Setting up event handlers")
|
|
||||||
return ctl
|
return ctl
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,15 +179,9 @@ func (c *LoginRecordController) processNextWorkItem() bool {
|
|||||||
return true
|
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.
|
|
||||||
func (c *LoginRecordController) reconcile(key string) error {
|
func (c *LoginRecordController) reconcile(key string) error {
|
||||||
|
|
||||||
loginRecord, err := c.loginRecordLister.Get(key)
|
loginRecord, err := c.loginRecordLister.Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The user may no longer exist, in which case we stop
|
|
||||||
// processing.
|
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
utilruntime.HandleError(fmt.Errorf("login record '%s' in work queue no longer exists", key))
|
utilruntime.HandleError(fmt.Errorf("login record '%s' in work queue no longer exists", key))
|
||||||
return nil
|
return nil
|
||||||
@@ -198,12 +189,15 @@ func (c *LoginRecordController) reconcile(key string) error {
|
|||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if now.Sub(loginRecord.CreationTimestamp.Time) > 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 {
|
if err = c.ksClient.IamV1alpha2().LoginRecords().Delete(loginRecord.Name, metav1.NewDeleteOptions(0)); err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return 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)
|
c.recorder.Event(loginRecord, corev1.EventTypeNormal, successSynced, messageResourceSynced)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -99,12 +99,9 @@ func NewUserController(k8sClient kubernetes.Interface, ksClient kubesphere.Inter
|
|||||||
ldapClient ldapclient.Interface,
|
ldapClient ldapclient.Interface,
|
||||||
authenticationOptions *authoptions.AuthenticationOptions,
|
authenticationOptions *authoptions.AuthenticationOptions,
|
||||||
multiClusterEnabled bool) *Controller {
|
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))
|
utilruntime.Must(kubespherescheme.AddToScheme(scheme.Scheme))
|
||||||
klog.V(4).Info("Creating event broadcaster")
|
|
||||||
eventBroadcaster := record.NewBroadcaster()
|
eventBroadcaster := record.NewBroadcaster()
|
||||||
eventBroadcaster.StartLogging(klog.Infof)
|
eventBroadcaster.StartLogging(klog.Infof)
|
||||||
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: k8sClient.CoreV1().Events("")})
|
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: k8sClient.CoreV1().Events("")})
|
||||||
@@ -132,6 +129,7 @@ func NewUserController(k8sClient kubernetes.Interface, ksClient kubesphere.Inter
|
|||||||
multiClusterEnabled: multiClusterEnabled,
|
multiClusterEnabled: multiClusterEnabled,
|
||||||
authenticationOptions: authenticationOptions,
|
authenticationOptions: authenticationOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
klog.Info("Setting up event handlers")
|
klog.Info("Setting up event handlers")
|
||||||
userInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
userInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
AddFunc: ctl.enqueueUser,
|
AddFunc: ctl.enqueueUser,
|
||||||
@@ -140,16 +138,11 @@ func NewUserController(k8sClient kubernetes.Interface, ksClient kubesphere.Inter
|
|||||||
},
|
},
|
||||||
DeleteFunc: ctl.enqueueUser,
|
DeleteFunc: ctl.enqueueUser,
|
||||||
})
|
})
|
||||||
|
|
||||||
loginRecordInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
loginRecordInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
AddFunc: func(new interface{}) {
|
AddFunc: func(new interface{}) {
|
||||||
if username := new.(*iamv1alpha2.LoginRecord).Labels[iamv1alpha2.UserReferenceLabel]; username != "" {
|
err := ctl.enqueueLogin(new)
|
||||||
ctl.workqueue.Add(username)
|
klog.Errorf("Failed to enqueue login object, error: %v", err)
|
||||||
}
|
|
||||||
},
|
|
||||||
DeleteFunc: func(obj interface{}) {
|
|
||||||
if username := obj.(*iamv1alpha2.LoginRecord).Labels[iamv1alpha2.UserReferenceLabel]; username != "" {
|
|
||||||
ctl.workqueue.Add(username)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return ctl
|
return ctl
|
||||||
@@ -235,9 +228,32 @@ func (c *Controller) processNextWorkItem() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// syncHandler compares the actual state with the desired, and attempts to
|
// enqueueLogin accepts a login object and set user lastLoginTime field
|
||||||
// converge the two. It then updates the Status block of the Foo resource
|
func (c *Controller) enqueueLogin(object interface{}) error {
|
||||||
// with the current status of the resource.
|
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 {
|
func (c *Controller) reconcile(key string) error {
|
||||||
|
|
||||||
// Get the user with this name
|
// Get the user with this name
|
||||||
|
|||||||
Reference in New Issue
Block a user