Provisioning: allow access check to proceed even when non access policy (#112946)

* Provisioning: allow access check to proceed even when non access policy

* Provisioning: access checker needs this for MT

* add permissions registration

* remove scopes

* use in MT for now

* no need to document an internal flag here

* revert vscode change

* refactor the authZ permission evaluation and mapper code to allow evaluating unscoped actions beyond creation

* update wire

* gofmt

* add boolean to struct

---------

Co-authored-by: IevaVasiljeva <ieva.vasiljeva@grafana.com>
This commit is contained in:
Charandas
2025-11-02 13:14:08 -08:00
committed by GitHub
co-authored by IevaVasiljeva
parent 445e88cb93
commit 6c728f8dec
8 changed files with 268 additions and 65 deletions
+1
View File
@@ -30,6 +30,7 @@ func ProvideRegistryServiceSink(
_ *provisioning.APIBuilder,
_ *ofrep.APIBuilder,
_ *secret.DependencyRegisterer,
_ *provisioning.DependencyRegisterer,
) *Service {
return &Service{}
}
@@ -0,0 +1,129 @@
package provisioning
import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/org"
)
const (
// Repositories
ActionProvisioningRepositoriesCreate = "provisioning.repositories:create" // CREATE.
ActionProvisioningRepositoriesWrite = "provisioning.repositories:write" // UPDATE.
ActionProvisioningRepositoriesRead = "provisioning.repositories:read" // GET + LIST.
ActionProvisioningRepositoriesDelete = "provisioning.repositories:delete" // DELETE.
// Jobs
ActionProvisioningJobsCreate = "provisioning.jobs:create" // CREATE.
ActionProvisioningJobsWrite = "provisioning.jobs:write" // UPDATE.
ActionProvisioningJobsRead = "provisioning.jobs:read" // GET + LIST.
ActionProvisioningJobsDelete = "provisioning.jobs:delete" // DELETE.
// Historic Jobs
ActionProvisioningHistoricJobsRead = "provisioning.historicjobs:read" // GET + LIST.
)
func registerAccessControlRoles(service accesscontrol.Service) error {
// Repositories
repositoriesReader := accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: "fixed:provisioning.repositories:reader",
DisplayName: "Repositories Reader",
Description: "Read and list provisioning repositories.",
Group: "Provisioning",
Permissions: []accesscontrol.Permission{
{
Action: ActionProvisioningRepositoriesRead,
},
},
},
Grants: []string{string(org.RoleAdmin)},
}
repositoriesWriter := accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: "fixed:provisioning.repositories:writer",
DisplayName: "Repositories Writer",
Description: "Create, update and delete provisioning repositories.",
Group: "Provisioning",
Permissions: []accesscontrol.Permission{
{
Action: ActionProvisioningRepositoriesCreate,
},
{
Action: ActionProvisioningRepositoriesRead,
},
{
Action: ActionProvisioningRepositoriesWrite,
},
{
Action: ActionProvisioningRepositoriesDelete,
},
},
},
Grants: []string{string(org.RoleAdmin)},
}
// Jobs
jobsReader := accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: "fixed:provisioning.jobs:reader",
DisplayName: "Jobs Reader",
Description: "Read and list provisioning jobs.",
Group: "Provisioning",
Permissions: []accesscontrol.Permission{
{
Action: ActionProvisioningJobsRead,
},
},
},
Grants: []string{string(org.RoleAdmin)},
}
jobsWriter := accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: "fixed:provisioning.jobs:writer",
DisplayName: "Jobs Writer",
Description: "Create, update and delete provisioning jobs.",
Group: "Provisioning",
Permissions: []accesscontrol.Permission{
{
Action: ActionProvisioningJobsCreate,
},
{
Action: ActionProvisioningJobsRead,
},
{
Action: ActionProvisioningJobsWrite,
},
{
Action: ActionProvisioningJobsDelete,
},
},
},
Grants: []string{string(org.RoleAdmin)},
}
// Historic Jobs
historicJobsReader := accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: "fixed:provisioning.historicjobs:reader",
DisplayName: "Historic Jobs Reader",
Description: "Read and list provisioning historic jobs.",
Group: "Provisioning",
Permissions: []accesscontrol.Permission{
{
Action: ActionProvisioningHistoricJobsRead,
},
},
},
Grants: []string{string(org.RoleAdmin)},
}
return service.DeclareFixedRoles(
repositoriesReader,
repositoriesWriter,
jobsReader,
jobsWriter,
historicJobsReader,
)
}
@@ -0,0 +1,24 @@
package provisioning
import (
"fmt"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
)
// DependencyRegisterer is set to satisfy wire gen and make sure the `RegisterDependencies` is called.
type DependencyRegisterer struct{}
func RegisterDependencies(
cfg *setting.Cfg,
accessControlService accesscontrol.Service,
features featuremgmt.FeatureToggles,
) (*DependencyRegisterer, error) {
if err := registerAccessControlRoles(accessControlService); err != nil {
return nil, fmt.Errorf("registering access control roles: %w", err)
}
return &DependencyRegisterer{}, nil
}
+29 -22
View File
@@ -87,7 +87,8 @@ type APIBuilder struct {
// onlyApiServer used to disable starting controllers for the standalone API server.
// HACK:This will be removed once we have proper wire providers for the controllers.
// TODO: Set this up in the standalone API server
onlyApiServer bool
onlyApiServer bool
useExclusivelyAccessCheckerForAuthz bool
allowedTargets []provisioning.SyncTargetType
allowImageRendering bool
@@ -147,6 +148,7 @@ func NewAPIBuilder(
minSyncInterval time.Duration,
registry prometheus.Registerer,
newStandaloneClientFactoryFunc func(loopbackConfigProvider apiserver.RestConfigProvider) resources.ClientFactory, // optional, only used for standalone apiserver
useExclusivelyAccessCheckerForAuthz bool,
) *APIBuilder {
var clients resources.ClientFactory
if newStandaloneClientFactoryFunc != nil {
@@ -158,26 +160,27 @@ func NewAPIBuilder(
resourceLister := resources.NewResourceListerForMigrations(unified, legacyMigrator, storageStatus)
b := &APIBuilder{
onlyApiServer: onlyApiServer,
tracer: tracer,
usageStats: usageStats,
features: features,
repoFactory: repoFactory,
clients: clients,
parsers: parsers,
repositoryResources: resources.NewRepositoryResourcesFactory(parsers, clients, resourceLister),
resourceLister: resourceLister,
legacyMigrator: legacyMigrator,
storageStatus: storageStatus,
unified: unified,
access: access,
jobHistoryConfig: jobHistoryConfig,
extraWorkers: extraWorkers,
restConfigGetter: restConfigGetter,
allowedTargets: allowedTargets,
allowImageRendering: allowImageRendering,
registry: registry,
validator: repository.NewValidator(minSyncInterval, allowedTargets, allowImageRendering),
onlyApiServer: onlyApiServer,
tracer: tracer,
usageStats: usageStats,
features: features,
repoFactory: repoFactory,
clients: clients,
parsers: parsers,
repositoryResources: resources.NewRepositoryResourcesFactory(parsers, clients, resourceLister),
resourceLister: resourceLister,
legacyMigrator: legacyMigrator,
storageStatus: storageStatus,
unified: unified,
access: access,
jobHistoryConfig: jobHistoryConfig,
extraWorkers: extraWorkers,
restConfigGetter: restConfigGetter,
allowedTargets: allowedTargets,
allowImageRendering: allowImageRendering,
registry: registry,
validator: repository.NewValidator(minSyncInterval, allowedTargets, allowImageRendering),
useExclusivelyAccessCheckerForAuthz: useExclusivelyAccessCheckerForAuthz,
}
for _, builder := range extraBuilders {
@@ -267,6 +270,7 @@ func RegisterAPIService(
cfg.ProvisioningMinSyncInterval,
reg,
nil,
false, // TODO: first, test this on the MT side before we enable it by default in ST as well
)
apiregistration.RegisterAPI(builder)
return builder, nil
@@ -283,7 +287,9 @@ func (b *APIBuilder) GetAuthorizer() authorizer.Authorizer {
}
info, ok := authlib.AuthInfoFrom(ctx)
if ok && authlib.IsIdentityType(info.GetIdentityType(), authlib.TypeAccessPolicy) {
// when running as standalone API server, the identity type may not always match TypeAccessPolicy
// so we allow it to use the access checker if there is any auth info available
if ok && (authlib.IsIdentityType(info.GetIdentityType(), authlib.TypeAccessPolicy) || b.useExclusivelyAccessCheckerForAuthz) {
res, err := b.access.Check(ctx, info, authlib.CheckRequest{
Verb: a.GetVerb(),
Group: a.GetAPIGroup(),
@@ -291,6 +297,7 @@ func (b *APIBuilder) GetAuthorizer() authorizer.Authorizer {
Name: a.GetName(),
Namespace: a.GetNamespace(),
Subresource: a.GetSubresource(),
Path: a.GetPath(),
}, "")
if err != nil {
return authorizer.DecisionDeny, "failed to perform authorization", err
+1
View File
@@ -49,6 +49,7 @@ var WireSet = wire.NewSet(
// Secrets
secret.RegisterDependencies,
// Provisioning
provisioning.RegisterDependencies,
provisioningExtras,
// Each must be added here *and* in the ServiceSink above