Auditing: Refactor policy rule provider and add default policy rule evaluator (#115318)
* Auditing: Add policy rule provider to fix wiring * Auditing: Add default policy rule evaluator for APIs
This commit is contained in:
@@ -19,11 +19,18 @@ func (NoopBackend) Shutdown() {}
|
||||
|
||||
func (NoopBackend) String() string { return "" }
|
||||
|
||||
// NoopPolicyRuleProvider is a no-op implementation of PolicyRuleProvider
|
||||
type NoopPolicyRuleProvider struct{}
|
||||
|
||||
func ProvideNoopPolicyRuleProvider() PolicyRuleProvider { return &NoopPolicyRuleProvider{} }
|
||||
|
||||
func (NoopPolicyRuleProvider) PolicyRuleProvider(PolicyRuleEvaluators) audit.PolicyRuleEvaluator {
|
||||
return NoopPolicyRuleEvaluator{}
|
||||
}
|
||||
|
||||
// NoopPolicyRuleEvaluator is a no-op implementation of audit.PolicyRuleEvaluator
|
||||
type NoopPolicyRuleEvaluator struct{}
|
||||
|
||||
func ProvideNoopPolicyRuleEvaluator() audit.PolicyRuleEvaluator { return &NoopPolicyRuleEvaluator{} }
|
||||
|
||||
func (NoopPolicyRuleEvaluator) EvaluatePolicyRule(authorizer.Attributes) audit.RequestAuditConfig {
|
||||
return audit.RequestAuditConfig{Level: auditinternal.LevelNone}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package auditing
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// PolicyRuleEvaluators is a map of API group+version to audit.PolicyRuleEvaluator
|
||||
type PolicyRuleEvaluators = map[schema.GroupVersion]audit.PolicyRuleEvaluator
|
||||
|
||||
type PolicyRuleProvider interface {
|
||||
PolicyRuleProvider(evaluators PolicyRuleEvaluators) audit.PolicyRuleEvaluator
|
||||
}
|
||||
|
||||
// PolicyRuleEvaluator alias for easier imports.
|
||||
type PolicyRuleEvaluator = audit.PolicyRuleEvaluator
|
||||
|
||||
// DefaultGrafanaPolicyRuleEvaluator provides a sane default configuration for audit logging for API group+versions.
|
||||
type defaultGrafanaPolicyRuleEvaluator struct{}
|
||||
|
||||
var _ PolicyRuleEvaluator = &defaultGrafanaPolicyRuleEvaluator{}
|
||||
|
||||
func NewDefaultGrafanaPolicyRuleEvaluator() audit.PolicyRuleEvaluator {
|
||||
return defaultGrafanaPolicyRuleEvaluator{}
|
||||
}
|
||||
|
||||
func (defaultGrafanaPolicyRuleEvaluator) EvaluatePolicyRule(attrs authorizer.Attributes) audit.RequestAuditConfig {
|
||||
// Skip non-resource and watch requests otherwise it is too noisy.
|
||||
if !attrs.IsResourceRequest() || attrs.GetVerb() == utils.VerbWatch {
|
||||
return audit.RequestAuditConfig{
|
||||
Level: auditinternal.LevelNone,
|
||||
}
|
||||
}
|
||||
|
||||
// Skip auditing if the user is part of the privileged group.
|
||||
// The loopback client uses this group, so requests initiated in `/api/` would be duplicated.
|
||||
if u := attrs.GetUser(); u != nil && slices.Contains(u.GetGroups(), user.SystemPrivilegedGroup) {
|
||||
return audit.RequestAuditConfig{
|
||||
Level: auditinternal.LevelNone,
|
||||
}
|
||||
}
|
||||
|
||||
return audit.RequestAuditConfig{
|
||||
Level: auditinternal.LevelMetadata,
|
||||
OmitStages: []auditinternal.Stage{
|
||||
// Only log on StageResponseComplete
|
||||
auditinternal.StageRequestReceived,
|
||||
auditinternal.StageResponseStarted,
|
||||
auditinternal.StagePanic,
|
||||
},
|
||||
OmitManagedFields: false, // Setting it to true causes extra copying/unmarshalling.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package auditing_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/apiserver/auditing"
|
||||
"github.com/stretchr/testify/require"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
func TestDefaultGrafanaPolicyRuleEvaluator(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
evaluator := auditing.NewDefaultGrafanaPolicyRuleEvaluator()
|
||||
require.NotNil(t, evaluator)
|
||||
|
||||
t.Run("returns audit level none for non-resource requests", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
attrs := authorizer.AttributesRecord{
|
||||
ResourceRequest: false,
|
||||
}
|
||||
|
||||
config := evaluator.EvaluatePolicyRule(attrs)
|
||||
require.Equal(t, auditinternal.LevelNone, config.Level)
|
||||
})
|
||||
|
||||
t.Run("returns audit level none for watch requests", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
attrs := authorizer.AttributesRecord{
|
||||
ResourceRequest: true,
|
||||
Verb: utils.VerbWatch,
|
||||
}
|
||||
|
||||
config := evaluator.EvaluatePolicyRule(attrs)
|
||||
require.Equal(t, auditinternal.LevelNone, config.Level)
|
||||
})
|
||||
|
||||
t.Run("returns audit level none for requests from privileged group", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
attrs := authorizer.AttributesRecord{
|
||||
ResourceRequest: true,
|
||||
Verb: utils.VerbCreate,
|
||||
User: &user.DefaultInfo{
|
||||
Groups: []string{"test-group", user.SystemPrivilegedGroup},
|
||||
},
|
||||
}
|
||||
|
||||
config := evaluator.EvaluatePolicyRule(attrs)
|
||||
require.Equal(t, auditinternal.LevelNone, config.Level)
|
||||
})
|
||||
|
||||
t.Run("return audit level metadata for other resource requests", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
attrs := authorizer.AttributesRecord{
|
||||
ResourceRequest: true,
|
||||
Verb: utils.VerbCreate,
|
||||
User: &user.DefaultInfo{
|
||||
Name: "test-user",
|
||||
Groups: []string{"test-group"},
|
||||
},
|
||||
}
|
||||
|
||||
config := evaluator.EvaluatePolicyRule(attrs)
|
||||
require.Equal(t, auditinternal.LevelMetadata, config.Level)
|
||||
})
|
||||
}
|
||||
@@ -37,7 +37,7 @@ var WireSetExts = wire.NewSet(
|
||||
|
||||
// Auditing Options
|
||||
auditing.ProvideNoopBackend,
|
||||
auditing.ProvideNoopPolicyRuleEvaluator,
|
||||
auditing.ProvideNoopPolicyRuleProvider,
|
||||
)
|
||||
|
||||
var provisioningExtras = wire.NewSet(
|
||||
|
||||
Generated
+4
-4
@@ -834,8 +834,8 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
|
||||
v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, appInstaller, shortURLAppInstaller, alertingRulesAppInstaller, correlationsAppInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller, alertingHistorianAppInstaller, quotasAppInstaller)
|
||||
builderMetrics := builder.ProvideBuilderMetrics(registerer)
|
||||
backend := auditing.ProvideNoopBackend()
|
||||
policyRuleEvaluator := auditing.ProvideNoopPolicyRuleEvaluator()
|
||||
apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics, backend, policyRuleEvaluator)
|
||||
policyRuleProvider := auditing.ProvideNoopPolicyRuleProvider()
|
||||
apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics, backend, policyRuleProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1495,8 +1495,8 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
|
||||
v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, appInstaller, shortURLAppInstaller, alertingRulesAppInstaller, correlationsAppInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller, alertingHistorianAppInstaller, quotasAppInstaller)
|
||||
builderMetrics := builder.ProvideBuilderMetrics(registerer)
|
||||
backend := auditing.ProvideNoopBackend()
|
||||
policyRuleEvaluator := auditing.ProvideNoopPolicyRuleEvaluator()
|
||||
apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics, backend, policyRuleEvaluator)
|
||||
policyRuleProvider := auditing.ProvideNoopPolicyRuleProvider()
|
||||
apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics, backend, policyRuleProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
@@ -59,6 +60,13 @@ type APIGroupAuthorizer interface {
|
||||
GetAuthorizer() authorizer.Authorizer
|
||||
}
|
||||
|
||||
// APIGroupAuditor allows different API groups to opt-in and provide their own auditing policy evaluator function.
|
||||
// Auditing is only enabled if this is implemented. If no customization is needed, you can use the default evaluator,
|
||||
// `pkg/apiserver/auditing.NewDefaultGrafanaPolicyRuleEvaluator()`.
|
||||
type APIGroupAuditor interface {
|
||||
GetPolicyRuleEvaluator() audit.PolicyRuleEvaluator
|
||||
}
|
||||
|
||||
type APIGroupMutation interface {
|
||||
// Mutate allows the builder to make changes to the object before it is persisted.
|
||||
// Context is used only for timeout/deadline/cancellation and tracing information.
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kube-openapi/pkg/common"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apiserver/auditing"
|
||||
"github.com/grafana/grafana/pkg/apiserver/endpoints/filters"
|
||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
@@ -497,6 +498,32 @@ func AddPostStartHooks(
|
||||
return nil
|
||||
}
|
||||
|
||||
func EvaluatorPolicyRuleFromBuilders(builders []APIGroupBuilder) auditing.PolicyRuleEvaluators {
|
||||
policyRuleEvaluators := make(auditing.PolicyRuleEvaluators, 0)
|
||||
|
||||
for _, b := range builders {
|
||||
auditor, ok := b.(APIGroupAuditor)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
policyRuleEvaluator := auditor.GetPolicyRuleEvaluator()
|
||||
if policyRuleEvaluator == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, gv := range GetGroupVersions(b) {
|
||||
if gv.Empty() {
|
||||
continue
|
||||
}
|
||||
|
||||
policyRuleEvaluators[gv] = policyRuleEvaluator
|
||||
}
|
||||
}
|
||||
|
||||
return policyRuleEvaluators
|
||||
}
|
||||
|
||||
func allowRegisteringResourceByInfo(allowedResources []string, name string) bool {
|
||||
// trim any subresources from the name
|
||||
name = strings.Split(name, "/")[0]
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
dataplaneaggregator "github.com/grafana/grafana/pkg/aggregator/apiserver"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apiserver/auditing"
|
||||
grafanaresponsewriter "github.com/grafana/grafana/pkg/apiserver/endpoints/responsewriter"
|
||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
@@ -115,8 +116,8 @@ type service struct {
|
||||
builderMetrics *builder.BuilderMetrics
|
||||
dualWriterMetrics *grafanarest.DualWriterMetrics
|
||||
|
||||
auditBackend audit.Backend
|
||||
auditPolicyRuleEvaluator audit.PolicyRuleEvaluator
|
||||
auditBackend audit.Backend
|
||||
auditPolicyRuleProvider auditing.PolicyRuleProvider
|
||||
}
|
||||
|
||||
func ProvideService(
|
||||
@@ -142,7 +143,7 @@ func ProvideService(
|
||||
appInstallers []appsdkapiserver.AppInstaller,
|
||||
builderMetrics *builder.BuilderMetrics,
|
||||
auditBackend audit.Backend,
|
||||
auditPolicyRuleEvaluator audit.PolicyRuleEvaluator,
|
||||
auditPolicyRuleProvider auditing.PolicyRuleProvider,
|
||||
) (*service, error) {
|
||||
scheme := builder.ProvideScheme()
|
||||
codecs := builder.ProvideCodecFactory(scheme)
|
||||
@@ -174,7 +175,7 @@ func ProvideService(
|
||||
builderMetrics: builderMetrics,
|
||||
dualWriterMetrics: grafanarest.NewDualWriterMetrics(reg),
|
||||
auditBackend: auditBackend,
|
||||
auditPolicyRuleEvaluator: auditPolicyRuleEvaluator,
|
||||
auditPolicyRuleProvider: auditPolicyRuleProvider,
|
||||
}
|
||||
// This will be used when running as a dskit service
|
||||
s.NamedService = services.NewBasicService(s.start, s.running, nil).WithName(modules.GrafanaAPIServer)
|
||||
@@ -365,7 +366,7 @@ func (s *service) start(ctx context.Context) error {
|
||||
|
||||
// Auditing Options
|
||||
serverConfig.AuditBackend = s.auditBackend
|
||||
serverConfig.AuditPolicyRuleEvaluator = s.auditPolicyRuleEvaluator
|
||||
serverConfig.AuditPolicyRuleEvaluator = s.auditPolicyRuleProvider.PolicyRuleProvider(builder.EvaluatorPolicyRuleFromBuilders(s.builders))
|
||||
|
||||
// Add OpenAPI specs for each group+version (existing builders)
|
||||
err = builder.SetupConfig(
|
||||
|
||||
Reference in New Issue
Block a user