未验证 提交 73763902 编写于 作者: K KubeSphere CI Bot 提交者: GitHub

Merge pull request #3433 from wansir/loginrecord

limit login record entries
......@@ -194,7 +194,8 @@ func addControllers(
client.KubeSphere(),
kubesphereInformer.Iam().V1alpha2().LoginRecords(),
kubesphereInformer.Iam().V1alpha2().Users(),
authenticationOptions.LoginHistoryRetentionPeriod)
authenticationOptions.LoginHistoryRetentionPeriod,
authenticationOptions.LoginHistoryMaximumEntries)
csrController := certificatesigningrequest.NewController(client.Kubernetes(),
kubernetesInformer.Certificates().V1beta1().CertificateSigningRequests(),
......
......@@ -47,7 +47,6 @@ require (
github.com/json-iterator/go v1.1.10
github.com/jszwec/csvutil v1.5.0
github.com/kelseyhightower/envconfig v1.4.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/kubernetes-csi/external-snapshotter/client/v3 v3.0.0
github.com/kubesphere/sonargo v0.0.2
github.com/mitchellh/mapstructure v1.2.2
......@@ -72,14 +71,12 @@ require (
github.com/spf13/viper v1.4.0
github.com/stretchr/testify v1.6.1
github.com/xanzy/ssh-agent v0.2.1 // indirect
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
golang.org/x/net v0.0.0-20200707034311-ab3426394381
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
google.golang.org/grpc v1.30.0
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
gopkg.in/cas.v2 v2.2.0
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/square/go-jose.v2 v2.4.0
gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect
gopkg.in/src-d/go-git.v4 v4.11.0
......@@ -194,7 +191,6 @@ replace (
github.com/brancz/kube-rbac-proxy => github.com/brancz/kube-rbac-proxy v0.5.0
github.com/bshuster-repo/logrus-logstash-hook => github.com/bshuster-repo/logrus-logstash-hook v0.4.1
github.com/bugsnag/bugsnag-go => github.com/bugsnag/bugsnag-go v1.5.0
github.com/bugsnag/osext => github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b
github.com/bugsnag/panicwrap => github.com/bugsnag/panicwrap v1.2.0
github.com/c-bata/go-prompt => github.com/c-bata/go-prompt v0.2.2
github.com/campoy/embedmd => github.com/campoy/embedmd v1.0.0
......@@ -213,7 +209,6 @@ replace (
github.com/circonus-labs/circonus-gometrics => github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible
github.com/circonus-labs/circonusllhist => github.com/circonus-labs/circonusllhist v0.1.3
github.com/clbanning/x2j => github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec
github.com/cloudflare/cfssl => github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004
github.com/cockroachdb/apd => github.com/cockroachdb/apd v1.1.0
github.com/cockroachdb/cockroach-go => github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c
github.com/cockroachdb/datadriven => github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa
......@@ -221,9 +216,6 @@ replace (
github.com/container-storage-interface/spec => github.com/container-storage-interface/spec v1.2.0
github.com/containerd/containerd => github.com/containerd/containerd v1.3.0
github.com/containerd/continuity => github.com/containerd/continuity v0.0.0-20181203112020-004b46473808
github.com/containerd/fifo => github.com/containerd/fifo v0.0.0-20210129194248-f8e8fdba47ef
github.com/containerd/ttrpc => github.com/containerd/ttrpc v1.0.2
github.com/containerd/typeurl => github.com/containerd/typeurl v1.0.1
github.com/containernetworking/cni => github.com/containernetworking/cni v0.8.0
github.com/coreos/bbolt => github.com/coreos/bbolt v1.3.3
github.com/coreos/etcd => github.com/coreos/etcd v3.3.17+incompatible
......@@ -257,21 +249,16 @@ replace (
github.com/dhui/dktest => github.com/dhui/dktest v0.3.0
github.com/disintegration/imaging => github.com/disintegration/imaging v1.6.1
github.com/docker/cli => github.com/docker/cli v0.0.0-20190506213505-d88565df0c2d
github.com/docker/compose-on-kubernetes => github.com/docker/compose-on-kubernetes v0.4.24
github.com/docker/distribution => github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker => github.com/docker/engine v1.4.2-0.20190822205725-ed20165a37b4
github.com/docker/docker-credential-helpers => github.com/docker/docker-credential-helpers v0.6.1
github.com/docker/go => github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c
github.com/docker/go-connections => github.com/docker/go-connections v0.4.0
github.com/docker/go-events => github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c
github.com/docker/go-metrics => github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82
github.com/docker/go-units => github.com/docker/go-units v0.4.0
github.com/docker/libtrust => github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7
github.com/docker/spdystream => github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c
github.com/docker/swarmkit => github.com/docker/swarmkit v1.12.0
github.com/docopt/docopt-go => github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/dustin/go-humanize => github.com/dustin/go-humanize v1.0.0
github.com/dvsekhvalnov/jose2go => github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae
github.com/eapache/go-resiliency => github.com/eapache/go-resiliency v1.1.0
github.com/eapache/go-xerial-snappy => github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21
github.com/eapache/queue => github.com/eapache/queue v1.1.0
......@@ -375,7 +362,6 @@ replace (
github.com/gomodule/redigo => github.com/gomodule/redigo v2.0.0+incompatible
github.com/google/addlicense => github.com/google/addlicense v0.0.0-20200906110928-a0294312aa76
github.com/google/btree => github.com/google/btree v1.0.0
github.com/google/certificate-transparency-go => github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93
github.com/google/flatbuffers => github.com/google/flatbuffers v1.11.0
github.com/google/go-cmp => github.com/google/go-cmp v0.4.0
github.com/google/go-github => github.com/google/go-github v17.0.0+incompatible
......@@ -385,7 +371,6 @@ replace (
github.com/google/martian => github.com/google/martian v2.1.0+incompatible
github.com/google/pprof => github.com/google/pprof v0.0.0-20200417002340-c6e0a841f49a
github.com/google/renameio => github.com/google/renameio v0.1.0
github.com/google/shlex => github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid => github.com/google/uuid v1.1.1
github.com/googleapis/gax-go => github.com/googleapis/gax-go v2.0.2+incompatible
github.com/googleapis/gax-go/v2 => github.com/googleapis/gax-go/v2 v2.0.5
......@@ -454,7 +439,6 @@ replace (
github.com/jstemmer/go-junit-report => github.com/jstemmer/go-junit-report v0.9.1
github.com/jsternberg/zap-logfmt => github.com/jsternberg/zap-logfmt v1.0.0
github.com/jtolds/gls => github.com/jtolds/gls v4.20.0+incompatible
github.com/juju/loggo => github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8
github.com/julienschmidt/httprouter => github.com/julienschmidt/httprouter v1.3.0
github.com/jung-kurt/gofpdf => github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5
github.com/jwilder/encoding => github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef
......@@ -506,7 +490,6 @@ replace (
github.com/mdlayher/wifi => github.com/mdlayher/wifi v0.0.0-20190303161829-b1436901ddee
github.com/mgutz/ansi => github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
github.com/miekg/dns => github.com/miekg/dns v1.1.29
github.com/miekg/pkcs11 => github.com/miekg/pkcs11 v1.0.2
github.com/minio/md5-simd => github.com/minio/md5-simd v1.1.0
github.com/minio/minio-go/v7 => github.com/minio/minio-go/v7 v7.0.2
github.com/minio/sha256-simd => github.com/minio/sha256-simd v0.1.1
......@@ -536,7 +519,6 @@ replace (
github.com/nats-io/nkeys => github.com/nats-io/nkeys v0.1.3
github.com/nats-io/nuid => github.com/nats-io/nuid v1.0.1
github.com/ncw/swift => github.com/ncw/swift v1.0.50
github.com/niemeyer/pretty => github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e
github.com/nxadm/tail => github.com/nxadm/tail v1.4.4
github.com/oklog/oklog => github.com/oklog/oklog v0.3.2
github.com/oklog/run => github.com/oklog/run v1.1.0
......@@ -549,7 +531,6 @@ replace (
github.com/opencontainers/go-digest => github.com/opencontainers/go-digest v1.0.0-rc1
github.com/opencontainers/image-spec => github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/runc => github.com/opencontainers/runc v0.1.1
github.com/opencontainers/runtime-spec => github.com/opencontainers/runtime-spec v1.0.2
github.com/opentracing-contrib/go-grpc => github.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02
github.com/opentracing-contrib/go-observer => github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492
github.com/opentracing-contrib/go-stdlib => github.com/opentracing-contrib/go-stdlib v0.0.0-20190519235532-cf7a6c988dc9
......@@ -635,9 +616,7 @@ replace (
github.com/streadway/handy => github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a
github.com/stretchr/objx => github.com/stretchr/objx v0.2.0
github.com/stretchr/testify => github.com/stretchr/testify v1.4.0
github.com/syndtr/gocapability => github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
github.com/thanos-io/thanos => github.com/thanos-io/thanos v0.13.1-0.20200910143741-e0b7f7b32e9c
github.com/theupdateframework/notary => github.com/theupdateframework/notary v0.7.0
github.com/tidwall/pretty => github.com/tidwall/pretty v1.0.0
github.com/tinylib/msgp => github.com/tinylib/msgp v1.1.0
github.com/tmc/grpc-websocket-proxy => github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5
......@@ -691,7 +670,6 @@ replace (
golang.org/x/oauth2 => golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
golang.org/x/sync => golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/sys => golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e
golang.org/x/term => golang.org/x/term v0.0.0-20201117132131-f5c789dd3221
golang.org/x/text => golang.org/x/text v0.3.0
golang.org/x/time => golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
golang.org/x/tools => golang.org/x/tools v0.0.0-20190710153321-831012c29e42
......@@ -710,7 +688,6 @@ replace (
gopkg.in/alexcesaro/quotedprintable.v3 => gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc
gopkg.in/asn1-ber.v1 => gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d
gopkg.in/cas.v2 => gopkg.in/cas.v2 v2.2.0
gopkg.in/cenkalti/backoff.v2 => gopkg.in/cenkalti/backoff.v2 v2.2.1
gopkg.in/check.v1 => gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15
gopkg.in/cheggaaa/pb.v1 => gopkg.in/cheggaaa/pb.v1 v1.0.25
gopkg.in/errgo.v2 => gopkg.in/errgo.v2 v2.1.0
......@@ -726,7 +703,6 @@ replace (
gopkg.in/ini.v1 => gopkg.in/ini.v1 v1.57.0
gopkg.in/mail.v2 => gopkg.in/mail.v2 v2.3.1
gopkg.in/natefinch/lumberjack.v2 => gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/rethinkdb/rethinkdb-go.v6 => gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1
gopkg.in/square/go-jose.v1 => gopkg.in/square/go-jose.v1 v1.1.2
gopkg.in/square/go-jose.v2 => gopkg.in/square/go-jose.v2 v2.4.0
gopkg.in/src-d/go-billy.v4 => gopkg.in/src-d/go-billy.v4 v4.3.0
......
......@@ -242,10 +242,11 @@ func (s *APIServer) installKubeSphereAPIs() {
s.KubernetesClient.KubeSphere(),
s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister(),
s.Config.AuthenticationOptions),
auth.NewOAuth2Authenticator(s.KubernetesClient.KubeSphere(),
s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister(),
auth.NewOAuthAuthenticator(s.KubernetesClient.KubeSphere(),
s.InformerFactory.KubeSphereSharedInformerFactory(),
s.Config.AuthenticationOptions),
auth.NewLoginRecorder(s.KubernetesClient.KubeSphere()),
auth.NewLoginRecorder(s.KubernetesClient.KubeSphere(),
s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister()),
s.Config.AuthenticationOptions))
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
urlruntime.Must(networkv1alpha2.AddToContainer(s.container, s.Config.NetworkOptions.WeaveScopeHost))
......@@ -342,7 +343,8 @@ func (s *APIServer) buildHandlerChain(stopCh <-chan struct{}) {
handler = filters.WithMultipleClusterDispatcher(handler, clusterDispatcher)
}
loginRecorder := auth.NewLoginRecorder(s.KubernetesClient.KubeSphere())
loginRecorder := auth.NewLoginRecorder(s.KubernetesClient.KubeSphere(),
s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister())
// authenticators are unordered
authn := unionauth.New(anonymous.NewAuthenticator(),
basictoken.New(basic.NewBasicAuthenticator(auth.NewPasswordAuthenticator(s.KubernetesClient.KubeSphere(),
......
......@@ -126,7 +126,10 @@ func (l ldapProvider) Authenticate(username string, password string) (identitypr
return nil, err
}
filter := fmt.Sprintf("(&(%s=%s)%s)", l.LoginAttribute, username, l.UserSearchFilter)
filter := fmt.Sprintf("(%s=%s)", l.LoginAttribute, ldap.EscapeFilter(username))
if l.UserSearchFilter != "" {
filter = fmt.Sprintf("(&%s%s)", filter, l.UserSearchFilter)
}
result, err := conn.Search(&ldap.SearchRequest{
BaseDN: l.UserSearchBase,
Scope: ldap.ScopeWholeSubtree,
......
......@@ -17,7 +17,7 @@ limitations under the License.
package options
import (
"fmt"
"errors"
"github.com/spf13/pflag"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
_ "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider/aliyunidaas"
......@@ -42,6 +42,9 @@ type AuthenticationOptions struct {
MaximumClockSkew time.Duration `json:"maximumClockSkew" yaml:"maximumClockSkew"`
// retention login history, records beyond this amount will be deleted
LoginHistoryRetentionPeriod time.Duration `json:"loginHistoryRetentionPeriod" yaml:"loginHistoryRetentionPeriod"`
// retention login history, records beyond this amount will be deleted
// LoginHistoryMaximumEntries restricts for all kubesphere accounts and must be greater than AuthenticateRateLimiterMaxTries
LoginHistoryMaximumEntries int `json:"loginHistoryMaximumEntries" yaml:"loginHistoryMaximumEntries"`
// allow multiple users login from different location at the same time
MultipleLogin bool `json:"multipleLogin" yaml:"multipleLogin"`
// secret to sign jwt token
......@@ -58,6 +61,7 @@ func NewAuthenticateOptions() *AuthenticationOptions {
AuthenticateRateLimiterDuration: time.Minute * 30,
MaximumClockSkew: 10 * time.Second,
LoginHistoryRetentionPeriod: time.Hour * 24 * 7,
LoginHistoryMaximumEntries: 100,
OAuthOptions: oauth.NewOptions(),
MultipleLogin: false,
JwtSecret: "",
......@@ -68,7 +72,10 @@ func NewAuthenticateOptions() *AuthenticationOptions {
func (options *AuthenticationOptions) Validate() []error {
var errs []error
if len(options.JwtSecret) == 0 {
errs = append(errs, fmt.Errorf("jwt secret is empty"))
errs = append(errs, errors.New("JWT secret MUST not be empty"))
}
if options.AuthenticateRateLimiterMaxTries > options.LoginHistoryMaximumEntries {
errs = append(errs, errors.New("authenticateRateLimiterMaxTries MUST not be greater than loginHistoryMaximumEntries"))
}
if err := identityprovider.SetupWithOptions(options.OAuthOptions.IdentityProviders); err != nil {
errs = append(errs, err)
......@@ -82,6 +89,7 @@ func (options *AuthenticationOptions) AddFlags(fs *pflag.FlagSet, s *Authenticat
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.DurationVar(&options.LoginHistoryRetentionPeriod, "login-history-retention-period", s.LoginHistoryRetentionPeriod, "login-history-retention-period defines how long login history should be kept.")
fs.IntVar(&options.LoginHistoryMaximumEntries, "login-history-maximum-entries", s.LoginHistoryMaximumEntries, "login-history-maximum-entries defines how many entries of 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.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.")
......
......@@ -22,6 +22,7 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
......@@ -35,6 +36,7 @@ import (
iamv1alpha2informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam/v1alpha2"
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/controller/utils/controller"
"sort"
"time"
)
......@@ -55,6 +57,7 @@ type loginRecordController struct {
userLister iamv1alpha2listers.UserLister
userSynced cache.InformerSynced
loginHistoryRetentionPeriod time.Duration
loginHistoryMaximumEntries int
// recorder is an event recorder for recording Event resources to the
// Kubernetes API.
recorder record.EventRecorder
......@@ -64,7 +67,8 @@ func NewLoginRecordController(k8sClient kubernetes.Interface,
ksClient kubesphere.Interface,
loginRecordInformer iamv1alpha2informers.LoginRecordInformer,
userInformer iamv1alpha2informers.UserInformer,
loginHistoryRetentionPeriod time.Duration) *loginRecordController {
loginHistoryRetentionPeriod time.Duration,
loginHistoryMaximumEntries int) *loginRecordController {
klog.V(4).Info("Creating event broadcaster")
eventBroadcaster := record.NewBroadcaster()
......@@ -82,6 +86,7 @@ func NewLoginRecordController(k8sClient kubernetes.Interface,
loginRecordLister: loginRecordInformer.Lister(),
userLister: userInformer.Lister(),
loginHistoryRetentionPeriod: loginHistoryRetentionPeriod,
loginHistoryMaximumEntries: loginHistoryMaximumEntries,
recorder: recorder,
}
ctl.Handler = ctl.reconcile
......@@ -117,7 +122,20 @@ func (c *loginRecordController) reconcile(key string) error {
return nil
}
if err = c.updateUserLastLoginTime(loginRecord); err != nil {
user, err := c.userForLoginRecord(loginRecord)
if err != nil {
// delete orphan object
if errors.IsNotFound(err) {
return c.ksClient.IamV1alpha2().LoginRecords().Delete(context.TODO(), loginRecord.Name, metav1.DeleteOptions{})
}
return err
}
if err = c.updateUserLastLoginTime(user, loginRecord); err != nil {
return err
}
if err = c.shrinkEntriesFor(user); err != nil {
return err
}
......@@ -136,28 +154,44 @@ func (c *loginRecordController) reconcile(key string) error {
}
// updateUserLastLoginTime accepts a login object and set user lastLoginTime field
func (c *loginRecordController) updateUserLastLoginTime(loginRecord *iamv1alpha2.LoginRecord) error {
username, ok := loginRecord.Labels[iamv1alpha2.UserReferenceLabel]
if !ok || len(username) == 0 {
klog.V(4).Info("login doesn't belong to any user")
return nil
}
user, err := c.userLister.Get(username)
if err != nil {
// ignore not found error
if errors.IsNotFound(err) {
klog.V(4).Infof("user %s doesn't exist any more, login record will be deleted later", username)
return nil
}
klog.Error(err)
return err
}
func (c *loginRecordController) updateUserLastLoginTime(user *iamv1alpha2.User, loginRecord *iamv1alpha2.LoginRecord) error {
// update lastLoginTime
if user.DeletionTimestamp.IsZero() &&
(user.Status.LastLoginTime == nil || user.Status.LastLoginTime.Before(&loginRecord.CreationTimestamp)) {
user.Status.LastLoginTime = &loginRecord.CreationTimestamp
user, err = c.ksClient.IamV1alpha2().Users().UpdateStatus(context.Background(), user, metav1.UpdateOptions{})
_, err := c.ksClient.IamV1alpha2().Users().UpdateStatus(context.Background(), user, metav1.UpdateOptions{})
return err
}
return nil
}
// shrinkEntriesFor will delete old entries out of limit
func (c *loginRecordController) shrinkEntriesFor(user *iamv1alpha2.User) error {
loginRecords, err := c.loginRecordLister.List(labels.SelectorFromSet(labels.Set{iamv1alpha2.UserReferenceLabel: user.Name}))
if err != nil {
return err
}
if len(loginRecords) <= c.loginHistoryMaximumEntries {
return nil
}
sort.Slice(loginRecords, func(i, j int) bool {
return loginRecords[j].CreationTimestamp.After(loginRecords[i].CreationTimestamp.Time)
})
oldEntries := loginRecords[:len(loginRecords)-c.loginHistoryMaximumEntries]
for _, r := range oldEntries {
err = c.ksClient.IamV1alpha2().LoginRecords().Delete(context.TODO(), r.Name, metav1.DeleteOptions{})
if err != nil {
return err
}
}
return nil
}
func (c *loginRecordController) userForLoginRecord(loginRecord *iamv1alpha2.LoginRecord) (*iamv1alpha2.User, error) {
username, ok := loginRecord.Labels[iamv1alpha2.UserReferenceLabel]
if !ok || len(username) == 0 {
klog.V(4).Info("login doesn't belong to any user")
return nil, errors.NewNotFound(iamv1alpha2.Resource(iamv1alpha2.ResourcesSingularUser), username)
}
return c.userLister.Get(username)
}
......@@ -17,257 +17,167 @@ limitations under the License.
package loginrecord
import (
"context"
"fmt"
"reflect"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/klog/klogr"
"kubesphere.io/kubesphere/pkg/apis"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"os"
"path/filepath"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"testing"
"time"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
kubeinformers "k8s.io/client-go/informers"
k8sfake "k8s.io/client-go/kubernetes/fake"
core "k8s.io/client-go/testing"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
ksinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var (
alwaysReady = func() bool { return true }
noResyncPeriodFunc = func() time.Duration { return 0 }
)
type fixture struct {
t *testing.T
ksclient *fake.Clientset
k8sclient *k8sfake.Clientset
// Objects to put in the store.
user *iamv1alpha2.User
loginRecord *iamv1alpha2.LoginRecord
// Actions expected to happen on the client.
kubeactions []core.Action
actions []core.Action
// Objects from here preloaded into NewSimpleFake.
kubeobjects []runtime.Object
objects []runtime.Object
}
func newFixture(t *testing.T) *fixture {
f := &fixture{}
f.t = t
f.objects = []runtime.Object{}
f.kubeobjects = []runtime.Object{}
return f
}
func newUser(name string) *iamv1alpha2.User {
return &iamv1alpha2.User{
TypeMeta: metav1.TypeMeta{APIVersion: iamv1alpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: iamv1alpha2.UserSpec{
Email: fmt.Sprintf("%s@kubesphere.io", name),
Lang: "zh-CN",
Description: "fake user",
},
}
}
func newLoginRecord(username string) *iamv1alpha2.LoginRecord {
return &iamv1alpha2.LoginRecord{
TypeMeta: metav1.TypeMeta{APIVersion: iamv1alpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: username,
CreationTimestamp: metav1.Now(),
Labels: map[string]string{iamv1alpha2.UserReferenceLabel: username},
},
Spec: iamv1alpha2.LoginRecordSpec{
Type: iamv1alpha2.Token,
Success: true,
Reason: "",
},
}
}
func (f *fixture) newController() (*loginRecordController, ksinformers.SharedInformerFactory, kubeinformers.SharedInformerFactory) {
f.ksclient = fake.NewSimpleClientset(f.objects...)
f.k8sclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
ksInformers := ksinformers.NewSharedInformerFactory(f.ksclient, noResyncPeriodFunc())
k8sInformers := kubeinformers.NewSharedInformerFactory(f.k8sclient, noResyncPeriodFunc())
if err := ksInformers.Iam().V1alpha2().Users().Informer().GetIndexer().Add(f.user); err != nil {
f.t.Errorf("add user:%s", err)
}
if err := ksInformers.Iam().V1alpha2().LoginRecords().Informer().GetIndexer().Add(f.loginRecord); err != nil {
f.t.Errorf("add login record:%s", err)
}
c := NewLoginRecordController(f.k8sclient, f.ksclient,
ksInformers.Iam().V1alpha2().LoginRecords(),
ksInformers.Iam().V1alpha2().Users(),
time.Minute*5)
c.userSynced = alwaysReady
c.loginRecordSynced = alwaysReady
c.recorder = &record.FakeRecorder{}
return c, ksInformers, k8sInformers
}
func (f *fixture) run(userName string) {
f.runController(userName, true, false)
}
var testEnv *envtest.Environment
var k8sManager ctrl.Manager
func (f *fixture) runExpectError(userName string) {
f.runController(userName, true, true)
func TestLoginRecordController(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t,
"LoginRecord Controller Test Suite",
[]Reporter{printer.NewlineReporter{}})
}
func (f *fixture) runController(user string, startInformers bool, expectError bool) {
c, i, k8sI := f.newController()
if startInformers {
stopCh := make(chan struct{})
defer close(stopCh)
i.Start(stopCh)
k8sI.Start(stopCh)
}
err := c.reconcile(user)
if !expectError && err != nil {
f.t.Errorf("error syncing user: %v", err)
} else if expectError && err == nil {
f.t.Error("expected error syncing user, got nil")
}
var _ = BeforeSuite(func(done Done) {
logf.SetLogger(klogr.New())
actions := filterInformerActions(f.ksclient.Actions())
for j, action := range actions {
if len(f.actions) < j+1 {
f.t.Errorf("%d unexpected actions: %+v", len(actions)-len(f.actions), actions[j:])
break
By("bootstrapping test environment")
t := true
if os.Getenv("TEST_USE_EXISTING_CLUSTER") == "true" {
testEnv = &envtest.Environment{
UseExistingCluster: &t,
}
expectedAction := f.actions[j]
checkAction(expectedAction, action, f.t)
}
if len(f.actions) > len(actions) {
f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
}
k8sActions := filterInformerActions(f.k8sclient.Actions())
for k, action := range k8sActions {
if len(f.kubeactions) < k+1 {
f.t.Errorf("%d unexpected actions: %+v", len(k8sActions)-len(f.kubeactions), k8sActions[k:])
break
} else {
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crds")},
AttachControlPlaneOutput: false,
}
expectedAction := f.kubeactions[k]
checkAction(expectedAction, action, f.t)
}
if len(f.kubeactions) > len(k8sActions) {
f.t.Errorf("%d additional expected actions:%+v", len(f.kubeactions)-len(k8sActions), f.kubeactions[len(k8sActions):])
}
}
cfg, err := testEnv.Start()
Expect(err).ToNot(HaveOccurred())
Expect(cfg).ToNot(BeNil())
// checkAction verifies that expected and actual actions are equal and both have
// same attached resources
func checkAction(expected, actual core.Action, t *testing.T) {
if !(expected.Matches(actual.GetVerb(), actual.GetResource().Resource) && actual.GetSubresource() == expected.GetSubresource()) {
t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expected, actual)
//return
// TODO : failed sometimes, need to be verified by hongming
}
err = apis.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
if reflect.TypeOf(actual) != reflect.TypeOf(expected) {
t.Errorf("Action has wrong type. Expected: %t. Got: %t", expected, actual)
return
}
k8sManager, err = ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme.Scheme,
MetricsBindAddress: "0",
})
Expect(err).ToNot(HaveOccurred())
switch a := actual.(type) {
case core.CreateActionImpl:
e, _ := expected.(core.CreateActionImpl)
expObject := e.GetObject()
object := a.GetObject()
if !reflect.DeepEqual(expObject, object) {
t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
}
case core.UpdateActionImpl:
e, _ := expected.(core.UpdateActionImpl)
expObject := e.GetObject()
object := a.GetObject()
expUser := expObject.(*iamv1alpha2.User)
user := object.(*iamv1alpha2.User)
expUser.Status.LastTransitionTime = nil
user.Status.LastTransitionTime = nil
if !reflect.DeepEqual(expUser, user) {
t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
}
case core.PatchActionImpl:
e, _ := expected.(core.PatchActionImpl)
expPatch := e.GetPatch()
patch := a.GetPatch()
if !reflect.DeepEqual(expPatch, patch) {
t.Errorf("Action %s %s has wrong patch\nDiff:\n %s",
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expPatch, patch))
}
default:
t.Errorf("Uncaptured Action %s %s, you should explicitly add a case to capture it",
actual.GetVerb(), actual.GetResource().Resource)
}
}
k8sClient, err := kubernetes.NewForConfig(cfg)
Expect(err).NotTo(HaveOccurred())
// filterInformerActions filters list and watch actions for testing resources.
// Since list and watch don't change resource state we can filter it to lower
// nose level in our tests.
func filterInformerActions(actions []core.Action) []core.Action {
var ret []core.Action
for _, action := range actions {
if len(action.GetNamespace()) == 0 &&
(action.Matches("list", "users") ||
action.Matches("watch", "users") ||
action.Matches("get", "users")) {
continue
}
ret = append(ret, action)
}
ksClient, err := kubesphere.NewForConfig(cfg)
Expect(err).NotTo(HaveOccurred())
return ret
}
ksInformers := externalversions.NewSharedInformerFactory(ksClient, time.Second*30)
Expect(err).NotTo(HaveOccurred())
func (f *fixture) expectUpdateUserStatusAction(user *iamv1alpha2.User) {
expect := user.DeepCopy()
action := core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect)
action.Subresource = "status"
expect.Status.LastLoginTime = &f.loginRecord.CreationTimestamp
f.actions = append(f.actions, action)
}
loginRecordInformer := ksInformers.Iam().V1alpha2().LoginRecords()
userInformer := ksInformers.Iam().V1alpha2().Users()
func getKey(user *iamv1alpha2.User, t *testing.T) string {
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(user)
if err != nil {
t.Errorf("Unexpected error getting key for user %v: %v", user.Name, err)
return ""
}
return key
}
loginRecordController := NewLoginRecordController(k8sClient, ksClient, loginRecordInformer, userInformer, time.Hour, 1)
err = k8sManager.Add(loginRecordController)
Expect(err).NotTo(HaveOccurred())
func TestDoNothing(t *testing.T) {
f := newFixture(t)
user := newUser("test")
loginRecord := newLoginRecord("test")
go func() {
stopChan := ctrl.SetupSignalHandler()
ksInformers.Start(stopChan)
err = k8sManager.Start(stopChan)
Expect(err).ToNot(HaveOccurred())
}()
f.user = user
f.loginRecord = loginRecord
f.objects = append(f.objects, user, loginRecord)
close(done)
}, 60)
f.expectUpdateUserStatusAction(user)
f.run(getKey(user, t))
}
var _ = Describe("LoginRecord", func() {
const timeout = time.Second * 30
const interval = time.Second * 1
BeforeEach(func() {
admin := &iamv1alpha2.User{
ObjectMeta: metav1.ObjectMeta{Name: "admin"},
}
Expect(k8sManager.GetClient().Create(context.Background(), admin, &client.CreateOptions{})).Should(Succeed())
})
// Add Tests for OpenAPI validation (or additonal CRD features) specified in
// your API definition.
// Avoid adding tests for vanilla CRUD operations because they would
// test Kubernetes API server, which isn't the goal here.
Context("LoginRecord Controller", func() {
It("Should create successfully", func() {
ctx := context.Background()
username := "admin"
loginRecord := &iamv1alpha2.LoginRecord{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-1", username),
Labels: map[string]string{
iamv1alpha2.UserReferenceLabel: username,
},
},
Spec: iamv1alpha2.LoginRecordSpec{
Type: iamv1alpha2.Token,
Provider: "",
Success: true,
Reason: iamv1alpha2.AuthenticatedSuccessfully,
SourceIP: "",
UserAgent: "",
},
}
By("Expecting to create login record successfully")
Expect(k8sManager.GetClient().Create(ctx, loginRecord, &client.CreateOptions{})).Should(Succeed())
expected := &iamv1alpha2.LoginRecord{}
Eventually(func() bool {
err := k8sManager.GetClient().Get(ctx, types.NamespacedName{Name: loginRecord.Name}, expected)
fmt.Print(err)
return !expected.CreationTimestamp.IsZero()
}, timeout, interval).Should(BeTrue())
loginRecord.Name = fmt.Sprintf("%s-2", username)
loginRecord.ResourceVersion = ""
By("Expecting to create login record successfully")
Expect(k8sManager.GetClient().Create(ctx, loginRecord, &client.CreateOptions{})).Should(Succeed())
Eventually(func() bool {
k8sManager.GetClient().Get(ctx, types.NamespacedName{Name: loginRecord.Name}, expected)
return !expected.CreationTimestamp.IsZero()
}, timeout, interval).Should(BeTrue())
By("Expecting to limit login record successfully")
Eventually(func() bool {
loginRecordList := &iamv1alpha2.LoginRecordList{}
selector := labels.SelectorFromSet(labels.Set{iamv1alpha2.UserReferenceLabel: username})
k8sManager.GetClient().List(ctx, loginRecordList, &client.ListOptions{LabelSelector: selector})
return len(loginRecordList.Items) == 1
}, timeout, interval).Should(BeTrue())
})
})
})
var _ = AfterSuite(func() {
By("tearing down the test environment")
gexec.KillAndWait(5 * time.Second)
err := testEnv.Stop()
Expect(err).ToNot(HaveOccurred())
})
......@@ -43,7 +43,7 @@ var k8sClient client.Client
var k8sManager ctrl.Manager
var testEnv *envtest.Environment
func TestMain(t *testing.T) {
func TestServiceAccountController(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t,
"ServiceAccount Controller Test Suite",
......
......@@ -72,14 +72,14 @@ type handler struct {
options *authoptions.AuthenticationOptions
tokenOperator auth.TokenManagementInterface
passwordAuthenticator auth.PasswordAuthenticator
oauth2Authenticator auth.OAuth2Authenticator
oauth2Authenticator auth.OAuthAuthenticator
loginRecorder auth.LoginRecorder
}
func newHandler(im im.IdentityManagementInterface,
tokenOperator auth.TokenManagementInterface,
passwordAuthenticator auth.PasswordAuthenticator,
oauth2Authenticator auth.OAuth2Authenticator,
oauth2Authenticator auth.OAuthAuthenticator,
loginRecorder auth.LoginRecorder,
options *authoptions.AuthenticationOptions) *handler {
return &handler{im: im,
......
......@@ -37,7 +37,7 @@ import (
func AddToContainer(c *restful.Container, im im.IdentityManagementInterface,
tokenOperator auth.TokenManagementInterface,
passwordAuthenticator auth.PasswordAuthenticator,
oauth2Authenticator auth.OAuth2Authenticator,
oauth2Authenticator auth.OAuthAuthenticator,
loginRecorder auth.LoginRecorder,
options *authoptions.AuthenticationOptions) error {
......
......@@ -22,6 +22,7 @@ import (
"fmt"
"golang.org/x/crypto/bcrypt"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/constants"
"net/mail"
......@@ -46,7 +47,7 @@ type PasswordAuthenticator interface {
Authenticate(username, password string) (authuser.Info, string, error)
}
type OAuth2Authenticator interface {
type OAuthAuthenticator interface {
Authenticate(provider, code string) (authuser.Info, string, error)
}
......@@ -77,12 +78,12 @@ func NewPasswordAuthenticator(ksClient kubesphere.Interface,
return passwordAuthenticator
}
func NewOAuth2Authenticator(ksClient kubesphere.Interface,
userLister iamv1alpha2listers.UserLister,
options *authoptions.AuthenticationOptions) OAuth2Authenticator {
func NewOAuthAuthenticator(ksClient kubesphere.Interface,
ksInformer informers.SharedInformerFactory,
options *authoptions.AuthenticationOptions) OAuthAuthenticator {
oauth2Authenticator := &oauth2Authenticator{
ksClient: ksClient,
userGetter: &userGetter{userLister: userLister},
userGetter: &userGetter{userLister: ksInformer.Iam().V1alpha2().Users().Lister()},
authOptions: options,
}
return oauth2Authenticator
......
......@@ -19,11 +19,12 @@ package auth
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/errors"
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"
"strings"
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
)
type LoginRecorder interface {
......@@ -31,25 +32,34 @@ type LoginRecorder interface {
}
type loginRecorder struct {
ksClient kubesphere.Interface
ksClient kubesphere.Interface
userGetter *userGetter
}
func NewLoginRecorder(ksClient kubesphere.Interface) LoginRecorder {
func NewLoginRecorder(ksClient kubesphere.Interface, userLister iamv1alpha2listers.UserLister) LoginRecorder {
return &loginRecorder{
ksClient: ksClient,
ksClient: ksClient,
userGetter: &userGetter{userLister: userLister},
}
}
func (l *loginRecorder) RecordLogin(username string, loginType iamv1alpha2.LoginType, provider string, sourceIP string, userAgent string, authErr error) 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)
// RecordLogin Create v1alpha2.LoginRecord for existing accounts
func (l *loginRecorder) RecordLogin(username string, loginType iamv1alpha2.LoginType, provider, sourceIP, userAgent string, authErr error) error {
// only for existing accounts, solve the problem of huge entries
user, err := l.userGetter.findUser(username)
if err != nil {
// ignore not found error
if errors.IsNotFound(err) {
return nil
}
klog.Error(err)
return err
}
loginEntry := &iamv1alpha2.LoginRecord{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("%s-", username),
GenerateName: fmt.Sprintf("%s-", user.Name),
Labels: map[string]string{
iamv1alpha2.UserReferenceLabel: username,
iamv1alpha2.UserReferenceLabel: user.Name,
},
},
Spec: iamv1alpha2.LoginRecordSpec{
......@@ -67,7 +77,7 @@ func (l *loginRecorder) RecordLogin(username string, loginType iamv1alpha2.Login
loginEntry.Spec.Reason = authErr.Error()
}
_, err := l.ksClient.IamV1alpha2().LoginRecords().Create(context.Background(), loginEntry, metav1.CreateOptions{})
_, err = l.ksClient.IamV1alpha2().LoginRecords().Create(context.Background(), loginEntry, metav1.CreateOptions{})
if err != nil {
klog.Error(err)
return err
......
/*
Copyright 2018 The Kubernetes 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 metadatainformer
import (
"context"
"sync"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/informers"
"k8s.io/client-go/metadata"
"k8s.io/client-go/metadata/metadatalister"
"k8s.io/client-go/tools/cache"
)
// NewSharedInformerFactory constructs a new instance of metadataSharedInformerFactory for all namespaces.
func NewSharedInformerFactory(client metadata.Interface, defaultResync time.Duration) SharedInformerFactory {
return NewFilteredSharedInformerFactory(client, defaultResync, metav1.NamespaceAll, nil)
}
// NewFilteredSharedInformerFactory constructs a new instance of metadataSharedInformerFactory.
// Listers obtained via this factory will be subject to the same filters as specified here.
func NewFilteredSharedInformerFactory(client metadata.Interface, defaultResync time.Duration, namespace string, tweakListOptions TweakListOptionsFunc) SharedInformerFactory {
return &metadataSharedInformerFactory{
client: client,
defaultResync: defaultResync,
namespace: namespace,
informers: map[schema.GroupVersionResource]informers.GenericInformer{},
startedInformers: make(map[schema.GroupVersionResource]bool),
tweakListOptions: tweakListOptions,
}
}
type metadataSharedInformerFactory struct {
client metadata.Interface
defaultResync time.Duration
namespace string
lock sync.Mutex
informers map[schema.GroupVersionResource]informers.GenericInformer
// startedInformers is used for tracking which informers have been started.
// This allows Start() to be called multiple times safely.
startedInformers map[schema.GroupVersionResource]bool
tweakListOptions TweakListOptionsFunc
}
var _ SharedInformerFactory = &metadataSharedInformerFactory{}
func (f *metadataSharedInformerFactory) ForResource(gvr schema.GroupVersionResource) informers.GenericInformer {
f.lock.Lock()
defer f.lock.Unlock()
key := gvr
informer, exists := f.informers[key]
if exists {
return informer
}
informer = NewFilteredMetadataInformer(f.client, gvr, f.namespace, f.defaultResync, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
f.informers[key] = informer
return informer
}
// Start initializes all requested informers.
func (f *metadataSharedInformerFactory) Start(stopCh <-chan struct{}) {
f.lock.Lock()
defer f.lock.Unlock()
for informerType, informer := range f.informers {
if !f.startedInformers[informerType] {
go informer.Informer().Run(stopCh)
f.startedInformers[informerType] = true
}
}
}
// WaitForCacheSync waits for all started informers' cache were synced.
func (f *metadataSharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[schema.GroupVersionResource]bool {
informers := func() map[schema.GroupVersionResource]cache.SharedIndexInformer {
f.lock.Lock()
defer f.lock.Unlock()
informers := map[schema.GroupVersionResource]cache.SharedIndexInformer{}
for informerType, informer := range f.informers {
if f.startedInformers[informerType] {
informers[informerType] = informer.Informer()
}
}
return informers
}()
res := map[schema.GroupVersionResource]bool{}
for informType, informer := range informers {
res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced)
}
return res
}
// NewFilteredMetadataInformer constructs a new informer for a metadata type.
func NewFilteredMetadataInformer(client metadata.Interface, gvr schema.GroupVersionResource, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions TweakListOptionsFunc) informers.GenericInformer {
return &metadataInformer{
gvr: gvr,
informer: cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.Resource(gvr).Namespace(namespace).List(context.TODO(), options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.Resource(gvr).Namespace(namespace).Watch(context.TODO(), options)
},
},
&metav1.PartialObjectMetadata{},
resyncPeriod,
indexers,
),
}
}
type metadataInformer struct {
informer cache.SharedIndexInformer
gvr schema.GroupVersionResource
}
var _ informers.GenericInformer = &metadataInformer{}
func (d *metadataInformer) Informer() cache.SharedIndexInformer {
return d.informer
}
func (d *metadataInformer) Lister() cache.GenericLister {
return metadatalister.NewRuntimeObjectShim(metadatalister.New(d.informer.GetIndexer(), d.gvr))
}
/*
Copyright 2018 The Kubernetes 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 metadatainformer
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/informers"
)
// SharedInformerFactory provides access to a shared informer and lister for dynamic client
type SharedInformerFactory interface {
Start(stopCh <-chan struct{})
ForResource(gvr schema.GroupVersionResource) informers.GenericInformer
WaitForCacheSync(stopCh <-chan struct{}) map[schema.GroupVersionResource]bool
}
// TweakListOptionsFunc defines the signature of a helper function
// that wants to provide more listing options to API
type TweakListOptionsFunc func(*metav1.ListOptions)
/*
Copyright 2018 The Kubernetes 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 metadatalister
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
)
// Lister helps list resources.
type Lister interface {
// List lists all resources in the indexer.
List(selector labels.Selector) (ret []*metav1.PartialObjectMetadata, err error)
// Get retrieves a resource from the indexer with the given name
Get(name string) (*metav1.PartialObjectMetadata, error)
// Namespace returns an object that can list and get resources in a given namespace.
Namespace(namespace string) NamespaceLister
}
// NamespaceLister helps list and get resources.
type NamespaceLister interface {
// List lists all resources in the indexer for a given namespace.
List(selector labels.Selector) (ret []*metav1.PartialObjectMetadata, err error)
// Get retrieves a resource from the indexer for a given namespace and name.
Get(name string) (*metav1.PartialObjectMetadata, error)
}
/*
Copyright 2018 The Kubernetes 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 metadatalister
import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/cache"
)
var _ Lister = &metadataLister{}
var _ NamespaceLister = &metadataNamespaceLister{}
// metadataLister implements the Lister interface.
type metadataLister struct {
indexer cache.Indexer
gvr schema.GroupVersionResource
}
// New returns a new Lister.
func New(indexer cache.Indexer, gvr schema.GroupVersionResource) Lister {
return &metadataLister{indexer: indexer, gvr: gvr}
}
// List lists all resources in the indexer.
func (l *metadataLister) List(selector labels.Selector) (ret []*metav1.PartialObjectMetadata, err error) {
err = cache.ListAll(l.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*metav1.PartialObjectMetadata))
})
return ret, err
}
// Get retrieves a resource from the indexer with the given name
func (l *metadataLister) Get(name string) (*metav1.PartialObjectMetadata, error) {
obj, exists, err := l.indexer.GetByKey(name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(l.gvr.GroupResource(), name)
}
return obj.(*metav1.PartialObjectMetadata), nil
}
// Namespace returns an object that can list and get resources from a given namespace.
func (l *metadataLister) Namespace(namespace string) NamespaceLister {
return &metadataNamespaceLister{indexer: l.indexer, namespace: namespace, gvr: l.gvr}
}
// metadataNamespaceLister implements the NamespaceLister interface.
type metadataNamespaceLister struct {
indexer cache.Indexer
namespace string
gvr schema.GroupVersionResource
}
// List lists all resources in the indexer for a given namespace.
func (l *metadataNamespaceLister) List(selector labels.Selector) (ret []*metav1.PartialObjectMetadata, err error) {
err = cache.ListAllByNamespace(l.indexer, l.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*metav1.PartialObjectMetadata))
})
return ret, err
}
// Get retrieves a resource from the indexer for a given namespace and name.
func (l *metadataNamespaceLister) Get(name string) (*metav1.PartialObjectMetadata, error) {
obj, exists, err := l.indexer.GetByKey(l.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(l.gvr.GroupResource(), name)
}
return obj.(*metav1.PartialObjectMetadata), nil
}
/*
Copyright 2018 The Kubernetes 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 metadatalister
import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/cache"
)
var _ cache.GenericLister = &metadataListerShim{}
var _ cache.GenericNamespaceLister = &metadataNamespaceListerShim{}
// metadataListerShim implements the cache.GenericLister interface.
type metadataListerShim struct {
lister Lister
}
// NewRuntimeObjectShim returns a new shim for Lister.
// It wraps Lister so that it implements cache.GenericLister interface
func NewRuntimeObjectShim(lister Lister) cache.GenericLister {
return &metadataListerShim{lister: lister}
}
// List will return all objects across namespaces
func (s *metadataListerShim) List(selector labels.Selector) (ret []runtime.Object, err error) {
objs, err := s.lister.List(selector)
if err != nil {
return nil, err
}
ret = make([]runtime.Object, len(objs))
for index, obj := range objs {
ret[index] = obj
}
return ret, err
}
// Get will attempt to retrieve assuming that name==key
func (s *metadataListerShim) Get(name string) (runtime.Object, error) {
return s.lister.Get(name)
}
func (s *metadataListerShim) ByNamespace(namespace string) cache.GenericNamespaceLister {
return &metadataNamespaceListerShim{
namespaceLister: s.lister.Namespace(namespace),
}
}
// metadataNamespaceListerShim implements the NamespaceLister interface.
// It wraps NamespaceLister so that it implements cache.GenericNamespaceLister interface
type metadataNamespaceListerShim struct {
namespaceLister NamespaceLister
}
// List will return all objects in this namespace
func (ns *metadataNamespaceListerShim) List(selector labels.Selector) (ret []runtime.Object, err error) {
objs, err := ns.namespaceLister.List(selector)
if err != nil {
return nil, err
}
ret = make([]runtime.Object, len(objs))
for index, obj := range objs {
ret[index] = obj
}
return ret, err
}
// Get will attempt to retrieve by namespace and name
func (ns *metadataNamespaceListerShim) Get(name string) (runtime.Object, error) {
return ns.namespaceLister.Get(name)
}
......@@ -652,7 +652,7 @@ go.uber.org/zap/internal/bufferpool
go.uber.org/zap/internal/color
go.uber.org/zap/internal/exit
go.uber.org/zap/zapcore
# golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 => golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
# golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de => golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/crypto/bcrypt
golang.org/x/crypto/blowfish
golang.org/x/crypto/cast5
......@@ -701,7 +701,7 @@ golang.org/x/oauth2/internal
# golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 => golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/sync/errgroup
golang.org/x/sync/singleflight
# golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c => golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e
# golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 => golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e
golang.org/x/sys/cpu
golang.org/x/sys/unix
golang.org/x/sys/windows
......@@ -1424,8 +1424,6 @@ k8s.io/client-go/listers/storage/v1
k8s.io/client-go/listers/storage/v1alpha1
k8s.io/client-go/listers/storage/v1beta1
k8s.io/client-go/metadata
k8s.io/client-go/metadata/metadatainformer
k8s.io/client-go/metadata/metadatalister
k8s.io/client-go/pkg/apis/clientauthentication
k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1
k8s.io/client-go/pkg/apis/clientauthentication/v1beta1
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册