fix: get application details failed (#481)
Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
@@ -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) {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 (
|
|||||||
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) {
|
|||||||
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 (
|
|||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
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)
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ var (
|
|||||||
adminEmail string
|
adminEmail string
|
||||||
adminPassword string
|
adminPassword string
|
||||||
tokenExpireTime time.Duration
|
tokenExpireTime time.Duration
|
||||||
|
maxAuthFailed int
|
||||||
|
authTimeInterval time.Duration
|
||||||
initUsers []initUser
|
initUsers []initUser
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,13 +71,16 @@ 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 {
|
|||||||
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 {
|
|||||||
// 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) {
|
|||||||
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) {
|
|||||||
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 @@
|
|||||||
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 {
|
|||||||
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 (
|
|||||||
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user