| 
									
										
										
										
											2016-02-10 23:43:35 +08:00
										 |  |  | package pluginproxy | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2022-04-11 19:17:08 +08:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2016-02-10 23:43:35 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2022-08-10 21:37:51 +08:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2016-02-10 23:43:35 +08:00
										 |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/infra/tracing" | 
					
						
							| 
									
										
										
										
											2016-02-10 23:43:35 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins" | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | 
					
						
							| 
									
										
										
										
											2023-03-08 00:22:30 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" | 
					
						
							| 
									
										
										
										
											2021-11-05 00:47:21 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/secrets" | 
					
						
							| 
									
										
										
										
											2020-03-03 18:45:16 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							| 
									
										
										
										
											2016-02-10 23:43:35 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/util" | 
					
						
							| 
									
										
										
										
											2020-03-03 18:45:16 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/util/proxyutil" | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/web" | 
					
						
							|  |  |  | 	"go.opentelemetry.io/otel/attribute" | 
					
						
							| 
									
										
										
										
											2016-02-10 23:43:35 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | type PluginProxy struct { | 
					
						
							|  |  |  | 	ps             *pluginsettings.DTO | 
					
						
							|  |  |  | 	pluginRoutes   []*plugins.Route | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | 	ctx            *contextmodel.ReqContext | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 	proxyPath      string | 
					
						
							|  |  |  | 	matchedRoute   *plugins.Route | 
					
						
							|  |  |  | 	cfg            *setting.Cfg | 
					
						
							|  |  |  | 	secretsService secrets.Service | 
					
						
							|  |  |  | 	tracer         tracing.Tracer | 
					
						
							|  |  |  | 	transport      *http.Transport | 
					
						
							| 
									
										
										
										
											2016-02-10 23:43:35 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | // NewPluginProxy creates a plugin proxy.
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func NewPluginProxy(ps *pluginsettings.DTO, routes []*plugins.Route, ctx *contextmodel.ReqContext, | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 	proxyPath string, cfg *setting.Cfg, secretsService secrets.Service, tracer tracing.Tracer, | 
					
						
							|  |  |  | 	transport *http.Transport) (*PluginProxy, error) { | 
					
						
							|  |  |  | 	return &PluginProxy{ | 
					
						
							|  |  |  | 		ps:             ps, | 
					
						
							|  |  |  | 		pluginRoutes:   routes, | 
					
						
							|  |  |  | 		ctx:            ctx, | 
					
						
							|  |  |  | 		proxyPath:      proxyPath, | 
					
						
							|  |  |  | 		cfg:            cfg, | 
					
						
							|  |  |  | 		secretsService: secretsService, | 
					
						
							|  |  |  | 		tracer:         tracer, | 
					
						
							|  |  |  | 		transport:      transport, | 
					
						
							|  |  |  | 	}, nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-04-11 19:17:08 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | func (proxy *PluginProxy) HandleRequest() { | 
					
						
							|  |  |  | 	// found route if there are any
 | 
					
						
							|  |  |  | 	for _, route := range proxy.pluginRoutes { | 
					
						
							|  |  |  | 		// method match
 | 
					
						
							|  |  |  | 		if route.Method != "" && route.Method != "*" && route.Method != proxy.ctx.Req.Method { | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2020-11-17 17:56:42 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 		t := web.NewTree() | 
					
						
							|  |  |  | 		t.Add(route.Path, nil) | 
					
						
							|  |  |  | 		_, params, isMatch := t.Match(proxy.proxyPath) | 
					
						
							| 
									
										
										
										
											2021-10-07 22:33:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 		if !isMatch { | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2020-11-17 17:56:42 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 		if route.ReqRole.IsValid() { | 
					
						
							|  |  |  | 			if !proxy.ctx.HasUserRole(route.ReqRole) { | 
					
						
							|  |  |  | 				proxy.ctx.JsonApiErr(http.StatusForbidden, "plugin proxy route access denied", nil) | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-11-17 17:56:42 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if path, exists := params["*"]; exists { | 
					
						
							|  |  |  | 			proxy.proxyPath = path | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			proxy.proxyPath = "" | 
					
						
							| 
									
										
										
										
											2016-02-10 23:43:35 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 		proxy.matchedRoute = route | 
					
						
							|  |  |  | 		break | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-02-10 23:43:35 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 	if proxy.matchedRoute == nil { | 
					
						
							|  |  |  | 		proxy.ctx.JsonApiErr(http.StatusNotFound, "plugin route match not found", nil) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-03-14 20:04:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 	proxyErrorLogger := logger.New( | 
					
						
							|  |  |  | 		"userId", proxy.ctx.UserID, | 
					
						
							|  |  |  | 		"orgId", proxy.ctx.OrgID, | 
					
						
							|  |  |  | 		"uname", proxy.ctx.Login, | 
					
						
							|  |  |  | 		"path", proxy.ctx.Req.URL.Path, | 
					
						
							|  |  |  | 		"remote_addr", proxy.ctx.RemoteAddr(), | 
					
						
							|  |  |  | 		"referer", proxy.ctx.Req.Referer(), | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2021-03-31 22:38:35 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 	reverseProxy := proxyutil.NewReverseProxy( | 
					
						
							|  |  |  | 		proxyErrorLogger, | 
					
						
							|  |  |  | 		proxy.director, | 
					
						
							|  |  |  | 		proxyutil.WithTransport(proxy.transport), | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	proxy.logRequest() | 
					
						
							|  |  |  | 	ctx, span := proxy.tracer.Start(proxy.ctx.Req.Context(), "plugin reverse proxy") | 
					
						
							|  |  |  | 	defer span.End() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	proxy.ctx.Req = proxy.ctx.Req.WithContext(ctx) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	span.SetAttributes("user", proxy.ctx.SignedInUser.Login, attribute.Key("user").String(proxy.ctx.SignedInUser.Login)) | 
					
						
							|  |  |  | 	span.SetAttributes("org_id", proxy.ctx.SignedInUser.OrgID, attribute.Key("org_id").Int64(proxy.ctx.SignedInUser.OrgID)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	proxy.tracer.Inject(ctx, proxy.ctx.Req.Header, span) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	reverseProxy.ServeHTTP(proxy.ctx.Resp, proxy.ctx.Req) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (proxy PluginProxy) director(req *http.Request) { | 
					
						
							|  |  |  | 	secureJsonData, err := proxy.secretsService.DecryptJsonData(proxy.ctx.Req.Context(), proxy.ps.SecureJSONData) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		proxy.ctx.JsonApiErr(500, "Failed to decrypt plugin settings", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	data := templateData{ | 
					
						
							|  |  |  | 		JsonData:       proxy.ps.JSONData, | 
					
						
							|  |  |  | 		SecureJsonData: secureJsonData, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	interpolatedURL, err := interpolateString(proxy.matchedRoute.URL, data) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		proxy.ctx.JsonApiErr(500, "Could not interpolate plugin route url", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	targetURL, err := url.Parse(interpolatedURL) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		proxy.ctx.JsonApiErr(500, "Could not parse url", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	req.URL.Scheme = targetURL.Scheme | 
					
						
							|  |  |  | 	req.URL.Host = targetURL.Host | 
					
						
							|  |  |  | 	req.Host = targetURL.Host | 
					
						
							|  |  |  | 	req.URL.Path = util.JoinURLFragments(targetURL.Path, proxy.proxyPath) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// clear cookie headers
 | 
					
						
							|  |  |  | 	req.Header.Del("Cookie") | 
					
						
							|  |  |  | 	req.Header.Del("Set-Cookie") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create a HTTP header with the context in it.
 | 
					
						
							|  |  |  | 	ctxJSON, err := json.Marshal(proxy.ctx.SignedInUser) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		proxy.ctx.JsonApiErr(500, "failed to marshal context to json.", err) | 
					
						
							|  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2016-02-10 23:43:35 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 	req.Header.Set("X-Grafana-Context", string(ctxJSON)) | 
					
						
							| 
									
										
										
										
											2022-04-11 19:17:08 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-15 22:28:25 +08:00
										 |  |  | 	proxyutil.ApplyUserHeader(proxy.cfg.SendUserHeader, req, proxy.ctx.SignedInUser) | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if err := addHeaders(&req.Header, proxy.matchedRoute, data); err != nil { | 
					
						
							|  |  |  | 		proxy.ctx.JsonApiErr(500, "Failed to render plugin headers", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := setBodyContent(req, proxy.matchedRoute, data); err != nil { | 
					
						
							| 
									
										
										
										
											2022-09-21 00:32:06 +08:00
										 |  |  | 		logger.FromContext(req.Context()).Error("Failed to set plugin route body content", "error", err) | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-02-09 20:44:38 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | func (proxy PluginProxy) logRequest() { | 
					
						
							|  |  |  | 	if !proxy.cfg.DataProxyLogging { | 
					
						
							| 
									
										
										
										
											2022-04-11 19:17:08 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var body string | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 	if proxy.ctx.Req.Body != nil { | 
					
						
							|  |  |  | 		buffer, err := io.ReadAll(proxy.ctx.Req.Body) | 
					
						
							| 
									
										
										
										
											2022-04-11 19:17:08 +08:00
										 |  |  | 		if err == nil { | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 			proxy.ctx.Req.Body = io.NopCloser(bytes.NewBuffer(buffer)) | 
					
						
							| 
									
										
										
										
											2022-04-11 19:17:08 +08:00
										 |  |  | 			body = string(buffer) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-02-09 20:44:38 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-21 00:32:06 +08:00
										 |  |  | 	ctxLogger := logger.FromContext(proxy.ctx.Req.Context()) | 
					
						
							|  |  |  | 	ctxLogger.Info("Proxying incoming request", | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 		"userid", proxy.ctx.UserID, | 
					
						
							|  |  |  | 		"orgid", proxy.ctx.OrgID, | 
					
						
							|  |  |  | 		"username", proxy.ctx.Login, | 
					
						
							|  |  |  | 		"app", proxy.ps.PluginID, | 
					
						
							|  |  |  | 		"uri", proxy.ctx.Req.RequestURI, | 
					
						
							|  |  |  | 		"method", proxy.ctx.Req.Method, | 
					
						
							| 
									
										
										
										
											2022-04-11 19:17:08 +08:00
										 |  |  | 		"body", body) | 
					
						
							| 
									
										
										
										
											2016-02-10 23:43:35 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-08-23 19:05:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | type templateData struct { | 
					
						
							|  |  |  | 	JsonData       map[string]interface{} | 
					
						
							|  |  |  | 	SecureJsonData map[string]string | 
					
						
							|  |  |  | } |