Commit 0aa24266 authored by Tim Gross's avatar Tim Gross
Browse files

variables: restrict allowed paths for variables

Restrict variable paths to RFC3986 URL-safe characters that don't conflict with
the use of characters "@" and "." in `template` blocks. This prevents users from
writing variables that will require tricky templating syntax or that they simply
won't be able to use.

Also restrict the length so that a user can't make queries in the state store
unusually expensive (as they are O(k) on the key length).
parent a62a6542
Branches unavailable
No related merge requests found
Showing with 23 additions and 6 deletions
+23 -6
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"reflect" "reflect"
"regexp"
"strings" "strings"
) )
...@@ -145,12 +146,25 @@ func (sv VariableData) Copy() VariableData { ...@@ -145,12 +146,25 @@ func (sv VariableData) Copy() VariableData {
} }
} }
func (sv VariableDecrypted) Validate() error { var (
// validVariablePath is used to validate a variable path. We restrict to
// RFC3986 URL-safe characters that don't conflict with the use of
// characters "@" and "." in template blocks. We also restrict the length so
// that a user can't make queries in the state store unusually expensive (as
// they are O(k) on the key length)
validVariablePath = regexp.MustCompile("^[a-zA-Z0-9-_~/]{1,128}$")
)
func (v VariableDecrypted) Validate() error {
if len(sv.Path) == 0 { if len(v.Path) == 0 {
return fmt.Errorf("variable requires path") return fmt.Errorf("variable requires path")
} }
parts := strings.Split(sv.Path, "/") if !validVariablePath.MatchString(v.Path) {
return fmt.Errorf("invalid path %q", v.Path)
}
parts := strings.Split(v.Path, "/")
switch { switch {
case len(parts) == 1 && parts[0] == "nomad": case len(parts) == 1 && parts[0] == "nomad":
return fmt.Errorf("\"nomad\" is a reserved top-level directory path, but you may write variables to \"nomad/jobs\" or below") return fmt.Errorf("\"nomad\" is a reserved top-level directory path, but you may write variables to \"nomad/jobs\" or below")
...@@ -158,13 +172,13 @@ func (sv VariableDecrypted) Validate() error { ...@@ -158,13 +172,13 @@ func (sv VariableDecrypted) Validate() error {
return fmt.Errorf("only paths at \"nomad/jobs\" or below are valid paths under the top-level \"nomad\" directory") return fmt.Errorf("only paths at \"nomad/jobs\" or below are valid paths under the top-level \"nomad\" directory")
} }
if len(sv.Items) == 0 { if len(v.Items) == 0 {
return errors.New("empty variables are invalid") return errors.New("empty variables are invalid")
} }
if sv.Items.Size() > maxVariableSize { if v.Items.Size() > maxVariableSize {
return errors.New("variables are limited to 16KiB in total size") return errors.New("variables are limited to 16KiB in total size")
} }
if sv.Namespace == AllNamespacesSentinel { if v.Namespace == AllNamespacesSentinel {
return errors.New("can not target wildcard (\"*\")namespace") return errors.New("can not target wildcard (\"*\")namespace")
} }
return nil return nil
......
...@@ -51,6 +51,9 @@ func TestStructs_VariableDecrypted_Validate(t *testing.T) { ...@@ -51,6 +51,9 @@ func TestStructs_VariableDecrypted_Validate(t *testing.T) {
{path: "nomad/jobs", ok: true}, {path: "nomad/jobs", ok: true},
{path: "nomadjobs", ok: true}, {path: "nomadjobs", ok: true},
{path: "nomad/jobs/whatever", ok: true}, {path: "nomad/jobs/whatever", ok: true},
{path: "example/_-~/whatever", ok: true},
{path: "example/@whatever"},
{path: "example/what.ever"},
} }
for _, tc := range testCases { for _, tc := range testCases {
tc := tc tc := tc
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment