hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

securityConfig.go (5392B)

    1 // Copyright 2018 The Hugo Authors. All rights reserved.
    2 //
    3 // Licensed under the Apache License, Version 2.0 (the "License");
    4 // you may not use this file except in compliance with the License.
    5 // You may obtain a copy of the License at
    6 // http://www.apache.org/licenses/LICENSE-2.0
    7 //
    8 // Unless required by applicable law or agreed to in writing, software
    9 // distributed under the License is distributed on an "AS IS" BASIS,
   10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   11 // See the License for the specific language governing permissions and
   12 // limitations under the License.
   13 
   14 package security
   15 
   16 import (
   17 	"bytes"
   18 	"encoding/json"
   19 	"errors"
   20 	"fmt"
   21 	"reflect"
   22 	"strings"
   23 
   24 	"github.com/gohugoio/hugo/common/herrors"
   25 	"github.com/gohugoio/hugo/common/types"
   26 	"github.com/gohugoio/hugo/config"
   27 	"github.com/gohugoio/hugo/parser"
   28 	"github.com/gohugoio/hugo/parser/metadecoders"
   29 	"github.com/mitchellh/mapstructure"
   30 )
   31 
   32 const securityConfigKey = "security"
   33 
   34 // DefaultConfig holds the default security policy.
   35 var DefaultConfig = Config{
   36 	Exec: Exec{
   37 		Allow: NewWhitelist(
   38 			"^dart-sass-embedded$",
   39 			"^go$",  // for Go Modules
   40 			"^npx$", // used by all Node tools (Babel, PostCSS).
   41 			"^postcss$",
   42 		),
   43 		// These have been tested to work with Hugo's external programs
   44 		// on Windows, Linux and MacOS.
   45 		OsEnv: NewWhitelist("(?i)^(PATH|PATHEXT|APPDATA|TMP|TEMP|TERM)$"),
   46 	},
   47 	Funcs: Funcs{
   48 		Getenv: NewWhitelist("^HUGO_"),
   49 	},
   50 	HTTP: HTTP{
   51 		URLs:    NewWhitelist(".*"),
   52 		Methods: NewWhitelist("(?i)GET|POST"),
   53 	},
   54 }
   55 
   56 // Config is the top level security config.
   57 type Config struct {
   58 	// Restricts access to os.Exec.
   59 	Exec Exec `json:"exec"`
   60 
   61 	// Restricts access to certain template funcs.
   62 	Funcs Funcs `json:"funcs"`
   63 
   64 	// Restricts access to resources.Get, getJSON, getCSV.
   65 	HTTP HTTP `json:"http"`
   66 
   67 	// Allow inline shortcodes
   68 	EnableInlineShortcodes bool `json:"enableInlineShortcodes"`
   69 }
   70 
   71 // Exec holds os/exec policies.
   72 type Exec struct {
   73 	Allow Whitelist `json:"allow"`
   74 	OsEnv Whitelist `json:"osEnv"`
   75 }
   76 
   77 // Funcs holds template funcs policies.
   78 type Funcs struct {
   79 	// OS env keys allowed to query in os.Getenv.
   80 	Getenv Whitelist `json:"getenv"`
   81 }
   82 
   83 type HTTP struct {
   84 	// URLs to allow in remote HTTP (resources.Get, getJSON, getCSV).
   85 	URLs Whitelist `json:"urls"`
   86 
   87 	// HTTP methods to allow.
   88 	Methods Whitelist `json:"methods"`
   89 }
   90 
   91 // ToTOML converts c to TOML with [security] as the root.
   92 func (c Config) ToTOML() string {
   93 	sec := c.ToSecurityMap()
   94 
   95 	var b bytes.Buffer
   96 
   97 	if err := parser.InterfaceToConfig(sec, metadecoders.TOML, &b); err != nil {
   98 		panic(err)
   99 	}
  100 
  101 	return strings.TrimSpace(b.String())
  102 }
  103 
  104 func (c Config) CheckAllowedExec(name string) error {
  105 	if !c.Exec.Allow.Accept(name) {
  106 		return &AccessDeniedError{
  107 			name:     name,
  108 			path:     "security.exec.allow",
  109 			policies: c.ToTOML(),
  110 		}
  111 	}
  112 	return nil
  113 
  114 }
  115 
  116 func (c Config) CheckAllowedGetEnv(name string) error {
  117 	if !c.Funcs.Getenv.Accept(name) {
  118 		return &AccessDeniedError{
  119 			name:     name,
  120 			path:     "security.funcs.getenv",
  121 			policies: c.ToTOML(),
  122 		}
  123 	}
  124 	return nil
  125 }
  126 
  127 func (c Config) CheckAllowedHTTPURL(url string) error {
  128 	if !c.HTTP.URLs.Accept(url) {
  129 		return &AccessDeniedError{
  130 			name:     url,
  131 			path:     "security.http.urls",
  132 			policies: c.ToTOML(),
  133 		}
  134 	}
  135 	return nil
  136 }
  137 
  138 func (c Config) CheckAllowedHTTPMethod(method string) error {
  139 	if !c.HTTP.Methods.Accept(method) {
  140 		return &AccessDeniedError{
  141 			name:     method,
  142 			path:     "security.http.method",
  143 			policies: c.ToTOML(),
  144 		}
  145 	}
  146 	return nil
  147 }
  148 
  149 // ToSecurityMap converts c to a map with 'security' as the root key.
  150 func (c Config) ToSecurityMap() map[string]any {
  151 	// Take it to JSON and back to get proper casing etc.
  152 	asJson, err := json.Marshal(c)
  153 	herrors.Must(err)
  154 	m := make(map[string]any)
  155 	herrors.Must(json.Unmarshal(asJson, &m))
  156 
  157 	// Add the root
  158 	sec := map[string]any{
  159 		"security": m,
  160 	}
  161 	return sec
  162 
  163 }
  164 
  165 // DecodeConfig creates a privacy Config from a given Hugo configuration.
  166 func DecodeConfig(cfg config.Provider) (Config, error) {
  167 	sc := DefaultConfig
  168 	if cfg.IsSet(securityConfigKey) {
  169 		m := cfg.GetStringMap(securityConfigKey)
  170 		dec, err := mapstructure.NewDecoder(
  171 			&mapstructure.DecoderConfig{
  172 				WeaklyTypedInput: true,
  173 				Result:           &sc,
  174 				DecodeHook:       stringSliceToWhitelistHook(),
  175 			},
  176 		)
  177 		if err != nil {
  178 			return sc, err
  179 		}
  180 
  181 		if err = dec.Decode(m); err != nil {
  182 			return sc, err
  183 		}
  184 	}
  185 
  186 	if !sc.EnableInlineShortcodes {
  187 		// Legacy
  188 		sc.EnableInlineShortcodes = cfg.GetBool("enableInlineShortcodes")
  189 	}
  190 
  191 	return sc, nil
  192 
  193 }
  194 
  195 func stringSliceToWhitelistHook() mapstructure.DecodeHookFuncType {
  196 	return func(
  197 		f reflect.Type,
  198 		t reflect.Type,
  199 		data any) (any, error) {
  200 
  201 		if t != reflect.TypeOf(Whitelist{}) {
  202 			return data, nil
  203 		}
  204 
  205 		wl := types.ToStringSlicePreserveString(data)
  206 
  207 		return NewWhitelist(wl...), nil
  208 
  209 	}
  210 }
  211 
  212 // AccessDeniedError represents a security policy conflict.
  213 type AccessDeniedError struct {
  214 	path     string
  215 	name     string
  216 	policies string
  217 }
  218 
  219 func (e *AccessDeniedError) Error() string {
  220 	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)
  221 }
  222 
  223 // IsAccessDenied reports whether err is an AccessDeniedError
  224 func IsAccessDenied(err error) bool {
  225 	var notFoundErr *AccessDeniedError
  226 	return errors.As(err, &notFoundErr)
  227 }