diff --git a/pkg/kapis/iam/v1alpha2/handler.go b/pkg/kapis/iam/v1alpha2/handler.go index aee6a393c..df428a752 100644 --- a/pkg/kapis/iam/v1alpha2/handler.go +++ b/pkg/kapis/iam/v1alpha2/handler.go @@ -167,7 +167,7 @@ func (h *iamHandler) RetrieveMemberRoleTemplates(request *restful.Request, respo api.HandleInternalError(response, request, err) return } - templateRoles := make(map[string]*rbacv1.Role) + templateRoles := make(map[string]*iamv1alpha2.WorkspaceRole) for _, role := range workspaceRoles { // merge template Role result, err := h.am.ListWorkspaceRoles(&query.Query{ @@ -183,12 +183,12 @@ func (h *iamHandler) RetrieveMemberRoleTemplates(request *restful.Request, respo } for _, obj := range result.Items { - templateRole := obj.(*rbacv1.Role) + templateRole := obj.(*iamv1alpha2.WorkspaceRole) templateRoles[templateRole.Name] = templateRole } } - results := make([]*rbacv1.Role, 0, len(templateRoles)) + results := make([]*iamv1alpha2.WorkspaceRole, 0, len(templateRoles)) for _, value := range templateRoles { results = append(results, value) } @@ -904,7 +904,7 @@ func (h *iamHandler) CreateWorkspaceMembers(request *restful.Request, response * } for _, member := range members { - err := h.am.CreateWorkspaceRoleBinding(member.Username, workspace, member.RoleRef) + err := h.am.CreateUserWorkspaceRoleBinding(member.Username, workspace, member.RoleRef) if err != nil { klog.Error(err) handleError(request, response, err) @@ -948,7 +948,7 @@ func (h *iamHandler) UpdateWorkspaceMember(request *restful.Request, response *r return } - err = h.am.CreateWorkspaceRoleBinding(member.Username, workspace, member.RoleRef) + err = h.am.CreateUserWorkspaceRoleBinding(member.Username, workspace, member.RoleRef) if err != nil { klog.Error(err) handleError(request, response, err) @@ -1494,12 +1494,131 @@ func (h *iamHandler) ListGroupBindings(request *restful.Request, response *restf response.WriteEntity(result) } -func (h *iamHandler) ListGroupsRoleBinding(request *restful.Request, response *restful.Response) { - //todo +func (h *iamHandler) ListGroupRoleBindings(request *restful.Request, response *restful.Response) { + workspaceName := request.PathParameter("workspace") + groupName := request.PathParameter("group") + result, err := h.am.ListGroupRoleBindings(workspaceName, groupName) + + if err != nil { + klog.Error(err) + api.HandleInternalError(response, request, err) + return + } + + response.WriteEntity(result) } -func (h *iamHandler) ListGroupsWorkspaceRoleBinding(request *restful.Request, response *restful.Response) { - //todo +func (h *iamHandler) ListGroupDevOpsRoleBindings(request *restful.Request, response *restful.Response) { + workspaceName := request.PathParameter("workspace") + groupName := request.PathParameter("group") + result, err := h.am.ListGroupDevOpsRoleBindings(workspaceName, groupName) + + if err != nil { + klog.Error(err) + api.HandleInternalError(response, request, err) + return + } + + response.WriteEntity(result) +} + +func (h *iamHandler) CreateRoleBinding(request *restful.Request, response *restful.Response) { + namespace := request.PathParameter("namespace") + var roleBindings []rbacv1.RoleBinding + err := request.ReadEntity(&roleBindings) + if err != nil { + klog.Error(err) + api.HandleBadRequest(response, request, err) + return + } + + results := []rbacv1.RoleBinding{} + for _, item := range roleBindings { + r, err := h.am.CreateRoleBinding(namespace, &item) + if err != nil { + klog.Error(err) + handleError(request, response, err) + return + } + results = append(results, *r) + } + + response.WriteEntity(results) +} + +func (h *iamHandler) DeleteRoleBinding(request *restful.Request, response *restful.Response) { + name := request.PathParameter("rolebinding") + namespace := request.PathParameter("namespace") + + err := h.am.DeleteRoleBinding(namespace, name) + + if err != nil { + if errors.IsNotFound(err) { + api.HandleNotFound(response, request, err) + return + } + api.HandleInternalError(response, request, err) + return + } + + response.WriteEntity(servererr.None) +} + +func (h *iamHandler) ListGroupWorkspaceRoleBindings(request *restful.Request, response *restful.Response) { + workspaceName := request.PathParameter("workspace") + groupName := request.PathParameter("group") + result, err := h.am.ListGroupWorkspaceRoleBindings(workspaceName, groupName) + + if err != nil { + klog.Error(err) + api.HandleInternalError(response, request, err) + return + } + + response.WriteEntity(result) +} + +func (h *iamHandler) CreateWorkspaceRoleBinding(request *restful.Request, response *restful.Response) { + workspaceName := request.PathParameter("workspace") + + var roleBindings []iamv1alpha2.WorkspaceRoleBinding + err := request.ReadEntity(&roleBindings) + if err != nil { + klog.Error(err) + api.HandleBadRequest(response, request, err) + return + } + + results := []iamv1alpha2.WorkspaceRoleBinding{} + for _, item := range roleBindings { + r, err := h.am.CreateWorkspaceRoleBinding(workspaceName, &item) + if err != nil { + klog.Error(err) + handleError(request, response, err) + return + } + results = append(results, *r) + } + + response.WriteEntity(results) +} + +func (h *iamHandler) DeleteWorkspaceRoleBinding(request *restful.Request, response *restful.Response) { + workspaceName := request.PathParameter("workspace") + name := request.PathParameter("rolebinding") + + err := h.am.DeleteWorkspaceRoleBinding(workspaceName, name) + + if err != nil { + if errors.IsNotFound(err) { + api.HandleNotFound(response, request, err) + return + } + api.HandleInternalError(response, request, err) + return + } + + response.WriteEntity(servererr.None) } func (h *iamHandler) CreateGroupBinding(request *restful.Request, response *restful.Response) { @@ -1514,16 +1633,19 @@ func (h *iamHandler) CreateGroupBinding(request *restful.Request, response *rest return } + results := []iamv1alpha2.GroupBinding{} + for _, item := range members { - err := h.group.CreateGroupBinding(workspace, item.GroupName, item.UserName) + b, err := h.group.CreateGroupBinding(workspace, item.GroupName, item.UserName) if err != nil { klog.Error(err) handleError(request, response, err) return } + results = append(results, *b) } - response.WriteEntity(members) + response.WriteEntity(results) } func (h *iamHandler) DeleteGroupBinding(request *restful.Request, response *restful.Response) { diff --git a/pkg/kapis/iam/v1alpha2/register.go b/pkg/kapis/iam/v1alpha2/register.go index 3f942e958..2f62ee717 100644 --- a/pkg/kapis/iam/v1alpha2/register.go +++ b/pkg/kapis/iam/v1alpha2/register.go @@ -22,6 +22,7 @@ import ( "github.com/emicklei/go-restful" restfulspec "github.com/emicklei/go-restful-openapi" rbacv1 "k8s.io/api/rbac/v1" + v1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/runtime/schema" "kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api/iam" @@ -527,21 +528,29 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf Metadata(restfulspec.KeyOpenAPITags, []string{constants.GroupTag})) ws.Route(ws.GET("/workspaces/{workspace}/groups/{group}/rolebindings"). - To(handler.ListGroupsRoleBinding). + To(handler.ListGroupRoleBindings). Param(ws.PathParameter("workspace", "workspace name")). Param(ws.PathParameter("group", "group name")). Doc("Retrieve group's rolebindings of all projects in the workspace."). Returns(http.StatusOK, api.StatusOK, api.ListResult{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.GroupTag})) - ws.Route(ws.GET("/workspaces/{workspace}/groups/{group}/workspacerolebinding"). - To(handler.ListGroupsWorkspaceRoleBinding). + ws.Route(ws.GET("/workspaces/{workspace}/groups/{group}/workspacerolebindings"). + To(handler.ListGroupWorkspaceRoleBindings). Param(ws.PathParameter("workspace", "workspace name")). Param(ws.PathParameter("group", "group name")). Doc("Retrieve group's workspacerolebindings of the workspace."). Returns(http.StatusOK, api.StatusOK, api.ListResult{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.GroupTag})) + ws.Route(ws.GET("/workspaces/{workspace}/groups/{group}/devopsrolebindings"). + To(handler.ListGroupDevOpsRoleBindings). + Param(ws.PathParameter("workspace", "workspace name")). + Param(ws.PathParameter("group", "group name")). + Doc("Retrieve group's rolebindings of all devops projects in the workspace."). + Returns(http.StatusOK, api.StatusOK, api.ListResult{}). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.GroupTag})) + ws.Route(ws.DELETE("/workspaces/{workspace}/groupbindings/{groupbinding}"). To(handler.DeleteGroupBinding). Param(ws.PathParameter("workspace", "workspace name")). @@ -558,6 +567,41 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf Returns(http.StatusOK, api.StatusOK, iamv1alpha2.GroupBinding{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.GroupTag})) + // namespace rolebinding + ws.Route(ws.POST("/namespaces/{namespace}/rolebindings"). + To(handler.CreateRoleBinding). + Doc("Create rolebinding in the specified namespace."). + Reads([]v1.RoleBinding{}). + Param(ws.PathParameter("namespace", "namespace")). + Returns(http.StatusOK, api.StatusOK, []v1.RoleBinding{}). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.NamespaceRoleTag})) + + ws.Route(ws.DELETE("/namespace/{namespace}/rolebindings/{rolebinding}"). + To(handler.DeleteRoleBinding). + Param(ws.PathParameter("workspace", "workspace name")). + Param(ws.PathParameter("namespace", "groupbinding name")). + Param(ws.PathParameter("rolebinding", "groupbinding name")). + Doc("Delete rolebinding under namespace."). + Returns(http.StatusOK, api.StatusOK, errors.None). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.GroupTag})) + + // workspace rolebinding + ws.Route(ws.POST("/workspaces/{workspace}/workspacerolebindings"). + To(handler.CreateWorkspaceRoleBinding). + Param(ws.PathParameter("workspace", "workspace name")). + Reads([]iamv1alpha2.WorkspaceRoleBinding{}). + Doc("Create group's workspacerolebindings of the workspace."). + Returns(http.StatusOK, api.StatusOK, []iamv1alpha2.WorkspaceRoleBinding{}). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.GroupTag})) + + ws.Route(ws.DELETE("/workspaces/{workspace}/workspacerolebindings/{rolebinding}"). + To(handler.DeleteWorkspaceRoleBinding). + Param(ws.PathParameter("workspace", "workspace name")). + Param(ws.PathParameter("rolebinding", "groupbinding name")). + Doc("Delete workspacerolebinding."). + Returns(http.StatusOK, api.StatusOK, errors.None). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.GroupTag})) + container.Add(ws) return nil } diff --git a/pkg/models/iam/am/am.go b/pkg/models/iam/am/am.go index 4ad733ee2..a3e6ad28d 100644 --- a/pkg/models/iam/am/am.go +++ b/pkg/models/iam/am/am.go @@ -66,7 +66,7 @@ type AccessManagementInterface interface { GetNamespaceRole(namespace string, name string) (*rbacv1.Role, error) CreateOrUpdateNamespaceRole(namespace string, role *rbacv1.Role) (*rbacv1.Role, error) DeleteNamespaceRole(namespace string, name string) error - CreateWorkspaceRoleBinding(username string, workspace string, role string) error + CreateUserWorkspaceRoleBinding(username string, workspace string, role string) error RemoveUserFromWorkspace(username string, workspace string) error CreateNamespaceRoleBinding(username string, namespace string, role string) error RemoveUserFromNamespace(username string, namespace string) error @@ -77,6 +77,13 @@ type AccessManagementInterface interface { GetDevOpsControlledWorkspace(devops string) (string, error) PatchNamespaceRole(namespace string, role *rbacv1.Role) (*rbacv1.Role, error) PatchClusterRole(clusterRole *rbacv1.ClusterRole) (*rbacv1.ClusterRole, error) + ListGroupRoleBindings(workspace, group string) ([]*rbacv1.RoleBinding, error) + ListGroupDevOpsRoleBindings(workspace, group string) ([]*rbacv1.RoleBinding, error) + CreateRoleBinding(namespace string, roleBinding *rbacv1.RoleBinding) (*rbacv1.RoleBinding, error) + DeleteRoleBinding(namespace, name string) error + ListGroupWorkspaceRoleBindings(group string, workspace string) ([]*iamv1alpha2.WorkspaceRoleBinding, error) + CreateWorkspaceRoleBinding(workspace string, roleBinding *iamv1alpha2.WorkspaceRoleBinding) (*iamv1alpha2.WorkspaceRoleBinding, error) + DeleteWorkspaceRoleBinding(workspaceName, name string) error } type amOperator struct { @@ -568,7 +575,7 @@ func (am *amOperator) PatchClusterRole(clusterRole *rbacv1.ClusterRole) (*rbacv1 return am.k8sclient.RbacV1().ClusterRoles().Patch(clusterRole.Name, types.MergePatchType, data) } -func (am *amOperator) CreateWorkspaceRoleBinding(username string, workspace string, role string) error { +func (am *amOperator) CreateUserWorkspaceRoleBinding(username string, workspace string, role string) error { _, err := am.GetWorkspaceRole(workspace, role) if err != nil { klog.Error(err) @@ -998,3 +1005,155 @@ func (am *amOperator) GetNamespaceControlledWorkspace(namespace string) (string, ns := obj.(*corev1.Namespace) return ns.Labels[tenantv1alpha1.WorkspaceLabel], nil } + +func (am *amOperator) ListGroupWorkspaceRoleBindings(workspace, group string) ([]*iamv1alpha2.WorkspaceRoleBinding, error) { + q := workspaceQuery(workspace) + roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralWorkspaceRoleBinding, "", q) + + if err != nil { + return nil, err + } + + result := make([]*iamv1alpha2.WorkspaceRoleBinding, 0) + + for _, obj := range roleBindings.Items { + roleBinding := obj.(*iamv1alpha2.WorkspaceRoleBinding) + inSpecifiedWorkspace := workspace == "" || roleBinding.Labels[tenantv1alpha1.WorkspaceLabel] == workspace + if containsgroup(roleBinding.Subjects, group) && inSpecifiedWorkspace { + result = append(result, roleBinding) + } + } + + return result, nil +} + +func (am *amOperator) CreateWorkspaceRoleBinding(workspace string, roleBinding *iamv1alpha2.WorkspaceRoleBinding) (*iamv1alpha2.WorkspaceRoleBinding, error) { + + _, err := am.GetWorkspaceRole(workspace, roleBinding.RoleRef.Name) + if err != nil { + klog.Error(err) + return nil, err + } + + if len(roleBinding.Subjects) == 0 { + err := errors.NewNotFound(iamv1alpha2.Resource(iamv1alpha2.ResourcesPluralUser), "") + return nil, err + } + + roleBinding.GenerateName = fmt.Sprintf("%s-%s-", roleBinding.Subjects[0].Name, roleBinding.RoleRef.Name) + + if roleBinding.Labels == nil { + roleBinding.Labels = map[string]string{} + } + + if roleBinding.Subjects[0].Kind == rbacv1.GroupKind { + roleBinding.Labels[iamv1alpha2.GroupReferenceLabel] = roleBinding.RoleRef.Name + } else if roleBinding.Subjects[0].Kind == rbacv1.UserKind { + roleBinding.Labels[iamv1alpha2.UserReferenceLabel] = roleBinding.RoleRef.Name + } + + roleBinding.Labels[tenantv1alpha1.WorkspaceLabel] = workspace + + return am.ksclient.IamV1alpha2().WorkspaceRoleBindings().Create(roleBinding) + +} +func (am *amOperator) DeleteWorkspaceRoleBinding(workspaceName, name string) error { + return am.ksclient.IamV1alpha2().WorkspaceRoleBindings().Delete(name, metav1.NewDeleteOptions(0)) +} + +func (am *amOperator) ListGroupRoleBindings(workspace, group string) ([]*rbacv1.RoleBinding, error) { + q := workspaceQuery(workspace) + namespaces, err := am.resourceGetter.List("namespaces", "", q) + if err != nil { + return nil, err + } + result := make([]*rbacv1.RoleBinding, 0) + for _, ns := range namespaces.Items { + namespace := ns.(*corev1.Namespace) + roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralRoleBinding, namespace.Name, query.New()) + if err != nil { + klog.Error(err) + return nil, err + } + + for _, obj := range roleBindings.Items { + roleBinding := obj.(*rbacv1.RoleBinding) + if containsgroup(roleBinding.Subjects, group) { + result = append(result, roleBinding) + } + } + } + return result, nil +} + +func (am *amOperator) ListGroupDevOpsRoleBindings(workspace, group string) ([]*rbacv1.RoleBinding, error) { + q := workspaceQuery(workspace) + namespaces, err := am.resourceGetter.List(devopsv1alpha3.ResourcePluralDevOpsProject, "", q) + if err != nil { + return nil, err + } + result := make([]*rbacv1.RoleBinding, 0) + for _, ns := range namespaces.Items { + namespace := ns.(*devopsv1alpha3.DevOpsProject) + roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralRoleBinding, namespace.Name, query.New()) + if err != nil { + klog.Error(err) + return nil, err + } + + for _, obj := range roleBindings.Items { + roleBinding := obj.(*rbacv1.RoleBinding) + if containsgroup(roleBinding.Subjects, group) { + result = append(result, roleBinding) + } + } + } + return result, nil +} + +func (am *amOperator) CreateRoleBinding(namespace string, roleBinding *rbacv1.RoleBinding) (*rbacv1.RoleBinding, error) { + + _, err := am.GetNamespaceRole(namespace, roleBinding.RoleRef.Name) + if err != nil { + klog.Error(err) + return nil, err + } + + if len(roleBinding.Subjects) == 0 { + err := errors.NewNotFound(iamv1alpha2.Resource(iamv1alpha2.ResourcesPluralUser), "") + return nil, err + } + + roleBinding.GenerateName = fmt.Sprintf("%s-%s-", roleBinding.Subjects[0].Name, roleBinding.RoleRef.Name) + + if roleBinding.Labels == nil { + roleBinding.Labels = map[string]string{} + } + + if roleBinding.Subjects[0].Kind == rbacv1.GroupKind { + roleBinding.Labels[iamv1alpha2.GroupReferenceLabel] = roleBinding.Subjects[0].Name + } else if roleBinding.Subjects[0].Kind == rbacv1.UserKind { + roleBinding.Labels[iamv1alpha2.UserReferenceLabel] = roleBinding.Subjects[0].Name + } + + return am.k8sclient.RbacV1().RoleBindings(namespace).Create(roleBinding) +} + +func (am *amOperator) DeleteRoleBinding(namespace, name string) error { + return am.k8sclient.RbacV1().RoleBindings(namespace).Delete(name, metav1.NewDeleteOptions(0)) +} + +func containsgroup(subjects []rbacv1.Subject, group string) bool { + for _, subject := range subjects { + if subject.Kind == rbacv1.GroupKind && subject.Name == group { + return true + } + } + return false +} + +func workspaceQuery(workspace string) *query.Query { + q := query.New() + q.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", tenantv1alpha1.WorkspaceLabel, workspace)) + return q +} diff --git a/pkg/models/iam/group/group.go b/pkg/models/iam/group/group.go index 1bdb3c159..2e357fa6e 100644 --- a/pkg/models/iam/group/group.go +++ b/pkg/models/iam/group/group.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/client-go/kubernetes" "k8s.io/klog" "kubesphere.io/kubesphere/pkg/api" @@ -43,7 +44,7 @@ type GroupOperator interface { UpdateGroup(workspace string, group *iamv1alpha2.Group) (*iamv1alpha2.Group, error) PatchGroup(workspace string, group *iamv1alpha2.Group) (*iamv1alpha2.Group, error) DeleteGroupBinding(workspace, name string) error - CreateGroupBinding(workspace, groupName, userName string) error + CreateGroupBinding(workspace, groupName, userName string) (*iamv1alpha2.GroupBinding, error) ListGroupBindings(workspace, group string, queryParam *query.Query) (*api.ListResult, error) } @@ -77,8 +78,43 @@ func (t *groupOperator) ListGroups(workspace string, queryParam *query.Query) (* } // CreateGroup adds a workspace label to group which indicates group is under the workspace -func (t *groupOperator) CreateGroup(workspace string, namespace *iamv1alpha2.Group) (*iamv1alpha2.Group, error) { - return t.ksclient.IamV1alpha2().Groups().Create(labelGroupWithWorkspaceName(namespace, workspace)) +func (t *groupOperator) CreateGroup(workspace string, group *iamv1alpha2.Group) (*iamv1alpha2.Group, error) { + + if group.GenerateName == "" { + err := errors.NewInvalid(iamv1alpha2.SchemeGroupVersion.WithKind(iamv1alpha2.ResourcePluralGroup).GroupKind(), + "", []*field.Error{field.Required(field.NewPath("metadata.generateName"), "generateName is required")}) + klog.Error(err) + return nil, err + } + // generateName is used as displayName + // ensure generateName is unique in workspace scope + if unique, err := t.isGenerateNameUnique(workspace, group.GenerateName); err != nil { + return nil, err + } else if !unique { + err = errors.NewConflict(iamv1alpha2.Resource(iamv1alpha2.ResourcePluralGroup), + group.GenerateName, fmt.Errorf("a group named %s already exists in the workspace", group.GenerateName)) + klog.Error(err) + return nil, err + } + + return t.ksclient.IamV1alpha2().Groups().Create(labelGroupWithWorkspaceName(group, workspace)) +} + +func (t *groupOperator) isGenerateNameUnique(workspace, generateName string) (bool, error) { + + result, err := t.ListGroups(workspace, query.New()) + + if err != nil { + klog.Error(err) + return false, err + } + for _, obj := range result.Items { + g := obj.(*iamv1alpha2.Group) + if g.GenerateName == generateName { + return false, err + } + } + return true, nil } func (t *groupOperator) DescribeGroup(workspace, group string) (*iamv1alpha2.Group, error) { @@ -142,11 +178,11 @@ func (t *groupOperator) DeleteGroupBinding(workspace, name string) error { return t.ksclient.IamV1alpha2().GroupBindings().Delete(name, metav1.NewDeleteOptions(0)) } -func (t *groupOperator) CreateGroupBinding(workspace, groupName, userName string) error { +func (t *groupOperator) CreateGroupBinding(workspace, groupName, userName string) (*iamv1alpha2.GroupBinding, error) { groupBinding := iamv1alpha2.GroupBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-%s", groupName, userName), + GenerateName: fmt.Sprintf("%s-%s-", groupName, userName), Labels: map[string]string{ iamv1alpha2.UserReferenceLabel: userName, iamv1alpha2.GroupReferenceLabel: groupName, @@ -161,11 +197,7 @@ func (t *groupOperator) CreateGroupBinding(workspace, groupName, userName string }, } - if _, err := t.ksclient.IamV1alpha2().GroupBindings().Create(&groupBinding); err != nil { - return err - } - - return nil + return t.ksclient.IamV1alpha2().GroupBindings().Create(&groupBinding) } func (t *groupOperator) ListGroupBindings(workspace, group string, queryParam *query.Query) (*api.ListResult, error) {