From 72875c788594dddee586bb3adabd0b68d11e1ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E7=BE=81?= Date: Thu, 20 Jun 2019 16:23:24 +0800 Subject: [PATCH] fix: get application details failed (#481) Signed-off-by: hongming --- cmd/ks-iam/app/options/options.go | 2 + cmd/ks-iam/app/server.go | 2 +- pkg/apis/iam/v1alpha2/register.go | 2 +- pkg/apiserver/iam/auth.go | 6 +- .../namespace/namespace_controller.go | 2 +- pkg/models/applications/applications.go | 15 ++++ pkg/models/iam/im.go | 71 ++++++++++++++++--- pkg/models/resources/nodes.go | 6 ++ pkg/models/resources/resources.go | 1 + 9 files changed, 94 insertions(+), 13 deletions(-) diff --git a/cmd/ks-iam/app/options/options.go b/cmd/ks-iam/app/options/options.go index 9de464ec6..85c678447 100644 --- a/cmd/ks-iam/app/options/options.go +++ b/cmd/ks-iam/app/options/options.go @@ -28,6 +28,7 @@ type ServerRunOptions struct { AdminPassword string TokenExpireTime string JWTSecret string + AuthRateLimit string } func NewServerRunOptions() *ServerRunOptions { @@ -42,5 +43,6 @@ func (s *ServerRunOptions) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&s.AdminPassword, "admin-password", "passw0rd", "default administrator's password") fs.StringVar(&s.TokenExpireTime, "token-expire-time", "2h", "token expire time,valid time units are \"ns\",\"us\",\"ms\",\"s\",\"m\",\"h\"") fs.StringVar(&s.JWTSecret, "jwt-secret", "", "jwt secret") + fs.StringVar(&s.AuthRateLimit, "auth-rate-limit", "5/30m", "specifies the maximum number of authentication attempts permitted and time interval,valid time units are \"s\",\"m\",\"h\"") s.GenericServerRunOptions.AddFlags(fs) } diff --git a/cmd/ks-iam/app/server.go b/cmd/ks-iam/app/server.go index cd9e31c6d..17239a7ba 100644 --- a/cmd/ks-iam/app/server.go +++ b/cmd/ks-iam/app/server.go @@ -75,7 +75,7 @@ func Run(s *options.ServerRunOptions) error { initializeAdminJenkins() initializeDevOpsDatabase() - err = iam.Init(s.AdminEmail, s.AdminPassword, expireTime) + err = iam.Init(s.AdminEmail, s.AdminPassword, expireTime, s.AuthRateLimit) jwtutil.Setup(s.JWTSecret) if err != nil { diff --git a/pkg/apis/iam/v1alpha2/register.go b/pkg/apis/iam/v1alpha2/register.go index 719dd0007..0771f0c5b 100644 --- a/pkg/apis/iam/v1alpha2/register.go +++ b/pkg/apis/iam/v1alpha2/register.go @@ -116,7 +116,7 @@ func addWebService(c *restful.Container) error { Returns(http.StatusOK, ok, iam.TokenReview{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.POST("/login"). - To(iam.LoginHandler). + To(iam.Login). Doc("KubeSphere APIs support token-based authentication via the Authtoken request header. The POST Login API is used to retrieve the authentication token. After the authentication token is obtained, it must be inserted into the Authtoken header for all requests."). Reads(iam.LoginRequest{}). Returns(http.StatusOK, ok, models.Token{}). diff --git a/pkg/apiserver/iam/auth.go b/pkg/apiserver/iam/auth.go index 49245654e..2a0cde349 100644 --- a/pkg/apiserver/iam/auth.go +++ b/pkg/apiserver/iam/auth.go @@ -55,7 +55,7 @@ const ( KindTokenReview = "TokenReview" ) -func LoginHandler(req *restful.Request, resp *restful.Response) { +func Login(req *restful.Request, resp *restful.Response) { var loginRequest LoginRequest err := req.ReadEntity(&loginRequest) @@ -70,6 +70,10 @@ func LoginHandler(req *restful.Request, resp *restful.Response) { token, err := iam.Login(loginRequest.Username, loginRequest.Password, ip) if err != nil { + if serviceError, ok := err.(restful.ServiceError); ok { + resp.WriteHeaderAndEntity(serviceError.Code, errors.New(serviceError.Message)) + return + } resp.WriteHeaderAndEntity(http.StatusUnauthorized, errors.Wrap(err)) return } diff --git a/pkg/controller/namespace/namespace_controller.go b/pkg/controller/namespace/namespace_controller.go index 81917aa44..b9035d658 100644 --- a/pkg/controller/namespace/namespace_controller.go +++ b/pkg/controller/namespace/namespace_controller.go @@ -60,7 +60,7 @@ var ( defaultRoles = []rbac.Role{ {ObjectMeta: metav1.ObjectMeta{Name: "admin", Annotations: map[string]string{constants.DescriptionAnnotationKey: adminDescription, constants.CreatorAnnotationKey: constants.System}}, Rules: []rbac.PolicyRule{{Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}}}}, {ObjectMeta: metav1.ObjectMeta{Name: "operator", Annotations: map[string]string{constants.DescriptionAnnotationKey: operatorDescription, constants.CreatorAnnotationKey: constants.System}}, Rules: []rbac.PolicyRule{{Verbs: []string{"get", "list", "watch"}, APIGroups: []string{"*"}, Resources: []string{"*"}}, - {Verbs: []string{"*"}, APIGroups: []string{"", "apps", "extensions", "batch", "logging.kubesphere.io", "monitoring.kubesphere.io", "iam.kubesphere.io", "resources.kubesphere.io", "autoscaling", "alerting.kubesphere.io", "app.k8s.io", "servicemesh.kubesphere.io"}, Resources: []string{"*"}}}}, + {Verbs: []string{"*"}, APIGroups: []string{"", "apps", "extensions", "batch", "logging.kubesphere.io", "monitoring.kubesphere.io", "iam.kubesphere.io", "resources.kubesphere.io", "autoscaling", "alerting.kubesphere.io", "app.k8s.io", "servicemesh.kubesphere.io", "operations.kubesphere.io"}, Resources: []string{"*"}}}}, {ObjectMeta: metav1.ObjectMeta{Name: "viewer", Annotations: map[string]string{constants.DescriptionAnnotationKey: viewerDescription, constants.CreatorAnnotationKey: constants.System}}, Rules: []rbac.PolicyRule{{Verbs: []string{"get", "list", "watch"}, APIGroups: []string{"*"}, Resources: []string{"*"}}}}, } ) diff --git a/pkg/models/applications/applications.go b/pkg/models/applications/applications.go index c90a00cdf..78b792f83 100644 --- a/pkg/models/applications/applications.go +++ b/pkg/models/applications/applications.go @@ -148,6 +148,11 @@ func getWorkLoads(namespace string, clusterRoles []openpitrix.ClusterRole) (*wor item, err := informers.SharedInformerFactory().Apps().V1().Deployments().Lister().Deployments(namespace).Get(name) if err != nil { + // app not ready + if errors.IsNotFound(err) { + continue + } + glog.Error(err) return nil, err } @@ -159,6 +164,11 @@ func getWorkLoads(namespace string, clusterRoles []openpitrix.ClusterRole) (*wor name := strings.Split(workLoadName, openpitrix.DaemonSuffix)[0] item, err := informers.SharedInformerFactory().Apps().V1().DaemonSets().Lister().DaemonSets(namespace).Get(name) if err != nil { + // app not ready + if errors.IsNotFound(err) { + continue + } + glog.Error(err) return nil, err } works.Daemonsets = append(works.Daemonsets, *item) @@ -169,6 +179,11 @@ func getWorkLoads(namespace string, clusterRoles []openpitrix.ClusterRole) (*wor name := strings.Split(workLoadName, openpitrix.StateSuffix)[0] item, err := informers.SharedInformerFactory().Apps().V1().StatefulSets().Lister().StatefulSets(namespace).Get(name) if err != nil { + // app not ready + if errors.IsNotFound(err) { + continue + } + glog.Error(err) return nil, err } works.Statefulsets = append(works.Statefulsets, *item) diff --git a/pkg/models/iam/im.go b/pkg/models/iam/im.go index 0ab9cae4e..bbe9e49b0 100644 --- a/pkg/models/iam/im.go +++ b/pkg/models/iam/im.go @@ -56,10 +56,12 @@ import ( ) var ( - adminEmail string - adminPassword string - tokenExpireTime time.Duration - initUsers []initUser + adminEmail string + adminPassword string + tokenExpireTime time.Duration + maxAuthFailed int + authTimeInterval time.Duration + initUsers []initUser ) type initUser struct { @@ -68,14 +70,17 @@ type initUser struct { } const ( - userInitFile = "/etc/ks-iam/users.json" + userInitFile = "/etc/ks-iam/users.json" + authRateLimitRegex = `(\d+)/(\d+[s|m|h])` + defaultMaxAuthFailed = 5 + defaultAuthTimeInterval = 30 * time.Minute ) -func Init(email, password string, t time.Duration) error { +func Init(email, password string, expireTime time.Duration, authRateLimit string) error { adminEmail = email adminPassword = password - tokenExpireTime = t - + tokenExpireTime = expireTime + maxAuthFailed, authTimeInterval = parseAuthRateLimit(authRateLimit) conn, err := ldapclient.Client() if err != nil { @@ -101,6 +106,23 @@ func Init(email, password string, t time.Duration) error { return nil } +func parseAuthRateLimit(authRateLimit string) (int, time.Duration) { + regex := regexp.MustCompile(authRateLimitRegex) + groups := regex.FindStringSubmatch(authRateLimit) + + maxCount := defaultMaxAuthFailed + timeInterval := defaultAuthTimeInterval + + if len(groups) == 3 { + maxCount, _ = strconv.Atoi(groups[1]) + timeInterval, _ = time.ParseDuration(groups[2]) + } else { + glog.Warning("invalid auth rate limit", authRateLimit) + } + + return maxCount, timeInterval +} + func checkAndCreateDefaultGroup(conn ldap.Client) error { groupSearchRequest := ldap.NewSearchRequest( @@ -203,9 +225,23 @@ func createGroupsBaseDN(conn ldap.Client) error { // User login func Login(username string, password string, ip string) (*models.Token, error) { + redisClient := redis.Client() + + records, err := redisClient.Keys(fmt.Sprintf("kubesphere:authfailed:%s:*", username)).Result() + + if err != nil { + glog.Error(err) + return nil, err + } + + if len(records) >= maxAuthFailed { + return nil, restful.NewError(http.StatusTooManyRequests, "auth rate limit exceeded") + } + conn, err := ldapclient.Client() if err != nil { + glog.Error(err) return nil, err } @@ -237,7 +273,13 @@ func Login(username string, password string, ip string) (*models.Token, error) { err = conn.Bind(dn, password) if err != nil { - glog.Errorln("auth error", username, err) + glog.Infoln("auth failed", username, err) + + if ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials) { + loginFailedRecord := fmt.Sprintf("kubesphere:authfailed:%s:%d", username, time.Now().UnixNano()) + redisClient.Set(loginFailedRecord, "", authTimeInterval) + } + return nil, err } @@ -876,6 +918,17 @@ func UpdateUser(user *models.User) (*models.User, error) { return nil, err } + // clear auth failed record + if user.Password != "" { + redisClient := redis.Client() + + records, err := redisClient.Keys(fmt.Sprintf("kubesphere:authfailed:%s:*", user.Username)).Result() + + if err == nil { + redisClient.Del(records...) + } + } + return GetUserInfo(user.Username) } func DeleteGroup(path string) error { diff --git a/pkg/models/resources/nodes.go b/pkg/models/resources/nodes.go index 6daa02c61..38e607f0b 100644 --- a/pkg/models/resources/nodes.go +++ b/pkg/models/resources/nodes.go @@ -18,6 +18,7 @@ package resources import ( + "fmt" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/params" @@ -45,6 +46,11 @@ func (*nodeSearcher) match(match map[string]string, item *v1.Node) bool { if !sliceutil.HasString(names, item.Name) { return false } + case Role: + labelKey := fmt.Sprintf("node-role.kubernetes.io/%s", v) + if _, ok := item.Labels[labelKey]; !ok { + return false + } case Keyword: if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) { return false diff --git a/pkg/models/resources/resources.go b/pkg/models/resources/resources.go index 428ee70fb..4017cced5 100644 --- a/pkg/models/resources/resources.go +++ b/pkg/models/resources/resources.go @@ -61,6 +61,7 @@ const ( Label = "label" OwnerKind = "ownerKind" OwnerName = "ownerName" + Role = "role" CreateTime = "createTime" UpdateTime = "updateTime" LastScheduleTime = "lastScheduleTime"