diff --git a/apps/investigations/pkg/app/authorizer.go b/apps/investigations/pkg/app/authorizer.go new file mode 100644 index 00000000000..0747b06da5e --- /dev/null +++ b/apps/investigations/pkg/app/authorizer.go @@ -0,0 +1,38 @@ +package app + +import ( + "context" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/accesscontrol" + + "k8s.io/apiserver/pkg/authorization/authorizer" +) + +func GetAuthorizer() authorizer.Authorizer { + return authorizer.AuthorizerFunc(func( + ctx context.Context, attr authorizer.Attributes, + ) (authorized authorizer.Decision, reason string, err error) { + if !attr.IsResourceRequest() { + return authorizer.DecisionNoOpinion, "", nil + } + + u, err := identity.GetRequester(ctx) + if err != nil { + return authorizer.DecisionDeny, "valid user is required", err + } + + p := u.GetPermissions() + if len(p) == 0 { + return authorizer.DecisionDeny, "no permissions", nil + } + + _, ok := p[accesscontrol.ActionDatasourcesExplore] + if !ok { + // defer to the default authorizer if datasources:explore is not present + return authorizer.DecisionNoOpinion, "", nil + } + + return authorizer.DecisionAllow, "", nil + }) +} diff --git a/apps/investigations/pkg/app/authorizer_test.go b/apps/investigations/pkg/app/authorizer_test.go new file mode 100644 index 00000000000..9829fb84793 --- /dev/null +++ b/apps/investigations/pkg/app/authorizer_test.go @@ -0,0 +1,87 @@ +package app + +import ( + "context" + "testing" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/stretchr/testify/assert" + "k8s.io/apiserver/pkg/authorization/authorizer" +) + +func TestGetAuthorizer(t *testing.T) { + tests := []struct { + name string + ctx context.Context + attr authorizer.Attributes + expectedDecision authorizer.Decision + expectedReason string + expectedErr error + }{ + { + name: "non-resource request", + ctx: context.TODO(), + attr: &mockAttributes{resourceRequest: false}, + expectedDecision: authorizer.DecisionNoOpinion, + expectedReason: "", + expectedErr: nil, + }, + { + name: "user has datasources:explore permission", + ctx: identity.WithRequester(context.TODO(), &mockUser{permissions: map[string][]string{accesscontrol.ActionDatasourcesExplore: {}}}), + attr: &mockAttributes{resourceRequest: true}, + expectedDecision: authorizer.DecisionAllow, + expectedReason: "", + expectedErr: nil, + }, + { + name: "user does not have datasources:explore permission", + ctx: identity.WithRequester(context.TODO(), &mockUser{}), + attr: &mockAttributes{resourceRequest: true}, + expectedDecision: authorizer.DecisionDeny, + expectedReason: "no permissions", + expectedErr: nil, + }, + { + name: "user does not have datasources:explore permission", + ctx: identity.WithRequester(context.TODO(), &mockUser{permissions: map[string][]string{"foo": {}}}), + attr: &mockAttributes{resourceRequest: true}, + expectedDecision: authorizer.DecisionNoOpinion, + expectedReason: "", + expectedErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + auth := GetAuthorizer() + decision, reason, err := auth.Authorize(tt.ctx, tt.attr) + assert.Equal(t, tt.expectedDecision, decision) + assert.Equal(t, tt.expectedReason, reason) + assert.Equal(t, tt.expectedErr, err) + }) + } +} + +type mockAttributes struct { + authorizer.Attributes + resourceRequest bool +} + +func (m *mockAttributes) IsResourceRequest() bool { + return m.resourceRequest +} + +// Implement other methods of authorizer.Attributes as needed + +type mockUser struct { + identity.Requester + permissions map[string][]string +} + +func (m *mockUser) GetPermissions() map[string][]string { + return m.permissions +} + +// Implement other methods of identity.Requester as needed diff --git a/apps/investigations/pkg/app/investigations_app.go b/apps/investigations/pkg/app/investigations_app.go index 8ec35c1ed19..3155eb88404 100644 --- a/apps/investigations/pkg/app/investigations_app.go +++ b/apps/investigations/pkg/app/investigations_app.go @@ -18,7 +18,7 @@ func New(cfg app.Config) (app.App, error) { Name: "investigation", KubeConfig: cfg.KubeConfig, InformerConfig: simple.AppInformerConfig{ - ErrorHandler: func(ctx context.Context, err error) { + ErrorHandler: func(_ context.Context, err error) { klog.ErrorS(err, "Informer processing error") }, }, diff --git a/pkg/registry/apps/investigations/register.go b/pkg/registry/apps/investigations/register.go index 83afeee6fc1..ea4a2fac98e 100644 --- a/pkg/registry/apps/investigations/register.go +++ b/pkg/registry/apps/investigations/register.go @@ -24,6 +24,7 @@ func RegisterApp( appCfg := &runner.AppBuilderConfig{ OpenAPIDefGetter: investigationv0alpha1.GetOpenAPIDefinitions, ManagedKinds: investigationapp.GetKinds(), + Authorizer: investigationapp.GetAuthorizer(), } provider.Provider = simple.NewAppProvider(apis.LocalManifest(), appCfg, investigationapp.New) return provider