@@ -98,16 +98,21 @@ func New(c *clientv3.Client, codec runtime.Codec, newFunc func() runtime.Object,
98
98
99
99
func newStore (c * clientv3.Client , codec runtime.Codec , newFunc func () runtime.Object , prefix string , groupResource schema.GroupResource , transformer value.Transformer , pagingEnabled bool , leaseManagerConfig LeaseManagerConfig ) * store {
100
100
versioner := storage.APIObjectVersioner {}
101
+ // for compatibility with etcd2 impl.
102
+ // no-op for default prefix of '/registry'.
103
+ // keeps compatibility with etcd2 impl for custom prefixes that don't start with '/'
104
+ pathPrefix := path .Join ("/" , prefix )
105
+ if ! strings .HasSuffix (pathPrefix , "/" ) {
106
+ // Ensure the pathPrefix ends in "/" here to simplify key concatenation later.
107
+ pathPrefix += "/"
108
+ }
101
109
result := & store {
102
- client : c ,
103
- codec : codec ,
104
- versioner : versioner ,
105
- transformer : transformer ,
106
- pagingEnabled : pagingEnabled ,
107
- // for compatibility with etcd2 impl.
108
- // no-op for default prefix of '/registry'.
109
- // keeps compatibility with etcd2 impl for custom prefixes that don't start with '/'
110
- pathPrefix : path .Join ("/" , prefix ),
110
+ client : c ,
111
+ codec : codec ,
112
+ versioner : versioner ,
113
+ transformer : transformer ,
114
+ pagingEnabled : pagingEnabled ,
115
+ pathPrefix : pathPrefix ,
111
116
groupResource : groupResource ,
112
117
groupResourceString : groupResource .String (),
113
118
watcher : newWatcher (c , codec , newFunc , versioner , transformer ),
@@ -123,9 +128,12 @@ func (s *store) Versioner() storage.Versioner {
123
128
124
129
// Get implements storage.Interface.Get.
125
130
func (s * store ) Get (ctx context.Context , key string , opts storage.GetOptions , out runtime.Object ) error {
126
- key = path .Join (s .pathPrefix , key )
131
+ preparedKey , err := s .prepareKey (key )
132
+ if err != nil {
133
+ return err
134
+ }
127
135
startTime := time .Now ()
128
- getResp , err := s .client .KV .Get (ctx , key )
136
+ getResp , err := s .client .KV .Get (ctx , preparedKey )
129
137
metrics .RecordEtcdRequestLatency ("get" , getTypeName (out ), startTime )
130
138
if err != nil {
131
139
return err
@@ -138,11 +146,11 @@ func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, ou
138
146
if opts .IgnoreNotFound {
139
147
return runtime .SetZeroValue (out )
140
148
}
141
- return storage .NewKeyNotFoundError (key , 0 )
149
+ return storage .NewKeyNotFoundError (preparedKey , 0 )
142
150
}
143
151
kv := getResp .Kvs [0 ]
144
152
145
- data , _ , err := s .transformer .TransformFromStorage (ctx , kv .Value , authenticatedDataString (key ))
153
+ data , _ , err := s .transformer .TransformFromStorage (ctx , kv .Value , authenticatedDataString (preparedKey ))
146
154
if err != nil {
147
155
return storage .NewInternalError (err .Error ())
148
156
}
@@ -152,6 +160,10 @@ func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, ou
152
160
153
161
// Create implements storage.Interface.Create.
154
162
func (s * store ) Create (ctx context.Context , key string , obj , out runtime.Object , ttl uint64 ) error {
163
+ preparedKey , err := s .prepareKey (key )
164
+ if err != nil {
165
+ return err
166
+ }
155
167
trace := utiltrace .New ("Create etcd3" ,
156
168
utiltrace.Field {"audit-id" , endpointsrequest .GetAuditIDTruncated (ctx )},
157
169
utiltrace.Field {"key" , key },
@@ -170,24 +182,23 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object,
170
182
if err != nil {
171
183
return err
172
184
}
173
- key = path .Join (s .pathPrefix , key )
174
185
175
186
opts , err := s .ttlOpts (ctx , int64 (ttl ))
176
187
if err != nil {
177
188
return err
178
189
}
179
190
180
- newData , err := s .transformer .TransformToStorage (ctx , data , authenticatedDataString (key ))
191
+ newData , err := s .transformer .TransformToStorage (ctx , data , authenticatedDataString (preparedKey ))
181
192
trace .Step ("TransformToStorage finished" , utiltrace.Field {"err" , err })
182
193
if err != nil {
183
194
return storage .NewInternalError (err .Error ())
184
195
}
185
196
186
197
startTime := time .Now ()
187
198
txnResp , err := s .client .KV .Txn (ctx ).If (
188
- notFound (key ),
199
+ notFound (preparedKey ),
189
200
).Then (
190
- clientv3 .OpPut (key , string (newData ), opts ... ),
201
+ clientv3 .OpPut (preparedKey , string (newData ), opts ... ),
191
202
).Commit ()
192
203
metrics .RecordEtcdRequestLatency ("create" , getTypeName (obj ), startTime )
193
204
trace .Step ("Txn call finished" , utiltrace.Field {"err" , err })
@@ -196,7 +207,7 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object,
196
207
}
197
208
198
209
if ! txnResp .Succeeded {
199
- return storage .NewKeyExistsError (key , 0 )
210
+ return storage .NewKeyExistsError (preparedKey , 0 )
200
211
}
201
212
202
213
if out != nil {
@@ -212,12 +223,15 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object,
212
223
func (s * store ) Delete (
213
224
ctx context.Context , key string , out runtime.Object , preconditions * storage.Preconditions ,
214
225
validateDeletion storage.ValidateObjectFunc , cachedExistingObject runtime.Object ) error {
226
+ preparedKey , err := s .prepareKey (key )
227
+ if err != nil {
228
+ return err
229
+ }
215
230
v , err := conversion .EnforcePtr (out )
216
231
if err != nil {
217
232
return fmt .Errorf ("unable to convert output object to pointer: %v" , err )
218
233
}
219
- key = path .Join (s .pathPrefix , key )
220
- return s .conditionalDelete (ctx , key , out , v , preconditions , validateDeletion , cachedExistingObject )
234
+ return s .conditionalDelete (ctx , preparedKey , out , v , preconditions , validateDeletion , cachedExistingObject )
221
235
}
222
236
223
237
func (s * store ) conditionalDelete (
@@ -330,6 +344,10 @@ func (s *store) conditionalDelete(
330
344
func (s * store ) GuaranteedUpdate (
331
345
ctx context.Context , key string , destination runtime.Object , ignoreNotFound bool ,
332
346
preconditions * storage.Preconditions , tryUpdate storage.UpdateFunc , cachedExistingObject runtime.Object ) error {
347
+ preparedKey , err := s .prepareKey (key )
348
+ if err != nil {
349
+ return err
350
+ }
333
351
trace := utiltrace .New ("GuaranteedUpdate etcd3" ,
334
352
utiltrace.Field {"audit-id" , endpointsrequest .GetAuditIDTruncated (ctx )},
335
353
utiltrace.Field {"key" , key },
@@ -340,16 +358,15 @@ func (s *store) GuaranteedUpdate(
340
358
if err != nil {
341
359
return fmt .Errorf ("unable to convert output object to pointer: %v" , err )
342
360
}
343
- key = path .Join (s .pathPrefix , key )
344
361
345
362
getCurrentState := func () (* objState , error ) {
346
363
startTime := time .Now ()
347
- getResp , err := s .client .KV .Get (ctx , key )
364
+ getResp , err := s .client .KV .Get (ctx , preparedKey )
348
365
metrics .RecordEtcdRequestLatency ("get" , getTypeName (destination ), startTime )
349
366
if err != nil {
350
367
return nil , err
351
368
}
352
- return s .getState (ctx , getResp , key , v , ignoreNotFound )
369
+ return s .getState (ctx , getResp , preparedKey , v , ignoreNotFound )
353
370
}
354
371
355
372
var origState * objState
@@ -365,9 +382,9 @@ func (s *store) GuaranteedUpdate(
365
382
}
366
383
trace .Step ("initial value restored" )
367
384
368
- transformContext := authenticatedDataString (key )
385
+ transformContext := authenticatedDataString (preparedKey )
369
386
for {
370
- if err := preconditions .Check (key , origState .obj ); err != nil {
387
+ if err := preconditions .Check (preparedKey , origState .obj ); err != nil {
371
388
// If our data is already up to date, return the error
372
389
if origStateIsCurrent {
373
390
return err
@@ -453,11 +470,11 @@ func (s *store) GuaranteedUpdate(
453
470
454
471
startTime := time .Now ()
455
472
txnResp , err := s .client .KV .Txn (ctx ).If (
456
- clientv3 .Compare (clientv3 .ModRevision (key ), "=" , origState .rev ),
473
+ clientv3 .Compare (clientv3 .ModRevision (preparedKey ), "=" , origState .rev ),
457
474
).Then (
458
- clientv3 .OpPut (key , string (newData ), opts ... ),
475
+ clientv3 .OpPut (preparedKey , string (newData ), opts ... ),
459
476
).Else (
460
- clientv3 .OpGet (key ),
477
+ clientv3 .OpGet (preparedKey ),
461
478
).Commit ()
462
479
metrics .RecordEtcdRequestLatency ("update" , getTypeName (destination ), startTime )
463
480
trace .Step ("Txn call finished" , utiltrace.Field {"err" , err })
@@ -467,8 +484,8 @@ func (s *store) GuaranteedUpdate(
467
484
trace .Step ("Transaction committed" )
468
485
if ! txnResp .Succeeded {
469
486
getResp := (* clientv3 .GetResponse )(txnResp .Responses [0 ].GetResponseRange ())
470
- klog .V (4 ).Infof ("GuaranteedUpdate of %s failed because of a conflict, going to retry" , key )
471
- origState , err = s .getState (ctx , getResp , key , v , ignoreNotFound )
487
+ klog .V (4 ).Infof ("GuaranteedUpdate of %s failed because of a conflict, going to retry" , preparedKey )
488
+ origState , err = s .getState (ctx , getResp , preparedKey , v , ignoreNotFound )
472
489
if err != nil {
473
490
return err
474
491
}
@@ -502,18 +519,21 @@ func getNewItemFunc(listObj runtime.Object, v reflect.Value) func() runtime.Obje
502
519
}
503
520
504
521
func (s * store ) Count (key string ) (int64 , error ) {
505
- key = path .Join (s .pathPrefix , key )
522
+ preparedKey , err := s .prepareKey (key )
523
+ if err != nil {
524
+ return 0 , err
525
+ }
506
526
507
527
// We need to make sure the key ended with "/" so that we only get children "directories".
508
528
// e.g. if we have key "/a", "/a/b", "/ab", getting keys with prefix "/a" will return all three,
509
529
// while with prefix "/a/" will return only "/a/b" which is the correct answer.
510
- if ! strings .HasSuffix (key , "/" ) {
511
- key += "/"
530
+ if ! strings .HasSuffix (preparedKey , "/" ) {
531
+ preparedKey += "/"
512
532
}
513
533
514
534
startTime := time .Now ()
515
- getResp , err := s .client .KV .Get (context .Background (), key , clientv3 .WithRange (clientv3 .GetPrefixRangeEnd (key )), clientv3 .WithCountOnly ())
516
- metrics .RecordEtcdRequestLatency ("listWithCount" , key , startTime )
535
+ getResp , err := s .client .KV .Get (context .Background (), preparedKey , clientv3 .WithRange (clientv3 .GetPrefixRangeEnd (preparedKey )), clientv3 .WithCountOnly ())
536
+ metrics .RecordEtcdRequestLatency ("listWithCount" , preparedKey , startTime )
517
537
if err != nil {
518
538
return 0 , err
519
539
}
@@ -522,6 +542,10 @@ func (s *store) Count(key string) (int64, error) {
522
542
523
543
// GetList implements storage.Interface.
524
544
func (s * store ) GetList (ctx context.Context , key string , opts storage.ListOptions , listObj runtime.Object ) error {
545
+ preparedKey , err := s .prepareKey (key )
546
+ if err != nil {
547
+ return err
548
+ }
525
549
recursive := opts .Recursive
526
550
resourceVersion := opts .ResourceVersion
527
551
match := opts .ResourceVersionMatch
@@ -542,16 +566,15 @@ func (s *store) GetList(ctx context.Context, key string, opts storage.ListOption
542
566
if err != nil || v .Kind () != reflect .Slice {
543
567
return fmt .Errorf ("need ptr to slice: %v" , err )
544
568
}
545
- key = path .Join (s .pathPrefix , key )
546
569
547
570
// For recursive lists, we need to make sure the key ended with "/" so that we only
548
571
// get children "directories". e.g. if we have key "/a", "/a/b", "/ab", getting keys
549
572
// with prefix "/a" will return all three, while with prefix "/a/" will return only
550
573
// "/a/b" which is the correct answer.
551
- if recursive && ! strings .HasSuffix (key , "/" ) {
552
- key += "/"
574
+ if recursive && ! strings .HasSuffix (preparedKey , "/" ) {
575
+ preparedKey += "/"
553
576
}
554
- keyPrefix := key
577
+ keyPrefix := preparedKey
555
578
556
579
// set the appropriate clientv3 options to filter the returned data set
557
580
var limitOption * clientv3.OpOption
@@ -590,7 +613,7 @@ func (s *store) GetList(ctx context.Context, key string, opts storage.ListOption
590
613
591
614
rangeEnd := clientv3 .GetPrefixRangeEnd (keyPrefix )
592
615
options = append (options , clientv3 .WithRange (rangeEnd ))
593
- key = continueKey
616
+ preparedKey = continueKey
594
617
595
618
// If continueRV > 0, the LIST request needs a specific resource version.
596
619
// continueRV==0 is invalid.
@@ -657,7 +680,7 @@ func (s *store) GetList(ctx context.Context, key string, opts storage.ListOption
657
680
}()
658
681
for {
659
682
startTime := time .Now ()
660
- getResp , err = s .client .KV .Get (ctx , key , options ... )
683
+ getResp , err = s .client .KV .Get (ctx , preparedKey , options ... )
661
684
if recursive {
662
685
metrics .RecordEtcdRequestLatency ("list" , getTypeName (listPtr ), startTime )
663
686
} else {
@@ -729,7 +752,7 @@ func (s *store) GetList(ctx context.Context, key string, opts storage.ListOption
729
752
}
730
753
* limitOption = clientv3 .WithLimit (limit )
731
754
}
732
- key = string (lastKey ) + "\x00 "
755
+ preparedKey = string (lastKey ) + "\x00 "
733
756
if withRev == 0 {
734
757
withRev = returnedRV
735
758
options = append (options , clientv3 .WithRev (withRev ))
@@ -794,12 +817,15 @@ func growSlice(v reflect.Value, maxCapacity int, sizes ...int) {
794
817
795
818
// Watch implements storage.Interface.Watch.
796
819
func (s * store ) Watch (ctx context.Context , key string , opts storage.ListOptions ) (watch.Interface , error ) {
820
+ preparedKey , err := s .prepareKey (key )
821
+ if err != nil {
822
+ return nil , err
823
+ }
797
824
rev , err := s .versioner .ParseResourceVersion (opts .ResourceVersion )
798
825
if err != nil {
799
826
return nil , err
800
827
}
801
- key = path .Join (s .pathPrefix , key )
802
- return s .watcher .Watch (ctx , key , int64 (rev ), opts .Recursive , opts .ProgressNotify , opts .Predicate )
828
+ return s .watcher .Watch (ctx , preparedKey , int64 (rev ), opts .Recursive , opts .ProgressNotify , opts .Predicate )
803
829
}
804
830
805
831
func (s * store ) getState (ctx context.Context , getResp * clientv3.GetResponse , key string , v reflect.Value , ignoreNotFound bool ) (* objState , error ) {
@@ -911,6 +937,30 @@ func (s *store) validateMinimumResourceVersion(minimumResourceVersion string, ac
911
937
return nil
912
938
}
913
939
940
+ func (s * store ) prepareKey (key string ) (string , error ) {
941
+ if key == ".." ||
942
+ strings .HasPrefix (key , "../" ) ||
943
+ strings .HasSuffix (key , "/.." ) ||
944
+ strings .Contains (key , "/../" ) {
945
+ return "" , fmt .Errorf ("invalid key: %q" , key )
946
+ }
947
+ if key == "." ||
948
+ strings .HasPrefix (key , "./" ) ||
949
+ strings .HasSuffix (key , "/." ) ||
950
+ strings .Contains (key , "/./" ) {
951
+ return "" , fmt .Errorf ("invalid key: %q" , key )
952
+ }
953
+ if key == "" || key == "/" {
954
+ return "" , fmt .Errorf ("empty key: %q" , key )
955
+ }
956
+ // We ensured that pathPrefix ends in '/' in construction, so skip any leading '/' in the key now.
957
+ startIndex := 0
958
+ if key [0 ] == '/' {
959
+ startIndex = 1
960
+ }
961
+ return s .pathPrefix + key [startIndex :], nil
962
+ }
963
+
914
964
// decode decodes value of bytes into object. It will also set the object resource version to rev.
915
965
// On success, objPtr would be set to the object.
916
966
func decode (codec runtime.Codec , versioner storage.Versioner , value []byte , objPtr runtime.Object , rev int64 ) error {
0 commit comments