Skip to content

Commit 347e27e

Browse files
committed
feat: introduce system buckets (part one)
* initial support for system buckets, starting with ".sys-inventory" * add QparamSystem to explicitly ask to "include system" * bucket naming: disallow leading '.' (is now reserved) * list-buckets: hide system buckets by default * enforce admin access for system buckets (when Auth is enabled - no-op otherwise) ----------- * TODO: - review/find all possible execution paths that are not yet covered (and require admin access if found) - CLI, tests, docs - the works Signed-off-by: Alex Aizman <alex.aizman@gmail.com>
1 parent 2d79ceb commit 347e27e

9 files changed

Lines changed: 63 additions & 20 deletions

File tree

‎ais/dpq.go‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ type (
8282
silent bool // QparamSilent
8383
latestVer bool // QparamLatestVer
8484
sync bool // QparamSync
85+
system bool // QparamSystem (allow system buckets)
8586

8687
// Special use (internal context)
8788
isS3 bool // frontend S3 API
@@ -185,6 +186,8 @@ func (dpq *dpq) parse(rawQuery string) error {
185186
dpq.latestVer = cos.IsParseBool(value)
186187
case apc.QparamSync:
187188
dpq.sync = cos.IsParseBool(value)
189+
case apc.QparamSystem:
190+
dpq.system = cos.IsParseBool(value)
188191

189192
case apc.QparamColoc:
190193
var coloc uint64

‎ais/proxy.go‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,12 @@ func (p *proxy) httpbckget(w http.ResponseWriter, r *http.Request, dpq *dpq) {
663663
if qbck.IsRemoteAIS() {
664664
qbck.Ns.UUID = p.a2u(qbck.Ns.UUID)
665665
}
666-
if err := p.checkAccess(w, r, nil, apc.AceListBuckets); err == nil {
666+
667+
ace := apc.AceListBuckets
668+
if dpq.system {
669+
ace |= apc.AceAdmin
670+
}
671+
if err := p.checkAccess(w, r, nil, ace); err == nil {
667672
p.listBuckets(w, r, qbck, msg, dpq)
668673
}
669674
return
@@ -2257,7 +2262,7 @@ func (p *proxy) listBuckets(w http.ResponseWriter, r *http.Request, qbck *cmn.Qu
22572262
present bool
22582263
)
22592264
if qbck.IsAIS() || qbck.IsHT() {
2260-
bcks := bmd.Select(qbck)
2265+
bcks := bmd.Select(qbck, dpq.system)
22612266
p.writeJSON(w, r, bcks, "list-buckets")
22622267
return
22632268
}
@@ -2269,7 +2274,7 @@ func (p *proxy) listBuckets(w http.ResponseWriter, r *http.Request, qbck *cmn.Qu
22692274
}
22702275
}
22712276
if present {
2272-
bcks := bmd.Select(qbck)
2277+
bcks := bmd.Select(qbck, dpq.system)
22732278
p.writeJSON(w, r, bcks, "list-buckets")
22742279
return
22752280
}

‎ais/prxauth.go‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,13 @@ func (p *proxy) access(ctx context.Context, hdr http.Header, bck *meta.Bck, ace
413413
return err
414414
}
415415

416+
// System buckets: require admin
417+
// - note that majority of control flows that operate on bucket(s) validate perm-s via bckArgs.initAndTry() => p.access()
418+
// - the rest that pass `bck == nil` MUST explicitly add apc.AceAdmin
419+
if bck != nil && bck.Bucket().IsSystem() {
420+
ace |= apc.AceAdmin
421+
}
422+
416423
// Validate token and parse claims ONCE
417424
claims, err := p.validateToken(ctx, hdr)
418425
if err != nil {

‎ais/tgtbck.go‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func (t *target) httpbckget(w http.ResponseWriter, r *http.Request, dpq *dpq) {
7070
// (see api.ListBuckets and the line below)
7171
if !qbck.IsBucket() {
7272
qbck.Name = msg.Name
73-
t.listBuckets(w, r, qbck)
73+
t.listBuckets(w, r, qbck, dpq)
7474
return
7575
}
7676
bck := meta.CloneBck((*cmn.Bck)(qbck))
@@ -169,7 +169,7 @@ func (t *target) httpbckget(w http.ResponseWriter, r *http.Request, dpq *dpq) {
169169
// there's a difference between looking for all (any) provider vs a specific one -
170170
// in the former case the fact that (the corresponding backend is not configured)
171171
// is not an error
172-
func (t *target) listBuckets(w http.ResponseWriter, r *http.Request, qbck *cmn.QueryBcks) {
172+
func (t *target) listBuckets(w http.ResponseWriter, r *http.Request, qbck *cmn.QueryBcks, dpq *dpq) {
173173
var (
174174
bcks cmn.Bcks
175175
config = cmn.GCO.Get()
@@ -179,7 +179,7 @@ func (t *target) listBuckets(w http.ResponseWriter, r *http.Request, qbck *cmn.Q
179179
)
180180
if qbck.Provider != "" {
181181
if qbck.IsAIS() || qbck.IsHT() { // built-in providers
182-
bcks = bmd.Select(qbck)
182+
bcks = bmd.Select(qbck, dpq.system)
183183
} else {
184184
bcks, code, err = t.blist(qbck, config)
185185
if err != nil {
@@ -195,7 +195,7 @@ func (t *target) listBuckets(w http.ResponseWriter, r *http.Request, qbck *cmn.Q
195195
var buckets cmn.Bcks
196196
qbck.Provider = provider
197197
if qbck.IsAIS() || qbck.IsHT() {
198-
buckets = bmd.Select(qbck)
198+
buckets = bmd.Select(qbck, dpq.system)
199199
} else {
200200
buckets, code, err = t.blist(qbck, config)
201201
if err != nil {

‎api/apc/query.go‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@ const (
241241
// GetBatch
242242
QparamTID = "tid" // designated target
243243
QparamColoc = "coloc" // colocation hint: enum { 0=ColocNone, 1=ColocOne, 2=ColocTwo }
244+
245+
// System
246+
QparamSystem = "sys"
244247
)
245248

246249
// QparamWhat enum.

‎cmn/bck.go‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,11 @@ func (b *Bck) ValidateName() error {
309309
if b.Name == "" {
310310
return errors.New("bucket name is missing")
311311
}
312-
if b.Name == "." {
313-
return fmt.Errorf(fmtErrBckName, b.Name)
312+
if b.Name[0] == '.' {
313+
if b.IsSystem() {
314+
return nil
315+
}
316+
return fmt.Errorf("reserved bucket name %q (unknown system bucket)", b.Name)
314317
}
315318
return cos.CheckAlphaPlus(b.Name, "bucket name")
316319
}
@@ -648,3 +651,16 @@ func NewHTTPObjPath(rawURL string) (*HTTPBckObj, error) {
648651
}
649652
return NewHTTPObj(urlObj), nil
650653
}
654+
655+
//
656+
// system buckets -----------------------------------------------
657+
//
658+
659+
const (
660+
sysPrefix = ".sys-" // currently unused; must be enforced when we add more system buckets
661+
sysInventoryName = ".sys-inventory"
662+
)
663+
664+
func (b *Bck) IsSystem() bool {
665+
return b.Name == sysInventoryName
666+
}

‎cmn/err.go‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import (
3232
const (
3333
stackTracePrefix = "stack: ["
3434

35-
fmtErrBckName = "bucket name %q is invalid: " + cos.OnlyPlus
3635
fmtErrNamespace = "bucket namespace (uuid: %q, name: %q) " + cos.OnlyNice
3736

3837
FmtErrIntegrity = "[%s%d, for troubleshooting see %s/blob/main/docs/troubleshooting.md]"

‎core/meta/bmd.go‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,22 +155,28 @@ func (m *BMD) Range(providerQuery *string, nsQuery *cmn.Ns, callback func(*Bck)
155155
}
156156
}
157157

158-
func (m *BMD) Select(qbck *cmn.QueryBcks) cmn.Bcks {
158+
func (m *BMD) Select(qbck *cmn.QueryBcks, includeSystem bool) cmn.Bcks {
159159
var (
160160
cp *string
161-
bcks = cmn.Bcks{} // (json representation: nil slice != empty slice)
161+
bcks = cmn.Bcks{} // nil slice != empty slice
162162
)
163163
if qbck.Provider != "" {
164164
cp = &qbck.Provider
165165
}
166166
m.Range(cp, nil, func(bck *Bck) bool {
167167
b := bck.Bucket()
168-
if qbck.Equal(b) || qbck.Contains(b) {
169-
if len(bcks) == 0 {
170-
bcks = make(cmn.Bcks, 0, 8)
171-
}
172-
bcks = append(bcks, bck.Clone())
168+
if !qbck.Equal(b) && !qbck.Contains(b) {
169+
return false
170+
}
171+
172+
if !includeSystem && b.IsSystem() {
173+
return false
174+
}
175+
176+
if len(bcks) == 0 {
177+
bcks = make(cmn.Bcks, 0, 16)
173178
}
179+
bcks = append(bcks, bck.Clone())
174180
return false
175181
})
176182
sort.Sort(bcks)

‎core/meta/bmd_test.go‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Package meta_test: unit tests for the package
22
/*
3-
* Copyright (c) 2018-2025, NVIDIA CORPORATION. All rights reserved.
3+
* Copyright (c) 2018-2026, NVIDIA CORPORATION. All rights reserved.
44
*/
55
package meta_test
66

@@ -23,8 +23,8 @@ var _ = Describe("BMD", func() {
2323
"bucket-1024",
2424
),
2525
Entry(
26-
"with dots",
27-
".bucket.name",
26+
"with dot in the middle",
27+
"bucket.name",
2828
),
2929
Entry(
3030
"with '_' and '-'",
@@ -41,6 +41,10 @@ var _ = Describe("BMD", func() {
4141
"empty bucket",
4242
"",
4343
),
44+
Entry(
45+
"with a leading dot",
46+
".bucket.name",
47+
),
4448
Entry(
4549
"contains '$'",
4650
"jhljs$lsf",

0 commit comments

Comments
 (0)