github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/config/security/securityConfig.go (about)

     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: MustNewWhitelist(
    38  			"^(dart-)?sass(-embedded)?$", // sass, dart-sass, 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: MustNewWhitelist(`(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG)$`),
    46  	},
    47  	Funcs: Funcs{
    48  		Getenv: MustNewWhitelist("^HUGO_", "^CI$"),
    49  	},
    50  	HTTP: HTTP{
    51  		URLs:    MustNewWhitelist(".*"),
    52  		Methods: MustNewWhitelist("(?i)GET|POST"),
    53  	},
    54  }
    55  
    56  // Config is the top level security config.
    57  // <docsmeta>{"name": "security", "description": "This section holds the top level security config.", "newIn": "0.91.0" }</docsmeta>
    58  type Config struct {
    59  	// Restricts access to os.Exec....
    60  	// <docsmeta>{ "newIn": "0.91.0" }</docsmeta>
    61  	Exec Exec `json:"exec"`
    62  
    63  	// Restricts access to certain template funcs.
    64  	Funcs Funcs `json:"funcs"`
    65  
    66  	// Restricts access to resources.GetRemote, getJSON, getCSV.
    67  	HTTP HTTP `json:"http"`
    68  
    69  	// Allow inline shortcodes
    70  	EnableInlineShortcodes bool `json:"enableInlineShortcodes"`
    71  
    72  	// Go templates related security config.
    73  	GoTemplates GoTemplates `json:"goTemplates"`
    74  }
    75  
    76  // Exec holds os/exec policies.
    77  type Exec struct {
    78  	Allow Whitelist `json:"allow"`
    79  	OsEnv Whitelist `json:"osEnv"`
    80  }
    81  
    82  // Funcs holds template funcs policies.
    83  type Funcs struct {
    84  	// OS env keys allowed to query in os.Getenv.
    85  	Getenv Whitelist `json:"getenv"`
    86  }
    87  
    88  type HTTP struct {
    89  	// URLs to allow in remote HTTP (resources.Get, getJSON, getCSV).
    90  	URLs Whitelist `json:"urls"`
    91  
    92  	// HTTP methods to allow.
    93  	Methods Whitelist `json:"methods"`
    94  
    95  	// Media types where the Content-Type in the response is used instead of resolving from the file content.
    96  	MediaTypes Whitelist `json:"mediaTypes"`
    97  }
    98  
    99  type GoTemplates struct {
   100  
   101  	// Enable to allow template actions inside bakcticks in ES6 template literals.
   102  	// This was blocked in Hugo 0.114.0 for security reasons and you now get errors on the form
   103  	// "... appears in a JS template literal" if you have this in your templates.
   104  	// See https://github.com/golang/go/issues/59234
   105  	AllowActionJSTmpl bool
   106  }
   107  
   108  // ToTOML converts c to TOML with [security] as the root.
   109  func (c Config) ToTOML() string {
   110  	sec := c.ToSecurityMap()
   111  
   112  	var b bytes.Buffer
   113  
   114  	if err := parser.InterfaceToConfig(sec, metadecoders.TOML, &b); err != nil {
   115  		panic(err)
   116  	}
   117  
   118  	return strings.TrimSpace(b.String())
   119  }
   120  
   121  func (c Config) CheckAllowedExec(name string) error {
   122  	if !c.Exec.Allow.Accept(name) {
   123  		return &AccessDeniedError{
   124  			name:     name,
   125  			path:     "security.exec.allow",
   126  			policies: c.ToTOML(),
   127  		}
   128  	}
   129  	return nil
   130  
   131  }
   132  
   133  func (c Config) CheckAllowedGetEnv(name string) error {
   134  	if !c.Funcs.Getenv.Accept(name) {
   135  		return &AccessDeniedError{
   136  			name:     name,
   137  			path:     "security.funcs.getenv",
   138  			policies: c.ToTOML(),
   139  		}
   140  	}
   141  	return nil
   142  }
   143  
   144  func (c Config) CheckAllowedHTTPURL(url string) error {
   145  	if !c.HTTP.URLs.Accept(url) {
   146  		return &AccessDeniedError{
   147  			name:     url,
   148  			path:     "security.http.urls",
   149  			policies: c.ToTOML(),
   150  		}
   151  	}
   152  	return nil
   153  }
   154  
   155  func (c Config) CheckAllowedHTTPMethod(method string) error {
   156  	if !c.HTTP.Methods.Accept(method) {
   157  		return &AccessDeniedError{
   158  			name:     method,
   159  			path:     "security.http.method",
   160  			policies: c.ToTOML(),
   161  		}
   162  	}
   163  	return nil
   164  }
   165  
   166  // ToSecurityMap converts c to a map with 'security' as the root key.
   167  func (c Config) ToSecurityMap() map[string]any {
   168  	// Take it to JSON and back to get proper casing etc.
   169  	asJson, err := json.Marshal(c)
   170  	herrors.Must(err)
   171  	m := make(map[string]any)
   172  	herrors.Must(json.Unmarshal(asJson, &m))
   173  
   174  	// Add the root
   175  	sec := map[string]any{
   176  		"security": m,
   177  	}
   178  	return sec
   179  
   180  }
   181  
   182  // DecodeConfig creates a privacy Config from a given Hugo configuration.
   183  func DecodeConfig(cfg config.Provider) (Config, error) {
   184  	sc := DefaultConfig
   185  	if cfg.IsSet(securityConfigKey) {
   186  		m := cfg.GetStringMap(securityConfigKey)
   187  		dec, err := mapstructure.NewDecoder(
   188  			&mapstructure.DecoderConfig{
   189  				WeaklyTypedInput: true,
   190  				Result:           &sc,
   191  				DecodeHook:       stringSliceToWhitelistHook(),
   192  			},
   193  		)
   194  		if err != nil {
   195  			return sc, err
   196  		}
   197  
   198  		if err = dec.Decode(m); err != nil {
   199  			return sc, err
   200  		}
   201  	}
   202  
   203  	if !sc.EnableInlineShortcodes {
   204  		// Legacy
   205  		sc.EnableInlineShortcodes = cfg.GetBool("enableInlineShortcodes")
   206  	}
   207  
   208  	return sc, nil
   209  
   210  }
   211  
   212  func stringSliceToWhitelistHook() mapstructure.DecodeHookFuncType {
   213  	return func(
   214  		f reflect.Type,
   215  		t reflect.Type,
   216  		data any) (any, error) {
   217  
   218  		if t != reflect.TypeOf(Whitelist{}) {
   219  			return data, nil
   220  		}
   221  
   222  		wl := types.ToStringSlicePreserveString(data)
   223  
   224  		return NewWhitelist(wl...)
   225  
   226  	}
   227  }
   228  
   229  // AccessDeniedError represents a security policy conflict.
   230  type AccessDeniedError struct {
   231  	path     string
   232  	name     string
   233  	policies string
   234  }
   235  
   236  func (e *AccessDeniedError) Error() string {
   237  	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)
   238  }
   239  
   240  // IsAccessDenied reports whether err is an AccessDeniedError
   241  func IsAccessDenied(err error) bool {
   242  	var notFoundErr *AccessDeniedError
   243  	return errors.As(err, &notFoundErr)
   244  }