-
Notifications
You must be signed in to change notification settings - Fork 0
/
sanitize.go
198 lines (162 loc) · 5.39 KB
/
sanitize.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package backend
import (
"path/filepath"
"regexp"
"strings"
"time"
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"
)
// errorMessage is the error message to return when invalid input is provided by the caller.
const errorMessage = "special characters are not allowed in resource names, please use name composed only from characters, hyphens and dots"
// whitelistPattern is the pattern of allowed characters for each key within
// the path.
var whitelistPattern = regexp.MustCompile(`^[0-9A-Za-z@_:.-]*$`)
// isStringSafe checks if the passed in string conforms to the whitelist.
func isStringSafe(s string) bool {
if strings.Contains(s, "..") {
return false
}
if strings.Contains(s, string(filepath.Separator)) {
return false
}
return whitelistPattern.MatchString(s)
}
// isSliceSafe checks if the passed in slice conforms to the whitelist.
func isSliceSafe(slice []string) bool {
for _, s := range slice {
if !isStringSafe(s) {
return false
}
}
return true
}
// Sanitizer wraps a Backend implementation to make sure all values requested
// of the backend are whitelisted.
type Sanitizer struct {
backend Backend
}
// NewSanitizer returns a new Sanitizer.
func NewSanitizer(backend Backend) *Sanitizer {
return &Sanitizer{
backend: backend,
}
}
// Backend returns the underlying backend. Useful when knowing the type of
// backend is important (for example, can the backend support forking).
func (s *Sanitizer) Backend() Backend {
return s.backend
}
// GetKeys returns a list of keys for a given path.
func (s *Sanitizer) GetKeys(bucket []string, opts ...OpOption) ([]string, error) {
if !isSliceSafe(bucket) {
return nil, trace.BadParameter(errorMessage)
}
return s.backend.GetKeys(bucket, opts...)
}
// GetItems returns a list of items (key value pairs) for a bucket.
func (s *Sanitizer) GetItems(bucket []string, opts ...OpOption) ([]Item, error) {
if !isSliceSafe(bucket) {
return nil, trace.BadParameter(errorMessage)
}
return s.backend.GetItems(bucket, opts...)
}
// CreateVal creates value with a given TTL and key in the bucket. If the
// value already exists, returns trace.AlreadyExistsError.
func (s *Sanitizer) CreateVal(bucket []string, key string, val []byte, ttl time.Duration) error {
if !isSliceSafe(bucket) {
return trace.BadParameter(errorMessage)
}
if !isStringSafe(key) {
return trace.BadParameter(errorMessage)
}
return s.backend.CreateVal(bucket, key, val, ttl)
}
// UpsertVal updates or inserts value with a given TTL into a bucket. Use
// backend.ForeverTTL for no TTL.
func (s *Sanitizer) UpsertVal(bucket []string, key string, val []byte, ttl time.Duration) error {
if !isSliceSafe(bucket) {
return trace.BadParameter(errorMessage)
}
if !isStringSafe(key) {
return trace.BadParameter(errorMessage)
}
return s.backend.UpsertVal(bucket, key, val, ttl)
}
// UpsertItems updates or inserts all passed in backend.Items (with a TTL)
// into the given bucket.
func (s *Sanitizer) UpsertItems(bucket []string, items []Item) error {
if !isSliceSafe(bucket) {
return trace.BadParameter(errorMessage)
}
for _, e := range items {
if !isStringSafe(e.Key) {
return trace.BadParameter(errorMessage)
}
}
return s.backend.UpsertItems(bucket, items)
}
// GetVal returns a value for a given key in the bucket.
func (s *Sanitizer) GetVal(bucket []string, key string) ([]byte, error) {
if !isSliceSafe(bucket) {
return nil, trace.BadParameter(errorMessage)
}
if !isStringSafe(key) {
return nil, trace.BadParameter(errorMessage)
}
return s.backend.GetVal(bucket, key)
}
// CompareAndSwapVal compares and swaps values in atomic operation, succeeds
// if prevVal matches the value stored in the database, requires prevVal as a
// non-empty value. Returns trace.CompareFailed in case if value did not match.
func (s *Sanitizer) CompareAndSwapVal(bucket []string, key string, val []byte, prevVal []byte, ttl time.Duration) error {
if !isSliceSafe(bucket) {
return trace.BadParameter(errorMessage)
}
if !isStringSafe(key) {
return trace.BadParameter(errorMessage)
}
return s.backend.CompareAndSwapVal(bucket, key, val, prevVal, ttl)
}
// DeleteKey deletes a key in a bucket.
func (s *Sanitizer) DeleteKey(bucket []string, key string) error {
if !isSliceSafe(bucket) {
return trace.BadParameter(errorMessage)
}
if !isStringSafe(key) {
return trace.BadParameter(errorMessage)
}
return s.backend.DeleteKey(bucket, key)
}
// DeleteBucket deletes the bucket by a given path.
func (s *Sanitizer) DeleteBucket(path []string, bucket string) error {
if !isSliceSafe(path) {
return trace.BadParameter(errorMessage)
}
if !isStringSafe(bucket) {
return trace.BadParameter(errorMessage)
}
return s.backend.DeleteBucket(path, bucket)
}
// AcquireLock grabs a lock that will be released automatically after a TTL.
func (s *Sanitizer) AcquireLock(token string, ttl time.Duration) error {
if !isStringSafe(token) {
return trace.BadParameter(errorMessage)
}
return s.backend.AcquireLock(token, ttl)
}
// ReleaseLock forces lock release before the TTL has expired.
func (s *Sanitizer) ReleaseLock(token string) error {
if !isStringSafe(token) {
return trace.BadParameter(errorMessage)
}
return s.backend.ReleaseLock(token)
}
// Close releases the resources taken up by this backend
func (s *Sanitizer) Close() error {
return s.backend.Close()
}
// Clock returns clock used by this backend
func (s *Sanitizer) Clock() clockwork.Clock {
return s.backend.Clock()
}