mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
RBAC: Add legacy authorization checks to teams (#94524)
* Setup team authorization for teams * Add list filter for teams
This commit is contained in:
parent
315778227b
commit
86fc8da703
@ -1,6 +1,8 @@
|
|||||||
package v0alpha1
|
package v0alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,6 +17,13 @@ type Team struct {
|
|||||||
type TeamSpec struct {
|
type TeamSpec struct {
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email,omitempty"`
|
||||||
|
|
||||||
|
// This is currently used for authorization checks but we don't want to expose it
|
||||||
|
InternalID int64 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Team) AuthID() string {
|
||||||
|
return fmt.Sprintf("%d", t.Spec.InternalID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
@ -54,6 +54,19 @@ func newLegacyAuthorizer(ac accesscontrol.AccessControl, store legacy.LegacyIden
|
|||||||
return []string{fmt.Sprintf("serviceaccounts:id:%d", res.ID)}, nil
|
return []string{fmt.Sprintf("serviceaccounts:id:%d", res.ID)}, nil
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
accesscontrol.ResourceAuthorizerOptions{
|
||||||
|
Resource: iamv0.TeamResourceInfo.GetName(),
|
||||||
|
Attr: "id",
|
||||||
|
Resolver: accesscontrol.ResourceResolverFunc(func(ctx context.Context, ns claims.NamespaceInfo, name string) ([]string, error) {
|
||||||
|
res, err := store.GetTeamInternalID(ctx, ns, legacy.GetTeamInternalIDQuery{
|
||||||
|
UID: name,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []string{fmt.Sprintf("teams:id:%d", res.ID)}, nil
|
||||||
|
}),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return gfauthorizer.NewResourceAuthorizer(client), client
|
return gfauthorizer.NewResourceAuthorizer(client), client
|
||||||
|
@ -22,6 +22,7 @@ type LegacyIdentityStore interface {
|
|||||||
ListServiceAccounts(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountsQuery) (*ListServiceAccountResult, error)
|
ListServiceAccounts(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountsQuery) (*ListServiceAccountResult, error)
|
||||||
ListServiceAccountTokens(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountTokenQuery) (*ListServiceAccountTokenResult, error)
|
ListServiceAccountTokens(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountTokenQuery) (*ListServiceAccountTokenResult, error)
|
||||||
|
|
||||||
|
GetTeamInternalID(ctx context.Context, ns claims.NamespaceInfo, query GetTeamInternalIDQuery) (*GetTeamInternalIDResult, error)
|
||||||
ListTeams(ctx context.Context, ns claims.NamespaceInfo, query ListTeamQuery) (*ListTeamResult, error)
|
ListTeams(ctx context.Context, ns claims.NamespaceInfo, query ListTeamQuery) (*ListTeamResult, error)
|
||||||
ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error)
|
ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error)
|
||||||
ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error)
|
ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error)
|
||||||
|
@ -3,6 +3,7 @@ package legacy
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -14,6 +15,79 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type GetTeamInternalIDQuery struct {
|
||||||
|
OrgID int64
|
||||||
|
UID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetTeamInternalIDResult struct {
|
||||||
|
ID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
var sqlQueryTeamInternalIDTemplate = mustTemplate("team_internal_id.sql")
|
||||||
|
|
||||||
|
func newGetTeamInternalID(sql *legacysql.LegacyDatabaseHelper, q *GetTeamInternalIDQuery) getTeamInternalIDQuery {
|
||||||
|
return getTeamInternalIDQuery{
|
||||||
|
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
|
||||||
|
TeamTable: sql.Table("team"),
|
||||||
|
Query: q,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type getTeamInternalIDQuery struct {
|
||||||
|
sqltemplate.SQLTemplate
|
||||||
|
TeamTable string
|
||||||
|
Query *GetTeamInternalIDQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r getTeamInternalIDQuery) Validate() error { return nil }
|
||||||
|
|
||||||
|
func (s *legacySQLStore) GetTeamInternalID(
|
||||||
|
ctx context.Context,
|
||||||
|
ns claims.NamespaceInfo,
|
||||||
|
query GetTeamInternalIDQuery,
|
||||||
|
) (*GetTeamInternalIDResult, error) {
|
||||||
|
query.OrgID = ns.OrgID
|
||||||
|
if query.OrgID == 0 {
|
||||||
|
return nil, fmt.Errorf("expected non zero org id")
|
||||||
|
}
|
||||||
|
|
||||||
|
sql, err := s.sql(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := newGetTeamInternalID(sql, &query)
|
||||||
|
q, err := sqltemplate.Execute(sqlQueryTeamInternalIDTemplate, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamInternalIDTemplate.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
|
||||||
|
defer func() {
|
||||||
|
if rows != nil {
|
||||||
|
_ = rows.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !rows.Next() {
|
||||||
|
return nil, errors.New("team not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
var id int64
|
||||||
|
if err := rows.Scan(&id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &GetTeamInternalIDResult{
|
||||||
|
id,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
type ListTeamQuery struct {
|
type ListTeamQuery struct {
|
||||||
OrgID int64
|
OrgID int64
|
||||||
UID string
|
UID string
|
||||||
|
5
pkg/registry/apis/iam/legacy/team_internal_id.sql
Normal file
5
pkg/registry/apis/iam/legacy/team_internal_id.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
SELECT t.id
|
||||||
|
FROM {{ .Ident .TeamTable }} as t
|
||||||
|
WHERE t.org_id = {{ .Arg .Query.OrgID }}
|
||||||
|
AND t.uid = {{ .Arg .Query.UID }}
|
||||||
|
LIMIT 1;
|
@ -97,7 +97,7 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge
|
|||||||
storage := map[string]rest.Storage{}
|
storage := map[string]rest.Storage{}
|
||||||
|
|
||||||
teamResource := iamv0.TeamResourceInfo
|
teamResource := iamv0.TeamResourceInfo
|
||||||
storage[teamResource.StoragePath()] = team.NewLegacyStore(b.store)
|
storage[teamResource.StoragePath()] = team.NewLegacyStore(b.store, b.accessClient)
|
||||||
storage[teamResource.StoragePath("members")] = team.NewLegacyTeamMemberREST(b.store)
|
storage[teamResource.StoragePath("members")] = team.NewLegacyTeamMemberREST(b.store)
|
||||||
|
|
||||||
teamBindingResource := iamv0.TeamBindingResourceInfo
|
teamBindingResource := iamv0.TeamBindingResourceInfo
|
||||||
|
@ -2,6 +2,7 @@ package team
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||||
@ -15,6 +16,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/registry/apis/iam/common"
|
"github.com/grafana/grafana/pkg/registry/apis/iam/common"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
|
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||||
|
"github.com/grafana/grafana/pkg/services/team"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -27,12 +29,13 @@ var (
|
|||||||
|
|
||||||
var resource = iamv0.TeamResourceInfo
|
var resource = iamv0.TeamResourceInfo
|
||||||
|
|
||||||
func NewLegacyStore(store legacy.LegacyIdentityStore) *LegacyStore {
|
func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient) *LegacyStore {
|
||||||
return &LegacyStore{store}
|
return &LegacyStore{store, ac}
|
||||||
}
|
}
|
||||||
|
|
||||||
type LegacyStore struct {
|
type LegacyStore struct {
|
||||||
store legacy.LegacyIdentityStore
|
store legacy.LegacyIdentityStore
|
||||||
|
ac claims.AccessClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LegacyStore) New() runtime.Object {
|
func (s *LegacyStore) New() runtime.Object {
|
||||||
@ -58,74 +61,84 @@ func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object,
|
|||||||
return resource.TableConverter().ConvertToTable(ctx, object, tableOptions)
|
return resource.TableConverter().ConvertToTable(ctx, object, tableOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LegacyStore) doList(ctx context.Context, ns claims.NamespaceInfo, query legacy.ListTeamQuery) (*iamv0.TeamList, error) {
|
func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||||
rsp, err := s.store.ListTeams(ctx, ns, query)
|
res, err := common.List(
|
||||||
if err != nil {
|
ctx, resource.GetName(), s.ac, common.PaginationFromListOptions(options),
|
||||||
return nil, err
|
func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0.Team], error) {
|
||||||
}
|
found, err := s.store.ListTeams(ctx, ns, legacy.ListTeamQuery{
|
||||||
list := &iamv0.TeamList{
|
Pagination: p,
|
||||||
ListMeta: metav1.ListMeta{
|
|
||||||
ResourceVersion: strconv.FormatInt(rsp.RV, 10),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, team := range rsp.Teams {
|
|
||||||
item := iamv0.Team{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: team.UID,
|
|
||||||
Namespace: ns.Value,
|
|
||||||
CreationTimestamp: metav1.NewTime(team.Created),
|
|
||||||
ResourceVersion: strconv.FormatInt(team.Updated.UnixMilli(), 10),
|
|
||||||
},
|
|
||||||
Spec: iamv0.TeamSpec{
|
|
||||||
Title: team.Name,
|
|
||||||
Email: team.Email,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
meta, err := utils.MetaAccessor(&item)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
meta.SetUpdatedTimestamp(&team.Updated)
|
|
||||||
meta.SetOriginInfo(&utils.ResourceOriginInfo{
|
|
||||||
Name: "SQL",
|
|
||||||
Path: strconv.FormatInt(team.ID, 10),
|
|
||||||
})
|
})
|
||||||
list.Items = append(list.Items, item)
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
list.ListMeta.Continue = common.OptionalFormatInt(rsp.Continue)
|
teams := make([]iamv0.Team, 0, len(found.Teams))
|
||||||
list.ListMeta.ResourceVersion = common.OptionalFormatInt(rsp.RV)
|
for _, t := range found.Teams {
|
||||||
|
teams = append(teams, toTeamObject(t, ns))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &common.ListResponse[iamv0.Team]{
|
||||||
|
Items: teams,
|
||||||
|
RV: found.RV,
|
||||||
|
Continue: found.Continue,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list teams: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
list := &iamv0.TeamList{Items: res.Items}
|
||||||
|
list.ListMeta.Continue = common.OptionalFormatInt(res.Continue)
|
||||||
|
list.ListMeta.ResourceVersion = common.OptionalFormatInt(res.RV)
|
||||||
|
|
||||||
return list, nil
|
return list, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
|
||||||
ns, err := request.NamespaceInfoFrom(ctx, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.doList(ctx, ns, legacy.ListTeamQuery{
|
|
||||||
OrgID: ns.OrgID,
|
|
||||||
Pagination: common.PaginationFromListOptions(options),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||||
ns, err := request.NamespaceInfoFrom(ctx, true)
|
ns, err := request.NamespaceInfoFrom(ctx, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rsp, err := s.doList(ctx, ns, legacy.ListTeamQuery{
|
|
||||||
|
found, err := s.store.ListTeams(ctx, ns, legacy.ListTeamQuery{
|
||||||
OrgID: ns.OrgID,
|
OrgID: ns.OrgID,
|
||||||
UID: name,
|
UID: name,
|
||||||
Pagination: common.Pagination{Limit: 1},
|
Pagination: common.Pagination{Limit: 1},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if found == nil || err != nil {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(rsp.Items) > 0 {
|
|
||||||
return &rsp.Items[0], nil
|
|
||||||
}
|
|
||||||
return nil, resource.NewNotFound(name)
|
return nil, resource.NewNotFound(name)
|
||||||
|
}
|
||||||
|
if len(found.Teams) < 1 {
|
||||||
|
return nil, resource.NewNotFound(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := toTeamObject(found.Teams[0], ns)
|
||||||
|
return &obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toTeamObject(t team.Team, ns claims.NamespaceInfo) iamv0.Team {
|
||||||
|
obj := iamv0.Team{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: t.UID,
|
||||||
|
Namespace: ns.Value,
|
||||||
|
CreationTimestamp: metav1.NewTime(t.Created),
|
||||||
|
ResourceVersion: strconv.FormatInt(t.Updated.UnixMilli(), 10),
|
||||||
|
},
|
||||||
|
Spec: iamv0.TeamSpec{
|
||||||
|
Title: t.Name,
|
||||||
|
Email: t.Email,
|
||||||
|
InternalID: t.ID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
meta, _ := utils.MetaAccessor(&obj)
|
||||||
|
meta.SetUpdatedTimestamp(&t.Updated)
|
||||||
|
meta.SetOriginInfo(&utils.ResourceOriginInfo{
|
||||||
|
Name: "SQL",
|
||||||
|
Path: strconv.FormatInt(t.ID, 10),
|
||||||
|
})
|
||||||
|
|
||||||
|
return obj
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user