From 3993d691f43244c0532a854fee3133ed8072c02e Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Thu, 23 Jan 2025 17:00:02 +0100 Subject: [PATCH] Advisor: Implement authorizer (#99440) --- apps/advisor/pkg/app/authorizer.go | 31 ++++++++++ apps/advisor/pkg/app/authorizer_test.go | 78 +++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- pkg/registry/apps/advisor/register.go | 1 + 5 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 apps/advisor/pkg/app/authorizer.go create mode 100644 apps/advisor/pkg/app/authorizer_test.go diff --git a/apps/advisor/pkg/app/authorizer.go b/apps/advisor/pkg/app/authorizer.go new file mode 100644 index 00000000000..67defbc85d9 --- /dev/null +++ b/apps/advisor/pkg/app/authorizer.go @@ -0,0 +1,31 @@ +package app + +import ( + "context" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "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 + } + + // require a user + u, err := identity.GetRequester(ctx) + if err != nil { + return authorizer.DecisionDeny, "valid user is required", err + } + + // check if is admin + if u.GetIsGrafanaAdmin() { + return authorizer.DecisionAllow, "", nil + } + + return authorizer.DecisionDeny, "forbidden", nil + }) +} diff --git a/apps/advisor/pkg/app/authorizer_test.go b/apps/advisor/pkg/app/authorizer_test.go new file mode 100644 index 00000000000..84414362fbd --- /dev/null +++ b/apps/advisor/pkg/app/authorizer_test.go @@ -0,0 +1,78 @@ +package app + +import ( + "context" + "testing" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "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 is admin", + ctx: identity.WithRequester(context.TODO(), &mockUser{isGrafanaAdmin: true}), + attr: &mockAttributes{resourceRequest: true}, + expectedDecision: authorizer.DecisionAllow, + expectedReason: "", + expectedErr: nil, + }, + { + name: "user is not admin", + ctx: identity.WithRequester(context.TODO(), &mockUser{isGrafanaAdmin: false}), + attr: &mockAttributes{resourceRequest: true}, + expectedDecision: authorizer.DecisionDeny, + expectedReason: "forbidden", + 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 + isGrafanaAdmin bool +} + +func (m *mockUser) GetIsGrafanaAdmin() bool { + return m.isGrafanaAdmin +} + +// Implement other methods of identity.Requester as needed diff --git a/go.mod b/go.mod index d57b3cb3d61..d0282c33045 100644 --- a/go.mod +++ b/go.mod @@ -198,7 +198,7 @@ require ( ) require ( - github.com/grafana/grafana/apps/advisor v0.0.0-20250121115006-c1eac9f9973f // @grafana/plugins-platform-backend + github.com/grafana/grafana/apps/advisor v0.0.0-20250123151950-b066a6313173 // @grafana/plugins-platform-backend github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250121113133-e747350fee2d // @grafana/alerting-backend github.com/grafana/grafana/apps/investigation v0.0.0-20250121113133-e747350fee2d // @fcjack @matryer github.com/grafana/grafana/apps/playlist v0.0.0-20250121113133-e747350fee2d // @grafana/grafana-app-platform-squad diff --git a/go.sum b/go.sum index 166125e556b..7285649b56c 100644 --- a/go.sum +++ b/go.sum @@ -1532,8 +1532,8 @@ github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ= github.com/grafana/grafana-plugin-sdk-go v0.262.0 h1:R2DV6lwBQE5zaogxX3PorD9Seo8CXA8YuStf84oqwkk= github.com/grafana/grafana-plugin-sdk-go v0.262.0/go.mod h1:U43Cnrj/9DNYyvFcNdeUWNjMXTKNB0jcTcQGpWKd2gw= -github.com/grafana/grafana/apps/advisor v0.0.0-20250121115006-c1eac9f9973f h1:c8IkbxPvM6+lscVOLgtbt8Gnro4Liltd+E2eqkoAeZA= -github.com/grafana/grafana/apps/advisor v0.0.0-20250121115006-c1eac9f9973f/go.mod h1:goSDiy3jtC2cp8wjpPZdUHRENcoSUHae1/Px/MDfddA= +github.com/grafana/grafana/apps/advisor v0.0.0-20250123151950-b066a6313173 h1:uOM89HiWVVOTls0LrD4coHTckb2lA4U0sIJwCYdbhbw= +github.com/grafana/grafana/apps/advisor v0.0.0-20250123151950-b066a6313173/go.mod h1:goSDiy3jtC2cp8wjpPZdUHRENcoSUHae1/Px/MDfddA= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250121113133-e747350fee2d h1:NRVOtiG1aUwOazBj9KM7X2o2shsM6TchqisezzoH1gw= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250121113133-e747350fee2d/go.mod h1:AvleS6icyPmcBjihtx5jYEvdzLmHGBp66NuE0AMR57A= github.com/grafana/grafana/apps/investigation v0.0.0-20250121113133-e747350fee2d h1:oNc/aDfDucQxLbRZK25yz3Cwc+dGo1C0Xmm2LaliWUQ= diff --git a/pkg/registry/apps/advisor/register.go b/pkg/registry/apps/advisor/register.go index a680dcc0880..c989d82262e 100644 --- a/pkg/registry/apps/advisor/register.go +++ b/pkg/registry/apps/advisor/register.go @@ -21,6 +21,7 @@ func RegisterApp( appCfg := &runner.AppBuilderConfig{ OpenAPIDefGetter: advisorv0alpha1.GetOpenAPIDefinitions, ManagedKinds: advisorapp.GetKinds(), + Authorizer: advisorapp.GetAuthorizer(), CustomConfig: any(checkRegistry), } provider.Provider = simple.NewAppProvider(apis.LocalManifest(), appCfg, advisorapp.New)