The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/pkg/registry/apis/iam/register.go

248 lines
8.8 KiB

package iam
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"
"k8s.io/kube-openapi/pkg/spec3"
"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"
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"
"github.com/grafana/grafana/pkg/registry/apis/iam/sso"
"github.com/grafana/grafana/pkg/registry/apis/iam/team"
"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"
)
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))
legacyAccessClient := newLegacyAccessClient(ac, store)
authorizer := newIAMAuthorizer(accessClient, legacyAccessClient)
builder := &IdentityAccessManagementAPIBuilder{
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)
return builder, nil
}
func NewAPIService(store legacy.LegacyIdentityStore) *IdentityAccessManagementAPIBuilder {
return &IdentityAccessManagementAPIBuilder{
store: store,
display: user.NewLegacyDisplayREST(store),
authorizer: authorizer.AuthorizerFunc(
func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
user, err := identity.GetRequester(ctx)
if err != nil {
return authorizer.DecisionDeny, "no identity found", err
}
if user.GetIsGrafanaAdmin() {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "only grafana admins have access for now", nil
}),
}
}
func (b *IdentityAccessManagementAPIBuilder) GetGroupVersion() schema.GroupVersion {
return legacyiamv0.SchemeGroupVersion
}
func (b *IdentityAccessManagementAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
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"
legacyiamv0.AddKnownTypes(scheme, runtime.APIVersionInternal)
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, opts builder.APIGroupOptions) error {
storage := map[string]rest.Storage{}
teamResource := legacyiamv0.TeamResourceInfo
storage[teamResource.StoragePath()] = team.NewLegacyStore(b.store, b.legacyAccessClient)
storage[teamResource.StoragePath("members")] = team.NewLegacyTeamMemberREST(b.store)
teamBindingResource := legacyiamv0.TeamBindingResourceInfo
storage[teamBindingResource.StoragePath()] = team.NewLegacyBindingStore(b.store)
userResource := legacyiamv0.UserResourceInfo
storage[userResource.StoragePath()] = user.NewLegacyStore(b.store, b.legacyAccessClient)
storage[userResource.StoragePath("teams")] = user.NewLegacyTeamMemberREST(b.store)
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 := legacyiamv0.SSOSettingResourceInfo
storage[ssoResource.StoragePath()] = sso.NewLegacyStore(b.sso)
}
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 {
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) {
oas.Info.Description = "Identity and Access Management"
defs := b.GetOpenAPIDefinitions()(func(path string) spec.Ref { return spec.Ref{} })
defsBase := "github.com/grafana/grafana/pkg/apis/iam/v0alpha1."
// Add missing schemas
for k, v := range defs {
clean := strings.Replace(k, defsBase, "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.", 1)
if oas.Components.Schemas[clean] == nil {
oas.Components.Schemas[clean] = &v.Schema
}
}
compBase := "com.github.grafana.grafana.pkg.apis.iam.v0alpha1."
schema := oas.Components.Schemas[compBase+"DisplayList"].Properties["display"]
schema.Items = &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
AllOf: []spec.Schema{
{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef("#/components/schemas/" + compBase + "Display"),
},
},
},
},
},
}
oas.Components.Schemas[compBase+"DisplayList"].Properties["display"] = schema
oas.Components.Schemas[compBase+"DisplayList"].Properties["metadata"] = spec.Schema{
SchemaProps: spec.SchemaProps{
AllOf: []spec.Schema{
{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef("#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta"),
},
},
}},
}
oas.Components.Schemas[compBase+"Display"].Properties["identity"] = spec.Schema{
SchemaProps: spec.SchemaProps{
AllOf: []spec.Schema{
{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef("#/components/schemas/" + compBase + "IdentityRef"),
},
},
}},
}
return oas, nil
}
func (b *IdentityAccessManagementAPIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.APIRoutes {
defs := b.GetOpenAPIDefinitions()(func(path string) spec.Ref { return spec.Ref{} })
return b.display.GetAPIRoutes(defs)
}
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
}