Skip to content

Commit f1b77e2

Browse files
fix: Storage prefix validation (#4044) (#4048)
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 <simon@swine.de>
1 parent 85ba9a4 commit f1b77e2

File tree

2 files changed

+93
-24
lines changed

2 files changed

+93
-24
lines changed

‎pkg/objstore/client/config.go

Lines changed: 53 additions & 11 deletions
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/samber/lo"
@@ -39,18 +38,25 @@ const (
3938

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

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

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

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

@@ -131,13 +137,49 @@ func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
131137
cfg.RegisterFlagsWithPrefixAndDefaultDirectory(prefix, "./data-shared", f)
132138
}
133139

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

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

‎pkg/objstore/client/factory_test.go

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -108,28 +108,51 @@ func TestClient_ConfigValidation(t *testing.T) {
108108
expectedError error
109109
}{
110110
{
111-
name: "valid storage_prefix",
112-
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "helloworld"},
111+
name: "storage_prefix/valid",
112+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "helloWORLD123"},
113113
},
114114
{
115-
name: "storage_prefix non-alphanumeric characters",
116-
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello-world!"},
117-
expectedError: ErrInvalidCharactersInStoragePrefix,
115+
name: "storage_prefix/valid-subdir",
116+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello/world/env"},
118117
},
119118
{
120-
name: "storage_prefix suffixed with a slash (non-alphanumeric)",
121-
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "helloworld/"},
122-
expectedError: ErrInvalidCharactersInStoragePrefix,
119+
name: "storage_prefix/valid-subdir-trailing-slash",
120+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello/world/env/"},
123121
},
124122
{
125-
name: "storage_prefix that has some character strings that have a meaning in unix paths (..)",
123+
name: "storage_prefix/invalid-directory-up",
126124
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: ".."},
127-
expectedError: ErrInvalidCharactersInStoragePrefix,
125+
expectedError: ErrStoragePrefixInvalidCharacters,
128126
},
129127
{
130-
name: "storage_prefix that has some character strings that have a meaning in unix paths (.)",
128+
name: "storage_prefix/invalid-directory",
131129
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "."},
132-
expectedError: ErrInvalidCharactersInStoragePrefix,
130+
expectedError: ErrStoragePrefixInvalidCharacters,
131+
},
132+
{
133+
name: "storage_prefix/invalid-absolute-path",
134+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "/hello/world"},
135+
expectedError: ErrStoragePrefixStartsWithSlash,
136+
},
137+
{
138+
name: "storage_prefix/invalid-..-in-a-path-segement",
139+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello/../test"},
140+
expectedError: ErrStoragePrefixInvalidCharacters,
141+
},
142+
{
143+
name: "storage_prefix/invalid-empty-path-segement",
144+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello//test"},
145+
expectedError: ErrStoragePrefixEmptyPathSegment,
146+
},
147+
{
148+
name: "storage_prefix/invalid-emoji",
149+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "👋"},
150+
expectedError: ErrStoragePrefixInvalidCharacters,
151+
},
152+
{
153+
name: "storage_prefix/invalid-emoji",
154+
cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello!world"},
155+
expectedError: ErrStoragePrefixInvalidCharacters,
133156
},
134157
{
135158
name: "unsupported backend",
@@ -142,7 +165,11 @@ func TestClient_ConfigValidation(t *testing.T) {
142165
tc := tc
143166
t.Run(tc.name, func(t *testing.T) {
144167
actualErr := tc.cfg.Validate()
145-
assert.ErrorIs(t, actualErr, tc.expectedError)
168+
if tc.expectedError != nil {
169+
assert.Equal(t, actualErr, tc.expectedError)
170+
} else {
171+
assert.NoError(t, actualErr)
172+
}
146173
})
147174
}
148175
}

0 commit comments

Comments
 (0)