From ebc257ad47133eb12ae63160c8b7307306f9ced8 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Aug 2019 14:27:33 +0200 Subject: [PATCH] Backport: Require authentication for snapshot api --- conf/defaults.ini | 4 ++++ conf/sample.ini | 4 ++++ pkg/api/api.go | 16 +++++++++------- pkg/middleware/auth.go | 13 +++++++++++++ pkg/middleware/auth_test.go | 16 ++++++++++++++++ pkg/setting/setting.go | 2 ++ 6 files changed, 48 insertions(+), 7 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index 9f2f186a4eb..baf7d6c62a2 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -193,6 +193,10 @@ external_enabled = true external_snapshot_url = https://snapshots-origin.raintank.io external_snapshot_name = Publish to snapshot.raintank.io +# Set to true to enable this Grafana instance act as an external snapshot server and allow unauthenticated requests for +# creating and deleting snapshots. +public_mode = false + # remove expired snapshot snapshot_remove_expired = true diff --git a/conf/sample.ini b/conf/sample.ini index 455d32c06dc..db436a77cf1 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -177,6 +177,10 @@ log_queries = ;external_snapshot_url = https://snapshots-origin.raintank.io ;external_snapshot_name = Publish to snapshot.raintank.io +# Set to true to enable this Grafana instance act as an external snapshot server and allow unauthenticated requests for +# creating and deleting snapshots. +;public_mode = false + # remove expired snapshot ;snapshot_remove_expired = true diff --git a/pkg/api/api.go b/pkg/api/api.go index 0526ee80afe..ee7c1bb1be3 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models" ) @@ -14,6 +15,7 @@ func (hs *HTTPServer) registerRoutes() { reqGrafanaAdmin := middleware.ReqGrafanaAdmin reqEditorRole := middleware.ReqEditorRole reqOrgAdmin := middleware.ReqOrgAdmin + reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn() redirectFromLegacyDashboardURL := middleware.RedirectFromLegacyDashboardURL() redirectFromLegacyDashboardSoloURL := middleware.RedirectFromLegacyDashboardSoloURL() quota := middleware.Quota @@ -101,13 +103,6 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/dashboard/snapshot/*", hs.Index) r.Get("/dashboard/snapshots/", reqSignedIn, hs.Index) - // api for dashboard snapshots - r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot) - r.Get("/api/snapshot/shared-options/", GetSharingOptions) - r.Get("/api/snapshots/:key", GetDashboardSnapshot) - r.Get("/api/snapshots-delete/:deleteKey", Wrap(DeleteDashboardSnapshotByDeleteKey)) - r.Delete("/api/snapshots/:key", reqEditorRole, Wrap(DeleteDashboardSnapshot)) - // api renew session based on remember cookie r.Get("/api/login/ping", quota("session"), LoginAPIPing) @@ -391,4 +386,11 @@ func (hs *HTTPServer) registerRoutes() { // streams //r.Post("/api/streams/push", reqSignedIn, bind(dtos.StreamMessage{}), liveConn.PushToStream) + + // Snapshots + r.Post("/api/snapshots/", reqSnapshotPublicModeOrSignedIn, bind(models.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot) + r.Get("/api/snapshot/shared-options/", reqSignedIn, GetSharingOptions) + r.Get("/api/snapshots/:key", GetDashboardSnapshot) + r.Get("/api/snapshots-delete/:deleteKey", reqSnapshotPublicModeOrSignedIn, Wrap(DeleteDashboardSnapshotByDeleteKey)) + r.Delete("/api/snapshots/:key", reqEditorRole, Wrap(DeleteDashboardSnapshot)) } diff --git a/pkg/middleware/auth.go b/pkg/middleware/auth.go index 5faee1e3fa7..da444735020 100644 --- a/pkg/middleware/auth.go +++ b/pkg/middleware/auth.go @@ -91,3 +91,16 @@ func Auth(options *AuthOptions) macaron.Handler { } } } + +func SnapshotPublicModeOrSignedIn() macaron.Handler { + return func(c *m.ReqContext) { + if setting.SnapshotPublicMode { + return + } + + _, err := c.Invoke(ReqSignedIn) + if err != nil { + c.JsonApiErr(500, "Failed to invoke required signed in middleware", err) + } + } +} diff --git a/pkg/middleware/auth_test.go b/pkg/middleware/auth_test.go index 81b0f525e98..1df2197d726 100644 --- a/pkg/middleware/auth_test.go +++ b/pkg/middleware/auth_test.go @@ -3,6 +3,8 @@ package middleware import ( "testing" + "github.com/grafana/grafana/pkg/setting" + . "github.com/smartystreets/goconvey/convey" ) @@ -31,5 +33,19 @@ func TestMiddlewareAuth(t *testing.T) { }) }) + Convey("snapshot public mode or signed in", func() { + middlewareScenario("Snapshot public mode disabled and unauthenticated request should return 401", func(sc *scenarioContext) { + sc.m.Get("/api/snapshot", SnapshotPublicModeOrSignedIn(), sc.defaultHandler) + sc.fakeReq("GET", "/api/snapshot").exec() + So(sc.resp.Code, ShouldEqual, 401) + }) + + middlewareScenario("Snapshot public mode enabled and unauthenticated request should return 200", func(sc *scenarioContext) { + setting.SnapshotPublicMode = true + sc.m.Get("/api/snapshot", SnapshotPublicModeOrSignedIn(), sc.defaultHandler) + sc.fakeReq("GET", "/api/snapshot").exec() + So(sc.resp.Code, ShouldEqual, 200) + }) + }) }) } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 1417392fdf8..7e33dad48eb 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -96,6 +96,7 @@ var ( ExternalSnapshotName string ExternalEnabled bool SnapShotRemoveExpired bool + SnapshotPublicMode bool // Dashboard history DashboardVersionsToKeep int @@ -599,6 +600,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { ExternalSnapshotName = snapshots.Key("external_snapshot_name").String() ExternalEnabled = snapshots.Key("external_enabled").MustBool(true) SnapShotRemoveExpired = snapshots.Key("snapshot_remove_expired").MustBool(true) + SnapshotPublicMode = snapshots.Key("public_mode").MustBool(false) // read dashboard settings dashboards := iniFile.Section("dashboards")