diff --git a/apps/iam/kinds/v0alpha1/rolebindingspec.cue b/apps/iam/kinds/v0alpha1/rolebindingspec.cue index 0b4a6396f26..ef889668201 100644 --- a/apps/iam/kinds/v0alpha1/rolebindingspec.cue +++ b/apps/iam/kinds/v0alpha1/rolebindingspec.cue @@ -14,8 +14,8 @@ RoleBindingSpec: { name: string } - subjects: [...#Subject] - roleRef: #RoleRef + subject: #Subject + roleRefs: [...#RoleRef] } GlobalRoleBindingSpec: { @@ -32,6 +32,6 @@ GlobalRoleBindingSpec: { name: string } - subjects: [...#Subject] - roleRef: #RoleRef + subject: #Subject + roleRefs: [...#RoleRef] } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_spec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_spec_gen.go index a60921ecab5..ff3ff78da08 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_spec_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_spec_gen.go @@ -30,15 +30,15 @@ func NewGlobalRoleBindingspecRoleRef() *GlobalRoleBindingspecRoleRef { // +k8s:openapi-gen=true type GlobalRoleBindingSpec struct { - Subjects []GlobalRoleBindingspecSubject `json:"subjects"` - RoleRef GlobalRoleBindingspecRoleRef `json:"roleRef"` + Subject GlobalRoleBindingspecSubject `json:"subject"` + RoleRefs []GlobalRoleBindingspecRoleRef `json:"roleRefs"` } // NewGlobalRoleBindingSpec creates a new GlobalRoleBindingSpec object. func NewGlobalRoleBindingSpec() *GlobalRoleBindingSpec { return &GlobalRoleBindingSpec{ - Subjects: []GlobalRoleBindingspecSubject{}, - RoleRef: *NewGlobalRoleBindingspecRoleRef(), + Subject: *NewGlobalRoleBindingspecSubject(), + RoleRefs: []GlobalRoleBindingspecRoleRef{}, } } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/register.go b/apps/iam/pkg/apis/iam/v0alpha1/register.go index 7d313ac250b..16ee89847ef 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/register.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/register.go @@ -5,10 +5,11 @@ import ( "strings" "time" - "github.com/grafana/grafana/pkg/apimachinery/utils" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/grafana/grafana/pkg/apimachinery/utils" ) const ( @@ -205,6 +206,30 @@ var TeamBindingResourceInfo = utils.NewResourceInfo( }, ) +var RoleBindingInfo = utils.NewResourceInfo(GROUP, VERSION, + "rolebindings", "rolebinding", "RoleBinding", + func() runtime.Object { return &RoleBinding{} }, + func() runtime.Object { return &RoleBindingList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Created At", Type: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + roleBinding, ok := obj.(*RoleBinding) + if ok { + if roleBinding != nil { + return []interface{}{ + roleBinding.Name, + roleBinding.CreationTimestamp.UTC().Format(time.RFC3339), + }, nil + } + } + return nil, fmt.Errorf("expected role binding") + }, + }, +) + var ( SchemeBuilder runtime.SchemeBuilder localSchemeBuilder = &SchemeBuilder @@ -237,6 +262,8 @@ func AddAuthZKnownTypes(scheme *runtime.Scheme) error { &CoreRoleList{}, &Role{}, &RoleList{}, + &RoleBinding{}, + &RoleBindingList{}, // What is this about? &metav1.PartialObjectMetadata{}, diff --git a/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_spec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_spec_gen.go index cd214ca6cdd..a3684c5c741 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_spec_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_spec_gen.go @@ -30,15 +30,15 @@ func NewRoleBindingspecRoleRef() *RoleBindingspecRoleRef { // +k8s:openapi-gen=true type RoleBindingSpec struct { - Subjects []RoleBindingspecSubject `json:"subjects"` - RoleRef RoleBindingspecRoleRef `json:"roleRef"` + Subject RoleBindingspecSubject `json:"subject"` + RoleRefs []RoleBindingspecRoleRef `json:"roleRefs"` } // NewRoleBindingSpec creates a new RoleBindingSpec object. func NewRoleBindingSpec() *RoleBindingSpec { return &RoleBindingSpec{ - Subjects: []RoleBindingspecSubject{}, - RoleRef: *NewRoleBindingspecRoleRef(), + Subject: *NewRoleBindingspecSubject(), + RoleRefs: []RoleBindingspecRoleRef{}, } } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go index ac63422d531..fb6892fa09d 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go @@ -504,27 +504,27 @@ func schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingSpec(ref common.ReferenceCall SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ - "subjects": { + "subject": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingspecSubject"), + }, + }, + "roleRefs": { SchemaProps: spec.SchemaProps{ Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingspecSubject"), + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingspecRoleRef"), }, }, }, }, }, - "roleRef": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingspecRoleRef"), - }, - }, }, - Required: []string{"subjects", "roleRef"}, + Required: []string{"subject", "roleRefs"}, }, }, Dependencies: []string{ @@ -1364,27 +1364,27 @@ func schema_pkg_apis_iam_v0alpha1_RoleBindingSpec(ref common.ReferenceCallback) SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ - "subjects": { + "subject": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingspecSubject"), + }, + }, + "roleRefs": { SchemaProps: spec.SchemaProps{ Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingspecSubject"), + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingspecRoleRef"), }, }, }, }, }, - "roleRef": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingspecRoleRef"), - }, - }, }, - Required: []string{"subjects", "roleRef"}, + Required: []string{"subject", "roleRefs"}, }, }, Dependencies: []string{ diff --git a/pkg/registry/apis/iam/authorizer.go b/pkg/registry/apis/iam/authorizer.go index 78461f18aa1..eadb07d7d50 100644 --- a/pkg/registry/apis/iam/authorizer.go +++ b/pkg/registry/apis/iam/authorizer.go @@ -4,9 +4,9 @@ import ( "context" "fmt" + authlib "github.com/grafana/authlib/types" "k8s.io/apiserver/pkg/authorization/authorizer" - authlib "github.com/grafana/authlib/types" iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" @@ -31,6 +31,7 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth resourceAuthorizer[iamv0.CoreRoleInfo.GetName()] = authorizer resourceAuthorizer[iamv0.RoleInfo.GetName()] = authorizer resourceAuthorizer[iamv0.ResourcePermissionInfo.GetName()] = authorizer + resourceAuthorizer[iamv0.RoleBindingInfo.GetName()] = authorizer resourceAuthorizer[iamv0.ServiceAccountResourceInfo.GetName()] = authorizer resourceAuthorizer[iamv0.UserResourceInfo.GetName()] = authorizer diff --git a/pkg/registry/apis/iam/models.go b/pkg/registry/apis/iam/models.go index fc1b3de6017..700a4d04d1c 100644 --- a/pkg/registry/apis/iam/models.go +++ b/pkg/registry/apis/iam/models.go @@ -2,13 +2,14 @@ package iam import ( "github.com/grafana/authlib/types" + "github.com/prometheus/client_golang/prometheus" + "k8s.io/apiserver/pkg/authorization/authorizer" + "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" "github.com/grafana/grafana/pkg/registry/apis/iam/user" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/ssosettings" "github.com/grafana/grafana/pkg/storage/unified/resource" - "github.com/prometheus/client_golang/prometheus" - "k8s.io/apiserver/pkg/authorization/authorizer" ) var _ builder.APIGroupBuilder = (*IdentityAccessManagementAPIBuilder)(nil) @@ -24,6 +25,10 @@ type CoreRoleStorageBackend interface{ resource.StorageBackend } // Used by wire to identify the storage backend for custom roles. type RoleStorageBackend interface{ resource.StorageBackend } +// RoleBindingStorageBackend uses the resource.StorageBackend interface to provide storage for role bindings. +// Used by wire to identify the storage backend for role bindings. +type RoleBindingStorageBackend interface{ resource.StorageBackend } + // This is used just so wire has something unique to return type IdentityAccessManagementAPIBuilder struct { // Stores @@ -31,6 +36,7 @@ type IdentityAccessManagementAPIBuilder struct { coreRolesStorage CoreRoleStorageBackend rolesStorage RoleStorageBackend resourcePermissionsStorage resource.StorageBackend + roleBindingsStorage RoleBindingStorageBackend // Access Control authorizer authorizer.Authorizer diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 23327981cd7..a92744700dc 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -19,6 +19,7 @@ import ( "k8s.io/kube-openapi/pkg/validation/spec" "github.com/grafana/authlib/types" + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" @@ -52,6 +53,7 @@ func RegisterAPIService( reg prometheus.Registerer, coreRolesStorage CoreRoleStorageBackend, rolesStorage RoleStorageBackend, + roleBindingsStorage RoleBindingStorageBackend, ) (*IdentityAccessManagementAPIBuilder, error) { dbProvider := legacysql.NewDatabaseProvider(sql) store := legacy.NewLegacySQLStores(dbProvider) @@ -63,6 +65,7 @@ func RegisterAPIService( coreRolesStorage: coreRolesStorage, rolesStorage: rolesStorage, resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider), + roleBindingsStorage: roleBindingsStorage, sso: ssoService, authorizer: authorizer, legacyAccessClient: legacyAccessClient, @@ -231,6 +234,12 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge return err } storage[iamv0.RoleInfo.StoragePath()] = roleStore + + roleBindingStore, err := NewLocalStore(iamv0.RoleBindingInfo, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.roleBindingsStorage) + if err != nil { + return err + } + storage[iamv0.RoleBindingInfo.StoragePath()] = roleBindingStore } if b.enableResourcePermissionApis { diff --git a/pkg/registry/apis/wireset.go b/pkg/registry/apis/wireset.go index 0dd28d4d274..b753747d478 100644 --- a/pkg/registry/apis/wireset.go +++ b/pkg/registry/apis/wireset.go @@ -27,6 +27,7 @@ var WireSetExts = wire.NewSet( noopstorage.ProvideStorageBackend, wire.Bind(new(iam.CoreRoleStorageBackend), new(*noopstorage.StorageBackendImpl)), wire.Bind(new(iam.RoleStorageBackend), new(*noopstorage.StorageBackendImpl)), + wire.Bind(new(iam.RoleBindingStorageBackend), new(*noopstorage.StorageBackendImpl)), ) var provisioningExtras = wire.NewSet( diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 9d7778c8f81..27ca946912c 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -816,7 +816,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api } folderAPIBuilder := folders.RegisterAPIService(cfg, featureToggles, apiserverService, folderimplService, folderPermissionsService, accessControl, acimplService, accessClient, registerer, resourceClient, zanzanaClient) storageBackendImpl := noopstorage.ProvideStorageBackend() - identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, registerer, storageBackendImpl, storageBackendImpl) + identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, registerer, storageBackendImpl, storageBackendImpl, storageBackendImpl) if err != nil { return nil, err } @@ -1423,7 +1423,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac } folderAPIBuilder := folders.RegisterAPIService(cfg, featureToggles, apiserverService, folderimplService, folderPermissionsService, accessControl, acimplService, accessClient, registerer, resourceClient, zanzanaClient) storageBackendImpl := noopstorage.ProvideStorageBackend() - identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, registerer, storageBackendImpl, storageBackendImpl) + identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, registerer, storageBackendImpl, storageBackendImpl, storageBackendImpl) if err != nil { return nil, err } diff --git a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json index 1004a318bc6..a1d5158e140 100644 --- a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json @@ -4945,18 +4945,18 @@ "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingSpec": { "type": "object", "required": [ - "subjects", - "roleRef" + "subject", + "roleRefs" ], "properties": { - "roleRef": { - "default": {} - }, - "subjects": { + "roleRefs": { "type": "array", "items": { "default": {} } + }, + "subject": { + "default": {} } } }, @@ -5434,18 +5434,18 @@ "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingSpec": { "type": "object", "required": [ - "subjects", - "roleRef" + "subject", + "roleRefs" ], "properties": { - "roleRef": { - "default": {} - }, - "subjects": { + "roleRefs": { "type": "array", "items": { "default": {} } + }, + "subject": { + "default": {} } } },