浏览代码

fix(server side rendering): Fixed issues with server side rendering for alerting & for auth proxy scenarios, fixes #6115, fixes #5906

Torkel Ödegaard 9 年之前
父节点
当前提交
175c651e65

+ 1 - 0
CHANGELOG.md

@@ -25,6 +25,7 @@
 * **Graph panel**: Fixed problem with auto decimals on y axis when datamin=datamax, fixes [#6070](https://github.com/grafana/grafana/pull/6070)
 * **Snapshot**: Can view embedded panels/png rendered panels in snapshots without login, fixes [#3769](https://github.com/grafana/grafana/pull/3769)
 * **Elasticsearch**: Fix for query template variable when looking up terms without query, no longer relies on elasticsearch default field, fixes [#3887](https://github.com/grafana/grafana/pull/3887)
+* **PNG Rendering**: Fix for server side rendering when using auth proxy, fixes [#5906](https://github.com/grafana/grafana/pull/5906)
 
 # 3.1.2 (unreleased)
 * **Templating**: Fixed issue when combining row & panel repeats, fixes [#5790](https://github.com/grafana/grafana/issues/5790)

+ 1 - 1
pkg/api/frontendsettings.go

@@ -38,7 +38,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
 		url := ds.Url
 
 		if ds.Access == m.DS_ACCESS_PROXY {
-			url = setting.AppSubUrl + "/api/datasources/proxy/" + strconv.FormatInt(ds.Id, 10)
+			url = "/api/datasources/proxy/" + strconv.FormatInt(ds.Id, 10)
 		}
 
 		var dsMap = map[string]interface{}{

+ 13 - 2
pkg/api/index.go

@@ -1,6 +1,7 @@
 package api
 
 import (
+	"fmt"
 	"strings"
 
 	"github.com/grafana/grafana/pkg/api/dtos"
@@ -32,6 +33,16 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
 		locale = parts[0]
 	}
 
+	appUrl := setting.AppUrl
+	appSubUrl := setting.AppSubUrl
+
+	// special case when doing localhost call from phantomjs
+	if c.IsRenderCall {
+		appUrl = fmt.Sprintf("%s://localhost:%s", setting.Protocol, setting.HttpPort)
+		appSubUrl = ""
+		settings["appSubUrl"] = ""
+	}
+
 	var data = dtos.IndexViewData{
 		User: &dtos.CurrentUser{
 			Id:             c.UserId,
@@ -49,8 +60,8 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
 			Locale:         locale,
 		},
 		Settings:                settings,
-		AppUrl:                  setting.AppUrl,
-		AppSubUrl:               setting.AppSubUrl,
+		AppUrl:                  appUrl,
+		AppSubUrl:               appSubUrl,
 		GoogleAnalyticsId:       setting.GoogleAnalyticsId,
 		GoogleTagManagerId:      setting.GoogleTagManagerId,
 		BuildVersion:            setting.BuildVersion,

+ 5 - 19
pkg/api/render.go

@@ -6,35 +6,21 @@ import (
 
 	"github.com/grafana/grafana/pkg/components/renderer"
 	"github.com/grafana/grafana/pkg/middleware"
-	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/util"
 )
 
 func RenderToPng(c *middleware.Context) {
 	queryReader := util.NewUrlQueryReader(c.Req.URL)
 	queryParams := fmt.Sprintf("?%s", c.Req.URL.RawQuery)
-	sessionId := c.Session.ID()
-
-	// Handle api calls authenticated without session
-	if sessionId == "" && c.ApiKeyId != 0 {
-		c.Session.Start(c)
-		c.Session.Set(middleware.SESS_KEY_APIKEY, c.ApiKeyId)
-		// release will make sure the new session is persisted before
-		// we spin up phantomjs
-		c.Session.Release()
-		// cleanup session after render is complete
-		defer func() { c.Session.Destory(c) }()
-	}
 
 	renderOpts := &renderer.RenderOpts{
-		Url:       c.Params("*") + queryParams,
-		Width:     queryReader.Get("width", "800"),
-		Height:    queryReader.Get("height", "400"),
-		SessionId: c.Session.ID(),
-		Timeout:   queryReader.Get("timeout", "30"),
+		Url:     c.Params("*") + queryParams,
+		Width:   queryReader.Get("width", "800"),
+		Height:  queryReader.Get("height", "400"),
+		OrgId:   c.OrgId,
+		Timeout: queryReader.Get("timeout", "30"),
 	}
 
-	renderOpts.Url = setting.ToAbsUrl(renderOpts.Url)
 	pngPath, err := renderer.RenderToPng(renderOpts)
 
 	if err != nil {

+ 23 - 8
pkg/components/renderer/renderer.go

@@ -12,16 +12,17 @@ import (
 	"strconv"
 
 	"github.com/grafana/grafana/pkg/log"
+	"github.com/grafana/grafana/pkg/middleware"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/util"
 )
 
 type RenderOpts struct {
-	Url       string
-	Width     string
-	Height    string
-	SessionId string
-	Timeout   string
+	Url     string
+	Width   string
+	Height  string
+	Timeout string
+	OrgId   int64
 }
 
 var rendererLog log.Logger = log.New("png-renderer")
@@ -34,14 +35,28 @@ func RenderToPng(params *RenderOpts) (string, error) {
 		executable = executable + ".exe"
 	}
 
+	params.Url = fmt.Sprintf("%s://localhost:%s/%s", setting.Protocol, setting.HttpPort, params.Url)
+
 	binPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, executable))
 	scriptPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "render.js"))
 	pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, util.GetRandomString(20)))
 	pngPath = pngPath + ".png"
 
-	cmd := exec.Command(binPath, "--ignore-ssl-errors=true", scriptPath, "url="+params.Url, "width="+params.Width,
-		"height="+params.Height, "png="+pngPath, "cookiename="+setting.SessionOptions.CookieName,
-		"domain="+setting.Domain, "sessionid="+params.SessionId)
+	renderKey := middleware.AddRenderAuthKey(params.OrgId)
+	defer middleware.RemoveRenderAuthKey(renderKey)
+
+	cmdArgs := []string{
+		"--ignore-ssl-errors=true",
+		scriptPath,
+		"url=" + params.Url,
+		"width=" + params.Width,
+		"height=" + params.Height,
+		"png=" + pngPath,
+		"domain=" + setting.Domain,
+		"renderKey=" + renderKey,
+	}
+
+	cmd := exec.Command(binPath, cmdArgs...)
 	stdout, err := cmd.StdoutPipe()
 
 	if err != nil {

+ 3 - 25
pkg/middleware/middleware.go

@@ -22,6 +22,7 @@ type Context struct {
 	Session SessionStore
 
 	IsSignedIn     bool
+	IsRenderCall   bool
 	AllowAnonymous bool
 	Logger         log.Logger
 }
@@ -42,11 +43,11 @@ func GetContextHandler() macaron.Handler {
 		// then init session and look for userId in session
 		// then look for api key in session (special case for render calls via api)
 		// then test if anonymous access is enabled
-		if initContextWithApiKey(ctx) ||
+		if initContextWithRenderAuth(ctx) ||
+			initContextWithApiKey(ctx) ||
 			initContextWithBasicAuth(ctx) ||
 			initContextWithAuthProxy(ctx) ||
 			initContextWithUserSessionCookie(ctx) ||
-			initContextWithApiKeyFromSession(ctx) ||
 			initContextWithAnonymousUser(ctx) {
 		}
 
@@ -176,29 +177,6 @@ func initContextWithBasicAuth(ctx *Context) bool {
 	}
 }
 
-// special case for panel render calls with api key
-func initContextWithApiKeyFromSession(ctx *Context) bool {
-	keyId := ctx.Session.Get(SESS_KEY_APIKEY)
-	if keyId == nil {
-		return false
-	}
-
-	keyQuery := m.GetApiKeyByIdQuery{ApiKeyId: keyId.(int64)}
-	if err := bus.Dispatch(&keyQuery); err != nil {
-		ctx.Logger.Error("Failed to get api key by id", "id", keyId, "error", err)
-		return false
-	} else {
-		apikey := keyQuery.Result
-
-		ctx.IsSignedIn = true
-		ctx.SignedInUser = &m.SignedInUser{}
-		ctx.OrgRole = apikey.Role
-		ctx.ApiKeyId = apikey.Id
-		ctx.OrgId = apikey.OrgId
-		return true
-	}
-}
-
 // Handle handles and logs error by given status.
 func (ctx *Context) Handle(status int, title string, err error) {
 	if err != nil {

+ 55 - 0
pkg/middleware/render_auth.go

@@ -0,0 +1,55 @@
+package middleware
+
+import (
+	"sync"
+
+	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/util"
+)
+
+var renderKeysLock sync.Mutex
+var renderKeys map[string]*m.SignedInUser = make(map[string]*m.SignedInUser)
+
+func initContextWithRenderAuth(ctx *Context) bool {
+	key := ctx.GetCookie("renderKey")
+	if key == "" {
+		return false
+	}
+
+	renderKeysLock.Lock()
+	defer renderKeysLock.Unlock()
+
+	if renderUser, exists := renderKeys[key]; !exists {
+		ctx.JsonApiErr(401, "Invalid Render Key", nil)
+		return true
+	} else {
+
+		ctx.IsSignedIn = true
+		ctx.SignedInUser = renderUser
+		ctx.IsRenderCall = true
+		return true
+	}
+}
+
+type renderContextFunc func(key string) (string, error)
+
+func AddRenderAuthKey(orgId int64) string {
+	renderKeysLock.Lock()
+
+	key := util.GetRandomString(32)
+
+	renderKeys[key] = &m.SignedInUser{
+		OrgId:   orgId,
+		OrgRole: m.ROLE_VIEWER,
+	}
+
+	renderKeysLock.Unlock()
+
+	return key
+}
+
+func RemoveRenderAuthKey(key string) {
+	renderKeysLock.Lock()
+	delete(renderKeys, key)
+	renderKeysLock.Unlock()
+}

+ 0 - 1
pkg/middleware/session.go

@@ -13,7 +13,6 @@ import (
 
 const (
 	SESS_KEY_USERID = "uid"
-	SESS_KEY_APIKEY = "apikey_id" // used fror render requests with api keys
 )
 
 var sessionManager *session.Manager

+ 5 - 5
pkg/services/alerting/notifier.go

@@ -69,11 +69,11 @@ func (n *RootNotifier) uploadImage(context *EvalContext) error {
 	}
 
 	renderOpts := &renderer.RenderOpts{
-		Url:       imageUrl,
-		Width:     "800",
-		Height:    "400",
-		SessionId: "cef0256d482b4293",
-		Timeout:   "30",
+		Url:     imageUrl,
+		Width:   "800",
+		Height:  "400",
+		Timeout: "30",
+		OrgId:   context.Rule.OrgId,
 	}
 
 	if imagePath, err := renderer.RenderToPng(renderOpts); err != nil {

+ 4 - 0
public/app/core/services/backend_srv.ts

@@ -114,6 +114,10 @@ export class BackendSrv {
     var requestIsLocal = options.url.indexOf('/') === 0;
     var firstAttempt = options.retry === 0;
 
+    if (requestIsLocal && !options.hasSubUrl && options.retry === 0) {
+      options.url = config.appSubUrl + options.url;
+    }
+
     if (requestIsLocal && options.headers && options.headers.Authorization) {
       options.headers['X-DS-Authorization'] = options.headers.Authorization;
       delete options.headers.Authorization;

+ 5 - 5
vendor/phantomjs/render.js

@@ -12,17 +12,17 @@
     params[parts[1]] = parts[2];
   });
 
-  var usage = "url=<url> png=<filename> width=<width> height=<height> cookiename=<cookiename> sessionid=<sessionid> domain=<domain>";
+  var usage = "url=<url> png=<filename> width=<width> height=<height> renderKey=<key>";
 
-  if (!params.url || !params.png || !params.cookiename || ! params.sessionid || !params.domain) {
+  if (!params.url || !params.png ||  !params.renderKey || !params.domain) {
     console.log(usage);
     phantom.exit();
   }
 
   phantom.addCookie({
-    'name': params.cookiename,
-    'value': params.sessionid,
-    'domain': params.domain
+    'name': 'renderKey',
+    'value': params.renderKey,
+    'domain': 'localhost',
   });
 
   page.viewportSize = {