From abf9fee8450bd16348a6ea2ed793efb7520a3e4d Mon Sep 17 00:00:00 2001 From: hongming Date: Mon, 24 Feb 2020 15:39:36 +0800 Subject: [PATCH] code refactor (#1786) * implement LDAP mock client Signed-off-by: hongming * update Signed-off-by: hongming * update Signed-off-by: hongming * resolve conflict Signed-off-by: hongming --- cmd/controller-manager/app/options/options.go | 2 +- cmd/ks-apiserver/app/options/options.go | 2 +- cmd/ks-apiserver/app/server.go | 2 +- cmd/ks-iam/app/options/options.go | 2 +- go.mod | 1 + pkg/api/iam/v1alpha2/types.go | 44 +- pkg/apiserver/iam/am.go | 18 - pkg/apiserver/iam/auth.go | 69 -- pkg/apiserver/iam/groups.go | 201 ---- pkg/apiserver/iam/im.go | 259 ---- pkg/apiserver/iam/types.go | 18 - pkg/apiserver/iam/workspaces.go | 165 --- pkg/kapis/iam/v1alpha2/handler.go | 457 +++++-- pkg/kapis/iam/v1alpha2/register.go | 181 +-- pkg/kapis/kapis.go | 17 +- pkg/kapis/openpitrix/v1/handler.go | 254 ++-- pkg/kapis/tenant/v1alpha2/devops.go | 154 --- pkg/kapis/tenant/v1alpha2/handler.go | 95 +- pkg/kapis/tenant/v1alpha2/register.go | 14 +- pkg/kapis/terminal/v1alpha2/register.go | 7 +- pkg/models/iam/am.go | 218 +--- pkg/models/iam/im.go | 1070 +++-------------- pkg/models/iam/im_test.go | 55 + pkg/models/iam/types.go | 22 +- pkg/models/openpitrix/applications_test.go | 1 - pkg/models/tenant/namespaces.go | 9 +- pkg/models/tenant/tenant.go | 101 +- pkg/models/tenant/workspaces.go | 23 +- pkg/server/config/config.go | 4 +- pkg/server/errors/errors.go | 19 +- pkg/simple/client/factory.go | 12 +- pkg/simple/client/ldap/channel.go | 6 +- pkg/simple/client/ldap/conn.go | 2 +- pkg/simple/client/ldap/ldap.go | 40 +- pkg/simple/client/ldap/mock.go | 231 ++++ pkg/simple/client/ldap/options.go | 9 +- pkg/simple/client/openpitrix/openpitrix.go | 2 +- pkg/simple/client/openpitrix/options.go | 2 +- pkg/utils/jwtutil/jwt.go | 17 +- 39 files changed, 1338 insertions(+), 2467 deletions(-) delete mode 100644 pkg/apiserver/iam/am.go delete mode 100644 pkg/apiserver/iam/auth.go delete mode 100644 pkg/apiserver/iam/groups.go delete mode 100644 pkg/apiserver/iam/im.go delete mode 100644 pkg/apiserver/iam/types.go delete mode 100644 pkg/apiserver/iam/workspaces.go delete mode 100644 pkg/kapis/tenant/v1alpha2/devops.go create mode 100644 pkg/models/iam/im_test.go create mode 100644 pkg/simple/client/ldap/mock.go diff --git a/cmd/controller-manager/app/options/options.go b/cmd/controller-manager/app/options/options.go index 7da45f106..7d737fe7f 100644 --- a/cmd/controller-manager/app/options/options.go +++ b/cmd/controller-manager/app/options/options.go @@ -29,7 +29,7 @@ func NewKubeSphereControllerManagerOptions() *KubeSphereControllerManagerOptions KubernetesOptions: k8s.NewKubernetesOptions(), DevopsOptions: jenkins.NewDevopsOptions(), S3Options: s3.NewS3Options(), - OpenPitrixOptions: openpitrix.NewOpenPitrixOptions(), + OpenPitrixOptions: openpitrix.NewOptions(), LeaderElection: &leaderelection.LeaderElectionConfig{ LeaseDuration: 30 * time.Second, RenewDeadline: 15 * time.Second, diff --git a/cmd/ks-apiserver/app/options/options.go b/cmd/ks-apiserver/app/options/options.go index 5cbcb660a..b65b11b05 100644 --- a/cmd/ks-apiserver/app/options/options.go +++ b/cmd/ks-apiserver/app/options/options.go @@ -42,7 +42,7 @@ func NewServerRunOptions() *ServerRunOptions { MySQLOptions: mysql.NewMySQLOptions(), MonitoringOptions: prometheus.NewPrometheusOptions(), S3Options: s3.NewS3Options(), - OpenPitrixOptions: openpitrix.NewOpenPitrixOptions(), + OpenPitrixOptions: openpitrix.NewOptions(), LoggingOptions: esclient.NewElasticSearchOptions(), } diff --git a/cmd/ks-apiserver/app/server.go b/cmd/ks-apiserver/app/server.go index 98f106ac9..3a67fa6b3 100644 --- a/cmd/ks-apiserver/app/server.go +++ b/cmd/ks-apiserver/app/server.go @@ -176,7 +176,7 @@ func createDeps(s *options.ServerRunOptions, stopCh <-chan struct{}) *apiserver. } if s.OpenPitrixOptions != nil && !s.OpenPitrixOptions.IsEmpty() { - deps.OpenPitrix, err = openpitrix.NewOpenPitrixClient(s.OpenPitrixOptions) + deps.OpenPitrix, err = openpitrix.NewClient(s.OpenPitrixOptions) if err != nil { klog.Fatalf("error happened when initializing openpitrix client, %v", err) } diff --git a/cmd/ks-iam/app/options/options.go b/cmd/ks-iam/app/options/options.go index 89e10cf4d..a157567e4 100644 --- a/cmd/ks-iam/app/options/options.go +++ b/cmd/ks-iam/app/options/options.go @@ -49,7 +49,7 @@ func NewServerRunOptions() *ServerRunOptions { s := &ServerRunOptions{ GenericServerRunOptions: genericoptions.NewServerRunOptions(), KubernetesOptions: k8s.NewKubernetesOptions(), - LdapOptions: ldap.NewLdapOptions(), + LdapOptions: ldap.NewOptions(), MySQLOptions: mysql.NewMySQLOptions(), RedisOptions: cache.NewRedisOptions(), } diff --git a/go.mod b/go.mod index 9cf07cb09..3ada1c68a 100644 --- a/go.mod +++ b/go.mod @@ -70,6 +70,7 @@ require ( github.com/opencontainers/go-digest v1.0.0-rc1 github.com/opencontainers/image-spec v1.0.1 // indirect github.com/openshift/api v3.9.0+incompatible // indirect + github.com/pkg/errors v0.8.1 github.com/projectcalico/libcalico-go v1.7.2-0.20191104213956-8f81e1e344ce github.com/prometheus/common v0.4.0 github.com/sony/sonyflake v0.0.0-20181109022403-6d5bd6181009 diff --git a/pkg/api/iam/v1alpha2/types.go b/pkg/api/iam/v1alpha2/types.go index 7c527a263..88cd91142 100644 --- a/pkg/api/iam/v1alpha2/types.go +++ b/pkg/api/iam/v1alpha2/types.go @@ -47,12 +47,16 @@ type LoginRequest struct { Password string `json:"password" description:"password"` } -type UserCreateRequest struct { +type UserDetail struct { *iam.User ClusterRole string `json:"cluster_role"` } -func (request *UserCreateRequest) Validate() error { +type CreateUserRequest struct { + *UserDetail +} + +func (request *CreateUserRequest) Validate() error { if request.Username == "" { return fmt.Errorf("username must not be empty") } @@ -68,3 +72,39 @@ func (request *UserCreateRequest) Validate() error { return nil } + +type ModifyUserRequest struct { + *UserDetail + CurrentPassword string `json:"current_password,omitempty" description:"this is necessary if you need to change your password"` +} + +func (request *TokenReview) Validate() error { + if request.Spec == nil || request.Spec.Token == "" { + return fmt.Errorf("token must not be null") + } + return nil +} + +func (request ModifyUserRequest) Validate() error { + + // Parses a single RFC 5322 address, e.g. "Barry Gibbs " + if _, err := mail.ParseAddress(request.Email); err != nil { + return fmt.Errorf("invalid email: %s", request.Email) + } + + if request.Password != "" { + if len(request.Password) < minPasswordLength { + return fmt.Errorf("password must be at least %d characters long", minPasswordLength) + } + if len(request.CurrentPassword) < minPasswordLength { + return fmt.Errorf("password must be at least %d characters long", minPasswordLength) + } + + } + return nil +} + +type ListUserResponse struct { + Items []*UserDetail `json:"items"` + TotalCount int `json:"total_count"` +} diff --git a/pkg/apiserver/iam/am.go b/pkg/apiserver/iam/am.go deleted file mode 100644 index 6c25b043f..000000000 --- a/pkg/apiserver/iam/am.go +++ /dev/null @@ -1,18 +0,0 @@ -/* - - Copyright 2019 The KubeSphere 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 iam diff --git a/pkg/apiserver/iam/auth.go b/pkg/apiserver/iam/auth.go deleted file mode 100644 index e098b462f..000000000 --- a/pkg/apiserver/iam/auth.go +++ /dev/null @@ -1,69 +0,0 @@ -/* - - Copyright 2019 The KubeSphere 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 iam - -import ( - "fmt" - "github.com/emicklei/go-restful" - iamv1alpha2 "kubesphere.io/kubesphere/pkg/api/iam/v1alpha2" - "kubesphere.io/kubesphere/pkg/models" - "kubesphere.io/kubesphere/pkg/models/iam" - "kubesphere.io/kubesphere/pkg/server/errors" - "kubesphere.io/kubesphere/pkg/utils/iputil" - "net/http" -) - -type OAuthRequest struct { - GrantType string `json:"grant_type"` - Username string `json:"username,omitempty" description:"username"` - Password string `json:"password,omitempty" description:"password"` - RefreshToken string `json:"refresh_token,omitempty"` -} - -func OAuth(req *restful.Request, resp *restful.Response) { - - authRequest := &OAuthRequest{} - - err := req.ReadEntity(authRequest) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - var result *models.AuthGrantResponse - switch authRequest.GrantType { - case "refresh_token": - result, err = iam.RefreshToken(authRequest.RefreshToken) - case "password": - ip := iputil.RemoteIp(req.Request) - result, err = iam.PasswordCredentialGrant(authRequest.Username, authRequest.Password, ip) - default: - resp.Header().Set("WWW-Authenticate", "grant_type is not supported") - resp.WriteHeaderAndEntity(http.StatusUnauthorized, errors.Wrap(fmt.Errorf("grant_type is not supported"))) - return - } - - if err != nil { - resp.Header().Set("WWW-Authenticate", err.Error()) - resp.WriteHeaderAndEntity(http.StatusUnauthorized, errors.Wrap(err)) - return - } - - resp.WriteEntity(result) - -} diff --git a/pkg/apiserver/iam/groups.go b/pkg/apiserver/iam/groups.go deleted file mode 100644 index e59f0796a..000000000 --- a/pkg/apiserver/iam/groups.go +++ /dev/null @@ -1,201 +0,0 @@ -/* - - Copyright 2019 The KubeSphere 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 iam - -import ( - "fmt" - "net/http" - "regexp" - "strings" - - "github.com/emicklei/go-restful" - "github.com/go-ldap/ldap" - - "kubesphere.io/kubesphere/pkg/models" - "kubesphere.io/kubesphere/pkg/models/iam" - "kubesphere.io/kubesphere/pkg/server/errors" -) - -func CreateGroup(req *restful.Request, resp *restful.Response) { - var group models.Group - - err := req.ReadEntity(&group) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - - if !regexp.MustCompile("[a-z0-9]([-a-z0-9]*[a-z0-9])?").MatchString(group.Name) { - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.New(fmt.Sprintf("incalid group name %s", group))) - return - } - - created, err := iam.CreateGroup(&group) - - if err != nil { - if ldap.IsErrorWithCode(err, ldap.LDAPResultEntryAlreadyExists) { - resp.WriteHeaderAndEntity(http.StatusConflict, errors.Wrap(err)) - } else { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - } - return - } - - resp.WriteAsJson(created) -} - -func DeleteGroup(req *restful.Request, resp *restful.Response) { - path := req.PathParameter("group") - - if path == "" { - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(fmt.Errorf("group path must not be null"))) - return - } - - err := iam.DeleteGroup(path) - - if err != nil { - if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { - resp.WriteHeaderAndEntity(http.StatusNotFound, errors.Wrap(err)) - return - } - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - resp.WriteAsJson(errors.None) - -} - -func UpdateGroup(req *restful.Request, resp *restful.Response) { - groupPathInPath := req.PathParameter("group") - - var group models.Group - - req.ReadEntity(&group) - - if groupPathInPath != group.Path { - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(fmt.Errorf("the path of group (%s) does not match the path on the URL (%s)", group.Path, groupPathInPath))) - return - } - - edited, err := iam.UpdateGroup(&group) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - resp.WriteAsJson(edited) - -} - -func DescribeGroup(req *restful.Request, resp *restful.Response) { - - path := req.PathParameter("group") - - group, err := iam.DescribeGroup(path) - - if err != nil { - if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { - resp.WriteHeaderAndEntity(http.StatusNotFound, errors.Wrap(err)) - } else { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - } - return - } - - resp.WriteAsJson(group) - -} - -func ListGroupUsers(req *restful.Request, resp *restful.Response) { - - path := req.PathParameter("group") - - group, err := iam.DescribeGroup(path) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - users := make([]*iam.User, 0) - - modify := false - - for i := 0; i < len(group.Members); i++ { - name := group.Members[i] - user, err := iam.GetUserInfo(name) - - if err != nil { - if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { - group.Members = append(group.Members[:i], group.Members[i+1:]...) - i-- - modify = true - continue - } else { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - } - - users = append(users, user) - } - - if modify { - go iam.UpdateGroup(group) - } - - resp.WriteAsJson(users) - -} - -func ListGroups(req *restful.Request, resp *restful.Response) { - - array := req.QueryParameter("path") - - if array == "" { - groups, err := iam.ChildList("") - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - resp.WriteAsJson(groups) - } else { - paths := strings.Split(array, ",") - - groups := make([]*models.Group, 0) - - for _, v := range paths { - path := strings.TrimSpace(v) - group, err := iam.DescribeGroup(path) - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - groups = append(groups, group) - } - - resp.WriteAsJson(groups) - } - -} diff --git a/pkg/apiserver/iam/im.go b/pkg/apiserver/iam/im.go deleted file mode 100644 index 9a708bc0c..000000000 --- a/pkg/apiserver/iam/im.go +++ /dev/null @@ -1,259 +0,0 @@ -/* - - Copyright 2019 The KubeSphere 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 iam - -import ( - "fmt" - "k8s.io/klog" - "kubesphere.io/kubesphere/pkg/server/params" - "net/http" - "net/mail" - "strings" - - "github.com/emicklei/go-restful" - "github.com/go-ldap/ldap" - rbacv1 "k8s.io/api/rbac/v1" - "kubesphere.io/kubesphere/pkg/constants" - "kubesphere.io/kubesphere/pkg/models/iam" - "kubesphere.io/kubesphere/pkg/server/errors" -) - -func DeleteUser(req *restful.Request, resp *restful.Response) { - username := req.PathParameter("user") - - operator := req.HeaderParameter(constants.UserNameHeader) - - if operator == username { - err := fmt.Errorf("cannot delete yourself") - klog.Info(err) - resp.WriteHeaderAndEntity(http.StatusForbidden, errors.Wrap(err)) - return - } - - err := iam.DeleteUser(username) - - if err != nil { - klog.Info(err) - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - resp.WriteAsJson(errors.None) -} - -func UpdateUser(req *restful.Request, resp *restful.Response) { - - usernameInPath := req.PathParameter("user") - usernameInHeader := req.HeaderParameter(constants.UserNameHeader) - var user iam.User - - err := req.ReadEntity(&user) - - if err != nil { - klog.Info(err) - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - - if usernameInPath != user.Username { - err = fmt.Errorf("the name of user (%s) does not match the name on the URL (%s)", user.Username, usernameInPath) - klog.Info(err) - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - - if _, err = mail.ParseAddress(user.Email); err != nil { - err = fmt.Errorf("invalid email: %s", user.Email) - klog.Info(err) - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - - if user.Password != "" && len(user.Password) < 6 { - err = fmt.Errorf("invalid password") - klog.Info(err) - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - - // change password by self - if usernameInHeader == user.Username && user.Password != "" { - isUserManager, err := isUserManager(usernameInHeader) - if err != nil { - klog.Error(err) - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - if !isUserManager { - _, err = iam.Login(usernameInHeader, user.CurrentPassword, "") - } - if err != nil { - err = fmt.Errorf("incorrect current password") - klog.Info(err) - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - } - - if usernameInHeader == user.Username { - // change cluster role by self is not permitted - user.ClusterRole = "" - } - - result, err := iam.UpdateUser(&user) - - if err != nil { - if ldap.IsErrorWithCode(err, ldap.LDAPResultEntryAlreadyExists) { - klog.Info(err) - resp.WriteHeaderAndEntity(http.StatusConflict, errors.Wrap(err)) - return - } - - klog.Error(err) - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - resp.WriteAsJson(result) -} - -func isUserManager(username string) (bool, error) { - rules, err := iam.GetUserClusterRules(username) - if err != nil { - return false, err - } - if iam.RulesMatchesRequired(rules, rbacv1.PolicyRule{Verbs: []string{"update"}, Resources: []string{"users"}, APIGroups: []string{"iam.kubesphere.io"}}) { - return true, nil - } - return false, nil -} - -func UserLoginLogs(req *restful.Request, resp *restful.Response) { - username := req.PathParameter("user") - logs, err := iam.LoginLog(username) - - if err != nil { - klog.Error(err) - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - result := make([]map[string]string, 0) - - for _, v := range logs { - item := strings.Split(v, ",") - time := item[0] - var ip string - if len(item) > 1 { - ip = item[1] - } - result = append(result, map[string]string{"login_time": time, "login_ip": ip}) - } - - resp.WriteAsJson(result) -} - -func DescribeUser(req *restful.Request, resp *restful.Response) { - - username := req.PathParameter("user") - - user, err := iam.DescribeUser(username) - - if err != nil { - if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { - klog.Info(err) - resp.WriteHeaderAndEntity(http.StatusNotFound, errors.Wrap(err)) - } else { - klog.Error(err) - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - } - return - } - - clusterRole, err := iam.GetUserClusterRole(username) - - if err != nil { - klog.Error(err) - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - user.ClusterRole = clusterRole.Name - - clusterRules, err := iam.GetUserClusterSimpleRules(username) - - if err != nil { - klog.Error(err) - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - result := struct { - *iam.User - ClusterRules []iam.SimpleRule `json:"cluster_rules"` - }{ - User: user, - ClusterRules: clusterRules, - } - - resp.WriteAsJson(result) -} - -func Precheck(req *restful.Request, resp *restful.Response) { - - check := req.QueryParameter("check") - - exist, err := iam.UserCreateCheck(check) - - if err != nil { - klog.Error(err) - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - resp.WriteAsJson(map[string]bool{"exist": exist}) -} - -func ListUsers(req *restful.Request, resp *restful.Response) { - - if check := req.QueryParameter("check"); check != "" { - Precheck(req, resp) - return - } - - limit, offset := params.ParsePaging(req.QueryParameter(params.PagingParam)) - conditions, err := params.ParseConditions(req.QueryParameter(params.ConditionsParam)) - orderBy := req.QueryParameter(params.OrderByParam) - reverse := params.ParseReverse(req) - - if err != nil { - klog.Info(err) - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - - users, err := iam.ListUsers(conditions, orderBy, reverse, limit, offset) - - if err != nil { - klog.Error(err) - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - resp.WriteAsJson(users) -} diff --git a/pkg/apiserver/iam/types.go b/pkg/apiserver/iam/types.go deleted file mode 100644 index 6c25b043f..000000000 --- a/pkg/apiserver/iam/types.go +++ /dev/null @@ -1,18 +0,0 @@ -/* - - Copyright 2019 The KubeSphere 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 iam diff --git a/pkg/apiserver/iam/workspaces.go b/pkg/apiserver/iam/workspaces.go deleted file mode 100644 index 41722275a..000000000 --- a/pkg/apiserver/iam/workspaces.go +++ /dev/null @@ -1,165 +0,0 @@ -/* - - Copyright 2019 The KubeSphere 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 iam - -import ( - "github.com/emicklei/go-restful" - k8serr "k8s.io/apimachinery/pkg/api/errors" - "kubesphere.io/kubesphere/pkg/constants" - "kubesphere.io/kubesphere/pkg/models/iam" - "kubesphere.io/kubesphere/pkg/models/workspaces" - "kubesphere.io/kubesphere/pkg/server/errors" - "kubesphere.io/kubesphere/pkg/server/params" - "net/http" -) - -func ListWorkspaceRoles(req *restful.Request, resp *restful.Response) { - - workspace := req.PathParameter("workspace") - conditions, err := params.ParseConditions(req.QueryParameter(params.ConditionsParam)) - orderBy := req.QueryParameter(params.OrderByParam) - limit, offset := params.ParsePaging(req.QueryParameter(params.PagingParam)) - reverse := params.ParseReverse(req) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - - result, err := iam.ListWorkspaceRoles(workspace, conditions, orderBy, reverse, limit, offset) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - resp.WriteAsJson(result.Items) -} - -func ListWorkspaceRoleRules(req *restful.Request, resp *restful.Response) { - workspace := req.PathParameter("workspace") - role := req.PathParameter("role") - - rules := iam.GetWorkspaceRoleSimpleRules(workspace, role) - - resp.WriteAsJson(rules) -} - -func DescribeWorkspaceRole(req *restful.Request, resp *restful.Response) { - workspace := req.PathParameter("workspace") - roleName := req.PathParameter("role") - - role, err := iam.GetWorkspaceRole(workspace, roleName) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - resp.WriteAsJson(role) -} - -func DescribeWorkspaceUser(req *restful.Request, resp *restful.Response) { - workspace := req.PathParameter("workspace") - username := req.PathParameter("member") - - workspaceRole, err := iam.GetUserWorkspaceRole(workspace, username) - - if err != nil { - if k8serr.IsNotFound(err) { - resp.WriteHeaderAndEntity(http.StatusNotFound, errors.Wrap(err)) - } else { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - } - - return - } - - user, err := iam.GetUserInfo(username) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - user.WorkspaceRole = workspaceRole.Annotations[constants.DisplayNameAnnotationKey] - - resp.WriteAsJson(user) -} - -func ListDevopsRoleRules(req *restful.Request, resp *restful.Response) { - role := req.PathParameter("role") - - rules := iam.GetDevopsRoleSimpleRules(role) - - resp.WriteAsJson(rules) -} - -func InviteUser(req *restful.Request, resp *restful.Response) { - workspace := req.PathParameter("workspace") - var user iam.User - err := req.ReadEntity(&user) - if err != nil { - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - - err = workspaces.InviteUser(workspace, &user) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - resp.WriteAsJson(errors.None) -} - -func RemoveUser(req *restful.Request, resp *restful.Response) { - workspace := req.PathParameter("workspace") - username := req.PathParameter("member") - - err := workspaces.RemoveUser(workspace, username) - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - resp.WriteAsJson(errors.None) -} - -func ListWorkspaceUsers(req *restful.Request, resp *restful.Response) { - workspace := req.PathParameter("workspace") - conditions, err := params.ParseConditions(req.QueryParameter(params.ConditionsParam)) - orderBy := req.QueryParameter(params.OrderByParam) - limit, offset := params.ParsePaging(req.QueryParameter(params.PagingParam)) - reverse := params.ParseReverse(req) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - - result, err := iam.ListWorkspaceUsers(workspace, conditions, orderBy, reverse, limit, offset) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - resp.WriteAsJson(result) -} diff --git a/pkg/kapis/iam/v1alpha2/handler.go b/pkg/kapis/iam/v1alpha2/handler.go index 67c83579b..5b91fa266 100644 --- a/pkg/kapis/iam/v1alpha2/handler.go +++ b/pkg/kapis/iam/v1alpha2/handler.go @@ -2,21 +2,26 @@ package v1alpha2 import ( "errors" + "fmt" "github.com/dgrijalva/jwt-go" "github.com/emicklei/go-restful" "github.com/go-ldap/ldap" rbacv1 "k8s.io/api/rbac/v1" - k8serr "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/klog" "kubesphere.io/kubesphere/pkg/api" iamv1alpha2 "kubesphere.io/kubesphere/pkg/api/iam/v1alpha2" + "kubesphere.io/kubesphere/pkg/constants" + "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/models/iam" "kubesphere.io/kubesphere/pkg/models/iam/policy" - kserr "kubesphere.io/kubesphere/pkg/server/errors" + "kubesphere.io/kubesphere/pkg/models/resources/v1alpha2" + apierr "kubesphere.io/kubesphere/pkg/server/errors" "kubesphere.io/kubesphere/pkg/server/params" + "kubesphere.io/kubesphere/pkg/simple/client/k8s" + ldappool "kubesphere.io/kubesphere/pkg/simple/client/ldap" "kubesphere.io/kubesphere/pkg/utils/iputil" "kubesphere.io/kubesphere/pkg/utils/jwtutil" "net/http" - "sort" ) type iamHandler struct { @@ -24,29 +29,34 @@ type iamHandler struct { imOperator iam.IdentityManagementInterface } -func newIAMHandler() *iamHandler { - return &iamHandler{} +func newIAMHandler(k8sClient k8s.Client, ldapClient ldappool.Client, options iam.Config) *iamHandler { + factory := informers.NewInformerFactories(k8sClient.Kubernetes(), k8sClient.KubeSphere(), k8sClient.S2i(), k8sClient.Application()) + return &iamHandler{ + amOperator: iam.NewAMOperator(factory.KubernetesSharedInformerFactory()), + imOperator: iam.NewIMOperator(ldapClient, options), + } } -// k8s token review +// Implement webhook authentication interface +// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication func (h *iamHandler) TokenReviewHandler(req *restful.Request, resp *restful.Response) { var tokenReview iamv1alpha2.TokenReview err := req.ReadEntity(&tokenReview) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } - if tokenReview.Spec == nil { - api.HandleBadRequest(resp, errors.New("token must not be null")) + if err = tokenReview.Validate(); err != nil { + klog.V(4).Infoln(err) + api.HandleBadRequest(resp, err) return } - uToken := tokenReview.Spec.Token - - token, err := jwtutil.ValidateToken(uToken) + token, err := jwtutil.ValidateToken(tokenReview.Spec.Token) if err != nil { failed := iamv1alpha2.TokenReview{APIVersion: tokenReview.APIVersion, @@ -59,18 +69,24 @@ func (h *iamHandler) TokenReviewHandler(req *restful.Request, resp *restful.Resp return } - claims := token.Claims.(jwt.MapClaims) + claims, ok := token.Claims.(jwt.MapClaims) + + if !ok { + api.HandleBadRequest(resp, errors.New("invalid token")) + return + } username, ok := claims["username"].(string) if !ok { - api.HandleBadRequest(resp, errors.New("username not found")) + api.HandleBadRequest(resp, errors.New("invalid token")) return } user, err := h.imOperator.DescribeUser(username) if err != nil { + klog.Errorln(err) api.HandleInternalError(resp, err) return } @@ -92,7 +108,9 @@ func (h *iamHandler) Login(req *restful.Request, resp *restful.Response) { err := req.ReadEntity(&loginRequest) if err != nil || loginRequest.Username == "" || loginRequest.Password == "" { - resp.WriteHeaderAndEntity(http.StatusUnauthorized, errors.New("incorrect username or password")) + err = errors.New("incorrect username or password") + klog.V(4).Infoln(err) + resp.WriteHeaderAndEntity(http.StatusUnauthorized, err) return } @@ -101,10 +119,12 @@ func (h *iamHandler) Login(req *restful.Request, resp *restful.Response) { token, err := h.imOperator.Login(loginRequest.Username, loginRequest.Password, ip) if err != nil { - if serviceError, ok := err.(restful.ServiceError); ok { - resp.WriteHeaderAndEntity(serviceError.Code, errors.New(serviceError.Message)) + if err == iam.AuthRateLimitExceeded { + klog.V(4).Infoln(err) + resp.WriteHeaderAndEntity(http.StatusTooManyRequests, err) return } + klog.V(4).Infoln(err) resp.WriteHeaderAndEntity(http.StatusUnauthorized, err) return } @@ -113,14 +133,16 @@ func (h *iamHandler) Login(req *restful.Request, resp *restful.Response) { } func (h *iamHandler) CreateUser(req *restful.Request, resp *restful.Response) { - var createRequest iamv1alpha2.UserCreateRequest + var createRequest iamv1alpha2.CreateUserRequest err := req.ReadEntity(&createRequest) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } if err := createRequest.Validate(); err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -128,23 +150,223 @@ func (h *iamHandler) CreateUser(req *restful.Request, resp *restful.Response) { created, err := h.imOperator.CreateUser(createRequest.User) if err != nil { - if ldap.IsErrorWithCode(err, ldap.LDAPResultEntryAlreadyExists) { - resp.WriteHeaderAndEntity(http.StatusConflict, kserr.Wrap(err)) + if err == iam.UserAlreadyExists { + klog.V(4).Infoln(err) + resp.WriteHeaderAndEntity(http.StatusConflict, err) return } + klog.Errorln(err) api.HandleInternalError(resp, err) return } - err := h.amOperator.CreateClusterRoleBinding(created.Username, createRequest.ClusterRole) + err = h.amOperator.CreateClusterRoleBinding(created.Username, createRequest.ClusterRole) if err != nil { + klog.Errorln(err) api.HandleInternalError(resp, err) return } + resp.WriteEntity(created) } +func (h *iamHandler) DeleteUser(req *restful.Request, resp *restful.Response) { + username := req.PathParameter("user") + operator := req.HeaderParameter(constants.UserNameHeader) + + if operator == username { + err := errors.New("cannot delete yourself") + klog.V(4).Infoln(err) + api.HandleForbidden(resp, err) + return + } + + err := h.amOperator.UnBindAllRoles(username) + + if err != nil { + klog.Errorln(err) + api.HandleInternalError(resp, err) + return + } + + err = h.imOperator.DeleteUser(username) + + // TODO release user resources + if err != nil { + klog.Errorln(err) + api.HandleInternalError(resp, err) + return + } + + resp.WriteEntity(apierr.None) +} + +func (h *iamHandler) ModifyUser(request *restful.Request, response *restful.Response) { + + username := request.PathParameter("user") + operator := request.HeaderParameter(constants.UserNameHeader) + var modifyUserRequest iamv1alpha2.ModifyUserRequest + + err := request.ReadEntity(&modifyUserRequest) + + if err != nil { + klog.V(4).Infoln(err) + api.HandleBadRequest(response, err) + return + } + + if username != modifyUserRequest.Username { + err = fmt.Errorf("the name of user (%s) does not match the name on the URL (%s)", modifyUserRequest.Username, username) + klog.V(4).Infoln(err) + api.HandleBadRequest(response, err) + return + } + + if err = modifyUserRequest.Validate(); err != nil { + klog.V(4).Infoln(err) + api.HandleBadRequest(response, err) + return + } + + // change password by self + if operator == modifyUserRequest.Username && modifyUserRequest.Password != "" { + + } + + result, err := h.imOperator.ModifyUser(modifyUserRequest.User) + + if err != nil { + klog.Errorln(err) + api.HandleInternalError(response, err) + return + } + + // TODO modify cluster role + + response.WriteEntity(result) +} + +func (h *iamHandler) DescribeUser(req *restful.Request, resp *restful.Response) { + username := req.PathParameter("user") + + user, err := h.imOperator.DescribeUser(username) + + if err != nil { + if err == iam.UserNotExists { + klog.V(4).Infoln(err) + api.HandleNotFound(resp, err) + return + } + klog.Errorln(err) + api.HandleInternalError(resp, err) + return + } + + // TODO append more user info + clusterRole, err := h.amOperator.GetClusterRole(username) + + if err != nil { + klog.Errorln(err) + api.HandleInternalError(resp, err) + return + } + + result := iamv1alpha2.UserDetail{ + User: user, + ClusterRole: clusterRole.Name, + } + + resp.WriteEntity(result) +} + +func (h *iamHandler) ListUsers(req *restful.Request, resp *restful.Response) { + + limit, offset := params.ParsePaging(req) + orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime) + reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, true) + conditions, err := params.ParseConditions(req) + + if err != nil { + klog.V(4).Infoln(err) + api.HandleBadRequest(resp, err) + return + } + + result, err := h.imOperator.ListUsers(conditions, orderBy, reverse, limit, offset) + + if err != nil { + klog.Errorln(err) + api.HandleInternalError(resp, err) + return + } + + resp.WriteEntity(result) +} + +func (h *iamHandler) ListUserRoles(req *restful.Request, resp *restful.Response) { + + username := req.PathParameter("user") + + roles, err := h.imOperator.GetUserRoles(username) + + if err != nil { + klog.Errorln(err) + api.HandleInternalError(resp, err) + return + } + + resp.WriteEntity(roles) +} + +func (h *iamHandler) ListRoles(req *restful.Request, resp *restful.Response) { + namespace := req.PathParameter("namespace") + limit, offset := params.ParsePaging(req) + orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime) + reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, true) + conditions, err := params.ParseConditions(req) + + if err != nil { + klog.V(4).Infoln(err) + api.HandleBadRequest(resp, err) + return + } + + result, err := h.amOperator.ListRoles(namespace, conditions, orderBy, reverse, limit, offset) + + if err != nil { + klog.Errorln(err) + api.HandleInternalError(resp, err) + return + } + + resp.WriteAsJson(result) + +} +func (h *iamHandler) ListClusterRoles(req *restful.Request, resp *restful.Response) { + limit, offset := params.ParsePaging(req) + orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime) + reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, true) + conditions, err := params.ParseConditions(req) + + if err != nil { + klog.V(4).Infoln(err) + api.HandleBadRequest(resp, err) + return + } + + result, err := h.amOperator.ListClusterRoles(conditions, orderBy, reverse, limit, offset) + + if err != nil { + klog.Errorln(err) + api.HandleInternalError(resp, err) + return + } + + resp.WriteEntity(result) + +} + func (h *iamHandler) ListRoleUsers(req *restful.Request, resp *restful.Response) { role := req.PathParameter("role") namespace := req.PathParameter("namespace") @@ -152,6 +374,7 @@ func (h *iamHandler) ListRoleUsers(req *restful.Request, resp *restful.Response) roleBindings, err := h.amOperator.ListRoleBindings(namespace, role) if err != nil { + klog.Errorln(err) api.HandleInternalError(resp, err) return } @@ -159,12 +382,13 @@ func (h *iamHandler) ListRoleUsers(req *restful.Request, resp *restful.Response) for _, roleBinding := range roleBindings { for _, subject := range roleBinding.Subjects { if subject.Kind == rbacv1.UserKind { - user, err := h.imOperator.GetUserInfo(subject.Name) + user, err := h.imOperator.DescribeUser(subject.Name) // skip if user not exist if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { continue } if err != nil { + klog.Errorln(err) api.HandleInternalError(resp, err) return } @@ -176,152 +400,133 @@ func (h *iamHandler) ListRoleUsers(req *restful.Request, resp *restful.Response) resp.WriteEntity(result) } -func (h *iamHandler) ListClusterRoles(req *restful.Request, resp *restful.Response) { - conditions, err := params.ParseConditions(req.QueryParameter(params.ConditionsParam)) - orderBy := req.QueryParameter(params.OrderByParam) - limit, offset := params.ParsePaging(req.QueryParameter(params.PagingParam)) - reverse := params.ParseReverse(req) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - - result, err := iam.ListClusterRoles(conditions, orderBy, reverse, limit, offset) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - resp.WriteAsJson(result) - -} - -func (h *iamHandler) ListRoles(req *restful.Request, resp *restful.Response) { - namespace := req.PathParameter("namespace") - conditions, err := params.ParseConditions(req.QueryParameter(params.ConditionsParam)) - orderBy := req.QueryParameter(params.OrderByParam) - limit, offset := params.ParsePaging(req.QueryParameter(params.PagingParam)) - reverse := params.ParseReverse(req) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - - result, err := iam.ListRoles(namespace, conditions, orderBy, reverse, limit, offset) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - resp.WriteAsJson(result) - -} - // List users by namespace func (h *iamHandler) ListNamespaceUsers(req *restful.Request, resp *restful.Response) { namespace := req.PathParameter("namespace") - users, err := iam.NamespaceUsers(namespace) + roleBindings, err := h.amOperator.ListRoleBindings(namespace, "") if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) + klog.Errorln(err) + api.HandleInternalError(resp, err) return } - // sort by time by default - sort.Slice(users, func(i, j int) bool { - return users[i].RoleBindTime.After(*users[j].RoleBindTime) - }) + result := make([]*iam.User, 0) + for _, roleBinding := range roleBindings { + for _, subject := range roleBinding.Subjects { + if subject.Kind == rbacv1.UserKind { + user, err := h.imOperator.DescribeUser(subject.Name) + // skip if user not exist + if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { + continue + } + if err != nil { + klog.Errorln(err) + api.HandleInternalError(resp, err) + return + } + result = append(result, user) + } + } + } - resp.WriteAsJson(users) + resp.WriteEntity(result) } -func (h *iamHandler) ListUserRoles(req *restful.Request, resp *restful.Response) { - - username := req.PathParameter("user") - - roles, err := iam.GetUserRoles("", username) +func (h *iamHandler) ListClusterRoleUsers(req *restful.Request, resp *restful.Response) { + clusterRole := req.PathParameter("clusterrole") + clusterRoleBindings, err := h.amOperator.ListClusterRoleBindings(clusterRole) if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) + klog.Errorln(err) + api.HandleInternalError(resp, err) return } - _, clusterRoles, err := iam.GetUserClusterRoles(username) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return + result := make([]*iam.User, 0) + for _, roleBinding := range clusterRoleBindings { + for _, subject := range roleBinding.Subjects { + if subject.Kind == rbacv1.UserKind { + user, err := h.imOperator.DescribeUser(subject.Name) + // skip if user not exist + if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { + continue + } + if err != nil { + klog.Errorln(err) + api.HandleInternalError(resp, err) + return + } + result = append(result, user) + } + } } - roleList := RoleList{} - roleList.Roles = roles - roleList.ClusterRoles = clusterRoles - - resp.WriteAsJson(roleList) + resp.WriteEntity(result) } func (h *iamHandler) RulesMapping(req *restful.Request, resp *restful.Response) { rules := policy.RoleRuleMapping - resp.WriteAsJson(rules) + resp.WriteEntity(rules) } func (h *iamHandler) ClusterRulesMapping(req *restful.Request, resp *restful.Response) { rules := policy.ClusterRoleRuleMapping - resp.WriteAsJson(rules) + resp.WriteEntity(rules) } func (h *iamHandler) ListClusterRoleRules(req *restful.Request, resp *restful.Response) { - clusterRoleName := req.PathParameter("clusterrole") - rules, err := iam.GetClusterRoleSimpleRules(clusterRoleName) + clusterRole := req.PathParameter("clusterrole") + rules, err := h.amOperator.GetClusterRoleSimpleRules(clusterRole) if err != nil { - resp.WriteError(http.StatusInternalServerError, err) + klog.Errorln(err) + api.HandleInternalError(resp, err) return } - resp.WriteAsJson(rules) -} - -func (h *iamHandler) ListClusterRoleUsers(req *restful.Request, resp *restful.Response) { - clusterRoleName := req.PathParameter("clusterrole") - conditions, err := params.ParseConditions(req.QueryParameter(params.ConditionsParam)) - orderBy := req.QueryParameter(params.OrderByParam) - limit, offset := params.ParsePaging(req.QueryParameter(params.PagingParam)) - reverse := params.ParseReverse(req) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - - result, err := iam.ListClusterRoleUsers(clusterRoleName, conditions, orderBy, reverse, limit, offset) - - if err != nil { - if k8serr.IsNotFound(err) { - resp.WriteError(http.StatusNotFound, err) - } else { - resp.WriteError(http.StatusInternalServerError, err) - } - return - } - - resp.WriteAsJson(result) + resp.WriteEntity(rules) } func (h *iamHandler) ListRoleRules(req *restful.Request, resp *restful.Response) { - namespaceName := req.PathParameter("namespace") - roleName := req.PathParameter("role") + namespace := req.PathParameter("namespace") + role := req.PathParameter("role") - rules, err := iam.GetRoleSimpleRules(namespaceName, roleName) + rules, err := h.amOperator.GetRoleSimpleRules(namespace, role) if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) + klog.Errorln(err) + api.HandleInternalError(resp, err) return } - resp.WriteAsJson(rules) + resp.WriteEntity(rules) +} + +func (h *iamHandler) ListWorkspaceRoles(request *restful.Request, response *restful.Response) { + panic("implement me") +} + +func (h *iamHandler) DescribeWorkspaceRole(request *restful.Request, response *restful.Response) { + panic("implement me") +} + +func (h *iamHandler) ListWorkspaceRoleRules(request *restful.Request, response *restful.Response) { + panic("implement me") +} + +func (h *iamHandler) ListWorkspaceUsers(request *restful.Request, response *restful.Response) { + panic("implement me") +} + +func (h *iamHandler) InviteUser(request *restful.Request, response *restful.Response) { + panic("implement me") +} + +func (h *iamHandler) RemoveUser(request *restful.Request, response *restful.Response) { + panic("implement me") +} + +func (h *iamHandler) DescribeWorkspaceUser(request *restful.Request, response *restful.Response) { + panic("implement me") } diff --git a/pkg/kapis/iam/v1alpha2/register.go b/pkg/kapis/iam/v1alpha2/register.go index 4c4c477df..ffefcd3dd 100644 --- a/pkg/kapis/iam/v1alpha2/register.go +++ b/pkg/kapis/iam/v1alpha2/register.go @@ -24,104 +24,25 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "kubesphere.io/kubesphere/pkg/api" iamv1alpha2 "kubesphere.io/kubesphere/pkg/api/iam/v1alpha2" - "kubesphere.io/kubesphere/pkg/apiserver/iam" "kubesphere.io/kubesphere/pkg/apiserver/runtime" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/models" - iam2 "kubesphere.io/kubesphere/pkg/models/iam" + "kubesphere.io/kubesphere/pkg/models/iam" "kubesphere.io/kubesphere/pkg/models/iam/policy" "kubesphere.io/kubesphere/pkg/server/errors" + "kubesphere.io/kubesphere/pkg/simple/client/k8s" + ldappool "kubesphere.io/kubesphere/pkg/simple/client/ldap" "net/http" - "time" ) const GroupName = "iam.kubesphere.io" var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"} -var ( - WebServiceBuilder = runtime.NewContainerBuilder(addWebService) - AddToContainer = WebServiceBuilder.AddToContainer -) - -type UserUpdateRequest struct { - Username string `json:"username" description:"username"` - Email string `json:"email" description:"email address"` - Lang string `json:"lang" description:"user's language setting, default is zh-CN"` - Description string `json:"description" description:"user's description"` - Password string `json:"password,omitempty" description:"this is necessary if you need to change your password"` - CurrentPassword string `json:"current_password,omitempty" description:"this is necessary if you need to change your password"` - ClusterRole string `json:"cluster_role" description:"user's cluster role"` -} - -type CreateUserRequest struct { - Username string `json:"username" description:"username"` - Email string `json:"email" description:"email address"` - Lang string `json:"lang,omitempty" description:"user's language setting, default is zh-CN"` - Description string `json:"description" description:"user's description"` - Password string `json:"password" description:"password'"` - ClusterRole string `json:"cluster_role" description:"user's cluster role"` -} - -type UserList struct { - Items []struct { - Username string `json:"username" description:"username"` - Email string `json:"email" description:"email address"` - Lang string `json:"lang,omitempty" description:"user's language setting, default is zh-CN"` - Description string `json:"description" description:"user's description"` - ClusterRole string `json:"cluster_role" description:"user's cluster role"` - CreateTime time.Time `json:"create_time" description:"user creation time"` - LastLoginTime time.Time `json:"last_login_time" description:"last login time"` - } `json:"items" description:"paging data"` - TotalCount int `json:"total_count" description:"total count"` -} -type NamespacedUser struct { - Username string `json:"username" description:"username"` - Email string `json:"email" description:"email address"` - Lang string `json:"lang,omitempty" description:"user's language setting, default is zh-CN"` - Description string `json:"description" description:"user's description"` - Role string `json:"role" description:"user's role in the specified namespace"` - RoleBinding string `json:"role_binding" description:"user's role binding name in the specified namespace"` - RoleBindTime string `json:"role_bind_time" description:"user's role binding time"` - CreateTime time.Time `json:"create_time" description:"user creation time"` - LastLoginTime time.Time `json:"last_login_time" description:"last login time"` -} - -type ClusterRoleList struct { - Items []rbacv1.ClusterRole `json:"items" description:"paging data"` - TotalCount int `json:"total_count" description:"total count"` -} - -type LoginLog struct { - LoginTime string `json:"login_time" description:"last login time"` - LoginIP string `json:"login_ip" description:"last login ip"` -} - -type RoleList struct { - Items []rbacv1.Role `json:"items" description:"paging data"` - TotalCount int `json:"total_count" description:"total count"` -} - -type InviteUserRequest struct { - Username string `json:"username" description:"username"` - WorkspaceRole string `json:"workspace_role" description:"user's workspace role'"` -} - -type DescribeWorkspaceUserResponse struct { - Username string `json:"username" description:"username"` - Email string `json:"email" description:"email address"` - Lang string `json:"lang" description:"user's language setting, default is zh-CN"` - Description string `json:"description" description:"user's description"` - ClusterRole string `json:"cluster_role" description:"user's cluster role"` - WorkspaceRole string `json:"workspace_role" description:"user's workspace role"` - CreateTime time.Time `json:"create_time" description:"user creation time"` - LastLoginTime time.Time `json:"last_login_time" description:"last login time"` -} - -func addWebService(c *restful.Container) error { +func AddToContainer(c *restful.Container, k8sClient k8s.Client, ldapClient ldappool.Client, options iam.Config) error { ws := runtime.NewWebService(GroupVersion) - handler := newIAMHandler() + handler := newIAMHandler(k8sClient, ldapClient, options) ws.Route(ws.POST("/authenticate"). To(handler.TokenReviewHandler). @@ -138,152 +59,132 @@ func addWebService(c *restful.Container) error { ws.Route(ws.POST("/users"). To(handler.CreateUser). Doc("Create a user account."). - Reads(CreateUserRequest{}). - Returns(http.StatusOK, api.StatusOK, errors.Error{}). + Reads(iamv1alpha2.CreateUserRequest{}). + Returns(http.StatusOK, api.StatusOK, iamv1alpha2.UserDetail{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag})) - ws.Route(ws.GET("/users/{user}"). - To(iam.DescribeUser). - Doc("Describe the specified user."). - Param(ws.PathParameter("user", "username")). - Returns(http.StatusOK, api.StatusOK, iam2.User{}). - Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag})) - ws.Route(ws.DELETE("/users/{user}"). - To(iam.DeleteUser). + To(handler.DeleteUser). Doc("Delete the specified user."). Param(ws.PathParameter("user", "username")). Returns(http.StatusOK, api.StatusOK, errors.Error{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag})) ws.Route(ws.PUT("/users/{user}"). - To(iam.UpdateUser). + To(handler.ModifyUser). Doc("Update information about the specified user."). Param(ws.PathParameter("user", "username")). - Reads(UserUpdateRequest{}). + Reads(iamv1alpha2.ModifyUserRequest{}). Returns(http.StatusOK, api.StatusOK, errors.Error{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag})) - ws.Route(ws.GET("/users/{user}/logs"). - To(iam.UserLoginLogs). - Doc("Retrieve the \"login logs\" for the specified user."). + ws.Route(ws.GET("/users/{user}"). + To(handler.DescribeUser). + Doc("Describe the specified user."). Param(ws.PathParameter("user", "username")). - Returns(http.StatusOK, api.StatusOK, LoginLog{}). + Returns(http.StatusOK, api.StatusOK, iamv1alpha2.UserDetail{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag})) ws.Route(ws.GET("/users"). - To(iam.ListUsers). + To(handler.ListUsers). Doc("List all users."). - Returns(http.StatusOK, api.StatusOK, UserList{}). + Returns(http.StatusOK, api.StatusOK, iamv1alpha2.ListUserResponse{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag})) ws.Route(ws.GET("/users/{user}/roles"). - To(iam.ListUserRoles). + To(handler.ListUserRoles). Doc("Retrieve all the roles that are assigned to the specified user."). Param(ws.PathParameter("user", "username")). - Returns(http.StatusOK, api.StatusOK, iam.RoleList{}). + Returns(http.StatusOK, api.StatusOK, []*rbacv1.Role{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/namespaces/{namespace}/roles"). - To(iam.ListRoles). + To(handler.ListRoles). Doc("Retrieve the roles that are assigned to the user in the specified namespace."). Param(ws.PathParameter("namespace", "kubernetes namespace")). - Returns(http.StatusOK, api.StatusOK, RoleList{}). + Returns(http.StatusOK, api.StatusOK, models.PageableResponse{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/clusterroles"). - To(iam.ListClusterRoles). + To(handler.ListClusterRoles). Doc("List all cluster roles."). - Returns(http.StatusOK, api.StatusOK, ClusterRoleList{}). + Returns(http.StatusOK, api.StatusOK, models.PageableResponse{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/namespaces/{namespace}/roles/{role}/users"). To(handler.ListRoleUsers). Doc("Retrieve the users that are bound to the role in the specified namespace."). Param(ws.PathParameter("namespace", "kubernetes namespace")). Param(ws.PathParameter("role", "role name")). - Returns(http.StatusOK, api.StatusOK, []NamespacedUser{}). + Returns(http.StatusOK, api.StatusOK, []iamv1alpha2.ListUserResponse{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/namespaces/{namespace}/users"). - To(iam.ListNamespaceUsers). + To(handler.ListNamespaceUsers). Doc("List all users in the specified namespace."). Param(ws.PathParameter("namespace", "kubernetes namespace")). - Returns(http.StatusOK, api.StatusOK, []NamespacedUser{}). + Returns(http.StatusOK, api.StatusOK, []iamv1alpha2.ListUserResponse{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/clusterroles/{clusterrole}/users"). - To(iam.ListClusterRoleUsers). + To(handler.ListClusterRoleUsers). Doc("List all users that are bound to the specified cluster role."). Param(ws.PathParameter("clusterrole", "cluster role name")). - Returns(http.StatusOK, api.StatusOK, UserList{}). + Returns(http.StatusOK, api.StatusOK, []iamv1alpha2.ListUserResponse{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/clusterroles/{clusterrole}/rules"). - To(iam.ListClusterRoleRules). + To(handler.ListClusterRoleRules). Doc("List all policy rules of the specified cluster role."). Param(ws.PathParameter("clusterrole", "cluster role name")). - Returns(http.StatusOK, api.StatusOK, []iam2.SimpleRule{}). + Returns(http.StatusOK, api.StatusOK, []iam.SimpleRule{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/namespaces/{namespace}/roles/{role}/rules"). - To(iam.ListRoleRules). + To(handler.ListRoleRules). Doc("List all policy rules of the specified role in the given namespace."). Param(ws.PathParameter("namespace", "kubernetes namespace")). Param(ws.PathParameter("role", "role name")). - Returns(http.StatusOK, api.StatusOK, []iam2.SimpleRule{}). - Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) - ws.Route(ws.GET("/devops/{devops}/roles/{role}/rules"). - To(iam.ListDevopsRoleRules). - Doc("List all policy rules of the specified role in the given devops project."). - Param(ws.PathParameter("devops", "devops project ID")). - Param(ws.PathParameter("role", "devops role name")). - Returns(http.StatusOK, api.StatusOK, []iam2.SimpleRule{}). + Returns(http.StatusOK, api.StatusOK, []iam.SimpleRule{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/rulesmapping/clusterroles"). - To(iam.ClusterRulesMapping). + To(handler.ClusterRulesMapping). Doc("Get the mapping relationships between cluster roles and policy rules."). Returns(http.StatusOK, api.StatusOK, policy.ClusterRoleRuleMapping). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/rulesmapping/roles"). - To(iam.RulesMapping). + To(handler.RulesMapping). Doc("Get the mapping relationships between namespaced roles and policy rules."). Returns(http.StatusOK, api.StatusOK, policy.RoleRuleMapping). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) + ws.Route(ws.GET("/workspaces/{workspace}/roles"). - To(iam.ListWorkspaceRoles). + To(handler.ListWorkspaceRoles). Doc("List all workspace roles."). Param(ws.PathParameter("workspace", "workspace name")). - Returns(http.StatusOK, api.StatusOK, ClusterRoleList{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/workspaces/{workspace}/roles/{role}"). - To(iam.DescribeWorkspaceRole). + To(handler.DescribeWorkspaceRole). Doc("Describe the workspace role."). Param(ws.PathParameter("workspace", "workspace name")). Param(ws.PathParameter("role", "workspace role name")). - Returns(http.StatusOK, api.StatusOK, rbacv1.ClusterRole{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/workspaces/{workspace}/roles/{role}/rules"). - To(iam.ListWorkspaceRoleRules). + To(handler.ListWorkspaceRoleRules). Doc("List all policy rules of the specified workspace role."). Param(ws.PathParameter("workspace", "workspace name")). Param(ws.PathParameter("role", "workspace role name")). - Returns(http.StatusOK, api.StatusOK, []iam2.SimpleRule{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/workspaces/{workspace}/members"). - To(iam.ListWorkspaceUsers). + To(handler.ListWorkspaceUsers). Doc("List all members in the specified workspace."). Param(ws.PathParameter("workspace", "workspace name")). - Returns(http.StatusOK, api.StatusOK, UserList{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.POST("/workspaces/{workspace}/members"). - To(iam.InviteUser). + To(handler.InviteUser). Doc("Invite a member to the specified workspace."). Param(ws.PathParameter("workspace", "workspace name")). - Reads(InviteUserRequest{}). - Returns(http.StatusOK, api.StatusOK, errors.Error{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.DELETE("/workspaces/{workspace}/members/{member}"). - To(iam.RemoveUser). + To(handler.RemoveUser). Doc("Remove the specified member from the workspace."). Param(ws.PathParameter("workspace", "workspace name")). Param(ws.PathParameter("member", "username")). Returns(http.StatusOK, api.StatusOK, errors.Error{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/workspaces/{workspace}/members/{member}"). - To(iam.DescribeWorkspaceUser). + To(handler.DescribeWorkspaceUser). Doc("Describe the specified user in the given workspace."). Param(ws.PathParameter("workspace", "workspace name")). Param(ws.PathParameter("member", "username")). - Returns(http.StatusOK, api.StatusOK, DescribeWorkspaceUserResponse{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) c.Add(ws) return nil diff --git a/pkg/kapis/kapis.go b/pkg/kapis/kapis.go index aefd76c7f..6c9228595 100644 --- a/pkg/kapis/kapis.go +++ b/pkg/kapis/kapis.go @@ -13,22 +13,25 @@ import ( servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/servicemesh/metrics/v1alpha2" tenantv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/tenant/v1alpha2" terminalv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/terminal/v1alpha2" + "kubesphere.io/kubesphere/pkg/models/iam" "kubesphere.io/kubesphere/pkg/simple/client/k8s" + ldappool "kubesphere.io/kubesphere/pkg/simple/client/ldap" + "kubesphere.io/kubesphere/pkg/simple/client/mysql" + op "kubesphere.io/kubesphere/pkg/simple/client/openpitrix" ) -func InstallAPIs(container *restful.Container, client k8s.Client) { +func InstallAPIs(container *restful.Container, client k8s.Client, op op.Client, db *mysql.Database) { urlruntime.Must(servicemeshv1alpha2.AddToContainer(container)) urlruntime.Must(devopsv1alpha2.AddToContainer(container)) urlruntime.Must(loggingv1alpha2.AddToContainer(container)) urlruntime.Must(monitoringv1alpha2.AddToContainer(container)) - urlruntime.Must(openpitrixv1.AddToContainer(container)) + urlruntime.Must(openpitrixv1.AddToContainer(container, client, op)) urlruntime.Must(operationsv1alpha2.AddToContainer(container, client)) urlruntime.Must(resourcesv1alpha2.AddToContainer(container, client)) - urlruntime.Must(tenantv1alpha2.AddToContainer(container)) - urlruntime.Must(terminalv1alpha2.AddToContainer(container)) - urlruntime.Must(tenantv1alpha2.AddToContainer(container)) + urlruntime.Must(tenantv1alpha2.AddToContainer(container, client, db)) + urlruntime.Must(terminalv1alpha2.AddToContainer(container, client)) } -func InstallAuthorizationAPIs(container *restful.Container) { - urlruntime.Must(iamv1alpha2.AddToContainer(container)) +func InstallAuthorizationAPIs(container *restful.Container, k8sClient k8s.Client, ldapClient ldappool.Client, imOptions iam.Config) { + urlruntime.Must(iamv1alpha2.AddToContainer(container, k8sClient, ldapClient, imOptions)) } diff --git a/pkg/kapis/openpitrix/v1/handler.go b/pkg/kapis/openpitrix/v1/handler.go index d3675c052..72be2365d 100644 --- a/pkg/kapis/openpitrix/v1/handler.go +++ b/pkg/kapis/openpitrix/v1/handler.go @@ -6,6 +6,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" k8sinformers "k8s.io/client-go/informers" + "k8s.io/klog" "kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/informers" @@ -15,7 +16,6 @@ import ( "kubesphere.io/kubesphere/pkg/server/params" "kubesphere.io/kubesphere/pkg/simple/client/k8s" op "kubesphere.io/kubesphere/pkg/simple/client/openpitrix" - "net/http" "strconv" "strings" ) @@ -43,6 +43,7 @@ func (h *openpitrixHandler) ListApplications(request *restful.Request, response conditions, err := params.ParseConditions(request) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(response, err) return } @@ -52,6 +53,7 @@ func (h *openpitrixHandler) ListApplications(request *restful.Request, response ns, err := h.informers.Core().V1().Namespaces().Lister().Get(namespace) if err != nil { + klog.Errorln(err) api.HandleInternalError(response, err) return } @@ -71,6 +73,7 @@ func (h *openpitrixHandler) ListApplications(request *restful.Request, response result, err := h.openpitrix.ListApplications(conditions, limit, offset, orderBy, reverse) if err != nil { + klog.Errorln(err) api.HandleInternalError(response, err) return } @@ -85,17 +88,15 @@ func (h *openpitrixHandler) DescribeApplication(req *restful.Request, resp *rest app, err := h.openpitrix.DescribeApplication(namespace, clusterId) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } ns, err := h.informers.Core().V1().Namespaces().Lister().Get(namespace) if err != nil { + klog.Errorln(err) api.HandleInternalError(resp, err) return } @@ -104,6 +105,7 @@ func (h *openpitrixHandler) DescribeApplication(req *restful.Request, resp *rest if runtimeId != app.Cluster.RuntimeId { err = fmt.Errorf("rumtime not match %s,%s", app.Cluster.RuntimeId, runtimeId) + klog.V(4).Infoln(err) api.HandleForbidden(resp, err) return } @@ -117,6 +119,7 @@ func (h *openpitrixHandler) CreateApplication(req *restful.Request, resp *restfu var createClusterRequest openpitrix.CreateClusterRequest err := req.ReadEntity(&createClusterRequest) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -126,6 +129,7 @@ func (h *openpitrixHandler) CreateApplication(req *restful.Request, resp *restfu err = h.openpitrix.CreateApplication(namespace, createClusterRequest) if err != nil { + klog.Errorln(err) api.HandleInternalError(resp, err) return } @@ -139,6 +143,7 @@ func (h *openpitrixHandler) ModifyApplication(req *restful.Request, resp *restfu namespace := req.PathParameter("namespace") err := req.ReadEntity(&modifyClusterAttributesRequest) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -146,17 +151,15 @@ func (h *openpitrixHandler) ModifyApplication(req *restful.Request, resp *restfu app, err := h.openpitrix.DescribeApplication(namespace, clusterId) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } ns, err := h.informers.Core().V1().Namespaces().Lister().Get(namespace) if err != nil { + klog.Errorln(err) api.HandleInternalError(resp, err) return } @@ -165,6 +168,7 @@ func (h *openpitrixHandler) ModifyApplication(req *restful.Request, resp *restfu if runtimeId != app.Cluster.RuntimeId { err = fmt.Errorf("rumtime not match %s,%s", app.Cluster.RuntimeId, runtimeId) + klog.V(4).Infoln(err) api.HandleForbidden(resp, err) return } @@ -172,11 +176,8 @@ func (h *openpitrixHandler) ModifyApplication(req *restful.Request, resp *restfu err = h.openpitrix.ModifyApplication(modifyClusterAttributesRequest) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -189,17 +190,15 @@ func (h *openpitrixHandler) DeleteApplication(req *restful.Request, resp *restfu app, err := h.openpitrix.DescribeApplication(namespace, clusterId) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } ns, err := h.informers.Core().V1().Namespaces().Lister().Get(namespace) if err != nil { + klog.Errorln(err) api.HandleInternalError(resp, err) return } @@ -208,6 +207,7 @@ func (h *openpitrixHandler) DeleteApplication(req *restful.Request, resp *restfu if runtimeId != app.Cluster.RuntimeId { err = fmt.Errorf("rumtime not match %s,%s", app.Cluster.RuntimeId, runtimeId) + klog.V(4).Infoln(err) api.HandleForbidden(resp, err) return } @@ -215,11 +215,8 @@ func (h *openpitrixHandler) DeleteApplication(req *restful.Request, resp *restfu err = h.openpitrix.DeleteApplication(clusterId) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -233,6 +230,7 @@ func (h *openpitrixHandler) GetAppVersionPackage(req *restful.Request, resp *res result, err := h.openpitrix.GetAppVersionPackage(appId, versionId) if err != nil { + klog.Errorln(err) api.HandleInternalError(resp, err) return } @@ -244,6 +242,7 @@ func (h *openpitrixHandler) DoAppAction(req *restful.Request, resp *restful.Resp var doActionRequest openpitrix.ActionRequest err := req.ReadEntity(&doActionRequest) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -253,15 +252,8 @@ func (h *openpitrixHandler) DoAppAction(req *restful.Request, resp *restful.Resp err = h.openpitrix.DoAppAction(appId, &doActionRequest) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - if status.Code(err) == codes.InvalidArgument { - api.HandleBadRequest(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -272,6 +264,7 @@ func (h *openpitrixHandler) DoAppVersionAction(req *restful.Request, resp *restf var doActionRequest openpitrix.ActionRequest err := req.ReadEntity(&doActionRequest) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -282,15 +275,8 @@ func (h *openpitrixHandler) DoAppVersionAction(req *restful.Request, resp *restf err = h.openpitrix.DoAppVersionAction(versionId, &doActionRequest) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - if status.Code(err) == codes.InvalidArgument { - api.HandleBadRequest(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -307,11 +293,8 @@ func (h *openpitrixHandler) GetAppVersionFiles(req *restful.Request, resp *restf result, err := h.openpitrix.GetAppVersionFiles(versionId, getAppVersionFilesRequest) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -327,6 +310,7 @@ func (h *openpitrixHandler) ListAppVersionAudits(req *restful.Request, resp *res conditions, err := params.ParseConditions(req) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -339,7 +323,8 @@ func (h *openpitrixHandler) ListAppVersionAudits(req *restful.Request, resp *res result, err := h.openpitrix.ListAppVersionAudits(conditions, orderBy, reverse, limit, offset) if err != nil { - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -353,6 +338,7 @@ func (h *openpitrixHandler) ListReviews(req *restful.Request, resp *restful.Resp conditions, err := params.ParseConditions(req) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -360,6 +346,7 @@ func (h *openpitrixHandler) ListReviews(req *restful.Request, resp *restful.Resp result, err := h.openpitrix.ListAppVersionReviews(conditions, orderBy, reverse, limit, offset) if err != nil { + klog.Errorln(err) api.HandleInternalError(resp, err) return } @@ -376,6 +363,7 @@ func (h *openpitrixHandler) ListAppVersions(req *restful.Request, resp *restful. conditions, err := params.ParseConditions(req) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -384,6 +372,7 @@ func (h *openpitrixHandler) ListAppVersions(req *restful.Request, resp *restful. result, err := h.openpitrix.ListAppVersions(conditions, orderBy, reverse, limit, offset) if err != nil { + klog.Errorln(err) api.HandleInternalError(resp, err) return } @@ -393,6 +382,7 @@ func (h *openpitrixHandler) ListAppVersions(req *restful.Request, resp *restful. if version, ok := item.(*openpitrix.AppVersion); ok { statisticsResult, err := h.openpitrix.ListApplications(¶ms.Conditions{Match: map[string]string{"app_id": version.AppId, "version_id": version.VersionId}}, 0, 0, "", false) if err != nil { + klog.Errorln(err) api.HandleInternalError(resp, err) return } @@ -412,6 +402,7 @@ func (h *openpitrixHandler) ListApps(req *restful.Request, resp *restful.Respons conditions, err := params.ParseConditions(req) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -419,7 +410,8 @@ func (h *openpitrixHandler) ListApps(req *restful.Request, resp *restful.Respons result, err := h.openpitrix.ListApps(conditions, orderBy, reverse, limit, offset) if err != nil { - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -429,7 +421,8 @@ func (h *openpitrixHandler) ListApps(req *restful.Request, resp *restful.Respons statuses := "active|used|enabled|stopped|pending|creating|upgrading|updating|rollbacking|stopping|starting|recovering|resizing|scaling|deleting" statisticsResult, err := h.openpitrix.ListApplications(¶ms.Conditions{Match: map[string]string{openpitrix.AppId: app.AppId, openpitrix.Status: statuses}}, 0, 0, "", false) if err != nil { - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } app.ClusterTotal = &statisticsResult.TotalCount @@ -447,6 +440,7 @@ func (h *openpitrixHandler) ModifyApp(req *restful.Request, resp *restful.Respon appId := req.PathParameter("app") if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -454,15 +448,8 @@ func (h *openpitrixHandler) ModifyApp(req *restful.Request, resp *restful.Respon err = h.openpitrix.ModifyApp(appId, &patchAppRequest) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - if status.Code(err) == codes.InvalidArgument { - api.HandleBadRequest(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -475,11 +462,8 @@ func (h *openpitrixHandler) DescribeApp(req *restful.Request, resp *restful.Resp result, err := h.openpitrix.DescribeApp(appId) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -507,7 +491,8 @@ func (h *openpitrixHandler) CreateApp(req *restful.Request, resp *restful.Respon createAppRequest := &openpitrix.CreateAppRequest{} err := req.ReadEntity(createAppRequest) if err != nil { - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) + klog.V(4).Infoln(err) + api.HandleBadRequest(resp, err) return } @@ -543,6 +528,7 @@ func (h *openpitrixHandler) CreateAppVersion(req *restful.Request, resp *restful var createAppVersionRequest openpitrix.CreateAppVersionRequest err := req.ReadEntity(&createAppVersionRequest) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -565,11 +551,8 @@ func (h *openpitrixHandler) CreateAppVersion(req *restful.Request, resp *restful } if err != nil { - if status.Code(err) == codes.InvalidArgument { - api.HandleBadRequest(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -583,6 +566,7 @@ func (h *openpitrixHandler) ModifyAppVersion(req *restful.Request, resp *restful versionId := req.PathParameter("version") if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -590,11 +574,8 @@ func (h *openpitrixHandler) ModifyAppVersion(req *restful.Request, resp *restful err = h.openpitrix.ModifyAppVersion(versionId, &patchAppVersionRequest) if err != nil { - if status.Code(err) == codes.InvalidArgument { - api.HandleBadRequest(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -607,11 +588,8 @@ func (h *openpitrixHandler) DeleteAppVersion(req *restful.Request, resp *restful err := h.openpitrix.DeleteAppVersion(versionId) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -624,11 +602,8 @@ func (h *openpitrixHandler) DescribeAppVersion(req *restful.Request, resp *restf result, err := h.openpitrix.DescribeAppVersion(versionId) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -641,11 +616,8 @@ func (h *openpitrixHandler) DescribeAttachment(req *restful.Request, resp *restf result, err := h.openpitrix.DescribeAttachment(attachmentId) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -664,6 +636,7 @@ func (h *openpitrixHandler) CreateCategory(req *restful.Request, resp *restful.R createCategoryRequest := &openpitrix.CreateCategoryRequest{} err := req.ReadEntity(createCategoryRequest) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -671,11 +644,8 @@ func (h *openpitrixHandler) CreateCategory(req *restful.Request, resp *restful.R result, err := h.openpitrix.CreateCategory(createCategoryRequest) if err != nil { - if status.Code(err) == codes.InvalidArgument { - api.HandleBadRequest(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -687,11 +657,8 @@ func (h *openpitrixHandler) DeleteCategory(req *restful.Request, resp *restful.R err := h.openpitrix.DeleteCategory(categoryId) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -702,6 +669,7 @@ func (h *openpitrixHandler) ModifyCategory(req *restful.Request, resp *restful.R categoryId := req.PathParameter("category") err := req.ReadEntity(&modifyCategoryRequest) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -709,11 +677,8 @@ func (h *openpitrixHandler) ModifyCategory(req *restful.Request, resp *restful.R err = h.openpitrix.ModifyCategory(categoryId, &modifyCategoryRequest) if err != nil { - if status.Code(err) == codes.InvalidArgument { - api.HandleBadRequest(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -725,11 +690,8 @@ func (h *openpitrixHandler) DescribeCategory(req *restful.Request, resp *restful result, err := h.openpitrix.DescribeCategory(categoryId) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -743,6 +705,7 @@ func (h *openpitrixHandler) ListCategories(req *restful.Request, resp *restful.R conditions, err := params.ParseConditions(req) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -750,7 +713,8 @@ func (h *openpitrixHandler) ListCategories(req *restful.Request, resp *restful.R result, err := h.openpitrix.ListCategories(conditions, orderBy, reverse, limit, offset) if err != nil { - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -759,7 +723,8 @@ func (h *openpitrixHandler) ListCategories(req *restful.Request, resp *restful.R if category, ok := item.(*openpitrix.Category); ok { statisticsResult, err := h.openpitrix.ListApps(¶ms.Conditions{Match: map[string]string{"category_id": category.CategoryID, "status": openpitrix.StatusActive, "repo": openpitrix.BuiltinRepoId}}, "", false, 0, 0) if err != nil { - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } category.AppTotal = &statisticsResult.TotalCount @@ -774,6 +739,7 @@ func (h *openpitrixHandler) CreateRepo(req *restful.Request, resp *restful.Respo createRepoRequest := &openpitrix.CreateRepoRequest{} err := req.ReadEntity(createRepoRequest) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -793,11 +759,8 @@ func (h *openpitrixHandler) CreateRepo(req *restful.Request, resp *restful.Respo } if err != nil { - if status.Code(err) == codes.InvalidArgument { - api.HandleBadRequest(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -809,6 +772,7 @@ func (h *openpitrixHandler) DoRepoAction(req *restful.Request, resp *restful.Res repoId := req.PathParameter("repo") err := req.ReadEntity(repoActionRequest) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -816,11 +780,8 @@ func (h *openpitrixHandler) DoRepoAction(req *restful.Request, resp *restful.Res err = h.openpitrix.DoRepoAction(repoId, repoActionRequest) if err != nil { - if status.Code(err) == codes.InvalidArgument { - api.HandleBadRequest(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -833,11 +794,8 @@ func (h *openpitrixHandler) DeleteRepo(req *restful.Request, resp *restful.Respo err := h.openpitrix.DeleteRepo(repoId) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -849,6 +807,7 @@ func (h *openpitrixHandler) ModifyRepo(req *restful.Request, resp *restful.Respo repoId := req.PathParameter("repo") err := req.ReadEntity(&updateRepoRequest) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -856,11 +815,8 @@ func (h *openpitrixHandler) ModifyRepo(req *restful.Request, resp *restful.Respo err = h.openpitrix.ModifyRepo(repoId, &updateRepoRequest) if err != nil { - if status.Code(err) == codes.InvalidArgument { - api.HandleBadRequest(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -873,11 +829,8 @@ func (h *openpitrixHandler) DescribeRepo(req *restful.Request, resp *restful.Res result, err := h.openpitrix.DescribeRepo(repoId) if err != nil { - if status.Code(err) == codes.NotFound { - api.HandleNotFound(resp, err) - return - } - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -890,6 +843,7 @@ func (h *openpitrixHandler) ListRepos(req *restful.Request, resp *restful.Respon conditions, err := params.ParseConditions(req) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -897,7 +851,8 @@ func (h *openpitrixHandler) ListRepos(req *restful.Request, resp *restful.Respon result, err := h.openpitrix.ListRepos(conditions, orderBy, reverse, limit, offset) if err != nil { - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } @@ -910,6 +865,7 @@ func (h *openpitrixHandler) ListRepoEvents(req *restful.Request, resp *restful.R conditions, err := params.ParseConditions(req) if err != nil { + klog.V(4).Infoln(err) api.HandleBadRequest(resp, err) return } @@ -917,9 +873,25 @@ func (h *openpitrixHandler) ListRepoEvents(req *restful.Request, resp *restful.R result, err := h.openpitrix.ListRepoEvents(repoId, conditions, limit, offset) if err != nil { - api.HandleInternalError(resp, err) + klog.Errorln(err) + handleOpenpitrixError(resp, err) return } resp.WriteEntity(result) } + +func handleOpenpitrixError(resp *restful.Response, err error) { + if status.Code(err) == codes.NotFound { + klog.V(4).Infoln(err) + api.HandleNotFound(resp, err) + return + } + if status.Code(err) == codes.InvalidArgument { + klog.V(4).Infoln(err) + api.HandleBadRequest(resp, err) + return + } + klog.Errorln(err) + api.HandleInternalError(resp, err) +} diff --git a/pkg/kapis/tenant/v1alpha2/devops.go b/pkg/kapis/tenant/v1alpha2/devops.go deleted file mode 100644 index 1295f42b8..000000000 --- a/pkg/kapis/tenant/v1alpha2/devops.go +++ /dev/null @@ -1,154 +0,0 @@ -/* - - Copyright 2019 The KubeSphere 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 v1alpha2 - -import ( - "github.com/emicklei/go-restful" - "k8s.io/klog" - "kubesphere.io/kubesphere/pkg/api" - devopsv1alpha2 "kubesphere.io/kubesphere/pkg/api/devops/v1alpha2" - "kubesphere.io/kubesphere/pkg/constants" - "kubesphere.io/kubesphere/pkg/server/errors" - "kubesphere.io/kubesphere/pkg/server/params" - "net/http" -) - -func (h *tenantHandler) DeleteDevOpsProjectHandler(req *restful.Request, resp *restful.Response) { - projectId := req.PathParameter("devops") - workspaceName := req.PathParameter("workspace") - username := req.HeaderParameter(constants.UserNameHeader) - - _, err := h.tenant.GetWorkspace(workspaceName) - - if err != nil { - klog.Errorf("%+v", err) - errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp) - return - } - - err = h.tenant.DeleteDevOpsProject(projectId, username) - - if err != nil { - klog.Errorf("%+v", err) - errors.ParseSvcErr(err, resp) - return - } - - resp.WriteAsJson(errors.None) -} - -func (h *tenantHandler) CreateDevOpsProjectHandler(req *restful.Request, resp *restful.Response) { - - workspaceName := req.PathParameter("workspace") - username := req.HeaderParameter(constants.UserNameHeader) - - var devops devopsv1alpha2.DevOpsProject - - err := req.ReadEntity(&devops) - - if err != nil { - klog.Infof("%+v", err) - errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp) - return - } - - klog.Infoln("create workspace", username, workspaceName, devops) - project, err := h.tenant.CreateDevOpsProject(username, workspaceName, &devops) - - if err != nil { - klog.Errorf("%+v", err) - errors.ParseSvcErr(err, resp) - return - } - - resp.WriteAsJson(project) -} - -func (h *tenantHandler) GetDevOpsProjectsCountHandler(req *restful.Request, resp *restful.Response) { - username := req.HeaderParameter(constants.UserNameHeader) - - result, err := h.tenant.GetDevOpsProjectsCount(username) - if err != nil { - klog.Errorf("%+v", err) - errors.ParseSvcErr(err, resp) - return - } - resp.WriteAsJson(struct { - Count uint32 `json:"count"` - }{Count: result}) -} - -func (h *tenantHandler) ListDevOpsProjectsHandler(req *restful.Request, resp *restful.Response) { - - workspace := req.PathParameter("workspace") - username := req.PathParameter("member") - if username == "" { - username = req.HeaderParameter(constants.UserNameHeader) - } - orderBy := req.QueryParameter(params.OrderByParam) - reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, false) - limit, offset := params.ParsePaging(req) - conditions, err := params.ParseConditions(req) - - if err != nil { - klog.Errorf("%+v", err) - errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp) - return - } - - result, err := h.tenant.ListDevOpsProjects(workspace, username, conditions, orderBy, reverse, limit, offset) - - if err != nil { - klog.Errorf("%+v", err) - errors.ParseSvcErr(err, resp) - return - } - - resp.WriteAsJson(result) -} - -func (h *tenantHandler) ListDevOpsRules(req *restful.Request, resp *restful.Response) { - - devops := req.PathParameter("devops") - username := req.HeaderParameter(constants.UserNameHeader) - - rules, err := h.tenant.GetUserDevOpsSimpleRules(username, devops) - - if err != nil { - klog.Errorf("%+v", err) - errors.ParseSvcErr(err, resp) - return - } - - resp.WriteAsJson(rules) -} - -func (h *tenantHandler) ListDevopsRules(req *restful.Request, resp *restful.Response) { - - devops := req.PathParameter("devops") - username := req.HeaderParameter(constants.UserNameHeader) - - rules, err := h.tenant.GetUserDevOpsSimpleRules(username, devops) - - if err != nil { - api.HandleInternalError(resp, err) - return - } - - resp.WriteAsJson(rules) -} diff --git a/pkg/kapis/tenant/v1alpha2/handler.go b/pkg/kapis/tenant/v1alpha2/handler.go index b4a70208a..c1fbd389c 100644 --- a/pkg/kapis/tenant/v1alpha2/handler.go +++ b/pkg/kapis/tenant/v1alpha2/handler.go @@ -11,12 +11,15 @@ import ( loggingv1alpha2 "kubesphere.io/kubesphere/pkg/api/logging/v1alpha2" "kubesphere.io/kubesphere/pkg/apiserver/logging" "kubesphere.io/kubesphere/pkg/constants" + "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/models/iam" "kubesphere.io/kubesphere/pkg/models/metrics" "kubesphere.io/kubesphere/pkg/models/resources/v1alpha2" "kubesphere.io/kubesphere/pkg/models/tenant" - "kubesphere.io/kubesphere/pkg/server/errors" + apierr "kubesphere.io/kubesphere/pkg/server/errors" "kubesphere.io/kubesphere/pkg/server/params" + "kubesphere.io/kubesphere/pkg/simple/client/k8s" + "kubesphere.io/kubesphere/pkg/simple/client/mysql" "kubesphere.io/kubesphere/pkg/utils/sliceutil" "net/http" "strings" @@ -24,24 +27,31 @@ import ( type tenantHandler struct { tenant tenant.Interface + am iam.AccessManagementInterface } -func newTenantHandler() *tenantHandler { - return &tenantHandler{} +func newTenantHandler(k8sClient k8s.Client, db *mysql.Database) *tenantHandler { + factory := informers.NewInformerFactories(k8sClient.Kubernetes(), k8sClient.KubeSphere(), k8sClient.S2i(), k8sClient.Application()) + + return &tenantHandler{ + tenant: tenant.New(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory(), factory.KubeSphereSharedInformerFactory(), db), + am: iam.NewAMOperator(factory.KubernetesSharedInformerFactory()), + } } func (h *tenantHandler) ListWorkspaceRules(req *restful.Request, resp *restful.Response) { workspace := req.PathParameter("workspace") username := req.HeaderParameter(constants.UserNameHeader) - rules, err := iam.GetUserWorkspaceSimpleRules(workspace, username) + rules, err := h.tenant.GetWorkspaceSimpleRules(workspace, username) if err != nil { + klog.Errorln(err) api.HandleInternalError(resp, err) return } - resp.WriteAsJson(rules) + resp.WriteEntity(rules) } func (h *tenantHandler) ListWorkspaces(req *restful.Request, resp *restful.Response) { @@ -52,6 +62,7 @@ func (h *tenantHandler) ListWorkspaces(req *restful.Request, resp *restful.Respo conditions, err := params.ParseConditions(req) if err != nil { + klog.Errorln(err) api.HandleBadRequest(resp, err) return } @@ -180,9 +191,49 @@ func (h *tenantHandler) DeleteNamespace(req *restful.Request, resp *restful.Resp return } - resp.WriteAsJson(errors.None) + resp.WriteAsJson(apierr.None) } +func (h *tenantHandler) ListDevopsProjects(req *restful.Request, resp *restful.Response) { + + workspace := req.PathParameter("workspace") + username := req.PathParameter("member") + if username == "" { + username = req.HeaderParameter(constants.UserNameHeader) + } + orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime) + limit, offset := params.ParsePaging(req) + reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, true) + conditions, err := params.ParseConditions(req) + + if err != nil { + api.HandleBadRequest(resp, err) + return + } + conditions.Match["workspace"] = workspace + + result, err := h.tenant.ListDevopsProjects(username, conditions, orderBy, reverse, limit, offset) + + if err != nil { + api.HandleInternalError(resp, err) + return + } + + resp.WriteEntity(result) +} + +func (h *tenantHandler) GetDevOpsProjectsCount(req *restful.Request, resp *restful.Response) { + username := req.HeaderParameter(constants.UserNameHeader) + + result, err := h.tenant.ListDevopsProjects(username, nil, "", false, 1, 0) + if err != nil { + api.HandleInternalError(resp, err) + return + } + resp.WriteEntity(struct { + Count int `json:"count"` + }{Count: result.TotalCount}) +} func (h *tenantHandler) DeleteDevopsProject(req *restful.Request, resp *restful.Response) { projectId := req.PathParameter("devops") workspace := req.PathParameter("workspace") @@ -195,21 +246,40 @@ func (h *tenantHandler) DeleteDevopsProject(req *restful.Request, resp *restful. return } - err = h.tenant.DeleteDevOpsProject(projectId, username) + err = h.tenant.DeleteDevOpsProject(username, projectId) if err != nil { api.HandleInternalError(resp, err) return } - resp.WriteAsJson(errors.None) + resp.WriteEntity(apierr.None) +} + +func (h *tenantHandler) CreateDevopsProject(req *restful.Request, resp *restful.Response) { + } func (h *tenantHandler) ListNamespaceRules(req *restful.Request, resp *restful.Response) { namespace := req.PathParameter("namespace") username := req.HeaderParameter(constants.UserNameHeader) - rules, err := iam.GetUserNamespaceSimpleRules(namespace, username) + rules, err := h.tenant.GetNamespaceSimpleRules(namespace, username) + + if err != nil { + api.HandleInternalError(resp, err) + return + } + + resp.WriteAsJson(rules) +} + +func (h *tenantHandler) ListDevopsRules(req *restful.Request, resp *restful.Response) { + + devops := req.PathParameter("devops") + username := req.HeaderParameter(constants.UserNameHeader) + + rules, err := h.tenant.GetUserDevopsSimpleRules(username, devops) if err != nil { api.HandleInternalError(resp, err) @@ -247,19 +317,20 @@ func (h *tenantHandler) regenerateLoggingRequest(req *restful.Request) (*restful newUrl := net.FormatURL("http", "127.0.0.1", 80, "/kapis/logging.kubesphere.io/v1alpha2/cluster") values := req.Request.URL.Query() - clusterRules, err := iam.GetUserClusterRules(username) + clusterRoleRules, err := h.am.GetClusterPolicyRules(username) + if err != nil { klog.Errorln(err) return nil, err } - hasClusterLogAccess := iam.RulesMatchesRequired(clusterRules, rbacv1.PolicyRule{Verbs: []string{"get"}, Resources: []string{"*"}, APIGroups: []string{"logging.kubesphere.io"}}) + hasClusterLogAccess := iam.RulesMatchesRequired(clusterRoleRules, rbacv1.PolicyRule{Verbs: []string{"get"}, Resources: []string{"*"}, APIGroups: []string{"logging.kubesphere.io"}}) // if the user is not a cluster admin if !hasClusterLogAccess { queryNamespaces := strings.Split(req.QueryParameter("namespaces"), ",") // then the user can only view logs of namespaces he belongs to namespaces := make([]string, 0) - roles, err := iam.GetUserRoles("", username) + roles, err := h.am.GetRoles("", username) if err != nil { klog.Errorln(err) return nil, err diff --git a/pkg/kapis/tenant/v1alpha2/register.go b/pkg/kapis/tenant/v1alpha2/register.go index cc1d61c72..f8454b485 100644 --- a/pkg/kapis/tenant/v1alpha2/register.go +++ b/pkg/kapis/tenant/v1alpha2/register.go @@ -32,6 +32,8 @@ import ( "kubesphere.io/kubesphere/pkg/models/iam" "kubesphere.io/kubesphere/pkg/server/errors" "kubesphere.io/kubesphere/pkg/server/params" + "kubesphere.io/kubesphere/pkg/simple/client/k8s" + "kubesphere.io/kubesphere/pkg/simple/client/mysql" "net/http" ) @@ -42,9 +44,9 @@ const ( var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"} -func AddToContainer(c *restful.Container) error { +func AddToContainer(c *restful.Container, k8sClient k8s.Client, db *mysql.Database) error { ws := runtime.NewWebService(GroupVersion) - handler := newTenantHandler() + handler := newTenantHandler(k8sClient, db) ws.Route(ws.GET("/workspaces"). To(handler.ListWorkspaces). @@ -103,7 +105,7 @@ func AddToContainer(c *restful.Container) error { Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag})) ws.Route(ws.GET("/workspaces/{workspace}/devops"). - To(handler.ListDevOpsProjectsHandler). + To(handler.ListDevopsProjects). Param(ws.PathParameter("workspace", "workspace name")). Param(ws.QueryParameter(params.PagingParam, "page"). Required(false). @@ -115,7 +117,7 @@ func AddToContainer(c *restful.Container) error { Doc("List devops projects for the current user"). Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag})) ws.Route(ws.GET("/workspaces/{workspace}/members/{member}/devops"). - To(handler.ListDevOpsProjectsHandler). + To(handler.ListDevopsProjects). Param(ws.PathParameter("workspace", "workspace name")). Param(ws.PathParameter("member", "workspace member's username")). Param(ws.QueryParameter(params.PagingParam, "page"). @@ -129,14 +131,14 @@ func AddToContainer(c *restful.Container) error { Doc("List the devops projects for the workspace member"). Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag})) ws.Route(ws.GET("/devopscount"). - To(handler.GetDevOpsProjectsCountHandler). + To(handler.GetDevOpsProjectsCount). Returns(http.StatusOK, api.StatusOK, struct { Count uint32 `json:"count"` }{}). Doc("Get the devops projects count for the member"). Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag})) ws.Route(ws.POST("/workspaces/{workspace}/devops"). - To(handler.CreateDevOpsProjectHandler). + To(handler.CreateDevopsProject). Param(ws.PathParameter("workspace", "workspace name")). Doc("Create a devops project in the specified workspace"). Reads(devopsv1alpha2.DevOpsProject{}). diff --git a/pkg/kapis/terminal/v1alpha2/register.go b/pkg/kapis/terminal/v1alpha2/register.go index 7997ccfae..4b90a4114 100644 --- a/pkg/kapis/terminal/v1alpha2/register.go +++ b/pkg/kapis/terminal/v1alpha2/register.go @@ -21,11 +21,10 @@ import ( "github.com/emicklei/go-restful" "github.com/emicklei/go-restful-openapi" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" "kubesphere.io/kubesphere/pkg/apiserver/runtime" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/models" + "kubesphere.io/kubesphere/pkg/simple/client/k8s" ) const ( @@ -34,11 +33,11 @@ const ( var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"} -func AddToContainer(c *restful.Container, client kubernetes.Interface, config *rest.Config) error { +func AddToContainer(c *restful.Container, k8sClient k8s.Client) error { webservice := runtime.NewWebService(GroupVersion) - handler := newTerminalHandler(client, config) + handler := newTerminalHandler(k8sClient.Kubernetes(), k8sClient.Config()) webservice.Route(webservice.GET("/namespaces/{namespace}/pods/{pod}"). To(handler.handleTerminalSession). diff --git a/pkg/models/iam/am.go b/pkg/models/iam/am.go index 1ce4e0035..01b8c5343 100644 --- a/pkg/models/iam/am.go +++ b/pkg/models/iam/am.go @@ -18,8 +18,6 @@ package iam import ( - "fmt" - "github.com/go-ldap/ldap" rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -38,9 +36,6 @@ import ( "kubesphere.io/kubesphere/pkg/server/params" "kubesphere.io/kubesphere/pkg/simple/client" "kubesphere.io/kubesphere/pkg/utils/k8sutil" - "kubesphere.io/kubesphere/pkg/utils/sliceutil" - "sort" - "strings" ) const ( @@ -50,9 +45,21 @@ const ( ) type AccessManagementInterface interface { - GetDevopsRoleSimpleRules(role string) []SimpleRule + GetClusterRole(username string) (*rbacv1.ClusterRole, error) + UnBindAllRoles(username string) error ListRoleBindings(namespace string, role string) ([]*rbacv1.RoleBinding, error) CreateClusterRoleBinding(username string, clusterRole string) error + ListRoles(namespace string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error) + ListClusterRoles(conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error) + ListClusterRoleBindings(clusterRole string) ([]*rbacv1.ClusterRoleBinding, error) + GetClusterRoleSimpleRules(clusterRole string) ([]SimpleRule, error) + GetRoleSimpleRules(namespace string, role string) ([]SimpleRule, error) + GetRoles(namespace, username string) ([]*rbacv1.Role, error) + GetClusterPolicyRules(username string) ([]rbacv1.PolicyRule, error) + GetPolicyRules(namespace, username string) ([]rbacv1.PolicyRule, error) + GetWorkspaceRoleSimpleRules(workspace, roleName string) []SimpleRule + GetWorkspaceRole(workspace, username string) (*rbacv1.ClusterRole, error) + GetWorkspaceRoleMap(username string) (map[string]string, error) } type amOperator struct { @@ -60,7 +67,31 @@ type amOperator struct { resources resource.ResourceGetter } -func newAMOperator(informers informers.SharedInformerFactory) AccessManagementInterface { +func (am *amOperator) ListClusterRoleBindings(clusterRole string) ([]*rbacv1.ClusterRoleBinding, error) { + panic("implement me") +} + +func (am *amOperator) GetRoles(namespace, username string) ([]*rbacv1.Role, error) { + panic("implement me") +} + +func (am *amOperator) GetClusterPolicyRules(username string) ([]rbacv1.PolicyRule, error) { + panic("implement me") +} + +func (am *amOperator) GetPolicyRules(namespace, username string) ([]rbacv1.PolicyRule, error) { + panic("implement me") +} + +func (am *amOperator) GetWorkspaceRole(workspace, username string) (*rbacv1.ClusterRole, error) { + panic("implement me") +} + +func (am *amOperator) UnBindAllRoles(username string) error { + panic("implement me") +} + +func NewAMOperator(informers informers.SharedInformerFactory) *amOperator { resourceGetter := resource.ResourceGetter{} resourceGetter.Add(v1alpha2.Role, role.NewRoleSearcher(informers)) resourceGetter.Add(v1alpha2.ClusterRoles, clusterrole.NewClusterRoleSearcher(informers)) @@ -198,7 +229,7 @@ func (am *amOperator) GetUserClusterRoles(username string) (*rbacv1.ClusterRole, return userFacingClusterRole, clusterRoles, nil } -func (am *amOperator) GetUserClusterRole(username string) (*rbacv1.ClusterRole, error) { +func (am *amOperator) GetClusterRole(username string) (*rbacv1.ClusterRole, error) { userFacingClusterRole, _, err := am.GetUserClusterRoles(username) if err != nil { return nil, err @@ -256,15 +287,15 @@ func (am *amOperator) GetWorkspaceRoleBindings(workspace string) ([]*rbacv1.Clus return result, nil } -func (am *amOperator) GetWorkspaceRole(workspace, role string) (*rbacv1.ClusterRole, error) { - if !sliceutil.HasString(constants.WorkSpaceRoles, role) { - return nil, apierrors.NewNotFound(schema.GroupResource{Resource: "workspace role"}, role) - } - role = fmt.Sprintf("workspace:%s:%s", workspace, strings.TrimPrefix(role, "workspace-")) - return am.informers.Rbac().V1().ClusterRoles().Lister().Get(role) -} +//func (am *amOperator) GetWorkspaceRole(workspace, role string) (*rbacv1.ClusterRole, error) { +// if !sliceutil.HasString(constants.WorkSpaceRoles, role) { +// return nil, apierrors.NewNotFound(schema.GroupResource{Resource: "workspace role"}, role) +// } +// role = fmt.Sprintf("workspace:%s:%s", workspace, strings.TrimPrefix(role, "workspace-")) +// return am.informers.Rbac().V1().ClusterRoles().Lister().Get(role) +//} -func (am *amOperator) GetUserWorkspaceRoleMap(username string) (map[string]string, error) { +func (am *amOperator) GetWorkspaceRoleMap(username string) (map[string]string, error) { clusterRoleBindings, err := am.informers.Rbac().V1().ClusterRoleBindings().Lister().List(labels.Everything()) @@ -286,7 +317,7 @@ func (am *amOperator) GetUserWorkspaceRoleMap(username string) (map[string]strin } func (am *amOperator) GetUserWorkspaceRole(workspace, username string) (*rbacv1.ClusterRole, error) { - workspaceRoleMap, err := am.GetUserWorkspaceRoleMap(username) + workspaceRoleMap, err := am.GetWorkspaceRoleMap(username) if err != nil { return nil, err @@ -341,55 +372,6 @@ func (am *amOperator) GetClusterRoleBindings(clusterRoleName string) ([]*rbacv1. return items, nil } -func (am *amOperator) ListClusterRoleUsers(clusterRoleName string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) { - - roleBindings, err := am.GetClusterRoleBindings(clusterRoleName) - - if err != nil { - return nil, err - } - users := make([]*User, 0) - for _, roleBinding := range roleBindings { - for _, subject := range roleBinding.Subjects { - if subject.Kind == rbacv1.UserKind && !k8sutil.ContainsUser(users, subject.Name) { - user, err := GetUserInfo(subject.Name) - if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { - continue - } - if err != nil { - klog.Errorln(err) - return nil, err - } - users = append(users, user) - } - } - } - - // order & reverse - sort.Slice(users, func(i, j int) bool { - if reverse { - i, j = j, i - } - switch orderBy { - default: - fallthrough - case v1alpha2.Name: - return strings.Compare(users[i].Username, users[j].Username) <= 0 - } - }) - - result := make([]interface{}, 0) - - for i, d := range users { - if i >= offset && (limit == -1 || len(result) < limit) { - result = append(result, d) - } - } - - return &models.PageableResponse{Items: result, TotalCount: len(users)}, nil - -} - func (am *amOperator) ListRoles(namespace string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) { return am.resources.ListResources(namespace, v1alpha2.Roles, conditions, orderBy, reverse, limit, offset) } @@ -431,92 +413,6 @@ func (am *amOperator) ListClusterRoles(conditions *params.Conditions, orderBy st return am.resources.ListResources("", v1alpha2.ClusterRoles, conditions, orderBy, reverse, limit, offset) } -func (am *amOperator) NamespaceUsers(namespaceName string) ([]*User, error) { - namespace, err := am.informers.Core().V1().Namespaces().Lister().Get(namespaceName) - if err != nil { - klog.Errorln(err) - return nil, err - } - roleBindings, err := am.GetRoleBindings(namespaceName, "") - - if err != nil { - return nil, err - } - - users := make([]*User, 0) - - for _, roleBinding := range roleBindings { - // controlled by ks-controller-manager - if roleBinding.Name == NamespaceViewerRoleBindName { - continue - } - for _, subject := range roleBinding.Subjects { - if subject.Kind == rbacv1.UserKind && !k8sutil.ContainsUser(users, subject.Name) { - - // show creator - if roleBinding.Name == NamespaceAdminRoleBindName && subject.Name != namespace.Annotations[constants.CreatorAnnotationKey] { - continue - } - - user, err := GetUserInfo(subject.Name) - - if err != nil { - if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { - continue - } - return nil, err - } - - user.Role = roleBinding.RoleRef.Name - user.RoleBindTime = &roleBinding.CreationTimestamp.Time - user.RoleBinding = roleBinding.Name - users = append(users, user) - } - } - } - - return users, nil -} - -func (am *amOperator) GetUserWorkspaceSimpleRules(workspace, username string) ([]SimpleRule, error) { - clusterRules, err := am.GetUserClusterRules(username) - if err != nil { - return nil, err - } - - // cluster-admin - if RulesMatchesRequired(clusterRules, rbacv1.PolicyRule{ - Verbs: []string{"*"}, - APIGroups: []string{"*"}, - Resources: []string{"*"}, - }) { - return am.GetWorkspaceRoleSimpleRules(workspace, constants.WorkspaceAdmin), nil - } - - workspaceRole, err := am.GetUserWorkspaceRole(workspace, username) - - if err != nil { - if apierrors.IsNotFound(err) { - - // workspaces-manager - if RulesMatchesRequired(clusterRules, rbacv1.PolicyRule{ - Verbs: []string{"*"}, - APIGroups: []string{"*"}, - Resources: []string{"workspaces", "workspaces/*"}, - }) { - return am.GetWorkspaceRoleSimpleRules(workspace, constants.WorkspacesManager), nil - } - - return []SimpleRule{}, nil - } - - klog.Error(err) - return nil, err - } - - return am.GetWorkspaceRoleSimpleRules(workspace, workspaceRole.Annotations[constants.DisplayNameAnnotationKey]), nil -} - func (am *amOperator) GetWorkspaceRoleSimpleRules(workspace, roleName string) []SimpleRule { workspaceRules := make([]SimpleRule, 0) @@ -583,20 +479,6 @@ func (am *amOperator) GetUserClusterSimpleRules(username string) ([]SimpleRule, return getClusterSimpleRule(clusterRules), nil } -func (am *amOperator) GetUserNamespaceSimpleRules(namespace, username string) ([]SimpleRule, error) { - clusterRules, err := am.GetUserClusterRules(username) - if err != nil { - return nil, err - } - rules, err := am.GetUserRules(namespace, username) - if err != nil { - return nil, err - } - rules = append(rules, clusterRules...) - - return getSimpleRule(rules), nil -} - // Convert roles to rules func (am *amOperator) GetRoleSimpleRules(namespace string, roleName string) ([]SimpleRule, error) { @@ -608,7 +490,7 @@ func (am *amOperator) GetRoleSimpleRules(namespace string, roleName string) ([]S return nil, err } - return getSimpleRule(role.Rules), nil + return ConvertToSimpleRule(role.Rules), nil } func getClusterSimpleRule(policyRules []rbacv1.PolicyRule) []SimpleRule { @@ -629,7 +511,7 @@ func getClusterSimpleRule(policyRules []rbacv1.PolicyRule) []SimpleRule { return rules } -func getSimpleRule(policyRules []rbacv1.PolicyRule) []SimpleRule { +func ConvertToSimpleRule(policyRules []rbacv1.PolicyRule) []SimpleRule { simpleRules := make([]SimpleRule, 0) for i := 0; i < len(policy.RoleRuleMapping); i++ { rule := SimpleRule{Name: policy.RoleRuleMapping[i].Name} diff --git a/pkg/models/iam/im.go b/pkg/models/iam/im.go index 6706d9b18..c1be6a830 100644 --- a/pkg/models/iam/im.go +++ b/pkg/models/iam/im.go @@ -1,74 +1,63 @@ /* - - Copyright 2019 The KubeSphere 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. - -*/ + * + * Copyright 2020 The KubeSphere 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 iam import ( - "encoding/json" - "errors" "fmt" - "github.com/emicklei/go-restful" + "github.com/dgrijalva/jwt-go" "github.com/go-ldap/ldap" "github.com/go-redis/redis" + "github.com/pkg/errors" "golang.org/x/oauth2" - "io/ioutil" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/klog" - "kubesphere.io/kubesphere/pkg/constants" - "kubesphere.io/kubesphere/pkg/db" - "kubesphere.io/kubesphere/pkg/informers" - "kubesphere.io/kubesphere/pkg/models/devops" - "kubesphere.io/kubesphere/pkg/models/kubeconfig" - "kubesphere.io/kubesphere/pkg/models/kubectl" + "kubesphere.io/kubesphere/pkg/models" "kubesphere.io/kubesphere/pkg/server/params" - clientset "kubesphere.io/kubesphere/pkg/simple/client" ldappool "kubesphere.io/kubesphere/pkg/simple/client/ldap" - "kubesphere.io/kubesphere/pkg/utils/k8sutil" - "kubesphere.io/kubesphere/pkg/utils/sliceutil" - "net/http" - "regexp" - "sort" + "kubesphere.io/kubesphere/pkg/utils/jwtutil" "strconv" "strings" "time" - - "github.com/dgrijalva/jwt-go" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "kubesphere.io/kubesphere/pkg/models" - "kubesphere.io/kubesphere/pkg/utils/jwtutil" ) type IdentityManagementInterface interface { CreateUser(user *User) (*User, error) + DeleteUser(username string) error DescribeUser(username string) (*User, error) Login(username, password, ip string) (*oauth2.Token, error) + ModifyUser(user *User) (*User, error) + ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) + GetUserRoles(username string) ([]*rbacv1.Role, error) + GetUserRole(namespace string, username string) (*rbacv1.Role, error) +} + +type Config struct { + authRateLimit string + maxAuthFailed int + authTimeInterval time.Duration + tokenIdleTimeout time.Duration + enableMultiLogin bool } type imOperator struct { - config Config - ldap ldappool.Client - redis redis.Client - initUsers []initUser -} - -type initUser struct { - User - Hidden bool `json:"hidden"` + config Config + ldap ldappool.Client + redis redis.Client } const ( @@ -83,164 +72,133 @@ const ( dateTimeLayout = "20060102150405Z" ) -func IdentityManagementInit(ldap ldappool.Client, config Config) (IdentityManagementInterface, error) { - - //maxAuthFailed, authTimeInterval := parseAuthRateLimit(authRateLimit) +var ( + AuthRateLimitExceeded = errors.New("user auth rate limit exceeded") + UserAlreadyExists = errors.New("user already exists") + UserNotExists = errors.New("user not exists") +) +func NewIMOperator(ldap ldappool.Client, config Config) *imOperator { imOperator := &imOperator{ldap: ldap, config: config} + return imOperator +} - err := imOperator.checkAndCreateDefaultUser() +// TODO init in controller +func (im *imOperator) Init() error { + + userSearchBase := &ldap.AddRequest{ + DN: im.ldap.UserSearchBase(), + Attributes: []ldap.Attribute{{ + Type: "objectClass", + Vals: []string{"organizationalUnit", "top"}, + }, { + Type: "ou", + Vals: []string{"Users"}, + }}, + Controls: nil, + } + + err := im.createIfNotExists(userSearchBase) + + if err != nil { + return err + } + + groupSearchBase := &ldap.AddRequest{ + DN: im.ldap.GroupSearchBase(), + Attributes: []ldap.Attribute{{ + Type: "objectClass", + Vals: []string{"organizationalUnit", "top"}, + }, { + Type: "ou", + Vals: []string{"Groups"}, + }}, + Controls: nil, + } + + err = im.createIfNotExists(groupSearchBase) + + if err != nil { + return err + } + + return nil +} + +func (im *imOperator) createIfNotExists(createRequest *ldap.AddRequest) error { + conn, err := im.ldap.NewConn() + if err != nil { + return err + } + defer conn.Close() + + searchRequest := ldap.NewSearchRequest( + createRequest.DN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + "(objectClass=*)", + nil, + nil, + ) + + _, err = conn.Search(searchRequest) + + if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { + err = conn.Add(createRequest) + } + + if err != nil { + klog.Errorln(err) + return err + } + + return nil +} + +func (im *imOperator) ModifyUser(user *User) (*User, error) { + conn, err := im.ldap.NewConn() if err != nil { klog.Errorln(err) return nil, err } - err = imOperator.checkAndCreateDefaultGroup() + defer conn.Close() + + dn := fmt.Sprintf("uid=%s,%s", user.Username, im.ldap.UserSearchBase()) + userModifyRequest := ldap.NewModifyRequest(dn, nil) + + if user.Description != "" { + userModifyRequest.Replace("description", []string{user.Description}) + } + + if user.Lang != "" { + userModifyRequest.Replace("preferredLanguage", []string{user.Lang}) + } + + if user.Password != "" { + userModifyRequest.Replace("userPassword", []string{user.Password}) + } + + err = conn.Modify(userModifyRequest) if err != nil { - klog.Errorln(err) + klog.Error(err) return nil, err } - return imOperator, nil -} + // clear auth failed record + if user.Password != "" { -func parseAuthRateLimit(authRateLimit string) (int, time.Duration) { - regex := regexp.MustCompile(authRateLimitRegex) - groups := regex.FindStringSubmatch(authRateLimit) + records, err := im.redis.Keys(fmt.Sprintf("kubesphere:authfailed:%s:*", user.Username)).Result() - maxCount := defaultMaxAuthFailed - timeInterval := defaultAuthTimeInterval - - if len(groups) == 3 { - maxCount, _ = strconv.Atoi(groups[1]) - timeInterval, _ = time.ParseDuration(groups[2]) - } else { - klog.Warning("invalid auth rate limit", authRateLimit) - } - - return maxCount, timeInterval -} - -func (im *imOperator) checkAndCreateDefaultGroup() error { - - conn, err := im.ldap.NewConn() - if err != nil { - return err - } - defer conn.Close() - - groupSearchRequest := ldap.NewSearchRequest( - im.ldap.GroupSearchBase(), - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - "(&(objectClass=posixGroup))", - nil, - nil, - ) - - _, err = conn.Search(groupSearchRequest) - - if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { - err = im.createGroupsBaseDN() - if err != nil { - return fmt.Errorf("GroupBaseDN %s create failed: %s\n", im.ldap.GroupSearchBase(), err) + if err == nil { + im.redis.Del(records...) } } - if err != nil { - return fmt.Errorf("iam database init failed: %s\n", err) - } - - return nil + return im.DescribeUser(user.Username) } -func (im *imOperator) checkAndCreateDefaultUser() error { - conn, err := im.ldap.NewConn() - if err != nil { - return err - } - defer conn.Close() - - userSearchRequest := ldap.NewSearchRequest( - im.ldap.UserSearchBase(), - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - "(&(objectClass=inetOrgPerson))", - []string{"uid"}, - nil, - ) - - result, err := conn.Search(userSearchRequest) - - if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { - err = im.createUserBaseDN() - if err != nil { - return fmt.Errorf("UserBaseDN %s create failed: %s\n", im.ldap.UserSearchBase(), err) - } - } - - if err != nil { - return fmt.Errorf("iam database init failed: %s\n", err) - } - - data, err := ioutil.ReadFile(im.config.userInitFile) - - if err == nil { - json.Unmarshal(data, &im.initUsers) - } - - im.initUsers = append(im.initUsers, initUser{User: User{Username: constants.AdminUserName, Email: im.config.adminEmail, Password: im.config.adminPassword, Description: "Administrator account that was always created by default.", ClusterRole: constants.ClusterAdmin}}) - - for _, user := range im.initUsers { - if result == nil || !containsUser(result.Entries, user) { - _, err = im.CreateUser(&user.User) - if err != nil && !ldap.IsErrorWithCode(err, ldap.LDAPResultEntryAlreadyExists) { - klog.Errorln(err) - return err - } - } - } - - return nil -} - -func containsUser(entries []*ldap.Entry, user initUser) bool { - for _, entry := range entries { - uid := entry.GetAttributeValue("uid") - if uid == user.Username { - return true - } - } - return false -} - -func (im *imOperator) createUserBaseDN() error { - - conn, err := im.ldap.NewConn() - if err != nil { - return err - } - defer conn.Close() - groupsCreateRequest := ldap.NewAddRequest(im.ldap.UserSearchBase(), nil) - groupsCreateRequest.Attribute("objectClass", []string{"organizationalUnit", "top"}) - groupsCreateRequest.Attribute("ou", []string{"Users"}) - return conn.Add(groupsCreateRequest) -} - -func (im *imOperator) createGroupsBaseDN() error { - - conn, err := im.ldap.NewConn() - if err != nil { - return err - } - defer conn.Close() - groupsCreateRequest := ldap.NewAddRequest(im.ldap.GroupSearchBase(), nil) - groupsCreateRequest.Attribute("objectClass", []string{"organizationalUnit", "top"}) - groupsCreateRequest.Attribute("ou", []string{"Groups"}) - return conn.Add(groupsCreateRequest) -} - -// User login func (im *imOperator) Login(username, password, ip string) (*oauth2.Token, error) { records, err := im.redis.Keys(fmt.Sprintf("kubesphere:authfailed:%s:*", username)).Result() @@ -251,10 +209,10 @@ func (im *imOperator) Login(username, password, ip string) (*oauth2.Token, error } if len(records) >= im.config.maxAuthFailed { - return nil, restful.NewError(http.StatusTooManyRequests, "auth rate limit exceeded") + return nil, AuthRateLimitExceeded } - user, err := im.DescribeUser(&User{Username: username, Email: username}) + user, err := im.DescribeUser(username) conn, err := im.ldap.NewConn() if err != nil { @@ -263,33 +221,32 @@ func (im *imOperator) Login(username, password, ip string) (*oauth2.Token, error } defer conn.Close() - dn := fmt.Sprintf("%s=%s,%s", uidAttribute, user.Username, im.ldap.UserSearchBase()) + dn := fmt.Sprintf("uid=%s,%s", user.Username, im.ldap.UserSearchBase()) // bind as the user to verify their password err = conn.Bind(dn, password) if err != nil { if ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials) { - authFailedCacheKey := fmt.Sprintf("kubesphere:authfailed:%s:%d", user.Username, time.Now().UnixNano()) - im.redis.Set(authFailedCacheKey, "", im.config.authTimeInterval) + cacheKey := fmt.Sprintf("kubesphere:authfailed:%s:%d", user.Username, time.Now().UnixNano()) + im.redis.Set(cacheKey, "", im.config.authTimeInterval) } return nil, err } - claims := jwt.MapClaims{} - loginTime := time.Now() // token without expiration time will auto sliding - claims["username"] = user.Username - claims["email"] = user.Email - claims["iat"] = loginTime.Unix() - + claims := jwt.MapClaims{ + "iat": loginTime.Unix(), + "username": user.Username, + "email": user.Email, + } token := jwtutil.MustSigned(claims) if !im.config.enableMultiLogin { // multi login not allowed, remove the previous token - sessionCacheKey := fmt.Sprintf("kubesphere:users:%s:token:*", user.Username) - sessions, err := im.redis.Keys(sessionCacheKey).Result() + cacheKey := fmt.Sprintf("kubesphere:users:%s:token:*", user.Username) + sessions, err := im.redis.Keys(cacheKey).Result() if err != nil { klog.Errorln(err) @@ -307,8 +264,8 @@ func (im *imOperator) Login(username, password, ip string) (*oauth2.Token, error } // cache token with expiration time - sessionCacheKey := fmt.Sprintf("kubesphere:users:%s:token:%s", user.Username, token) - if err = im.redis.Set(sessionCacheKey, token, im.config.tokenIdleTimeout).Err(); err != nil { + cacheKey := fmt.Sprintf("kubesphere:users:%s:token:%s", user.Username, token) + if err = im.redis.Set(cacheKey, token, im.config.tokenIdleTimeout).Err(); err != nil { klog.Errorln(err) return nil, err } @@ -336,123 +293,10 @@ func (im *imOperator) LoginHistory(username string) ([]string, error) { } func (im *imOperator) ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) { - conn, err := im.ldap.NewConn() - if err != nil { - return nil, err - } - defer conn.Close() - - pageControl := ldap.NewControlPaging(1000) - - users := make([]User, 0) - - filter := "(&(objectClass=inetOrgPerson))" - - if keyword := conditions.Match["keyword"]; keyword != "" { - filter = fmt.Sprintf("(&(objectClass=inetOrgPerson)(|(uid=*%s*)(mail=*%s*)(description=*%s*)))", keyword, keyword, keyword) - } - - if username := conditions.Match["username"]; username != "" { - uidFilter := "" - for _, username := range strings.Split(username, "|") { - uidFilter += fmt.Sprintf("(uid=%s)", username) - } - filter = fmt.Sprintf("(&(objectClass=inetOrgPerson)(|%s))", uidFilter) - } - - if email := conditions.Match["email"]; email != "" { - emailFilter := "" - for _, username := range strings.Split(email, "|") { - emailFilter += fmt.Sprintf("(mail=%s)", username) - } - filter = fmt.Sprintf("(&(objectClass=inetOrgPerson)(|%s))", emailFilter) - } - - for { - userSearchRequest := ldap.NewSearchRequest( - im.ldap.UserSearchBase(), - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - filter, - []string{"uid", "mail", "description", "preferredLanguage", "createTimestamp"}, - []ldap.Control{pageControl}, - ) - - response, err := conn.Search(userSearchRequest) - - if err != nil { - klog.Errorln("search user", err) - return nil, err - } - - for _, entry := range response.Entries { - - uid := entry.GetAttributeValue("uid") - email := entry.GetAttributeValue("mail") - description := entry.GetAttributeValue("description") - lang := entry.GetAttributeValue("preferredLanguage") - createTimestamp, _ := time.Parse("20060102150405Z", entry.GetAttributeValue("createTimestamp")) - - user := User{Username: uid, Email: email, Description: description, Lang: lang, CreateTime: createTimestamp} - - if !im.shouldHidden(user) { - users = append(users, user) - } - } - - updatedControl := ldap.FindControl(response.Controls, ldap.ControlTypePaging) - if ctrl, ok := updatedControl.(*ldap.ControlPaging); ctrl != nil && ok && len(ctrl.Cookie) != 0 { - pageControl.SetCookie(ctrl.Cookie) - continue - } - - break - } - - sort.Slice(users, func(i, j int) bool { - if reverse { - tmp := i - i = j - j = tmp - } - switch orderBy { - case "username": - return strings.Compare(users[i].Username, users[j].Username) <= 0 - case "createTime": - fallthrough - default: - return users[i].CreateTime.Before(users[j].CreateTime) - } - }) - - items := make([]interface{}, 0) - - for i, user := range users { - - if i >= offset && len(items) < limit { - - user.LastLoginTime = im.GetLastLoginTime(user.Username) - clusterRole, err := im.GetUserClusterRole(user.Username) - if err != nil { - return nil, err - } - user.ClusterRole = clusterRole.Name - items = append(items, user) - } - } - - return &models.PageableResponse{Items: items, TotalCount: len(users)}, nil + panic("implement me") } -func (im *imOperator) shouldHidden(user User) bool { - for _, initUser := range im.initUsers { - if initUser.Username == user.Username { - return initUser.Hidden - } - } - return false -} - -func (im *imOperator) DescribeUser(user *User) (*User, error) { +func (im *imOperator) DescribeUser(username string) (*User, error) { conn, err := im.ldap.NewConn() if err != nil { @@ -461,11 +305,11 @@ func (im *imOperator) DescribeUser(user *User) (*User, error) { } defer conn.Close() - filter := fmt.Sprintf("(&(objectClass=inetOrgPerson)(|(uid=%s)(mail=%s)))", user.Username, user.Email) + filter := fmt.Sprintf("(&(objectClass=inetOrgPerson)(|(uid=%s)(mail=%s)))", username, username) searchRequest := ldap.NewSearchRequest( im.ldap.UserSearchBase(), - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, filter, []string{mailAttribute, descriptionAttribute, preferredLanguageAttribute, createTimestampAttribute}, nil, @@ -479,7 +323,7 @@ func (im *imOperator) DescribeUser(user *User) (*User, error) { } if len(result.Entries) != 1 { - return nil, ldap.NewError(ldap.LDAPResultNoSuchObject, errors.New("user does not exist")) + return nil, UserNotExists } entry := result.Entries[0] @@ -492,11 +336,14 @@ func convertLdapEntryToUser(entry *ldap.Entry) *User { email := entry.GetAttributeValue(mailAttribute) description := entry.GetAttributeValue(descriptionAttribute) lang := entry.GetAttributeValue(preferredLanguageAttribute) - createTimestamp, _ := time.Parse(dateTimeLayout, entry.GetAttributeValue(createTimestampAttribute)) + createTimestamp, err := time.Parse(dateTimeLayout, entry.GetAttributeValue(createTimestampAttribute)) + if err != nil { + klog.Errorln(err) + } return &User{Username: username, Email: email, Description: description, Lang: lang, CreateTime: createTimestamp} } -func (im *imOperator) GetLastLoginTime(username string) string { +func (im *imOperator) getLastLoginTime(username string) string { cacheKey := fmt.Sprintf("kubesphere:users:%s:login-log", username) lastLogin, err := im.redis.LRange(cacheKey, -1, -1).Result() if err != nil { @@ -512,164 +359,31 @@ func (im *imOperator) GetLastLoginTime(username string) string { func (im *imOperator) DeleteUser(username string) error { conn, err := im.ldap.NewConn() + if err != nil { + klog.Errorln(err) return err } + defer conn.Close() deleteRequest := ldap.NewDelRequest(fmt.Sprintf("uid=%s,%s", username, im.ldap.UserSearchBase()), nil) if err = conn.Del(deleteRequest); err != nil { - klog.Errorln("delete user", err) + klog.Errorln(err) return err } - if err = im.deleteRoleBindings(username); err != nil { - klog.Errorln("delete user role bindings failed", username, err) - } - - if err := kubeconfig.DelKubeConfig(username); err != nil { - klog.Errorln("delete user kubeconfig failed", username, err) - } - - if err := kubectl.DelKubectlDeploy(username); err != nil { - klog.Errorln("delete user terminal pod failed", username, err) - } - - if err := im.deleteUserInDevOps(username); err != nil { - klog.Errorln("delete user in devops failed", username, err) - } - return nil - -} - -// deleteUserInDevOps is used to clean up user data of devops, such as permission rules -func (im *imOperator) deleteUserInDevOps(username string) error { - - devopsDb, err := clientset.ClientSets().MySQL() - if err != nil { - if err == clientset.ErrClientSetNotEnabled { - klog.Warning("mysql is not enable") - return nil - } - return err - } - - dp, err := clientset.ClientSets().Devops() - if err != nil { - if err == clientset.ErrClientSetNotEnabled { - klog.Warning("devops client is not enable") - return nil - } - return err - } - - jenkinsClient := dp.Jenkins() - - _, err = devopsDb.DeleteFrom(devops.ProjectMembershipTableName). - Where(db.And( - db.Eq(devops.ProjectMembershipUsernameColumn, username), - )).Exec() - if err != nil { - klog.Errorf("%+v", err) - return err - } - - err = jenkinsClient.DeleteUserInProject(username) - if err != nil { - klog.Errorf("%+v", err) - return err - } - return nil -} - -func (im *imOperator) deleteRoleBindings(username string) error { - roleBindingLister := informers.SharedInformerFactory().Rbac().V1().RoleBindings().Lister() - roleBindings, err := roleBindingLister.List(labels.Everything()) - - if err != nil { - return err - } - - for _, roleBinding := range roleBindings { - roleBinding = roleBinding.DeepCopy() - length1 := len(roleBinding.Subjects) - - for index, subject := range roleBinding.Subjects { - if subject.Kind == rbacv1.UserKind && subject.Name == username { - roleBinding.Subjects = append(roleBinding.Subjects[:index], roleBinding.Subjects[index+1:]...) - index-- - } - } - - length2 := len(roleBinding.Subjects) - - if length2 == 0 { - deletePolicy := metav1.DeletePropagationBackground - err = clientset.ClientSets().K8s().Kubernetes().RbacV1().RoleBindings(roleBinding.Namespace).Delete(roleBinding.Name, &metav1.DeleteOptions{PropagationPolicy: &deletePolicy}) - - if err != nil { - klog.Errorf("delete role binding %s %s %s failed: %v", username, roleBinding.Namespace, roleBinding.Name, err) - } - } else if length2 < length1 { - _, err = clientset.ClientSets().K8s().Kubernetes().RbacV1().RoleBindings(roleBinding.Namespace).Update(roleBinding) - - if err != nil { - klog.Errorf("update role binding %s %s %s failed: %v", username, roleBinding.Namespace, roleBinding.Name, err) - } - } - } - - clusterRoleBindingLister := informers.SharedInformerFactory().Rbac().V1().ClusterRoleBindings().Lister() - clusterRoleBindings, err := clusterRoleBindingLister.List(labels.Everything()) - - for _, clusterRoleBinding := range clusterRoleBindings { - clusterRoleBinding = clusterRoleBinding.DeepCopy() - length1 := len(clusterRoleBinding.Subjects) - - for index, subject := range clusterRoleBinding.Subjects { - if subject.Kind == rbacv1.UserKind && subject.Name == username { - clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects[:index], clusterRoleBinding.Subjects[index+1:]...) - index-- - } - } - - length2 := len(clusterRoleBinding.Subjects) - if length2 == 0 { - // delete if it's not workspace role binding - if isWorkspaceRoleBinding(clusterRoleBinding) { - _, err = clientset.ClientSets().K8s().Kubernetes().RbacV1().ClusterRoleBindings().Update(clusterRoleBinding) - } else { - deletePolicy := metav1.DeletePropagationBackground - err = clientset.ClientSets().K8s().Kubernetes().RbacV1().ClusterRoleBindings().Delete(clusterRoleBinding.Name, &metav1.DeleteOptions{PropagationPolicy: &deletePolicy}) - } - if err != nil { - klog.Errorf("update cluster role binding %s failed:%s", clusterRoleBinding.Name, err) - } - } else if length2 < length1 { - _, err = clientset.ClientSets().K8s().Kubernetes().RbacV1().ClusterRoleBindings().Update(clusterRoleBinding) - - if err != nil { - klog.Errorf("update cluster role binding %s failed:%s", clusterRoleBinding.Name, err) - } - } - - } - return nil } -func (im *imOperator) isWorkspaceRoleBinding(clusterRoleBinding *rbacv1.ClusterRoleBinding) bool { - return k8sutil.IsControlledBy(clusterRoleBinding.OwnerReferences, "Workspace", "") -} - func (im *imOperator) CreateUser(user *User) (*User, error) { user.Username = strings.TrimSpace(user.Username) user.Email = strings.TrimSpace(user.Email) user.Password = strings.TrimSpace(user.Password) user.Description = strings.TrimSpace(user.Description) - existed, err := im.DescribeUser(user) + existed, err := im.DescribeUser(user.Username) if err != nil { klog.Errorln(err) @@ -677,7 +391,7 @@ func (im *imOperator) CreateUser(user *User) (*User, error) { } if existed != nil { - return nil, ldap.NewError(ldap.LDAPResultEntryAlreadyExists, errors.New("username or email already exists")) + return nil, UserAlreadyExists } uidNumber := im.uidNumberNext() @@ -716,442 +430,14 @@ func (im *imOperator) CreateUser(user *User) (*User, error) { return user, nil } -func (im *imOperator) UpdateUser(user *User) (*User, error) { - - client, err := clientset.ClientSets().Ldap() - if err != nil { - return nil, err - } - conn, err := im.ldap.NewConn() - if err != nil { - return nil, err - } - defer conn.Close() - - dn := fmt.Sprintf("uid=%s,%s", user.Username, im.ldap.UserSearchBase()) - userModifyRequest := ldap.NewModifyRequest(dn, nil) - if user.Email != "" { - userSearchRequest := ldap.NewSearchRequest( - im.ldap.UserSearchBase(), - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - fmt.Sprintf("(&(objectClass=inetOrgPerson)(mail=%s))", user.Email), - []string{"uid", "mail"}, - nil, - ) - result, err := conn.Search(userSearchRequest) - if err != nil { - klog.Error(err) - return nil, err - } - if len(result.Entries) > 1 { - err = ldap.NewError(ldap.ErrorDebugging, fmt.Errorf("email is duplicated: %s", user.Email)) - klog.Error(err) - return nil, err - } - if len(result.Entries) == 1 && result.Entries[0].GetAttributeValue("uid") != user.Username { - err = ldap.NewError(ldap.LDAPResultEntryAlreadyExists, fmt.Errorf("email is duplicated: %s", user.Email)) - klog.Error(err) - return nil, err - } - userModifyRequest.Replace("mail", []string{user.Email}) - } - if user.Description != "" { - userModifyRequest.Replace("description", []string{user.Description}) - } - - if user.Lang != "" { - userModifyRequest.Replace("preferredLanguage", []string{user.Lang}) - } - - if user.Password != "" { - userModifyRequest.Replace("userPassword", []string{user.Password}) - } - - err = conn.Modify(userModifyRequest) - - if err != nil { - klog.Error(err) - return nil, err - } - - if user.ClusterRole != "" { - err = CreateClusterRoleBinding(user.Username, user.ClusterRole) - - if err != nil { - klog.Errorln(err) - return nil, err - } - } - - // clear auth failed record - if user.Password != "" { - redisClient, err := clientset.ClientSets().Redis() - if err != nil { - return nil, err - } - - records, err := im.redis.Keys(fmt.Sprintf("kubesphere:authfailed:%s:*", user.Username)).Result() - - if err == nil { - im.redis.Del(records...) - } - } - - return GetUserInfo(user.Username) -} -func (im *imOperator) DeleteGroup(path string) error { - - client, err := clientset.ClientSets().Ldap() - if err != nil { - return err - } - conn, err := im.ldap.NewConn() - if err != nil { - return err - } - defer conn.Close() - - searchBase, cn := splitPath(path) - - groupDeleteRequest := ldap.NewDelRequest(fmt.Sprintf("cn=%s,%s", cn, searchBase), nil) - err = conn.Del(groupDeleteRequest) - - if err != nil { - klog.Errorln("delete user group", err) - return err - } - - return nil -} - -func (im *imOperator) CreateGroup(group *models.Group) (*models.Group, error) { - - client, err := clientset.ClientSets().Ldap() - if err != nil { - return nil, err - } - conn, err := im.ldap.NewConn() - if err != nil { - return nil, err - } - defer conn.Close() - - maxGid, err := getMaxGid() - - if err != nil { - klog.Errorln("get max gid", err) - return nil, err - } - - maxGid += 1 - - if group.Path == "" { - group.Path = group.Name - } - - searchBase, cn := splitPath(group.Path) - - groupCreateRequest := ldap.NewAddRequest(fmt.Sprintf("cn=%s,%s", cn, searchBase), nil) - groupCreateRequest.Attribute("objectClass", []string{"posixGroup", "top"}) - groupCreateRequest.Attribute("cn", []string{cn}) - groupCreateRequest.Attribute("gidNumber", []string{strconv.Itoa(maxGid)}) - - if group.Description != "" { - groupCreateRequest.Attribute("description", []string{group.Description}) - } - - if group.Members != nil { - groupCreateRequest.Attribute("memberUid", group.Members) - } - - err = conn.Add(groupCreateRequest) - - if err != nil { - klog.Errorln("create group", err) - return nil, err - } - - group.Gid = strconv.Itoa(maxGid) - - return DescribeGroup(group.Path) -} - -func (im *imOperator) UpdateGroup(group *models.Group) (*models.Group, error) { - - client, err := clientset.ClientSets().Ldap() - if err != nil { - return nil, err - } - conn, err := im.ldap.NewConn() - if err != nil { - return nil, err - } - defer conn.Close() - - old, err := DescribeGroup(group.Path) - - if err != nil { - return nil, err - } - - searchBase, cn := splitPath(group.Path) - - groupUpdateRequest := ldap.NewModifyRequest(fmt.Sprintf("cn=%s,%s", cn, searchBase), nil) - - if old.Description == "" { - if group.Description != "" { - groupUpdateRequest.Add("description", []string{group.Description}) - } - } else { - if group.Description != "" { - groupUpdateRequest.Replace("description", []string{group.Description}) - } else { - groupUpdateRequest.Delete("description", []string{}) - } - } - - if group.Members != nil { - groupUpdateRequest.Replace("memberUid", group.Members) - } - - err = conn.Modify(groupUpdateRequest) - - if err != nil { - klog.Errorln("update group", err) - return nil, err - } - - return group, nil -} - -func (im *imOperator) ChildList(path string) ([]models.Group, error) { - - client, err := clientset.ClientSets().Ldap() - if err != nil { - return nil, err - } - conn, err := im.ldap.NewConn() - if err != nil { - return nil, err - } - defer conn.Close() - - var groupSearchRequest *ldap.SearchRequest - if path == "" { - groupSearchRequest = ldap.NewSearchRequest(client.GroupSearchBase(), - ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false, - "(&(objectClass=posixGroup))", - []string{"cn", "gidNumber", "memberUid", "description"}, - nil) - } else { - searchBase, cn := splitPath(path) - groupSearchRequest = ldap.NewSearchRequest(fmt.Sprintf("cn=%s,%s", cn, searchBase), - ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false, - "(&(objectClass=posixGroup))", - []string{"cn", "gidNumber", "memberUid", "description"}, - nil) - } - - result, err := conn.Search(groupSearchRequest) - - if err != nil { - return nil, err - } - - groups := make([]models.Group, 0) - - for _, v := range result.Entries { - dn := v.DN - cn := v.GetAttributeValue("cn") - gid := v.GetAttributeValue("gidNumber") - members := v.GetAttributeValues("memberUid") - description := v.GetAttributeValue("description") - - group := models.Group{Path: convertDNToPath(dn), Name: cn, Gid: gid, Members: members, Description: description} - - childSearchRequest := ldap.NewSearchRequest(dn, - ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false, - "(&(objectClass=posixGroup))", - []string{""}, - nil) - - result, err = conn.Search(childSearchRequest) - - if err != nil { - return nil, err - } - - childGroups := make([]string, 0) - - for _, v := range result.Entries { - child := convertDNToPath(v.DN) - childGroups = append(childGroups, child) - } - - group.ChildGroups = childGroups - - groups = append(groups, group) - } - - return groups, nil -} - -func (im *imOperator) DescribeGroup(path string) (*models.Group, error) { - client, err := clientset.ClientSets().Ldap() - if err != nil { - return nil, err - } - conn, err := im.ldap.NewConn() - if err != nil { - return nil, err - } - defer conn.Close() - - searchBase, cn := splitPath(path) - - groupSearchRequest := ldap.NewSearchRequest(searchBase, - ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false, - fmt.Sprintf("(&(objectClass=posixGroup)(cn=%s))", cn), - []string{"cn", "gidNumber", "memberUid", "description"}, - nil) - - result, err := conn.Search(groupSearchRequest) - - if err != nil { - klog.Errorln("search group", err) - return nil, err - } - - if len(result.Entries) != 1 { - return nil, ldap.NewError(ldap.LDAPResultNoSuchObject, fmt.Errorf("group %s does not exist", path)) - } - - dn := result.Entries[0].DN - cn = result.Entries[0].GetAttributeValue("cn") - gid := result.Entries[0].GetAttributeValue("gidNumber") - members := result.Entries[0].GetAttributeValues("memberUid") - description := result.Entries[0].GetAttributeValue("description") - - group := models.Group{Path: convertDNToPath(dn), Name: cn, Gid: gid, Members: members, Description: description} - - childGroups := make([]string, 0) - - group.ChildGroups = childGroups - - return &group, nil - -} - -func (im *imOperator) WorkspaceUsersTotalCount(workspace string) (int, error) { - workspaceRoleBindings, err := GetWorkspaceRoleBindings(workspace) - - if err != nil { - return 0, err - } - - users := make([]string, 0) - - for _, roleBinding := range workspaceRoleBindings { - for _, subject := range roleBinding.Subjects { - if subject.Kind == rbacv1.UserKind && !k8sutil.ContainsUser(users, subject.Name) { - users = append(users, subject.Name) - } - } - } - - return len(users), nil -} - -func (im *imOperator) ListWorkspaceUsers(workspace string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) { - - workspaceRoleBindings, err := GetWorkspaceRoleBindings(workspace) - - if err != nil { - return nil, err - } - - users := make([]*User, 0) - - for _, roleBinding := range workspaceRoleBindings { - for _, subject := range roleBinding.Subjects { - if subject.Kind == rbacv1.UserKind && !k8sutil.ContainsUser(users, subject.Name) { - user, err := GetUserInfo(subject.Name) - if err != nil { - return nil, err - } - prefix := fmt.Sprintf("workspace:%s:", workspace) - user.WorkspaceRole = fmt.Sprintf("workspace-%s", strings.TrimPrefix(roleBinding.Name, prefix)) - if matchConditions(conditions, user) { - users = append(users, user) - } - } - } - } - - // order & reverse - sort.Slice(users, func(i, j int) bool { - if reverse { - tmp := i - i = j - j = tmp - } - switch orderBy { - default: - fallthrough - case "name": - return strings.Compare(users[i].Username, users[j].Username) <= 0 - } - }) - - result := make([]interface{}, 0) - - for i, d := range users { - if i >= offset && (limit == -1 || len(result) < limit) { - result = append(result, d) - } - } - - return &models.PageableResponse{Items: result, TotalCount: len(users)}, nil -} - func (im *imOperator) uidNumberNext() int { + // TODO fix me return 0 } - -func matchConditions(conditions *params.Conditions, user *User) bool { - for k, v := range conditions.Match { - switch k { - case "keyword": - if !strings.Contains(user.Username, v) && - !strings.Contains(user.Email, v) && - !strings.Contains(user.Description, v) { - return false - } - case "name": - names := strings.Split(v, "|") - if !sliceutil.HasString(names, user.Username) { - return false - } - case "email": - email := strings.Split(v, "|") - if !sliceutil.HasString(email, user.Email) { - return false - } - case "role": - if user.WorkspaceRole != v { - return false - } - } - } - return true +func (im *imOperator) GetUserRoles(username string) ([]*rbacv1.Role, error) { + panic("implement me") } -type User struct { - Username string `json:"username"` - Email string `json:"email"` - Lang string `json:"lang,omitempty"` - Description string `json:"description"` - CreateTime time.Time `json:"create_time"` - Groups []string `json:"groups,omitempty"` - Password string `json:"password,omitempty"` +func (im *imOperator) GetUserRole(namespace string, username string) (*rbacv1.Role, error) { + panic("implement me") } diff --git a/pkg/models/iam/im_test.go b/pkg/models/iam/im_test.go new file mode 100644 index 000000000..b0ac18769 --- /dev/null +++ b/pkg/models/iam/im_test.go @@ -0,0 +1,55 @@ +/* + * + * Copyright 2020 The KubeSphere 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 iam + +import ( + "github.com/golang/mock/gomock" + "kubesphere.io/kubesphere/pkg/simple/client/ldap" + "testing" +) + +func TestIMOperator(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + ldappool, err := ldap.NewMockClient(&ldap.Options{ + Host: "192.168.0.7:30389", + ManagerDN: "cn=admin,dc=kubesphere,dc=io", + ManagerPassword: "admin", + UserSearchBase: "ou=Users,dc=kubesphere,dc=io", + GroupSearchBase: "ou=Groups,dc=kubesphere,dc=io", + InitialCap: 8, + MaxCap: 64, + }, ctrl, func(client *ldap.MockClient) { + client.EXPECT().Search(gomock.Any()).AnyTimes() + }) + + if err != nil { + t.Fatal(err) + } + + defer ldappool.Close() + + im := NewIMOperator(ldappool, Config{}) + + err = im.Init() + + if err != nil { + t.Fatal(err) + } +} diff --git a/pkg/models/iam/types.go b/pkg/models/iam/types.go index d70f8b31c..6af94c46d 100644 --- a/pkg/models/iam/types.go +++ b/pkg/models/iam/types.go @@ -28,6 +28,16 @@ const ( KindTokenReview = "TokenReview" ) +type User struct { + Username string `json:"username"` + Email string `json:"email"` + Lang string `json:"lang,omitempty"` + Description string `json:"description"` + CreateTime time.Time `json:"create_time"` + Groups []string `json:"groups,omitempty"` + Password string `json:"password,omitempty"` +} + type Action struct { Name string `json:"name"` Rules []v1.PolicyRule `json:"rules"` @@ -47,15 +57,3 @@ type RoleList struct { ClusterRoles []*v1.ClusterRole `json:"clusterRole" description:"cluster role list"` Roles []*v1.Role `json:"roles" description:"role list"` } - -type Config struct { - adminEmail string - adminPassword string - authRateLimit string - maxAuthFailed int - authTimeInterval time.Duration - tokenIdleTimeout time.Duration - userInitFile string - enableMultiLogin bool - generateKubeConfig bool -} diff --git a/pkg/models/openpitrix/applications_test.go b/pkg/models/openpitrix/applications_test.go index 37dae83f0..0dff68d0c 100644 --- a/pkg/models/openpitrix/applications_test.go +++ b/pkg/models/openpitrix/applications_test.go @@ -81,7 +81,6 @@ func TestApplicationOperator_CreateApplication(t *testing.T) { defer ctrl.Finish() for _, test := range tests { - op := openpitrix.NewMockClient(ctrl) objs := namespacesToRuntimeObjects(test.existNamespaces...) k8s := fake.NewSimpleClientset(objs...) diff --git a/pkg/models/tenant/namespaces.go b/pkg/models/tenant/namespaces.go index e49225292..71e2f5986 100644 --- a/pkg/models/tenant/namespaces.go +++ b/pkg/models/tenant/namespaces.go @@ -41,6 +41,7 @@ type NamespaceInterface interface { type namespaceSearcher struct { k8s kubernetes.Interface informers k8sinformers.SharedInformerFactory + am iam.AccessManagementInterface } func (s *namespaceSearcher) CreateNamespace(workspace string, namespace *v1.Namespace, username string) (*v1.Namespace, error) { @@ -56,8 +57,8 @@ func (s *namespaceSearcher) CreateNamespace(workspace string, namespace *v1.Name return s.k8s.CoreV1().Namespaces().Create(namespace) } -func newNamespaceOperator(k8s kubernetes.Interface, informers k8sinformers.SharedInformerFactory) NamespaceInterface { - return &namespaceSearcher{k8s: k8s, informers: informers} +func newNamespaceOperator(k8s kubernetes.Interface, informers k8sinformers.SharedInformerFactory, am iam.AccessManagementInterface) NamespaceInterface { + return &namespaceSearcher{k8s: k8s, informers: informers, am: am} } func (s *namespaceSearcher) match(match map[string]string, item *v1.Namespace) bool { @@ -111,7 +112,7 @@ func (s *namespaceSearcher) compare(a, b *v1.Namespace, orderBy string) bool { func (s *namespaceSearcher) GetNamespaces(username string) ([]*v1.Namespace, error) { - roles, err := iam.GetUserRoles("", username) + roles, err := s.am.GetRoles("", username) if err != nil { return nil, err @@ -143,7 +144,7 @@ func containsNamespace(namespaces []*v1.Namespace, namespace *v1.Namespace) bool func (s *namespaceSearcher) Search(username string, conditions *params.Conditions, orderBy string, reverse bool) ([]*v1.Namespace, error) { - rules, err := iam.GetUserClusterRules(username) + rules, err := s.am.GetClusterPolicyRules(username) if err != nil { return nil, err diff --git a/pkg/models/tenant/tenant.go b/pkg/models/tenant/tenant.go index 14e13bf9a..200fa23f9 100644 --- a/pkg/models/tenant/tenant.go +++ b/pkg/models/tenant/tenant.go @@ -19,12 +19,16 @@ package tenant import ( "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" k8sinformers "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" + "k8s.io/klog" "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" ksinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/models" + "kubesphere.io/kubesphere/pkg/models/iam" "kubesphere.io/kubesphere/pkg/server/params" "kubesphere.io/kubesphere/pkg/simple/client/mysql" "strconv" @@ -36,14 +40,35 @@ type Interface interface { DescribeWorkspace(username, workspace string) (*v1alpha1.Workspace, error) ListWorkspaces(username string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) ListNamespaces(username string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) - GetWorkspace(workspace string) (*v1alpha1.Workspace, error) - DevOpsProjectOperator + ListDevopsProjects(username string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error) + GetWorkspaceSimpleRules(workspace, username string) ([]iam.SimpleRule, error) + GetNamespaceSimpleRules(namespace, username string) ([]iam.SimpleRule, error) + CountDevOpsProjects(username string) (uint32, error) + DeleteDevOpsProject(username, projectId string) error + GetUserDevopsSimpleRules(username string, devops string) (interface{}, error) } type tenantOperator struct { workspaces WorkspaceInterface namespaces NamespaceInterface - DevOpsProjectOperator + am iam.AccessManagementInterface + devops DevOpsProjectOperator +} + +func (t *tenantOperator) CountDevOpsProjects(username string) (uint32, error) { + return t.devops.GetDevOpsProjectsCount(username) +} + +func (t *tenantOperator) DeleteDevOpsProject(username, projectId string) error { + return t.devops.DeleteDevOpsProject(projectId, username) +} + +func (t *tenantOperator) GetUserDevopsSimpleRules(username string, projectId string) (interface{}, error) { + return t.devops.GetUserDevOpsSimpleRules(username, projectId) +} + +func (t *tenantOperator) ListDevopsProjects(username string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error) { + return t.devops.ListDevOpsProjects(conditions.Match["workspace"], username, conditions, orderBy, reverse, limit, offset) } func (t *tenantOperator) DeleteNamespace(workspace, namespace string) error { @@ -51,9 +76,11 @@ func (t *tenantOperator) DeleteNamespace(workspace, namespace string) error { } func New(client kubernetes.Interface, informers k8sinformers.SharedInformerFactory, ksinformers ksinformers.SharedInformerFactory, db *mysql.Database) Interface { + amOperator := iam.NewAMOperator(informers) return &tenantOperator{ - workspaces: newWorkspaceOperator(client, informers, ksinformers, db), - namespaces: newNamespaceOperator(client, informers), + workspaces: newWorkspaceOperator(client, informers, ksinformers, amOperator, db), + namespaces: newNamespaceOperator(client, informers, amOperator), + am: amOperator, } } @@ -95,17 +122,69 @@ func (t *tenantOperator) ListWorkspaces(username string, conditions *params.Cond return &models.PageableResponse{Items: result, TotalCount: len(workspaces)}, nil } +func (t *tenantOperator) GetWorkspaceSimpleRules(workspace, username string) ([]iam.SimpleRule, error) { + clusterRules, err := t.am.GetClusterPolicyRules(username) + if err != nil { + return nil, err + } + + // cluster-admin + if iam.RulesMatchesRequired(clusterRules, rbacv1.PolicyRule{ + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }) { + return t.am.GetWorkspaceRoleSimpleRules(workspace, constants.WorkspaceAdmin), nil + } + + workspaceRole, err := t.am.GetWorkspaceRole(workspace, username) + + // workspaces-manager + if iam.RulesMatchesRequired(clusterRules, rbacv1.PolicyRule{ + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"workspaces", "workspaces/*"}, + }) { + return t.am.GetWorkspaceRoleSimpleRules(workspace, constants.WorkspacesManager), nil + } + + if err != nil { + if apierrors.IsNotFound(err) { + return []iam.SimpleRule{}, nil + } + + klog.Error(err) + return nil, err + } + + return t.am.GetWorkspaceRoleSimpleRules(workspace, workspaceRole.Annotations[constants.DisplayNameAnnotationKey]), nil +} + +func (t *tenantOperator) GetNamespaceSimpleRules(namespace, username string) ([]iam.SimpleRule, error) { + clusterRules, err := t.am.GetClusterPolicyRules(username) + if err != nil { + return nil, err + } + rules, err := t.am.GetPolicyRules(namespace, username) + if err != nil { + return nil, err + } + rules = append(rules, clusterRules...) + + return iam.ConvertToSimpleRule(rules), nil +} + func (t *tenantOperator) appendAnnotations(username string, workspace *v1alpha1.Workspace) *v1alpha1.Workspace { workspace = workspace.DeepCopy() if workspace.Annotations == nil { workspace.Annotations = make(map[string]string) } - ns, err := t.ListNamespaces(username, ¶ms.Conditions{Match: map[string]string{constants.WorkspaceLabelKey: workspace.Name}}, "", false, 1, 0) - if err == nil { + + if ns, err := t.ListNamespaces(username, ¶ms.Conditions{Match: map[string]string{constants.WorkspaceLabelKey: workspace.Name}}, "", false, 1, 0); err == nil { workspace.Annotations["kubesphere.io/namespace-count"] = strconv.Itoa(ns.TotalCount) } - devops, err := t.ListDevOpsProjects(workspace.Name, username, ¶ms.Conditions{}, "", false, 1, 0) - if err == nil { + + if devops, err := t.ListDevopsProjects(username, ¶ms.Conditions{Match: map[string]string{"workspace": workspace.Name}}, "", false, 1, 0); err == nil { workspace.Annotations["kubesphere.io/devops-count"] = strconv.Itoa(devops.TotalCount) } @@ -135,7 +214,3 @@ func (t *tenantOperator) ListNamespaces(username string, conditions *params.Cond return &models.PageableResponse{Items: result, TotalCount: len(namespaces)}, nil } - -func (t *tenantOperator) GetWorkspace(workspace string) (*v1alpha1.Workspace, error) { - return t.workspaces.GetWorkspace(workspace) -} diff --git a/pkg/models/tenant/workspaces.go b/pkg/models/tenant/workspaces.go index a08f84c84..08ddb70d0 100644 --- a/pkg/models/tenant/workspaces.go +++ b/pkg/models/tenant/workspaces.go @@ -46,13 +46,18 @@ import ( "k8s.io/apimachinery/pkg/labels" ) +type InWorkspaceUser struct { + *iam.User + WorkspaceRole string `json:"workspaceRole"` +} + type WorkspaceInterface interface { GetWorkspace(workspace string) (*v1alpha1.Workspace, error) SearchWorkspace(username string, conditions *params.Conditions, orderBy string, reverse bool) ([]*v1alpha1.Workspace, error) ListNamespaces(workspace string) ([]*core.Namespace, error) DeleteNamespace(workspace, namespace string) error RemoveUser(user, workspace string) error - AddUser(workspace string, user *iam.User) error + AddUser(workspace string, user *InWorkspaceUser) error CountDevopsProjectsInWorkspace(workspace string) (int, error) CountUsersInWorkspace(workspace string) (int, error) CountOrgRoles() (int, error) @@ -64,17 +69,19 @@ type workspaceOperator struct { client kubernetes.Interface informers informers.SharedInformerFactory ksInformers externalversions.SharedInformerFactory + am iam.AccessManagementInterface // TODO: use db interface instead of mysql client // we can refactor this after rewrite devops using crd db *mysql.Database } -func newWorkspaceOperator(client kubernetes.Interface, informers informers.SharedInformerFactory, ksinformers externalversions.SharedInformerFactory, db *mysql.Database) WorkspaceInterface { +func newWorkspaceOperator(client kubernetes.Interface, informers informers.SharedInformerFactory, ksinformers externalversions.SharedInformerFactory, am iam.AccessManagementInterface, db *mysql.Database) WorkspaceInterface { return &workspaceOperator{ client: client, informers: informers, ksInformers: ksinformers, + am: am, db: db, } } @@ -104,7 +111,7 @@ func (w *workspaceOperator) DeleteNamespace(workspace string, namespace string) } func (w *workspaceOperator) RemoveUser(workspace string, username string) error { - workspaceRole, err := iam.GetUserWorkspaceRole(workspace, username) + workspaceRole, err := w.am.GetWorkspaceRole(workspace, username) if err != nil { return err } @@ -117,9 +124,9 @@ func (w *workspaceOperator) RemoveUser(workspace string, username string) error return nil } -func (w *workspaceOperator) AddUser(workspaceName string, user *iam.User) error { +func (w *workspaceOperator) AddUser(workspaceName string, user *InWorkspaceUser) error { - workspaceRole, err := iam.GetUserWorkspaceRole(workspaceName, user.Username) + workspaceRole, err := w.am.GetWorkspaceRole(workspaceName, user.Username) if err != nil && !apierrors.IsNotFound(err) { klog.Errorf("get workspace role failed: %+v", err) @@ -215,7 +222,7 @@ func (w *workspaceOperator) CountDevopsProjectsInWorkspace(workspaceName string) } func (w *workspaceOperator) CountUsersInWorkspace(workspace string) (int, error) { - count, err := iam.WorkspaceUsersTotalCount(workspace) + count, err := w.CountUsersInWorkspace(workspace) if err != nil { return 0, err } @@ -285,7 +292,7 @@ func (*workspaceOperator) compare(a, b *v1alpha1.Workspace, orderBy string) bool } func (w *workspaceOperator) SearchWorkspace(username string, conditions *params.Conditions, orderBy string, reverse bool) ([]*v1alpha1.Workspace, error) { - rules, err := iam.GetUserClusterRules(username) + rules, err := w.am.GetClusterPolicyRules(username) if err != nil { return nil, err @@ -299,7 +306,7 @@ func (w *workspaceOperator) SearchWorkspace(username string, conditions *params. return nil, err } } else { - workspaceRoles, err := iam.GetUserWorkspaceRoleMap(username) + workspaceRoles, err := w.am.GetWorkspaceRoleMap(username) if err != nil { return nil, err } diff --git a/pkg/server/config/config.go b/pkg/server/config/config.go index 0db2c2c3e..7629f0506 100644 --- a/pkg/server/config/config.go +++ b/pkg/server/config/config.go @@ -186,10 +186,10 @@ func newConfig() *Config { SonarQubeOptions: sonarqube.NewSonarQubeOptions(), KubernetesOptions: k8s.NewKubernetesOptions(), ServiceMeshOptions: servicemesh.NewServiceMeshOptions(), - LdapOptions: ldap.NewLdapOptions(), + LdapOptions: ldap.NewOptions(), RedisOptions: cache.NewRedisOptions(), S3Options: s3.NewS3Options(), - OpenPitrixOptions: openpitrix.NewOpenPitrixOptions(), + OpenPitrixOptions: openpitrix.NewOptions(), MonitoringOptions: prometheus.NewPrometheusOptions(), KubeSphereOptions: kubesphere.NewKubeSphereOptions(), AlertingOptions: alerting.NewAlertingOptions(), diff --git a/pkg/server/errors/errors.go b/pkg/server/errors/errors.go index b89f315bc..08a0c19e2 100644 --- a/pkg/server/errors/errors.go +++ b/pkg/server/errors/errors.go @@ -18,8 +18,7 @@ package errors import ( - "github.com/emicklei/go-restful" - "net/http" + "fmt" ) type Error struct { @@ -28,24 +27,16 @@ type Error struct { var None = Error{Message: "success"} -func (e *Error) Error() string { +func (e Error) Error() string { return e.Message } -func Wrap(err error) Error { +func Wrap(err error) error { return Error{Message: err.Error()} } -func New(message string) Error { - return Error{Message: message} -} - -func ParseSvcErr(err error, resp *restful.Response) { - if svcErr, ok := err.(restful.ServiceError); ok { - resp.WriteServiceError(svcErr.Code, svcErr) - } else { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, Wrap(err)) - } +func New(format string, args ...interface{}) error { + return Error{Message: fmt.Sprintf(format, args...)} } func GetServiceErrorCode(err error) int { diff --git a/pkg/simple/client/factory.go b/pkg/simple/client/factory.go index d19325ea2..33b34ba64 100644 --- a/pkg/simple/client/factory.go +++ b/pkg/simple/client/factory.go @@ -38,11 +38,11 @@ func NewClientSetOptions() *ClientSetOptions { mySQLOptions: mysql.NewMySQLOptions(), redisOptions: cache.NewRedisOptions(), kubernetesOptions: k8s.NewKubernetesOptions(), - ldapOptions: ldap.NewLdapOptions(), + ldapOptions: ldap.NewOptions(), devopsOptions: jenkins.NewDevopsOptions(), sonarqubeOptions: sonarqube.NewSonarQubeOptions(), s3Options: s3.NewS3Options(), - openPitrixOptions: openpitrix.NewOpenPitrixOptions(), + openPitrixOptions: openpitrix.NewOptions(), prometheusOptions: prometheus.NewPrometheusOptions(), kubesphereOptions: kubesphere.NewKubeSphereOptions(), elasticSearhOptions: esclient.NewElasticSearchOptions(), @@ -114,7 +114,7 @@ type ClientSet struct { mySQLClient *mysql.Client k8sClient k8s.Client - ldapClient *ldap.Client + ldapClient ldap.Client devopsClient *jenkins.Client sonarQubeClient *sonarqube.Client redisClient cache.Interface @@ -242,7 +242,7 @@ func (cs *ClientSet) SonarQube() (*sonarqube.Client, error) { } } -func (cs *ClientSet) Ldap() (*ldap.Client, error) { +func (cs *ClientSet) Ldap() (ldap.Client, error) { var err error if cs.csoptions.ldapOptions == nil || cs.csoptions.ldapOptions.Host == "" { @@ -256,7 +256,7 @@ func (cs *ClientSet) Ldap() (*ldap.Client, error) { defer mutex.Unlock() if cs.ldapClient == nil { - cs.ldapClient, err = ldap.NewLdapClient(cs.csoptions.ldapOptions, cs.stopCh) + cs.ldapClient, err = ldap.NewClient(cs.csoptions.ldapOptions, cs.stopCh) if err != nil { return nil, err } @@ -311,7 +311,7 @@ func (cs *ClientSet) OpenPitrix() (openpitrix.Client, error) { if cs.openpitrixClient != nil { return cs.openpitrixClient, nil } else { - cs.openpitrixClient, err = openpitrix.NewOpenPitrixClient(cs.csoptions.openPitrixOptions) + cs.openpitrixClient, err = openpitrix.NewClient(cs.csoptions.openPitrixOptions) if err != nil { return nil, err } diff --git a/pkg/simple/client/ldap/channel.go b/pkg/simple/client/ldap/channel.go index 1c49cb826..04f72530f 100644 --- a/pkg/simple/client/ldap/channel.go +++ b/pkg/simple/client/ldap/channel.go @@ -25,7 +25,7 @@ type channelPool struct { // PoolFactory is a function to create new connections. type PoolFactory func(string) (ldap.Client, error) -// NewChannelPool returns a new pool based on buffered channels with an initial +// newChannelPool returns a new pool based on buffered channels with an initial // capacity and maximum capacity. Factory is used when initial capacity is // greater than zero to fill the pool. A zero initialCap doesn't fill the Pool // until a new Get() is called. During a Get(), If there is no new connection @@ -36,7 +36,7 @@ type PoolFactory func(string) (ldap.Client, error) // of the call is one of those passed, most likely you want to set this to something // like // []uint8{ldap.LDAPResultTimeLimitExceeded, ldap.ErrorNetwork} -func NewChannelPool(initialCap, maxCap int, name string, factory PoolFactory, closeAt []uint16) (Pool, error) { +func newChannelPool(initialCap, maxCap int, name string, factory PoolFactory, closeAt []uint16) (Pool, error) { if initialCap < 0 || maxCap <= 0 || initialCap > maxCap { return nil, errors.New("invalid capacity settings") } @@ -85,7 +85,7 @@ func (c *channelPool) Get() (*PoolConn, error) { return nil, ErrClosed } - // wrap our connections with our ldap.PoolClient implementation (wrapConn + // wrap our connections with our ldap.Client implementation (wrapConn // method) that puts the connection back to the pool if it's closed. select { case conn := <-conns: diff --git a/pkg/simple/client/ldap/conn.go b/pkg/simple/client/ldap/conn.go index c393a6a71..c10d3b90c 100644 --- a/pkg/simple/client/ldap/conn.go +++ b/pkg/simple/client/ldap/conn.go @@ -8,7 +8,7 @@ import ( "github.com/go-ldap/ldap" ) -// PoolConn implements PoolClient to override the Close() method +// PoolConn implements Client to override the Close() method type PoolConn struct { Conn ldap.Client c *channelPool diff --git a/pkg/simple/client/ldap/ldap.go b/pkg/simple/client/ldap/ldap.go index 15ffc7cf2..9fd6f49c8 100644 --- a/pkg/simple/client/ldap/ldap.go +++ b/pkg/simple/client/ldap/ldap.go @@ -20,11 +20,13 @@ package ldap import ( "fmt" "github.com/go-ldap/ldap" + "github.com/golang/mock/gomock" "k8s.io/klog" ) type Client interface { NewConn() (ldap.Client, error) + Close() GroupSearchBase() string UserSearchBase() string } @@ -35,8 +37,8 @@ type poolClient struct { } // panic if cannot connect to ldap service -func NewLdapClient(options *Options, stopCh <-chan struct{}) (Client, error) { - pool, err := NewChannelPool(8, 64, "kubesphere", func(s string) (ldap.Client, error) { +func NewClient(options *Options, stopCh <-chan struct{}) (Client, error) { + pool, err := newChannelPool(options.InitialCap, options.MaxCap, options.PoolName, func(s string) (ldap.Client, error) { conn, err := ldap.Dial("tcp", options.Host) if err != nil { return nil, err @@ -46,7 +48,6 @@ func NewLdapClient(options *Options, stopCh <-chan struct{}) (Client, error) { if err != nil { klog.Error(err) - pool.Close() return nil, err } @@ -57,13 +58,16 @@ func NewLdapClient(options *Options, stopCh <-chan struct{}) (Client, error) { go func() { <-stopCh - if client.pool != nil { - client.pool.Close() - } + client.Close() }() return client, nil } +func (l *poolClient) Close() { + if l.pool != nil { + l.pool.Close() + } +} func (l *poolClient) NewConn() (ldap.Client, error) { if l.pool == nil { @@ -81,7 +85,7 @@ func (l *poolClient) NewConn() (ldap.Client, error) { err = conn.Bind(l.options.ManagerDN, l.options.ManagerPassword) if err != nil { conn.Close() - klog.Error(err) + klog.Errorln(err) return nil, err } return conn, nil @@ -94,3 +98,25 @@ func (l *poolClient) GroupSearchBase() string { func (l *poolClient) UserSearchBase() string { return l.options.UserSearchBase } + +func NewMockClient(options *Options, ctrl *gomock.Controller, setup func(client *MockClient)) (Client, error) { + pool, err := newChannelPool(options.InitialCap, options.MaxCap, options.PoolName, func(s string) (ldap.Client, error) { + conn := newMockClient(ctrl) + conn.EXPECT().Bind(gomock.Any(), gomock.Any()).AnyTimes() + conn.EXPECT().Close().AnyTimes() + setup(conn) + return conn, nil + }, []uint16{ldap.LDAPResultAdminLimitExceeded, ldap.ErrorNetwork}) + + if err != nil { + klog.Error(err) + return nil, err + } + + client := &poolClient{ + pool: pool, + options: options, + } + + return client, nil +} diff --git a/pkg/simple/client/ldap/mock.go b/pkg/simple/client/ldap/mock.go new file mode 100644 index 000000000..3477cfa84 --- /dev/null +++ b/pkg/simple/client/ldap/mock.go @@ -0,0 +1,231 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/go-ldap/ldap (interfaces: Client) + +// Package ldap is a generated GoMock package. +package ldap + +import ( + tls "crypto/tls" + ldap "github.com/go-ldap/ldap" + gomock "github.com/golang/mock/gomock" + reflect "reflect" + time "time" +) + +// MockClient is a mock of Client interface +type MockClient struct { + ctrl *gomock.Controller + recorder *MockClientMockRecorder +} + +// MockClientMockRecorder is the mock recorder for MockClient +type MockClientMockRecorder struct { + mock *MockClient +} + +// newMockClient creates a new mock instance +func newMockClient(ctrl *gomock.Controller) *MockClient { + mock := &MockClient{ctrl: ctrl} + mock.recorder = &MockClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockClient) EXPECT() *MockClientMockRecorder { + return m.recorder +} + +// Add mocks base method +func (m *MockClient) Add(arg0 *ldap.AddRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Add", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Add indicates an expected call of Add +func (mr *MockClientMockRecorder) Add(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockClient)(nil).Add), arg0) +} + +// Bind mocks base method +func (m *MockClient) Bind(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Bind", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Bind indicates an expected call of Bind +func (mr *MockClientMockRecorder) Bind(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bind", reflect.TypeOf((*MockClient)(nil).Bind), arg0, arg1) +} + +// Close mocks base method +func (m *MockClient) Close() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Close") +} + +// Close indicates an expected call of Close +func (mr *MockClientMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockClient)(nil).Close)) +} + +// Compare mocks base method +func (m *MockClient) Compare(arg0, arg1, arg2 string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Compare", arg0, arg1, arg2) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Compare indicates an expected call of Compare +func (mr *MockClientMockRecorder) Compare(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Compare", reflect.TypeOf((*MockClient)(nil).Compare), arg0, arg1, arg2) +} + +// Del mocks base method +func (m *MockClient) Del(arg0 *ldap.DelRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Del", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Del indicates an expected call of Del +func (mr *MockClientMockRecorder) Del(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Del", reflect.TypeOf((*MockClient)(nil).Del), arg0) +} + +// Modify mocks base method +func (m *MockClient) Modify(arg0 *ldap.ModifyRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Modify", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Modify indicates an expected call of Modify +func (mr *MockClientMockRecorder) Modify(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Modify", reflect.TypeOf((*MockClient)(nil).Modify), arg0) +} + +// ModifyDN mocks base method +func (m *MockClient) ModifyDN(arg0 *ldap.ModifyDNRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ModifyDN", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ModifyDN indicates an expected call of ModifyDN +func (mr *MockClientMockRecorder) ModifyDN(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ModifyDN", reflect.TypeOf((*MockClient)(nil).ModifyDN), arg0) +} + +// PasswordModify mocks base method +func (m *MockClient) PasswordModify(arg0 *ldap.PasswordModifyRequest) (*ldap.PasswordModifyResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PasswordModify", arg0) + ret0, _ := ret[0].(*ldap.PasswordModifyResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PasswordModify indicates an expected call of PasswordModify +func (mr *MockClientMockRecorder) PasswordModify(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordModify", reflect.TypeOf((*MockClient)(nil).PasswordModify), arg0) +} + +// Search mocks base method +func (m *MockClient) Search(arg0 *ldap.SearchRequest) (*ldap.SearchResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Search", arg0) + ret0, _ := ret[0].(*ldap.SearchResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Search indicates an expected call of Search +func (mr *MockClientMockRecorder) Search(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockClient)(nil).Search), arg0) +} + +// SearchWithPaging mocks base method +func (m *MockClient) SearchWithPaging(arg0 *ldap.SearchRequest, arg1 uint32) (*ldap.SearchResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SearchWithPaging", arg0, arg1) + ret0, _ := ret[0].(*ldap.SearchResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SearchWithPaging indicates an expected call of SearchWithPaging +func (mr *MockClientMockRecorder) SearchWithPaging(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchWithPaging", reflect.TypeOf((*MockClient)(nil).SearchWithPaging), arg0, arg1) +} + +// SetTimeout mocks base method +func (m *MockClient) SetTimeout(arg0 time.Duration) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetTimeout", arg0) +} + +// SetTimeout indicates an expected call of SetTimeout +func (mr *MockClientMockRecorder) SetTimeout(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTimeout", reflect.TypeOf((*MockClient)(nil).SetTimeout), arg0) +} + +// SimpleBind mocks base method +func (m *MockClient) SimpleBind(arg0 *ldap.SimpleBindRequest) (*ldap.SimpleBindResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SimpleBind", arg0) + ret0, _ := ret[0].(*ldap.SimpleBindResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SimpleBind indicates an expected call of SimpleBind +func (mr *MockClientMockRecorder) SimpleBind(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SimpleBind", reflect.TypeOf((*MockClient)(nil).SimpleBind), arg0) +} + +// Start mocks base method +func (m *MockClient) Start() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Start") +} + +// Start indicates an expected call of Start +func (mr *MockClientMockRecorder) Start() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockClient)(nil).Start)) +} + +// StartTLS mocks base method +func (m *MockClient) StartTLS(arg0 *tls.Config) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StartTLS", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// StartTLS indicates an expected call of StartTLS +func (mr *MockClientMockRecorder) StartTLS(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartTLS", reflect.TypeOf((*MockClient)(nil).StartTLS), arg0) +} diff --git a/pkg/simple/client/ldap/options.go b/pkg/simple/client/ldap/options.go index 9f2b9ed0c..2913cc3d7 100644 --- a/pkg/simple/client/ldap/options.go +++ b/pkg/simple/client/ldap/options.go @@ -11,11 +11,14 @@ type Options struct { ManagerPassword string `json:"managerPassword,omitempty" yaml:"managerPassword"` UserSearchBase string `json:"userSearchBase,omitempty" yaml:"userSearchBase"` GroupSearchBase string `json:"groupSearchBase,omitempty" yaml:"groupSearchBase"` + InitialCap int `json:"initialCap,omitempty" yaml:"initialCap"` + MaxCap int `json:"maxCap,omitempty" yaml:"maxCap"` + PoolName string `json:"poolName,omitempty" yaml:"poolName"` } -// NewLdapOptions return a default option +// NewOptions return a default option // which host field point to nowhere. -func NewLdapOptions() *Options { +func NewOptions() *Options { return &Options{ Host: "", ManagerDN: "cn=admin,dc=example,dc=org", @@ -25,7 +28,7 @@ func NewLdapOptions() *Options { } func (l *Options) Validate() []error { - errors := []error{} + var errors []error return errors } diff --git a/pkg/simple/client/openpitrix/openpitrix.go b/pkg/simple/client/openpitrix/openpitrix.go index b919c6ffc..28e497659 100644 --- a/pkg/simple/client/openpitrix/openpitrix.go +++ b/pkg/simple/client/openpitrix/openpitrix.go @@ -134,7 +134,7 @@ func newAppManagerClient(endpoint string) (pb.AppManagerClient, error) { return pb.NewAppManagerClient(conn), nil } -func NewOpenPitrixClient(options *Options) (Client, error) { +func NewClient(options *Options) (Client, error) { runtimeMangerClient, err := newRuntimeManagerClient(options.RuntimeManagerEndpoint) diff --git a/pkg/simple/client/openpitrix/options.go b/pkg/simple/client/openpitrix/options.go index ca18c2e36..62a5e7869 100644 --- a/pkg/simple/client/openpitrix/options.go +++ b/pkg/simple/client/openpitrix/options.go @@ -16,7 +16,7 @@ type Options struct { RepoIndexerEndpoint string `json:"repoIndexerEndpoint,omitempty" yaml:"repoIndexerEndpoint,omitempty"` } -func NewOpenPitrixOptions() *Options { +func NewOptions() *Options { return &Options{} } diff --git a/pkg/utils/jwtutil/jwt.go b/pkg/utils/jwtutil/jwt.go index fe95334b0..efc093c76 100644 --- a/pkg/utils/jwtutil/jwt.go +++ b/pkg/utils/jwtutil/jwt.go @@ -20,19 +20,26 @@ package jwtutil import ( "fmt" "github.com/dgrijalva/jwt-go" + "os" ) const secretEnv = "JWT_SECRET" -var secret []byte +var secretKey []byte -func Setup(key string) { - secret = []byte(key) +func init() { + if secret := os.Getenv(secretEnv); secret != "" { + Setup(secret) + } +} + +func Setup(secret string) { + secretKey = []byte(secret) } func MustSigned(claims jwt.MapClaims) string { uToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - token, err := uToken.SignedString(secret) + token, err := uToken.SignedString(secretKey) if err != nil { panic(err) } @@ -41,7 +48,7 @@ func MustSigned(claims jwt.MapClaims) string { func provideKey(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok { - return secret, nil + return secretKey, nil } else { return nil, fmt.Errorf("expect token signed with HMAC but got %v", token.Header["alg"]) }