diff --git a/api/auth/v1/conversion.go b/api/auth/v1/conversion.go index e4715fd1e..795214dc2 100644 --- a/api/auth/v1/conversion.go +++ b/api/auth/v1/conversion.go @@ -200,7 +200,8 @@ func AddFieldLabelConversionsForUser(scheme *runtime.Scheme) error { return scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.WithKind("User"), func(label, value string) (string, string, error) { switch label { - case "keyword": + case "keyword", + "spec.tenantID": return label, value, nil default: return "", "", fmt.Errorf("field label not supported: %s", label) @@ -215,7 +216,8 @@ func AddFieldLabelConversionsForGroup(scheme *runtime.Scheme) error { return scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.WithKind("Group"), func(label, value string) (string, string, error) { switch label { - case "keyword": + case "keyword", + "spec.tenantID": return label, value, nil default: return "", "", fmt.Errorf("field label not supported: %s", label) diff --git a/pkg/auth/authentication/oidc/identityprovider/ldap/ldap.go b/pkg/auth/authentication/oidc/identityprovider/ldap/ldap.go index 34edd4112..30b1d3d2d 100644 --- a/pkg/auth/authentication/oidc/identityprovider/ldap/ldap.go +++ b/pkg/auth/authentication/oidc/identityprovider/ldap/ldap.go @@ -184,9 +184,11 @@ func (c *identityProvider) ListUsers(ctx context.Context, options *internalversi userList := auth.UserList{} for _, entry := range ldapUsers { user, err := c.userFromEntry(*entry) - if err == nil { - userList.Items = append(userList.Items, *user) + if err != nil { + continue } + userList.Items = append(userList.Items, *user) + } return &userList, nil @@ -234,9 +236,11 @@ func (c *identityProvider) ListGroups(ctx context.Context, options *internalvers groupList := auth.GroupList{} for _, entry := range ldapGroups { grp, err := c.groupFromEntry(*entry) - if err == nil { - groupList.Items = append(groupList.Items, *grp) + if err != nil { + continue } + groupList.Items = append(groupList.Items, *grp) + } return &groupList, nil @@ -431,6 +435,7 @@ func (c *identityProvider) groupsEntry(conn *ldap.Conn, keyword string, limit in log.Infof("performing ldap search %s %s %s", req.BaseDN, scopeString(req.Scope), req.Filter) + resp, err := conn.SearchWithPaging(req, uint32(limit)) if err != nil { return nil, fmt.Errorf("ldap: search with filter %q failed: %v", req.Filter, err) @@ -457,7 +462,7 @@ func (c *identityProvider) groupFromEntry(group ldap.Entry) (authGroup *auth.Gro members := getAttrs(group, c.GroupSearch.GroupAttr) for _, dn := range members { name := parseNameFromDN(dn, c.UserSearch.Username) - if name == "" { + if name != "" { authGroup.Status.Users = append(authGroup.Status.Users, auth.Subject{ ID: dn, Name: name, diff --git a/pkg/auth/authentication/oidc/identityprovider/local/local.go b/pkg/auth/authentication/oidc/identityprovider/local/local.go index 4f779803c..0712d844a 100644 --- a/pkg/auth/authentication/oidc/identityprovider/local/local.go +++ b/pkg/auth/authentication/oidc/identityprovider/local/local.go @@ -295,10 +295,12 @@ func (c *DefaultIdentityProvider) ListGroups(ctx context.Context, options *metai localGroupList = newList } - items := localGroupList[0:min(len(localGroupList), limit)] + if limit > 0 { + localGroupList = localGroupList[0:min(len(localGroupList), limit)] + } groupList := auth.GroupList{} - for _, item := range items { + for _, item := range localGroupList { group := convertToGroup(item) groupList.Items = append(groupList.Items, group) } diff --git a/pkg/auth/controller/group/group_controller.go b/pkg/auth/controller/group/group_controller.go index c4e70a094..e6d2ace9a 100644 --- a/pkg/auth/controller/group/group_controller.go +++ b/pkg/auth/controller/group/group_controller.go @@ -26,11 +26,15 @@ import ( "github.com/casbin/casbin/v2" "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" + + "tkestack.io/tke/api/auth" v1 "tkestack.io/tke/api/auth/v1" clientset "tkestack.io/tke/api/client/clientset/versioned" authv1informer "tkestack.io/tke/api/client/informers/externalversions/auth/v1" @@ -53,6 +57,8 @@ const ( groupDeletionGracePeriod = 5 * time.Second controllerName = "group-controller" + + groupSyncedPeriod = 1 * time.Minute ) // Controller is responsible for performing actions dependent upon a group phase. @@ -146,7 +152,8 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}) { log.Error("Failed to wait for group caches to sync") } - //TODO sync groups(non-tke-local) for idp into casbin + // sync groups(include 3rd party) for identity providers into casbin + go c.pollThirdPartyGroup(stopCh) for i := 0; i < workers; i++ { go wait.Until(c.worker, time.Second, stopCh) @@ -222,11 +229,28 @@ func (c *Controller) syncItem(key string) error { } func (c *Controller) processUpdate(group *v1.LocalGroup, key string) error { - return c.handleSubjects(key, group) + return c.handleSubjects(key, convertToGroup(group)) } -func (c *Controller) handleSubjects(key string, group *v1.LocalGroup) error { - rules := c.enforcer.GetFilteredGroupingPolicy(1, authutil.GroupPrefix(group.Spec.TenantID)) +func convertToGroup(localGroup *v1.LocalGroup) *v1.Group { + return &v1.Group{ + ObjectMeta: metav1.ObjectMeta{ + Name: localGroup.ObjectMeta.Name, + }, + Spec: v1.GroupSpec{ + ID: localGroup.ObjectMeta.Name, + DisplayName: localGroup.Spec.DisplayName, + TenantID: localGroup.Spec.TenantID, + Description: localGroup.Spec.TenantID, + }, + Status: v1.GroupStatus{ + Users: localGroup.Status.Users, + }, + } +} + +func (c *Controller) handleSubjects(key string, group *v1.Group) error { + rules := c.enforcer.GetFilteredGroupingPolicy(1, authutil.GroupKey(group.Spec.TenantID, key)) log.Debugf("Get grouping rules for group: %s, %v", group.Name, rules) var existMembers []string for _, rule := range rules { @@ -265,3 +289,45 @@ func (c *Controller) handleSubjects(key string, group *v1.LocalGroup) error { return utilerrors.NewAggregate(errs) } + +// pollThirdPartyGroup syncs groups with members into storage +func (c *Controller) pollThirdPartyGroup(stopCh <-chan struct{}) { + timerC := time.NewTicker(groupSyncedPeriod) + for { + select { + case <-timerC.C: + c.resyncGroups() + case <-stopCh: + timerC.Stop() + return + } + } +} + +func (c *Controller) resyncGroups() { + defer log.Info("Finished syncing groups with users") + + idpList, err := c.client.AuthV1().IdentityProviders().List(metav1.ListOptions{}) + if err != nil { + log.Error("List all identity providers failed", log.Err(err)) + return + } + + for _, idp := range idpList.Items { + tenantSelector := fields.AndSelectors( + fields.OneTermEqualSelector("spec.tenantID", idp.Name), + fields.OneTermEqualSelector(auth.QueryLimitTag, "0"), + ) + + groups, err := c.client.AuthV1().Groups().List(metav1.ListOptions{FieldSelector: tenantSelector.String()}) + if err != nil { + log.Error("List groups for tenant failed", log.String("tenant", idp.Name), log.Err(err)) + continue + } + log.Debug("syncing groups for tenantID", log.String("tenant", idp.Name), log.Any("groups", groups)) + for _, grp := range groups.Items { + _ = c.handleSubjects(grp.Name, grp.DeepCopy()) + } + } + +} diff --git a/pkg/auth/registry/group/storage/storage.go b/pkg/auth/registry/group/storage/storage.go index 6f3ad3a09..b24ff187e 100644 --- a/pkg/auth/registry/group/storage/storage.go +++ b/pkg/auth/registry/group/storage/storage.go @@ -113,6 +113,13 @@ func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) // List selects resources in the storage which match to the selector. 'options' can be nil. func (r *REST) List(ctx context.Context, options *metainternal.ListOptions) (runtime.Object, error) { _, tenantID := authentication.GetUsernameAndTenantID(ctx) + + if tenantID == "" { + tenantID, _ = options.FieldSelector.RequiresExactMatch("spec.tenantID") + if tenantID == "" { + return nil, apierrors.NewBadRequest("List groups must specify tenantID") + } + } log.Info("store", log.Any("store", identityprovider.IdentityProvidersStore)) idp, ok := identityprovider.IdentityProvidersStore[tenantID] if !ok { diff --git a/pkg/auth/registry/user/storage/storage.go b/pkg/auth/registry/user/storage/storage.go index a3a9936ec..523c58b77 100644 --- a/pkg/auth/registry/user/storage/storage.go +++ b/pkg/auth/registry/user/storage/storage.go @@ -112,6 +112,13 @@ func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) // List selects resources in the storage which match to the selector. 'options' can be nil. func (r *REST) List(ctx context.Context, options *metainternal.ListOptions) (runtime.Object, error) { _, tenantID := authentication.GetUsernameAndTenantID(ctx) + if tenantID == "" { + tenantID, _ = options.FieldSelector.RequiresExactMatch("spec.tenantID") + if tenantID == "" { + return nil, apierrors.NewBadRequest("List groups must specify tenantID") + } + } + idp, ok := identityprovider.IdentityProvidersStore[tenantID] if !ok { log.Error("Tenant has no related identity providers", log.String("tenantID", tenantID)) diff --git a/pkg/auth/util/query.go b/pkg/auth/util/query.go index 0ce711938..2e32087ed 100644 --- a/pkg/auth/util/query.go +++ b/pkg/auth/util/query.go @@ -36,7 +36,7 @@ func ParseQueryKeywordAndLimit(options *metainternal.ListOptions) (string, int) if options.FieldSelector != nil { keyword, _ = options.FieldSelector.RequiresExactMatch(auth.KeywordQueryTag) limitStr, _ := options.FieldSelector.RequiresExactMatch(auth.QueryLimitTag) - if li, err := strconv.Atoi(limitStr); err == nil && li > 0 { + if li, err := strconv.Atoi(limitStr); err == nil && li >= 0 { limit = li } }