From ce3cd21a766c934629154927d535b57b48d7b294 Mon Sep 17 00:00:00 2001 From: hongming Date: Fri, 29 May 2020 12:49:28 +0800 Subject: [PATCH] update user's role templates API Signed-off-by: hongming --- cmd/controller-manager/app/controllers.go | 5 + cmd/controller-manager/app/server.go | 7 +- pkg/apis/iam/v1alpha2/register.go | 1 + pkg/apis/iam/v1alpha2/types.go | 32 +- .../iam/v1alpha2/zz_generated.deepcopy.go | 124 ++- .../authorization/authorizerfactory/rbac.go | 81 +- .../authorizerfactory/rbac_test.go | 703 +++++++++++++++++- .../clusterrolebinding_controller.go | 12 - .../globalrolebinding_controller.go | 334 +++++++++ pkg/kapis/iam/v1alpha2/handler.go | 150 ++-- pkg/kapis/iam/v1alpha2/register.go | 73 +- pkg/kapis/tenant/v1alpha2/register.go | 6 + pkg/models/iam/am/am.go | 41 +- pkg/models/iam/im/im.go | 1 + pkg/models/resources/v1alpha3/role/roles.go | 2 +- pkg/models/resources/v1alpha3/user/users.go | 2 +- pkg/models/tenant/tenant.go | 6 +- 17 files changed, 1409 insertions(+), 171 deletions(-) create mode 100644 pkg/controller/globalrolebinding/globalrolebinding_controller.go diff --git a/cmd/controller-manager/app/controllers.go b/cmd/controller-manager/app/controllers.go index f8d73e428..0f64146ac 100644 --- a/cmd/controller-manager/app/controllers.go +++ b/cmd/controller-manager/app/controllers.go @@ -25,6 +25,7 @@ import ( "kubesphere.io/kubesphere/pkg/controller/destinationrule" "kubesphere.io/kubesphere/pkg/controller/devopscredential" "kubesphere.io/kubesphere/pkg/controller/devopsproject" + "kubesphere.io/kubesphere/pkg/controller/globalrolebinding" "kubesphere.io/kubesphere/pkg/controller/job" "kubesphere.io/kubesphere/pkg/controller/network/nsnetworkpolicy" "kubesphere.io/kubesphere/pkg/controller/network/provider" @@ -47,6 +48,7 @@ func AddControllers( informerFactory informers.InformerFactory, devopsClient devops.Interface, s3Client s3.Interface, + multiClusterEnabled bool, stopCh <-chan struct{}) error { kubernetesInformer := informerFactory.KubernetesSharedInformerFactory() @@ -125,6 +127,8 @@ func AddControllers( clusterRoleBindingController := clusterrolebinding.NewController(client.Kubernetes(), kubernetesInformer, kubesphereInformer) + globalRoleBindingController := globalrolebinding.NewController(client.Kubernetes(), kubernetesInformer, kubesphereInformer, multiClusterEnabled) + clusterController := cluster.NewClusterController( client.Kubernetes(), client.Config(), @@ -158,6 +162,7 @@ func AddControllers( "nsnp-controller": nsnpController, "csr-controller": csrController, "clusterrolebinding-controller": clusterRoleBindingController, + "globalrolebinding-controller": globalRoleBindingController, } for name, ctrl := range controllers { diff --git a/cmd/controller-manager/app/server.go b/cmd/controller-manager/app/server.go index 6028ec334..2379dc4f6 100644 --- a/cmd/controller-manager/app/server.go +++ b/cmd/controller-manager/app/server.go @@ -36,17 +36,16 @@ import ( "kubesphere.io/kubesphere/pkg/controller/network/nsnetworkpolicy" "kubesphere.io/kubesphere/pkg/controller/user" "kubesphere.io/kubesphere/pkg/controller/workspace" - "kubesphere.io/kubesphere/pkg/simple/client/openpitrix" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins" "kubesphere.io/kubesphere/pkg/simple/client/k8s" + "kubesphere.io/kubesphere/pkg/simple/client/openpitrix" "kubesphere.io/kubesphere/pkg/simple/client/s3" "kubesphere.io/kubesphere/pkg/utils/term" "os" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/runtime/signals" + "sigs.k8s.io/controller-runtime/pkg/webhook" ) func NewControllerManagerCommand() *cobra.Command { @@ -147,7 +146,7 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{}) klog.Fatal("Unable to create namespace controller") } - if err := AddControllers(mgr, kubernetesClient, informerFactory, devopsClient, s3Client, stopCh); err != nil { + if err := AddControllers(mgr, kubernetesClient, informerFactory, devopsClient, s3Client, s.MultiClusterOptions.Enable, stopCh); err != nil { klog.Fatalf("unable to register controllers to the manager: %v", err) } diff --git a/pkg/apis/iam/v1alpha2/register.go b/pkg/apis/iam/v1alpha2/register.go index 1e2c87954..0a664e463 100644 --- a/pkg/apis/iam/v1alpha2/register.go +++ b/pkg/apis/iam/v1alpha2/register.go @@ -59,6 +59,7 @@ func addKnownTypes(scheme *runtime.Scheme) error { &WorkspaceRoleList{}, &WorkspaceRoleBinding{}, &WorkspaceRoleBindingList{}, + &FederatedClusterRoleBinding{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/pkg/apis/iam/v1alpha2/types.go b/pkg/apis/iam/v1alpha2/types.go index a45fd1b70..86d99cd8d 100644 --- a/pkg/apis/iam/v1alpha2/types.go +++ b/pkg/apis/iam/v1alpha2/types.go @@ -64,8 +64,7 @@ const ( ScopeWorkspace = "workspace" ScopeCluster = "cluster" ScopeNamespace = "namespace" - LocalCluster = "local" - GlobalAdmin = "global-admin" + PlatformAdmin = "platform-admin" ClusterAdmin = "cluster-admin" ) @@ -284,3 +283,32 @@ type WorkspaceRoleBindingList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []WorkspaceRoleBinding `json:"items"` } + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type FederatedClusterRoleBinding struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec FederatedClusterRoleBindingSpec `json:"spec"` +} + +type FederatedClusterRoleBindingSpec struct { + Template Template `json:"template"` + Placement Placement `json:"placement"` +} +type Template struct { + Subjects []rbacv1.Subject `json:"subjects,omitempty"` + RoleRef rbacv1.RoleRef `json:"roleRef"` +} + +type Placement struct { + Clusters []Cluster `json:"clusters,omitempty"` + ClusterSelector ClusterSelector `json:"clusterSelector,omitempty"` +} + +type ClusterSelector struct { + MatchLabels map[string]string `json:"matchLabels,omitempty"` +} + +type Cluster struct { + Name string `json:"name"` +} diff --git a/pkg/apis/iam/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/iam/v1alpha2/zz_generated.deepcopy.go index 3250eef04..0a2a65d29 100644 --- a/pkg/apis/iam/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/iam/v1alpha2/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2019 The KubeSphere Authors. +Copyright 2020 The KubeSphere Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,6 +25,86 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Cluster) DeepCopyInto(out *Cluster) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster. +func (in *Cluster) DeepCopy() *Cluster { + if in == nil { + return nil + } + out := new(Cluster) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterSelector) DeepCopyInto(out *ClusterSelector) { + *out = *in + if in.MatchLabels != nil { + in, out := &in.MatchLabels, &out.MatchLabels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSelector. +func (in *ClusterSelector) DeepCopy() *ClusterSelector { + if in == nil { + return nil + } + out := new(ClusterSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederatedClusterRoleBinding) DeepCopyInto(out *FederatedClusterRoleBinding) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederatedClusterRoleBinding. +func (in *FederatedClusterRoleBinding) DeepCopy() *FederatedClusterRoleBinding { + if in == nil { + return nil + } + out := new(FederatedClusterRoleBinding) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FederatedClusterRoleBinding) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederatedClusterRoleBindingSpec) DeepCopyInto(out *FederatedClusterRoleBindingSpec) { + *out = *in + in.Template.DeepCopyInto(&out.Template) + in.Placement.DeepCopyInto(&out.Placement) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederatedClusterRoleBindingSpec. +func (in *FederatedClusterRoleBindingSpec) DeepCopy() *FederatedClusterRoleBindingSpec { + if in == nil { + return nil + } + out := new(FederatedClusterRoleBindingSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GlobalRole) DeepCopyInto(out *GlobalRole) { *out = *in @@ -152,6 +232,48 @@ func (in *GlobalRoleList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Placement) DeepCopyInto(out *Placement) { + *out = *in + if in.Clusters != nil { + in, out := &in.Clusters, &out.Clusters + *out = make([]Cluster, len(*in)) + copy(*out, *in) + } + in.ClusterSelector.DeepCopyInto(&out.ClusterSelector) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Placement. +func (in *Placement) DeepCopy() *Placement { + if in == nil { + return nil + } + out := new(Placement) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Template) DeepCopyInto(out *Template) { + *out = *in + if in.Subjects != nil { + in, out := &in.Subjects, &out.Subjects + *out = make([]v1.Subject, len(*in)) + copy(*out, *in) + } + out.RoleRef = in.RoleRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Template. +func (in *Template) DeepCopy() *Template { + if in == nil { + return nil + } + out := new(Template) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *User) DeepCopyInto(out *User) { *out = *in diff --git a/pkg/apiserver/authorization/authorizerfactory/rbac.go b/pkg/apiserver/authorization/authorizerfactory/rbac.go index 035a3cff1..a84d15875 100644 --- a/pkg/apiserver/authorization/authorizerfactory/rbac.go +++ b/pkg/apiserver/authorization/authorizerfactory/rbac.go @@ -78,7 +78,7 @@ type ruleAccumulator struct { errors []error } -func (r *ruleAccumulator) visit(source fmt.Stringer, _ string, rule *rbacv1.PolicyRule, err error) bool { +func (r *ruleAccumulator) visit(_ fmt.Stringer, _ string, rule *rbacv1.PolicyRule, err error) bool { if rule != nil { r.rules = append(r.rules, *rule) } @@ -200,6 +200,7 @@ func (r *RBACAuthorizer) rulesFor(requestAttributes authorizer.Attributes) ([]rb } func (r *RBACAuthorizer) visitRulesFor(requestAttributes authorizer.Attributes, visitor func(source fmt.Stringer, regoPolicy string, rule *rbacv1.PolicyRule, err error) bool) { + if globalRoleBindings, err := r.am.ListGlobalRoleBindings(""); err != nil { if !visitor(nil, "", nil, err) { return @@ -229,7 +230,54 @@ func (r *RBACAuthorizer) visitRulesFor(requestAttributes authorizer.Attributes, } } - if requestAttributes.GetResourceScope() == request.WorkspaceScope { + if requestAttributes.GetResourceScope() == request.ClusterScope || requestAttributes.GetResourceScope() == request.NamespaceScope { + if clusterRoleBindings, err := r.am.ListClusterRoleBindings(""); err != nil { + if !visitor(nil, "", nil, err) { + return + } + } else { + sourceDescriber := &clusterRoleBindingDescriber{} + for _, clusterRoleBinding := range clusterRoleBindings { + subjectIndex, applies := appliesTo(requestAttributes.GetUser(), clusterRoleBinding.Subjects, "") + if !applies { + continue + } + regoPolicy, rules, err := r.am.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "") + if err != nil { + visitor(nil, "", nil, err) + continue + } + sourceDescriber.binding = clusterRoleBinding + sourceDescriber.subject = &clusterRoleBinding.Subjects[subjectIndex] + if !visitor(sourceDescriber, regoPolicy, nil, nil) { + return + } + for i := range rules { + if !visitor(sourceDescriber, "", &rules[i], nil) { + return + } + } + } + } + } + + if requestAttributes.GetResourceScope() == request.WorkspaceScope || requestAttributes.GetResourceScope() == request.NamespaceScope { + + var workspace string + var err error + + if requestAttributes.GetResourceScope() == request.NamespaceScope { + if workspace, err = r.am.GetControlledWorkspace(requestAttributes.GetNamespace()); err != nil { + if !visitor(nil, "", nil, err) { + return + } + } + } + + if workspace == "" { + workspace = requestAttributes.GetWorkspace() + } + if workspaceRoleBindings, err := r.am.ListWorkspaceRoleBindings("", requestAttributes.GetWorkspace()); err != nil { if !visitor(nil, "", nil, err) { return @@ -290,35 +338,6 @@ func (r *RBACAuthorizer) visitRulesFor(requestAttributes authorizer.Attributes, } } } - - if clusterRoleBindings, err := r.am.ListClusterRoleBindings(""); err != nil { - if !visitor(nil, "", nil, err) { - return - } - } else { - sourceDescriber := &clusterRoleBindingDescriber{} - for _, clusterRoleBinding := range clusterRoleBindings { - subjectIndex, applies := appliesTo(requestAttributes.GetUser(), clusterRoleBinding.Subjects, "") - if !applies { - continue - } - regoPolicy, rules, err := r.am.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "") - if err != nil { - visitor(nil, "", nil, err) - continue - } - sourceDescriber.binding = clusterRoleBinding - sourceDescriber.subject = &clusterRoleBinding.Subjects[subjectIndex] - if !visitor(sourceDescriber, regoPolicy, nil, nil) { - return - } - for i := range rules { - if !visitor(sourceDescriber, "", &rules[i], nil) { - return - } - } - } - } } // appliesTo returns whether any of the bindingSubjects applies to the specified subject, diff --git a/pkg/apiserver/authorization/authorizerfactory/rbac_test.go b/pkg/apiserver/authorization/authorizerfactory/rbac_test.go index 1f8b3a9fb..4ae431113 100644 --- a/pkg/apiserver/authorization/authorizerfactory/rbac_test.go +++ b/pkg/apiserver/authorization/authorizerfactory/rbac_test.go @@ -19,12 +19,12 @@ package authorizerfactory import ( "errors" "github.com/google/go-cmp/cmp" - fakesnapshot "github.com/kubernetes-csi/external-snapshotter/v2/pkg/client/clientset/versioned/fake" - fakeapp "github.com/kubernetes-sigs/application/pkg/client/clientset/versioned/fake" "hash/fnv" "io" - fakeistio "istio.io/client-go/pkg/clientset/versioned/fake" + corev1 "k8s.io/api/core/v1" fakek8s "k8s.io/client-go/kubernetes/fake" + iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" + tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer" "kubesphere.io/kubesphere/pkg/apiserver/request" fakeks "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake" @@ -40,10 +40,15 @@ import ( // StaticRoles is a rule resolver that resolves from lists of role objects. type StaticRoles struct { - roles []*rbacv1.Role - roleBindings []*rbacv1.RoleBinding - clusterRoles []*rbacv1.ClusterRole - clusterRoleBindings []*rbacv1.ClusterRoleBinding + roles []*rbacv1.Role + roleBindings []*rbacv1.RoleBinding + clusterRoles []*rbacv1.ClusterRole + clusterRoleBindings []*rbacv1.ClusterRoleBinding + workspaceRoles []*iamv1alpha2.WorkspaceRole + workspaceRoleBindings []*iamv1alpha2.WorkspaceRoleBinding + globalRoles []*iamv1alpha2.GlobalRole + globalRoleBindings []*iamv1alpha2.GlobalRoleBinding + namespaces []*corev1.Namespace } func (r *StaticRoles) GetRole(namespace, name string) (*rbacv1.Role, error) { @@ -72,12 +77,11 @@ func (r *StaticRoles) ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding, return nil, errors.New("must provide namespace when listing role bindings") } - roleBindingList := []*rbacv1.RoleBinding{} + var roleBindingList []*rbacv1.RoleBinding for _, roleBinding := range r.roleBindings { if roleBinding.Namespace != namespace { continue } - // TODO(ericchiang): need to implement label selectors? roleBindingList = append(roleBindingList, roleBinding) } return roleBindingList, nil @@ -130,7 +134,7 @@ func TestRBACAuthorizer(t *testing.T) { Resources: []string{"*"}, } - staticRoles1 := StaticRoles{ + staticRoles := StaticRoles{ roles: []*rbacv1.Role{ { ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1", Name: "readthings"}, @@ -147,6 +151,24 @@ func TestRBACAuthorizer(t *testing.T) { Rules: []rbacv1.PolicyRule{ruleWriteNodes}, }, }, + workspaceRoles: []*iamv1alpha2.WorkspaceRole{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "system-workspace-workspace-manager", + Labels: map[string]string{tenantv1alpha1.WorkspaceLabel: "system-workspace"}, + }, + Rules: []rbacv1.PolicyRule{ruleAdmin}, + }, + }, + globalRoles: []*iamv1alpha2.GlobalRole{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "global-admin", + }, + Rules: []rbacv1.PolicyRule{ruleAdmin}, + }, + }, + roleBindings: []*rbacv1.RoleBinding{ { ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1"}, @@ -157,13 +179,40 @@ func TestRBACAuthorizer(t *testing.T) { RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "Role", Name: "readthings"}, }, }, - clusterRoleBindings: []*rbacv1.ClusterRoleBinding{ + workspaceRoleBindings: []*iamv1alpha2.WorkspaceRoleBinding{ { - Subjects: []rbacv1.Subject{ - {Kind: rbacv1.UserKind, Name: "admin"}, - {Kind: rbacv1.GroupKind, Name: "admin"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "system-workspace-workspace-manager-tester", + Labels: map[string]string{tenantv1alpha1.WorkspaceLabel: "system-workspace"}, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: iamv1alpha2.SchemeGroupVersion.Group, + Kind: iamv1alpha2.ResourceKindWorkspaceRole, + Name: "system-workspace-workspace-manager", + }, + Subjects: []rbacv1.Subject{ + { + Kind: iamv1alpha2.ResourceKindUser, + APIGroup: iamv1alpha2.SchemeGroupVersion.Group, + Name: "tester", + }, + }, + }, + }, + globalRoleBindings: []*iamv1alpha2.GlobalRoleBinding{ + { + RoleRef: rbacv1.RoleRef{ + APIGroup: iamv1alpha2.SchemeGroupVersion.Group, + Kind: iamv1alpha2.ResourceKindGlobalRole, + Name: "global-admin", + }, + Subjects: []rbacv1.Subject{ + { + Kind: iamv1alpha2.ResourceKindUser, + APIGroup: iamv1alpha2.SchemeGroupVersion.Group, + Name: "admin", + }, }, - RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: "cluster-admin"}, }, }, } @@ -174,28 +223,48 @@ func TestRBACAuthorizer(t *testing.T) { // For a given context, what are the rules that apply? user user.Info namespace string + workspace string effectiveRules []rbacv1.PolicyRule }{ { - StaticRoles: staticRoles1, + StaticRoles: staticRoles, + user: &user.DefaultInfo{Name: "admin"}, + workspace: "system-workspace", + effectiveRules: []rbacv1.PolicyRule{ruleAdmin}, + }, + { + StaticRoles: staticRoles, + user: &user.DefaultInfo{Name: "admin"}, + namespace: "namespace1", + effectiveRules: []rbacv1.PolicyRule{ruleAdmin}, + }, + { + StaticRoles: staticRoles, + user: &user.DefaultInfo{Name: "tester"}, + workspace: "system-workspace", + effectiveRules: []rbacv1.PolicyRule{ruleAdmin}, + }, + { + StaticRoles: staticRoles, user: &user.DefaultInfo{Name: "foobar"}, namespace: "namespace1", effectiveRules: []rbacv1.PolicyRule{ruleReadPods, ruleReadServices}, }, { - StaticRoles: staticRoles1, + StaticRoles: staticRoles, user: &user.DefaultInfo{Name: "foobar"}, namespace: "namespace2", effectiveRules: nil, }, + { - StaticRoles: staticRoles1, - // Same as above but without a namespace. Only cluster rules should apply. - user: &user.DefaultInfo{Name: "foobar", Groups: []string{"admin"}}, - effectiveRules: []rbacv1.PolicyRule{ruleAdmin}, + StaticRoles: staticRoles, + // Same as above but without a namespace. Only global rules should apply. + user: &user.DefaultInfo{Name: "foobar"}, + effectiveRules: nil, }, { - StaticRoles: staticRoles1, + StaticRoles: staticRoles, user: &user.DefaultInfo{}, effectiveRules: nil, }, @@ -203,21 +272,26 @@ func TestRBACAuthorizer(t *testing.T) { for i, tc := range tests { ruleResolver, err := newMockRBACAuthorizer(&tc.StaticRoles) - if err != nil { t.Fatal(err) } scope := request.ClusterScope + if tc.workspace != "" { + scope = request.WorkspaceScope + } + if tc.namespace != "" { scope = request.NamespaceScope } rules, err := ruleResolver.rulesFor(authorizer.AttributesRecord{ - User: tc.user, - Namespace: tc.namespace, - ResourceScope: scope, + User: tc.user, + Namespace: tc.namespace, + Workspace: tc.workspace, + ResourceScope: scope, + ResourceRequest: true, }) if err != nil { @@ -235,17 +309,556 @@ func TestRBACAuthorizer(t *testing.T) { } } +func TestRBACAuthorizerMakeDecision(t *testing.T) { + + staticRoles := StaticRoles{ + roles: []*rbacv1.Role{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kubesphere-system", + Name: "kubesphere-system-admin", + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kubesphere-system", + Name: "kubesphere-system-viewer", + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"get", "list", "watch"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + }, + }, + }, + clusterRoles: []*rbacv1.ClusterRole{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-viewer", + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"get", "list", "watch"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-admin", + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + }, + }, + }, + workspaceRoles: []*iamv1alpha2.WorkspaceRole{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "system-workspace-admin", + Labels: map[string]string{tenantv1alpha1.WorkspaceLabel: "system-workspace"}, + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "system-workspace-viewer", + Labels: map[string]string{tenantv1alpha1.WorkspaceLabel: "system-workspace"}, + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"get", "list", "watch"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + }, + }, + }, + globalRoles: []*iamv1alpha2.GlobalRole{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "global-admin", + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + NonResourceURLs: []string{"*"}, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "global-viewer", + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"get", "list", "watch"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + }, + }, + }, + + roleBindings: []*rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kubesphere-system", + Name: "kubesphere-system-admin", + }, + Subjects: []rbacv1.Subject{ + {Kind: rbacv1.UserKind, Name: "kubesphere-system-admin"}, + }, + RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "Role", Name: "kubesphere-system-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kubesphere-system", + Name: "kubesphere-system-viewer", + }, + Subjects: []rbacv1.Subject{ + { + Kind: rbacv1.UserKind, + Name: "kubesphere-system-viewer", + }, + }, + RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "Role", Name: "kubesphere-system-viewer"}, + }, + }, + workspaceRoleBindings: []*iamv1alpha2.WorkspaceRoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "system-workspace-admin", + Labels: map[string]string{tenantv1alpha1.WorkspaceLabel: "system-workspace"}, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: iamv1alpha2.SchemeGroupVersion.Group, + Kind: iamv1alpha2.ResourceKindWorkspaceRole, + Name: "system-workspace-admin", + }, + Subjects: []rbacv1.Subject{ + { + Kind: iamv1alpha2.ResourceKindUser, + Name: "system-workspace-admin", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "system-workspace-viewer", + Labels: map[string]string{tenantv1alpha1.WorkspaceLabel: "system-workspace"}, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: iamv1alpha2.SchemeGroupVersion.Group, + Kind: iamv1alpha2.ResourceKindWorkspaceRole, + Name: "system-workspace-viewer", + }, + Subjects: []rbacv1.Subject{ + { + Kind: iamv1alpha2.ResourceKindUser, + Name: "system-workspace-viewer", + }, + }, + }, + }, + clusterRoleBindings: []*rbacv1.ClusterRoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-admin", + }, + Subjects: []rbacv1.Subject{ + {Kind: rbacv1.UserKind, Name: "cluster-admin"}, + }, + RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "Role", Name: "cluster-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-viewer", + }, + Subjects: []rbacv1.Subject{ + {Kind: rbacv1.UserKind, Name: "cluster-viewer"}, + }, + RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: "cluster-viewer"}, + }, + }, + globalRoleBindings: []*iamv1alpha2.GlobalRoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "admin", + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: iamv1alpha2.SchemeGroupVersion.Group, + Kind: iamv1alpha2.ResourceKindGlobalRole, + Name: "global-admin", + }, + Subjects: []rbacv1.Subject{ + { + Kind: iamv1alpha2.ResourceKindUser, + APIGroup: iamv1alpha2.SchemeGroupVersion.Group, + Name: "admin", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "viewer", + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: iamv1alpha2.SchemeGroupVersion.Group, + Kind: iamv1alpha2.ResourceKindGlobalRole, + Name: "global-viewer", + }, + Subjects: []rbacv1.Subject{ + { + Kind: iamv1alpha2.ResourceKindUser, + APIGroup: iamv1alpha2.SchemeGroupVersion.Group, + Name: "viewer", + }, + }, + }, + }, + + namespaces: []*corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "kubesphere-system", + Labels: map[string]string{tenantv1alpha1.WorkspaceLabel: "system-workspace"}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "kube-system", + Labels: map[string]string{tenantv1alpha1.WorkspaceLabel: "system-workspace"}, + }, + }, + }, + } + + tests := []struct { + StaticRoles + Request authorizer.AttributesRecord + ExpectedDecision authorizer.Decision + }{ + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "admin", + }, + Verb: "create", + APIGroup: "", + APIVersion: "v1", + Resource: "namespaces", + ResourceRequest: true, + ResourceScope: request.ClusterScope, + }, + ExpectedDecision: authorizer.DecisionAllow, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "viewer", + }, + Verb: "create", + APIGroup: "", + APIVersion: "v1", + Resource: "namespaces", + ResourceRequest: true, + ResourceScope: request.ClusterScope, + }, + ExpectedDecision: authorizer.DecisionNoOpinion, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "viewer", + }, + Verb: "list", + APIGroup: "", + APIVersion: "v1", + Resource: "namespaces", + ResourceRequest: true, + ResourceScope: request.ClusterScope, + }, + ExpectedDecision: authorizer.DecisionAllow, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "admin", + }, + Verb: "list", + Workspace: "system-workspace", + APIGroup: "tenant.kubesphere.io", + APIVersion: "v1alpha2", + Resource: "namespaces", + ResourceRequest: true, + ResourceScope: request.WorkspaceScope, + }, + ExpectedDecision: authorizer.DecisionAllow, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "system-workspace-admin", + }, + Verb: "list", + Workspace: "system-workspace", + APIGroup: "tenant.kubesphere.io", + APIVersion: "v1alpha2", + Resource: "namespaces", + ResourceRequest: true, + ResourceScope: request.WorkspaceScope, + }, + ExpectedDecision: authorizer.DecisionAllow, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "system-workspace-viewer", + }, + Verb: "list", + Workspace: "system-workspace", + APIGroup: "tenant.kubesphere.io", + APIVersion: "v1alpha2", + Resource: "namespaces", + ResourceRequest: true, + ResourceScope: request.WorkspaceScope, + }, + ExpectedDecision: authorizer.DecisionAllow, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "admin", + }, + Verb: "create", + Workspace: "system-workspace", + APIGroup: "tenant.kubesphere.io", + APIVersion: "v1alpha2", + Resource: "namespaces", + ResourceRequest: true, + ResourceScope: iamv1alpha2.ScopeWorkspace, + }, + ExpectedDecision: authorizer.DecisionAllow, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "system-workspace-admin", + }, + Verb: "create", + Workspace: "system-workspace", + APIGroup: "tenant.kubesphere.io", + APIVersion: "v1alpha2", + Resource: "namespaces", + ResourceRequest: true, + ResourceScope: request.WorkspaceScope, + }, + ExpectedDecision: authorizer.DecisionAllow, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "system-workspace-viewer", + }, + Verb: "create", + Workspace: "system-workspace", + APIGroup: "tenant.kubesphere.io", + APIVersion: "v1alpha2", + Resource: "namespaces", + ResourceRequest: true, + ResourceScope: request.WorkspaceScope, + }, + ExpectedDecision: authorizer.DecisionNoOpinion, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "admin", + }, + Verb: "create", + APIGroup: "apps", + APIVersion: "v1", + Resource: "deployments", + Namespace: "kubesphere-system", + ResourceRequest: true, + ResourceScope: request.NamespaceScope, + }, + ExpectedDecision: authorizer.DecisionAllow, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "viewer", + }, + Verb: "create", + APIGroup: "apps", + APIVersion: "v1", + Resource: "deployments", + Namespace: "kubesphere-system", + ResourceRequest: true, + ResourceScope: request.NamespaceScope, + }, + ExpectedDecision: authorizer.DecisionNoOpinion, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "system-workspace-admin", + }, + Verb: "create", + APIGroup: "apps", + APIVersion: "v1", + Resource: "deployments", + Namespace: "kubesphere-system", + ResourceRequest: true, + ResourceScope: request.NamespaceScope, + }, + ExpectedDecision: authorizer.DecisionAllow, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "system-workspace-viewer", + }, + Verb: "create", + APIGroup: "apps", + APIVersion: "v1", + Resource: "deployments", + Namespace: "kubesphere-system", + ResourceRequest: true, + ResourceScope: request.NamespaceScope, + }, + ExpectedDecision: authorizer.DecisionNoOpinion, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "kubesphere-system-admin", + }, + Verb: "create", + APIGroup: "apps", + APIVersion: "v1", + Resource: "deployments", + Namespace: "kubesphere-system", + ResourceRequest: true, + ResourceScope: request.NamespaceScope, + }, + ExpectedDecision: authorizer.DecisionAllow, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "kubesphere-system-viewer", + }, + Verb: "create", + APIGroup: "apps", + APIVersion: "v1", + Resource: "deployments", + Namespace: "kubesphere-system", + ResourceRequest: true, + ResourceScope: request.NamespaceScope, + }, + ExpectedDecision: authorizer.DecisionNoOpinion, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "system-workspace-admin", + }, + Verb: "create", + APIGroup: "apps", + APIVersion: "v1", + Resource: "deployments", + Namespace: "kube-system", + ResourceRequest: true, + ResourceScope: request.NamespaceScope, + }, + ExpectedDecision: authorizer.DecisionAllow, + }, + { + StaticRoles: staticRoles, + Request: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "kubesphere-system-admin", + }, + Verb: "create", + APIGroup: "apps", + APIVersion: "v1", + Resource: "deployments", + Namespace: "kube-system", + ResourceRequest: true, + ResourceScope: request.NamespaceScope, + }, + ExpectedDecision: authorizer.DecisionNoOpinion, + }, + } + + for i, tc := range tests { + ruleResolver, err := newMockRBACAuthorizer(&tc.StaticRoles) + if err != nil { + t.Fatal(err) + } + + decision, message, err := ruleResolver.Authorize(&tc.Request) + + if err != nil { + t.Errorf("case %d: %v: %s", i, err, message) + continue + } + + if decision != tc.ExpectedDecision { + t.Errorf("case %d: %d != %d", i, decision, tc.ExpectedDecision) + } + } +} + func newMockRBACAuthorizer(staticRoles *StaticRoles) (*RBACAuthorizer, error) { ksClient := fakeks.NewSimpleClientset() k8sClient := fakek8s.NewSimpleClientset() - istioClient := fakeistio.NewSimpleClientset() - appClient := fakeapp.NewSimpleClientset() - snapshotClient := fakesnapshot.NewSimpleClientset() - - fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, istioClient, appClient, snapshotClient, nil) + fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil, nil) k8sInformerFactory := fakeInformerFactory.KubernetesSharedInformerFactory() + ksInformerFactory := fakeInformerFactory.KubeSphereSharedInformerFactory() for _, role := range staticRoles.roles { err := k8sInformerFactory.Rbac().V1().Roles().Informer().GetIndexer().Add(role) @@ -274,6 +887,34 @@ func newMockRBACAuthorizer(staticRoles *StaticRoles) (*RBACAuthorizer, error) { return nil, err } } + + for _, workspaceRole := range staticRoles.workspaceRoles { + err := ksInformerFactory.Iam().V1alpha2().WorkspaceRoles().Informer().GetIndexer().Add(workspaceRole) + if err != nil { + return nil, err + } + } + + for _, workspaceRoleBinding := range staticRoles.workspaceRoleBindings { + err := ksInformerFactory.Iam().V1alpha2().WorkspaceRoleBindings().Informer().GetIndexer().Add(workspaceRoleBinding) + if err != nil { + return nil, err + } + } + + for _, globalRole := range staticRoles.globalRoles { + err := ksInformerFactory.Iam().V1alpha2().GlobalRoles().Informer().GetIndexer().Add(globalRole) + if err != nil { + return nil, err + } + } + + for _, globalRoleBinding := range staticRoles.globalRoleBindings { + err := ksInformerFactory.Iam().V1alpha2().GlobalRoleBindings().Informer().GetIndexer().Add(globalRoleBinding) + if err != nil { + return nil, err + } + } return NewRBACAuthorizer(am.NewReadOnlyOperator(fakeInformerFactory)), nil } diff --git a/pkg/controller/clusterrolebinding/clusterrolebinding_controller.go b/pkg/controller/clusterrolebinding/clusterrolebinding_controller.go index 2b6a43e45..32cd7e0f1 100644 --- a/pkg/controller/clusterrolebinding/clusterrolebinding_controller.go +++ b/pkg/controller/clusterrolebinding/clusterrolebinding_controller.go @@ -18,7 +18,6 @@ package clusterrolebinding import ( "fmt" - "golang.org/x/crypto/bcrypt" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -209,7 +208,6 @@ func (c *Controller) reconcile(key string) error { } isClusterAdmin := clusterRoleBinding.RoleRef.Name == iamv1alpha2.ClusterAdmin - if isClusterAdmin { for _, subject := range clusterRoleBinding.Subjects { if subject.Kind == iamv1alpha2.ResourceKindUser { @@ -229,13 +227,3 @@ func (c *Controller) reconcile(key string) error { func (c *Controller) Start(stopCh <-chan struct{}) error { return c.Run(4, stopCh) } - -func encrypt(password string) (string, error) { - // when user is already mapped to another identity, password is empty by default - // unable to log in directly until password reset - if password == "" { - return "", nil - } - bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - return string(bytes), err -} diff --git a/pkg/controller/globalrolebinding/globalrolebinding_controller.go b/pkg/controller/globalrolebinding/globalrolebinding_controller.go new file mode 100644 index 000000000..ca8db24ab --- /dev/null +++ b/pkg/controller/globalrolebinding/globalrolebinding_controller.go @@ -0,0 +1,334 @@ +/* +Copyright 2019 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package globalrolebinding + +import ( + "encoding/json" + "fmt" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + k8sinformers "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog" + iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" + ksinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions" + iamv1alpha2informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam/v1alpha2" + iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "time" +) + +const ( + // SuccessSynced is used as part of the Event 'reason' when a Foo is synced + successSynced = "Synced" + // is synced successfully + messageResourceSynced = "GlobalRoleBinding synced successfully" + controllerName = "globalrolebinding-controller" + federatedClusterRoleBindingKind = "FederatedClusterRoleBinding" + federatedResourceVersion = "types.kubefed.io/v1beta1" + federatedResourceAPIPath = "/apis/types.kubefed.io/v1beta1/federatedclusterrolebindings" +) + +type Controller struct { + k8sClient kubernetes.Interface + informer iamv1alpha2informers.GlobalRoleBindingInformer + lister iamv1alpha2listers.GlobalRoleBindingLister + synced cache.InformerSynced + // workqueue is a rate limited work queue. This is used to queue work to be + // processed instead of performing it as soon as a change happens. This + // means we can ensure we only process a fixed amount of resources at a + // time, and makes it easy to ensure we are never processing the same item + // simultaneously in two different workers. + workqueue workqueue.RateLimitingInterface + // recorder is an event recorder for recording Event resources to the + // Kubernetes API. + recorder record.EventRecorder + multiClusterEnabled bool +} + +func NewController(k8sClient kubernetes.Interface, k8sInformer k8sinformers.SharedInformerFactory, ksInformer ksinformers.SharedInformerFactory, multiClusterEnabled bool) *Controller { + // Create event broadcaster + // Add sample-controller types to the default Kubernetes Scheme so Events can be + // logged for sample-controller types. + + klog.V(4).Info("Creating event broadcaster") + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(klog.Infof) + eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: k8sClient.CoreV1().Events("")}) + recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerName}) + informer := ksInformer.Iam().V1alpha2().GlobalRoleBindings() + ctl := &Controller{ + k8sClient: k8sClient, + informer: informer, + lister: informer.Lister(), + synced: informer.Informer().HasSynced, + workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ClusterRoleBinding"), + recorder: recorder, + multiClusterEnabled: multiClusterEnabled, + } + klog.Info("Setting up event handlers") + informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: ctl.enqueueClusterRoleBinding, + UpdateFunc: func(old, new interface{}) { + ctl.enqueueClusterRoleBinding(new) + }, + DeleteFunc: ctl.enqueueClusterRoleBinding, + }) + return ctl +} + +func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error { + defer utilruntime.HandleCrash() + defer c.workqueue.ShutDown() + + //init client + + // Start the informer factories to begin populating the informer caches + klog.Info("Starting User controller") + + // Wait for the caches to be synced before starting workers + klog.Info("Waiting for informer caches to sync") + if ok := cache.WaitForCacheSync(stopCh, c.synced); !ok { + return fmt.Errorf("failed to wait for caches to sync") + } + + klog.Info("Starting workers") + // Launch two workers to process Foo resources + for i := 0; i < threadiness; i++ { + go wait.Until(c.runWorker, time.Second, stopCh) + } + + klog.Info("Started workers") + <-stopCh + klog.Info("Shutting down workers") + return nil +} + +func (c *Controller) enqueueClusterRoleBinding(obj interface{}) { + var key string + var err error + if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil { + utilruntime.HandleError(err) + return + } + c.workqueue.Add(key) +} + +func (c *Controller) runWorker() { + for c.processNextWorkItem() { + } +} + +func (c *Controller) processNextWorkItem() bool { + obj, shutdown := c.workqueue.Get() + + if shutdown { + return false + } + + // We wrap this block in a func so we can defer c.workqueue.Done. + err := func(obj interface{}) error { + // We call Done here so the workqueue knows we have finished + // processing this item. We also must remember to call Forget if we + // do not want this work item being re-queued. For example, we do + // not call Forget if a transient error occurs, instead the item is + // put back on the workqueue and attempted again after a back-off + // period. + defer c.workqueue.Done(obj) + var key string + var ok bool + // We expect strings to come off the workqueue. These are of the + // form namespace/name. We do this as the delayed nature of the + // workqueue means the items in the informer cache may actually be + // more up to date that when the item was initially put onto the + // workqueue. + if key, ok = obj.(string); !ok { + // As the item in the workqueue is actually invalid, we call + // Forget here else we'd go into a loop of attempting to + // process a work item that is invalid. + c.workqueue.Forget(obj) + utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj)) + return nil + } + // Run the reconcile, passing it the namespace/name string of the + // Foo resource to be synced. + if err := c.reconcile(key); err != nil { + // Put the item back on the workqueue to handle any transient errors. + c.workqueue.AddRateLimited(key) + return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error()) + } + // Finally, if no error occurs we Forget this item so it does not + // get queued again until another change happens. + c.workqueue.Forget(obj) + klog.Infof("Successfully synced %s:%s", "key", key) + return nil + }(obj) + + if err != nil { + utilruntime.HandleError(err) + return true + } + + return true +} + +// syncHandler compares the actual state with the desired, and attempts to +// converge the two. It then updates the Status block of the Foo resource +// with the current status of the resource. +func (c *Controller) reconcile(key string) error { + + // Get the clusterRoleBinding with this name + globalRoleBinding, err := c.lister.Get(key) + if err != nil { + // The user may no longer exist, in which case we stop + // processing. + if errors.IsNotFound(err) { + utilruntime.HandleError(fmt.Errorf("clusterrolebinding '%s' in work queue no longer exists", key)) + return nil + } + klog.Error(err) + return err + } + + isPlatformAdmin := globalRoleBinding.RoleRef.Name == iamv1alpha2.PlatformAdmin + + if isPlatformAdmin { + if err := c.relateToClusterAdmin(globalRoleBinding); err != nil { + klog.Error(err) + return err + } + } + + c.recorder.Event(globalRoleBinding, corev1.EventTypeNormal, successSynced, messageResourceSynced) + return nil +} + +func (c *Controller) Start(stopCh <-chan struct{}) error { + return c.Run(4, stopCh) +} + +func (c *Controller) relateToClusterAdmin(globalRoleBinding *iamv1alpha2.GlobalRoleBinding) error { + + if c.multiClusterEnabled { + + federatedClusterRoleBinding := &iamv1alpha2.FederatedClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: federatedClusterRoleBindingKind, + APIVersion: federatedResourceVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("fed-%s", globalRoleBinding.Name), + }, + Spec: iamv1alpha2.FederatedClusterRoleBindingSpec{ + Template: iamv1alpha2.Template{ + Subjects: ensureSubjectAPIVersionIsValid(globalRoleBinding.Subjects), + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: iamv1alpha2.ResourceKindClusterRole, + Name: iamv1alpha2.ClusterAdmin, + }, + }, + Placement: iamv1alpha2.Placement{ + ClusterSelector: iamv1alpha2.ClusterSelector{}, + }, + }, + } + + // rbac.authorization.k8s.io + + err := controllerutil.SetControllerReference(globalRoleBinding, federatedClusterRoleBinding, scheme.Scheme) + + if err != nil { + return err + } + + data, err := json.Marshal(federatedClusterRoleBinding) + + if err != nil { + return err + } + + cli := c.k8sClient.(*kubernetes.Clientset) + + err = cli.RESTClient().Post(). + AbsPath(federatedResourceAPIPath). + Body(data). + Do().Error() + + if err != nil { + if errors.IsAlreadyExists(err) { + return nil + } + return err + } + } else { + + clusterRoleBinding := &rbacv1.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("fed-%s", globalRoleBinding.Name), + }, + Subjects: ensureSubjectAPIVersionIsValid(globalRoleBinding.Subjects), + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: iamv1alpha2.ResourceKindClusterRole, + Name: iamv1alpha2.ClusterAdmin, + }, + } + + err := controllerutil.SetControllerReference(globalRoleBinding, clusterRoleBinding, scheme.Scheme) + + if err != nil { + return err + } + + _, err = c.k8sClient.RbacV1().ClusterRoleBindings().Create(clusterRoleBinding) + + if err != nil { + if errors.IsAlreadyExists(err) { + return nil + } + return err + } + } + + return nil +} + +func ensureSubjectAPIVersionIsValid(subjects []rbacv1.Subject) []rbacv1.Subject { + validSubjects := make([]rbacv1.Subject, 0) + for _, subject := range subjects { + if subject.Kind == iamv1alpha2.ResourceKindUser { + validSubject := rbacv1.Subject{ + Kind: iamv1alpha2.ResourceKindUser, + APIGroup: "rbac.authorization.k8s.io", + Name: subject.Name, + } + validSubjects = append(validSubjects, validSubject) + } + } + return validSubjects +} diff --git a/pkg/kapis/iam/v1alpha2/handler.go b/pkg/kapis/iam/v1alpha2/handler.go index 3dcef3165..a535e98f4 100644 --- a/pkg/kapis/iam/v1alpha2/handler.go +++ b/pkg/kapis/iam/v1alpha2/handler.go @@ -10,7 +10,6 @@ import ( iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" "kubesphere.io/kubesphere/pkg/apiserver/query" - apirequeset "kubesphere.io/kubesphere/pkg/apiserver/request" "kubesphere.io/kubesphere/pkg/models/iam/am" "kubesphere.io/kubesphere/pkg/models/iam/im" servererr "kubesphere.io/kubesphere/pkg/server/errors" @@ -34,14 +33,7 @@ type Member struct { RoleRef string `json:"roleRef"` } -func (h *iamHandler) DescribeUserOrClusterMember(request *restful.Request, response *restful.Response) { - requestInfo, ok := apirequeset.RequestInfoFrom(request.Request.Context()) - - if ok && requestInfo.ResourceScope == apirequeset.ClusterScope { - h.DescribeClusterMember(request, response) - return - } - +func (h *iamHandler) DescribeUser(request *restful.Request, response *restful.Response) { username := request.PathParameter("user") user, err := h.im.DescribeUser(username) @@ -68,67 +60,121 @@ func (h *iamHandler) DescribeUserOrClusterMember(request *restful.Request, respo response.WriteEntity(user) } -func (h *iamHandler) RetrieveMemberRole(req *restful.Request, resp *restful.Response) { - username := req.PathParameter("user") +func (h *iamHandler) RetrieveMemberRoleTemplates(request *restful.Request, response *restful.Response) { + username := request.PathParameter("user") - if strings.HasSuffix(req.Request.URL.Path, iamv1alpha2.ResourcesSingularGlobalRole) { + if strings.HasSuffix(request.Request.URL.Path, iamv1alpha2.ResourcesPluralGlobalRole) { globalRole, err := h.am.GetGlobalRoleOfUser(username) if err != nil { - api.HandleInternalError(resp, req, err) + api.HandleInternalError(response, request, err) return } - resp.WriteEntity(globalRole) + + result, err := h.am.ListGlobalRoles(&query.Query{ + Pagination: query.NoPagination, + SortBy: "", + Ascending: false, + Filters: map[query.Field]query.Value{iamv1alpha2.AggregateTo: query.Value(globalRole.Name)}, + }) + + if err != nil { + api.HandleInternalError(response, request, err) + return + } + + response.WriteEntity(result.Items) return } - if strings.HasSuffix(req.Request.URL.Path, iamv1alpha2.ResourcesSingularClusterRole) { + if strings.HasSuffix(request.Request.URL.Path, iamv1alpha2.ResourcesPluralClusterRole) { clusterRole, err := h.am.GetClusterRoleOfUser(username) if err != nil { - api.HandleInternalError(resp, req, err) + api.HandleInternalError(response, request, err) return } - resp.WriteEntity(clusterRole) + + result, err := h.am.ListClusterRoles(&query.Query{ + Pagination: query.NoPagination, + SortBy: "", + Ascending: false, + Filters: map[query.Field]query.Value{iamv1alpha2.AggregateTo: query.Value(clusterRole.Name)}, + }) + + if err != nil { + api.HandleInternalError(response, request, err) + return + } + + response.WriteEntity(result.Items) return } - if strings.HasSuffix(req.Request.URL.Path, iamv1alpha2.ResourcesSingularWorkspaceRole) { - workspace := req.PathParameter("workspace") + if strings.HasSuffix(request.Request.URL.Path, iamv1alpha2.ResourcesPluralWorkspaceRole) { + workspace := request.PathParameter("workspace") workspaceRole, err := h.am.GetWorkspaceRoleOfUser(username, workspace) if err != nil { - api.HandleInternalError(resp, req, err) + api.HandleInternalError(response, request, err) return } - resp.WriteEntity(workspaceRole) + result, err := h.am.ListWorkspaceRoles(&query.Query{ + Pagination: query.NoPagination, + SortBy: "", + Ascending: false, + Filters: map[query.Field]query.Value{iamv1alpha2.AggregateTo: query.Value(workspaceRole.Name)}, + }) + + if err != nil { + api.HandleInternalError(response, request, err) + return + } + + response.WriteEntity(result.Items) return } - if strings.HasSuffix(req.Request.URL.Path, iamv1alpha2.ResourcesSingularRole) { - namespace := req.PathParameter("namespace") + if strings.HasSuffix(request.Request.URL.Path, iamv1alpha2.ResourcesPluralRole) { + namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops")) + + if err != nil { + klog.Error(err) + if errors.IsNotFound(err) { + api.HandleNotFound(response, request, err) + return + } + api.HandleInternalError(response, request, err) + return + } role, err := h.am.GetNamespaceRoleOfUser(username, namespace) if err != nil { - api.HandleInternalError(resp, req, err) + api.HandleInternalError(response, request, err) return } - resp.WriteEntity(role) + + result, err := h.am.ListRoles(namespace, &query.Query{ + Pagination: query.NoPagination, + SortBy: "", + Ascending: false, + Filters: map[query.Field]query.Value{iamv1alpha2.AggregateTo: query.Value(role.Name)}, + }) + + if err != nil { + api.HandleInternalError(response, request, err) + return + } + + response.WriteEntity(result.Items) return } } -func (h *iamHandler) ListUsersOrClusterMembers(request *restful.Request, response *restful.Response) { - requestInfo, ok := apirequeset.RequestInfoFrom(request.Request.Context()) - - if ok && requestInfo.ResourceScope == apirequeset.ClusterScope { - h.ListClusterMembers(request, response) - return - } - +func (h *iamHandler) ListUsers(request *restful.Request, response *restful.Response) { queryParam := query.ParseQueryParameter(request) result, err := h.im.ListUsers(queryParam) if err != nil { @@ -409,15 +455,7 @@ func (h *iamHandler) DeleteWorkspaceRole(request *restful.Request, response *res response.WriteEntity(servererr.None) } -func (h *iamHandler) CreateUserOrClusterMembers(request *restful.Request, response *restful.Response) { - - requestInfo, ok := apirequeset.RequestInfoFrom(request.Request.Context()) - - if ok && requestInfo.ResourceScope == apirequeset.ClusterScope { - h.CreateClusterMembers(request, response) - return - } - +func (h *iamHandler) CreateUser(request *restful.Request, response *restful.Response) { var user iamv1alpha2.User err := request.ReadEntity(&user) @@ -477,14 +515,7 @@ func (h *iamHandler) CreateUserOrClusterMembers(request *restful.Request, respon response.WriteEntity(created) } -func (h *iamHandler) UpdateUserOrClusterMember(request *restful.Request, response *restful.Response) { - requestInfo, ok := apirequeset.RequestInfoFrom(request.Request.Context()) - - if ok && requestInfo.ResourceScope == apirequeset.ClusterScope { - h.UpdateClusterMember(request, response) - return - } - +func (h *iamHandler) UpdateUser(request *restful.Request, response *restful.Response) { username := request.PathParameter("user") var user iamv1alpha2.User @@ -538,14 +569,7 @@ func (h *iamHandler) UpdateUserOrClusterMember(request *restful.Request, respons response.WriteEntity(updated) } -func (h *iamHandler) DeleteUserOrClusterMember(request *restful.Request, response *restful.Response) { - requestInfo, ok := apirequeset.RequestInfoFrom(request.Request.Context()) - - if ok && requestInfo.ResourceScope == apirequeset.ClusterScope { - h.RemoveClusterMember(request, response) - return - } - +func (h *iamHandler) DeleteUser(request *restful.Request, response *restful.Response) { username := request.PathParameter("user") err := h.im.DeleteUser(username) @@ -1127,7 +1151,7 @@ func (h *iamHandler) CreateClusterMembers(request *restful.Request, response *re } func (h *iamHandler) RemoveClusterMember(request *restful.Request, response *restful.Response) { - username := request.PathParameter("user") + username := request.PathParameter("clustermember") err := h.am.RemoveUserFromCluster(username) @@ -1145,7 +1169,7 @@ func (h *iamHandler) RemoveClusterMember(request *restful.Request, response *res } func (h *iamHandler) UpdateClusterMember(request *restful.Request, response *restful.Response) { - username := request.PathParameter("user") + username := request.PathParameter("clustermember") var member Member @@ -1183,11 +1207,11 @@ func (h *iamHandler) UpdateClusterMember(request *restful.Request, response *res } func (h *iamHandler) DescribeClusterMember(request *restful.Request, response *restful.Response) { - username := request.PathParameter("user") + username := request.PathParameter("clustermember") queryParam := query.New() queryParam.Filters[query.FieldName] = query.Value(username) - queryParam.Filters[iamv1alpha2.ScopeCluster] = iamv1alpha2.LocalCluster + queryParam.Filters[iamv1alpha2.ScopeCluster] = "true" result, err := h.im.ListUsers(queryParam) @@ -1208,7 +1232,7 @@ func (h *iamHandler) DescribeClusterMember(request *restful.Request, response *r func (h *iamHandler) ListClusterMembers(request *restful.Request, response *restful.Response) { queryParam := query.ParseQueryParameter(request) - queryParam.Filters[iamv1alpha2.ScopeCluster] = iamv1alpha2.LocalCluster + queryParam.Filters[iamv1alpha2.ScopeCluster] = "true" result, err := h.im.ListUsers(queryParam) diff --git a/pkg/kapis/iam/v1alpha2/register.go b/pkg/kapis/iam/v1alpha2/register.go index a8b109ac2..995a3395f 100644 --- a/pkg/kapis/iam/v1alpha2/register.go +++ b/pkg/kapis/iam/v1alpha2/register.go @@ -44,33 +44,65 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf // users ws.Route(ws.POST("/users"). - To(handler.CreateUserOrClusterMembers). + To(handler.CreateUser). Doc("Create user in global scope."). Returns(http.StatusOK, api.StatusOK, iamv1alpha2.User{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.DELETE("/users/{user}"). - To(handler.DeleteUserOrClusterMember). + To(handler.DeleteUser). Doc("Delete user."). Returns(http.StatusOK, api.StatusOK, errors.None). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.PUT("/users/{user}"). - To(handler.UpdateUserOrClusterMember). + To(handler.UpdateUser). Doc("Update user info."). Reads(iamv1alpha2.User{}). Returns(http.StatusOK, api.StatusOK, iamv1alpha2.User{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/users/{user}"). - To(handler.DescribeUserOrClusterMember). + To(handler.DescribeUser). Doc("Retrieve user details."). Param(ws.PathParameter("user", "username")). Returns(http.StatusOK, api.StatusOK, iamv1alpha2.User{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/users"). - To(handler.ListUsersOrClusterMembers). + To(handler.ListUsers). Doc("List all users."). Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{iamv1alpha2.User{}}}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) + // clustermembers + ws.Route(ws.POST("/clustermembers"). + To(handler.CreateClusterMembers). + Doc("Add user to current cluster."). + Reads([]Member{}). + Returns(http.StatusOK, api.StatusOK, errors.None). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) + ws.Route(ws.DELETE("/clustermembers/{clustermember}"). + To(handler.RemoveClusterMember). + Doc("Delete user from cluster scope."). + Returns(http.StatusOK, api.StatusOK, errors.None). + Param(ws.PathParameter("clustermember", "username")). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) + ws.Route(ws.PUT("/clustermembers/{clustermember}"). + To(handler.UpdateClusterMember). + Doc("Update user cluster role bind."). + Reads(Member{}). + Returns(http.StatusOK, api.StatusOK, iamv1alpha2.User{}). + Param(ws.PathParameter("clustermember", "username")). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) + ws.Route(ws.GET("/clustermembers/{clustermember}"). + To(handler.DescribeClusterMember). + Doc("Retrieve user details in cluster."). + Param(ws.PathParameter("clustermember", "username")). + Returns(http.StatusOK, api.StatusOK, iamv1alpha2.User{}). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) + ws.Route(ws.GET("/clustermembers"). + To(handler.ListClusterMembers). + Doc("List all users in cluster."). + Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{iamv1alpha2.User{}}}). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) + ws.Route(ws.GET("/workspaces/{workspace}/users"). To(handler.ListWorkspaceMembers). Doc("List all members in the specified workspace."). @@ -337,28 +369,35 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf Returns(http.StatusOK, api.StatusOK, rbacv1.ClusterRole{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) - ws.Route(ws.GET("/users/{user}/globalrole"). - To(handler.RetrieveMemberRole). - Doc("Retrieve user's global role."). + ws.Route(ws.GET("/users/{user}/globalroles"). + To(handler.RetrieveMemberRoleTemplates). + Doc("Retrieve user's global role templates."). Param(ws.PathParameter("user", "username")). Returns(http.StatusOK, api.StatusOK, iamv1alpha2.GlobalRole{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) - ws.Route(ws.GET("/users/{user}/clusterrole"). - To(handler.RetrieveMemberRole). - Doc("Retrieve user's role in cluster."). + ws.Route(ws.GET("/users/{user}/clusterroles"). + To(handler.RetrieveMemberRoleTemplates). + Doc("Retrieve user's role templates in cluster."). Param(ws.PathParameter("user", "username")). Returns(http.StatusOK, api.StatusOK, rbacv1.ClusterRole{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) - ws.Route(ws.GET("/workspaces/{workspace}/users/{user}/workspacerole"). - To(handler.RetrieveMemberRole). - Doc("Retrieve member's role in workspace."). + ws.Route(ws.GET("/workspaces/{workspace}/users/{user}/workspaceroles"). + To(handler.RetrieveMemberRoleTemplates). + Doc("Retrieve member's role templates in workspace."). Param(ws.PathParameter("workspace", "workspace")). Param(ws.PathParameter("user", "username")). Returns(http.StatusOK, api.StatusOK, iamv1alpha2.WorkspaceRole{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) - ws.Route(ws.GET("/namespaces/{namespace}/users/{user}/role"). - To(handler.RetrieveMemberRole). - Doc("Retrieve member's role in namespace."). + ws.Route(ws.GET("/namespaces/{namespace}/users/{user}/roles"). + To(handler.RetrieveMemberRoleTemplates). + Doc("Retrieve member's role templates in namespace."). + Param(ws.PathParameter("namespace", "namespace")). + Param(ws.PathParameter("user", "username")). + Returns(http.StatusOK, api.StatusOK, rbacv1.Role{}). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) + ws.Route(ws.GET("/devops/{devops}/users/{user}/roles"). + To(handler.RetrieveMemberRoleTemplates). + Doc("Retrieve member's role templates in devops project."). Param(ws.PathParameter("namespace", "namespace")). Param(ws.PathParameter("user", "username")). Returns(http.StatusOK, api.StatusOK, rbacv1.Role{}). diff --git a/pkg/kapis/tenant/v1alpha2/register.go b/pkg/kapis/tenant/v1alpha2/register.go index 9a3c9d020..a8ab022d6 100644 --- a/pkg/kapis/tenant/v1alpha2/register.go +++ b/pkg/kapis/tenant/v1alpha2/register.go @@ -82,6 +82,12 @@ func AddToContainer(c *restful.Container, factory informers.InformerFactory, k8s Doc("List clusters authorized to the specified workspace."). Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag})) + ws.Route(ws.GET("/namespaces"). + To(handler.ListNamespaces). + Param(ws.PathParameter("workspace", "workspace name")). + Doc("List the namespaces for the current user"). + Returns(http.StatusOK, api.StatusOK, []corev1.Namespace{}). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag})) ws.Route(ws.GET("/workspaces/{workspace}/namespaces"). To(handler.ListNamespaces). Param(ws.PathParameter("workspace", "workspace name")). diff --git a/pkg/models/iam/am/am.go b/pkg/models/iam/am/am.go index b359b5c72..ed03c2653 100644 --- a/pkg/models/iam/am/am.go +++ b/pkg/models/iam/am/am.go @@ -18,6 +18,7 @@ package am import ( "encoding/json" "fmt" + corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -39,7 +40,7 @@ type AccessManagementInterface interface { GetWorkspaceRoleOfUser(username, workspace string) (*iamv1alpha2.WorkspaceRole, error) GetClusterRoleOfUser(username string) (*rbacv1.ClusterRole, error) GetNamespaceRoleOfUser(username, namespace string) (*rbacv1.Role, error) - ListRoles(username string, query *query.Query) (*api.ListResult, error) + ListRoles(namespace string, query *query.Query) (*api.ListResult, error) ListClusterRoles(query *query.Query) (*api.ListResult, error) ListWorkspaceRoles(query *query.Query) (*api.ListResult, error) ListGlobalRoles(query *query.Query) (*api.ListResult, error) @@ -70,6 +71,7 @@ type AccessManagementInterface interface { CreateOrUpdateClusterRoleBinding(username string, role string) error RemoveUserFromCluster(username string) error GetControlledNamespace(devops string) (string, error) + GetControlledWorkspace(namespace string) (string, error) } type amOperator struct { @@ -341,11 +343,11 @@ func contains(subjects []rbacv1.Subject, username string) bool { } func (am *amOperator) ListRoles(namespace string, query *query.Query) (*api.ListResult, error) { - return am.resourceGetter.List("roles", namespace, query) + return am.resourceGetter.List(iamv1alpha2.ResourcesPluralRole, namespace, query) } func (am *amOperator) ListClusterRoles(query *query.Query) (*api.ListResult, error) { - return am.resourceGetter.List("clusterroles", "", query) + return am.resourceGetter.List(iamv1alpha2.ResourcesPluralClusterRole, "", query) } func (am *amOperator) ListWorkspaceRoles(queryParam *query.Query) (*api.ListResult, error) { @@ -495,7 +497,7 @@ func (am *amOperator) CreateOrUpdateWorkspaceRoleBinding(username string, worksp roleBinding := iamv1alpha2.WorkspaceRoleBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-%s-%s", workspace, username, role), + Name: fmt.Sprintf("%s-%s", role, username), Labels: map[string]string{iamv1alpha2.UserReferenceLabel: username, tenantv1alpha1.WorkspaceLabel: workspace}, }, @@ -833,30 +835,44 @@ func (am *amOperator) DeleteNamespaceRole(namespace string, name string) error { } // GetRoleReferenceRules attempts to resolve the RoleBinding or ClusterRoleBinding. -func (am *amOperator) GetRoleReferenceRules(roleRef rbacv1.RoleRef, namespace string) (string, []rbacv1.PolicyRule, error) { +func (am *amOperator) GetRoleReferenceRules(roleRef rbacv1.RoleRef, namespace string) (regoPolicy string, rules []rbacv1.PolicyRule, err error) { + + empty := make([]rbacv1.PolicyRule, 0) + switch roleRef.Kind { case iamv1alpha2.ResourceKindRole: role, err := am.GetNamespaceRole(namespace, roleRef.Name) if err != nil { + if errors.IsNotFound(err) { + return "", empty, nil + } return "", nil, err } - return role.Annotations[iamv1alpha2.RegoOverrideAnnotation], role.Rules, nil case iamv1alpha2.ResourceKindClusterRole: clusterRole, err := am.GetClusterRole(roleRef.Name) if err != nil { + if errors.IsNotFound(err) { + return "", empty, nil + } return "", nil, err } return clusterRole.Annotations[iamv1alpha2.RegoOverrideAnnotation], clusterRole.Rules, nil case iamv1alpha2.ResourceKindGlobalRole: globalRole, err := am.GetGlobalRole(roleRef.Name) if err != nil { + if errors.IsNotFound(err) { + return "", empty, nil + } return "", nil, err } return globalRole.Annotations[iamv1alpha2.RegoOverrideAnnotation], globalRole.Rules, nil case iamv1alpha2.ResourceKindWorkspaceRole: workspaceRole, err := am.GetWorkspaceRole("", roleRef.Name) if err != nil { + if errors.IsNotFound(err) { + return "", empty, nil + } return "", nil, err } return workspaceRole.Annotations[iamv1alpha2.RegoOverrideAnnotation], workspaceRole.Rules, nil @@ -910,3 +926,16 @@ func (am *amOperator) GetControlledNamespace(devops string) (string, error) { return devopsProject.Status.AdminNamespace, nil } + +func (am *amOperator) GetControlledWorkspace(namespace string) (string, error) { + obj, err := am.resourceGetter.Get("namespaces", "", namespace) + if err != nil { + if errors.IsNotFound(err) { + return "", nil + } + klog.Error(err) + return "", err + } + ns := obj.(*corev1.Namespace) + return ns.Labels[tenantv1alpha1.WorkspaceLabel], nil +} diff --git a/pkg/models/iam/im/im.go b/pkg/models/iam/im/im.go index ef672771e..975f3ed47 100644 --- a/pkg/models/iam/im/im.go +++ b/pkg/models/iam/im/im.go @@ -70,6 +70,7 @@ func (im *defaultIMOperator) UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.Us old := obj.(*iamv1alpha2.User).DeepCopy() user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] = old.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] user.Spec.EncryptedPassword = old.Spec.EncryptedPassword + user.Status = old.Status return im.ksClient.IamV1alpha2().Users().Update(user) } diff --git a/pkg/models/resources/v1alpha3/role/roles.go b/pkg/models/resources/v1alpha3/role/roles.go index dc1e79f6e..470035d96 100644 --- a/pkg/models/resources/v1alpha3/role/roles.go +++ b/pkg/models/resources/v1alpha3/role/roles.go @@ -107,7 +107,7 @@ func (d *rolesGetter) fetchAggregationRoles(namespace, name string) ([]*rbacv1.R if err = json.Unmarshal([]byte(annotation), &roleNames); err == nil { for _, roleName := range roleNames { - role, err := d.Get("", roleName) + role, err := d.Get(namespace, roleName) if err != nil { if errors.IsNotFound(err) { diff --git a/pkg/models/resources/v1alpha3/user/users.go b/pkg/models/resources/v1alpha3/user/users.go index 93bc125c5..54bd88349 100644 --- a/pkg/models/resources/v1alpha3/user/users.go +++ b/pkg/models/resources/v1alpha3/user/users.go @@ -58,7 +58,7 @@ func (d *usersGetter) List(_ string, query *query.Query) (*api.ListResult, error users, err = d.listAllUsersInWorkspace(string(workspace), string(workspaceRole)) delete(query.Filters, iamv1alpha2.ScopeWorkspace) delete(query.Filters, iamv1alpha2.ResourcesSingularWorkspaceRole) - } else if cluster := query.Filters[iamv1alpha2.ScopeCluster]; cluster == iamv1alpha2.LocalCluster { + } else if cluster := query.Filters[iamv1alpha2.ScopeCluster]; cluster == "true" { clusterRole := query.Filters[iamv1alpha2.ResourcesSingularClusterRole] users, err = d.listAllUsersInCluster(string(clusterRole)) delete(query.Filters, iamv1alpha2.ScopeCluster) diff --git a/pkg/models/tenant/tenant.go b/pkg/models/tenant/tenant.go index f2d6f4ce0..b5b3aa3ec 100644 --- a/pkg/models/tenant/tenant.go +++ b/pkg/models/tenant/tenant.go @@ -183,7 +183,9 @@ func (t *tenantOperator) ListNamespaces(user user.Info, workspace string, queryP if decision == authorizer.DecisionAllow { - queryParam.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", tenantv1alpha1.WorkspaceLabel, workspace)) + if workspace != "" { + queryParam.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", tenantv1alpha1.WorkspaceLabel, workspace)) + } result, err := t.resourceGetter.List("namespaces", "", queryParam) @@ -213,7 +215,7 @@ func (t *tenantOperator) ListNamespaces(user user.Info, workspace string, queryP } // skip if not controlled by the specified workspace - if ns := namespace.(*corev1.Namespace); ns.Labels[tenantv1alpha1.WorkspaceLabel] != workspace { + if ns := namespace.(*corev1.Namespace); workspace != "" && ns.Labels[tenantv1alpha1.WorkspaceLabel] != workspace { continue }