|
|
|
|
@ -2,6 +2,7 @@ package pluginproxy |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"bytes" |
|
|
|
|
"encoding/json" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"html/template" |
|
|
|
|
@ -10,6 +11,7 @@ import ( |
|
|
|
|
"net/http" |
|
|
|
|
"net/http/httputil" |
|
|
|
|
"net/url" |
|
|
|
|
"strconv" |
|
|
|
|
"strings" |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
@ -23,9 +25,19 @@ import ( |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
logger log.Logger = log.New("data-proxy-log") |
|
|
|
|
logger log.Logger = log.New("data-proxy-log") |
|
|
|
|
client *http.Client = &http.Client{ |
|
|
|
|
Timeout: time.Second * 30, |
|
|
|
|
Transport: &http.Transport{Proxy: http.ProxyFromEnvironment}, |
|
|
|
|
} |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
type jwtToken struct { |
|
|
|
|
ExpiresOn time.Time `json:"-"` |
|
|
|
|
ExpiresOnString string `json:"expires_on"` |
|
|
|
|
AccessToken string `json:"access_token"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type DataSourceProxy struct { |
|
|
|
|
ds *m.DataSource |
|
|
|
|
ctx *middleware.Context |
|
|
|
|
@ -229,8 +241,6 @@ func checkWhiteList(c *middleware.Context, host string) bool { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (proxy *DataSourceProxy) applyRoute(req *http.Request) { |
|
|
|
|
logger.Info("ApplyDataSourceRouteRules", "route", proxy.route.Path, "proxyPath", proxy.proxyPath) |
|
|
|
|
|
|
|
|
|
proxy.proxyPath = strings.TrimPrefix(proxy.proxyPath, proxy.route.Path) |
|
|
|
|
|
|
|
|
|
data := templateData{ |
|
|
|
|
@ -238,8 +248,6 @@ func (proxy *DataSourceProxy) applyRoute(req *http.Request) { |
|
|
|
|
SecureJsonData: proxy.ds.SecureJsonData.Decrypt(), |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
logger.Info("Apply Route Rule", "rule", proxy.route.Path) |
|
|
|
|
|
|
|
|
|
routeUrl, err := url.Parse(proxy.route.Url) |
|
|
|
|
if err != nil { |
|
|
|
|
logger.Error("Error parsing plugin route url") |
|
|
|
|
@ -254,25 +262,80 @@ func (proxy *DataSourceProxy) applyRoute(req *http.Request) { |
|
|
|
|
if err := addHeaders(&req.Header, proxy.route, data); err != nil { |
|
|
|
|
logger.Error("Failed to render plugin headers", "error", err) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func addHeaders(reqHeaders *http.Header, route *plugins.AppPluginRoute, data templateData) error { |
|
|
|
|
for _, header := range route.Headers { |
|
|
|
|
var contentBuf bytes.Buffer |
|
|
|
|
t, err := template.New("content").Parse(header.Content) |
|
|
|
|
if err != nil { |
|
|
|
|
return errors.New(fmt.Sprintf("could not parse header content template for header %s.", header.Name)) |
|
|
|
|
if proxy.route.TokenAuth != nil { |
|
|
|
|
if token, err := proxy.getAccessToken(data); err != nil { |
|
|
|
|
logger.Error("Failed to get access token", "error", err) |
|
|
|
|
} else { |
|
|
|
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
err = t.Execute(&contentBuf, data) |
|
|
|
|
if err != nil { |
|
|
|
|
return errors.New(fmt.Sprintf("failed to execute header content template for header %s.", header.Name)) |
|
|
|
|
func (proxy *DataSourceProxy) getAccessToken(data templateData) (string, error) { |
|
|
|
|
urlInterpolated, err := interpolateString(proxy.route.TokenAuth.Url, data) |
|
|
|
|
if err != nil { |
|
|
|
|
return "", err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
logger.Info("client secret", "ClientSecret", data.SecureJsonData["clientSecret"]) |
|
|
|
|
params := make(url.Values) |
|
|
|
|
for key, value := range proxy.route.TokenAuth.Params { |
|
|
|
|
if interpolatedParam, err := interpolateString(value, data); err != nil { |
|
|
|
|
return "", err |
|
|
|
|
} else { |
|
|
|
|
logger.Info("param", key, interpolatedParam) |
|
|
|
|
params.Add(key, interpolatedParam) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
value := contentBuf.String() |
|
|
|
|
getTokenReq, _ := http.NewRequest("POST", urlInterpolated, bytes.NewBufferString(params.Encode())) |
|
|
|
|
getTokenReq.Header.Add("Content-Type", "application/x-www-form-urlencoded") |
|
|
|
|
getTokenReq.Header.Add("Content-Length", strconv.Itoa(len(params.Encode()))) |
|
|
|
|
|
|
|
|
|
logger.Info("Adding headers", "name", header.Name, "value", value) |
|
|
|
|
reqHeaders.Add(header.Name, value) |
|
|
|
|
resp, err := client.Do(getTokenReq) |
|
|
|
|
if err != nil { |
|
|
|
|
return "", err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
defer resp.Body.Close() |
|
|
|
|
respData, err := ioutil.ReadAll(resp.Body) |
|
|
|
|
logger.Info("Resp", "resp", string(respData)) |
|
|
|
|
|
|
|
|
|
var token jwtToken |
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&token); err != nil { |
|
|
|
|
return "", err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
expiresOnEpoch, _ := strconv.ParseInt(token.ExpiresOnString, 10, 64) |
|
|
|
|
token.ExpiresOn = time.Unix(expiresOnEpoch, 0) |
|
|
|
|
|
|
|
|
|
logger.Debug("Got new access token", "ExpiresOn", token.ExpiresOn) |
|
|
|
|
return "", nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func interpolateString(text string, data templateData) (string, error) { |
|
|
|
|
t, err := template.New("content").Parse(text) |
|
|
|
|
if err != nil { |
|
|
|
|
return "", errors.New(fmt.Sprintf("Could not parse template %s.", text)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var contentBuf bytes.Buffer |
|
|
|
|
err = t.Execute(&contentBuf, data) |
|
|
|
|
if err != nil { |
|
|
|
|
return "", errors.New(fmt.Sprintf("Failed to execute template %s.", text)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return contentBuf.String(), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func addHeaders(reqHeaders *http.Header, route *plugins.AppPluginRoute, data templateData) error { |
|
|
|
|
for _, header := range route.Headers { |
|
|
|
|
interpolated, err := interpolateString(header.Content, data) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
reqHeaders.Add(header.Name, interpolated) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
|