diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index ea6e11765..db980a35b 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -36,6 +36,7 @@ const ( WorkspaceLabelKey = "kubesphere.io/workspace" DisplayNameAnnotationKey = "displayName" + DescriptionAnnotationKey = "desc" CreatorLabelAnnotationKey = "creator" OpenPitrixRuntimeAnnotationKey = "openpitrix_runtime" WorkspaceAdmin = "workspace-admin" diff --git a/pkg/controller/clusterrolebinding/clusterrolebinding_controller.go b/pkg/controller/clusterrolebinding/clusterrolebinding_controller.go index 1ce0cc06f..e16c76088 100644 --- a/pkg/controller/clusterrolebinding/clusterrolebinding_controller.go +++ b/pkg/controller/clusterrolebinding/clusterrolebinding_controller.go @@ -40,7 +40,7 @@ import ( ) var ( - log = logf.Log.WithName("controller") + log = logf.Log.WithName("clusterrolebinding-controller") ) /** diff --git a/pkg/controller/job/job_controller.go b/pkg/controller/job/job_controller.go index 4e730f908..409b62524 100644 --- a/pkg/controller/job/job_controller.go +++ b/pkg/controller/job/job_controller.go @@ -272,9 +272,7 @@ func (v *JobController) getCurrentRevision(item *batchv1.Job) JobRevision { revision.Status = Failed revision.Reasons = append(revision.Reasons, condition.Reason) revision.Messages = append(revision.Messages, condition.Message) - } - - if condition.Type == batchv1.JobComplete && condition.Status == v1.ConditionTrue { + } else if condition.Type == batchv1.JobComplete && condition.Status == v1.ConditionTrue { revision.Status = Completed } } diff --git a/pkg/controller/namespace/namespace_controller.go b/pkg/controller/namespace/namespace_controller.go index 45b21cb74..59cd09878 100644 --- a/pkg/controller/namespace/namespace_controller.go +++ b/pkg/controller/namespace/namespace_controller.go @@ -45,13 +45,19 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" ) +const ( + adminDescription = "Allows admin access to perform any action on any resource, it gives full control over every resource in the namespace." + operatorDescription = "The maintainer of the namespace who can manage resources other than users and roles in the namespace." + viewerDescription = "Allows viewer access to view all resources in the namespace." +) + var ( - log = logf.Log.WithName("controller") + log = logf.Log.WithName("namespace-controller") defaultRoles = []rbac.Role{ - {ObjectMeta: metav1.ObjectMeta{Name: "admin"}, Rules: []rbac.PolicyRule{{Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "operator"}, Rules: []rbac.PolicyRule{{Verbs: []string{"get", "list", "watch"}, APIGroups: []string{"*"}, Resources: []string{"*"}}, - {Verbs: []string{"*"}, APIGroups: []string{"", "apps", "extensions", "batch", "logging.kubesphere.io", "monitoring.kubesphere.io", "iam.kubesphere.io", "resources.kubesphere.io", "autoscaling"}, Resources: []string{"*"}}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "viewer"}, Rules: []rbac.PolicyRule{{Verbs: []string{"get", "list", "watch"}, APIGroups: []string{"*"}, Resources: []string{"*"}}}}, + {ObjectMeta: metav1.ObjectMeta{Name: "admin", Annotations: map[string]string{constants.DescriptionAnnotationKey: adminDescription}}, Rules: []rbac.PolicyRule{{Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}}}}, + {ObjectMeta: metav1.ObjectMeta{Name: "operator", Annotations: map[string]string{constants.DescriptionAnnotationKey: operatorDescription}}, Rules: []rbac.PolicyRule{{Verbs: []string{"get", "list", "watch"}, APIGroups: []string{"*"}, Resources: []string{"*"}}, + {Verbs: []string{"*"}, APIGroups: []string{"", "apps", "extensions", "batch", "logging.kubesphere.io", "monitoring.kubesphere.io", "iam.kubesphere.io", "resources.kubesphere.io", "autoscaling", "alerting.kubesphere.io"}, Resources: []string{"*"}}}}, + {ObjectMeta: metav1.ObjectMeta{Name: "viewer", Annotations: map[string]string{constants.DescriptionAnnotationKey: viewerDescription}}, Rules: []rbac.PolicyRule{{Verbs: []string{"get", "list", "watch"}, APIGroups: []string{"*"}, Resources: []string{"*"}}}}, } ) diff --git a/pkg/controller/workspace/workspace_controller.go b/pkg/controller/workspace/workspace_controller.go index dc0598352..0ff7597aa 100644 --- a/pkg/controller/workspace/workspace_controller.go +++ b/pkg/controller/workspace/workspace_controller.go @@ -21,8 +21,11 @@ package workspace import ( "context" "fmt" + corev1 "k8s.io/api/core/v1" rbac "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" @@ -42,7 +45,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" ) -var log = logf.Log.WithName("controller") +const ( + workspaceAdminDescription = "Allows admin access to perform any action on any resource, it gives full control over every resource in the workspace." + workspaceRegularDescription = "Normal user in the workspace, can create namespace and DevOps project." + workspaceViewerDescription = "Allows viewer access to view all resources in the workspace." +) + +var log = logf.Log.WithName("workspace-controller") /** * USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller @@ -161,6 +170,10 @@ func (r *ReconcileWorkspace) Reconcile(request reconcile.Request) (reconcile.Res return reconcile.Result{}, err } + if err = r.bindNamespaces(instance); err != nil { + return reconcile.Result{}, err + } + return reconcile.Result{}, nil } @@ -442,6 +455,33 @@ func (r *ReconcileWorkspace) createWorkspaceRoleBindings(instance *tenantv1alpha return nil } +func (r *ReconcileWorkspace) bindNamespaces(instance *tenantv1alpha1.Workspace) error { + + nsList := &corev1.NamespaceList{} + options := client.ListOptions{LabelSelector: labels.SelectorFromSet(labels.Set{constants.WorkspaceLabelKey: instance.Name})} + err := r.List(context.TODO(), &options, nsList) + + if err != nil { + log.Error(err, fmt.Sprintf("list workspace %s namespace failed", instance.Name)) + return err + } + + for _, namespace := range nsList.Items { + if !metav1.IsControlledBy(&namespace, instance) { + if err := controllerutil.SetControllerReference(instance, &namespace, r.scheme); err != nil { + return err + } + log.Info("Bind workspace", "namespace", namespace.Name, "workspace", instance.Name) + err = r.Update(context.TODO(), &namespace) + if err != nil { + return err + } + } + } + + return nil +} + func hasSubject(subjects []rbac.Subject, user rbac.Subject) bool { for _, subject := range subjects { if reflect.DeepEqual(subject, user) { @@ -477,7 +517,7 @@ func getWorkspaceAdmin(workspaceName string) *rbac.ClusterRole { admin := &rbac.ClusterRole{} admin.Name = getWorkspaceAdminRoleName(workspaceName) admin.Labels = map[string]string{constants.WorkspaceLabelKey: workspaceName} - admin.Annotations = map[string]string{constants.DisplayNameAnnotationKey: constants.WorkspaceAdmin} + admin.Annotations = map[string]string{constants.DisplayNameAnnotationKey: constants.WorkspaceAdmin, constants.DescriptionAnnotationKey: workspaceAdminDescription} admin.Rules = []rbac.PolicyRule{ { Verbs: []string{"*"}, @@ -499,7 +539,7 @@ func getWorkspaceRegular(workspaceName string) *rbac.ClusterRole { regular := &rbac.ClusterRole{} regular.Name = getWorkspaceRegularRoleName(workspaceName) regular.Labels = map[string]string{constants.WorkspaceLabelKey: workspaceName} - regular.Annotations = map[string]string{constants.DisplayNameAnnotationKey: constants.WorkspaceRegular} + regular.Annotations = map[string]string{constants.DisplayNameAnnotationKey: constants.WorkspaceRegular, constants.DescriptionAnnotationKey: workspaceRegularDescription} regular.Rules = []rbac.PolicyRule{ { Verbs: []string{"get"}, @@ -527,7 +567,7 @@ func getWorkspaceViewer(workspaceName string) *rbac.ClusterRole { viewer := &rbac.ClusterRole{} viewer.Name = getWorkspaceViewerRoleName(workspaceName) viewer.Labels = map[string]string{constants.WorkspaceLabelKey: workspaceName} - viewer.Annotations = map[string]string{constants.DisplayNameAnnotationKey: constants.WorkspaceViewer} + viewer.Annotations = map[string]string{constants.DisplayNameAnnotationKey: constants.WorkspaceViewer, constants.DescriptionAnnotationKey: workspaceViewerDescription} viewer.Rules = []rbac.PolicyRule{ { Verbs: []string{"get", "list"}, diff --git a/pkg/models/iam/am.go b/pkg/models/iam/am.go index 6e22357de..ccf1319d1 100644 --- a/pkg/models/iam/am.go +++ b/pkg/models/iam/am.go @@ -509,16 +509,13 @@ func GetWorkspaceRoleSimpleRules(workspace, roleName string) []models.SimpleRule {Name: "members", Actions: []string{"edit", "delete", "create", "view"}}, {Name: "devops", Actions: []string{"edit", "delete", "create", "view"}}, {Name: "projects", Actions: []string{"edit", "delete", "create", "view"}}, - {Name: "organizations", Actions: []string{"edit", "delete", "create", "view"}}, {Name: "roles", Actions: []string{"view"}}, } case constants.WorkspaceRegular: workspaceRules = []models.SimpleRule{ - {Name: "workspaces", Actions: []string{"view"}}, {Name: "members", Actions: []string{"view"}}, {Name: "devops", Actions: []string{"create"}}, {Name: "projects", Actions: []string{"create"}}, - {Name: "organizations", Actions: []string{"view"}}, } case constants.WorkspaceViewer: workspaceRules = []models.SimpleRule{ @@ -526,7 +523,6 @@ func GetWorkspaceRoleSimpleRules(workspace, roleName string) []models.SimpleRule {Name: "members", Actions: []string{"view"}}, {Name: "devops", Actions: []string{"view"}}, {Name: "projects", Actions: []string{"view"}}, - {Name: "organizations", Actions: []string{"view"}}, {Name: "roles", Actions: []string{"view"}}, } } diff --git a/pkg/models/iam/im.go b/pkg/models/iam/im.go index f0be92b07..087d6c9b2 100644 --- a/pkg/models/iam/im.go +++ b/pkg/models/iam/im.go @@ -53,10 +53,16 @@ var ( adminEmail string adminPassword string tokenExpireTime time.Duration + initUsers []initUser ) +type initUser struct { + models.User + Hidden bool `json:"hidden"` +} + const ( - userInitFile = "/etc/ks-iam/users.json" + userInitFile = "/Users/hongming/users.json" ) func Init(email, password string, t time.Duration) error { @@ -121,11 +127,11 @@ func checkAndCreateDefaultUser(conn ldap.Client) error { ldapclient.UserSearchBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, "(&(objectClass=inetOrgPerson))", - nil, + []string{"uid"}, nil, ) - users, err := conn.Search(userSearchRequest) + result, err := conn.Search(userSearchRequest) if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { err = createUserBaseDN(conn) @@ -139,16 +145,16 @@ func checkAndCreateDefaultUser(conn ldap.Client) error { } data, err := ioutil.ReadFile(userInitFile) - var initUsers []models.User if err == nil { json.Unmarshal(data, &initUsers) } - initUsers = append(initUsers, models.User{Username: constants.AdminUserName, Email: adminEmail, Password: adminPassword, Description: "Administrator account that was always created by default.", ClusterRole: constants.ClusterAdmin}) + initUsers = append(initUsers, initUser{User: models.User{Username: constants.AdminUserName, Email: adminEmail, Password: adminPassword, Description: "Administrator account that was always created by default.", ClusterRole: constants.ClusterAdmin}}) - if users == nil || len(users.Entries) < len(initUsers) { - for _, user := range initUsers { - _, err = CreateUser(&user) + for _, user := range initUsers { + if result == nil || !containsUser(result.Entries, user) { + _, err = CreateUser(&user.User) if err != nil && !ldap.IsErrorWithCode(err, ldap.LDAPResultEntryAlreadyExists) { + glog.Errorln("user init failed", user.Username, err) return fmt.Errorf("user %s init failed: %s\n", user.Username, err) } } @@ -157,6 +163,16 @@ func checkAndCreateDefaultUser(conn ldap.Client) error { return nil } +func containsUser(entries []*ldap.Entry, user initUser) bool { + for _, entry := range entries { + uid := entry.GetAttributeValue("uid") + if uid == user.Username { + return true + } + } + return false +} + func createUserBaseDN(conn ldap.Client) error { conn, err := ldapclient.Client() @@ -314,7 +330,9 @@ func ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limi user := models.User{Username: uid, Email: email, Description: description, Lang: lang, CreateTime: createTimestamp} - users = append(users, user) + if !shouldHidden(user) { + users = append(users, user) + } } updatedControl := ldap.FindControl(response.Controls, ldap.ControlTypePaging) @@ -362,6 +380,15 @@ func ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limi return &models.PageableResponse{Items: items, TotalCount: len(users)}, nil } +func shouldHidden(user models.User) bool { + for _, initUser := range initUsers { + if initUser.Username == user.Username { + return initUser.Hidden + } + } + return false +} + func DescribeUser(username string) (*models.User, error) { user, err := GetUserInfo(username) diff --git a/pkg/models/iam/policy/policy.go b/pkg/models/iam/policy/policy.go index ef6cd7d83..bedf8ed5f 100644 --- a/pkg/models/iam/policy/policy.go +++ b/pkg/models/iam/policy/policy.go @@ -60,47 +60,13 @@ var ( {Name: "workspaces", Actions: []models.Action{ { - Name: "create", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{"tenant.kubesphere.io"}, - Resources: []string{"workspaces"}, - }, - }, - }, - { - Name: "view", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"get", "list"}, - APIGroups: []string{"tenant.kubesphere.io"}, - Resources: []string{"workspaces"}, - }, - }, - }, - {Name: "edit", + Name: "manager", Rules: []v1.PolicyRule{ { Verbs: []string{"*"}, - APIGroups: []string{"tenant.kubesphere.io", "monitoring.kubesphere.io"}, + APIGroups: []string{"*"}, Resources: []string{"workspaces", "workspaces/*"}, }, - { - Verbs: []string{"*"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - { - Verbs: []string{"*"}, - APIGroups: []string{"", "apps", "extensions", "batch", "resources.kubesphere.io"}, - Resources: []string{"serviceaccounts", "limitranges", "deployments", "configmaps", "secrets", "jobs", "cronjobs", "persistentvolumeclaims", "statefulsets", "daemonsets", "ingresses", "services", "pods/*", "pods", "events", "deployments/scale"}, - }, - { - Verbs: []string{"*"}, - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"rolebindings", "roles"}, - }, }, }, }, @@ -121,6 +87,32 @@ var ( }, }, }, + { + Name: "alerting", + Actions: []models.Action{ + {Name: "view", + Rules: []v1.PolicyRule{{ + Verbs: []string{"get", "list"}, + APIGroups: []string{"alerting.kubesphere.io"}, + Resources: []string{"*"}, + }}, + }, + {Name: "create", + Rules: []v1.PolicyRule{{ + Verbs: []string{"create"}, + APIGroups: []string{"alerting.kubesphere.io"}, + Resources: []string{"*"}, + }}, + }, + {Name: "delete", + Rules: []v1.PolicyRule{{ + Verbs: []string{"delete"}, + APIGroups: []string{"alerting.kubesphere.io"}, + Resources: []string{"*"}, + }}, + }, + }, + }, { Name: "logging", Actions: []models.Action{ @@ -210,12 +202,6 @@ var ( APIGroups: []string{"rbac.authorization.k8s.io"}, Resources: []string{"clusterroles"}, }, - { - Verbs: []string{"get", "list"}, - APIGroups: []string{"kubesphere.io"}, - ResourceNames: []string{"cluster-roles"}, - Resources: []string{"resources"}, - }, { Verbs: []string{"get", "list"}, APIGroups: []string{"iam.kubesphere.io"}, @@ -411,12 +397,12 @@ var ( Rules: []v1.PolicyRule{ { Verbs: []string{"get"}, - APIGroups: []string{""}, + APIGroups: []string{"*"}, Resources: []string{"namespaces"}, }, { Verbs: []string{"list"}, - APIGroups: []string{""}, + APIGroups: []string{"*"}, Resources: []string{"events"}, }, }, @@ -441,6 +427,49 @@ var ( }, }, }, + { + Name: "monitoring", + Actions: []models.Action{ + {Name: "view", + Rules: []v1.PolicyRule{{ + Verbs: []string{"get", "list"}, + APIGroups: []string{"monitoring.kubesphere.io"}, + Resources: []string{"*"}, + }, { + Verbs: []string{"get", "list"}, + APIGroups: []string{"resources.kubesphere.io"}, + Resources: []string{"health"}, + }}, + }, + }, + }, + + { + Name: "alerting", + Actions: []models.Action{ + {Name: "view", + Rules: []v1.PolicyRule{{ + Verbs: []string{"get", "list"}, + APIGroups: []string{"alerting.kubesphere.io"}, + Resources: []string{"*"}, + }}, + }, + {Name: "create", + Rules: []v1.PolicyRule{{ + Verbs: []string{"create"}, + APIGroups: []string{"alerting.kubesphere.io"}, + Resources: []string{"*"}, + }}, + }, + {Name: "delete", + Rules: []v1.PolicyRule{{ + Verbs: []string{"delete"}, + APIGroups: []string{"alerting.kubesphere.io"}, + Resources: []string{"*"}, + }}, + }, + }, + }, { Name: "members", Actions: []models.Action{ diff --git a/pkg/models/workspaces/workspaces.go b/pkg/models/workspaces/workspaces.go index 168c9c187..18716e9de 100644 --- a/pkg/models/workspaces/workspaces.go +++ b/pkg/models/workspaces/workspaces.go @@ -19,6 +19,8 @@ package workspaces import ( "fmt" + "github.com/golang/glog" + "github.com/kiali/kiali/log" "k8s.io/apimachinery/pkg/runtime/schema" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/db" @@ -94,16 +96,24 @@ func InviteUser(workspaceName string, user *models.User) error { workspaceRole, err := iam.GetUserWorkspaceRole(workspaceName, user.Username) if err != nil && !apierrors.IsNotFound(err) { + glog.Errorf("get workspace role failed: %+v", err) return err } workspaceRoleName := fmt.Sprintf("workspace:%s:%s", workspaceName, strings.TrimPrefix(user.WorkspaceRole, "workspace-")) + var currentWorkspaceRoleName string + if workspaceRole != nil { + currentWorkspaceRoleName = workspaceRole.Name + } - if workspaceRole != nil && workspaceRole.Name != workspaceRoleName { - err := DeleteWorkspaceRoleBinding(workspaceName, user.Username, user.WorkspaceRole) + if currentWorkspaceRoleName != workspaceRoleName && currentWorkspaceRoleName != "" { + err := DeleteWorkspaceRoleBinding(workspaceName, user.Username, workspaceRole.Annotations[constants.DisplayNameAnnotationKey]) if err != nil { + glog.Errorf("delete workspace role binding failed: %+v", err) return err } + } else if currentWorkspaceRoleName != "" { + return nil } return CreateWorkspaceRoleBinding(workspaceName, user.Username, user.WorkspaceRole) @@ -120,13 +130,18 @@ func CreateWorkspaceRoleBinding(workspace, username string, role string) error { if err != nil { return err } - workspaceRoleBinding = workspaceRoleBinding.DeepCopy() + if !k8sutil.ContainsUser(workspaceRoleBinding.Subjects, username) { + workspaceRoleBinding = workspaceRoleBinding.DeepCopy() workspaceRoleBinding.Subjects = append(workspaceRoleBinding.Subjects, v1.Subject{APIGroup: "rbac.authorization.k8s.io", Kind: "User", Name: username}) _, err = k8s.Client().RbacV1().ClusterRoleBindings().Update(workspaceRoleBinding) + if err != nil { + log.Errorf("update workspace role binding failed: %+v", err) + return err + } } - return err + return nil } func DeleteWorkspaceRoleBinding(workspace, username string, role string) error {