未验证 提交 72875c78 编写于 作者: 不羁 提交者: soulseen

fix: get application details failed (#481)

Signed-off-by: Nhongming <talonwan@yunify.com>
上级 ed0b211c
...@@ -28,6 +28,7 @@ type ServerRunOptions struct { ...@@ -28,6 +28,7 @@ type ServerRunOptions struct {
AdminPassword string AdminPassword string
TokenExpireTime string TokenExpireTime string
JWTSecret string JWTSecret string
AuthRateLimit string
} }
func NewServerRunOptions() *ServerRunOptions { func NewServerRunOptions() *ServerRunOptions {
...@@ -42,5 +43,6 @@ func (s *ServerRunOptions) AddFlags(fs *pflag.FlagSet) { ...@@ -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.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.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.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) s.GenericServerRunOptions.AddFlags(fs)
} }
...@@ -75,7 +75,7 @@ func Run(s *options.ServerRunOptions) error { ...@@ -75,7 +75,7 @@ func Run(s *options.ServerRunOptions) error {
initializeAdminJenkins() initializeAdminJenkins()
initializeDevOpsDatabase() initializeDevOpsDatabase()
err = iam.Init(s.AdminEmail, s.AdminPassword, expireTime) err = iam.Init(s.AdminEmail, s.AdminPassword, expireTime, s.AuthRateLimit)
jwtutil.Setup(s.JWTSecret) jwtutil.Setup(s.JWTSecret)
if err != nil { if err != nil {
......
...@@ -116,7 +116,7 @@ func addWebService(c *restful.Container) error { ...@@ -116,7 +116,7 @@ func addWebService(c *restful.Container) error {
Returns(http.StatusOK, ok, iam.TokenReview{}). Returns(http.StatusOK, ok, iam.TokenReview{}).
Metadata(restfulspec.KeyOpenAPITags, tags)) Metadata(restfulspec.KeyOpenAPITags, tags))
ws.Route(ws.POST("/login"). 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."). 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{}). Reads(iam.LoginRequest{}).
Returns(http.StatusOK, ok, models.Token{}). Returns(http.StatusOK, ok, models.Token{}).
......
...@@ -55,7 +55,7 @@ const ( ...@@ -55,7 +55,7 @@ const (
KindTokenReview = "TokenReview" KindTokenReview = "TokenReview"
) )
func LoginHandler(req *restful.Request, resp *restful.Response) { func Login(req *restful.Request, resp *restful.Response) {
var loginRequest LoginRequest var loginRequest LoginRequest
err := req.ReadEntity(&loginRequest) err := req.ReadEntity(&loginRequest)
...@@ -70,6 +70,10 @@ func LoginHandler(req *restful.Request, resp *restful.Response) { ...@@ -70,6 +70,10 @@ func LoginHandler(req *restful.Request, resp *restful.Response) {
token, err := iam.Login(loginRequest.Username, loginRequest.Password, ip) token, err := iam.Login(loginRequest.Username, loginRequest.Password, ip)
if err != nil { 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)) resp.WriteHeaderAndEntity(http.StatusUnauthorized, errors.Wrap(err))
return return
} }
......
...@@ -60,7 +60,7 @@ var ( ...@@ -60,7 +60,7 @@ var (
defaultRoles = []rbac.Role{ 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: "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{"*"}}, {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{"*"}}}}, {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{"*"}}}},
} }
) )
......
...@@ -148,6 +148,11 @@ func getWorkLoads(namespace string, clusterRoles []openpitrix.ClusterRole) (*wor ...@@ -148,6 +148,11 @@ func getWorkLoads(namespace string, clusterRoles []openpitrix.ClusterRole) (*wor
item, err := informers.SharedInformerFactory().Apps().V1().Deployments().Lister().Deployments(namespace).Get(name) item, err := informers.SharedInformerFactory().Apps().V1().Deployments().Lister().Deployments(namespace).Get(name)
if err != nil { if err != nil {
// app not ready
if errors.IsNotFound(err) {
continue
}
glog.Error(err)
return nil, err return nil, err
} }
...@@ -159,6 +164,11 @@ func getWorkLoads(namespace string, clusterRoles []openpitrix.ClusterRole) (*wor ...@@ -159,6 +164,11 @@ func getWorkLoads(namespace string, clusterRoles []openpitrix.ClusterRole) (*wor
name := strings.Split(workLoadName, openpitrix.DaemonSuffix)[0] name := strings.Split(workLoadName, openpitrix.DaemonSuffix)[0]
item, err := informers.SharedInformerFactory().Apps().V1().DaemonSets().Lister().DaemonSets(namespace).Get(name) item, err := informers.SharedInformerFactory().Apps().V1().DaemonSets().Lister().DaemonSets(namespace).Get(name)
if err != nil { if err != nil {
// app not ready
if errors.IsNotFound(err) {
continue
}
glog.Error(err)
return nil, err return nil, err
} }
works.Daemonsets = append(works.Daemonsets, *item) works.Daemonsets = append(works.Daemonsets, *item)
...@@ -169,6 +179,11 @@ func getWorkLoads(namespace string, clusterRoles []openpitrix.ClusterRole) (*wor ...@@ -169,6 +179,11 @@ func getWorkLoads(namespace string, clusterRoles []openpitrix.ClusterRole) (*wor
name := strings.Split(workLoadName, openpitrix.StateSuffix)[0] name := strings.Split(workLoadName, openpitrix.StateSuffix)[0]
item, err := informers.SharedInformerFactory().Apps().V1().StatefulSets().Lister().StatefulSets(namespace).Get(name) item, err := informers.SharedInformerFactory().Apps().V1().StatefulSets().Lister().StatefulSets(namespace).Get(name)
if err != nil { if err != nil {
// app not ready
if errors.IsNotFound(err) {
continue
}
glog.Error(err)
return nil, err return nil, err
} }
works.Statefulsets = append(works.Statefulsets, *item) works.Statefulsets = append(works.Statefulsets, *item)
......
...@@ -56,10 +56,12 @@ import ( ...@@ -56,10 +56,12 @@ import (
) )
var ( var (
adminEmail string adminEmail string
adminPassword string adminPassword string
tokenExpireTime time.Duration tokenExpireTime time.Duration
initUsers []initUser maxAuthFailed int
authTimeInterval time.Duration
initUsers []initUser
) )
type initUser struct { type initUser struct {
...@@ -68,14 +70,17 @@ type initUser struct { ...@@ -68,14 +70,17 @@ type initUser struct {
} }
const ( 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 adminEmail = email
adminPassword = password adminPassword = password
tokenExpireTime = t tokenExpireTime = expireTime
maxAuthFailed, authTimeInterval = parseAuthRateLimit(authRateLimit)
conn, err := ldapclient.Client() conn, err := ldapclient.Client()
if err != nil { if err != nil {
...@@ -101,6 +106,23 @@ func Init(email, password string, t time.Duration) error { ...@@ -101,6 +106,23 @@ func Init(email, password string, t time.Duration) error {
return nil 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 { func checkAndCreateDefaultGroup(conn ldap.Client) error {
groupSearchRequest := ldap.NewSearchRequest( groupSearchRequest := ldap.NewSearchRequest(
...@@ -203,9 +225,23 @@ func createGroupsBaseDN(conn ldap.Client) error { ...@@ -203,9 +225,23 @@ func createGroupsBaseDN(conn ldap.Client) error {
// User login // User login
func Login(username string, password string, ip string) (*models.Token, error) { 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() conn, err := ldapclient.Client()
if err != nil { if err != nil {
glog.Error(err)
return nil, err return nil, err
} }
...@@ -237,7 +273,13 @@ func Login(username string, password string, ip string) (*models.Token, error) { ...@@ -237,7 +273,13 @@ func Login(username string, password string, ip string) (*models.Token, error) {
err = conn.Bind(dn, password) err = conn.Bind(dn, password)
if err != nil { 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 return nil, err
} }
...@@ -876,6 +918,17 @@ func UpdateUser(user *models.User) (*models.User, error) { ...@@ -876,6 +918,17 @@ func UpdateUser(user *models.User) (*models.User, error) {
return nil, err 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) return GetUserInfo(user.Username)
} }
func DeleteGroup(path string) error { func DeleteGroup(path string) error {
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
package resources package resources
import ( import (
"fmt"
"kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/params" "kubesphere.io/kubesphere/pkg/params"
...@@ -45,6 +46,11 @@ func (*nodeSearcher) match(match map[string]string, item *v1.Node) bool { ...@@ -45,6 +46,11 @@ func (*nodeSearcher) match(match map[string]string, item *v1.Node) bool {
if !sliceutil.HasString(names, item.Name) { if !sliceutil.HasString(names, item.Name) {
return false return false
} }
case Role:
labelKey := fmt.Sprintf("node-role.kubernetes.io/%s", v)
if _, ok := item.Labels[labelKey]; !ok {
return false
}
case Keyword: case Keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) { if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false return false
......
...@@ -61,6 +61,7 @@ const ( ...@@ -61,6 +61,7 @@ const (
Label = "label" Label = "label"
OwnerKind = "ownerKind" OwnerKind = "ownerKind"
OwnerName = "ownerName" OwnerName = "ownerName"
Role = "role"
CreateTime = "createTime" CreateTime = "createTime"
UpdateTime = "updateTime" UpdateTime = "updateTime"
LastScheduleTime = "lastScheduleTime" LastScheduleTime = "lastScheduleTime"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册