From a3f2166438618c72461e4621a56cb4850a5b4705 Mon Sep 17 00:00:00 2001 From: ldpliu Date: Tue, 20 Apr 2021 12:45:48 +0800 Subject: [PATCH] acstract cache --- pkg/{proxyserver => }/cache/managedcluster.go | 2 + .../cache/managedcluster_test.go | 5 +- .../cache/managedclusterset.go | 28 +- .../cache/managedclusterset_test.go | 9 +- .../clusterview.go => cache/subjectrecord.go} | 103 +++---- .../subjectrecord_test.go} | 7 +- pkg/{proxyserver => }/cache/watcher.go | 0 pkg/{proxyserver => }/cache/watcher_test.go | 0 pkg/proxyserver/api/register.go | 7 +- pkg/proxyserver/cache/rbac/allow.go | 58 ---- pkg/proxyserver/cache/rbac/allow_test.go | 72 ----- .../rest/managedcluster/managedcluster.go | 3 +- .../managedclusterset/managedclusterset.go | 3 +- pkg/utils/role.go | 108 +++++++ pkg/utils/role_test.go | 278 ++++++++++++++++++ 15 files changed, 479 insertions(+), 204 deletions(-) rename pkg/{proxyserver => }/cache/managedcluster.go (96%) rename pkg/{proxyserver => }/cache/managedcluster_test.go (99%) rename pkg/{proxyserver => }/cache/managedclusterset.go (87%) rename pkg/{proxyserver => }/cache/managedclusterset_test.go (97%) rename pkg/{proxyserver/cache/clusterview.go => cache/subjectrecord.go} (82%) rename pkg/{proxyserver/cache/clusterview_test.go => cache/subjectrecord_test.go} (96%) rename pkg/{proxyserver => }/cache/watcher.go (100%) rename pkg/{proxyserver => }/cache/watcher_test.go (100%) delete mode 100644 pkg/proxyserver/cache/rbac/allow.go delete mode 100644 pkg/proxyserver/cache/rbac/allow_test.go diff --git a/pkg/proxyserver/cache/managedcluster.go b/pkg/cache/managedcluster.go similarity index 96% rename from pkg/proxyserver/cache/managedcluster.go rename to pkg/cache/managedcluster.go index be6d6877c..a79cce23f 100644 --- a/pkg/proxyserver/cache/managedcluster.go +++ b/pkg/cache/managedcluster.go @@ -6,6 +6,7 @@ import ( clusterinformerv1 "github.com/open-cluster-management/api/client/cluster/informers/externalversions/cluster/v1" clusterv1lister "github.com/open-cluster-management/api/client/cluster/listers/cluster/v1" clusterv1 "github.com/open-cluster-management/api/cluster/v1" + "github.com/open-cluster-management/multicloud-operators-foundation/pkg/utils" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -38,6 +39,7 @@ func NewClusterCache(clusterInformer clusterinformerv1.ManagedClusterInformer, "cluster.open-cluster-management.io", "managedclusters", clusterInformer.Informer(), clusterCache.ListResources, + utils.GetViewResourceFromClusterRole, ) clusterCache.cache = authCache diff --git a/pkg/proxyserver/cache/managedcluster_test.go b/pkg/cache/managedcluster_test.go similarity index 99% rename from pkg/proxyserver/cache/managedcluster_test.go rename to pkg/cache/managedcluster_test.go index 561e7e74a..46a13c131 100644 --- a/pkg/proxyserver/cache/managedcluster_test.go +++ b/pkg/cache/managedcluster_test.go @@ -1,6 +1,9 @@ package cache import ( + "testing" + "time" + clusterfake "github.com/open-cluster-management/api/client/cluster/clientset/versioned/fake" clusterv1informers "github.com/open-cluster-management/api/client/cluster/informers/externalversions" clusterv1 "github.com/open-cluster-management/api/cluster/v1" @@ -12,8 +15,6 @@ import ( "k8s.io/apiserver/pkg/authentication/user" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" - "testing" - "time" ) var ( diff --git a/pkg/proxyserver/cache/managedclusterset.go b/pkg/cache/managedclusterset.go similarity index 87% rename from pkg/proxyserver/cache/managedclusterset.go rename to pkg/cache/managedclusterset.go index f62535ed5..25f1c1e3f 100644 --- a/pkg/proxyserver/cache/managedclusterset.go +++ b/pkg/cache/managedclusterset.go @@ -6,6 +6,8 @@ import ( clusterinformerv1alpha1 "github.com/open-cluster-management/api/client/cluster/informers/externalversions/cluster/v1alpha1" clusterv1alpha1lister "github.com/open-cluster-management/api/client/cluster/listers/cluster/v1alpha1" clusterv1alpha1 "github.com/open-cluster-management/api/cluster/v1alpha1" + + v1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -23,27 +25,27 @@ type ClusterSetLister interface { } type ClusterSetCache struct { - cache *AuthCache + Cache *AuthCache clusterSetLister clusterv1alpha1lister.ManagedClusterSetLister } func NewClusterSetCache(clusterSetInformer clusterinformerv1alpha1.ManagedClusterSetInformer, clusterRoleInformer rbacv1informers.ClusterRoleInformer, clusterRolebindingInformer rbacv1informers.ClusterRoleBindingInformer, + getResourceNamesFromClusterRole func(*v1.ClusterRole, string, string) (sets.String, bool), ) *ClusterSetCache { clusterSetCache := &ClusterSetCache{ clusterSetLister: clusterSetInformer.Lister(), } - authCache := NewAuthCache(clusterRoleInformer, clusterRolebindingInformer, + cache := NewAuthCache(clusterRoleInformer, clusterRolebindingInformer, "cluster.open-cluster-management.io", "managedclustersets", clusterSetInformer.Informer(), clusterSetCache.ListResources, + getResourceNamesFromClusterRole, ) - clusterSetCache.cache = authCache - + clusterSetCache.Cache = cache return clusterSetCache } - func (c *ClusterSetCache) ListResources() (sets.String, error) { allClusterSets := sets.String{} clusterSets, err := c.clusterSetLister.List(labels.Everything()) @@ -57,8 +59,13 @@ func (c *ClusterSetCache) ListResources() (sets.String, error) { return allClusterSets, nil } +// Run begins watching and synchronizing the cache +func (c *ClusterSetCache) Run(period time.Duration) { + go utilwait.Forever(func() { c.Cache.synchronize() }, period) +} + func (c *ClusterSetCache) List(userInfo user.Info, selector labels.Selector) (*clusterv1alpha1.ManagedClusterSetList, error) { - names := c.cache.listNames(userInfo) + names := c.Cache.listNames(userInfo) clusterSetList := &clusterv1alpha1.ManagedClusterSetList{} for key := range names { @@ -96,14 +103,9 @@ func (c *ClusterSetCache) ConvertResource(name string) runtime.Object { } func (c *ClusterSetCache) RemoveWatcher(w CacheWatcher) { - c.cache.RemoveWatcher(w) + c.Cache.RemoveWatcher(w) } func (c *ClusterSetCache) AddWatcher(w CacheWatcher) { - c.cache.AddWatcher(w) -} - -// Run begins watching and synchronizing the cache -func (c *ClusterSetCache) Run(period time.Duration) { - go utilwait.Forever(func() { c.cache.synchronize() }, period) + c.Cache.AddWatcher(w) } diff --git a/pkg/proxyserver/cache/managedclusterset_test.go b/pkg/cache/managedclusterset_test.go similarity index 97% rename from pkg/proxyserver/cache/managedclusterset_test.go rename to pkg/cache/managedclusterset_test.go index 42ec82c68..ab32fc7bb 100644 --- a/pkg/proxyserver/cache/managedclusterset_test.go +++ b/pkg/cache/managedclusterset_test.go @@ -1,9 +1,13 @@ package cache import ( + "testing" + "time" + clusterfake "github.com/open-cluster-management/api/client/cluster/clientset/versioned/fake" clusterinformers "github.com/open-cluster-management/api/client/cluster/informers/externalversions" clusterv1alpha1 "github.com/open-cluster-management/api/cluster/v1alpha1" + "github.com/open-cluster-management/multicloud-operators-foundation/pkg/utils" "github.com/stretchr/testify/assert" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -12,8 +16,6 @@ import ( "k8s.io/apiserver/pkg/authentication/user" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" - "testing" - "time" ) var ( @@ -177,6 +179,7 @@ func fakeNewClusterSetCache(stopCh chan struct{}) *ClusterSetCache { clusterInformers.Cluster().V1alpha1().ManagedClusterSets(), informers.Rbac().V1().ClusterRoles(), informers.Rbac().V1().ClusterRoleBindings(), + utils.GetViewResourceFromClusterRole, ) } @@ -228,7 +231,7 @@ func TestClusterSetCacheList(t *testing.T) { expectedClusterSets: sets.String{}, }, } - clusterSetCache.cache.synchronize() + clusterSetCache.Cache.synchronize() for _, test := range tests { t.Run(test.name, func(t *testing.T) { clusterSetList, err := clusterSetCache.List(test.user, labels.Everything()) diff --git a/pkg/proxyserver/cache/clusterview.go b/pkg/cache/subjectrecord.go similarity index 82% rename from pkg/proxyserver/cache/clusterview.go rename to pkg/cache/subjectrecord.go index 5d27b214d..1b1e5ddc4 100644 --- a/pkg/proxyserver/cache/clusterview.go +++ b/pkg/cache/subjectrecord.go @@ -2,12 +2,12 @@ package cache import ( "fmt" - "github.com/openshift/library-go/pkg/authorization/authorizationutil" - "k8s.io/klog/v2" "strings" "sync" - "github.com/open-cluster-management/multicloud-operators-foundation/pkg/proxyserver/cache/rbac" + "github.com/openshift/library-go/pkg/authorization/authorizationutil" + "k8s.io/klog/v2" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/labels" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -18,19 +18,19 @@ import ( "k8s.io/client-go/tools/cache" ) -// subjectRecord is a cache record for the set of resources a subject can access -type subjectRecord struct { - subject string - names sets.String +// SubjectRecord is a cache record for the set of resources a subject can access +type SubjectRecord struct { + Subject string + Names sets.String } -// subjectRecordKeyFn is a key func for subjectRecord objects +// subjectRecordKeyFn is a key func for SubjectRecord objects func subjectRecordKeyFn(obj interface{}) (string, error) { - subjectRecord, ok := obj.(*subjectRecord) + SubjectRecord, ok := obj.(*SubjectRecord) if !ok { - return "", fmt.Errorf("expected subjectRecord") + return "", fmt.Errorf("expected SubjectRecord") } - return subjectRecord.subject, nil + return SubjectRecord.Subject, nil } // LastSyncResourceVersioner is any object that can divulge a LastSyncResourceVersion @@ -109,7 +109,8 @@ type AuthCache struct { userSubjectRecordStore cache.Store groupSubjectRecordStore cache.Store - syncResources func() (sets.String, error) + syncResources func() (sets.String, error) + getResourceNamesFromClusterRole func(*rbacv1.ClusterRole, string, string) (sets.String, bool) group string resource string @@ -123,6 +124,7 @@ func NewAuthCache(clusterRoleInformer rbacv1informers.ClusterRoleInformer, group, resource string, lastSyncResourceVersioner LastSyncResourceVersioner, syncResourcesFunc func() (sets.String, error), + getResourceNamesFromClusterRole func(*rbacv1.ClusterRole, string, string) (sets.String, bool), ) *AuthCache { scrLister := syncedClusterRoleLister{ clusterRoleInformer.Lister(), @@ -144,8 +146,9 @@ func NewAuthCache(clusterRoleInformer rbacv1informers.ClusterRoleInformer, userSubjectRecordStore: cache.NewStore(subjectRecordKeyFn), groupSubjectRecordStore: cache.NewStore(subjectRecordKeyFn), + skip: &statelessSkipSynchronizer{}, - skip: &statelessSkipSynchronizer{}, + getResourceNamesFromClusterRole: getResourceNamesFromClusterRole, watchers: []CacheWatcher{}, } @@ -198,7 +201,7 @@ func (ac *AuthCache) synchronizeClusterRoleBindings(userSubjectRecordStore cache if err != nil { continue } - resources, all := getResourceNamesFromClusterRole(clusterRole, ac.group, ac.resource) + resources, all := ac.getResourceNamesFromClusterRole(clusterRole, ac.group, ac.resource) if all { resources = ac.knownResources } @@ -256,6 +259,32 @@ func (ac *AuthCache) synchronizeClusterRoleBindings(userSubjectRecordStore cache ac.knownGroups = newAllGroups } +func (ac *AuthCache) GetUserSubjectRecord() []*SubjectRecord { + if ac == nil || ac.userSubjectRecordStore == nil { + return []*SubjectRecord{} + } + subjectRecordStore := ac.userSubjectRecordStore.List() + var returnSubjectRecord []*SubjectRecord + for _, subjectRecord := range subjectRecordStore { + s := subjectRecord.(*SubjectRecord) + returnSubjectRecord = append(returnSubjectRecord, s) + } + return returnSubjectRecord +} + +func (ac *AuthCache) GetGroupSubjectRecord() []*SubjectRecord { + if ac == nil || ac.userSubjectRecordStore == nil { + return []*SubjectRecord{} + } + subjectRecordStore := ac.groupSubjectRecordStore.List() + var returnSubjectRecord []*SubjectRecord + for _, subjectRecord := range subjectRecordStore { + s := subjectRecord.(*SubjectRecord) + returnSubjectRecord = append(returnSubjectRecord, s) + } + return returnSubjectRecord +} + func (ac *AuthCache) listNames(userInfo user.Info) sets.String { keys := sets.String{} user := userInfo.GetName() @@ -263,15 +292,15 @@ func (ac *AuthCache) listNames(userInfo user.Info) sets.String { obj, exists, _ := ac.userSubjectRecordStore.GetByKey(user) if exists { - subjectRecord := obj.(*subjectRecord) - keys.Insert(subjectRecord.names.List()...) + SubjectRecord := obj.(*SubjectRecord) + keys.Insert(SubjectRecord.Names.List()...) } for _, group := range groups { obj, exists, _ := ac.groupSubjectRecordStore.GetByKey(group) if exists { - subjectRecord := obj.(*subjectRecord) - keys.Insert(subjectRecord.names.List()...) + SubjectRecord := obj.(*SubjectRecord) + keys.Insert(SubjectRecord.Names.List()...) } } @@ -311,13 +340,13 @@ func (ac *AuthCache) notifyWatchers(names, users, groups sets.String) { } func updateResourcesToSubject(subjectRecordStore cache.Store, subject string, names sets.String) { - var item *subjectRecord + var item *SubjectRecord obj, exists, _ := subjectRecordStore.GetByKey(subject) if exists { - item = obj.(*subjectRecord) - item.names = names + item = obj.(*SubjectRecord) + item.Names = names } else { - item = &subjectRecord{subject: subject, names: names} + item = &SubjectRecord{Subject: subject, Names: names} subjectRecordStore.Add(item) } return @@ -326,35 +355,9 @@ func updateResourcesToSubject(subjectRecordStore cache.Store, subject string, na func deleteSubject(subjectRecordStore cache.Store, subject string) { obj, exists, _ := subjectRecordStore.GetByKey(subject) if exists { - subjectRecord := obj.(*subjectRecord) - subjectRecordStore.Delete(subjectRecord) + SubjectRecord := obj.(*SubjectRecord) + subjectRecordStore.Delete(SubjectRecord) } return } - -func getResourceNamesFromClusterRole(clusterRole *rbacv1.ClusterRole, group, resource string) (sets.String, bool) { - names := sets.NewString() - all := false - for _, rule := range clusterRole.Rules { - if !rbac.APIGroupMatches(&rule, group) { - continue - } - - if !rbac.VerbMatches(&rule, "get") && !rbac.VerbMatches(&rule, "list") && !rbac.VerbMatches(&rule, "*") { - continue - } - - if len(rule.ResourceNames) == 0 { - all = true - return names, all - } - - if !rbac.ResourceMatches(&rule, resource, "") { - continue - } - - names.Insert(rule.ResourceNames...) - } - return names, all -} diff --git a/pkg/proxyserver/cache/clusterview_test.go b/pkg/cache/subjectrecord_test.go similarity index 96% rename from pkg/proxyserver/cache/clusterview_test.go rename to pkg/cache/subjectrecord_test.go index d43459ef6..cdc025cc3 100644 --- a/pkg/proxyserver/cache/clusterview_test.go +++ b/pkg/cache/subjectrecord_test.go @@ -1,15 +1,17 @@ package cache import ( + "testing" + "time" + clusterfake "github.com/open-cluster-management/api/client/cluster/clientset/versioned/fake" clusterv1informers "github.com/open-cluster-management/api/client/cluster/informers/externalversions" + "github.com/open-cluster-management/multicloud-operators-foundation/pkg/utils" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" - "testing" - "time" ) func validateSet(set, expectedSet sets.String) bool { @@ -45,6 +47,7 @@ func TestSyncManagedClusterCache(t *testing.T) { "cluster.open-cluster-management.io", "managedclusters", clusterInformers.Cluster().V1().ManagedClusters().Informer(), clusterCache.ListResources, + utils.GetViewResourceFromClusterRole, ) autheCache.synchronize() diff --git a/pkg/proxyserver/cache/watcher.go b/pkg/cache/watcher.go similarity index 100% rename from pkg/proxyserver/cache/watcher.go rename to pkg/cache/watcher.go diff --git a/pkg/proxyserver/cache/watcher_test.go b/pkg/cache/watcher_test.go similarity index 100% rename from pkg/proxyserver/cache/watcher_test.go rename to pkg/cache/watcher_test.go diff --git a/pkg/proxyserver/api/register.go b/pkg/proxyserver/api/register.go index 80d18e493..eddc447be 100644 --- a/pkg/proxyserver/api/register.go +++ b/pkg/proxyserver/api/register.go @@ -2,15 +2,17 @@ package api import ( "context" + "time" + clusterclient "github.com/open-cluster-management/api/client/cluster/clientset/versioned" clusterinformers "github.com/open-cluster-management/api/client/cluster/informers/externalversions" - "github.com/open-cluster-management/multicloud-operators-foundation/pkg/proxyserver/cache" + "github.com/open-cluster-management/multicloud-operators-foundation/pkg/cache" "github.com/open-cluster-management/multicloud-operators-foundation/pkg/proxyserver/rest/log" "github.com/open-cluster-management/multicloud-operators-foundation/pkg/proxyserver/rest/managedcluster" "github.com/open-cluster-management/multicloud-operators-foundation/pkg/proxyserver/rest/managedclusterset" "github.com/open-cluster-management/multicloud-operators-foundation/pkg/proxyserver/rest/proxy" + "github.com/open-cluster-management/multicloud-operators-foundation/pkg/utils" "k8s.io/client-go/informers" - "time" apisclusterview "github.com/open-cluster-management/multicloud-operators-foundation/pkg/proxyserver/apis/clusterview" clusterviewv1 "github.com/open-cluster-management/multicloud-operators-foundation/pkg/proxyserver/apis/clusterview/v1" @@ -82,6 +84,7 @@ func installClusterViewGroup(server *genericapiserver.GenericAPIServer, clusterInformer.Cluster().V1alpha1().ManagedClusterSets(), informerFactory.Rbac().V1().ClusterRoles(), informerFactory.Rbac().V1().ClusterRoleBindings(), + utils.GetViewResourceFromClusterRole, ) v1alpha1storage := map[string]rest.Storage{ diff --git a/pkg/proxyserver/cache/rbac/allow.go b/pkg/proxyserver/cache/rbac/allow.go deleted file mode 100644 index f96f3cd0e..000000000 --- a/pkg/proxyserver/cache/rbac/allow.go +++ /dev/null @@ -1,58 +0,0 @@ -package rbac - -import ( - "strings" - - rbacv1 "k8s.io/api/rbac/v1" -) - -func APIGroupMatches(rule *rbacv1.PolicyRule, requestedGroup string) bool { - for _, ruleGroup := range rule.APIGroups { - if ruleGroup == rbacv1.APIGroupAll { - return true - } - if ruleGroup == requestedGroup { - return true - } - } - - return false -} - -func ResourceMatches(rule *rbacv1.PolicyRule, combinedRequestedResource, requestedSubresource string) bool { - for _, ruleResource := range rule.Resources { - // if everything is allowed, we match - if ruleResource == rbacv1.ResourceAll { - return true - } - // if we have an exact match, we match - if ruleResource == combinedRequestedResource { - return true - } - - // We can also match a */subresource. - // if there isn't a subresource, then continue - if len(requestedSubresource) == 0 { - continue - } - // if the rule isn't in the format */subresource, then we don't match, continue - if len(ruleResource) == len(requestedSubresource)+2 && - strings.HasPrefix(ruleResource, "*/") && - strings.HasSuffix(ruleResource, requestedSubresource) { - return true - - } - } - - return false -} - -func VerbMatches(rule *rbacv1.PolicyRule, requestedVerb string) bool { - for _, verb := range rule.Verbs { - if verb == requestedVerb { - return true - } - } - - return false -} diff --git a/pkg/proxyserver/cache/rbac/allow_test.go b/pkg/proxyserver/cache/rbac/allow_test.go deleted file mode 100644 index b96aff088..000000000 --- a/pkg/proxyserver/cache/rbac/allow_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package rbac - -import ( - "github.com/stretchr/testify/assert" - rbacv1 "k8s.io/api/rbac/v1" - "testing" -) - -func TestAPIGroupMatches(t *testing.T) { - tests := []struct { - name string - rule *rbacv1.PolicyRule - group string - expectedRst bool - }{ - { - name: "has group", - rule: &rbacv1.PolicyRule{ - APIGroups: []string{"cluster.open-cluster-management.io"}, - }, - group: "cluster.open-cluster-management.io", - expectedRst: true, - }, - { - name: "has all groups", - rule: &rbacv1.PolicyRule{ - APIGroups: []string{"*"}, - }, - group: "", - expectedRst: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert.Equal(t, APIGroupMatches(test.rule, test.group), test.expectedRst) - }) - } -} - -func TestResourceMatches(t *testing.T) { - tests := []struct { - name string - rule *rbacv1.PolicyRule - resource string - subResource string - expectedRst bool - }{ - { - name: "has resource", - rule: &rbacv1.PolicyRule{ - Resources: []string{"managedclusters"}, - }, - resource: "managedclusters", - subResource: "", - expectedRst: true, - }, - { - name: "has resource and subresource", - rule: &rbacv1.PolicyRule{ - Resources: []string{"managedclusters", "*/status"}, - }, - resource: "managedclusters", - subResource: "status", - expectedRst: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert.Equal(t, ResourceMatches(test.rule, test.resource, test.subResource), test.expectedRst) - }) - } -} diff --git a/pkg/proxyserver/rest/managedcluster/managedcluster.go b/pkg/proxyserver/rest/managedcluster/managedcluster.go index 6d5cdd12f..efe2409c6 100644 --- a/pkg/proxyserver/rest/managedcluster/managedcluster.go +++ b/pkg/proxyserver/rest/managedcluster/managedcluster.go @@ -3,10 +3,11 @@ package managedcluster import ( "context" "fmt" + clientset "github.com/open-cluster-management/api/client/cluster/clientset/versioned" clusterv1lister "github.com/open-cluster-management/api/client/cluster/listers/cluster/v1" clusterv1 "github.com/open-cluster-management/api/cluster/v1" - "github.com/open-cluster-management/multicloud-operators-foundation/pkg/proxyserver/cache" + "github.com/open-cluster-management/multicloud-operators-foundation/pkg/cache" "github.com/open-cluster-management/multicloud-operators-foundation/pkg/proxyserver/helpers" "k8s.io/apimachinery/pkg/api/errors" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" diff --git a/pkg/proxyserver/rest/managedclusterset/managedclusterset.go b/pkg/proxyserver/rest/managedclusterset/managedclusterset.go index 7dd2478ec..e1e45b72e 100644 --- a/pkg/proxyserver/rest/managedclusterset/managedclusterset.go +++ b/pkg/proxyserver/rest/managedclusterset/managedclusterset.go @@ -3,11 +3,12 @@ package managedclusterset import ( "context" "fmt" + clientset "github.com/open-cluster-management/api/client/cluster/clientset/versioned" clusterv1alpha1lister "github.com/open-cluster-management/api/client/cluster/listers/cluster/v1alpha1" clusterv1 "github.com/open-cluster-management/api/cluster/v1" clusterv1alpha1 "github.com/open-cluster-management/api/cluster/v1alpha1" - "github.com/open-cluster-management/multicloud-operators-foundation/pkg/proxyserver/cache" + "github.com/open-cluster-management/multicloud-operators-foundation/pkg/cache" "github.com/open-cluster-management/multicloud-operators-foundation/pkg/proxyserver/helpers" "k8s.io/apimachinery/pkg/api/errors" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" diff --git a/pkg/utils/role.go b/pkg/utils/role.go index 53c146b39..98848c074 100644 --- a/pkg/utils/role.go +++ b/pkg/utils/role.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "reflect" + "strings" clusterv1alpha1 "github.com/open-cluster-management/api/cluster/v1alpha1" rbacv1 "k8s.io/api/rbac/v1" @@ -155,3 +156,110 @@ func ApplyClusterRole(kubeClient kubernetes.Interface, clusterRoleName string, r func BuildClusterRoleName(objName, rule string) string { return fmt.Sprintf("open-cluster-management:%s:%s", rule, objName) } + +func APIGroupMatches(rule *rbacv1.PolicyRule, requestedGroup string) bool { + for _, ruleGroup := range rule.APIGroups { + if ruleGroup == rbacv1.APIGroupAll { + return true + } + if ruleGroup == requestedGroup { + return true + } + } + + return false +} + +func ResourceMatches(rule *rbacv1.PolicyRule, combinedRequestedResource, requestedSubresource string) bool { + for _, ruleResource := range rule.Resources { + // if everything is allowed, we match + if ruleResource == rbacv1.ResourceAll { + return true + } + // if we have an exact match, we match + if ruleResource == combinedRequestedResource { + return true + } + + // We can also match a */subresource. + // if there isn't a subresource, then continue + if len(requestedSubresource) == 0 { + continue + } + // if the rule isn't in the format */subresource, then we don't match, continue + if len(ruleResource) == len(requestedSubresource)+2 && + strings.HasPrefix(ruleResource, "*/") && + strings.HasSuffix(ruleResource, requestedSubresource) { + return true + + } + } + + return false +} + +func VerbMatches(rule *rbacv1.PolicyRule, requestedVerb string) bool { + for _, verb := range rule.Verbs { + if verb == requestedVerb { + return true + } + } + + return false +} + +// GetViewResourceFromClusterRole match the "get" permission of resource, +// which means this role has view permission to this resource +func GetViewResourceFromClusterRole(clusterRole *rbacv1.ClusterRole, group, resource string) (sets.String, bool) { + names := sets.NewString() + all := false + for _, rule := range clusterRole.Rules { + if !APIGroupMatches(&rule, group) { + continue + } + + if !VerbMatches(&rule, "get") && !VerbMatches(&rule, "list") && !VerbMatches(&rule, "*") { + continue + } + + if len(rule.ResourceNames) == 0 { + all = true + return names, all + } + + if !ResourceMatches(&rule, resource, "") { + continue + } + + names.Insert(rule.ResourceNames...) + } + return names, all +} + +// GetViewResourceFromClusterRole match the "update" permission of resource, +// which means this role has admin permission to this resource +func GetAdminResourceFromClusterRole(clusterRole *rbacv1.ClusterRole, group, resource string) (sets.String, bool) { + names := sets.NewString() + all := false + for _, rule := range clusterRole.Rules { + if !APIGroupMatches(&rule, group) { + continue + } + + if !(VerbMatches(&rule, "update") && (VerbMatches(&rule, "get") || VerbMatches(&rule, "list"))) && !VerbMatches(&rule, "*") { + continue + } + + if len(rule.ResourceNames) == 0 { + all = true + return names, all + } + + if !ResourceMatches(&rule, resource, "") { + continue + } + + names.Insert(rule.ResourceNames...) + } + return names, all +} diff --git a/pkg/utils/role_test.go b/pkg/utils/role_test.go index 8aee69b28..66feb23f2 100644 --- a/pkg/utils/role_test.go +++ b/pkg/utils/role_test.go @@ -6,6 +6,7 @@ import ( "testing" clusterv1alpha1 "github.com/open-cluster-management/api/cluster/v1alpha1" + "github.com/stretchr/testify/assert" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -168,3 +169,280 @@ func TestBuildClusterRoleName(t *testing.T) { t.Errorf("Failed to generate clusterroleName: %v", roleName) } } + +func TestGetAdminResourceFromClusterRole(t *testing.T) { + tests := []struct { + name string + clusterrole *rbacv1.ClusterRole + group string + resource string + expectedRst sets.String + expectAll bool + }{ + { + name: "get one cluster", + clusterrole: &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: "role1", ResourceVersion: "1"}, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"list"}, + APIGroups: []string{"clusterview.open-cluster-management.io"}, + Resources: []string{"managedclusters"}, + }, + { + Verbs: []string{"update", "get"}, + APIGroups: []string{"cluster.open-cluster-management.io"}, + Resources: []string{"managedclusters"}, + ResourceNames: []string{"cluster1"}, + }, + }, + }, + group: "cluster.open-cluster-management.io", + resource: "managedclusters", + expectedRst: sets.NewString("cluster1"), + expectAll: false, + }, + { + name: "get all clusters 1", + clusterrole: &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: "role1", ResourceVersion: "1"}, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"list"}, + APIGroups: []string{"clusterview.open-cluster-management.io"}, + Resources: []string{"managedclusters"}, + }, + { + Verbs: []string{"update", "get"}, + APIGroups: []string{"cluster.open-cluster-management.io"}, + Resources: []string{"managedclusters"}, + ResourceNames: []string{}, + }, + }, + }, + group: "cluster.open-cluster-management.io", + resource: "managedclusters", + expectedRst: sets.NewString("cluster1"), + expectAll: true, + }, + { + name: "get all clusters 2", + clusterrole: &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: "role1", ResourceVersion: "1"}, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"list"}, + APIGroups: []string{"clusterview.open-cluster-management.io"}, + Resources: []string{"managedclusters"}, + }, + { + Verbs: []string{"list", "create", "update"}, + APIGroups: []string{"cluster.open-cluster-management.io"}, + Resources: []string{"managedclusters"}, + ResourceNames: []string{}, + }, + }, + }, + group: "cluster.open-cluster-management.io", + resource: "managedclusters", + expectedRst: sets.NewString("cluster1"), + expectAll: true, + }, + { + name: "get all clusters 3", + clusterrole: &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: "role1", ResourceVersion: "1"}, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + }, + }, + group: "cluster.open-cluster-management.io", + resource: "managedclusters", + expectAll: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + returnset, all := GetAdminResourceFromClusterRole(test.clusterrole, test.group, test.resource) + if test.expectAll { + assert.Equal(t, test.expectAll, all) + return + } + assert.Equal(t, test.expectedRst, returnset) + }) + } +} +func TestGetViewResourceFromClusterRole(t *testing.T) { + tests := []struct { + name string + clusterrole *rbacv1.ClusterRole + group string + resource string + expectedRst sets.String + expectAll bool + }{ + { + name: "get one cluster", + clusterrole: &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: "role1", ResourceVersion: "1"}, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"list"}, + APIGroups: []string{"clusterview.open-cluster-management.io"}, + Resources: []string{"managedclusters"}, + }, + { + Verbs: []string{"get"}, + APIGroups: []string{"cluster.open-cluster-management.io"}, + Resources: []string{"managedclusters"}, + ResourceNames: []string{"cluster1"}, + }, + }, + }, + group: "cluster.open-cluster-management.io", + resource: "managedclusters", + expectedRst: sets.NewString("cluster1"), + expectAll: false, + }, + { + name: "get all clusters 1", + clusterrole: &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: "role1", ResourceVersion: "1"}, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"list"}, + APIGroups: []string{"clusterview.open-cluster-management.io"}, + Resources: []string{"managedclusters"}, + }, + { + Verbs: []string{"get"}, + APIGroups: []string{"cluster.open-cluster-management.io"}, + Resources: []string{"managedclusters"}, + ResourceNames: []string{}, + }, + }, + }, + group: "cluster.open-cluster-management.io", + resource: "managedclusters", + expectedRst: sets.NewString("cluster1"), + expectAll: true, + }, + { + name: "get all clusters 2", + clusterrole: &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: "role1", ResourceVersion: "1"}, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"list"}, + APIGroups: []string{"clusterview.open-cluster-management.io"}, + Resources: []string{"managedclusters"}, + }, + { + Verbs: []string{"get", "create", "update"}, + APIGroups: []string{"cluster.open-cluster-management.io"}, + Resources: []string{"managedclusters"}, + ResourceNames: []string{}, + }, + }, + }, + group: "cluster.open-cluster-management.io", + resource: "managedclusters", + expectedRst: sets.NewString("cluster1"), + expectAll: true, + }, + { + name: "get all clusters 3", + clusterrole: &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: "role1", ResourceVersion: "1"}, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + }, + }, + group: "cluster.open-cluster-management.io", + resource: "managedclusters", + expectAll: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + returnset, all := GetViewResourceFromClusterRole(test.clusterrole, test.group, test.resource) + if test.expectAll { + assert.Equal(t, test.expectAll, all) + return + } + assert.Equal(t, test.expectedRst, returnset) + }) + } +} +func TestResourceMatches(t *testing.T) { + tests := []struct { + name string + rule *rbacv1.PolicyRule + resource string + subResource string + expectedRst bool + }{ + { + name: "has resource", + rule: &rbacv1.PolicyRule{ + Resources: []string{"managedclusters"}, + }, + resource: "managedclusters", + subResource: "", + expectedRst: true, + }, + { + name: "has resource and subresource", + rule: &rbacv1.PolicyRule{ + Resources: []string{"managedclusters", "*/status"}, + }, + resource: "managedclusters", + subResource: "status", + expectedRst: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, ResourceMatches(test.rule, test.resource, test.subResource), test.expectedRst) + }) + } +} + +func TestAPIGroupMatches(t *testing.T) { + tests := []struct { + name string + rule *rbacv1.PolicyRule + group string + expectedRst bool + }{ + { + name: "has group", + rule: &rbacv1.PolicyRule{ + APIGroups: []string{"cluster.open-cluster-management.io"}, + }, + group: "cluster.open-cluster-management.io", + expectedRst: true, + }, + { + name: "has all groups", + rule: &rbacv1.PolicyRule{ + APIGroups: []string{"*"}, + }, + group: "", + expectedRst: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, APIGroupMatches(test.rule, test.group), test.expectedRst) + }) + } +}