From a8d5f552a016acd6578ead4563c385098ffaee49 Mon Sep 17 00:00:00 2001 From: hongming Date: Tue, 16 Oct 2018 18:52:10 +0800 Subject: [PATCH] add workspace api --- pkg/apis/v1alpha/install.go | 3 +- pkg/apis/v1alpha/workspaces/workspaces.go | 430 +++++++++++ pkg/app/app.go | 11 + pkg/constants/common.go | 15 + pkg/models/iam/iam.go | 314 +++++++- pkg/models/iam/policy.go | 105 ++- pkg/models/iam/tools.go | 161 ---- pkg/models/iam/types.go | 41 ++ pkg/models/workspaces/types.go | 46 ++ pkg/models/workspaces/workspaces.go | 859 ++++++++++++++++++++++ pkg/util/errors/errors.go | 20 + 11 files changed, 1822 insertions(+), 183 deletions(-) create mode 100644 pkg/apis/v1alpha/workspaces/workspaces.go delete mode 100644 pkg/models/iam/tools.go create mode 100644 pkg/models/iam/types.go create mode 100644 pkg/models/workspaces/types.go create mode 100644 pkg/models/workspaces/workspaces.go create mode 100644 pkg/util/errors/errors.go diff --git a/pkg/apis/v1alpha/install.go b/pkg/apis/v1alpha/install.go index eeedb44c7..876271882 100644 --- a/pkg/apis/v1alpha/install.go +++ b/pkg/apis/v1alpha/install.go @@ -39,6 +39,7 @@ import ( "kubesphere.io/kubesphere/pkg/apis/v1alpha/users" "kubesphere.io/kubesphere/pkg/apis/v1alpha/volumes" "kubesphere.io/kubesphere/pkg/apis/v1alpha/workloadstatus" + "kubesphere.io/kubesphere/pkg/apis/v1alpha/workspaces" _ "kubesphere.io/kubesphere/pkg/filter/container" ) @@ -67,7 +68,7 @@ func init() { statefulsets.Register(ws, "/namespaces/{namespace}/statefulsets/{statefulset}/revisions/{revision}") resources.Register(ws, "/resources") monitoring.Register(ws, "/monitoring") - + workspaces.Register(ws, "/workspaces") // add webservice to default container restful.Add(ws) diff --git a/pkg/apis/v1alpha/workspaces/workspaces.go b/pkg/apis/v1alpha/workspaces/workspaces.go new file mode 100644 index 000000000..7be20b7e4 --- /dev/null +++ b/pkg/apis/v1alpha/workspaces/workspaces.go @@ -0,0 +1,430 @@ +package workspaces + +import ( + "net/http" + + "github.com/emicklei/go-restful" + "k8s.io/api/core/v1" + + "fmt" + "strings" + + "k8s.io/kubernetes/pkg/util/slice" + + "kubesphere.io/kubesphere/pkg/constants" + "kubesphere.io/kubesphere/pkg/models/iam" + "kubesphere.io/kubesphere/pkg/models/workspaces" +) + +func Register(ws *restful.WebService, subPath string) { + ws.Route(ws.GET(subPath).To(WorkspaceListHandler)) + ws.Route(ws.POST(subPath).To(WorkspaceCreateHandler)) + ws.Route(ws.DELETE(subPath + "/{name}").To(DeleteWorkspaceHandler)) + ws.Route(ws.GET(subPath + "/{name}").To(WorkspaceDetailHandler)) + ws.Route(ws.PUT(subPath + "/{name}").To(WorkspaceEditHandler)) + ws.Route(ws.GET(subPath + "/{name}/namespaces").To(NamespaceHandler)) + ws.Route(ws.POST(subPath + "/{name}/namespaces").To(NamespaceCreateHandler)) + ws.Route(ws.DELETE(subPath + "/{name}/namespaces/{namespace}").To(NamespaceDeleteHandler)) + ws.Route(ws.GET(subPath + "/{name}/devops").To(DevOpsProjectHandler)) + ws.Route(ws.POST(subPath + "/{name}/devops").To(DevOpsProjectCreateHandler)) + ws.Route(ws.DELETE(subPath + "/{name}/devops/{id}").To(DevOpsProjectDeleteHandler)) + ws.Route(ws.GET(subPath + "/{name}/members").To(MembersHandler)) + ws.Route(ws.GET(subPath + "/{name}/members/{member}").To(MemberHandler)) + ws.Route(ws.GET(subPath + "/{name}/roles").To(RolesHandler)) + ws.Route(ws.GET(subPath + "/{name}/roles/{role}").To(RoleHandler)) + ws.Route(ws.POST(subPath + "/{name}/members").To(MembersInviteHandler)) + ws.Route(ws.DELETE(subPath + "/{name}/members").To(MembersRemoveHandler)) +} + +func RoleHandler(req *restful.Request, resp *restful.Response) { + workspaceName := req.PathParameter("name") + roleName := req.PathParameter("role") + + if !slice.ContainsString(workspaces.WorkSpaceRoles, roleName, nil) { + resp.WriteHeaderAndEntity(http.StatusNotFound, constants.MessageResponse{Message: fmt.Sprintf("role %s not found", roleName)}) + return + } + + role, rules, err := iam.WorkspaceRoleRules(workspaceName, roleName) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + users, err := iam.WorkspaceRoleUsers(workspaceName, roleName) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(map[string]interface{}{"role": role, "rules": rules, "users": users}) +} + +func RolesHandler(req *restful.Request, resp *restful.Response) { + + name := req.PathParameter("name") + + workspace, err := workspaces.Detail(name) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + roles, err := workspaces.Roles(workspace) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(roles) +} + +func MembersHandler(req *restful.Request, resp *restful.Response) { + workspace := req.PathParameter("name") + + users, err := workspaces.GetWorkspaceMembers(workspace) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(users) +} + +func MemberHandler(req *restful.Request, resp *restful.Response) { + workspace := req.PathParameter("name") + username := req.PathParameter("member") + + user, err := iam.GetUser(username) + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + namespaces, err := workspaces.Namespaces(workspace) + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + user.WorkspaceRole = user.WorkspaceRoles[workspace] + + roles := make(map[string]string) + + for _, namespace := range namespaces { + if role := user.Roles[namespace.Name]; role != "" { + roles[namespace.Name] = role + } + } + + user.Roles = roles + user.Rules = nil + user.WorkspaceRules = nil + user.WorkspaceRoles = nil + user.ClusterRules = nil + resp.WriteEntity(user) +} + +func MembersInviteHandler(req *restful.Request, resp *restful.Response) { + var users []workspaces.UserInvite + workspace := req.PathParameter("name") + err := req.ReadEntity(&users) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + err = workspaces.Invite(workspace, users) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteHeaderAndEntity(http.StatusOK, constants.MessageResponse{Message: "success"}) +} + +func MembersRemoveHandler(req *restful.Request, resp *restful.Response) { + query := req.QueryParameter("name") + workspace := req.PathParameter("name") + + names := strings.Split(query, ",") + + err := workspaces.RemoveMembers(workspace, names) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteHeaderAndEntity(http.StatusOK, constants.MessageResponse{Message: "success"}) +} + +func NamespaceDeleteHandler(req *restful.Request, resp *restful.Response) { + namespace := req.PathParameter("namespace") + workspace := req.PathParameter("name") + force := req.QueryParameter("force") + err := workspaces.UnBindNamespace(workspace, namespace) + + if err != nil && force != "true" { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + err = workspaces.DeleteNamespace(namespace) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteHeaderAndEntity(http.StatusOK, constants.MessageResponse{Message: "success"}) +} + +func DevOpsProjectDeleteHandler(req *restful.Request, resp *restful.Response) { + devops := req.PathParameter("id") + workspace := req.PathParameter("name") + force := req.QueryParameter("force") + username := req.HeaderParameter("X-Token-Username") + + err := workspaces.UnBindDevopsProject(workspace, devops) + + if err != nil && force != "true" { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + err = workspaces.DeleteDevopsProject(username, devops) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(constants.MessageResponse{Message: "success"}) +} + +func DevOpsProjectCreateHandler(req *restful.Request, resp *restful.Response) { + + workspace := req.PathParameter("name") + username := req.HeaderParameter("X-Token-Username") + + var devops workspaces.DevopsProject + + err := req.ReadEntity(&devops) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusBadRequest, constants.MessageResponse{Message: err.Error()}) + return + } + + project, err := workspaces.CreateDevopsProject(username, devops) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + if project.ProjectId == nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: "project create failed"}) + } else { + err = workspaces.BindingDevopsProject(workspace, *project.ProjectId) + + if err != nil { + workspaces.DeleteDevopsProject(username, *project.ProjectId) + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(project) + } + +} + +func NamespaceCreateHandler(req *restful.Request, resp *restful.Response) { + workspace := req.PathParameter("name") + username := req.HeaderParameter("X-Token-Username") + + namespace := &v1.Namespace{} + + err := req.ReadEntity(namespace) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusBadRequest, constants.MessageResponse{Message: err.Error()}) + return + } + + if namespace.Annotations == nil { + namespace.Annotations = make(map[string]string, 0) + } + + namespace.Annotations["creator"] = username + namespace.Annotations["workspace"] = workspace + + namespace, err = workspaces.CreateNamespace(namespace) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusBadRequest, constants.MessageResponse{Message: err.Error()}) + return + } + + err = workspaces.BindingNamespace(workspace, namespace.Name) + + if err != nil { + workspaces.DeleteNamespace(namespace.Name) + resp.WriteHeaderAndEntity(http.StatusBadRequest, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(namespace) +} + +func DevOpsProjectHandler(req *restful.Request, resp *restful.Response) { + + workspace := req.PathParameter("name") + + devOpsProjects, err := workspaces.DevopsProjects(workspace) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(devOpsProjects) +} + +func NamespaceHandler(req *restful.Request, resp *restful.Response) { + + workspace := req.PathParameter("name") + + namespaces, err := workspaces.Namespaces(workspace) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(namespaces) +} +func WorkspaceCreateHandler(req *restful.Request, resp *restful.Response) { + var workspace workspaces.Workspace + username := req.HeaderParameter("X-Token-Username") + err := req.ReadEntity(&workspace) + if err != nil { + resp.WriteHeaderAndEntity(http.StatusBadRequest, constants.MessageResponse{Message: err.Error()}) + return + } + if workspace.Name == "" || strings.Contains(workspace.Name, ":") { + resp.WriteHeaderAndEntity(http.StatusBadRequest, constants.MessageResponse{Message: "invalid workspace name"}) + return + } + + workspace.Path = workspace.Name + workspace.Members = nil + + workspace.Creator = username + + created, err := workspaces.Create(&workspace) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(created) + +} + +func DeleteWorkspaceHandler(req *restful.Request, resp *restful.Response) { + name := req.PathParameter("name") + + if name == "" || strings.Contains(name, ":") { + resp.WriteHeaderAndEntity(http.StatusBadRequest, constants.MessageResponse{Message: "invalid workspace name"}) + return + } + + workspace, err := workspaces.Detail(name) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + err = workspaces.Delete(workspace) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(constants.MessageResponse{Message: "success"}) +} +func WorkspaceEditHandler(req *restful.Request, resp *restful.Response) { + var workspace workspaces.Workspace + name := req.PathParameter("name") + err := req.ReadEntity(&workspace) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusBadRequest, constants.MessageResponse{Message: err.Error()}) + return + } + + if name != workspace.Name { + resp.WriteError(http.StatusBadRequest, fmt.Errorf("the name of workspace (%s) does not match the name on the URL (%s)", workspace.Name, name)) + return + } + + if workspace.Name == "" || strings.Contains(workspace.Name, ":") { + resp.WriteHeaderAndEntity(http.StatusBadRequest, constants.MessageResponse{Message: "invalid workspace name"}) + return + } + + workspace.Path = workspace.Name + + workspace.Members = nil + + edited, err := workspaces.Edit(&workspace) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(edited) +} +func WorkspaceDetailHandler(req *restful.Request, resp *restful.Response) { + + name := req.PathParameter("name") + + workspace, err := workspaces.Detail(name) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(workspace) +} + +func WorkspaceListHandler(req *restful.Request, resp *restful.Response) { + + var names []string + + if query := req.QueryParameter("name"); query != "" { + names = strings.Split(query, ",") + } + + list, err := workspaces.List(names) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(list) +} diff --git a/pkg/app/app.go b/pkg/app/app.go index 78cac589a..25c2e9f68 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -43,6 +43,7 @@ import ( "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/models" "kubesphere.io/kubesphere/pkg/models/controllers" + "kubesphere.io/kubesphere/pkg/models/workspaces" "kubesphere.io/kubesphere/pkg/options" ) @@ -92,6 +93,16 @@ func preCheck() error { models.CreateKubectlDeploy(constants.AdminUserName) return nil } + + db := client.NewSharedDBClient() + defer db.Close() + if !db.HasTable(&workspaces.WorkspaceNSBinding{}) { + db.CreateTable(&workspaces.WorkspaceNSBinding{}) + } + + if !db.HasTable(&workspaces.WorkspaceDPBinding{}) { + db.CreateTable(&workspaces.WorkspaceDPBinding{}) + } return err } diff --git a/pkg/constants/common.go b/pkg/constants/common.go index 81220aefc..9e8b2be02 100644 --- a/pkg/constants/common.go +++ b/pkg/constants/common.go @@ -40,14 +40,29 @@ const ( DataHome = "/etc/kubesphere" IngressControllerFolder = DataHome + "/ingress-controller" IngressControllerPrefix = "kubesphere-router-" + DevopsAPIServerEnv = "DEVOPS_API_SERVER" + AccountAPIServerEnv = "ACCOUNT_API_SERVER" + DevopsProxyTokenEnv = "DEVOPS_PROXY_TOKEN" OpenPitrixProxyTokenEnv = "OPENPITRIX_PROXY_TOKEN" ) var ( + DevopsAPIServer = "ks-devops-apiserver.kubesphere-system.svc" + AccountAPIServer = "ks-account.kubesphere-system.svc" + DevopsProxyToken = "" OpenPitrixProxyToken = "" ) func init() { + if env := os.Getenv(DevopsAPIServerEnv); env != "" { + DevopsAPIServer = env + } + if env := os.Getenv(AccountAPIServerEnv); env != "" { + AccountAPIServer = env + } + if env := os.Getenv(DevopsProxyTokenEnv); env != "" { + DevopsProxyToken = env + } if env := os.Getenv(OpenPitrixProxyTokenEnv); env != "" { OpenPitrixProxyToken = env } diff --git a/pkg/models/iam/iam.go b/pkg/models/iam/iam.go index c019b3db5..d0c6b7603 100644 --- a/pkg/models/iam/iam.go +++ b/pkg/models/iam/iam.go @@ -1,18 +1,152 @@ package iam import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "strings" + "github.com/golang/glog" "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/util/slice" "kubesphere.io/kubesphere/pkg/client" + "kubesphere.io/kubesphere/pkg/constants" + ksErr "kubesphere.io/kubesphere/pkg/util/errors" ) const ClusterRoleKind = "ClusterRole" +// Get user list based on workspace role +func WorkspaceRoleUsers(workspace string, roleName string) ([]User, error) { + + k8sClient := client.NewK8sClient() + + roleBinding, err := k8sClient.RbacV1().ClusterRoleBindings().Get(fmt.Sprintf("system:%s:%s", workspace, roleName), meta_v1.GetOptions{}) + + if err != nil { + return nil, err + } + + names := make([]string, 0) + + for _, subject := range roleBinding.Subjects { + if subject.Kind == v1.UserKind { + names = append(names, subject.Name) + } + } + + users, err := GetUsers(names) + + if err != nil { + return nil, err + } + + for i := 0; i < len(users); i++ { + users[i].WorkspaceRole = roleName + } + + return users, nil +} + +func GetUsers(names []string) ([]User, error) { + var users []User + + if names == nil || len(names) == 0 { + return make([]User, 0), nil + } + + result, err := http.Get(fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/users?name=%s", constants.AccountAPIServer, strings.Join(names, ","))) + + if err != nil { + return nil, err + } + + data, err := ioutil.ReadAll(result.Body) + + if err != nil { + return nil, err + } + + if result.StatusCode > 200 { + return nil, ksErr.Wrap(data) + } + + err = json.Unmarshal(data, &users) + + if err != nil { + return nil, err + } + + return users, nil +} + +func GetUser(name string) (*User, error) { + + result, err := http.Get(fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/users/%s", constants.AccountAPIServer, name)) + + if err != nil { + return nil, err + } + + data, err := ioutil.ReadAll(result.Body) + + if err != nil { + return nil, err + } + + if result.StatusCode > 200 { + return nil, ksErr.Wrap(data) + } + + var user User + + err = json.Unmarshal(data, &user) + + if err != nil { + return nil, err + } + + return &user, nil +} + +// Get rules +func WorkspaceRoleRules(workspace string, roleName string) (*v1.ClusterRole, []Rule, error) { + k8sClient := client.NewK8sClient() + + role, err := k8sClient.RbacV1().ClusterRoles().Get(fmt.Sprintf("system:%s:%s", workspace, roleName), meta_v1.GetOptions{}) + + if err != nil { + return nil, nil, err + } + + for i := 0; i < len(role.Rules); i++ { + role.Rules[i].ResourceNames = nil + } + + rules := make([]Rule, 0) + for i := 0; i < len(WorkspaceRoleRuleMapping); i++ { + rule := Rule{Name: WorkspaceRoleRuleMapping[i].Name} + rule.Actions = make([]Action, 0) + for j := 0; j < len(WorkspaceRoleRuleMapping[i].Actions); j++ { + if actionValidate(role.Rules, WorkspaceRoleRuleMapping[i].Actions[j]) { + rule.Actions = append(rule.Actions, WorkspaceRoleRuleMapping[i].Actions[j]) + } + } + if len(rule.Actions) > 0 { + rules = append(rules, rule) + } + } + + role.Name = roleName + + return role, rules, nil +} + func GetUserNamespaces(username string, requiredRule v1.PolicyRule) (allNamespace bool, namespaces []string, err error) { clusterRoles, err := GetClusterRoles(username) @@ -247,11 +381,18 @@ func GetClusterRoles(username string) ([]v1.ClusterRole, error) { if role.Annotations == nil { role.Annotations = make(map[string]string, 0) } + role.Annotations["rbac.authorization.k8s.io/clusterrolebinding"] = roleBinding.Name + + if roleBinding.Annotations != nil && + roleBinding.Annotations["rbac.authorization.k8s.io/clusterrole"] == roleBinding.RoleRef.Name { + role.Annotations["rbac.authorization.k8s.io/clusterrole"] = "true" + } + roles = append(roles, *role) break } else if apierrors.IsNotFound(err) { - glog.Infoln(err.Error()) + log.Println(err) break } else { return nil, err @@ -303,21 +444,172 @@ func ruleValidate(rules []v1.PolicyRule, rule v1.PolicyRule) bool { func verbValidate(rules []v1.PolicyRule, apiGroup string, nonResourceURL string, resource string, resourceName string, verb string) bool { for _, rule := range rules { - if slice.ContainsString(rule.APIGroups, apiGroup, nil) || slice.ContainsString(rule.APIGroups, v1.APIGroupAll, nil) { - if slice.ContainsString(rule.Verbs, verb, nil) || slice.ContainsString(rule.Verbs, v1.VerbAll, nil) { - if nonResourceURL == "" { - if slice.ContainsString(rule.Resources, resource, nil) || slice.ContainsString(rule.Resources, v1.ResourceAll, nil) { - if resourceName == "" { - return true - } else if slice.ContainsString(rule.ResourceNames, resourceName, nil) || slice.ContainsString(rule.Resources, v1.ResourceAll, nil) { + + if nonResourceURL == "" { + if slice.ContainsString(rule.APIGroups, apiGroup, nil) || + slice.ContainsString(rule.APIGroups, v1.APIGroupAll, nil) { + if slice.ContainsString(rule.Verbs, verb, nil) || + slice.ContainsString(rule.Verbs, v1.VerbAll, nil) { + if slice.ContainsString(rule.Resources, v1.ResourceAll, nil) { + return true + } else if slice.ContainsString(rule.Resources, resource, nil) { + if len(rule.ResourceNames) > 0 { + if slice.ContainsString(rule.ResourceNames, resourceName, nil) { + return true + } + } else if resourceName == "" { return true } } - } else if slice.ContainsString(rule.NonResourceURLs, nonResourceURL, nil) || slice.ContainsString(rule.NonResourceURLs, v1.NonResourceAll, nil) { - return true } } + + } else if slice.ContainsString(rule.NonResourceURLs, nonResourceURL, nil) || + slice.ContainsString(rule.NonResourceURLs, v1.NonResourceAll, nil) { + if slice.ContainsString(rule.Verbs, verb, nil) || + slice.ContainsString(rule.Verbs, v1.VerbAll, nil) { + return true + } } } return false } + +func GetUserRules(username string) (map[string][]Rule, error) { + + items := make(map[string][]Rule, 0) + userRoles, err := GetRoles(username) + + if err != nil { + return nil, err + } + + rulesMapping := make(map[string][]v1.PolicyRule, 0) + + for _, role := range userRoles { + rules := rulesMapping[role.Namespace] + if rules == nil { + rules = make([]v1.PolicyRule, 0) + } + rules = append(rules, role.Rules...) + rulesMapping[role.Namespace] = rules + } + + for namespace, policyRules := range rulesMapping { + rules := convertToRules(policyRules) + if len(rules) > 0 { + items[namespace] = rules + } + } + + return items, nil +} + +func convertToRules(policyRules []v1.PolicyRule) []Rule { + rules := make([]Rule, 0) + + for i := 0; i < (len(RoleRuleGroup)); i++ { + rule := Rule{Name: RoleRuleGroup[i].Name} + rule.Actions = make([]Action, 0) + for j := 0; j < (len(RoleRuleGroup[i].Actions)); j++ { + if actionValidate(policyRules, RoleRuleGroup[i].Actions[j]) { + rule.Actions = append(rule.Actions, RoleRuleGroup[i].Actions[j]) + } + } + + if len(rule.Actions) > 0 { + rules = append(rules, rule) + } + } + + return rules +} + +func GetUserClusterRules(username string) ([]Rule, error) { + + rules := make([]Rule, 0) + + clusterRoles, err := GetClusterRoles(username) + + if err != nil { + return nil, err + } + + clusterRules := make([]v1.PolicyRule, 0) + + for _, role := range clusterRoles { + clusterRules = append(clusterRules, role.Rules...) + } + + for i := 0; i < (len(ClusterRoleRuleGroup)); i++ { + rule := Rule{Name: ClusterRoleRuleGroup[i].Name} + rule.Actions = make([]Action, 0) + for j := 0; j < (len(ClusterRoleRuleGroup[i].Actions)); j++ { + if actionValidate(clusterRules, ClusterRoleRuleGroup[i].Actions[j]) { + rule.Actions = append(rule.Actions, ClusterRoleRuleGroup[i].Actions[j]) + } + } + if len(rule.Actions) > 0 { + rules = append(rules, rule) + } + } + + return rules, nil +} + +func GetClusterRoleRules(name string) ([]Rule, error) { + + clusterRole, err := GetClusterRole(name) + + if err != nil { + return nil, err + } + + rules := make([]Rule, 0) + + for i := 0; i < len(ClusterRoleRuleGroup); i++ { + rule := Rule{Name: ClusterRoleRuleGroup[i].Name} + rule.Actions = make([]Action, 0) + for j := 0; j < (len(ClusterRoleRuleGroup[i].Actions)); j++ { + if actionValidate(clusterRole.Rules, ClusterRoleRuleGroup[i].Actions[j]) { + rule.Actions = append(rule.Actions, ClusterRoleRuleGroup[i].Actions[j]) + } + } + if len(rule.Actions) > 0 { + rules = append(rules, rule) + } + } + + return rules, nil +} + +func GetRoleRules(namespace string, name string) ([]Rule, error) { + role, err := GetRole(namespace, name) + if err != nil { + return nil, err + } + + rules := make([]Rule, 0) + for i := 0; i < len(RoleRuleGroup); i++ { + rule := Rule{Name: RoleRuleGroup[i].Name} + rule.Actions = make([]Action, 0) + for j := 0; j < len(RoleRuleGroup[i].Actions); j++ { + if actionValidate(role.Rules, RoleRuleGroup[i].Actions[j]) { + rule.Actions = append(rule.Actions, RoleRuleGroup[i].Actions[j]) + } + } + if len(rule.Actions) > 0 { + rules = append(rules, rule) + } + } + return rules, nil +} + +func actionValidate(rules []v1.PolicyRule, action Action) bool { + for _, rule := range action.Rules { + if !ruleValidate(rules, rule) { + return false + } + } + return true +} diff --git a/pkg/models/iam/policy.go b/pkg/models/iam/policy.go index 9a36bf3f7..8ab352664 100644 --- a/pkg/models/iam/policy.go +++ b/pkg/models/iam/policy.go @@ -29,16 +29,6 @@ const ( clusterRulesConfigPath = "/etc/kubesphere/rules/clusterrules.json" ) -type Action struct { - Name string `json:"name"` - Rules []v1.PolicyRule `json:"rules"` -} - -type Rule struct { - Name string `json:"name"` - Actions []Action `json:"actions"` -} - func init() { rulesConfig, err := ioutil.ReadFile(rulesConfigPath) if err == nil { @@ -61,6 +51,101 @@ func init() { } var ( + WorkspaceRoleRuleMapping = []Rule{ + { + Name: "workspaces", + Actions: []Action{ + {Name: "edit", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"update", "patch"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces"}, + }, + }, + }, + {Name: "delete", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces"}, + }, + }, + }, + }, + }, + + {Name: "members", + Actions: []Action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"list"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/members"}, + }, + }, + }, + }, + }, + { + Name: "devops", + Actions: []Action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"list"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/devops"}, + }, + }, + }, + {Name: "create", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/devops"}, + }, + }, + }, + }, + }, + { + Name: "projects", + Actions: []Action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"list"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/namespaces"}, + }, + }, + }, + {Name: "create", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/namespaces"}, + }, + }, + }, + }, + }, + { + Name: "registries", + Actions: []Action{ + {Name: "view"}, + {Name: "create"}, + {Name: "edit"}, + {Name: "delete"}, + }, + }, + } + ClusterRoleRuleGroup = []Rule{{ Name: "projects", Actions: []Action{ diff --git a/pkg/models/iam/tools.go b/pkg/models/iam/tools.go deleted file mode 100644 index 79b619c04..000000000 --- a/pkg/models/iam/tools.go +++ /dev/null @@ -1,161 +0,0 @@ -/* - Copyright 2018 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 ( - "k8s.io/api/rbac/v1" -) - -func GetUserRules(username string) (map[string][]Rule, error) { - - items := make(map[string][]Rule, 0) - userRoles, err := GetRoles(username) - - if err != nil { - return nil, err - } - - rulesMapping := make(map[string][]v1.PolicyRule, 0) - - for _, role := range userRoles { - rules := rulesMapping[role.Namespace] - if rules == nil { - rules = make([]v1.PolicyRule, 0) - } - rules = append(rules, role.Rules...) - rulesMapping[role.Namespace] = rules - } - - for namespace, policyRules := range rulesMapping { - rules := convertToRules(policyRules) - if len(rules) > 0 { - items[namespace] = rules - } - } - - return items, nil -} - -func convertToRules(policyRules []v1.PolicyRule) []Rule { - rules := make([]Rule, 0) - - for i := 0; i < (len(RoleRuleGroup)); i++ { - rule := Rule{Name: RoleRuleGroup[i].Name} - rule.Actions = make([]Action, 0) - for j := 0; j < (len(RoleRuleGroup[i].Actions)); j++ { - if actionValidate(policyRules, RoleRuleGroup[i].Actions[j]) { - rule.Actions = append(rule.Actions, RoleRuleGroup[i].Actions[j]) - } - } - - if len(rule.Actions) > 0 { - rules = append(rules, rule) - } - } - - return rules -} - -func GetUserClusterRules(username string) ([]Rule, error) { - - rules := make([]Rule, 0) - - clusterRoles, err := GetClusterRoles(username) - - if err != nil { - return nil, err - } - - clusterRules := make([]v1.PolicyRule, 0) - - for _, role := range clusterRoles { - clusterRules = append(clusterRules, role.Rules...) - } - - for i := 0; i < (len(ClusterRoleRuleGroup)); i++ { - rule := Rule{Name: ClusterRoleRuleGroup[i].Name} - rule.Actions = make([]Action, 0) - for j := 0; j < (len(ClusterRoleRuleGroup[i].Actions)); j++ { - if actionValidate(clusterRules, ClusterRoleRuleGroup[i].Actions[j]) { - rule.Actions = append(rule.Actions, ClusterRoleRuleGroup[i].Actions[j]) - } - } - if len(rule.Actions) > 0 { - rules = append(rules, rule) - } - } - - return rules, nil -} - -func GetClusterRoleRules(name string) ([]Rule, error) { - - clusterRole, err := GetClusterRole(name) - - if err != nil { - return nil, err - } - - rules := make([]Rule, 0) - - for i := 0; i < len(ClusterRoleRuleGroup); i++ { - rule := Rule{Name: ClusterRoleRuleGroup[i].Name} - rule.Actions = make([]Action, 0) - for j := 0; j < (len(ClusterRoleRuleGroup[i].Actions)); j++ { - if actionValidate(clusterRole.Rules, ClusterRoleRuleGroup[i].Actions[j]) { - rule.Actions = append(rule.Actions, ClusterRoleRuleGroup[i].Actions[j]) - } - } - if len(rule.Actions) > 0 { - rules = append(rules, rule) - } - } - - return rules, nil -} - -func GetRoleRules(namespace string, name string) ([]Rule, error) { - role, err := GetRole(namespace, name) - if err != nil { - return nil, err - } - - rules := make([]Rule, 0) - for i := 0; i < len(RoleRuleGroup); i++ { - rule := Rule{Name: RoleRuleGroup[i].Name} - rule.Actions = make([]Action, 0) - for j := 0; j < len(RoleRuleGroup[i].Actions); j++ { - if actionValidate(role.Rules, RoleRuleGroup[i].Actions[j]) { - rule.Actions = append(rule.Actions, RoleRuleGroup[i].Actions[j]) - } - } - if len(rule.Actions) > 0 { - rules = append(rules, rule) - } - } - return rules, nil -} - -func actionValidate(rules []v1.PolicyRule, action Action) bool { - for _, rule := range action.Rules { - if !ruleValidate(rules, rule) { - return false - } - } - return true -} diff --git a/pkg/models/iam/types.go b/pkg/models/iam/types.go new file mode 100644 index 000000000..c457dd7ec --- /dev/null +++ b/pkg/models/iam/types.go @@ -0,0 +1,41 @@ +package iam + +import ( + "k8s.io/api/rbac/v1" +) + +type Action struct { + Name string `json:"name"` + Rules []v1.PolicyRule `json:"rules"` +} + +type Rule struct { + Name string `json:"name"` + Actions []Action `json:"actions"` +} + +type SimpleRule struct { + Name string `json:"name"` + Actions []string `json:"actions"` +} + +type User struct { + Username string `json:"username"` + //UID string `json:"uid"` + Groups []string `json:"groups"` + Password string `json:"password,omitempty"` + //Extra map[string]interface{} `json:"extra"` + AvatarUrl string `json:"avatar_url"` + Description string `json:"description"` + Email string `json:"email"` + LastLoginTime string `json:"last_login_time"` + Status int `json:"status"` + ClusterRole string `json:"cluster_role"` + ClusterRules []SimpleRule `json:"cluster_rules,omitempty"` + Roles map[string]string `json:"roles,omitempty"` + Rules map[string][]SimpleRule `json:"rules,omitempty"` + Role string `json:"role,omitempty"` + WorkspaceRoles map[string]string `json:"workspace_roles,omitempty"` + WorkspaceRole string `json:"workspace_role,omitempty"` + WorkspaceRules map[string][]SimpleRule `json:"workspace_rules,omitempty"` +} diff --git a/pkg/models/workspaces/types.go b/pkg/models/workspaces/types.go new file mode 100644 index 000000000..3147dbd18 --- /dev/null +++ b/pkg/models/workspaces/types.go @@ -0,0 +1,46 @@ +package workspaces + +import "time" + +type Workspace struct { + Group `json:",inline"` + Namespaces []string `json:"namespaces,omitempty"` + DevopsProjects []string `json:"devops_projects,omitempty"` +} + +type UserInvite struct { + Username string `json:"username"` + Role string `json:"role"` +} + +type Group struct { + Path string `json:"path"` + Name string `json:"name"` + Gid string `json:"gid"` + Members []string `json:"members"` + Logo string `json:"logo"` + Creator string `json:"creator"` + CreateTime string `json:"create_time"` + ChildGroups []string `json:"child_groups,omitempty"` + Description string `json:"description"` +} + +type WorkspaceNSBinding struct { + Workspace string `gorm:"primary_key"` + Namespace string `gorm:"primary_key"` +} + +type WorkspaceDPBinding struct { + Workspace string `gorm:"primary_key"` + DevOpsProject string `gorm:"primary_key"` +} + +type DevopsProject struct { + ProjectId *string `json:"project_id,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + Creator string `json:"creator"` + CreateTime *time.Time `json:"create_time,omitempty"` + Status *string `json:"status"` + Visibility *string `json:"visibility,omitempty"` +} diff --git a/pkg/models/workspaces/workspaces.go b/pkg/models/workspaces/workspaces.go new file mode 100644 index 000000000..6547517f1 --- /dev/null +++ b/pkg/models/workspaces/workspaces.go @@ -0,0 +1,859 @@ +package workspaces + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/jinzhu/gorm" + core "k8s.io/api/core/v1" + k8sErr "k8s.io/apimachinery/pkg/api/errors" + + "log" + "strings" + + "github.com/emicklei/go-restful" + "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + v13 "k8s.io/client-go/listers/rbac/v1" + "k8s.io/kubernetes/pkg/util/slice" + + "kubesphere.io/kubesphere/pkg/client" + "kubesphere.io/kubesphere/pkg/constants" + "kubesphere.io/kubesphere/pkg/models/controllers" + "kubesphere.io/kubesphere/pkg/models/iam" + ksErr "kubesphere.io/kubesphere/pkg/util/errors" +) + +var WorkSpaceRoles = []string{"admin", "operator", "viewer"} + +func UnBindNamespace(workspace string, namespace string) error { + db := client.NewSharedDBClient() + defer db.Close() + return db.Delete(&WorkspaceNSBinding{Workspace: workspace, Namespace: namespace}).Error +} + +func UnBindDevopsProject(workspace string, devops string) error { + db := client.NewSharedDBClient() + defer db.Close() + return db.Delete(&WorkspaceDPBinding{Workspace: workspace, DevOpsProject: devops}).Error +} + +func DeleteDevopsProject(username string, devops string) error { + request, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://%s/api/v1alpha/projects/%s", constants.DevopsAPIServer, devops), nil) + request.Header.Add("X-Token-Username", username) + + result, err := http.DefaultClient.Do(request) + + if err != nil { + return err + } + data, err := ioutil.ReadAll(result.Body) + if err != nil { + return err + } + if result.StatusCode > 200 { + return ksErr.Wrap(data) + } + return nil +} + +func CreateDevopsProject(username string, devops DevopsProject) (*DevopsProject, error) { + + data, err := json.Marshal(devops) + + if err != nil { + return nil, err + } + + request, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/api/v1alpha/projects", constants.DevopsAPIServer), bytes.NewReader(data)) + request.Header.Add("X-Token-Username", username) + request.Header.Add("Content-Type", "application/json") + result, err := http.DefaultClient.Do(request) + + if err != nil { + return nil, err + } + + data, err = ioutil.ReadAll(result.Body) + + if err != nil { + return nil, err + } + + if result.StatusCode > 200 { + return nil, ksErr.Wrap(data) + } + + var project DevopsProject + + err = json.Unmarshal(data, &project) + + if err != nil { + return nil, err + } + + return &project, nil +} + +func Namespaces(workspace string) ([]*core.Namespace, error) { + db := client.NewSharedDBClient() + defer db.Close() + + var workspaceNSBindings []WorkspaceNSBinding + + if err := db.Where("workspace = ?", workspace).Find(&workspaceNSBindings).Error; err != nil { + return nil, err + } + + namespaces := make([]*core.Namespace, 0) + + for _, workspaceNSBinding := range workspaceNSBindings { + namespace, err := client.NewK8sClient().CoreV1().Namespaces().Get(workspaceNSBinding.Namespace, meta_v1.GetOptions{}) + if err != nil { + if k8sErr.IsNotFound(err) { + db.Delete(&WorkspaceNSBinding{Workspace: workspace, Namespace: workspaceNSBinding.Namespace}) + } else { + return nil, err + } + } else { + namespaces = append(namespaces, namespace) + } + } + + return namespaces, nil +} + +func BindingDevopsProject(workspace string, devops string) error { + db := client.NewSharedDBClient() + defer db.Close() + return db.Create(&WorkspaceDPBinding{Workspace: workspace, DevOpsProject: devops}).Error +} + +func DeleteNamespace(namespace string) error { + deletePolicy := meta_v1.DeletePropagationBackground + err := client.NewK8sClient().CoreV1().Namespaces().Delete(namespace, &meta_v1.DeleteOptions{PropagationPolicy: &deletePolicy}) + return err +} + +func Delete(workspace *Workspace) error { + + err := release(workspace) + + if err != nil { + return err + } + + req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups/%s", constants.AccountAPIServer, workspace.Name), nil) + + if err != nil { + return err + } + result, err := http.DefaultClient.Do(req) + + if err != nil { + return err + } + + data, err := ioutil.ReadAll(result.Body) + + if err != nil { + return err + } + + if result.StatusCode > 200 { + return ksErr.Wrap(data) + } + + return nil +} + +func release(workspace *Workspace) error { + for _, namespace := range workspace.Namespaces { + err := DeleteNamespace(namespace) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + } + + for _, devops := range workspace.DevopsProjects { + err := DeleteDevopsProject(workspace.Creator, devops) + if err != nil { + return err + } + } + + err := workspaceRoleRelease(workspace.Name) + + return err +} +func workspaceRoleRelease(workspace string) error { + k8sClient := client.NewK8sClient() + deletePolicy := meta_v1.DeletePropagationForeground + + for _, role := range WorkSpaceRoles { + err := k8sClient.RbacV1().ClusterRoles().Delete(fmt.Sprintf("system:%s:%s", workspace, role), &meta_v1.DeleteOptions{PropagationPolicy: &deletePolicy}) + + if err != nil && !apierrors.IsNotFound(err) { + return err + } + } + + for _, role := range WorkSpaceRoles { + err := k8sClient.RbacV1().ClusterRoleBindings().Delete(fmt.Sprintf("system:%s:%s", workspace, role), &meta_v1.DeleteOptions{PropagationPolicy: &deletePolicy}) + + if err != nil && !apierrors.IsNotFound(err) { + return err + } + } + + return nil +} + +func Create(workspace *Workspace) (*Workspace, error) { + + data, err := json.Marshal(workspace) + + if err != nil { + return nil, err + } + + result, err := http.Post(fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups", constants.AccountAPIServer), restful.MIME_JSON, bytes.NewReader(data)) + + if err != nil { + return nil, err + } + + data, err = ioutil.ReadAll(result.Body) + + if err != nil { + return nil, err + } + + if result.StatusCode > 200 { + return nil, ksErr.Wrap(data) + } + + var created Workspace + + err = json.Unmarshal(data, &created) + + if err != nil { + return nil, err + } + + created.Members = make([]string, 0) + created.Namespaces = make([]string, 0) + created.DevopsProjects = make([]string, 0) + + go WorkspaceRoleInit(workspace) + + return &created, nil +} + +func Edit(workspace *Workspace) (*Workspace, error) { + + data, err := json.Marshal(workspace) + + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups/%s", constants.AccountAPIServer, workspace.Name), bytes.NewReader(data)) + req.Header.Set("Content-Type", "application/json") + + if err != nil { + return nil, err + } + + result, err := http.DefaultClient.Do(req) + + if err != nil { + return nil, err + } + + data, err = ioutil.ReadAll(result.Body) + + if err != nil { + return nil, err + } + + if result.StatusCode > 200 { + return nil, ksErr.Wrap(data) + } + + var edited Workspace + + err = json.Unmarshal(data, &edited) + + if err != nil { + return nil, err + } + + return &edited, nil +} + +func Detail(name string) (*Workspace, error) { + + result, err := http.Get(fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups/%s", constants.AccountAPIServer, name)) + + if err != nil { + return nil, err + } + + data, err := ioutil.ReadAll(result.Body) + + if err != nil { + return nil, err + } + + if result.StatusCode > 200 { + return nil, ksErr.Wrap(data) + } + + var group Group + + err = json.Unmarshal(data, &group) + + if err != nil { + return nil, err + } + + db := client.NewSharedDBClient() + defer db.Close() + + workspace, err := convertGroupToWorkspace(db, group) + + if err != nil { + return nil, err + } + + return workspace, nil +} + +func List(names []string) ([]*Workspace, error) { + + url := fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups", constants.AccountAPIServer) + + if names != nil { + url = url + "?path=" + strings.Join(names, ",") + } + + result, err := http.Get(url) + + if err != nil { + return nil, err + } + + data, err := ioutil.ReadAll(result.Body) + + if err != nil { + return nil, err + } + + if result.StatusCode > 200 { + return nil, ksErr.Wrap(data) + } + + var groups []Group + + err = json.Unmarshal(data, &groups) + + if err != nil { + return nil, err + } + + db := client.NewSharedDBClient() + + defer db.Close() + + workspaces := make([]*Workspace, 0) + for _, group := range groups { + workspace, err := convertGroupToWorkspace(db, group) + if err != nil { + return nil, err + } + workspaces = append(workspaces, workspace) + } + return workspaces, nil +} + +func DevopsProjects(workspace string) ([]DevopsProject, error) { + + db := client.NewSharedDBClient() + defer db.Close() + + var workspaceDOPBindings []WorkspaceDPBinding + + if err := db.Where("workspace = ?", workspace).Find(&workspaceDOPBindings).Error; err != nil { + return nil, err + } + + devOpsProjects := make([]DevopsProject, 0) + + for _, workspaceDOPBinding := range workspaceDOPBindings { + request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/api/v1alpha/projects/%s", constants.DevopsAPIServer, workspaceDOPBinding.DevOpsProject), nil) + request.Header.Add("X-Token-Username", "admin") + + result, err := http.DefaultClient.Do(request) + if err != nil { + return nil, err + } + data, err := ioutil.ReadAll(result.Body) + + if err != nil { + return nil, err + } + + if result.StatusCode == 403 || result.StatusCode == 404 { + if err := db.Delete(&workspaceDOPBinding).Error; err != nil { + return nil, err + } + continue + } + + if result.StatusCode > 200 { + return nil, ksErr.Wrap(data) + } + + var project DevopsProject + + err = json.Unmarshal(data, &project) + + if err != nil { + return nil, err + } + devOpsProjects = append(devOpsProjects, project) + } + + return devOpsProjects, nil + +} +func convertGroupToWorkspace(db *gorm.DB, group Group) (*Workspace, error) { + var workspaceNSBindings []WorkspaceNSBinding + + if err := db.Where("workspace = ?", group.Name).Find(&workspaceNSBindings).Error; err != nil { + return nil, err + } + + namespaces := make([]string, 0) + + for _, workspaceNSBinding := range workspaceNSBindings { + namespaces = append(namespaces, workspaceNSBinding.Namespace) + } + + var workspaceDOPBindings []WorkspaceDPBinding + + if err := db.Where("workspace = ?", group.Name).Find(&workspaceDOPBindings).Error; err != nil { + return nil, err + } + + devOpsProjects := make([]string, 0) + + for _, workspaceDOPBinding := range workspaceDOPBindings { + devOpsProjects = append(devOpsProjects, workspaceDOPBinding.DevOpsProject) + } + + workspace := Workspace{Group: group} + workspace.Namespaces = namespaces + workspace.DevopsProjects = devOpsProjects + return &workspace, nil +} + +func CreateNamespace(namespace *core.Namespace) (*core.Namespace, error) { + return client.NewK8sClient().CoreV1().Namespaces().Create(namespace) +} + +func BindingNamespace(workspace string, namespace string) error { + db := client.NewSharedDBClient() + defer db.Close() + return db.Create(&WorkspaceNSBinding{Workspace: workspace, Namespace: namespace}).Error +} + +func Invite(workspaceName string, users []UserInvite) error { + for _, user := range users { + if !slice.ContainsString(WorkSpaceRoles, user.Role, nil) { + return fmt.Errorf("role %s not exist", user.Role) + } + } + + workspace, err := Detail(workspaceName) + + if err != nil { + return err + } + + for _, user := range users { + if !slice.ContainsString(workspace.Members, user.Username, nil) { + workspace.Members = append(workspace.Members, user.Username) + } + } + + workspace, err = Edit(workspace) + + if err != nil { + return err + } + + for _, user := range users { + err := CreateWorkspaceRoleBinding(workspace, user.Username, user.Role) + if err != nil { + return err + } + } + + return nil +} + +func RemoveMembers(workspaceName string, users []string) error { + + workspace, err := Detail(workspaceName) + + if err != nil { + return err + } + + err = UnbindWorkspace(workspace, users) + + if err != nil { + return err + } + + return nil +} + +//func checkUserExist(username string) (bool, error) { +// result, err := http.Get(fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/users?check=%s", constants.AccountAPIServer, username)) +// +// if err != nil { +// return false, err +// } +// +// data, err := ioutil.ReadAll(result.Body) +// +// if err != nil { +// return false, err +// } +// +// if result.StatusCode > 200 { +// return false, ksErr.Wrap(data) +// } +// +// var r map[string]bool +// +// err = json.Unmarshal(data, &r) +// +// if err != nil { +// return false, err +// } +// +// return r["exist"], nil +// +//} + +func Roles(workspace *Workspace) ([]*v1.ClusterRole, error) { + roles := make([]*v1.ClusterRole, 0) + + k8sClient := client.NewK8sClient() + + for _, name := range WorkSpaceRoles { + role, err := k8sClient.RbacV1().ClusterRoles().Get(fmt.Sprintf("system:%s:%s", workspace.Name, name), meta_v1.GetOptions{}) + + if err != nil { + if apierrors.IsNotFound(err) { + go WorkspaceRoleInit(workspace) + } + return nil, err + } + + role.Name = name + roles = append(roles, role) + } + + return roles, nil +} + +func GetWorkspaceMembers(workspace string) ([]iam.User, error) { + + result, err := http.Get(fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups/%s/users", constants.AccountAPIServer, workspace)) + + if err != nil { + return nil, err + } + + data, err := ioutil.ReadAll(result.Body) + + if err != nil { + return nil, err + } + + if result.StatusCode > 200 { + return nil, ksErr.Wrap(data) + } + + var users []iam.User + + err = json.Unmarshal(data, &users) + + if err != nil { + return nil, err + } + + return users, nil + +} + +func WorkspaceRoleInit(workspace *Workspace) error { + k8sClient := client.NewK8sClient() + + admin := new(v1.ClusterRole) + admin.Name = fmt.Sprintf("system:%s:admin", workspace.Name) + admin.Kind = iam.ClusterRoleKind + admin.Rules = []v1.PolicyRule{ + { + Verbs: []string{"*"}, + APIGroups: []string{"kubesphere.io"}, + ResourceNames: []string{workspace.Name}, + Resources: []string{"workspaces", "workspaces/namespaces", "workspaces/members", "workspaces/devops", "workspaces/registries"}, + }, + } + + admin.Labels = map[string]string{"creator": "system"} + + operator := new(v1.ClusterRole) + operator.Name = fmt.Sprintf("system:%s:operator", workspace.Name) + operator.Kind = iam.ClusterRoleKind + operator.Rules = []v1.PolicyRule{ + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces"}, + ResourceNames: []string{workspace.Name}, + }, { + Verbs: []string{"list", "create"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/namespaces", "workspaces/devops"}, + ResourceNames: []string{workspace.Name}, + }, { + Verbs: []string{"list"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/members", "workspaces/registries"}, + ResourceNames: []string{workspace.Name}, + }, + } + operator.Labels = map[string]string{"creator": "system"} + + viewer := new(v1.ClusterRole) + viewer.Name = fmt.Sprintf("system:%s:viewer", workspace.Name) + viewer.Kind = iam.ClusterRoleKind + viewer.Rules = []v1.PolicyRule{ + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces"}, + ResourceNames: []string{workspace.Name}, + }, { + Verbs: []string{"list"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/namespaces", "workspaces/members", "workspaces/devops", "workspaces/registries"}, + ResourceNames: []string{workspace.Name}, + }, + } + viewer.Labels = map[string]string{"creator": "system"} + + _, err := k8sClient.RbacV1().ClusterRoles().Create(admin) + if err != nil { + if !apierrors.IsAlreadyExists(err) { + log.Println("cluster role create failed", admin.Name, err) + return err + } + } + + adminRoleBinding := new(v1.ClusterRoleBinding) + adminRoleBinding.Name = admin.Name + adminRoleBinding.RoleRef = v1.RoleRef{Kind: "ClusterRole", Name: admin.Name} + adminRoleBinding.Subjects = []v1.Subject{{Kind: v1.UserKind, Name: workspace.Creator}} + _, err = k8sClient.RbacV1().ClusterRoleBindings().Create(adminRoleBinding) + if err != nil { + if !apierrors.IsAlreadyExists(err) { + log.Println("cluster rolebinding create failed", adminRoleBinding.Name, err) + return err + } + } + + _, err = k8sClient.RbacV1().ClusterRoles().Create(operator) + if err != nil { + if !apierrors.IsAlreadyExists(err) { + log.Println("cluster role create failed", viewer.Name, err) + return err + } + } + + operatorRoleBinding := new(v1.ClusterRoleBinding) + operatorRoleBinding.Name = operator.Name + operatorRoleBinding.RoleRef = v1.RoleRef{Kind: "ClusterRole", Name: operator.Name} + operatorRoleBinding.Subjects = make([]v1.Subject, 0) + _, err = k8sClient.RbacV1().ClusterRoleBindings().Create(operatorRoleBinding) + if err != nil { + if !apierrors.IsAlreadyExists(err) { + log.Println("cluster rolebinding create failed", operatorRoleBinding.Name, err) + return err + } + } + + _, err = k8sClient.RbacV1().ClusterRoles().Create(viewer) + if err != nil { + if !apierrors.IsAlreadyExists(err) { + log.Println("cluster role create failed", viewer.Name, err) + return err + } + } + + viewerRoleBinding := new(v1.ClusterRoleBinding) + viewerRoleBinding.Name = viewer.Name + viewerRoleBinding.RoleRef = v1.RoleRef{Kind: "ClusterRole", Name: viewer.Name} + viewerRoleBinding.Subjects = make([]v1.Subject, 0) + _, err = k8sClient.RbacV1().ClusterRoleBindings().Create(viewerRoleBinding) + if err != nil { + if !apierrors.IsAlreadyExists(err) { + log.Println("cluster rolebinding create failed", viewerRoleBinding.Name, err) + return err + } + } + + return nil +} + +func unbindWorkspaceRole(workspace string, users []string) error { + k8sClient := client.NewK8sClient() + + for _, name := range WorkSpaceRoles { + roleBinding, err := k8sClient.RbacV1().ClusterRoleBindings().Get(fmt.Sprintf("system:%s:%s", workspace, name), meta_v1.GetOptions{}) + + if err != nil { + return err + } + + modify := false + + for i := 0; i < len(roleBinding.Subjects); i++ { + if roleBinding.Subjects[i].Kind == v1.UserKind && slice.ContainsString(users, roleBinding.Subjects[i].Name, nil) { + roleBinding.Subjects = append(roleBinding.Subjects[:i], roleBinding.Subjects[i+1:]...) + i-- + modify = true + } + } + + if modify { + roleBinding, err = k8sClient.RbacV1().ClusterRoleBindings().Update(roleBinding) + if err != nil { + return err + } + } + } + + return nil +} + +func unbindNamespacesRole(namespaces []string, users []string) error { + + lister := controllers.ResourceControllers.Controllers[controllers.Namespaces].Lister().(v13.RoleBindingLister) + + k8sClient := client.NewK8sClient() + for _, namespace := range namespaces { + + roleBindings, err := lister.RoleBindings(namespace).List(labels.Everything()) + + if err != nil { + return err + } + for _, roleBinding := range roleBindings { + + modify := false + for i := 0; i < len(roleBinding.Subjects); i++ { + if roleBinding.Subjects[i].Kind == v1.UserKind && slice.ContainsString(users, roleBinding.Subjects[i].Name, nil) { + roleBinding.Subjects = append(roleBinding.Subjects[:i], roleBinding.Subjects[i+1:]...) + modify = true + } + } + if modify { + _, err := k8sClient.RbacV1().RoleBindings(namespace).Update(roleBinding) + if err != nil { + return err + } + } + } + } + + return nil +} + +func UnbindWorkspace(workspace *Workspace, users []string) error { + + err := unbindNamespacesRole(workspace.Namespaces, users) + + if err != nil { + return err + } + + err = unbindWorkspaceRole(workspace.Name, users) + + if err != nil { + return err + } + + return nil +} + +func CreateWorkspaceRoleBinding(workspace *Workspace, username string, role string) error { + + k8sClient := client.NewK8sClient() + + for _, roleName := range WorkSpaceRoles { + roleBinding, err := k8sClient.RbacV1().ClusterRoleBindings().Get(fmt.Sprintf("system:%s:%s", workspace.Name, roleName), meta_v1.GetOptions{}) + + if err != nil { + if apierrors.IsNotFound(err) { + go WorkspaceRoleInit(workspace) + } + return err + } + + modify := false + + for i, v := range roleBinding.Subjects { + if v.Kind == v1.UserKind && v.Name == username { + if roleName == role { + return nil + } else { + modify = true + roleBinding.Subjects = append(roleBinding.Subjects[:i], roleBinding.Subjects[i+1:]...) + if err != nil { + return err + } + break + } + } + } + + if roleName == role { + modify = true + roleBinding.Subjects = append(roleBinding.Subjects, v1.Subject{Kind: v1.UserKind, Name: username}) + } + + if !modify { + continue + } + + _, err = k8sClient.RbacV1().ClusterRoleBindings().Update(roleBinding) + if err != nil { + return err + } + } + + return nil +} diff --git a/pkg/util/errors/errors.go b/pkg/util/errors/errors.go new file mode 100644 index 000000000..2fd332dde --- /dev/null +++ b/pkg/util/errors/errors.go @@ -0,0 +1,20 @@ +package errors + +import ( + "encoding/json" + "errors" +) + +func Wrap(data []byte) error { + var j map[string]string + err := json.Unmarshal(data, &j) + if err != nil { + return errors.New(string(data)) + } else if message := j["message"]; message != "" { + return errors.New(message) + } else if message := j["Error"]; message != "" { + return errors.New(message) + } else { + return errors.New(string(data)) + } +}