mirror of
https://github.com/grafana/grafana.git
synced 2025-08-02 22:59:43 +08:00
Canvas: Allow API calls to grafana origin (#91822)
* allow post URL * check for config * allow relative paths * add allowed internal pattern; add checks for method * update defaults.ini * add custom header * update config comment * use globbing, switch to older middleware - deprecated call * add codeowner * update to use current api, add test * update fall through logic * Update pkg/middleware/validate_action_url.go Co-authored-by: Dan Cech <dcech@grafana.com> * Update pkg/middleware/validate_action_url.go Co-authored-by: Dan Cech <dcech@grafana.com> * add more tests * Update pkg/middleware/validate_action_url_test.go Co-authored-by: Dan Cech <dcech@grafana.com> * fix request headers * add additional tests for all verbs * fix request headers++ * throw error when method is unknown --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com> Co-authored-by: Brian Gann <bkgann@gmail.com> Co-authored-by: Brian Gann <briangann@users.noreply.github.com> Co-authored-by: Dan Cech <dcech@grafana.com>
This commit is contained in:
119
pkg/middleware/validate_action_url.go
Normal file
119
pkg/middleware/validate_action_url.go
Normal file
@ -0,0 +1,119 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidAllowedURL = func(url string) error {
|
||||
return fmt.Errorf("action URL '%s' is invalid", url)
|
||||
}
|
||||
)
|
||||
|
||||
type errorWithStatus struct {
|
||||
Underlying error
|
||||
HTTPStatus int
|
||||
}
|
||||
|
||||
func (e errorWithStatus) Error() string {
|
||||
return e.Underlying.Error()
|
||||
}
|
||||
|
||||
func (e errorWithStatus) Unwrap() error {
|
||||
return e.Underlying
|
||||
}
|
||||
|
||||
func ValidateActionUrl(cfg *setting.Cfg, logger log.Logger) func(http.Handler) http.Handler {
|
||||
// get the urls allowed from server config
|
||||
allGlobs, globErr := cacheGlobs(cfg.ActionsAllowPostURL)
|
||||
if globErr != nil {
|
||||
logger.Error("invalid glob settings in config section [security] actions_allow_post_url", "url", cfg.ActionsAllowPostURL)
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := contexthandler.FromContext(r.Context())
|
||||
// if no header
|
||||
// return nil
|
||||
// check if action header exists
|
||||
action := ctx.Req.Header.Get("X-Grafana-Action")
|
||||
if action == "" {
|
||||
// header not found, this is not an action request
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if globErr != nil {
|
||||
http.Error(w, "check server logs for glob configuration failure", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
matchErr := check(ctx, allGlobs, logger)
|
||||
if matchErr != nil {
|
||||
http.Error(w, matchErr.Error(), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
// no errors fall through
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// check
|
||||
// Detects header for action urls and compares to globbed pattern list
|
||||
// returns true if allowed
|
||||
func check(ctx *contextmodel.ReqContext, allGlobs *[]glob.Glob, logger log.Logger) *errorWithStatus {
|
||||
// only process POST and PUT
|
||||
if ctx.Req.Method != http.MethodPost && ctx.Req.Method != http.MethodPut {
|
||||
return &errorWithStatus{
|
||||
Underlying: fmt.Errorf("method not allowed for path %s", ctx.Req.URL),
|
||||
HTTPStatus: http.StatusMethodNotAllowed,
|
||||
}
|
||||
}
|
||||
// for each split config
|
||||
// if matches glob
|
||||
// return nil
|
||||
urlToCheck := ctx.Req.URL
|
||||
if matchesAllowedPath(allGlobs, urlToCheck.Path) {
|
||||
return nil
|
||||
}
|
||||
logger.Warn("POST/PUT to path not allowed", "url", urlToCheck)
|
||||
// return some error
|
||||
return &errorWithStatus{
|
||||
Underlying: fmt.Errorf("method POST/PUT not allowed for path %s", urlToCheck),
|
||||
HTTPStatus: http.StatusMethodNotAllowed,
|
||||
}
|
||||
}
|
||||
|
||||
func matchesAllowedPath(allGlobs *[]glob.Glob, pathToCheck string) bool {
|
||||
logger.Debug("Checking url", "actions", pathToCheck)
|
||||
for _, rule := range *allGlobs {
|
||||
logger.Debug("Checking match", "actions", rule)
|
||||
if rule.Match(pathToCheck) {
|
||||
// allowed
|
||||
logger.Debug("POST/PUT call matches allow configuration settings")
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cacheGlobs(actionsAllowPostURL string) (*[]glob.Glob, error) {
|
||||
allowedUrls := util.SplitString(actionsAllowPostURL)
|
||||
allGlobs := make([]glob.Glob, 0)
|
||||
for _, i := range allowedUrls {
|
||||
g, err := glob.Compile(i)
|
||||
if err != nil {
|
||||
return nil, errInvalidAllowedURL(err.Error())
|
||||
}
|
||||
allGlobs = append(allGlobs, g)
|
||||
}
|
||||
return &allGlobs, nil
|
||||
}
|
Reference in New Issue
Block a user