summaryrefslogtreecommitdiffstats
path: root/config/security/securityConfig.go
diff options
context:
space:
mode:
Diffstat (limited to 'config/security/securityConfig.go')
-rw-r--r--config/security/securityConfig.go227
1 files changed, 227 insertions, 0 deletions
diff --git a/config/security/securityConfig.go b/config/security/securityConfig.go
new file mode 100644
index 000000000..09c5cb625
--- /dev/null
+++ b/config/security/securityConfig.go
@@ -0,0 +1,227 @@
+// Copyright 2018 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package security
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "reflect"
+ "strings"
+
+ "github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/common/types"
+ "github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/parser"
+ "github.com/gohugoio/hugo/parser/metadecoders"
+ "github.com/mitchellh/mapstructure"
+)
+
+const securityConfigKey = "security"
+
+// DefaultConfig holds the default security policy.
+var DefaultConfig = Config{
+ Exec: Exec{
+ Allow: NewWhitelist(
+ "^dart-sass-embedded$",
+ "^go$", // for Go Modules
+ "^npx$", // used by all Node tools (Babel, PostCSS).
+ "^postcss$",
+ ),
+ // These have been tested to work with Hugo's external programs
+ // on Windows, Linux and MacOS.
+ OsEnv: NewWhitelist("(?i)^(PATH|PATHEXT|APPDATA|TMP|TEMP|TERM)$"),
+ },
+ Funcs: Funcs{
+ Getenv: NewWhitelist("^HUGO_"),
+ },
+ HTTP: HTTP{
+ URLs: NewWhitelist(".*"),
+ Methods: NewWhitelist("(?i)GET|POST"),
+ },
+}
+
+// Config is the top level security config.
+type Config struct {
+ // Restricts access to os.Exec.
+ Exec Exec `json:"exec"`
+
+ // Restricts access to certain template funcs.
+ Funcs Funcs `json:"funcs"`
+
+ // Restricts access to resources.Get, getJSON, getCSV.
+ HTTP HTTP `json:"http"`
+
+ // Allow inline shortcodes
+ EnableInlineShortcodes bool `json:"enableInlineShortcodes"`
+}
+
+// Exec holds os/exec policies.
+type Exec struct {
+ Allow Whitelist `json:"allow"`
+ OsEnv Whitelist `json:"osEnv"`
+}
+
+// Funcs holds template funcs policies.
+type Funcs struct {
+ // OS env keys allowed to query in os.Getenv.
+ Getenv Whitelist `json:"getenv"`
+}
+
+type HTTP struct {
+ // URLs to allow in remote HTTP (resources.Get, getJSON, getCSV).
+ URLs Whitelist `json:"urls"`
+
+ // HTTP methods to allow.
+ Methods Whitelist `json:"methods"`
+}
+
+// ToTOML converts c to TOML with [security] as the root.
+func (c Config) ToTOML() string {
+ sec := c.ToSecurityMap()
+
+ var b bytes.Buffer
+
+ if err := parser.InterfaceToConfig(sec, metadecoders.TOML, &b); err != nil {
+ panic(err)
+ }
+
+ return strings.TrimSpace(b.String())
+}
+
+func (c Config) CheckAllowedExec(name string) error {
+ if !c.Exec.Allow.Accept(name) {
+ return &AccessDeniedError{
+ name: name,
+ path: "security.exec.allow",
+ policies: c.ToTOML(),
+ }
+ }
+ return nil
+
+}
+
+func (c Config) CheckAllowedGetEnv(name string) error {
+ if !c.Funcs.Getenv.Accept(name) {
+ return &AccessDeniedError{
+ name: name,
+ path: "security.funcs.getenv",
+ policies: c.ToTOML(),
+ }
+ }
+ return nil
+}
+
+func (c Config) CheckAllowedHTTPURL(url string) error {
+ if !c.HTTP.URLs.Accept(url) {
+ return &AccessDeniedError{
+ name: url,
+ path: "security.http.urls",
+ policies: c.ToTOML(),
+ }
+ }
+ return nil
+}
+
+func (c Config) CheckAllowedHTTPMethod(method string) error {
+ if !c.HTTP.Methods.Accept(method) {
+ return &AccessDeniedError{
+ name: method,
+ path: "security.http.method",
+ policies: c.ToTOML(),
+ }
+ }
+ return nil
+}
+
+// ToSecurityMap converts c to a map with 'security' as the root key.
+func (c Config) ToSecurityMap() map[string]interface{} {
+ // Take it to JSON and back to get proper casing etc.
+ asJson, err := json.Marshal(c)
+ herrors.Must(err)
+ m := make(map[string]interface{})
+ herrors.Must(json.Unmarshal(asJson, &m))
+
+ // Add the root
+ sec := map[string]interface{}{
+ "security": m,
+ }
+ return sec
+
+}
+
+// DecodeConfig creates a privacy Config from a given Hugo configuration.
+func DecodeConfig(cfg config.Provider) (Config, error) {
+ sc := DefaultConfig
+ if cfg.IsSet(securityConfigKey) {
+ m := cfg.GetStringMap(securityConfigKey)
+ dec, err := mapstructure.NewDecoder(
+ &mapstructure.DecoderConfig{
+ WeaklyTypedInput: true,
+ Result: &sc,
+ DecodeHook: stringSliceToWhitelistHook(),
+ },
+ )
+ if err != nil {
+ return sc, err
+ }
+
+ if err = dec.Decode(m); err != nil {
+ return sc, err
+ }
+ }
+
+ if !sc.EnableInlineShortcodes {
+ // Legacy
+ sc.EnableInlineShortcodes = cfg.GetBool("enableInlineShortcodes")
+ }
+
+ return sc, nil
+
+}
+
+func stringSliceToWhitelistHook() mapstructure.DecodeHookFuncType {
+ return func(
+ f reflect.Type,
+ t reflect.Type,
+ data interface{}) (interface{}, error) {
+
+ if t != reflect.TypeOf(Whitelist{}) {
+ return data, nil
+ }
+
+ wl := types.ToStringSlicePreserveString(data)
+
+ return NewWhitelist(wl...), nil
+
+ }
+}
+
+// AccessDeniedError represents a security policy conflict.
+type AccessDeniedError struct {
+ path string
+ name string
+ policies string
+}
+
+func (e *AccessDeniedError) Error() string {
+ return fmt.Sprintf("access denied: %q is not whitelisted in policy %q; the current security configuration is:\n\n%s\n\n", e.name, e.path, e.policies)
+}
+
+// IsAccessDenied reports whether err is an AccessDeniedError
+func IsAccessDenied(err error) bool {
+ var notFoundErr *AccessDeniedError
+ return errors.As(err, &notFoundErr)
+}