fix(webhook/downloadFrom): better default security and DX for allow / deny lists

This commit is contained in:
Julien Neuhart
2026-03-27 10:52:04 +01:00
parent 06b2b2e10c
commit 8625a4e899
7 changed files with 37 additions and 105 deletions

View File

@@ -55,8 +55,8 @@ type Api struct {
}
type downloadFromConfig struct {
allowList *regexp2.Regexp
denyList *regexp2.Regexp
allowList []*regexp2.Regexp
denyList []*regexp2.Regexp
maxRetry int
disable bool
}
@@ -192,8 +192,8 @@ func (a *Api) Descriptor() gotenberg.ModuleDescriptor {
fs.String("api-root-path", "/", "Set the root path of the API - for service discovery via URL paths")
fs.String("api-trace-header", "Gotenberg-Trace", "Set the header name to use for identifying requests")
fs.Bool("api-enable-basic-auth", false, "Enable basic authentication - will look for the GOTENBERG_API_BASIC_AUTH_USERNAME and GOTENBERG_API_BASIC_AUTH_PASSWORD environment variables")
fs.String("api-download-from-allow-list", "", "Set the allowed URLs for the download from feature using a regular expression")
fs.String("api-download-from-deny-list", "", "Set the denied URLs for the download from feature using a regular expression")
fs.StringSlice("api-download-from-allow-list", []string{}, "Set the allowed URLs for the download from feature using regular expressions - supports multiple values")
fs.StringSlice("api-download-from-deny-list", []string{}, "Set the denied URLs for the download from feature using regular expressions - supports multiple values")
fs.Int("api-download-from-max-retry", 4, "Set the maximum number of retries for the download from feature")
fs.Bool("api-disable-download-from", false, "Disable the download from feature")
fs.Bool("api-disable-health-check-logging", false, "Disable health check logging")
@@ -217,8 +217,8 @@ func (a *Api) Provision(ctx *gotenberg.Context) error {
a.rootPath = flags.MustString("api-root-path")
a.traceHeader = flags.MustString("api-trace-header")
a.downloadFromCfg = downloadFromConfig{
allowList: flags.MustRegexp("api-download-from-allow-list"),
denyList: flags.MustRegexp("api-download-from-deny-list"),
allowList: flags.MustRegexpSlice("api-download-from-allow-list"),
denyList: flags.MustRegexpSlice("api-download-from-deny-list"),
maxRetry: flags.MustInt("api-download-from-max-retry"),
disable: flags.MustBool("api-disable-download-from"),
}

View File

@@ -225,12 +225,7 @@ func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSyst
)
}
err := gotenberg.FilterDeadline(
gotenberg.RegexpToSlice(downloadFromCfg.allowList),
gotenberg.RegexpToSlice(downloadFromCfg.denyList),
dl.Url,
deadline,
)
err := gotenberg.FilterDeadline(downloadFromCfg.allowList, downloadFromCfg.denyList, dl.Url, deadline)
if err != nil {
return fmt.Errorf("filter URL: %w", err)
}

View File

@@ -114,22 +114,12 @@ func webhookMiddleware(w *Webhook) api.Middleware {
// Let's check if the webhook URLs are acceptable according to our
// allowed/denied lists.
err := gotenberg.FilterDeadline(
gotenberg.RegexpToSlice(w.allowList),
gotenberg.RegexpToSlice(w.denyList),
webhookUrl,
deadline,
)
err := gotenberg.FilterDeadline(w.allowList, w.denyList, webhookUrl, deadline)
if err != nil {
return fmt.Errorf("filter webhook URL: %w", err)
}
err = gotenberg.FilterDeadline(
gotenberg.RegexpToSlice(w.errorAllowList),
gotenberg.RegexpToSlice(w.errorDenyList),
webhookErrorUrl,
deadline,
)
err = gotenberg.FilterDeadline(w.errorAllowList, w.errorDenyList, webhookErrorUrl, deadline)
if err != nil {
return fmt.Errorf("filter webhook error URL: %w", err)
}

View File

@@ -19,10 +19,10 @@ func init() {
// to any destinations in an asynchronous fashion.
type Webhook struct {
enableSyncMode bool
allowList *regexp2.Regexp
denyList *regexp2.Regexp
errorAllowList *regexp2.Regexp
errorDenyList *regexp2.Regexp
allowList []*regexp2.Regexp
denyList []*regexp2.Regexp
errorAllowList []*regexp2.Regexp
errorDenyList []*regexp2.Regexp
maxRetry int
retryMinWait time.Duration
retryMaxWait time.Duration
@@ -38,10 +38,10 @@ func (w *Webhook) Descriptor() gotenberg.ModuleDescriptor {
FlagSet: func() *flag.FlagSet {
fs := flag.NewFlagSet("webhook", flag.ExitOnError)
fs.Bool("webhook-enable-sync-mode", false, "Enable synchronous mode for the webhook feature")
fs.String("webhook-allow-list", "", "Set the allowed URLs for the webhook feature using a regular expression")
fs.String("webhook-deny-list", "", "Set the denied URLs for the webhook feature using a regular expression")
fs.String("webhook-error-allow-list", "", "Set the allowed URLs in case of an error for the webhook feature using a regular expression")
fs.String("webhook-error-deny-list", "", "Set the denied URLs in case of an error for the webhook feature using a regular expression")
fs.StringSlice("webhook-allow-list", []string{}, "Set the allowed URLs for the webhook feature using regular expressions - supports multiple values")
fs.StringSlice("webhook-deny-list", []string{}, "Set the denied URLs for the webhook feature using regular expressions - supports multiple values")
fs.StringSlice("webhook-error-allow-list", []string{}, "Set the allowed URLs in case of an error for the webhook feature using regular expressions - supports multiple values")
fs.StringSlice("webhook-error-deny-list", []string{}, "Set the denied URLs in case of an error for the webhook feature using regular expressions - supports multiple values")
fs.Int("webhook-max-retry", 4, "Set the maximum number of retries for the webhook feature")
fs.Duration("webhook-retry-min-wait", time.Duration(1)*time.Second, "Set the minimum duration to wait before trying to call the webhook again")
fs.Duration("webhook-retry-max-wait", time.Duration(30)*time.Second, "Set the maximum duration to wait before trying to call the webhook again")
@@ -58,10 +58,10 @@ func (w *Webhook) Descriptor() gotenberg.ModuleDescriptor {
func (w *Webhook) Provision(ctx *gotenberg.Context) error {
flags := ctx.ParsedFlags()
w.enableSyncMode = flags.MustBool("webhook-enable-sync-mode")
w.allowList = flags.MustRegexp("webhook-allow-list")
w.denyList = flags.MustRegexp("webhook-deny-list")
w.errorAllowList = flags.MustRegexp("webhook-error-allow-list")
w.errorDenyList = flags.MustRegexp("webhook-error-deny-list")
w.allowList = flags.MustRegexpSlice("webhook-allow-list")
w.denyList = flags.MustRegexpSlice("webhook-deny-list")
w.errorAllowList = flags.MustRegexpSlice("webhook-error-allow-list")
w.errorDenyList = flags.MustRegexpSlice("webhook-error-deny-list")
w.maxRetry = flags.MustInt("webhook-max-retry")
w.retryMinWait = flags.MustDuration("webhook-retry-min-wait")
w.retryMaxWait = flags.MustDuration("webhook-retry-max-wait")