mirror of
https://github.com/grafana/grafana.git
synced 2025-07-28 11:22:20 +08:00
RBAC: Cover plugin routes (#80578)
* RBAC: Cover plugin routes * Action instead of ReqAction * Fix test initializations * Fix NewPluginProxy call * Duplicate test to add RBAC checks * Cover legacy access control as well * Fix typo * action -> reqAction * Add example Co-authored-by: Andres Martinez Gotor <andres.martinez@grafana.com> --------- Co-authored-by: Andres Martinez Gotor <andres.martinez@grafana.com>
This commit is contained in:
@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
@ -262,7 +263,8 @@ func TestPluginProxy(t *testing.T) {
|
||||
ps := &pluginsettings.DTO{
|
||||
SecureJSONData: map[string][]byte{},
|
||||
}
|
||||
proxy, err := NewPluginProxy(ps, routes, ctx, "", &setting.Cfg{}, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, featuremgmt.WithFeatures())
|
||||
cfg := &setting.Cfg{}
|
||||
proxy, err := NewPluginProxy(ps, routes, ctx, "", cfg, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, acimpl.ProvideAccessControl(cfg), featuremgmt.WithFeatures())
|
||||
require.NoError(t, err)
|
||||
proxy.HandleRequest()
|
||||
|
||||
@ -400,7 +402,8 @@ func TestPluginProxyRoutes(t *testing.T) {
|
||||
ps := &pluginsettings.DTO{
|
||||
SecureJSONData: map[string][]byte{},
|
||||
}
|
||||
proxy, err := NewPluginProxy(ps, testRoutes, ctx, tc.proxyPath, &setting.Cfg{}, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, featuremgmt.WithFeatures())
|
||||
cfg := &setting.Cfg{}
|
||||
proxy, err := NewPluginProxy(ps, testRoutes, ctx, tc.proxyPath, cfg, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, acimpl.ProvideAccessControl(cfg), featuremgmt.WithFeatures())
|
||||
require.NoError(t, err)
|
||||
proxy.HandleRequest()
|
||||
|
||||
@ -421,6 +424,121 @@ func TestPluginProxyRoutes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginProxyRoutesAccessControl(t *testing.T) {
|
||||
routes := []*plugins.Route{
|
||||
{
|
||||
Path: "settings",
|
||||
Method: "GET",
|
||||
URL: "http://localhost/api/settings",
|
||||
ReqRole: org.RoleAdmin, // Protected by role
|
||||
},
|
||||
{
|
||||
Path: "projects",
|
||||
Method: "GET",
|
||||
URL: "http://localhost/api/projects",
|
||||
ReqAction: "plugin-id.projects:read", // Protected by RBAC action
|
||||
},
|
||||
}
|
||||
|
||||
tcs := []struct {
|
||||
proxyPath string
|
||||
usrRole org.RoleType
|
||||
usrPerms map[string][]string
|
||||
expectedURLPath string
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
proxyPath: "/settings",
|
||||
usrRole: org.RoleAdmin,
|
||||
expectedURLPath: "/api/settings",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
proxyPath: "/settings",
|
||||
usrRole: org.RoleViewer,
|
||||
expectedURLPath: "/api/settings",
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
proxyPath: "/projects",
|
||||
usrPerms: map[string][]string{"plugin-id.projects:read": {}},
|
||||
expectedURLPath: "/api/projects",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
proxyPath: "/projects",
|
||||
usrPerms: map[string][]string{},
|
||||
expectedURLPath: "/api/projects",
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(fmt.Sprintf("Should enforce RBAC when proxying path %s %s", tc.proxyPath, http.StatusText(tc.expectedStatus)), func(t *testing.T) {
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
requestHandled := false
|
||||
requestURL := ""
|
||||
backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requestURL = r.URL.RequestURI()
|
||||
w.WriteHeader(200)
|
||||
_, _ = w.Write([]byte("I am the backend"))
|
||||
requestHandled = true
|
||||
}))
|
||||
t.Cleanup(backendServer.Close)
|
||||
|
||||
backendURL, err := url.Parse(backendServer.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
testRoutes := make([]*plugins.Route, len(routes))
|
||||
for i, r := range routes {
|
||||
u, err := url.Parse(r.URL)
|
||||
require.NoError(t, err)
|
||||
u.Scheme = backendURL.Scheme
|
||||
u.Host = backendURL.Host
|
||||
testRoute := *r
|
||||
testRoute.URL = u.String()
|
||||
testRoutes[i] = &testRoute
|
||||
}
|
||||
|
||||
responseWriter := web.NewResponseWriter("GET", httptest.NewRecorder())
|
||||
|
||||
ctx := &contextmodel.ReqContext{
|
||||
Logger: logger.New("pluginproxy-test"),
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
OrgRole: tc.usrRole,
|
||||
Permissions: map[int64]map[string][]string{1: tc.usrPerms},
|
||||
},
|
||||
Context: &web.Context{
|
||||
Req: httptest.NewRequest("GET", tc.proxyPath, nil),
|
||||
Resp: responseWriter,
|
||||
},
|
||||
}
|
||||
ps := &pluginsettings.DTO{
|
||||
SecureJSONData: map[string][]byte{},
|
||||
}
|
||||
cfg := &setting.Cfg{}
|
||||
proxy, err := NewPluginProxy(ps, testRoutes, ctx, tc.proxyPath, cfg, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, acimpl.ProvideAccessControl(cfg), featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall))
|
||||
require.NoError(t, err)
|
||||
proxy.HandleRequest()
|
||||
|
||||
for {
|
||||
if requestHandled || ctx.Resp.Written() {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
require.Equal(t, tc.expectedStatus, ctx.Resp.Status())
|
||||
|
||||
if tc.expectedStatus == http.StatusForbidden {
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(t, tc.expectedURLPath, requestURL)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// getPluginProxiedRequest is a helper for easier setup of tests based on global config and ReqContext.
|
||||
func getPluginProxiedRequest(t *testing.T, ps *pluginsettings.DTO, secretsService secrets.Service, ctx *contextmodel.ReqContext, cfg *setting.Cfg, route *plugins.Route) *http.Request {
|
||||
// insert dummy route if none is specified
|
||||
@ -431,7 +549,7 @@ func getPluginProxiedRequest(t *testing.T, ps *pluginsettings.DTO, secretsServic
|
||||
ReqRole: org.RoleEditor,
|
||||
}
|
||||
}
|
||||
proxy, err := NewPluginProxy(ps, []*plugins.Route{}, ctx, "", cfg, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, featuremgmt.WithFeatures())
|
||||
proxy, err := NewPluginProxy(ps, []*plugins.Route{}, ctx, "", cfg, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, acimpl.ProvideAccessControl(cfg), featuremgmt.WithFeatures())
|
||||
require.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "/api/plugin-proxy/grafana-simple-app/api/v4/alerts", nil)
|
||||
|
Reference in New Issue
Block a user