|
|
|
@ -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) |
|
|
|
|