Merge branch 'master' into monitoring-fix
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -204,7 +204,6 @@ func addWebService(c *restful.Container) error {
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(webservice.PathParameter("devops", "devops project's Id")).
|
||||
Param(webservice.PathParameter("credentials", "credential's Id")).
|
||||
Param(webservice.QueryParameter("domain", "credential's domain")).
|
||||
Param(webservice.QueryParameter("content", "get additional content")).
|
||||
Returns(http.StatusOK, RespOK, devops.JenkinsCredential{}).
|
||||
Reads(devops.JenkinsCredential{}))
|
||||
@@ -214,8 +213,6 @@ func addWebService(c *restful.Container) error {
|
||||
Doc("Get project credential pipeline").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(webservice.PathParameter("devops", "devops project's Id")).
|
||||
Param(webservice.PathParameter("credentials", "credential's Id")).
|
||||
Param(webservice.QueryParameter("domain", "credential's domain")).
|
||||
Returns(http.StatusOK, RespOK, []devops.JenkinsCredential{}).
|
||||
Reads([]devops.JenkinsCredential{}))
|
||||
|
||||
@@ -754,7 +751,7 @@ func addWebService(c *restful.Container) error {
|
||||
webservice.Route(webservice.POST("/devops/notifycommit").
|
||||
To(devopsapi.PostNotifyCommit).
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Doc("Get notify commit by HTTP POST method.").
|
||||
Doc("Get notification commit by HTTP POST method.").
|
||||
Consumes("application/json").
|
||||
Produces("text/plain; charset=utf-8").
|
||||
Param(webservice.QueryParameter("url", "url of git scm").
|
||||
|
||||
@@ -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{}).
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{"*"}}}},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1025,9 +1025,9 @@ type ReqJson struct {
|
||||
|
||||
// ToJenkinsfile response
|
||||
type ResJenkinsfile struct {
|
||||
Status string `json:"status,omitempty" description:"status"`
|
||||
Status string `json:"status,omitempty" description:"status e.g. ok"`
|
||||
Data struct {
|
||||
Result string `json:"result,omitempty" description:"result"`
|
||||
Result string `json:"result,omitempty" description:"result e.g. success"`
|
||||
Jenkinsfile string `json:"jenkinsfile,omitempty" description:"jenkinsfile"`
|
||||
Errors []struct {
|
||||
Location []string `json:"location,omitempty" description:"err location"`
|
||||
@@ -1041,9 +1041,9 @@ type ReqJenkinsfile struct {
|
||||
}
|
||||
|
||||
type ResJson struct {
|
||||
Status string `json:"status,omitempty" description:"status"`
|
||||
Status string `json:"status,omitempty" description:"status e.g. ok"`
|
||||
Data struct {
|
||||
Result string `json:"result,omitempty" description:"result"`
|
||||
Result string `json:"result,omitempty" description:"result e.g. success"`
|
||||
JSON struct {
|
||||
Pipeline struct {
|
||||
Stages []interface{} `json:"stages,omitempty" description:"stages"`
|
||||
|
||||
@@ -24,7 +24,7 @@ type DevOpsProjectMembership struct {
|
||||
Username string `json:"username" description:"member's username,username can uniquely identify a user"`
|
||||
ProjectId string `json:"project_id" db:"project_id" description:"the devops projects which project membership belongs to"`
|
||||
Role string `json:"role" description:"devops project membership's role type. e.g. owner '"`
|
||||
Status string `json:"status" description:"Desperated, status of project membership"`
|
||||
Status string `json:"status" description:"Desperated, status of project membership. e.g. active "`
|
||||
GrantBy string `json:"grand_by,omitempty" description:"Username of the user who assigned the role"`
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ func UpdateProject(project *DevOpsProject) (*DevOpsProject, error) {
|
||||
query.Set(DevOpsProjectExtraColumn, project.Extra)
|
||||
}
|
||||
if !govalidator.IsNull(project.Name) {
|
||||
query.Set(DevOpsProjectNameColumn, project.Extra)
|
||||
query.Set(DevOpsProjectNameColumn, project.Name)
|
||||
}
|
||||
if len(query.UpdateStmt.Value) > 0 {
|
||||
_, err := query.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -61,6 +61,7 @@ const (
|
||||
Label = "label"
|
||||
OwnerKind = "ownerKind"
|
||||
OwnerName = "ownerName"
|
||||
Role = "role"
|
||||
CreateTime = "createTime"
|
||||
UpdateTime = "updateTime"
|
||||
LastScheduleTime = "lastScheduleTime"
|
||||
|
||||
Reference in New Issue
Block a user