grafana-iam: Wire RoleBindings APIs to OSS (#111728)

* Update role binding specs

* Wire role bindings APIs in OSS

* gofmt

* revert modules updates

* update specs

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>

* update registration

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>

* fix reader

* fix format

* add role bindings mapper

* remove namespace from subject spec

* fix formatting

* update api specs

* Fix role binding registration

* remove mapper

---------

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
Alexander Zobnin
2025-09-29 14:28:12 +02:00
committed by GitHub
co-authored by Gabriel MABILLE
parent c4a2bb5540
commit 893523dd7c
11 changed files with 92 additions and 48 deletions
+4 -4
View File
@@ -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]
}
@@ -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{},
}
}
+28 -1
View File
@@ -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{},
@@ -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{},
}
}
@@ -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{
+2 -1
View File
@@ -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
+8 -2
View File
@@ -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
+9
View File
@@ -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 {
+1
View File
@@ -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(
+2 -2
View File
@@ -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
}
@@ -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": {}
}
}
},