hugo/hugolib/securitypolicies_test.go

224 lines
6 KiB
Go
Raw Permalink Normal View History

// Copyright 2019 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 hugolib
import (
"fmt"
"net/http"
"net/http/httptest"
"runtime"
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/markup/asciidocext"
"github.com/gohugoio/hugo/markup/pandoc"
"github.com/gohugoio/hugo/markup/rst"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
)
func TestSecurityPolicies(t *testing.T) {
c := qt.New(t)
testVariant := func(c *qt.C, withBuilder func(b *sitesBuilder), expectErr string) {
c.Helper()
b := newTestSitesBuilder(c)
withBuilder(b)
if expectErr != "" {
err := b.BuildE(BuildCfg{})
b.Assert(err, qt.IsNotNil)
b.Assert(err, qt.ErrorMatches, expectErr)
} else {
b.Build(BuildCfg{})
}
}
httpTestVariant := func(c *qt.C, templ, expectErr string, withBuilder func(b *sitesBuilder)) {
ts := httptest.NewServer(http.FileServer(http.Dir("testdata/")))
c.Cleanup(func() {
ts.Close()
})
cb := func(b *sitesBuilder) {
b.WithTemplatesAdded("index.html", fmt.Sprintf(templ, ts.URL))
if withBuilder != nil {
withBuilder(b)
}
}
testVariant(c, cb, expectErr)
}
c.Run("os.GetEnv, denied", func(c *qt.C) {
c.Parallel()
cb := func(b *sitesBuilder) {
b.WithTemplatesAdded("index.html", `{{ os.Getenv "FOOBAR" }}`)
}
testVariant(c, cb, `(?s).*"FOOBAR" is not whitelisted in policy "security\.funcs\.getenv".*`)
})
c.Run("os.GetEnv, OK", func(c *qt.C) {
c.Parallel()
cb := func(b *sitesBuilder) {
b.WithTemplatesAdded("index.html", `{{ os.Getenv "HUGO_FOO" }}`)
}
testVariant(c, cb, "")
})
c.Run("Asciidoc, denied", func(c *qt.C) {
c.Parallel()
if !asciidocext.Supports() {
c.Skip()
}
cb := func(b *sitesBuilder) {
b.WithContent("page.ad", "foo")
}
testVariant(c, cb, `(?s).*"asciidoctor" is not whitelisted in policy "security\.exec\.allow".*`)
})
c.Run("RST, denied", func(c *qt.C) {
c.Parallel()
if !rst.Supports() {
c.Skip()
}
cb := func(b *sitesBuilder) {
b.WithContent("page.rst", "foo")
}
if runtime.GOOS == "windows" {
testVariant(c, cb, `(?s).*python(\.exe)?" is not whitelisted in policy "security\.exec\.allow".*`)
} else {
testVariant(c, cb, `(?s).*"rst2html(\.py)?" is not whitelisted in policy "security\.exec\.allow".*`)
}
})
c.Run("Pandoc, denied", func(c *qt.C) {
c.Parallel()
if !pandoc.Supports() {
c.Skip()
}
cb := func(b *sitesBuilder) {
b.WithContent("page.pdc", "foo")
}
testVariant(c, cb, `"(?s).*pandoc" is not whitelisted in policy "security\.exec\.allow".*`)
})
c.Run("Dart SASS, OK", func(c *qt.C) {
c.Parallel()
if !dartsass.Supports() {
c.Skip()
}
cb := func(b *sitesBuilder) {
b.WithTemplatesAdded("index.html", `{{ $scss := "body { color: #333; }" | resources.FromString "foo.scss" | resources.ToCSS (dict "transpiler" "dartsass") }}`)
}
testVariant(c, cb, "")
})
c.Run("Dart SASS, denied", func(c *qt.C) {
c.Parallel()
if !dartsass.Supports() {
c.Skip()
}
cb := func(b *sitesBuilder) {
b.WithConfigFile("toml", `
[security]
[security.exec]
allow="none"
`)
b.WithTemplatesAdded("index.html", `{{ $scss := "body { color: #333; }" | resources.FromString "foo.scss" | resources.ToCSS (dict "transpiler" "dartsass") }}`)
}
testVariant(c, cb, `(?s).*sass(-embedded)?" is not whitelisted in policy "security\.exec\.allow".*`)
})
c.Run("resources.GetRemote, OK", func(c *qt.C) {
c.Parallel()
httpTestVariant(c, `{{ $json := resources.GetRemote "%[1]s/fruits.json" }}{{ $json.Content }}`, "", nil)
})
c.Run("resources.GetRemote, denied method", func(c *qt.C) {
c.Parallel()
httpTestVariant(c, `{{ $json := resources.GetRemote "%[1]s/fruits.json" (dict "method" "DELETE" ) }}{{ $json.Content }}`, `(?s).*"DELETE" is not whitelisted in policy "security\.http\.method".*`, nil)
})
c.Run("resources.GetRemote, denied URL", func(c *qt.C) {
c.Parallel()
httpTestVariant(c, `{{ $json := resources.GetRemote "%[1]s/fruits.json" }}{{ $json.Content }}`, `(?s).*is not whitelisted in policy "security\.http\.urls".*`,
func(b *sitesBuilder) {
b.WithConfigFile("toml", `
[security]
[security.http]
urls="none"
`)
})
})
c.Run("resources.GetRemote, fake JSON", func(c *qt.C) {
c.Parallel()
httpTestVariant(c, `{{ $json := resources.GetRemote "%[1]s/fakejson.json" }}{{ $json.Content }}`, `(?s).*failed to resolve media type.*`,
func(b *sitesBuilder) {
b.WithConfigFile("toml", `
`)
})
})
c.Run("resources.GetRemote, fake JSON whitelisted", func(c *qt.C) {
c.Parallel()
httpTestVariant(c, `{{ $json := resources.GetRemote "%[1]s/fakejson.json" }}{{ $json.Content }}`, ``,
func(b *sitesBuilder) {
b.WithConfigFile("toml", `
[security]
[security.http]
mediaTypes=["application/json"]
`)
})
})
c.Run("getJSON, OK", func(c *qt.C) {
c.Parallel()
httpTestVariant(c, `{{ $json := getJSON "%[1]s/fruits.json" }}{{ $json.Content }}`, "", nil)
})
c.Run("getJSON, denied URL", func(c *qt.C) {
c.Parallel()
httpTestVariant(c, `{{ $json := getJSON "%[1]s/fruits.json" }}{{ $json.Content }}`, `(?s).*is not whitelisted in policy "security\.http\.urls".*`,
func(b *sitesBuilder) {
b.WithConfigFile("toml", `
[security]
[security.http]
urls="none"
`)
})
})
c.Run("getCSV, denied URL", func(c *qt.C) {
c.Parallel()
httpTestVariant(c, `{{ $d := getCSV ";" "%[1]s/cities.csv" }}{{ $d.Content }}`, `(?s).*is not whitelisted in policy "security\.http\.urls".*`,
func(b *sitesBuilder) {
b.WithConfigFile("toml", `
[security]
[security.http]
urls="none"
`)
})
})
}