From 382be8b16bb60421589afbb4ab4eb31154c5f794 Mon Sep 17 00:00:00 2001 From: hongming Date: Fri, 20 May 2022 17:12:55 +0800 Subject: [PATCH] fix: cluster list granted to users is incorrect --- cmd/controller-manager/app/controllers.go | 3 +- pkg/apiserver/apiserver.go | 24 ++-- pkg/controller/cluster/cluster_controller.go | 110 ++++++++++++++---- pkg/kapis/tenant/v1alpha2/handler.go | 12 +- pkg/kapis/tenant/v1alpha2/register.go | 8 +- pkg/kapis/tenant/v1alpha3/handler.go | 8 +- pkg/kapis/tenant/v1alpha3/register.go | 13 +-- pkg/models/resources/v1alpha3/interface.go | 35 ++---- pkg/models/tenant/tenant.go | 102 +++++++--------- pkg/models/tenant/tenent_test.go | 2 +- .../kubesphere.io/api/iam/v1alpha2/types.go | 1 + 11 files changed, 176 insertions(+), 142 deletions(-) diff --git a/cmd/controller-manager/app/controllers.go b/cmd/controller-manager/app/controllers.go index 4b602a59b..a4a3be453 100644 --- a/cmd/controller-manager/app/controllers.go +++ b/cmd/controller-manager/app/controllers.go @@ -488,9 +488,10 @@ func addAllControllers(mgr manager.Manager, client k8s.Client, informerFactory i if cmOptions.MultiClusterOptions.Enable { clusterController := cluster.NewClusterController( client.Kubernetes(), + client.KubeSphere(), client.Config(), kubesphereInformer.Cluster().V1alpha1().Clusters(), - client.KubeSphere().ClusterV1alpha1().Clusters(), + kubesphereInformer.Iam().V1alpha2().Users().Lister(), cmOptions.MultiClusterOptions.ClusterControllerResyncPeriod, cmOptions.MultiClusterOptions.HostClusterName, ) diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 3396f0a93..00acc7812 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -26,31 +26,25 @@ import ( "sync" "time" - "kubesphere.io/kubesphere/pkg/models/openpitrix" - "kubesphere.io/kubesphere/pkg/utils/clusterclient" - - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/client-go/discovery" - - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/util/retry" - "github.com/emicklei/go-restful" extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" urlruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" unionauth "k8s.io/apiserver/pkg/authentication/request/union" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" + "k8s.io/client-go/discovery" + "k8s.io/client-go/util/retry" "k8s.io/klog" - runtimecache "sigs.k8s.io/controller-runtime/pkg/cache" - runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" - clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1" iamv1alpha2 "kubesphere.io/api/iam/v1alpha2" notificationv2beta1 "kubesphere.io/api/notification/v2beta1" tenantv1alpha1 "kubesphere.io/api/tenant/v1alpha1" typesv1beta1 "kubesphere.io/api/types/v1beta1" + runtimecache "sigs.k8s.io/controller-runtime/pkg/cache" + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" audit "kubesphere.io/kubesphere/pkg/apiserver/auditing" "kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/basic" @@ -101,6 +95,7 @@ import ( "kubesphere.io/kubesphere/pkg/models/iam/am" "kubesphere.io/kubesphere/pkg/models/iam/group" "kubesphere.io/kubesphere/pkg/models/iam/im" + "kubesphere.io/kubesphere/pkg/models/openpitrix" "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/loginrecord" "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/user" "kubesphere.io/kubesphere/pkg/simple/client/alerting" @@ -113,6 +108,7 @@ import ( "kubesphere.io/kubesphere/pkg/simple/client/monitoring" "kubesphere.io/kubesphere/pkg/simple/client/s3" "kubesphere.io/kubesphere/pkg/simple/client/sonarqube" + "kubesphere.io/kubesphere/pkg/utils/clusterclient" "kubesphere.io/kubesphere/pkg/utils/iputil" "kubesphere.io/kubesphere/pkg/utils/metrics" ) @@ -238,9 +234,9 @@ func (s *APIServer) installKubeSphereAPIs(stopCh <-chan struct{}) { urlruntime.Must(resourcesv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.InformerFactory, s.KubernetesClient.Master())) urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.InformerFactory, s.KubernetesClient.Kubernetes(), - s.KubernetesClient.KubeSphere(), s.EventsClient, s.LoggingClient, s.AuditingClient, amOperator, rbacAuthorizer, s.MonitoringClient, s.RuntimeCache, s.Config.MeteringOptions, s.OpenpitrixClient)) + s.KubernetesClient.KubeSphere(), s.EventsClient, s.LoggingClient, s.AuditingClient, amOperator, imOperator, rbacAuthorizer, s.MonitoringClient, s.RuntimeCache, s.Config.MeteringOptions, s.OpenpitrixClient)) urlruntime.Must(tenantv1alpha3.AddToContainer(s.container, s.InformerFactory, s.KubernetesClient.Kubernetes(), - s.KubernetesClient.KubeSphere(), s.EventsClient, s.LoggingClient, s.AuditingClient, amOperator, rbacAuthorizer, s.MonitoringClient, s.RuntimeCache, s.Config.MeteringOptions, s.OpenpitrixClient)) + s.KubernetesClient.KubeSphere(), s.EventsClient, s.LoggingClient, s.AuditingClient, amOperator, imOperator, rbacAuthorizer, s.MonitoringClient, s.RuntimeCache, s.Config.MeteringOptions, s.OpenpitrixClient)) urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), rbacAuthorizer, s.KubernetesClient.Config(), s.Config.TerminalOptions)) urlruntime.Must(clusterkapisv1alpha1.AddToContainer(s.container, s.KubernetesClient.KubeSphere(), diff --git a/pkg/controller/cluster/cluster_controller.go b/pkg/controller/cluster/cluster_controller.go index 63138f566..c05e04a93 100644 --- a/pkg/controller/cluster/cluster_controller.go +++ b/pkg/controller/cluster/cluster_controller.go @@ -25,6 +25,7 @@ import ( "fmt" "net/http" "reflect" + "strings" "time" "gopkg.in/yaml.v2" @@ -48,11 +49,13 @@ import ( fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1" clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1" + iamv1alpha2 "kubesphere.io/api/iam/v1alpha2" "kubesphere.io/kubesphere/pkg/apiserver/config" - clusterclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/cluster/v1alpha1" + kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned" clusterinformer "kubesphere.io/kubesphere/pkg/client/informers/externalversions/cluster/v1alpha1" clusterlister "kubesphere.io/kubesphere/pkg/client/listers/cluster/v1alpha1" + iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/simple/client/multicluster" "kubesphere.io/kubesphere/pkg/utils/k8sutil" @@ -126,12 +129,13 @@ type clusterController struct { eventRecorder record.EventRecorder // build this only for host cluster - client kubernetes.Interface + k8sClient kubernetes.Interface hostConfig *rest.Config - clusterClient clusterclient.ClusterInterface + ksClient kubesphere.Interface clusterLister clusterlister.ClusterLister + userLister iamv1alpha2listers.UserLister clusterHasSynced cache.InformerSynced queue workqueue.RateLimitingInterface @@ -144,10 +148,11 @@ type clusterController struct { } func NewClusterController( - client kubernetes.Interface, + k8sClient kubernetes.Interface, + ksClient kubesphere.Interface, config *rest.Config, clusterInformer clusterinformer.ClusterInformer, - clusterClient clusterclient.ClusterInterface, + userLister iamv1alpha2listers.UserLister, resyncPeriod time.Duration, hostClusterName string, ) *clusterController { @@ -156,19 +161,20 @@ func NewClusterController( broadcaster.StartLogging(func(format string, args ...interface{}) { klog.Info(fmt.Sprintf(format, args)) }) - broadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: client.CoreV1().Events("")}) + broadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: k8sClient.CoreV1().Events("")}) recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cluster-controller"}) c := &clusterController{ eventBroadcaster: broadcaster, eventRecorder: recorder, - client: client, + k8sClient: k8sClient, + ksClient: ksClient, hostConfig: config, - clusterClient: clusterClient, queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "cluster"), workerLoopPeriod: time.Second, resyncPeriod: resyncPeriod, hostClusterName: hostClusterName, + userLister: userLister, } c.clusterLister = clusterInformer.Lister() c.clusterHasSynced = clusterInformer.Informer().HasSynced @@ -213,7 +219,7 @@ func (c *clusterController) Run(workers int, stopCh <-chan struct{}) error { klog.Errorf("Error create host cluster, error %v", err) } - if err := c.probeClusters(); err != nil { + if err := c.resyncClusters(); err != nil { klog.Errorf("failed to reconcile cluster ready status, err: %v", err) } }, c.resyncPeriod, stopCh) @@ -256,7 +262,7 @@ func (c *clusterController) reconcileHostCluster() error { if len(clusters) == 0 { hostCluster.Spec.Connection.KubeConfig = hostKubeConfig hostCluster.Name = c.hostClusterName - _, err = c.clusterClient.Create(context.TODO(), hostCluster, metav1.CreateOptions{}) + _, err = c.ksClient.ClusterV1alpha1().Clusters().Create(context.TODO(), hostCluster, metav1.CreateOptions{}) return err } else if len(clusters) > 1 { return fmt.Errorf("there MUST not be more than one host clusters, while there are %d", len(clusters)) @@ -280,18 +286,20 @@ func (c *clusterController) reconcileHostCluster() error { } // update host cluster config - _, err = c.clusterClient.Update(context.TODO(), cluster, metav1.UpdateOptions{}) + _, err = c.ksClient.ClusterV1alpha1().Clusters().Update(context.TODO(), cluster, metav1.UpdateOptions{}) return err } -func (c *clusterController) probeClusters() error { +func (c *clusterController) resyncClusters() error { clusters, err := c.clusterLister.List(labels.Everything()) if err != nil { return err } for _, cluster := range clusters { - c.syncCluster(cluster.Name) + if err = c.syncCluster(cluster.Name); err != nil { + klog.Warningf("failed to sync cluster %s: %s", cluster.Name, err) + } } return nil } @@ -328,7 +336,7 @@ func (c *clusterController) syncCluster(key string) error { // registering our finalizer. if !sets.NewString(cluster.ObjectMeta.Finalizers...).Has(clusterv1alpha1.Finalizer) { cluster.ObjectMeta.Finalizers = append(cluster.ObjectMeta.Finalizers, clusterv1alpha1.Finalizer) - if cluster, err = c.clusterClient.Update(context.TODO(), cluster, metav1.UpdateOptions{}); err != nil { + if cluster, err = c.ksClient.ClusterV1alpha1().Clusters().Update(context.TODO(), cluster, metav1.UpdateOptions{}); err != nil { return err } } @@ -338,17 +346,21 @@ func (c *clusterController) syncCluster(key string) error { // need to unJoin federation first, before there are // some cleanup work to do in member cluster which depends // agent to proxy traffic - err = c.unJoinFederation(nil, name) - if err != nil { + if err = c.unJoinFederation(nil, name); err != nil { klog.Errorf("Failed to unjoin federation for cluster %s, error %v", name, err) return err } + // cleanup after cluster has been deleted + if err := c.syncClusterMembers(nil, cluster); err != nil { + klog.Errorf("Failed to sync cluster members for %s: %v", name, err) + return err + } // remove our cluster finalizer finalizers := sets.NewString(cluster.ObjectMeta.Finalizers...) finalizers.Delete(clusterv1alpha1.Finalizer) cluster.ObjectMeta.Finalizers = finalizers.List() - if _, err = c.clusterClient.Update(context.TODO(), cluster, metav1.UpdateOptions{}); err != nil { + if _, err = c.ksClient.ClusterV1alpha1().Clusters().Update(context.TODO(), cluster, metav1.UpdateOptions{}); err != nil { return err } } @@ -407,7 +419,7 @@ func (c *clusterController) syncCluster(key string) error { } c.updateClusterCondition(cluster, federationNotReadyCondition) - _, err = c.clusterClient.Update(context.TODO(), cluster, metav1.UpdateOptions{}) + _, err = c.ksClient.ClusterV1alpha1().Clusters().Update(context.TODO(), cluster, metav1.UpdateOptions{}) if err != nil { klog.Errorf("Failed to update cluster status, %#v", err) } @@ -496,8 +508,8 @@ func (c *clusterController) syncCluster(key string) error { return err } - if !reflect.DeepEqual(oldCluster, cluster) { - _, err = c.clusterClient.Update(context.TODO(), cluster, metav1.UpdateOptions{}) + if !reflect.DeepEqual(oldCluster.Status, cluster.Status) { + _, err = c.ksClient.ClusterV1alpha1().Clusters().Update(context.TODO(), cluster, metav1.UpdateOptions{}) if err != nil { klog.Errorf("Failed to update cluster status, %#v", err) return err @@ -508,6 +520,10 @@ func (c *clusterController) syncCluster(key string) error { return err } + if err = c.syncClusterMembers(clusterClient, cluster); err != nil { + return fmt.Errorf("failed to sync cluster membership for %s: %s", cluster.Name, err) + } + return nil } @@ -541,7 +557,7 @@ func (c *clusterController) setClusterNameInConfigMap(client kubernetes.Interfac } func (c *clusterController) checkIfClusterIsHostCluster(memberClusterNodes *v1.NodeList) bool { - hostNodes, err := c.client.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) + hostNodes, err := c.k8sClient.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) if err != nil { return false } @@ -786,3 +802,55 @@ func (c *clusterController) updateKubeConfigExpirationDateCondition(cluster *clu }) return nil } + +// syncClusterMembers Sync granted clusters for users periodically +func (c *clusterController) syncClusterMembers(clusterClient *kubernetes.Clientset, cluster *clusterv1alpha1.Cluster) error { + users, err := c.userLister.List(labels.Everything()) + if err != nil { + return fmt.Errorf("failed to list users: %s", err) + } + + grantedUsers := sets.NewString() + clusterName := cluster.Name + if cluster.DeletionTimestamp.IsZero() { + list, err := clusterClient.RbacV1().ClusterRoleBindings().List(context.Background(), + metav1.ListOptions{LabelSelector: iamv1alpha2.UserReferenceLabel}) + if err != nil { + return fmt.Errorf("failed to list clusterrolebindings: %s", err) + } + for _, clusterRoleBinding := range list.Items { + for _, sub := range clusterRoleBinding.Subjects { + if sub.Kind == iamv1alpha2.ResourceKindUser { + grantedUsers.Insert(sub.Name) + } + } + } + } + + for _, user := range users { + user = user.DeepCopy() + grantedClustersAnnotation := user.Annotations[iamv1alpha2.GrantedClustersAnnotation] + var grantedClusters sets.String + if len(grantedClustersAnnotation) > 0 { + grantedClusters = sets.NewString(strings.Split(grantedClustersAnnotation, ",")...) + } else { + grantedClusters = sets.NewString() + } + if grantedUsers.Has(user.Name) && !grantedClusters.Has(clusterName) { + grantedClusters.Insert(clusterName) + } else if !grantedUsers.Has(user.Name) && grantedClusters.Has(clusterName) { + grantedClusters.Delete(clusterName) + } + grantedClustersAnnotation = strings.Join(grantedClusters.List(), ",") + if user.Annotations[iamv1alpha2.GrantedClustersAnnotation] != grantedClustersAnnotation { + if user.Annotations == nil { + user.Annotations = make(map[string]string, 0) + } + user.Annotations[iamv1alpha2.GrantedClustersAnnotation] = grantedClustersAnnotation + if _, err := c.ksClient.IamV1alpha2().Users().Update(context.Background(), user, metav1.UpdateOptions{}); err != nil { + return fmt.Errorf("failed to update user %s: %s", user.Name, err) + } + } + } + return nil +} diff --git a/pkg/kapis/tenant/v1alpha2/handler.go b/pkg/kapis/tenant/v1alpha2/handler.go index 663a7af95..f59c1004a 100644 --- a/pkg/kapis/tenant/v1alpha2/handler.go +++ b/pkg/kapis/tenant/v1alpha2/handler.go @@ -28,8 +28,6 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/klog" - "kubesphere.io/kubesphere/pkg/models/openpitrix" - quotav1alpha2 "kubesphere.io/api/quota/v1alpha2" tenantv1alpha2 "kubesphere.io/api/tenant/v1alpha2" @@ -43,6 +41,8 @@ import ( kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned" "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/models/iam/am" + "kubesphere.io/kubesphere/pkg/models/iam/im" + "kubesphere.io/kubesphere/pkg/models/openpitrix" resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource" "kubesphere.io/kubesphere/pkg/models/tenant" servererr "kubesphere.io/kubesphere/pkg/server/errors" @@ -60,7 +60,7 @@ type tenantHandler struct { func NewTenantHandler(factory informers.InformerFactory, k8sclient kubernetes.Interface, ksclient kubesphere.Interface, evtsClient events.Client, loggingClient logging.Client, auditingclient auditing.Client, - am am.AccessManagementInterface, authorizer authorizer.Authorizer, + am am.AccessManagementInterface, im im.IdentityManagementInterface, authorizer authorizer.Authorizer, monitoringclient monitoringclient.Interface, resourceGetter *resourcev1alpha3.ResourceGetter, meteringOptions *meteringclient.Options, opClient openpitrix.Interface) *tenantHandler { @@ -69,7 +69,7 @@ func NewTenantHandler(factory informers.InformerFactory, k8sclient kubernetes.In } return &tenantHandler{ - tenant: tenant.New(factory, k8sclient, ksclient, evtsClient, loggingClient, auditingclient, am, authorizer, monitoringclient, resourceGetter, opClient), + tenant: tenant.New(factory, k8sclient, ksclient, evtsClient, loggingClient, auditingclient, am, im, authorizer, monitoringclient, resourceGetter, opClient), meteringOptions: meteringOptions, } } @@ -557,8 +557,8 @@ func (h *tenantHandler) ListClusters(r *restful.Request, response *restful.Respo return } - result, err := h.tenant.ListClusters(user) - + queryParam := query.ParseQueryParameter(r) + result, err := h.tenant.ListClusters(user, queryParam) if err != nil { klog.Error(err) if errors.IsNotFound(err) { diff --git a/pkg/kapis/tenant/v1alpha2/register.go b/pkg/kapis/tenant/v1alpha2/register.go index 0d5061828..6eaa70f17 100644 --- a/pkg/kapis/tenant/v1alpha2/register.go +++ b/pkg/kapis/tenant/v1alpha2/register.go @@ -19,8 +19,6 @@ package v1alpha2 import ( "net/http" - "kubesphere.io/kubesphere/pkg/models/openpitrix" - "github.com/emicklei/go-restful" restfulspec "github.com/emicklei/go-restful-openapi" corev1 "k8s.io/api/core/v1" @@ -42,8 +40,10 @@ import ( "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/models" "kubesphere.io/kubesphere/pkg/models/iam/am" + "kubesphere.io/kubesphere/pkg/models/iam/im" "kubesphere.io/kubesphere/pkg/models/metering" "kubesphere.io/kubesphere/pkg/models/monitoring" + "kubesphere.io/kubesphere/pkg/models/openpitrix" resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource" "kubesphere.io/kubesphere/pkg/server/errors" "kubesphere.io/kubesphere/pkg/simple/client/auditing" @@ -65,12 +65,12 @@ func Resource(resource string) schema.GroupResource { func AddToContainer(c *restful.Container, factory informers.InformerFactory, k8sclient kubernetes.Interface, ksclient kubesphere.Interface, evtsClient events.Client, loggingClient logging.Client, - auditingclient auditing.Client, am am.AccessManagementInterface, authorizer authorizer.Authorizer, + auditingclient auditing.Client, am am.AccessManagementInterface, im im.IdentityManagementInterface, authorizer authorizer.Authorizer, monitoringclient monitoringclient.Interface, cache cache.Cache, meteringOptions *meteringclient.Options, opClient openpitrix.Interface) error { mimePatch := []string{restful.MIME_JSON, runtime.MimeMergePatchJson, runtime.MimeJsonPatchJson} ws := runtime.NewWebService(GroupVersion) - handler := NewTenantHandler(factory, k8sclient, ksclient, evtsClient, loggingClient, auditingclient, am, authorizer, monitoringclient, resourcev1alpha3.NewResourceGetter(factory, cache), meteringOptions, opClient) + handler := NewTenantHandler(factory, k8sclient, ksclient, evtsClient, loggingClient, auditingclient, am, im, authorizer, monitoringclient, resourcev1alpha3.NewResourceGetter(factory, cache), meteringOptions, opClient) ws.Route(ws.GET("/clusters"). To(handler.ListClusters). diff --git a/pkg/kapis/tenant/v1alpha3/handler.go b/pkg/kapis/tenant/v1alpha3/handler.go index d67a36ad1..34ee780fb 100644 --- a/pkg/kapis/tenant/v1alpha3/handler.go +++ b/pkg/kapis/tenant/v1alpha3/handler.go @@ -24,8 +24,6 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/klog" - "kubesphere.io/kubesphere/pkg/models/openpitrix" - "kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer" "kubesphere.io/kubesphere/pkg/apiserver/query" @@ -33,6 +31,8 @@ import ( kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned" "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/models/iam/am" + "kubesphere.io/kubesphere/pkg/models/iam/im" + "kubesphere.io/kubesphere/pkg/models/openpitrix" resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource" "kubesphere.io/kubesphere/pkg/models/tenant" "kubesphere.io/kubesphere/pkg/simple/client/auditing" @@ -49,7 +49,7 @@ type tenantHandler struct { func newTenantHandler(factory informers.InformerFactory, k8sclient kubernetes.Interface, ksclient kubesphere.Interface, evtsClient events.Client, loggingClient logging.Client, auditingclient auditing.Client, - am am.AccessManagementInterface, authorizer authorizer.Authorizer, + am am.AccessManagementInterface, im im.IdentityManagementInterface, authorizer authorizer.Authorizer, monitoringclient monitoringclient.Interface, resourceGetter *resourcev1alpha3.ResourceGetter, meteringOptions *meteringclient.Options, opClient openpitrix.Interface) *tenantHandler { @@ -58,7 +58,7 @@ func newTenantHandler(factory informers.InformerFactory, k8sclient kubernetes.In } return &tenantHandler{ - tenant: tenant.New(factory, k8sclient, ksclient, evtsClient, loggingClient, auditingclient, am, authorizer, monitoringclient, resourceGetter, opClient), + tenant: tenant.New(factory, k8sclient, ksclient, evtsClient, loggingClient, auditingclient, am, im, authorizer, monitoringclient, resourceGetter, opClient), meteringOptions: meteringOptions, } } diff --git a/pkg/kapis/tenant/v1alpha3/register.go b/pkg/kapis/tenant/v1alpha3/register.go index 09a3dc127..d514670bb 100644 --- a/pkg/kapis/tenant/v1alpha3/register.go +++ b/pkg/kapis/tenant/v1alpha3/register.go @@ -19,8 +19,6 @@ package v1alpha3 import ( "net/http" - "kubesphere.io/kubesphere/pkg/models/openpitrix" - "github.com/emicklei/go-restful" restfulspec "github.com/emicklei/go-restful-openapi" "k8s.io/apimachinery/pkg/runtime/schema" @@ -39,6 +37,8 @@ import ( "kubesphere.io/kubesphere/pkg/kapis/tenant/v1alpha2" "kubesphere.io/kubesphere/pkg/models" "kubesphere.io/kubesphere/pkg/models/iam/am" + "kubesphere.io/kubesphere/pkg/models/iam/im" + "kubesphere.io/kubesphere/pkg/models/openpitrix" resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource" "kubesphere.io/kubesphere/pkg/server/errors" "kubesphere.io/kubesphere/pkg/simple/client/auditing" @@ -60,14 +60,13 @@ func Resource(resource string) schema.GroupResource { func AddToContainer(c *restful.Container, factory informers.InformerFactory, k8sclient kubernetes.Interface, ksclient kubesphere.Interface, evtsClient events.Client, loggingClient logging.Client, - auditingclient auditing.Client, am am.AccessManagementInterface, authorizer authorizer.Authorizer, - monitoringclient monitoringclient.Interface, cache cache.Cache, meteringOptions *meteringclient.Options, - opClient openpitrix.Interface) error { + auditingclient auditing.Client, am am.AccessManagementInterface, im im.IdentityManagementInterface, authorizer authorizer.Authorizer, + monitoringclient monitoringclient.Interface, cache cache.Cache, meteringOptions *meteringclient.Options, opClient openpitrix.Interface) error { mimePatch := []string{restful.MIME_JSON, runtime.MimeMergePatchJson, runtime.MimeJsonPatchJson} ws := runtime.NewWebService(GroupVersion) - v1alpha2Handler := v1alpha2.NewTenantHandler(factory, k8sclient, ksclient, evtsClient, loggingClient, auditingclient, am, authorizer, monitoringclient, resourcev1alpha3.NewResourceGetter(factory, cache), meteringOptions, opClient) - handler := newTenantHandler(factory, k8sclient, ksclient, evtsClient, loggingClient, auditingclient, am, authorizer, monitoringclient, resourcev1alpha3.NewResourceGetter(factory, cache), meteringOptions, opClient) + v1alpha2Handler := v1alpha2.NewTenantHandler(factory, k8sclient, ksclient, evtsClient, loggingClient, auditingclient, am, im, authorizer, monitoringclient, resourcev1alpha3.NewResourceGetter(factory, cache), meteringOptions, opClient) + handler := newTenantHandler(factory, k8sclient, ksclient, evtsClient, loggingClient, auditingclient, am, im, authorizer, monitoringclient, resourcev1alpha3.NewResourceGetter(factory, cache), meteringOptions, opClient) ws.Route(ws.POST("/workspacetemplates"). To(v1alpha2Handler.CreateWorkspaceTemplate). diff --git a/pkg/models/resources/v1alpha3/interface.go b/pkg/models/resources/v1alpha3/interface.go index da51178f5..1f56579c8 100644 --- a/pkg/models/resources/v1alpha3/interface.go +++ b/pkg/models/resources/v1alpha3/interface.go @@ -20,6 +20,9 @@ import ( "sort" "strings" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/klog" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -150,33 +153,13 @@ func DefaultObjectMetaFilter(item metav1.ObjectMeta, filter query.Filter) bool { } } -func labelMatch(labels map[string]string, filter string) bool { - fields := strings.SplitN(filter, "=", 2) - var key, value string - var opposite bool - if len(fields) == 2 { - key = fields[0] - if strings.HasSuffix(key, "!") { - key = strings.TrimSuffix(key, "!") - opposite = true - } - value = fields[1] - } else { - key = fields[0] - value = "*" +func labelMatch(m map[string]string, filter string) bool { + labelSelector, err := labels.Parse(filter) + if err != nil { + klog.Warningf("invalid labelSelector %s: %s", filter, err) + return false } - for k, v := range labels { - if opposite { - if (k == key) && v != value { - return true - } - } else { - if (k == key) && (value == "*" || v == value) { - return true - } - } - } - return false + return labelSelector.Matches(labels.Set(m)) } func objectsToInterfaces(objs []runtime.Object) []interface{} { diff --git a/pkg/models/tenant/tenant.go b/pkg/models/tenant/tenant.go index eea1a1392..7d264e368 100644 --- a/pkg/models/tenant/tenant.go +++ b/pkg/models/tenant/tenant.go @@ -30,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/client-go/kubernetes" "k8s.io/klog" @@ -40,6 +41,8 @@ import ( tenantv1alpha2 "kubesphere.io/api/tenant/v1alpha2" typesv1beta1 "kubesphere.io/api/types/v1beta1" + iamv1alpha2 "kubesphere.io/api/iam/v1alpha2" + "kubesphere.io/kubesphere/pkg/api" auditingv1alpha1 "kubesphere.io/kubesphere/pkg/api/auditing/v1alpha1" eventsv1alpha1 "kubesphere.io/kubesphere/pkg/api/events/v1alpha1" @@ -53,6 +56,7 @@ import ( "kubesphere.io/kubesphere/pkg/models/auditing" "kubesphere.io/kubesphere/pkg/models/events" "kubesphere.io/kubesphere/pkg/models/iam/am" + "kubesphere.io/kubesphere/pkg/models/iam/im" "kubesphere.io/kubesphere/pkg/models/logging" "kubesphere.io/kubesphere/pkg/models/metering" "kubesphere.io/kubesphere/pkg/models/monitoring" @@ -92,7 +96,7 @@ type Interface interface { DeleteNamespace(workspace, namespace string) error UpdateNamespace(workspace string, namespace *corev1.Namespace) (*corev1.Namespace, error) PatchNamespace(workspace string, namespace *corev1.Namespace) (*corev1.Namespace, error) - ListClusters(info user.Info) (*api.ListResult, error) + ListClusters(info user.Info, queryParam *query.Query) (*api.ListResult, error) Metering(user user.Info, queryParam *meteringv1alpha1.Query, priceInfo meteringclient.PriceInfo) (monitoring.Metrics, error) MeteringHierarchy(user user.Info, queryParam *meteringv1alpha1.Query, priceInfo meteringclient.PriceInfo) (metering.ResourceStatistic, error) CreateWorkspaceResourceQuota(workspace string, resourceQuota *quotav1alpha2.ResourceQuota) (*quotav1alpha2.ResourceQuota, error) @@ -103,6 +107,7 @@ type Interface interface { type tenantOperator struct { am am.AccessManagementInterface + im im.IdentityManagementInterface authorizer authorizer.Authorizer k8sclient kubernetes.Interface ksclient kubesphere.Interface @@ -114,9 +119,10 @@ type tenantOperator struct { opRelease openpitrix.ReleaseInterface } -func New(informers informers.InformerFactory, k8sclient kubernetes.Interface, ksclient kubesphere.Interface, evtsClient eventsclient.Client, loggingClient loggingclient.Client, auditingclient auditingclient.Client, am am.AccessManagementInterface, authorizer authorizer.Authorizer, monitoringclient monitoringclient.Interface, resourceGetter *resourcev1alpha3.ResourceGetter, opClient openpitrix.Interface) Interface { +func New(informers informers.InformerFactory, k8sclient kubernetes.Interface, ksclient kubesphere.Interface, evtsClient eventsclient.Client, loggingClient loggingclient.Client, auditingclient auditingclient.Client, am am.AccessManagementInterface, im im.IdentityManagementInterface, authorizer authorizer.Authorizer, monitoringclient monitoringclient.Interface, resourceGetter *resourcev1alpha3.ResourceGetter, opClient openpitrix.Interface) Interface { return &tenantOperator{ am: am, + im: im, authorizer: authorizer, resourceGetter: resourcesv1alpha3.NewResourceGetter(informers, nil), k8sclient: k8sclient, @@ -498,7 +504,7 @@ func (t *tenantOperator) ListWorkspaceClusters(workspaceName string) (*api.ListR for _, cluster := range workspace.Spec.Placement.Clusters { obj, err := t.resourceGetter.Get(clusterv1alpha1.ResourcesPluralCluster, "", cluster.Name) if err != nil { - klog.Error(err) + klog.Warning(err) if errors.IsNotFound(err) { continue } @@ -527,89 +533,69 @@ func (t *tenantOperator) ListWorkspaceClusters(workspaceName string) (*api.ListR return &api.ListResult{Items: []interface{}{}, TotalItems: 0}, nil } -func (t *tenantOperator) ListClusters(user user.Info) (*api.ListResult, error) { +func (t *tenantOperator) ListClusters(user user.Info, queryParam *query.Query) (*api.ListResult, error) { listClustersInGlobalScope := authorizer.AttributesRecord{ User: user, Verb: "list", + APIGroup: "cluster.kubesphere.io", Resource: "clusters", ResourceScope: request.GlobalScope, ResourceRequest: true, } allowedListClustersInGlobalScope, _, err := t.authorizer.Authorize(listClustersInGlobalScope) - if err != nil { - klog.Error(err) - return nil, err + return nil, fmt.Errorf("failed to authorize: %s", err) } - listWorkspacesInGlobalScope := authorizer.AttributesRecord{ - User: user, - Verb: "list", - Resource: "workspaces", - ResourceScope: request.GlobalScope, - ResourceRequest: true, + if allowedListClustersInGlobalScope == authorizer.DecisionAllow { + return t.resourceGetter.List(clusterv1alpha1.ResourcesPluralCluster, "", queryParam) } - allowedListWorkspacesInGlobalScope, _, err := t.authorizer.Authorize(listWorkspacesInGlobalScope) - + userDetail, err := t.im.DescribeUser(user.GetName()) if err != nil { - klog.Error(err) - return nil, err + return nil, fmt.Errorf("failed to describe user: %s", err) } - if allowedListClustersInGlobalScope == authorizer.DecisionAllow || - allowedListWorkspacesInGlobalScope == authorizer.DecisionAllow { - result, err := t.resourceGetter.List(clusterv1alpha1.ResourcesPluralCluster, "", query.New()) + grantedClustersAnnotation := userDetail.Annotations[iamv1alpha2.GrantedClustersAnnotation] + var grantedClusters sets.String + if len(grantedClustersAnnotation) > 0 { + grantedClusters = sets.NewString(strings.Split(grantedClustersAnnotation, ",")...) + } else { + grantedClusters = sets.NewString() + } + var clusters []*clusterv1alpha1.Cluster + for _, grantedCluster := range grantedClusters.List() { + obj, err := t.resourceGetter.Get(clusterv1alpha1.ResourcesPluralCluster, "", grantedCluster) if err != nil { - klog.Error(err) - return nil, err - } - return result, nil - } - - workspaceRoleBindings, err := t.am.ListWorkspaceRoleBindings(user.GetName(), user.GetGroups(), "") - - if err != nil { - klog.Error(err) - return nil, err - } - - clusters := map[string]*clusterv1alpha1.Cluster{} - - for _, roleBinding := range workspaceRoleBindings { - workspaceName := roleBinding.Labels[tenantv1alpha1.WorkspaceLabel] - workspace, err := t.DescribeWorkspaceTemplate(workspaceName) - if err != nil { - klog.Error(err) - return nil, err - } - - for _, grantedCluster := range workspace.Spec.Placement.Clusters { - // skip if cluster exist - if clusters[grantedCluster.Name] != nil { + if errors.IsNotFound(err) { continue } - obj, err := t.resourceGetter.Get(clusterv1alpha1.ResourcesPluralCluster, "", grantedCluster.Name) - if err != nil { - klog.Error(err) - if errors.IsNotFound(err) { - continue - } - return nil, err - } - cluster := obj.(*clusterv1alpha1.Cluster) - clusters[cluster.Name] = cluster + return nil, fmt.Errorf("failed to fetch cluster: %s", err) } + cluster := obj.(*clusterv1alpha1.Cluster) + clusters = append(clusters, cluster) } - items := make([]interface{}, 0) + items := make([]runtime.Object, 0) for _, cluster := range clusters { items = append(items, cluster) } - return &api.ListResult{Items: items, TotalItems: len(items)}, nil + // apply additional labelSelector + if queryParam.LabelSelector != "" { + queryParam.Filters[query.FieldLabel] = query.Value(queryParam.LabelSelector) + } + + // use default pagination search logic + result := resources.DefaultList(items, queryParam, func(left runtime.Object, right runtime.Object, field query.Field) bool { + return resources.DefaultObjectMetaCompare(left.(*clusterv1alpha1.Cluster).ObjectMeta, right.(*clusterv1alpha1.Cluster).ObjectMeta, field) + }, func(workspace runtime.Object, filter query.Filter) bool { + return resources.DefaultObjectMetaFilter(workspace.(*clusterv1alpha1.Cluster).ObjectMeta, filter) + }) + + return result, nil } func (t *tenantOperator) DeleteWorkspaceTemplate(workspace string, opts metav1.DeleteOptions) error { diff --git a/pkg/models/tenant/tenent_test.go b/pkg/models/tenant/tenent_test.go index 4fc0164cc..a81afc8b4 100644 --- a/pkg/models/tenant/tenent_test.go +++ b/pkg/models/tenant/tenent_test.go @@ -544,5 +544,5 @@ func prepare() Interface { amOperator := am.NewOperator(ksClient, k8sClient, fakeInformerFactory, nil) authorizer := rbac.NewRBACAuthorizer(amOperator) - return New(fakeInformerFactory, k8sClient, ksClient, nil, nil, nil, amOperator, authorizer, nil, nil, nil) + return New(fakeInformerFactory, k8sClient, ksClient, nil, nil, nil, amOperator, nil, authorizer, nil, nil, nil) } diff --git a/staging/src/kubesphere.io/api/iam/v1alpha2/types.go b/staging/src/kubesphere.io/api/iam/v1alpha2/types.go index 994dd3b40..835ac9ce6 100644 --- a/staging/src/kubesphere.io/api/iam/v1alpha2/types.go +++ b/staging/src/kubesphere.io/api/iam/v1alpha2/types.go @@ -58,6 +58,7 @@ const ( GlobalRoleAnnotation = "iam.kubesphere.io/globalrole" WorkspaceRoleAnnotation = "iam.kubesphere.io/workspacerole" ClusterRoleAnnotation = "iam.kubesphere.io/clusterrole" + GrantedClustersAnnotation = "iam.kubesphere.io/granted-clusters" UninitializedAnnotation = "iam.kubesphere.io/uninitialized" LastPasswordChangeTimeAnnotation = "iam.kubesphere.io/last-password-change-time" RoleAnnotation = "iam.kubesphere.io/role"