Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
8221c427
Unverified
Commit
8221c427
authored
Mar 14, 2019
by
Andrej Ocenas
Committed by
GitHub
Mar 14, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #15998 from grafana/add-grafana-user-proxy-header
Add custom header with grafana user when using proxy
parents
23852b59
697a87b7
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
172 additions
and
20 deletions
+172
-20
conf/defaults.ini
+3
-0
conf/sample.ini
+3
-0
docs/sources/installation/configuration.md
+16
-0
pkg/api/app_routes.go
+3
-3
pkg/api/dataproxy.go
+1
-1
pkg/api/pluginproxy/ds_proxy.go
+7
-1
pkg/api/pluginproxy/ds_proxy_test.go
+73
-14
pkg/api/pluginproxy/pluginproxy.go
+6
-1
pkg/api/pluginproxy/pluginproxy_test.go
+56
-0
pkg/setting/setting.go
+4
-0
No files found.
conf/defaults.ini
View file @
8221c427
...
...
@@ -157,6 +157,9 @@ logging = false
# How long the data proxy should wait before timing out default is 30 (seconds)
timeout
=
30
# If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request, default is false.
send_user_header
=
false
#################################### Analytics ###########################
[analytics]
# Server reporting, sends usage counters to stats.grafana.org every 24 hours.
...
...
conf/sample.ini
View file @
8221c427
...
...
@@ -144,6 +144,9 @@ log_queries =
# How long the data proxy should wait before timing out default is 30 (seconds)
;timeout = 30
# If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request, default is false.
;send_user_header = false
#################################### Analytics ####################################
[analytics]
# Server reporting, sends usage counters to stats.grafana.org every 24 hours.
...
...
docs/sources/installation/configuration.md
View file @
8221c427
...
...
@@ -411,6 +411,22 @@ How long sessions lasts in seconds. Defaults to `86400` (24 hours).
<hr
/>
## [dataproxy]
### logging
This enables data proxy logging, default is false.
### timeout
How long the data proxy should wait before timing out default is 30 (seconds)
### send_user_header
If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request, default is false.
<hr
/>
## [analytics]
### reporting_enabled
...
...
pkg/api/app_routes.go
View file @
8221c427
...
...
@@ -48,18 +48,18 @@ func (hs *HTTPServer) initAppPluginRoutes(r *macaron.Macaron) {
handlers
=
append
(
handlers
,
middleware
.
RoleAuth
(
m
.
ROLE_EDITOR
,
m
.
ROLE_ADMIN
))
}
}
handlers
=
append
(
handlers
,
AppPluginRoute
(
route
,
plugin
.
Id
))
handlers
=
append
(
handlers
,
AppPluginRoute
(
route
,
plugin
.
Id
,
hs
))
r
.
Route
(
url
,
route
.
Method
,
handlers
...
)
log
.
Debug
(
"Plugins: Adding proxy route %s"
,
url
)
}
}
}
func
AppPluginRoute
(
route
*
plugins
.
AppPluginRoute
,
appID
string
)
macaron
.
Handler
{
func
AppPluginRoute
(
route
*
plugins
.
AppPluginRoute
,
appID
string
,
hs
*
HTTPServer
)
macaron
.
Handler
{
return
func
(
c
*
m
.
ReqContext
)
{
path
:=
c
.
Params
(
"*"
)
proxy
:=
pluginproxy
.
NewApiPluginProxy
(
c
,
path
,
route
,
appID
)
proxy
:=
pluginproxy
.
NewApiPluginProxy
(
c
,
path
,
route
,
appID
,
hs
.
Cfg
)
proxy
.
Transport
=
pluginProxyTransport
proxy
.
ServeHTTP
(
c
.
Resp
,
c
.
Req
.
Request
)
}
...
...
pkg/api/dataproxy.go
View file @
8221c427
...
...
@@ -31,7 +31,7 @@ func (hs *HTTPServer) ProxyDataSourceRequest(c *m.ReqContext) {
// macaron does not include trailing slashes when resolving a wildcard path
proxyPath
:=
ensureProxyPathTrailingSlash
(
c
.
Req
.
URL
.
Path
,
c
.
Params
(
"*"
))
proxy
:=
pluginproxy
.
NewDataSourceProxy
(
ds
,
plugin
,
c
,
proxyPath
)
proxy
:=
pluginproxy
.
NewDataSourceProxy
(
ds
,
plugin
,
c
,
proxyPath
,
hs
.
Cfg
)
proxy
.
HandleRequest
()
}
...
...
pkg/api/pluginproxy/ds_proxy.go
View file @
8221c427
...
...
@@ -34,13 +34,14 @@ type DataSourceProxy struct {
proxyPath
string
route
*
plugins
.
AppPluginRoute
plugin
*
plugins
.
DataSourcePlugin
cfg
*
setting
.
Cfg
}
type
httpClient
interface
{
Do
(
req
*
http
.
Request
)
(
*
http
.
Response
,
error
)
}
func
NewDataSourceProxy
(
ds
*
m
.
DataSource
,
plugin
*
plugins
.
DataSourcePlugin
,
ctx
*
m
.
ReqContext
,
proxyPath
string
)
*
DataSourceProxy
{
func
NewDataSourceProxy
(
ds
*
m
.
DataSource
,
plugin
*
plugins
.
DataSourcePlugin
,
ctx
*
m
.
ReqContext
,
proxyPath
string
,
cfg
*
setting
.
Cfg
)
*
DataSourceProxy
{
targetURL
,
_
:=
url
.
Parse
(
ds
.
Url
)
return
&
DataSourceProxy
{
...
...
@@ -49,6 +50,7 @@ func NewDataSourceProxy(ds *m.DataSource, plugin *plugins.DataSourcePlugin, ctx
ctx
:
ctx
,
proxyPath
:
proxyPath
,
targetUrl
:
targetURL
,
cfg
:
cfg
,
}
}
...
...
@@ -170,6 +172,10 @@ func (proxy *DataSourceProxy) getDirector() func(req *http.Request) {
req
.
Header
.
Add
(
"Authorization"
,
dsAuth
)
}
if
proxy
.
cfg
.
SendUserHeader
&&
!
proxy
.
ctx
.
SignedInUser
.
IsAnonymous
{
req
.
Header
.
Add
(
"X-Grafana-User"
,
proxy
.
ctx
.
SignedInUser
.
Login
)
}
// clear cookie header, except for whitelisted cookies
var
keptCookies
[]
*
http
.
Cookie
if
proxy
.
ds
.
JsonData
!=
nil
{
...
...
pkg/api/pluginproxy/ds_proxy_test.go
View file @
8221c427
...
...
@@ -81,7 +81,7 @@ func TestDSRouteRule(t *testing.T) {
}
Convey
(
"When matching route path"
,
func
()
{
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"api/v4/some/method"
)
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"api/v4/some/method"
,
&
setting
.
Cfg
{}
)
proxy
.
route
=
plugin
.
Routes
[
0
]
ApplyRoute
(
proxy
.
ctx
.
Req
.
Context
(),
req
,
proxy
.
proxyPath
,
proxy
.
route
,
proxy
.
ds
)
...
...
@@ -92,7 +92,7 @@ func TestDSRouteRule(t *testing.T) {
})
Convey
(
"When matching route path and has dynamic url"
,
func
()
{
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"api/common/some/method"
)
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"api/common/some/method"
,
&
setting
.
Cfg
{}
)
proxy
.
route
=
plugin
.
Routes
[
3
]
ApplyRoute
(
proxy
.
ctx
.
Req
.
Context
(),
req
,
proxy
.
proxyPath
,
proxy
.
route
,
proxy
.
ds
)
...
...
@@ -104,20 +104,20 @@ func TestDSRouteRule(t *testing.T) {
Convey
(
"Validating request"
,
func
()
{
Convey
(
"plugin route with valid role"
,
func
()
{
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"api/v4/some/method"
)
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"api/v4/some/method"
,
&
setting
.
Cfg
{}
)
err
:=
proxy
.
validateRequest
()
So
(
err
,
ShouldBeNil
)
})
Convey
(
"plugin route with admin role and user is editor"
,
func
()
{
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"api/admin"
)
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"api/admin"
,
&
setting
.
Cfg
{}
)
err
:=
proxy
.
validateRequest
()
So
(
err
,
ShouldNotBeNil
)
})
Convey
(
"plugin route with admin role and user is admin"
,
func
()
{
ctx
.
SignedInUser
.
OrgRole
=
m
.
ROLE_ADMIN
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"api/admin"
)
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"api/admin"
,
&
setting
.
Cfg
{}
)
err
:=
proxy
.
validateRequest
()
So
(
err
,
ShouldBeNil
)
})
...
...
@@ -186,7 +186,7 @@ func TestDSRouteRule(t *testing.T) {
So
(
err
,
ShouldBeNil
)
client
=
newFakeHTTPClient
(
json
)
proxy1
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"pathwithtoken1"
)
proxy1
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"pathwithtoken1"
,
&
setting
.
Cfg
{}
)
proxy1
.
route
=
plugin
.
Routes
[
0
]
ApplyRoute
(
proxy1
.
ctx
.
Req
.
Context
(),
req
,
proxy1
.
proxyPath
,
proxy1
.
route
,
proxy1
.
ds
)
...
...
@@ -200,7 +200,7 @@ func TestDSRouteRule(t *testing.T) {
req
,
_
:=
http
.
NewRequest
(
"GET"
,
"http://localhost/asd"
,
nil
)
client
=
newFakeHTTPClient
(
json2
)
proxy2
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"pathwithtoken2"
)
proxy2
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"pathwithtoken2"
,
&
setting
.
Cfg
{}
)
proxy2
.
route
=
plugin
.
Routes
[
1
]
ApplyRoute
(
proxy2
.
ctx
.
Req
.
Context
(),
req
,
proxy2
.
proxyPath
,
proxy2
.
route
,
proxy2
.
ds
)
...
...
@@ -215,7 +215,7 @@ func TestDSRouteRule(t *testing.T) {
req
,
_
:=
http
.
NewRequest
(
"GET"
,
"http://localhost/asd"
,
nil
)
client
=
newFakeHTTPClient
([]
byte
{})
proxy3
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"pathwithtoken1"
)
proxy3
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"pathwithtoken1"
,
&
setting
.
Cfg
{}
)
proxy3
.
route
=
plugin
.
Routes
[
0
]
ApplyRoute
(
proxy3
.
ctx
.
Req
.
Context
(),
req
,
proxy3
.
proxyPath
,
proxy3
.
route
,
proxy3
.
ds
)
...
...
@@ -236,7 +236,7 @@ func TestDSRouteRule(t *testing.T) {
ds
:=
&
m
.
DataSource
{
Url
:
"htttp://graphite:8080"
,
Type
:
m
.
DS_GRAPHITE
}
ctx
:=
&
m
.
ReqContext
{}
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"/render"
)
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"/render"
,
&
setting
.
Cfg
{}
)
req
,
err
:=
http
.
NewRequest
(
http
.
MethodGet
,
"http://grafana.com/sub"
,
nil
)
So
(
err
,
ShouldBeNil
)
...
...
@@ -261,7 +261,7 @@ func TestDSRouteRule(t *testing.T) {
}
ctx
:=
&
m
.
ReqContext
{}
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
""
)
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
""
,
&
setting
.
Cfg
{}
)
req
,
err
:=
http
.
NewRequest
(
http
.
MethodGet
,
"http://grafana.com/sub"
,
nil
)
So
(
err
,
ShouldBeNil
)
...
...
@@ -291,7 +291,7 @@ func TestDSRouteRule(t *testing.T) {
}
ctx
:=
&
m
.
ReqContext
{}
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
""
)
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
""
,
&
setting
.
Cfg
{}
)
requestURL
,
_
:=
url
.
Parse
(
"http://grafana.com/sub"
)
req
:=
http
.
Request
{
URL
:
requestURL
,
Header
:
make
(
http
.
Header
)}
...
...
@@ -317,7 +317,7 @@ func TestDSRouteRule(t *testing.T) {
}
ctx
:=
&
m
.
ReqContext
{}
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
""
)
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
""
,
&
setting
.
Cfg
{}
)
requestURL
,
_
:=
url
.
Parse
(
"http://grafana.com/sub"
)
req
:=
http
.
Request
{
URL
:
requestURL
,
Header
:
make
(
http
.
Header
)}
...
...
@@ -347,7 +347,7 @@ func TestDSRouteRule(t *testing.T) {
}
ctx
:=
&
m
.
ReqContext
{}
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
""
)
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
""
,
&
setting
.
Cfg
{}
)
requestURL
,
_
:=
url
.
Parse
(
"http://grafana.com/sub"
)
req
:=
http
.
Request
{
URL
:
requestURL
,
Header
:
make
(
http
.
Header
)}
...
...
@@ -369,7 +369,7 @@ func TestDSRouteRule(t *testing.T) {
Url
:
"http://host/root/"
,
}
ctx
:=
&
m
.
ReqContext
{}
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"/path/to/folder/"
)
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
"/path/to/folder/"
,
&
setting
.
Cfg
{}
)
req
,
err
:=
http
.
NewRequest
(
http
.
MethodGet
,
"http://grafana.com/sub"
,
nil
)
req
.
Header
.
Add
(
"Origin"
,
"grafana.com"
)
req
.
Header
.
Add
(
"Referer"
,
"grafana.com"
)
...
...
@@ -388,9 +388,68 @@ func TestDSRouteRule(t *testing.T) {
So
(
req
.
Header
.
Get
(
"X-Canary"
),
ShouldEqual
,
"stillthere"
)
})
})
Convey
(
"When SendUserHeader config is enabled"
,
func
()
{
req
:=
getDatasourceProxiedRequest
(
&
m
.
ReqContext
{
SignedInUser
:
&
m
.
SignedInUser
{
Login
:
"test_user"
,
},
},
&
setting
.
Cfg
{
SendUserHeader
:
true
},
)
Convey
(
"Should add header with username"
,
func
()
{
So
(
req
.
Header
.
Get
(
"X-Grafana-User"
),
ShouldEqual
,
"test_user"
)
})
})
Convey
(
"When SendUserHeader config is disabled"
,
func
()
{
req
:=
getDatasourceProxiedRequest
(
&
m
.
ReqContext
{
SignedInUser
:
&
m
.
SignedInUser
{
Login
:
"test_user"
,
},
},
&
setting
.
Cfg
{
SendUserHeader
:
false
},
)
Convey
(
"Should not add header with username"
,
func
()
{
// Get will return empty string even if header is not set
So
(
req
.
Header
.
Get
(
"X-Grafana-User"
),
ShouldEqual
,
""
)
})
})
Convey
(
"When SendUserHeader config is enabled but user is anonymous"
,
func
()
{
req
:=
getDatasourceProxiedRequest
(
&
m
.
ReqContext
{
SignedInUser
:
&
m
.
SignedInUser
{
IsAnonymous
:
true
},
},
&
setting
.
Cfg
{
SendUserHeader
:
true
},
)
Convey
(
"Should not add header with username"
,
func
()
{
// Get will return empty string even if header is not set
So
(
req
.
Header
.
Get
(
"X-Grafana-User"
),
ShouldEqual
,
""
)
})
})
})
}
// getDatasourceProxiedRequest is a helper for easier setup of tests based on global config and ReqContext.
func
getDatasourceProxiedRequest
(
ctx
*
m
.
ReqContext
,
cfg
*
setting
.
Cfg
)
*
http
.
Request
{
plugin
:=
&
plugins
.
DataSourcePlugin
{}
ds
:=
&
m
.
DataSource
{
Type
:
"custom"
,
Url
:
"http://host/root/"
,
}
proxy
:=
NewDataSourceProxy
(
ds
,
plugin
,
ctx
,
""
,
cfg
)
req
,
err
:=
http
.
NewRequest
(
http
.
MethodGet
,
"http://grafana.com/sub"
,
nil
)
So
(
err
,
ShouldBeNil
)
proxy
.
getDirector
()(
req
)
return
req
}
type
httpClientStub
struct
{
fakeBody
[]
byte
}
...
...
pkg/api/pluginproxy/pluginproxy.go
View file @
8221c427
...
...
@@ -2,6 +2,7 @@ package pluginproxy
import
(
"encoding/json"
"github.com/grafana/grafana/pkg/setting"
"net"
"net/http"
"net/http/httputil"
...
...
@@ -37,7 +38,7 @@ func getHeaders(route *plugins.AppPluginRoute, orgId int64, appID string) (http.
return
result
,
err
}
func
NewApiPluginProxy
(
ctx
*
m
.
ReqContext
,
proxyPath
string
,
route
*
plugins
.
AppPluginRoute
,
appID
string
)
*
httputil
.
ReverseProxy
{
func
NewApiPluginProxy
(
ctx
*
m
.
ReqContext
,
proxyPath
string
,
route
*
plugins
.
AppPluginRoute
,
appID
string
,
cfg
*
setting
.
Cfg
)
*
httputil
.
ReverseProxy
{
targetURL
,
_
:=
url
.
Parse
(
route
.
Url
)
director
:=
func
(
req
*
http
.
Request
)
{
...
...
@@ -79,6 +80,10 @@ func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPl
req
.
Header
.
Add
(
"X-Grafana-Context"
,
string
(
ctxJson
))
if
cfg
.
SendUserHeader
&&
!
ctx
.
SignedInUser
.
IsAnonymous
{
req
.
Header
.
Add
(
"X-Grafana-User"
,
ctx
.
SignedInUser
.
Login
)
}
if
len
(
route
.
Headers
)
>
0
{
headers
,
err
:=
getHeaders
(
route
,
ctx
.
OrgId
,
appID
)
if
err
!=
nil
{
...
...
pkg/api/pluginproxy/pluginproxy_test.go
View file @
8221c427
package
pluginproxy
import
(
"net/http"
"testing"
"github.com/grafana/grafana/pkg/bus"
...
...
@@ -44,4 +45,59 @@ func TestPluginProxy(t *testing.T) {
})
})
Convey
(
"When SendUserHeader config is enabled"
,
t
,
func
()
{
req
:=
getPluginProxiedRequest
(
&
m
.
ReqContext
{
SignedInUser
:
&
m
.
SignedInUser
{
Login
:
"test_user"
,
},
},
&
setting
.
Cfg
{
SendUserHeader
:
true
},
)
Convey
(
"Should add header with username"
,
func
()
{
// Get will return empty string even if header is not set
So
(
req
.
Header
.
Get
(
"X-Grafana-User"
),
ShouldEqual
,
"test_user"
)
})
})
Convey
(
"When SendUserHeader config is disabled"
,
t
,
func
()
{
req
:=
getPluginProxiedRequest
(
&
m
.
ReqContext
{
SignedInUser
:
&
m
.
SignedInUser
{
Login
:
"test_user"
,
},
},
&
setting
.
Cfg
{
SendUserHeader
:
false
},
)
Convey
(
"Should not add header with username"
,
func
()
{
// Get will return empty string even if header is not set
So
(
req
.
Header
.
Get
(
"X-Grafana-User"
),
ShouldEqual
,
""
)
})
})
Convey
(
"When SendUserHeader config is enabled but user is anonymous"
,
t
,
func
()
{
req
:=
getPluginProxiedRequest
(
&
m
.
ReqContext
{
SignedInUser
:
&
m
.
SignedInUser
{
IsAnonymous
:
true
},
},
&
setting
.
Cfg
{
SendUserHeader
:
true
},
)
Convey
(
"Should not add header with username"
,
func
()
{
// Get will return empty string even if header is not set
So
(
req
.
Header
.
Get
(
"X-Grafana-User"
),
ShouldEqual
,
""
)
})
})
}
// getPluginProxiedRequest is a helper for easier setup of tests based on global config and ReqContext.
func
getPluginProxiedRequest
(
ctx
*
m
.
ReqContext
,
cfg
*
setting
.
Cfg
)
*
http
.
Request
{
route
:=
&
plugins
.
AppPluginRoute
{}
proxy
:=
NewApiPluginProxy
(
ctx
,
""
,
route
,
""
,
cfg
)
req
,
err
:=
http
.
NewRequest
(
http
.
MethodGet
,
"http://grafana.com/sub"
,
nil
)
So
(
err
,
ShouldBeNil
)
proxy
.
Director
(
req
)
return
req
}
pkg/setting/setting.go
View file @
8221c427
...
...
@@ -242,6 +242,9 @@ type Cfg struct {
// User
EditorsCanOwn
bool
// Dataproxy
SendUserHeader
bool
// DistributedCache
RemoteCacheOptions
*
RemoteCacheOptions
}
...
...
@@ -604,6 +607,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
dataproxy
:=
iniFile
.
Section
(
"dataproxy"
)
DataProxyLogging
=
dataproxy
.
Key
(
"logging"
)
.
MustBool
(
false
)
DataProxyTimeout
=
dataproxy
.
Key
(
"timeout"
)
.
MustInt
(
30
)
cfg
.
SendUserHeader
=
dataproxy
.
Key
(
"send_user_header"
)
.
MustBool
(
false
)
// read security settings
security
:=
iniFile
.
Section
(
"security"
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment