diff --git a/.gitignore b/.gitignore index b6efda64d..f564d8ad5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,8 +11,15 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out -.idea +# Files generated by JetBrains IDEs, e.g. IntelliJ IDEA +.idea/ +*.iml bin/ +# Vscode files .vscode/ + tmp/ + +# OSX trash +.DS_Store diff --git a/cmd/kubesphere.go b/cmd/kubesphere.go index 8f9526ded..214f70c1a 100644 --- a/cmd/kubesphere.go +++ b/cmd/kubesphere.go @@ -17,11 +17,11 @@ limitations under the License. package main import ( + "github.com/spf13/pflag" "kubesphere.io/kubesphere/pkg/app" "kubesphere.io/kubesphere/pkg/logs" "kubesphere.io/kubesphere/pkg/options" "kubesphere.io/kubesphere/pkg/version" - "github.com/spf13/pflag" ) func main() { diff --git a/pkg/apis/v1alpha/iam/iam_handler.go b/pkg/apis/v1alpha/iam/iam_handler.go new file mode 100644 index 000000000..f5d971ff7 --- /dev/null +++ b/pkg/apis/v1alpha/iam/iam_handler.go @@ -0,0 +1,223 @@ +/* + 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 ( + "github.com/emicklei/go-restful" + "kubesphere.io/kubesphere/pkg/filter/route" + "kubesphere.io/kubesphere/pkg/models" + "net/http" + "strings" + "kubesphere.io/kubesphere/pkg/constants" + "k8s.io/api/rbac/v1" +) + +func Register(ws *restful.WebService) { + //roles + ws.Route(ws.GET("/users/{username}/roles").To(userRolesHandler).Filter(route.RouteLogging)).Produces(restful.MIME_JSON) + //rules define + ws.Route(ws.GET("/roles/rules").To(roleRulesHandler).Filter(route.RouteLogging)).Produces(restful.MIME_JSON) + ws.Route(ws.GET("/clusterroles/rules").To(clusterRoleRulesHandler).Filter(route.RouteLogging)).Produces(restful.MIME_JSON) + //user->rules + ws.Route(ws.GET("/rules").To(usersRulesHandler).Filter(route.RouteLogging)).Produces(restful.MIME_JSON) + ws.Route(ws.GET("/users/{username}/rules").To(userRulesHandler).Filter(route.RouteLogging)).Produces(restful.MIME_JSON) + //role->rules + ws.Route(ws.GET("/clusterroles/{name}/rules").To(clusterRoleRulesHandler).Filter(route.RouteLogging)).Produces(restful.MIME_JSON) + ws.Route(ws.GET("/namespace/{namespace}/roles/{name}/rules").To(roleRulesHandler).Filter(route.RouteLogging)).Produces(restful.MIME_JSON) + //role->users + ws.Route(ws.GET("/namespaces/{namespace}/roles/{name}/users").To(roleUsersHandler).Filter(route.RouteLogging)).Produces(restful.MIME_JSON) + ws.Route(ws.GET("/clusterroles/{name}/users").To(clusterRoleUsersHandler).Filter(route.RouteLogging)).Produces(restful.MIME_JSON) +} + +// username -> roles +func userRolesHandler(req *restful.Request, resp *restful.Response) { + + username := req.PathParameter("username") + + roles, err := models.GetRoles(username) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + clusterRoles, err := models.GetClusterRoles(username) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + roleList := roleList{} + roleList.Roles = roles + roleList.ClusterRoles = clusterRoles + + resp.WriteEntity(roleList) +} + +// namespaces + role name -> users +func roleUsersHandler(req *restful.Request, resp *restful.Response) { + name := req.PathParameter("name") + namespace := req.PathParameter("namespace") + + roleBindings, err := models.GetRoleBindings(namespace, name) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + users := make([]string, 0) + + for _, roleBinding := range roleBindings { + for _, subject := range roleBinding.Subjects { + if subject.Kind == v1.UserKind { + users = append(users, subject.Name) + } + } + } + + resp.WriteEntity(users) +} + +// cluster role name -> users +func clusterRoleUsersHandler(req *restful.Request, resp *restful.Response) { + name := req.PathParameter("name") + + roleBindings, err := models.GetClusterRoleBindings(name) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + users := make([]string, 0) + + for _, roleBinding := range roleBindings { + for _, subject := range roleBinding.Subjects { + if subject.Kind == v1.UserKind { + users = append(users, subject.Name) + } + } + } + + resp.WriteEntity(users) +} + +// username -> rules +func usersRulesHandler(req *restful.Request, resp *restful.Response) { + users := strings.Split(req.QueryParameter("users"), ",") + + usersRules := make(map[string]userRuleList, 0) + + for _, username := range users { + _, contains := usersRules[username] + if username != "" && !contains { + + userRuleList := userRuleList{} + + clusterRules, err := getUserClusterRules(username) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + rules, err := getUserRules(username) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + userRuleList.ClusterRules = clusterRules + userRuleList.Rules = rules + + usersRules[username] = userRuleList + } + } + + resp.WriteEntity(usersRules) +} + +// username -> rules +func userRulesHandler(req *restful.Request, resp *restful.Response) { + username := req.PathParameter("username") + + userRuleList := userRuleList{} + + clusterRules, err := getUserClusterRules(username) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + rules, err := getUserRules(username) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + userRuleList.ClusterRules = clusterRules + userRuleList.Rules = rules + + resp.WriteEntity(userRuleList) +} + +// cluster role name -> rules +func clusterRoleRulesHandler(req *restful.Request, resp *restful.Response) { + + name := req.PathParameter("name") + + var rules []rule + + if name == "" { + rules = clusterRoleRuleGroup + } else { + var err error + rules, err = getClusterRoleRules(name) + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + } + + resp.WriteEntity(rules) +} + +// role name -> rules +func roleRulesHandler(req *restful.Request, resp *restful.Response) { + name := req.PathParameter("name") + namespace := req.PathParameter("namespace") + + var rules []rule + + if namespace == "" && name == "" { + rules = roleRuleGroup + } else { + var err error + rules, err = getRoleRules(namespace, name) + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + } + resp.WriteEntity(rules) +} diff --git a/pkg/apis/v1alpha/iam/policy.go b/pkg/apis/v1alpha/iam/policy.go new file mode 100644 index 000000000..9b6516985 --- /dev/null +++ b/pkg/apis/v1alpha/iam/policy.go @@ -0,0 +1,436 @@ +/* + 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" +) + +type roleList struct { + ClusterRoles []v1.ClusterRole `json:"clusterRoles" protobuf:"bytes,2,rep,name=clusterRoles"` + Roles []v1.Role `json:"roles" protobuf:"bytes,2,rep,name=roles"` +} + +type action struct { + Name string `json:"name"` + Rules []v1.PolicyRule `json:"rules"` +} + +type rule struct { + Name string `json:"name"` + Actions []action `json:"actions"` +} + +type userRuleList struct { + ClusterRules []rule `json:"clusterRules"` + Rules map[string][]rule `json:"rules"` +} + +// TODO design all frontend-facing rules +var ( + clusterRoleRuleGroup = []rule{projectsManagement, userManagement, roleManagement, registryManagement, + volumeManagement, storageclassManagement, nodeManagement, appCatalogManagement, appManagement} + + roleRuleGroup = []rule{deploymentManagement, projectManagement, statefulsetManagement, daemonsetManagement, + serviceManagement, routeManagement, pvcManagement} + + projectsManagement = rule{ + Name: "projectsManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + }, + }, + }, + {Name: "create", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + }, + }, + }, + {Name: "delete", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete", "deletecollection"}, + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + }, + }, + }, + }, + } + + userManagement = rule{ + Name: "userManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{"iam.kubesphere.io"}, + Resources: []string{"users"}, + }, + { + Verbs: []string{"get", "list"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings", "clusterrolebindings"}, + }, + }, + }, + {Name: "create", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"iam.kubesphere.io"}, + Resources: []string{"users"}, + }, + }, + }, + {Name: "edit", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"update", "patch"}, + APIGroups: []string{"iam.kubesphere.io"}, + Resources: []string{"users"}, + }, + }, + }, + {Name: "delete", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete", "deletecollection"}, + APIGroups: []string{"iam.kubesphere.io"}, + Resources: []string{"users"}, + }, + }, + }, + }, + } + + roleManagement = rule{ + Name: "roleManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles", "clusterroles"}, + }, + }, + }, + + {Name: "create", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles", "clusterroles"}, + }, + }, + }, + {Name: "edit", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"update", "patch"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles", "clusterroles"}, + }, + }, + }, + {Name: "delete", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete", "deletecollection"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles", "clusterroles"}, + }, + }, + }, + {Name: "roleBinding", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"create", "delete", "deletecollection"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings", "clusterrolebindings"}, + }, + }, + }, + }, + } + + nodeManagement = rule{ + Name: "nodeManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{""}, + Resources: []string{"nodes"}, + }, + }, + }, + }, + } + + volumeManagement = rule{ + Name: "volumeManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{""}, + Resources: []string{"persistentvolumes"}, + }, + }, + }, + }, + } + + storageclassManagement = rule{ + Name: "storageclassManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{"storage.k8s.io"}, + Resources: []string{"storageclasses"}, + }, + }, + }, + }, + } + + registryManagement = rule{ + Name: "registryManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{"extend.kubesphere.io"}, + Resources: []string{ + "registries", + }, + }, + }, + }, + }, + } + + appCatalogManagement = rule{ + Name: "appCatalogManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{"extend.kubesphere.io"}, + Resources: []string{"appcatalog"}, + }, + }, + }, + }, + } + + appManagement = rule{ + Name: "appManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{"extend.kubesphere.io"}, + Resources: []string{"apps"}, + }, + }, + }, + }, + } + + statefulsetManagement = rule{ + Name: "statefulsetManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{"apps"}, + Resources: []string{"statefulsets"}, + }, + }, + }, + }, + } + + daemonsetManagement = rule{ + Name: "daemonsetManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{"apps", "extensions"}, + Resources: []string{"daemonsets"}, + }, + }, + }, + }, + } + + serviceManagement = rule{ + Name: "serviceManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{""}, + Resources: []string{"services"}, + }, + }, + }, + }, + } + + routeManagement = rule{ + Name: "routeManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{"extensions"}, + Resources: []string{"ingresses"}, + }, + }, + }, + }, + } + pvcManagement = rule{ + Name: "pvcManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{""}, + Resources: []string{"persistentvolumeclaims"}, + }, + }, + }, + }, + } + + deploymentManagement = rule{ + Name: "deploymentManagement", + Actions: []action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list"}, + APIGroups: []string{"apps", "extensions"}, + Resources: []string{ + "deployments", + "deployments/rollback", + "deployments/scale", + }, + }, + }, + }, + {Name: "create", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"apps", "extensions"}, + Resources: []string{"deployments"}, + }, + }, + }, + {Name: "delete", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete", "deletecollection"}, + APIGroups: []string{"apps", "extensions"}, + Resources: []string{"deployments"}, + }, + }, + }, + {Name: "edit", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"update", "patch"}, + APIGroups: []string{"apps", "extensions"}, + Resources: []string{"deployments", "deployments/rollback"}, + }, + }, + }, + {Name: "scale", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"create", "update", "patch", "delete"}, + APIGroups: []string{"apps", "extensions"}, + Resources: []string{"deployments/scale"}, + }, + }, + }, + }, + } + projectManagement = rule{ + Name: "projectManagement", + Actions: []action{ + {Name: "memberManagement", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list", "create", "delete"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}, + }, + }, + }, + {Name: "memberRoleManagement", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "list", "create", "delete"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + }, + }, + }, + {Name: "delete", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete"}, + APIGroups: []string{"extend.kubesphere.io"}, + Resources: []string{"namespace"}, + }, + }, + }, + }, + } +) diff --git a/pkg/apis/v1alpha/iam/tools.go b/pkg/apis/v1alpha/iam/tools.go new file mode 100644 index 000000000..630059fb8 --- /dev/null +++ b/pkg/apis/v1alpha/iam/tools.go @@ -0,0 +1,226 @@ +/* + 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" + "k8s.io/kubernetes/pkg/util/slice" + "kubesphere.io/kubesphere/pkg/models" +) + +func getUserRules(username string) (map[string][]rule, error) { + + items := make(map[string][]rule, 0) + roles, err := models.GetRoles(username) + + if err != nil { + return nil, err + } + + namespaces := make([]string, 0) + + for i := 0; i < len(roles); i++ { + if !slice.ContainsString(namespaces, roles[i].Namespace, nil) { + namespaces = append(namespaces, roles[i].Namespace) + } + } + + for _, namespace := range namespaces { + rules := getMergeRules(namespace, roles) + if len(rules) > 0 { + items[namespace] = rules + } + } + + return items, nil +} + +func getMergeRules(namespace string, roles []v1.Role) []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++ { + permit := false + for _, role := range roles { + if role.Namespace == namespace && actionValidate(role.Rules, roleRuleGroup[i].Actions[j]) { + permit = true + break + } + } + if permit { + 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) + + roles, err := models.GetClusterRoles(username) + + if err != nil { + return nil, err + } + + 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++ { + actionPermit := false + for _, role := range roles { + if actionValidate(role.Rules, clusterRoleRuleGroup[i].Actions[j]) { + actionPermit = true + break + } + } + if actionPermit { + 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 := models.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 := models.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 +} + +func ruleValidate(rules []v1.PolicyRule, rule v1.PolicyRule) bool { + + for _, apiGroup := range rule.APIGroups { + if len(rule.NonResourceURLs) == 0 { + for _, resource := range rule.Resources { + + //if len(rule.ResourceNames) == 0 { + + for _, verb := range rule.Verbs { + if !verbValidate(rules, apiGroup, "", resource, "", verb) { + return false + } + } + + //} else { + // for _, resourceName := range rule.ResourceNames { + // for _, verb := range rule.Verbs { + // if !verbValidate(rules, apiGroup, "", resource, resourceName, verb) { + // return false + // } + // } + // } + //} + } + } else { + for _, nonResourceURL := range rule.NonResourceURLs { + for _, verb := range rule.Verbs { + if !verbValidate(rules, apiGroup, nonResourceURL, "", "", verb) { + return false + } + } + } + } + } + return true +} + +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) { + return true + } + } + } else if slice.ContainsString(rule.NonResourceURLs, nonResourceURL, nil) || slice.ContainsString(rule.NonResourceURLs, v1.NonResourceAll, nil) { + return true + } + } + } + } + return false +} diff --git a/pkg/apis/v1alpha/install.go b/pkg/apis/v1alpha/install.go index 6fc7f2862..3202c3106 100644 --- a/pkg/apis/v1alpha/install.go +++ b/pkg/apis/v1alpha/install.go @@ -26,6 +26,7 @@ import ( "kubesphere.io/kubesphere/pkg/apis/v1alpha/registries" "kubesphere.io/kubesphere/pkg/apis/v1alpha/storage" "kubesphere.io/kubesphere/pkg/apis/v1alpha/volumes" + "kubesphere.io/kubesphere/pkg/apis/v1alpha/iam" ) func init() { @@ -41,6 +42,7 @@ func init() { nodes.Register(ws, "/nodes") pods.Register(ws) containers.Register(ws) + iam.Register(ws) // add webservice to default container restful.Add(ws) diff --git a/pkg/apis/v1alpha/storage/storage_handler.go b/pkg/apis/v1alpha/storage/storage_handler.go index 0c935e881..fa4a70e50 100644 --- a/pkg/apis/v1alpha/storage/storage_handler.go +++ b/pkg/apis/v1alpha/storage/storage_handler.go @@ -4,16 +4,42 @@ import ( "github.com/emicklei/go-restful" "kubesphere.io/kubesphere/pkg/filter/route" "kubesphere.io/kubesphere/pkg/models" + "net/http" ) func Register(ws *restful.WebService, subPath string) { ws.Route(ws.GET(subPath+"/storageclasses/{storageclass}/persistentvolumeclaims"). - To(models.GetPvcListBySc).Filter(route.RouteLogging)). + To(GetPvcListBySc).Filter(route.RouteLogging)). Consumes(restful.MIME_JSON, restful.MIME_XML). Produces(restful.MIME_JSON) ws.Route(ws.GET(subPath+"/storageclasses/{storageclass}/metrics"). - To(models.GetScMetrics).Filter(route.RouteLogging)). + To(GetScMetrics).Filter(route.RouteLogging)). Consumes(restful.MIME_JSON, restful.MIME_XML). Produces(restful.MIME_JSON) } + +// List all PersistentVolumeClaims of a specific StorageClass +// Extended API URL: "GET /api/v1alpha/storage/storageclasses/{name}/persistentvolumeclaims" +func GetPvcListBySc(request *restful.Request, response *restful.Response) { + scName := request.PathParameter("storageclass") + claims, err := models.GetPvcListBySc(scName) + if err != nil { + response.WriteError(http.StatusInternalServerError, err) + } + result := models.PvcListBySc{scName, claims} + + response.WriteAsJson(result) +} + +// Get metrics of a specific StorageClass +// Extended API URL: "GET /api/v1alpha/storage/storageclasses/{name}/metrics" +func GetScMetrics(request *restful.Request, response *restful.Response) { + scName := request.PathParameter("storageclass") + metrics, err := models.GetScMetrics(scName) + if err != nil { + response.WriteError(http.StatusInternalServerError, err) + } + result := models.ScMetrics{Name: scName, Metrics: metrics} + response.WriteAsJson(result) +} diff --git a/pkg/apis/v1alpha/volumes/volumes_handler.go b/pkg/apis/v1alpha/volumes/volumes_handler.go index 8b7bbd95a..ff41a22bb 100644 --- a/pkg/apis/v1alpha/volumes/volumes_handler.go +++ b/pkg/apis/v1alpha/volumes/volumes_handler.go @@ -4,11 +4,25 @@ import ( "github.com/emicklei/go-restful" "kubesphere.io/kubesphere/pkg/filter/route" "kubesphere.io/kubesphere/pkg/models" + "net/http" ) func Register(ws *restful.WebService, subPath string) { ws.Route(ws.GET(subPath+"/namespaces/{namespace}/persistentvolumeclaims/{pvc}/pods"). - To(models.GetPodListByPvc).Filter(route.RouteLogging)). + To(GetPodListByPvc).Filter(route.RouteLogging)). Consumes(restful.MIME_JSON, restful.MIME_XML). Produces(restful.MIME_JSON) } + +// List all pods of a specific PVC +// Extended API URL: "GET /api/v1alpha/volumes/namespaces/{namespace}/persistentvolumeclaims/{name}/pods" +func GetPodListByPvc(request *restful.Request, response *restful.Response) { + pvcName := request.PathParameter("pvc") + nsName := request.PathParameter("namespace") + pods, err := models.GetPodListByPvc(pvcName, nsName) + if err != nil { + response.WriteError(http.StatusInternalServerError, err) + } + result := models.PodListByPvc{Name: pvcName, Namespace: nsName, Pods: pods} + response.WriteAsJson(result) +} diff --git a/pkg/filter/route/route_fliter.go b/pkg/filter/route/route_filter.go similarity index 90% rename from pkg/filter/route/route_fliter.go rename to pkg/filter/route/route_filter.go index 2981ed552..4b8261d36 100644 --- a/pkg/filter/route/route_fliter.go +++ b/pkg/filter/route/route_filter.go @@ -20,19 +20,21 @@ import ( "github.com/emicklei/go-restful" "github.com/golang/glog" "strings" + "time" ) // Route Filter (defines FilterFunction) func RouteLogging(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { + start := time.Now() chain.ProcessFilter(req, resp) - glog.Infof("%s - \"%s %s %s\" %d %d", + glog.Infof("%s - \"%s %s %s\" %d %dms", strings.Split(req.Request.RemoteAddr, ":")[0], req.Request.Method, req.Request.URL.RequestURI(), req.Request.Proto, resp.StatusCode(), - resp.ContentLength(), + time.Now().Sub(start)/1000000, ) } \ No newline at end of file diff --git a/pkg/models/roles.go b/pkg/models/roles.go new file mode 100644 index 000000000..5de506b9d --- /dev/null +++ b/pkg/models/roles.go @@ -0,0 +1,139 @@ +package models + +import ( + "k8s.io/api/rbac/v1" + "kubesphere.io/kubesphere/pkg/client" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ClusterRoleKind = "ClusterRole" + +func GetRole(namespace string, name string) (*v1.Role, error) { + k8s := client.NewK8sClient() + role, err := k8s.RbacV1().Roles(namespace).Get(name, meta_v1.GetOptions{}) + if err != nil { + return nil, err + } + return role, nil +} + +func GetClusterRoleBindings(name string) ([]v1.ClusterRoleBinding, error) { + k8s := client.NewK8sClient() + roleBindingList, err := k8s.RbacV1().ClusterRoleBindings().List(meta_v1.ListOptions{}) + + if err != nil { + return nil, err + } + + items := make([]v1.ClusterRoleBinding, 0) + + for _, roleBinding := range roleBindingList.Items { + if roleBinding.RoleRef.Name == name { + items = append(items, roleBinding) + } + } + + return roleBindingList.Items, nil +} + +func GetRoleBindings(namespace string, name string) ([]v1.RoleBinding, error) { + k8s := client.NewK8sClient() + + roleBindingList, err := k8s.RbacV1().RoleBindings(namespace).List(meta_v1.ListOptions{}) + + if err != nil { + return nil, err + } + + items := make([]v1.RoleBinding, 0) + + for _, roleBinding := range roleBindingList.Items { + if roleBinding.RoleRef.Name == name { + items = append(items, roleBinding) + } + } + + return roleBindingList.Items, nil +} + +func GetClusterRole(name string) (*v1.ClusterRole, error) { + k8s := client.NewK8sClient() + role, err := k8s.RbacV1().ClusterRoles().Get(name, meta_v1.GetOptions{}) + if err != nil { + return nil, err + } + return role, nil +} + +func GetRoles(username string) ([]v1.Role, error) { + k8s := client.NewK8sClient() + + roleBindings, err := k8s.RbacV1().RoleBindings("").List(meta_v1.ListOptions{}) + + if err != nil { + return nil, err + } + roles := make([]v1.Role, 0) + + for _, roleBinding := range roleBindings.Items { + for _, subject := range roleBinding.Subjects { + if subject.Kind == v1.UserKind && subject.Name == username { + if roleBinding.RoleRef.Kind == ClusterRoleKind { + + clusterRole, err := k8s.RbacV1().ClusterRoles().Get(roleBinding.RoleRef.Name, meta_v1.GetOptions{}) + + if err != nil { + return nil, err + } + + var role = v1.Role(*clusterRole) + role.Namespace = roleBinding.Namespace + + roles = append(roles, role) + + } else { + rule, err := k8s.RbacV1().Roles(roleBinding.Namespace).Get(roleBinding.RoleRef.Name, meta_v1.GetOptions{}) + + if err != nil { + return nil, err + } + + roles = append(roles, *rule) + } + } + } + } + + return roles, nil +} + +func GetClusterRoles(username string) ([]v1.ClusterRole, error) { + k8s := client.NewK8sClient() + + clusterRoleBindings, err := k8s.RbacV1().ClusterRoleBindings().List(meta_v1.ListOptions{}) + + if err != nil { + return nil, err + } + + roles := make([]v1.ClusterRole, 0) + + for _, roleBinding := range clusterRoleBindings.Items { + for _, subject := range roleBinding.Subjects { + if subject.Kind == v1.UserKind && subject.Name == username { + if roleBinding.RoleRef.Kind == ClusterRoleKind { + + rule, err := k8s.RbacV1().ClusterRoles().Get(roleBinding.RoleRef.Name, meta_v1.GetOptions{}) + + if err != nil { + return nil, err + } + + roles = append(roles, *rule) + } + } + } + } + + return roles, nil +} diff --git a/pkg/models/storage.go b/pkg/models/storage.go index 56e4cc2dc..10afddc91 100644 --- a/pkg/models/storage.go +++ b/pkg/models/storage.go @@ -1,61 +1,28 @@ package models import ( - "github.com/emicklei/go-restful" v12 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/apis/meta/v1" "kubesphere.io/kubesphere/pkg/client" - "kubesphere.io/kubesphere/pkg/constants" - "net/http" ) -type pvcListBySc struct { +type PvcListBySc struct { Name string `json:"name"` - Claims []v12.PersistentVolumeClaim `json:"persistentvolumeclaims"` + Claims []v12.PersistentVolumeClaim `json:"items"` } -type scMetrics struct { +type ScMetrics struct { Name string `json:"name"` - Metrics storageMetrics `json:"metrics"` + Metrics StorageMetrics `json:"metrics"` } -type storageMetrics struct { +type StorageMetrics struct { Capacity string `json:"capacity,omitempty"` Usage string `json:"usage,omitempty"` } -// List all PersistentVolumeClaims of a specific StorageClass -// Extended API URL: "GET /api/v1alpha/storage/storageclasses/{name}/persistentvolumeclaims" -func GetPvcListBySc(request *restful.Request, response *restful.Response) { - scName := request.PathParameter("storageclass") - claims, err := getPvcListBySc(scName) - if err != nil { - response.WriteError(http.StatusInternalServerError, err) - } - result := constants.ResultMessage{ - - Data: pvcListBySc{scName, claims}} - - response.WriteAsJson(result) -} - -// Get metrics of a specific StorageClass -// Extended API URL: "GET /api/v1alpha/storage/storageclasses/{name}/metrics" -func GetScMetrics(request *restful.Request, response *restful.Response) { - scName := request.PathParameter("storageclass") - metrics, err := getScMetrics(scName) - if err != nil { - response.WriteError(http.StatusInternalServerError, err) - } - result := constants.ResultMessage{ - - Data: scMetrics{Name: scName, Metrics: metrics}, - } - response.WriteAsJson(result) -} - -func getPvcListBySc(storageclass string) (res []v12.PersistentVolumeClaim, err error) { +func GetPvcListBySc(storageclass string) (res []v12.PersistentVolumeClaim, err error) { cli := client.NewK8sClient() claimList, err := cli.CoreV1().PersistentVolumeClaims("").List(v1.ListOptions{}) @@ -72,11 +39,11 @@ func getPvcListBySc(storageclass string) (res []v12.PersistentVolumeClaim, err e return res, nil } -func getScMetrics(storageclass string) (res storageMetrics, err error) { +func GetScMetrics(storageclass string) (res StorageMetrics, err error) { cli := client.NewK8sClient() pvList, err := cli.CoreV1().PersistentVolumes().List(v1.ListOptions{}) if err != nil { - return storageMetrics{}, err + return StorageMetrics{}, err } var total resource.Quantity @@ -86,5 +53,5 @@ func getScMetrics(storageclass string) (res storageMetrics, err error) { } total.Add(volume.Spec.Capacity[v12.ResourceStorage]) } - return storageMetrics{Usage: total.String()}, nil + return StorageMetrics{Usage: total.String()}, nil } diff --git a/pkg/models/volumes.go b/pkg/models/volumes.go index 77ed368c2..07a150901 100644 --- a/pkg/models/volumes.go +++ b/pkg/models/volumes.go @@ -1,39 +1,18 @@ package models import ( - "github.com/emicklei/go-restful" v12 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1" "kubesphere.io/kubesphere/pkg/client" - "kubesphere.io/kubesphere/pkg/constants" - "net/http" ) -type podListByPvc struct { +type PodListByPvc struct { Name string `json:"name"` Namespace string `json:"namespace"` Pods []v12.Pod `json:"pods"` } -// List all pods of a specific PVC -// Extended API URL: "GET /api/v1alpha/volumes/namespaces/{namespace}/persistentvolumeclaims/{name}/pods" -func GetPodListByPvc(request *restful.Request, response *restful.Response) { - - pvcName := request.PathParameter("pvc") - nsName := request.PathParameter("namespace") - pods, err := getPodListByPvc(pvcName, nsName) - if err != nil { - response.WriteError(http.StatusInternalServerError, err) - } - result := constants.ResultMessage{ - - Data: podListByPvc{ - Name: pvcName, Namespace: nsName, Pods: pods}} - - response.WriteAsJson(result) -} - -func getPodListByPvc(pvc string, ns string) (res []v12.Pod, err error) { +func GetPodListByPvc(pvc string, ns string) (res []v12.Pod, err error) { cli := client.NewK8sClient() podList, err := cli.CoreV1().Pods(ns).List(v1.ListOptions{}) if err != nil { @@ -41,14 +20,14 @@ func getPodListByPvc(pvc string, ns string) (res []v12.Pod, err error) { } for _, pod := range podList.Items { - if isPvcInPod(pod, pvc) == true { + if IsPvcInPod(pod, pvc) == true { res = append(res, pod) } } return res, nil } -func isPvcInPod(pod v12.Pod, pvcname string) bool { +func IsPvcInPod(pod v12.Pod, pvcname string) bool { for _, v := range pod.Spec.Volumes { if v.VolumeSource.PersistentVolumeClaim != nil && v.VolumeSource.PersistentVolumeClaim.ClaimName == pvcname {