IAM: Register CoreRole apis (#106924)
* IAM: Register CoreRole apis
* one line store instantiation
* Small refactor for readability
* Add authorizer for CoreRole
* Nit
* Error strings should not end with punctiation
* Account for error
* Switch to use the local resource client
* error should not start with upper casing
* noopStorageErr should have a name starting with err
* Update workspace
* I don't know why I don't have the same output as the CI 🤷
* Dependency xOwnership
* imports
* Import order
* Rename alias to make it clear this is legacy
This commit is contained in:
+4
-1
@@ -4,6 +4,7 @@ go 1.24.4
|
||||
|
||||
require (
|
||||
github.com/grafana/grafana-app-sdk v0.39.0
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e
|
||||
k8s.io/apimachinery v0.33.1
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
|
||||
)
|
||||
@@ -33,11 +34,13 @@ require (
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.64.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||
|
||||
@@ -36,6 +36,8 @@ github.com/grafana/grafana-app-sdk v0.39.0 h1:WC2E9BKXWDX/e2bajdAFjQEyyWf9BFp7Yz
|
||||
github.com/grafana/grafana-app-sdk v0.39.0/go.mod h1:xRyBQOttgWTc3tGe9pI0upnpEPVhzALf7Mh/61O4zyY=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.38.2 h1:EdQTRxbbH72zdqJ09Z76zcSjfALJXkpPLgvKEPPnloc=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.38.2/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
GROUP = "iam.grafana.app"
|
||||
VERSION = "v0alpha1"
|
||||
APIVERSION = GROUP + "/" + VERSION
|
||||
)
|
||||
|
||||
var CoreRoleInfo = utils.NewResourceInfo(GROUP, VERSION,
|
||||
"coreroles", "corerole", "CoreRole",
|
||||
func() runtime.Object { return &CoreRole{} },
|
||||
func() runtime.Object { return &CoreRoleList{} },
|
||||
utils.TableColumns{
|
||||
Definition: []metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name"},
|
||||
{Name: "Title", Type: "string", Format: "string", Description: "Core role name"}, // Not sure this is actually needed
|
||||
{Name: "Created At", Type: "date"},
|
||||
},
|
||||
Reader: func(obj any) ([]interface{}, error) {
|
||||
core, ok := obj.(*CoreRole)
|
||||
if ok {
|
||||
if core != nil {
|
||||
return []interface{}{
|
||||
core.Name,
|
||||
core.Spec.Title,
|
||||
core.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("expected core role")
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
var (
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
schemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION}
|
||||
)
|
||||
|
||||
func init() {
|
||||
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
|
||||
}
|
||||
|
||||
// Adds the list of known types to the given scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(schemeGroupVersion,
|
||||
&CoreRole{},
|
||||
&CoreRoleList{},
|
||||
|
||||
// What is this about?
|
||||
&metav1.PartialObjectMetadata{},
|
||||
&metav1.PartialObjectMetadataList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, schemeGroupVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
||||
// return RegisterDefaults(scheme)
|
||||
return nil
|
||||
}
|
||||
@@ -212,6 +212,7 @@ require (
|
||||
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250527064921-326081cdb7a1 // @grafana/alerting-backend
|
||||
github.com/grafana/grafana/apps/dashboard v0.0.0-20250616215703-eb4de388c3cf // @grafana/grafana-app-platform-squad @grafana/dashboards-squad
|
||||
github.com/grafana/grafana/apps/folder v0.0.0-20250527064921-326081cdb7a1 // @grafana/grafana-search-and-storage
|
||||
github.com/grafana/grafana/apps/iam v0.0.0-20250620082852-de59956db4df // @grafana/identity-access-team
|
||||
github.com/grafana/grafana/apps/investigations v0.0.0-20250527064921-326081cdb7a1 // @fcjack @matryer
|
||||
github.com/grafana/grafana/apps/playlist v0.0.0-20250527064921-326081cdb7a1 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/pkg/aggregator v0.0.0-20250527064921-326081cdb7a1 // @grafana/grafana-app-platform-squad
|
||||
|
||||
@@ -1621,6 +1621,8 @@ github.com/grafana/grafana/apps/dashboard v0.0.0-20250616215703-eb4de388c3cf h1:
|
||||
github.com/grafana/grafana/apps/dashboard v0.0.0-20250616215703-eb4de388c3cf/go.mod h1:XcJIUxPfKAhDb9mx9jHeV9U2xVA2WgE93ukd4Bx8U/M=
|
||||
github.com/grafana/grafana/apps/folder v0.0.0-20250527064921-326081cdb7a1 h1:SkVUcSlXIUi46gSGfe5/Xqn2p51c/unmkZRDX+274Os=
|
||||
github.com/grafana/grafana/apps/folder v0.0.0-20250527064921-326081cdb7a1/go.mod h1:z9u5VFG9q1CcIt62c9RIP8dEWHX81NdiPCjtpKKeuFU=
|
||||
github.com/grafana/grafana/apps/iam v0.0.0-20250620082852-de59956db4df h1:t0y8BiEJ7dBq2/eYQI66kYyOGYdPCxNPNftrRwL6kIo=
|
||||
github.com/grafana/grafana/apps/iam v0.0.0-20250620082852-de59956db4df/go.mod h1:6j/luBu5kqJysChmy9bnlCDYvukIAxqaoUEVI+HaCuA=
|
||||
github.com/grafana/grafana/apps/investigations v0.0.0-20250527064921-326081cdb7a1 h1:IN+KiBwtvJ3JCLdsPyAmGKJupWckH3h5iXpELd78rPo=
|
||||
github.com/grafana/grafana/apps/investigations v0.0.0-20250527064921-326081cdb7a1/go.mod h1:/uuCYrNS3VOTZ7gTD+cBHD9WBIYhqj7bK6EnX3TP1wg=
|
||||
github.com/grafana/grafana/apps/playlist v0.0.0-20250527064921-326081cdb7a1 h1:GNPPHXhUv++4qKdrUJg/Mfi/HJPGlc5u+/FjElYAcOM=
|
||||
|
||||
@@ -125,6 +125,7 @@ var serviceIdentityTokenPermissions = getTokenPermissions(
|
||||
"dashboard.grafana.app",
|
||||
"secret.grafana.app",
|
||||
"query.grafana.app",
|
||||
"iam.grafana.app",
|
||||
)
|
||||
|
||||
var ServiceIdentityClaims = &authn.Claims[authn.AccessTokenClaims]{
|
||||
|
||||
@@ -7,18 +7,53 @@ import (
|
||||
"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"
|
||||
iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
|
||||
legacyiamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
gfauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer"
|
||||
)
|
||||
|
||||
func newLegacyAuthorizer(ac accesscontrol.AccessControl, store legacy.LegacyIdentityStore) (authorizer.Authorizer, authlib.AccessClient) {
|
||||
type iamAuthorizer struct {
|
||||
resourceAuthorizer map[string]authorizer.Authorizer // Map resource to its authorizer
|
||||
}
|
||||
|
||||
func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient authlib.AccessClient) authorizer.Authorizer {
|
||||
resourceAuthorizer := make(map[string]authorizer.Authorizer)
|
||||
|
||||
// Identity specific resources
|
||||
legacyAuthorizer := gfauthorizer.NewResourceAuthorizer(legacyAccessClient)
|
||||
resourceAuthorizer[legacyiamv0.UserResourceInfo.GetName()] = legacyAuthorizer
|
||||
resourceAuthorizer[legacyiamv0.ServiceAccountResourceInfo.GetName()] = legacyAuthorizer
|
||||
resourceAuthorizer[legacyiamv0.TeamResourceInfo.GetName()] = legacyAuthorizer
|
||||
resourceAuthorizer["display"] = legacyAuthorizer
|
||||
|
||||
// Access specific resources
|
||||
authorizer := gfauthorizer.NewResourceAuthorizer(accessClient)
|
||||
resourceAuthorizer[iamv0.CoreRoleInfo.GetName()] = authorizer
|
||||
|
||||
return &iamAuthorizer{resourceAuthorizer: resourceAuthorizer}
|
||||
}
|
||||
|
||||
func (s *iamAuthorizer) Authorize(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
if !attr.IsResourceRequest() {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
|
||||
authz, ok := s.resourceAuthorizer[attr.GetResource()]
|
||||
if !ok {
|
||||
return authorizer.DecisionDeny, "", fmt.Errorf("no authorizer found for resource %s", attr.GetResource())
|
||||
}
|
||||
|
||||
return authz.Authorize(ctx, attr)
|
||||
}
|
||||
|
||||
func newLegacyAccessClient(ac accesscontrol.AccessControl, store legacy.LegacyIdentityStore) authlib.AccessClient {
|
||||
client := accesscontrol.NewLegacyAccessClient(
|
||||
ac,
|
||||
accesscontrol.ResourceAuthorizerOptions{
|
||||
Resource: iamv0.UserResourceInfo.GetName(),
|
||||
Resource: legacyiamv0.UserResourceInfo.GetName(),
|
||||
Attr: "id",
|
||||
Mapping: map[string]string{
|
||||
utils.VerbGet: accesscontrol.ActionOrgUsersRead,
|
||||
@@ -42,7 +77,7 @@ func newLegacyAuthorizer(ac accesscontrol.AccessControl, store legacy.LegacyIden
|
||||
},
|
||||
},
|
||||
accesscontrol.ResourceAuthorizerOptions{
|
||||
Resource: iamv0.ServiceAccountResourceInfo.GetName(),
|
||||
Resource: legacyiamv0.ServiceAccountResourceInfo.GetName(),
|
||||
Attr: "id",
|
||||
Resolver: accesscontrol.ResourceResolverFunc(func(ctx context.Context, ns authlib.NamespaceInfo, name string) ([]string, error) {
|
||||
res, err := store.GetServiceAccountInternalID(ctx, ns, legacy.GetServiceAccountInternalIDQuery{
|
||||
@@ -55,7 +90,7 @@ func newLegacyAuthorizer(ac accesscontrol.AccessControl, store legacy.LegacyIden
|
||||
}),
|
||||
},
|
||||
accesscontrol.ResourceAuthorizerOptions{
|
||||
Resource: iamv0.TeamResourceInfo.GetName(),
|
||||
Resource: legacyiamv0.TeamResourceInfo.GetName(),
|
||||
Attr: "id",
|
||||
Resolver: accesscontrol.ResourceResolverFunc(func(ctx context.Context, ns authlib.NamespaceInfo, name string) ([]string, error) {
|
||||
res, err := store.GetTeamInternalID(ctx, ns, legacy.GetTeamInternalIDQuery{
|
||||
@@ -69,5 +104,5 @@ func newLegacyAuthorizer(ac accesscontrol.AccessControl, store legacy.LegacyIden
|
||||
},
|
||||
)
|
||||
|
||||
return gfauthorizer.NewResourceAuthorizer(client), client
|
||||
return client
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package iam
|
||||
|
||||
import (
|
||||
"github.com/grafana/authlib/types"
|
||||
"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)
|
||||
var _ builder.APIGroupRouteProvider = (*IdentityAccessManagementAPIBuilder)(nil)
|
||||
|
||||
// CoreRoleStorageBackend uses the resource.StorageBackend interface to provide storage for core roles.
|
||||
// Used wire to identify the storage backend for core roles.
|
||||
type CoreRoleStorageBackend interface{ resource.StorageBackend }
|
||||
|
||||
// This is used just so wire has something unique to return
|
||||
type IdentityAccessManagementAPIBuilder struct {
|
||||
// Stores
|
||||
store legacy.LegacyIdentityStore
|
||||
coreRolesStorage CoreRoleStorageBackend
|
||||
|
||||
// Access Control
|
||||
authorizer authorizer.Authorizer
|
||||
// legacyAccessClient is used for the identity apis, we need to migrate to the access client
|
||||
legacyAccessClient types.AccessClient
|
||||
// accessClient is used for the core role apis
|
||||
accessClient types.AccessClient
|
||||
|
||||
reg prometheus.Registerer
|
||||
|
||||
// non-k8s api route
|
||||
display *user.LegacyDisplayREST
|
||||
|
||||
// Not set for multi-tenant deployment for now
|
||||
sso ssosettings.Service
|
||||
|
||||
// Toggle for enabling authz management apis
|
||||
enableAuthZApis bool
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package noopstorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
|
||||
)
|
||||
|
||||
var (
|
||||
_ resource.StorageBackend = &StorageBackendImpl{}
|
||||
|
||||
errNoopStorage = errors.New("unavailable functionality")
|
||||
)
|
||||
|
||||
type StorageBackendImpl struct{}
|
||||
|
||||
func ProvideStorageBackend() *StorageBackendImpl {
|
||||
return &StorageBackendImpl{}
|
||||
}
|
||||
|
||||
// GetResourceStats implements resource.StorageBackend.
|
||||
func (c *StorageBackendImpl) GetResourceStats(ctx context.Context, namespace string, minCount int) ([]resource.ResourceStats, error) {
|
||||
return []resource.ResourceStats{}, errNoopStorage
|
||||
}
|
||||
|
||||
// ListHistory implements resource.StorageBackend.
|
||||
func (c *StorageBackendImpl) ListHistory(context.Context, *resourcepb.ListRequest, func(resource.ListIterator) error) (int64, error) {
|
||||
return 0, errNoopStorage
|
||||
}
|
||||
|
||||
// ListIterator implements resource.StorageBackend.
|
||||
func (c *StorageBackendImpl) ListIterator(context.Context, *resourcepb.ListRequest, func(resource.ListIterator) error) (int64, error) {
|
||||
return 0, errNoopStorage
|
||||
}
|
||||
|
||||
// ReadResource implements resource.StorageBackend.
|
||||
func (c *StorageBackendImpl) ReadResource(_ context.Context, req *resourcepb.ReadRequest) *resource.BackendReadResponse {
|
||||
return &resource.BackendReadResponse{
|
||||
Key: req.GetKey(),
|
||||
Error: &resourcepb.ErrorResult{Code: http.StatusForbidden, Message: errNoopStorage.Error()},
|
||||
}
|
||||
}
|
||||
|
||||
// WatchWriteEvents implements resource.StorageBackend.
|
||||
func (c *StorageBackendImpl) WatchWriteEvents(ctx context.Context) (<-chan *resource.WrittenEvent, error) {
|
||||
stream := make(chan *resource.WrittenEvent, 10)
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
// WriteEvent implements resource.StorageBackend.
|
||||
func (c *StorageBackendImpl) WriteEvent(context.Context, resource.WriteEvent) (int64, error) {
|
||||
return 0, errNoopStorage
|
||||
}
|
||||
@@ -4,10 +4,12 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
common "k8s.io/kube-openapi/pkg/common"
|
||||
@@ -15,8 +17,12 @@ 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"
|
||||
iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
legacyiamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
|
||||
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||
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/serviceaccount"
|
||||
@@ -25,41 +31,37 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam/user"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/ssosettings"
|
||||
"github.com/grafana/grafana/pkg/storage/legacysql"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/apistore"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
)
|
||||
|
||||
var _ builder.APIGroupBuilder = (*IdentityAccessManagementAPIBuilder)(nil)
|
||||
var _ builder.APIGroupRouteProvider = (*IdentityAccessManagementAPIBuilder)(nil)
|
||||
|
||||
// This is used just so wire has something unique to return
|
||||
type IdentityAccessManagementAPIBuilder struct {
|
||||
store legacy.LegacyIdentityStore
|
||||
authorizer authorizer.Authorizer
|
||||
accessClient types.AccessClient
|
||||
|
||||
// non-k8s api route
|
||||
display *user.LegacyDisplayREST
|
||||
|
||||
// Not set for multi-tenant deployment for now
|
||||
sso ssosettings.Service
|
||||
}
|
||||
|
||||
func RegisterAPIService(
|
||||
features featuremgmt.FeatureToggles,
|
||||
apiregistration builder.APIRegistrar,
|
||||
ssoService ssosettings.Service,
|
||||
sql db.DB,
|
||||
ac accesscontrol.AccessControl,
|
||||
accessClient types.AccessClient,
|
||||
reg prometheus.Registerer,
|
||||
coreRolesStorage CoreRoleStorageBackend,
|
||||
) (*IdentityAccessManagementAPIBuilder, error) {
|
||||
store := legacy.NewLegacySQLStores(legacysql.NewDatabaseProvider(sql))
|
||||
authorizer, client := newLegacyAuthorizer(ac, store)
|
||||
legacyAccessClient := newLegacyAccessClient(ac, store)
|
||||
authorizer := newIAMAuthorizer(accessClient, legacyAccessClient)
|
||||
|
||||
builder := &IdentityAccessManagementAPIBuilder{
|
||||
store: store,
|
||||
sso: ssoService,
|
||||
authorizer: authorizer,
|
||||
accessClient: client,
|
||||
display: user.NewLegacyDisplayREST(store),
|
||||
store: store,
|
||||
coreRolesStorage: coreRolesStorage,
|
||||
sso: ssoService,
|
||||
authorizer: authorizer,
|
||||
legacyAccessClient: legacyAccessClient,
|
||||
accessClient: accessClient,
|
||||
display: user.NewLegacyDisplayREST(store),
|
||||
reg: reg,
|
||||
enableAuthZApis: features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzApis),
|
||||
}
|
||||
apiregistration.RegisterAPI(builder)
|
||||
|
||||
@@ -85,54 +87,80 @@ func NewAPIService(store legacy.LegacyIdentityStore) *IdentityAccessManagementAP
|
||||
}
|
||||
|
||||
func (b *IdentityAccessManagementAPIBuilder) GetGroupVersion() schema.GroupVersion {
|
||||
return iamv0.SchemeGroupVersion
|
||||
return legacyiamv0.SchemeGroupVersion
|
||||
}
|
||||
|
||||
func (b *IdentityAccessManagementAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
|
||||
iamv0.AddKnownTypes(scheme, iamv0.VERSION)
|
||||
if b.enableAuthZApis {
|
||||
if err := iamv0.AddToScheme(scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
legacyiamv0.AddKnownTypes(scheme, legacyiamv0.VERSION)
|
||||
|
||||
// Link this version to the internal representation.
|
||||
// This is used for server-side-apply (PATCH), and avoids the error:
|
||||
// "no kind is registered for the type"
|
||||
iamv0.AddKnownTypes(scheme, runtime.APIVersionInternal)
|
||||
legacyiamv0.AddKnownTypes(scheme, runtime.APIVersionInternal)
|
||||
|
||||
metav1.AddToGroupVersion(scheme, iamv0.SchemeGroupVersion)
|
||||
return scheme.SetVersionPriority(iamv0.SchemeGroupVersion)
|
||||
metav1.AddToGroupVersion(scheme, legacyiamv0.SchemeGroupVersion)
|
||||
return scheme.SetVersionPriority(legacyiamv0.SchemeGroupVersion)
|
||||
}
|
||||
|
||||
func (b *IdentityAccessManagementAPIBuilder) AllowedV0Alpha1Resources() []string {
|
||||
return []string{builder.AllResourcesAllowed}
|
||||
}
|
||||
|
||||
func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, _ builder.APIGroupOptions) error {
|
||||
func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error {
|
||||
storage := map[string]rest.Storage{}
|
||||
|
||||
teamResource := iamv0.TeamResourceInfo
|
||||
storage[teamResource.StoragePath()] = team.NewLegacyStore(b.store, b.accessClient)
|
||||
teamResource := legacyiamv0.TeamResourceInfo
|
||||
storage[teamResource.StoragePath()] = team.NewLegacyStore(b.store, b.legacyAccessClient)
|
||||
storage[teamResource.StoragePath("members")] = team.NewLegacyTeamMemberREST(b.store)
|
||||
|
||||
teamBindingResource := iamv0.TeamBindingResourceInfo
|
||||
teamBindingResource := legacyiamv0.TeamBindingResourceInfo
|
||||
storage[teamBindingResource.StoragePath()] = team.NewLegacyBindingStore(b.store)
|
||||
|
||||
userResource := iamv0.UserResourceInfo
|
||||
storage[userResource.StoragePath()] = user.NewLegacyStore(b.store, b.accessClient)
|
||||
userResource := legacyiamv0.UserResourceInfo
|
||||
storage[userResource.StoragePath()] = user.NewLegacyStore(b.store, b.legacyAccessClient)
|
||||
storage[userResource.StoragePath("teams")] = user.NewLegacyTeamMemberREST(b.store)
|
||||
|
||||
serviceAccountResource := iamv0.ServiceAccountResourceInfo
|
||||
storage[serviceAccountResource.StoragePath()] = serviceaccount.NewLegacyStore(b.store, b.accessClient)
|
||||
serviceAccountResource := legacyiamv0.ServiceAccountResourceInfo
|
||||
storage[serviceAccountResource.StoragePath()] = serviceaccount.NewLegacyStore(b.store, b.legacyAccessClient)
|
||||
storage[serviceAccountResource.StoragePath("tokens")] = serviceaccount.NewLegacyTokenREST(b.store)
|
||||
|
||||
if b.sso != nil {
|
||||
ssoResource := iamv0.SSOSettingResourceInfo
|
||||
ssoResource := legacyiamv0.SSOSettingResourceInfo
|
||||
storage[ssoResource.StoragePath()] = sso.NewLegacyStore(b.sso)
|
||||
}
|
||||
|
||||
apiGroupInfo.VersionedResourcesStorageMap[iamv0.VERSION] = storage
|
||||
if b.enableAuthZApis {
|
||||
// v0alpha1
|
||||
store, err := NewLocalStore(iamv0.CoreRoleInfo, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.coreRolesStorage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storage[iamv0.CoreRoleInfo.StoragePath()] = store
|
||||
}
|
||||
|
||||
apiGroupInfo.VersionedResourcesStorageMap[legacyiamv0.VERSION] = storage
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *IdentityAccessManagementAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
|
||||
return iamv0.GetOpenAPIDefinitions
|
||||
defs := legacyiamv0.GetOpenAPIDefinitions
|
||||
if b.enableAuthZApis {
|
||||
defs = func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
|
||||
def1 := legacyiamv0.GetOpenAPIDefinitions(ref)
|
||||
def2 := iamv0.GetOpenAPIDefinitions(ref)
|
||||
for k, v := range def2 {
|
||||
def1[k] = v
|
||||
}
|
||||
return def1
|
||||
}
|
||||
}
|
||||
return defs
|
||||
}
|
||||
|
||||
func (b *IdentityAccessManagementAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
||||
@@ -195,3 +223,25 @@ func (b *IdentityAccessManagementAPIBuilder) GetAPIRoutes(gv schema.GroupVersion
|
||||
func (b *IdentityAccessManagementAPIBuilder) GetAuthorizer() authorizer.Authorizer {
|
||||
return b.authorizer
|
||||
}
|
||||
|
||||
func NewLocalStore(resourceInfo utils.ResourceInfo, scheme *runtime.Scheme, defaultOptsGetter generic.RESTOptionsGetter,
|
||||
reg prometheus.Registerer, ac types.AccessClient, storageBackend resource.StorageBackend) (grafanarest.Storage, error) {
|
||||
server, err := resource.NewResourceServer(resource.ResourceServerOptions{
|
||||
Backend: storageBackend,
|
||||
Reg: reg,
|
||||
AccessClient: ac,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defaultOpts, err := defaultOptsGetter.GetRESTOptions(resourceInfo.GroupResource(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := resource.NewLocalResourceClient(server)
|
||||
optsGetter := apistore.NewRESTOptionsGetterForClient(client, defaultOpts.StorageConfig.Config, nil)
|
||||
|
||||
store, err := grafanaregistry.NewRegistryStore(scheme, resourceInfo, optsGetter)
|
||||
return store, err
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/featuretoggle"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/folders"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam/noopstorage"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/webhooks"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/query"
|
||||
@@ -30,6 +31,12 @@ var ProvisioningExtras = wire.NewSet(
|
||||
MergeProvisioningExtras,
|
||||
)
|
||||
|
||||
// WireSetExts is a set of providers that can be overridden by enterprise implementations.
|
||||
var WireSetExts = wire.NewSet(
|
||||
noopstorage.ProvideStorageBackend,
|
||||
wire.Bind(new(iam.CoreRoleStorageBackend), new(*noopstorage.StorageBackendImpl)),
|
||||
)
|
||||
|
||||
var WireSet = wire.NewSet(
|
||||
ProvideRegistryServiceSink, // dummy background service that forces registration
|
||||
|
||||
@@ -44,6 +51,7 @@ var WireSet = wire.NewSet(
|
||||
featuretoggle.RegisterAPIService,
|
||||
datasource.RegisterAPIService,
|
||||
folders.RegisterAPIService,
|
||||
WireSetExts, // this will be moved to wireexts_oss.go in a following PR
|
||||
iam.RegisterAPIService,
|
||||
ProvisioningExtras,
|
||||
provisioning.RegisterAPIService,
|
||||
|
||||
@@ -102,7 +102,8 @@ func NewMapperRegistry() MapperRegistry {
|
||||
"folders": newResourceTranslation("folders", "uid", true),
|
||||
},
|
||||
"iam.grafana.app": {
|
||||
"teams": newResourceTranslation("teams", "id", false),
|
||||
"teams": newResourceTranslation("teams", "id", false),
|
||||
"coreroles": newResourceTranslation("roles", "uid", false),
|
||||
},
|
||||
"secret.grafana.app": {
|
||||
"securevalues": newResourceTranslation("secret.securevalues", "uid", false),
|
||||
|
||||
Reference in New Issue
Block a user