feat: Chromium underlying requests are not tested againt the allow/deny lists

This commit is contained in:
Julien Neuhart
2021-08-22 16:35:04 +02:00
parent 0e417430b8
commit da170b9b16
6 changed files with 95 additions and 13 deletions

View File

@@ -45,7 +45,7 @@ CHROMIUM_USER_AGENT=
CHROMIUM_INCOGNITO=false CHROMIUM_INCOGNITO=false
CHROMIUM_IGNORE_CERTIFICATE_ERRORS=false CHROMIUM_IGNORE_CERTIFICATE_ERRORS=false
CHROMIUM_ALLOW_LIST= CHROMIUM_ALLOW_LIST=
CHROMIUM_DENY_LIST= CHROMIUM_DENY_LIST="^file:///[^tmp].*"
CHROMIUM_DISABLE_ROUTES=false CHROMIUM_DISABLE_ROUTES=false
LIBREOFFICE_DISABLES_ROUTES=false LIBREOFFICE_DISABLES_ROUTES=false
LOG_LEVEL=info LOG_LEVEL=info

View File

@@ -10,6 +10,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/chromedp/cdproto/fetch"
"github.com/chromedp/cdproto/network" "github.com/chromedp/cdproto/network"
"github.com/chromedp/cdproto/page" "github.com/chromedp/cdproto/page"
"github.com/chromedp/chromedp" "github.com/chromedp/chromedp"
@@ -183,7 +184,7 @@ func (mod Chromium) Descriptor() gotenberg.ModuleDescriptor {
fs.Bool("chromium-incognito", false, "Start Chromium with incognito mode") fs.Bool("chromium-incognito", false, "Start Chromium with incognito mode")
fs.Bool("chromium-ignore-certificate-errors", false, "Ignore the certificate errors") fs.Bool("chromium-ignore-certificate-errors", false, "Ignore the certificate errors")
fs.String("chromium-allow-list", "", "Set the allowed URLs for Chromium using a regular expression") fs.String("chromium-allow-list", "", "Set the allowed URLs for Chromium using a regular expression")
fs.String("chromium-deny-list", "", "Set the denied URLs for Chromium using a regular expression") fs.String("chromium-deny-list", "^file:///[^tmp].*", "Set the denied URLs for Chromium using a regular expression")
fs.Bool("chromium-disable-routes", false, "Disable the routes") fs.Bool("chromium-disable-routes", false, "Disable the routes")
return fs return fs
@@ -286,6 +287,7 @@ func (mod Chromium) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath
taskCtx, cancel := chromedp.NewContext(allocatorCtx) taskCtx, cancel := chromedp.NewContext(allocatorCtx)
defer cancel() defer cancel()
// We validate the "main" URL against our allow / deny lists.
if !mod.allowList.MatchString(URL) { if !mod.allowList.MatchString(URL) {
return fmt.Errorf("'%s' does not match the expression from the allowed list: %w", URL, ErrURLNotAuthorized) return fmt.Errorf("'%s' does not match the expression from the allowed list: %w", URL, ErrURLNotAuthorized)
} }
@@ -295,8 +297,13 @@ func (mod Chromium) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath
} }
printToPDF := func(URL string, options Options, result *[]byte) chromedp.Tasks { printToPDF := func(URL string, options Options, result *[]byte) chromedp.Tasks {
// We validate the underlying requests against our allow / deny lists.
// If a request does not pass the validation, we make it fail.
listenForEventRequestPaused(taskCtx, logger, mod.allowList, mod.denyList)
return chromedp.Tasks{ return chromedp.Tasks{
network.Enable(), network.Enable(),
fetch.Enable(),
chromedp.ActionFunc(func(ctx context.Context) error { chromedp.ActionFunc(func(ctx context.Context) error {
if len(options.ExtraHTTPHeaders) == 0 { if len(options.ExtraHTTPHeaders) == 0 {
logger.Debug("no extra HTTP headers") logger.Debug("no extra HTTP headers")

View File

@@ -222,26 +222,25 @@ func TestChromium_PDF(t *testing.T) {
expectErr bool expectErr bool
}{ }{
{ {
URL: "https://google.com", URL: "file:///tests/test/testdata/chromium/html/sample4/index.html",
allowList: regexp.MustCompile("https://google.fr"), allowList: regexp.MustCompile("file:///tmp/*"),
expectErr: true, expectErr: true,
}, },
{ {
URL: "https://google.com", URL: "file:///tests/test/testdata/chromium/html/sample4/index.html",
denyList: regexp.MustCompile("https://google.com"), denyList: regexp.MustCompile("file:///tests/*"),
expectErr: true, expectErr: true,
}, },
{ {
URL: "", URL: "file:///tests/test/testdata/chromium/html/sample4/index.html",
options: Options{ options: Options{
ExtraHTTPHeaders: map[string]string{ ExtraHTTPHeaders: map[string]string{
"foo": "bar", "foo": "bar",
}, },
}, },
expectErr: true,
}, },
{ {
URL: "https://google.com", URL: "file:///tests/test/testdata/chromium/html/sample4/index.html",
options: Options{ options: Options{
WaitDelay: time.Duration(1) * time.Nanosecond, WaitDelay: time.Duration(1) * time.Nanosecond,
}, },
@@ -262,21 +261,21 @@ func TestChromium_PDF(t *testing.T) {
}, },
}, },
{ {
URL: "https://google.com", URL: "file:///tests/test/testdata/chromium/html/sample4/index.html",
options: Options{ options: Options{
MarginBottom: 100, MarginBottom: 100,
}, },
expectErr: true, expectErr: true,
}, },
{ {
URL: "https://google.com", URL: "file:///tests/test/testdata/chromium/html/sample4/index.html",
options: Options{ options: Options{
PageRanges: "foo", PageRanges: "foo",
}, },
expectErr: true, expectErr: true,
}, },
{ {
URL: "https://google.com", URL: "file:///tests/test/testdata/chromium/html/sample4/index.html",
userAgent: "foo", userAgent: "foo",
incognito: true, incognito: true,
ignoreCertificateErrors: true, ignoreCertificateErrors: true,
@@ -285,7 +284,15 @@ func TestChromium_PDF(t *testing.T) {
URL: "file:///tests/test/testdata/chromium/html/sample1/index.html", URL: "file:///tests/test/testdata/chromium/html/sample1/index.html",
}, },
{ {
URL: "https://google.com", URL: "file:///tests/test/testdata/chromium/html/sample3/index.html",
allowList: regexp.MustCompile("file:///tests/*"),
},
{
URL: "file:///tests/test/testdata/chromium/html/sample3/index.html",
denyList: regexp.MustCompile("file:///etc/*"),
},
{
URL: "file:///tests/test/testdata/chromium/html/sample4/index.html",
options: Options{ options: Options{
HeaderTemplate: func() string { HeaderTemplate: func() string {
b, err := ioutil.ReadFile("/tests/test/testdata/chromium/url/sample2/header.html") b, err := ioutil.ReadFile("/tests/test/testdata/chromium/url/sample2/header.html")

View File

@@ -3,7 +3,10 @@ package chromium
import ( import (
"context" "context"
"fmt" "fmt"
"regexp"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/cdproto/fetch"
"github.com/chromedp/cdproto/network" "github.com/chromedp/cdproto/network"
"github.com/chromedp/cdproto/page" "github.com/chromedp/cdproto/page"
"github.com/chromedp/chromedp" "github.com/chromedp/chromedp"
@@ -11,6 +14,51 @@ import (
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
// listenForEventRequestPaused listens for requests to check if they are
// allowed or not.
func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowList *regexp.Regexp, denyList *regexp.Regexp) {
chromedp.ListenTarget(ctx, func(ev interface{}) {
switch e := ev.(type) {
case *fetch.EventRequestPaused:
go func() {
logger.Debug(fmt.Sprintf("event EventRequestPaused fired for '%s'", e.Request.URL))
allow := true
if !allowList.MatchString(e.Request.URL) {
logger.Warn(fmt.Sprintf("'%s' does not match the expression from the allowed list", e.Request.URL))
allow = false
}
if denyList.String() != "" && denyList.MatchString(e.Request.URL) {
logger.Warn(fmt.Sprintf("'%s' matches the expression from the denied list", e.Request.URL))
allow = false
}
cctx := chromedp.FromContext(ctx)
executorCtx := cdp.WithExecutor(ctx, cctx.Target)
if allow {
req := fetch.ContinueRequest(e.RequestID)
err := req.Do(executorCtx)
if err != nil {
logger.Error(fmt.Sprintf("continue request: %s", err))
}
return
}
req := fetch.FailRequest(e.RequestID, network.ErrorReasonAccessDenied)
err := req.Do(executorCtx)
if err != nil {
logger.Error(fmt.Sprintf("fail request: %s", err))
}
}()
}
})
}
// waitForEventDomContentEventFired waits until the event DomContentEventFired // waitForEventDomContentEventFired waits until the event DomContentEventFired
// is fired or the context timeout. // is fired or the context timeout.
func waitForEventDomContentEventFired(ctx context.Context, logger *zap.Logger) func() error { func waitForEventDomContentEventFired(ctx context.Context, logger *zap.Logger) func() error {

View File

@@ -0,0 +1,10 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Gutenberg</title>
</head>
<body>
<iframe src='file:///etc/passwd'></iframe>
</body>
</html>

View File

@@ -0,0 +1,10 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Gutenberg</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>