mirror of
https://github.com/grafana/grafana.git
synced 2025-01-26 08:16:59 -06:00
fix(server side rendering): Fixed issues with server side rendering for alerting & for auth proxy scenarios, fixes #6115, fixes #5906
This commit is contained in:
parent
bb77caa369
commit
175c651e65
@ -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)
|
* **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)
|
* **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)
|
* **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)
|
# 3.1.2 (unreleased)
|
||||||
* **Templating**: Fixed issue when combining row & panel repeats, fixes [#5790](https://github.com/grafana/grafana/issues/5790)
|
* **Templating**: Fixed issue when combining row & panel repeats, fixes [#5790](https://github.com/grafana/grafana/issues/5790)
|
||||||
|
@ -38,7 +38,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
|||||||
url := ds.Url
|
url := ds.Url
|
||||||
|
|
||||||
if ds.Access == m.DS_ACCESS_PROXY {
|
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{}{
|
var dsMap = map[string]interface{}{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
@ -32,6 +33,16 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
|
|||||||
locale = parts[0]
|
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{
|
var data = dtos.IndexViewData{
|
||||||
User: &dtos.CurrentUser{
|
User: &dtos.CurrentUser{
|
||||||
Id: c.UserId,
|
Id: c.UserId,
|
||||||
@ -49,8 +60,8 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
|
|||||||
Locale: locale,
|
Locale: locale,
|
||||||
},
|
},
|
||||||
Settings: settings,
|
Settings: settings,
|
||||||
AppUrl: setting.AppUrl,
|
AppUrl: appUrl,
|
||||||
AppSubUrl: setting.AppSubUrl,
|
AppSubUrl: appSubUrl,
|
||||||
GoogleAnalyticsId: setting.GoogleAnalyticsId,
|
GoogleAnalyticsId: setting.GoogleAnalyticsId,
|
||||||
GoogleTagManagerId: setting.GoogleTagManagerId,
|
GoogleTagManagerId: setting.GoogleTagManagerId,
|
||||||
BuildVersion: setting.BuildVersion,
|
BuildVersion: setting.BuildVersion,
|
||||||
|
@ -6,35 +6,21 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/renderer"
|
"github.com/grafana/grafana/pkg/components/renderer"
|
||||||
"github.com/grafana/grafana/pkg/middleware"
|
"github.com/grafana/grafana/pkg/middleware"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RenderToPng(c *middleware.Context) {
|
func RenderToPng(c *middleware.Context) {
|
||||||
queryReader := util.NewUrlQueryReader(c.Req.URL)
|
queryReader := util.NewUrlQueryReader(c.Req.URL)
|
||||||
queryParams := fmt.Sprintf("?%s", c.Req.URL.RawQuery)
|
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{
|
renderOpts := &renderer.RenderOpts{
|
||||||
Url: c.Params("*") + queryParams,
|
Url: c.Params("*") + queryParams,
|
||||||
Width: queryReader.Get("width", "800"),
|
Width: queryReader.Get("width", "800"),
|
||||||
Height: queryReader.Get("height", "400"),
|
Height: queryReader.Get("height", "400"),
|
||||||
SessionId: c.Session.ID(),
|
OrgId: c.OrgId,
|
||||||
Timeout: queryReader.Get("timeout", "30"),
|
Timeout: queryReader.Get("timeout", "30"),
|
||||||
}
|
}
|
||||||
|
|
||||||
renderOpts.Url = setting.ToAbsUrl(renderOpts.Url)
|
|
||||||
pngPath, err := renderer.RenderToPng(renderOpts)
|
pngPath, err := renderer.RenderToPng(renderOpts)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -12,16 +12,17 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
|
"github.com/grafana/grafana/pkg/middleware"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RenderOpts struct {
|
type RenderOpts struct {
|
||||||
Url string
|
Url string
|
||||||
Width string
|
Width string
|
||||||
Height string
|
Height string
|
||||||
SessionId string
|
Timeout string
|
||||||
Timeout string
|
OrgId int64
|
||||||
}
|
}
|
||||||
|
|
||||||
var rendererLog log.Logger = log.New("png-renderer")
|
var rendererLog log.Logger = log.New("png-renderer")
|
||||||
@ -34,14 +35,28 @@ func RenderToPng(params *RenderOpts) (string, error) {
|
|||||||
executable = executable + ".exe"
|
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))
|
binPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, executable))
|
||||||
scriptPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "render.js"))
|
scriptPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "render.js"))
|
||||||
pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, util.GetRandomString(20)))
|
pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, util.GetRandomString(20)))
|
||||||
pngPath = pngPath + ".png"
|
pngPath = pngPath + ".png"
|
||||||
|
|
||||||
cmd := exec.Command(binPath, "--ignore-ssl-errors=true", scriptPath, "url="+params.Url, "width="+params.Width,
|
renderKey := middleware.AddRenderAuthKey(params.OrgId)
|
||||||
"height="+params.Height, "png="+pngPath, "cookiename="+setting.SessionOptions.CookieName,
|
defer middleware.RemoveRenderAuthKey(renderKey)
|
||||||
"domain="+setting.Domain, "sessionid="+params.SessionId)
|
|
||||||
|
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()
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -22,6 +22,7 @@ type Context struct {
|
|||||||
Session SessionStore
|
Session SessionStore
|
||||||
|
|
||||||
IsSignedIn bool
|
IsSignedIn bool
|
||||||
|
IsRenderCall bool
|
||||||
AllowAnonymous bool
|
AllowAnonymous bool
|
||||||
Logger log.Logger
|
Logger log.Logger
|
||||||
}
|
}
|
||||||
@ -42,11 +43,11 @@ func GetContextHandler() macaron.Handler {
|
|||||||
// then init session and look for userId in session
|
// then init session and look for userId in session
|
||||||
// then look for api key in session (special case for render calls via api)
|
// then look for api key in session (special case for render calls via api)
|
||||||
// then test if anonymous access is enabled
|
// then test if anonymous access is enabled
|
||||||
if initContextWithApiKey(ctx) ||
|
if initContextWithRenderAuth(ctx) ||
|
||||||
|
initContextWithApiKey(ctx) ||
|
||||||
initContextWithBasicAuth(ctx) ||
|
initContextWithBasicAuth(ctx) ||
|
||||||
initContextWithAuthProxy(ctx) ||
|
initContextWithAuthProxy(ctx) ||
|
||||||
initContextWithUserSessionCookie(ctx) ||
|
initContextWithUserSessionCookie(ctx) ||
|
||||||
initContextWithApiKeyFromSession(ctx) ||
|
|
||||||
initContextWithAnonymousUser(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.
|
// Handle handles and logs error by given status.
|
||||||
func (ctx *Context) Handle(status int, title string, err error) {
|
func (ctx *Context) Handle(status int, title string, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
55
pkg/middleware/render_auth.go
Normal file
55
pkg/middleware/render_auth.go
Normal file
@ -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()
|
||||||
|
}
|
@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
SESS_KEY_USERID = "uid"
|
SESS_KEY_USERID = "uid"
|
||||||
SESS_KEY_APIKEY = "apikey_id" // used fror render requests with api keys
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var sessionManager *session.Manager
|
var sessionManager *session.Manager
|
||||||
|
@ -69,11 +69,11 @@ func (n *RootNotifier) uploadImage(context *EvalContext) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderOpts := &renderer.RenderOpts{
|
renderOpts := &renderer.RenderOpts{
|
||||||
Url: imageUrl,
|
Url: imageUrl,
|
||||||
Width: "800",
|
Width: "800",
|
||||||
Height: "400",
|
Height: "400",
|
||||||
SessionId: "cef0256d482b4293",
|
Timeout: "30",
|
||||||
Timeout: "30",
|
OrgId: context.Rule.OrgId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if imagePath, err := renderer.RenderToPng(renderOpts); err != nil {
|
if imagePath, err := renderer.RenderToPng(renderOpts); err != nil {
|
||||||
|
@ -114,6 +114,10 @@ export class BackendSrv {
|
|||||||
var requestIsLocal = options.url.indexOf('/') === 0;
|
var requestIsLocal = options.url.indexOf('/') === 0;
|
||||||
var firstAttempt = options.retry === 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) {
|
if (requestIsLocal && options.headers && options.headers.Authorization) {
|
||||||
options.headers['X-DS-Authorization'] = options.headers.Authorization;
|
options.headers['X-DS-Authorization'] = options.headers.Authorization;
|
||||||
delete options.headers.Authorization;
|
delete options.headers.Authorization;
|
||||||
|
10
vendor/phantomjs/render.js
vendored
10
vendor/phantomjs/render.js
vendored
@ -12,17 +12,17 @@
|
|||||||
params[parts[1]] = parts[2];
|
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);
|
console.log(usage);
|
||||||
phantom.exit();
|
phantom.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
phantom.addCookie({
|
phantom.addCookie({
|
||||||
'name': params.cookiename,
|
'name': 'renderKey',
|
||||||
'value': params.sessionid,
|
'value': params.renderKey,
|
||||||
'domain': params.domain
|
'domain': 'localhost',
|
||||||
});
|
});
|
||||||
|
|
||||||
page.viewportSize = {
|
page.viewportSize = {
|
||||||
|
Loading…
Reference in New Issue
Block a user