Skip to content

Commit 73744e5

Browse files
pstibranypracucci
andauthored
Ignore objects with names that end with / when listing rule groups (#3999)
* Ignore objects with names that end with / Signed-off-by: Peter Štibraný <[email protected]> * CHANGELOG.md Signed-off-by: Peter Štibraný <[email protected]> * Update CHANGELOG.md Signed-off-by: Marco Pracucci <[email protected]> * Validate user, namespace and group. Signed-off-by: Peter Štibraný <[email protected]> Co-authored-by: Marco Pracucci <[email protected]>
1 parent 3064bab commit 73744e5

File tree

3 files changed

+74
-5
lines changed

3 files changed

+74
-5
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* [BUGFIX] Distributor: reverted changes done to rate limiting in #3825. #3948
2323
* [BUGFIX] Ingester: Fix race condition when opening and closing tsdb concurrently. #3959
2424
* [BUGFIX] Querier: streamline tracing spans. #3924
25+
* [BUGFIX] Ruler Storage: ignore objects with empty namespace or group in the name. #3999
2526
* [BUGFIX] Distributor: fix issue causing distributors to not extend the replication set because of failing instances when zone-aware replication is enabled. #3977
2627

2728
## 1.8.0 in progress

pkg/ruler/rulestore/bucketclient/bucket_client.go

+19-5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ const (
2929

3030
var (
3131
errInvalidRuleGroupKey = errors.New("invalid rule group object key")
32+
errEmptyUser = errors.New("empty user")
33+
errEmptyNamespace = errors.New("empty namespace")
34+
errEmptyGroupName = errors.New("empty group name")
3235
)
3336

3437
// BucketRuleStore is used to support the RuleStore interface against an object storage backend. It is implemented
@@ -104,7 +107,7 @@ func (b *BucketRuleStore) ListAllRuleGroups(ctx context.Context) (map[string]rul
104107
err := b.bucket.Iter(ctx, "", func(key string) error {
105108
userID, namespace, group, err := parseRuleGroupObjectKeyWithUser(key)
106109
if err != nil {
107-
level.Warn(b.logger).Log("msg", "invalid rule group object key found while listing rule groups", "key", key)
110+
level.Warn(b.logger).Log("msg", "invalid rule group object key found while listing rule groups", "key", key, "err", err)
108111

109112
// Do not fail just because of a spurious item in the bucket.
110113
return nil
@@ -141,7 +144,7 @@ func (b *BucketRuleStore) ListRuleGroupsForUserAndNamespace(ctx context.Context,
141144
err := userBucket.Iter(ctx, prefix, func(key string) error {
142145
namespace, group, err := parseRuleGroupObjectKey(key)
143146
if err != nil {
144-
level.Warn(b.logger).Log("msg", "invalid rule group object key found while listing rule groups", "user", userID, "key", key)
147+
level.Warn(b.logger).Log("msg", "invalid rule group object key found while listing rule groups", "user", userID, "key", key, "err", err)
145148

146149
// Do not fail just because of a spurious item in the bucket.
147150
return nil
@@ -280,25 +283,36 @@ func parseRuleGroupObjectKeyWithUser(key string) (user, namespace, group string,
280283
}
281284

282285
user = parts[0]
286+
if user == "" {
287+
return "", "", "", errEmptyUser
288+
}
283289
namespace, group, err = parseRuleGroupObjectKey(parts[1])
284290
return
285291
}
286292

287293
// parseRuleGroupObjectKey parses a bucket object key in the format "<namespace>/<rules group>".
288-
func parseRuleGroupObjectKey(key string) (namespace, group string, err error) {
294+
func parseRuleGroupObjectKey(key string) (namespace, group string, _ error) {
289295
parts := strings.Split(key, objstore.DirDelim)
290296
if len(parts) != 2 {
291297
return "", "", errInvalidRuleGroupKey
292298
}
293299

294300
decodedNamespace, err := base64.URLEncoding.DecodeString(parts[0])
295301
if err != nil {
296-
return
302+
return "", "", err
303+
}
304+
305+
if len(decodedNamespace) == 0 {
306+
return "", "", errEmptyNamespace
297307
}
298308

299309
decodedGroup, err := base64.URLEncoding.DecodeString(parts[1])
300310
if err != nil {
301-
return
311+
return "", "", err
312+
}
313+
314+
if len(decodedGroup) == 0 {
315+
return "", "", errEmptyGroupName
302316
}
303317

304318
return string(decodedNamespace), string(decodedGroup), nil

pkg/ruler/rulestore/bucketclient/bucket_client_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,18 @@ func TestParseRuleGroupObjectKey(t *testing.T) {
291291
key: "way/too/long",
292292
expectedErr: errInvalidRuleGroupKey,
293293
},
294+
"empty namespace": {
295+
key: fmt.Sprintf("/%s", encodedGroup),
296+
expectedErr: errEmptyNamespace,
297+
},
294298
"invalid namespace encoding": {
295299
key: fmt.Sprintf("invalid/%s", encodedGroup),
296300
expectedErr: errors.New("illegal base64 data at input byte 4"),
297301
},
302+
"empty group": {
303+
key: fmt.Sprintf("%s/", encodedNamespace),
304+
expectedErr: errEmptyGroupName,
305+
},
298306
"invalid group encoding": {
299307
key: fmt.Sprintf("%s/invalid", encodedNamespace),
300308
expectedErr: errors.New("illegal base64 data at input byte 4"),
@@ -343,10 +351,22 @@ func TestParseRuleGroupObjectKeyWithUser(t *testing.T) {
343351
key: "way/too/much/long",
344352
expectedErr: errInvalidRuleGroupKey,
345353
},
354+
"empty user": {
355+
key: fmt.Sprintf("/%s/%s", encodedNamespace, encodedGroup),
356+
expectedErr: errEmptyUser,
357+
},
358+
"empty namespace": {
359+
key: fmt.Sprintf("user-1//%s", encodedGroup),
360+
expectedErr: errEmptyNamespace,
361+
},
346362
"invalid namespace encoding": {
347363
key: fmt.Sprintf("user-1/invalid/%s", encodedGroup),
348364
expectedErr: errors.New("illegal base64 data at input byte 4"),
349365
},
366+
"empty group name": {
367+
key: fmt.Sprintf("user-1/%s/", encodedNamespace),
368+
expectedErr: errEmptyGroupName,
369+
},
350370
"invalid group encoding": {
351371
key: fmt.Sprintf("user-1/%s/invalid", encodedNamespace),
352372
expectedErr: errors.New("illegal base64 data at input byte 4"),
@@ -374,3 +394,37 @@ func TestParseRuleGroupObjectKeyWithUser(t *testing.T) {
374394
})
375395
}
376396
}
397+
398+
func TestListAllRuleGroupsWithNoNamespaceOrGroup(t *testing.T) {
399+
obj := mockBucket{
400+
names: []string{
401+
"rules/",
402+
"rules/user1/",
403+
"rules/user2/bnM=/", // namespace "ns", ends with '/'
404+
"rules/user3/bnM=/Z3JvdXAx", // namespace "ns", group "group1"
405+
},
406+
}
407+
408+
s := NewBucketRuleStore(obj, nil, log.NewNopLogger())
409+
out, err := s.ListAllRuleGroups(context.Background())
410+
require.NoError(t, err)
411+
412+
require.Equal(t, 1, len(out)) // one user
413+
require.Equal(t, 1, len(out["user3"])) // one group
414+
require.Equal(t, "group1", out["user3"][0].Name) // one group
415+
}
416+
417+
type mockBucket struct {
418+
objstore.Bucket
419+
420+
names []string
421+
}
422+
423+
func (mb mockBucket) Iter(_ context.Context, dir string, f func(string) error, options ...objstore.IterOption) error {
424+
for _, n := range mb.names {
425+
if err := f(n); err != nil {
426+
return err
427+
}
428+
}
429+
return nil
430+
}

0 commit comments

Comments
 (0)