mirror of
https://github.com/grafana/grafana.git
synced 2024-11-22 08:56:43 -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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
@ -15,6 +17,13 @@ type Team struct {
|
||||
type TeamSpec struct {
|
||||
Title string `json:"title,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
|
||||
|
@ -54,6 +54,19 @@ func newLegacyAuthorizer(ac accesscontrol.AccessControl, store legacy.LegacyIden
|
||||
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
|
||||
|
@ -22,6 +22,7 @@ type LegacyIdentityStore interface {
|
||||
ListServiceAccounts(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountsQuery) (*ListServiceAccountResult, 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)
|
||||
ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error)
|
||||
ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error)
|
||||
|
@ -3,6 +3,7 @@ package legacy
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@ -14,6 +15,79 @@ import (
|
||||
"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 {
|
||||
OrgID int64
|
||||
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{}
|
||||
|
||||
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)
|
||||
|
||||
teamBindingResource := iamv0.TeamBindingResourceInfo
|
||||
|
@ -2,6 +2,7 @@ package team
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"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/legacy"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -27,12 +29,13 @@ var (
|
||||
|
||||
var resource = iamv0.TeamResourceInfo
|
||||
|
||||
func NewLegacyStore(store legacy.LegacyIdentityStore) *LegacyStore {
|
||||
return &LegacyStore{store}
|
||||
func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient) *LegacyStore {
|
||||
return &LegacyStore{store, ac}
|
||||
}
|
||||
|
||||
type LegacyStore struct {
|
||||
store legacy.LegacyIdentityStore
|
||||
ac claims.AccessClient
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (s *LegacyStore) doList(ctx context.Context, ns claims.NamespaceInfo, query legacy.ListTeamQuery) (*iamv0.TeamList, error) {
|
||||
rsp, err := s.store.ListTeams(ctx, ns, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list := &iamv0.TeamList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: strconv.FormatInt(rsp.RV, 10),
|
||||
func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||
res, err := common.List(
|
||||
ctx, resource.GetName(), s.ac, common.PaginationFromListOptions(options),
|
||||
func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0.Team], error) {
|
||||
found, err := s.store.ListTeams(ctx, ns, legacy.ListTeamQuery{
|
||||
Pagination: p,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
teams := make([]iamv0.Team, 0, len(found.Teams))
|
||||
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
|
||||
},
|
||||
}
|
||||
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, fmt.Errorf("failed to list teams: %w", err)
|
||||
}
|
||||
|
||||
list.ListMeta.Continue = common.OptionalFormatInt(rsp.Continue)
|
||||
list.ListMeta.ResourceVersion = common.OptionalFormatInt(rsp.RV)
|
||||
list := &iamv0.TeamList{Items: res.Items}
|
||||
list.ListMeta.Continue = common.OptionalFormatInt(res.Continue)
|
||||
list.ListMeta.ResourceVersion = common.OptionalFormatInt(res.RV)
|
||||
|
||||
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) {
|
||||
ns, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rsp, err := s.doList(ctx, ns, legacy.ListTeamQuery{
|
||||
|
||||
found, err := s.store.ListTeams(ctx, ns, legacy.ListTeamQuery{
|
||||
OrgID: ns.OrgID,
|
||||
UID: name,
|
||||
Pagination: common.Pagination{Limit: 1},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if found == nil || err != nil {
|
||||
return nil, resource.NewNotFound(name)
|
||||
}
|
||||
if len(rsp.Items) > 0 {
|
||||
return &rsp.Items[0], nil
|
||||
if len(found.Teams) < 1 {
|
||||
return nil, resource.NewNotFound(name)
|
||||
}
|
||||
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