grafana-iam: Skeleton of the resource permission api backend (#110218)
* Extract from #108753 Co-Authored-By: mohammad-hamid <mohammad.hamid@grafana.com> * Tackle create Co-Authored-By: mohammad-hamid <mohammad.hamid@grafana.com> * WIP use identity store to resolve role names * Commit empty service for now * Clean * For now only show name and created at --------- Co-authored-by: mohammad-hamid <mohammad.hamid@grafana.com>
This commit is contained in:
co-authored by
mohammad-hamid
parent
a0075ffdd6
commit
b6226c6173
@@ -72,6 +72,30 @@ var RoleInfo = utils.NewResourceInfo(GROUP, VERSION,
|
||||
},
|
||||
)
|
||||
|
||||
var ResourcePermissionInfo = utils.NewResourceInfo(GROUP, VERSION,
|
||||
"resourcepermissions", "resourcepermission", "ResourcePermission",
|
||||
func() runtime.Object { return &ResourcePermission{} },
|
||||
func() runtime.Object { return &ResourcePermissionList{} },
|
||||
utils.TableColumns{
|
||||
Definition: []metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name"},
|
||||
{Name: "Created At", Type: "date"},
|
||||
},
|
||||
Reader: func(obj any) ([]interface{}, error) {
|
||||
perm, ok := obj.(*ResourcePermission)
|
||||
if ok {
|
||||
if perm != nil {
|
||||
return []interface{}{
|
||||
perm.Name,
|
||||
perm.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("expected resource permission")
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
var (
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
@@ -90,6 +114,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
&CoreRoleList{},
|
||||
&Role{},
|
||||
&RoleList{},
|
||||
&ResourcePermission{},
|
||||
&ResourcePermissionList{},
|
||||
|
||||
// What is this about?
|
||||
&metav1.PartialObjectMetadata{},
|
||||
|
||||
@@ -33,6 +33,7 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth
|
||||
authorizer := gfauthorizer.NewResourceAuthorizer(accessClient)
|
||||
resourceAuthorizer[iamv0.CoreRoleInfo.GetName()] = authorizer
|
||||
resourceAuthorizer[iamv0.RoleInfo.GetName()] = authorizer
|
||||
resourceAuthorizer[iamv0.ResourcePermissionInfo.GetName()] = authorizer
|
||||
|
||||
return &iamAuthorizer{resourceAuthorizer: resourceAuthorizer}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,10 @@ type RoleStorageBackend interface{ resource.StorageBackend }
|
||||
// This is used just so wire has something unique to return
|
||||
type IdentityAccessManagementAPIBuilder struct {
|
||||
// Stores
|
||||
store legacy.LegacyIdentityStore
|
||||
coreRolesStorage CoreRoleStorageBackend
|
||||
rolesStorage RoleStorageBackend
|
||||
store legacy.LegacyIdentityStore
|
||||
coreRolesStorage CoreRoleStorageBackend
|
||||
rolesStorage RoleStorageBackend
|
||||
resourcePermissionsStorage resource.StorageBackend
|
||||
|
||||
// Access Control
|
||||
authorizer authorizer.Authorizer
|
||||
@@ -47,7 +48,8 @@ type IdentityAccessManagementAPIBuilder struct {
|
||||
sso ssosettings.Service
|
||||
|
||||
// Toggle for enabling authz management apis
|
||||
enableAuthZApis bool
|
||||
enableAuthZApis bool
|
||||
enableResourcePermissionApis bool
|
||||
|
||||
// Toggle for enabling authn mutation
|
||||
enableAuthnMutation bool
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam/resourcepermission"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam/serviceaccount"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam/sso"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam/team"
|
||||
@@ -53,23 +54,26 @@ func RegisterAPIService(
|
||||
coreRolesStorage CoreRoleStorageBackend,
|
||||
rolesStorage RoleStorageBackend,
|
||||
) (*IdentityAccessManagementAPIBuilder, error) {
|
||||
store := legacy.NewLegacySQLStores(legacysql.NewDatabaseProvider(sql))
|
||||
dbProvider := legacysql.NewDatabaseProvider(sql)
|
||||
store := legacy.NewLegacySQLStores(dbProvider)
|
||||
legacyAccessClient := newLegacyAccessClient(ac, store)
|
||||
authorizer := newIAMAuthorizer(accessClient, legacyAccessClient)
|
||||
|
||||
builder := &IdentityAccessManagementAPIBuilder{
|
||||
store: store,
|
||||
coreRolesStorage: coreRolesStorage,
|
||||
rolesStorage: rolesStorage,
|
||||
sso: ssoService,
|
||||
authorizer: authorizer,
|
||||
legacyAccessClient: legacyAccessClient,
|
||||
accessClient: accessClient,
|
||||
display: user.NewLegacyDisplayREST(store),
|
||||
reg: reg,
|
||||
enableAuthZApis: features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzApis),
|
||||
enableAuthnMutation: features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthnMutation),
|
||||
enableDualWriter: true,
|
||||
store: store,
|
||||
coreRolesStorage: coreRolesStorage,
|
||||
rolesStorage: rolesStorage,
|
||||
resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider),
|
||||
sso: ssoService,
|
||||
authorizer: authorizer,
|
||||
legacyAccessClient: legacyAccessClient,
|
||||
accessClient: accessClient,
|
||||
display: user.NewLegacyDisplayREST(store),
|
||||
reg: reg,
|
||||
enableAuthZApis: features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzApis),
|
||||
enableResourcePermissionApis: features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzResourcePermissionApis),
|
||||
enableAuthnMutation: features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthnMutation),
|
||||
enableDualWriter: true,
|
||||
}
|
||||
apiregistration.RegisterAPI(builder)
|
||||
|
||||
@@ -173,6 +177,14 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge
|
||||
storage[iamv0.RoleInfo.StoragePath()] = roleStore
|
||||
}
|
||||
|
||||
if b.enableResourcePermissionApis {
|
||||
resourcePermissionStore, err := NewLocalStore(iamv0.ResourcePermissionInfo, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.resourcePermissionsStorage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storage[iamv0.ResourcePermissionInfo.StoragePath()] = resourcePermissionStore
|
||||
}
|
||||
|
||||
apiGroupInfo.VersionedResourcesStorageMap[legacyiamv0.VERSION] = storage
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package resourcepermission
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
errNotImplemented = errors.New("not supported by this storage backend")
|
||||
errEmptyName = fmt.Errorf("name cannot be empty")
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
package resourcepermission
|
||||
|
||||
// List
|
||||
|
||||
// Get
|
||||
|
||||
// Create
|
||||
|
||||
// Update
|
||||
|
||||
// Delete
|
||||
@@ -0,0 +1,103 @@
|
||||
package resourcepermission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
"github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/storage/legacysql"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
|
||||
)
|
||||
|
||||
var (
|
||||
_ resource.StorageBackend = &ResourcePermSqlBackend{}
|
||||
)
|
||||
|
||||
type ResourcePermSqlBackend struct {
|
||||
dbProvider legacysql.LegacyDatabaseProvider
|
||||
logger log.Logger
|
||||
|
||||
subscribers []chan *resource.WrittenEvent
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func ProvideStorageBackend(dbProvider legacysql.LegacyDatabaseProvider) *ResourcePermSqlBackend {
|
||||
return &ResourcePermSqlBackend{
|
||||
dbProvider: dbProvider,
|
||||
logger: log.New("resourceperm_storage_backend"),
|
||||
|
||||
subscribers: make([]chan *resource.WrittenEvent, 0),
|
||||
mutex: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ResourcePermSqlBackend) GetResourceStats(ctx context.Context, namespace string, minCount int) ([]resource.ResourceStats, error) {
|
||||
return []resource.ResourceStats{}, errNotImplemented
|
||||
}
|
||||
|
||||
func (s *ResourcePermSqlBackend) ListHistory(context.Context, *resourcepb.ListRequest, func(resource.ListIterator) error) (int64, error) {
|
||||
return 0, errNotImplemented
|
||||
}
|
||||
|
||||
func (s *ResourcePermSqlBackend) ListIterator(context.Context, *resourcepb.ListRequest, func(resource.ListIterator) error) (int64, error) {
|
||||
return 0, errNotImplemented
|
||||
}
|
||||
|
||||
func (s *ResourcePermSqlBackend) ListModifiedSince(ctx context.Context, key resource.NamespacedResource, sinceRv int64) (int64, iter.Seq2[*resource.ModifiedResource, error]) {
|
||||
return 0, func(yield func(*resource.ModifiedResource, error) bool) {
|
||||
yield(nil, errNotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ResourcePermSqlBackend) ReadResource(_ context.Context, req *resourcepb.ReadRequest) *resource.BackendReadResponse {
|
||||
return &resource.BackendReadResponse{
|
||||
Key: req.GetKey(),
|
||||
Error: &resourcepb.ErrorResult{Code: http.StatusForbidden, Message: errNotImplemented.Error()},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ResourcePermSqlBackend) WatchWriteEvents(ctx context.Context) (<-chan *resource.WrittenEvent, error) {
|
||||
stream := make(chan *resource.WrittenEvent, 10)
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func isValidKey(key *resourcepb.ResourceKey, requireName bool) error {
|
||||
gr := v0alpha1.ResourcePermissionInfo.GroupResource()
|
||||
if key.Group != gr.Group {
|
||||
return fmt.Errorf("expecting group (%s != %s)", key.Group, gr.Group)
|
||||
}
|
||||
if key.Resource != gr.Resource {
|
||||
return fmt.Errorf("expecting resource (%s != %s)", key.Resource, gr.Resource)
|
||||
}
|
||||
if requireName && key.Name == "" {
|
||||
return fmt.Errorf("expecting name (uid): %w", errEmptyName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ResourcePermSqlBackend) WriteEvent(ctx context.Context, event resource.WriteEvent) (rv int64, err error) {
|
||||
ns, err := types.ParseNamespace(event.Key.Namespace)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if ns.OrgID <= 0 {
|
||||
return 0, apierrors.NewBadRequest("write requires a valid namespace")
|
||||
}
|
||||
|
||||
if err := isValidKey(event.Key, true); err != nil {
|
||||
return 0, apierrors.NewBadRequest(fmt.Sprintf("invalid key %q: %v", event.Key, err.Error()))
|
||||
}
|
||||
|
||||
switch event.Type {
|
||||
default:
|
||||
return 0, fmt.Errorf("unsupported event type: %v", event.Type)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package resourcepermission
|
||||
|
||||
// List
|
||||
|
||||
// Get
|
||||
|
||||
// Create
|
||||
|
||||
// Update
|
||||
|
||||
// Delete
|
||||
Reference in New Issue
Block a user