Skip to content

Commit 445c20d

Browse files
fix: Storage prefix validation (#4044) (#4047)
Our validation has been overly strict, since inception of the project. Although we failed to call the validate alltogether. This was fixed in This PR loosens the criteria accordingly Fixes #3968 (cherry picked from commit ed11e4a) Co-authored-by: Christian Simon <[email protected]>
1 parent 35d8f3d commit 445c20d

File tree

2 files changed

+93
-24
lines changed

2 files changed

+93
-24
lines changed

pkg/objstore/client/config.go

+53-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"errors"
55
"flag"
66
"fmt"
7-
"regexp"
87
"strings"
98

109
"github.com/go-kit/log"
@@ -40,18 +39,25 @@ const (
4039

4140
// Filesystem is the value for the filesystem storage backend.
4241
Filesystem = "filesystem"
43-
44-
// validPrefixCharactersRegex allows only alphanumeric characters to prevent subtle bugs and simplify validation
45-
validPrefixCharactersRegex = `^[\da-zA-Z]+$`
4642
)
4743

4844
var (
4945
SupportedBackends = []string{S3, GCS, Azure, Swift, Filesystem, COS}
5046

51-
ErrUnsupportedStorageBackend = errors.New("unsupported storage backend")
52-
ErrInvalidCharactersInStoragePrefix = errors.New("storage prefix contains invalid characters, it may only contain digits and English alphabet letters")
47+
ErrUnsupportedStorageBackend = errors.New("unsupported storage backend")
48+
ErrStoragePrefixStartsWithSlash = errors.New("storage prefix starts with a slash")
49+
ErrStoragePrefixEmptyPathSegment = errors.New("storage prefix contains an empty path segment")
50+
ErrStoragePrefixInvalidCharacters = errors.New("storage prefix contains invalid characters: only alphanumeric, hyphen, underscore, dot, and no segement should be . or ..")
5351
)
5452

53+
type ErrInvalidCharactersInStoragePrefix struct {
54+
Prefix string
55+
}
56+
57+
func (e *ErrInvalidCharactersInStoragePrefix) Error() string {
58+
return fmt.Sprintf("storage prefix '%s' contains invalid characters", e.Prefix)
59+
}
60+
5561
type StorageBackendConfig struct {
5662
Backend string `yaml:"backend"`
5763

@@ -132,13 +138,49 @@ func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet, logge
132138
cfg.RegisterFlagsWithPrefixAndDefaultDirectory(prefix, "./data-shared", f, logger)
133139
}
134140

135-
func (cfg *Config) Validate() error {
136-
if cfg.StoragePrefix != "" {
137-
acceptablePrefixCharacters := regexp.MustCompile(validPrefixCharactersRegex)
138-
if !acceptablePrefixCharacters.MatchString(cfg.StoragePrefix) {
139-
return ErrInvalidCharactersInStoragePrefix
141+
// alphanumeric, hyphen, underscore, dot, and must not be . or ..
142+
func validStoragePrefixPart(part string) bool {
143+
if part == "." || part == ".." {
144+
return false
145+
}
146+
for i, b := range part {
147+
if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == '-' || b == '.' || (b >= '0' && b <= '9' && i > 0)) {
148+
return false
149+
}
150+
}
151+
return true
152+
}
153+
154+
func validStoragePrefix(prefix string) error {
155+
// without a prefix exit quickly
156+
if prefix == "" {
157+
return nil
158+
}
159+
160+
parts := strings.Split(prefix, "/")
161+
162+
for idx, part := range parts {
163+
if part == "" {
164+
if idx == 0 {
165+
return ErrStoragePrefixStartsWithSlash
166+
}
167+
if idx != len(parts)-1 {
168+
return ErrStoragePrefixEmptyPathSegment
169+
}
170+
// slash at the end is fine
171+
}
172+
if !validStoragePrefixPart(part) {
173+
return ErrStoragePrefixInvalidCharacters
140174
}
141175
}
142176

177+
return nil
178+
}
179+
180+
func (cfg *Config) Validate() error {
181+
if err := validStoragePrefix(cfg.StoragePrefix); err != nil {
182+
return err
183+
}
184+
143185
return cfg.StorageBackendConfig.Validate()
144186
}

pkg/objstore/client/factory_test.go

+40-13
Original file line numberDiff line numberDiff line change
@@ -138,28 +138,51 @@ func TestClient_ConfigValidation(t *testing.T) {
138138
expectedError error
139139
}{
140140
{
141-
name: "valid storage_prefix",
142-
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "helloworld"},
141+
name: "storage_prefix/valid",
142+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "helloWORLD123"},
143143
},
144144
{
145-
name: "storage_prefix non-alphanumeric characters",
146-
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello-world!"},
147-
expectedError: ErrInvalidCharactersInStoragePrefix,
145+
name: "storage_prefix/valid-subdir",
146+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello/world/env"},
148147
},
149148
{
150-
name: "storage_prefix suffixed with a slash (non-alphanumeric)",
151-
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "helloworld/"},
152-
expectedError: ErrInvalidCharactersInStoragePrefix,
149+
name: "storage_prefix/valid-subdir-trailing-slash",
150+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello/world/env/"},
153151
},
154152
{
155-
name: "storage_prefix that has some character strings that have a meaning in unix paths (..)",
153+
name: "storage_prefix/invalid-directory-up",
156154
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: ".."},
157-
expectedError: ErrInvalidCharactersInStoragePrefix,
155+
expectedError: ErrStoragePrefixInvalidCharacters,
158156
},
159157
{
160-
name: "storage_prefix that has some character strings that have a meaning in unix paths (.)",
158+
name: "storage_prefix/invalid-directory",
161159
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "."},
162-
expectedError: ErrInvalidCharactersInStoragePrefix,
160+
expectedError: ErrStoragePrefixInvalidCharacters,
161+
},
162+
{
163+
name: "storage_prefix/invalid-absolute-path",
164+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "/hello/world"},
165+
expectedError: ErrStoragePrefixStartsWithSlash,
166+
},
167+
{
168+
name: "storage_prefix/invalid-..-in-a-path-segement",
169+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello/../test"},
170+
expectedError: ErrStoragePrefixInvalidCharacters,
171+
},
172+
{
173+
name: "storage_prefix/invalid-empty-path-segement",
174+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello//test"},
175+
expectedError: ErrStoragePrefixEmptyPathSegment,
176+
},
177+
{
178+
name: "storage_prefix/invalid-emoji",
179+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "👋"},
180+
expectedError: ErrStoragePrefixInvalidCharacters,
181+
},
182+
{
183+
name: "storage_prefix/invalid-emoji",
184+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello!world"},
185+
expectedError: ErrStoragePrefixInvalidCharacters,
163186
},
164187
{
165188
name: "unsupported backend",
@@ -172,7 +195,11 @@ func TestClient_ConfigValidation(t *testing.T) {
172195
tc := tc
173196
t.Run(tc.name, func(t *testing.T) {
174197
actualErr := tc.cfg.Validate()
175-
assert.ErrorIs(t, actualErr, tc.expectedError)
198+
if tc.expectedError != nil {
199+
assert.Equal(t, actualErr, tc.expectedError)
200+
} else {
201+
assert.NoError(t, actualErr)
202+
}
176203
})
177204
}
178205
}

0 commit comments

Comments
 (0)