Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/acl: add acl support #4443

Merged
merged 22 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions pkg/acl/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type Cache interface {
Get(id uint32) *Rule
GetId(r *Rule) uint32
Size() int
GetMissIds(maxId uint32) []uint32
GetMissIds() []uint32
}

func NewCache() Cache {
Expand All @@ -52,20 +52,16 @@ type cache struct {
cksum2Id map[uint32][]uint32
}

// GetMissIds return all miss ids from 1 to max(maxId, c.maxId)
func (c *cache) GetMissIds(maxId uint32) []uint32 {
// GetMissIds return all miss ids from 1 to c.maxId
func (c *cache) GetMissIds() []uint32 {
c.lock.RLock()
defer c.lock.RUnlock()

if c.maxId == maxId && uint32(len(c.id2Rule)) == maxId {
if uint32(len(c.id2Rule)) == c.maxId {
return nil
}

if c.maxId > maxId {
maxId = c.maxId
}

n := maxId + 1
n := c.maxId + 1
mark := make([]bool, n)
for i := uint32(1); i < n; i++ {
if _, ok := c.id2Rule[i]; ok {
Expand Down
3 changes: 1 addition & 2 deletions pkg/acl/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ func TestCache(t *testing.T) {
assert.Equal(t, uint32(3), c.GetId(rule2))

c.Put(8, rule2)
assert.Equal(t, []uint32{4, 5, 6, 7, 9, 10}, c.GetMissIds(10))
assert.Equal(t, []uint32{4, 5, 6, 7}, c.GetMissIds(6))
assert.Equal(t, []uint32{4, 5, 6, 7}, c.GetMissIds())

assert.NotPanics(t, func() {
c.Put(10, nil)
Expand Down
68 changes: 64 additions & 4 deletions pkg/meta/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"time"

"github.com/dustin/go-humanize"
aclAPI "github.com/juicedata/juicefs/pkg/acl"
"github.com/juicedata/juicefs/pkg/utils"
"github.com/juicedata/juicefs/pkg/version"
"github.com/pkg/errors"
Expand Down Expand Up @@ -162,6 +163,7 @@ type baseMeta struct {
reloadCb []func(*Format)
umounting bool
sesMu sync.Mutex
aclCache aclAPI.Cache

dirStatsLock sync.Mutex
dirStats map[Ino]dirStat
Expand Down Expand Up @@ -202,6 +204,7 @@ func newBaseMeta(addr string, conf *Config) *baseMeta {
msgCallbacks: &msgCallbacks{
callbacks: make(map[uint32]MsgCallback),
},
aclCache: aclAPI.NewCache(),

usedSpaceG: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "used_space",
Expand Down Expand Up @@ -1164,11 +1167,19 @@ func (m *baseMeta) parseAttr(buf []byte, attr *Attr) {
attr.Parent = Ino(rb.Get64())
}
attr.Full = true
if rb.Left() >= 8 {
attr.AccessACLId = rb.Get32()
attr.DefaultACLId = rb.Get32()
}
logger.Tracef("attr: %+v -> %+v", buf, attr)
}

func (m *baseMeta) marshal(attr *Attr) []byte {
w := utils.NewBuffer(36 + 24 + 4 + 8)
size := uint32(36 + 24 + 4 + 8)
if attr.AccessACLId+attr.DefaultACLId > 0 {
jiefenghuang marked this conversation as resolved.
Show resolved Hide resolved
size += 8
}
w := utils.NewBuffer(size)
w.Put8(attr.Flags)
w.Put16((uint16(attr.Typ) << 12) | (attr.Mode & 0xfff))
w.Put32(attr.Uid)
Expand All @@ -1183,6 +1194,10 @@ func (m *baseMeta) marshal(attr *Attr) []byte {
w.Put64(attr.Length)
w.Put32(attr.Rdev)
w.Put64(uint64(attr.Parent))
if attr.AccessACLId+attr.DefaultACLId > 0 {
w.Put32(attr.AccessACLId)
w.Put32(attr.DefaultACLId)
}
logger.Tracef("attr: %+v -> %+v", attr, w.Bytes())
return w.Bytes()
}
Expand Down Expand Up @@ -2696,7 +2711,7 @@ LOOP:
return eno
}

func (m *baseMeta) mergeAttr(ctx Context, inode Ino, set uint16, cur, attr *Attr, now time.Time) (*Attr, syscall.Errno) {
func (m *baseMeta) mergeAttr(ctx Context, inode Ino, set uint16, cur, attr *Attr, now time.Time, rule *aclAPI.Rule) (*Attr, syscall.Errno) {
dirtyAttr := *cur
if (set&(SetAttrUID|SetAttrGID)) != 0 && (set&SetAttrMode) != 0 {
attr.Mode |= (cur.Mode & 06000)
Expand Down Expand Up @@ -2731,7 +2746,12 @@ func (m *baseMeta) mergeAttr(ctx Context, inode Ino, set uint16, cur, attr *Attr
attr.Mode &= 05777
}
}
if attr.Mode != cur.Mode {

if rule != nil {
rule.SetMode(attr.Mode)
dirtyAttr.Mode = attr.Mode&07000 | rule.GetMode()
changed = true
} else if attr.Mode != cur.Mode {
if ctx.Uid() != 0 && ctx.Uid() != cur.Uid &&
(cur.Mode&01777 != attr.Mode&01777 || attr.Mode&02000 > cur.Mode&02000 || attr.Mode&04000 > cur.Mode&04000) {
return nil, syscall.EPERM
Expand Down Expand Up @@ -2793,6 +2813,46 @@ func (m *baseMeta) CheckSetAttr(ctx Context, inode Ino, set uint16, attr Attr) s
if st := m.en.doGetAttr(ctx, inode, &cur); st != 0 {
return st
}
_, st := m.mergeAttr(ctx, inode, set, &cur, &attr, time.Now())
_, st := m.mergeAttr(ctx, inode, set, &cur, &attr, time.Now(), nil)
return st
}

const aclCounter = "acl"
jiefenghuang marked this conversation as resolved.
Show resolved Hide resolved

var errACLNotInCache = errors.New("acl not in cache")

func (m *baseMeta) getFaclFromCache(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) error {
ino = m.checkRoot(ino)
cAttr := &Attr{}
if m.conf.OpenCache > 0 && m.of.Check(ino, cAttr) {
aclId := getAttrACLId(cAttr, aclType)
if aclId == aclAPI.None {
return ENOATTR
}

if cRule := m.aclCache.Get(aclId); cRule != nil {
*rule = *cRule
return nil
}
}
return errACLNotInCache
}

func setAttrACLId(attr *Attr, aclType uint8, id uint32) {
switch aclType {
case aclAPI.TypeAccess:
attr.AccessACLId = id
case aclAPI.TypeDefault:
attr.DefaultACLId = id
}
}

func getAttrACLId(attr *Attr, aclType uint8) uint32 {
switch aclType {
case aclAPI.TypeAccess:
return attr.AccessACLId
case aclAPI.TypeDefault:
return attr.DefaultACLId
}
return aclAPI.None
}
177 changes: 176 additions & 1 deletion pkg/meta/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ import (
"testing"
"time"

"xorm.io/xorm"

aclAPI "github.com/juicedata/juicefs/pkg/acl"
"github.com/juicedata/juicefs/pkg/utils"
"github.com/redis/go-redis/v9"
"xorm.io/xorm"
"github.com/stretchr/testify/assert"
)

func testConfig() *Config {
Expand Down Expand Up @@ -146,10 +149,182 @@ func testMeta(t *testing.T, m Meta) {
testCheckAndRepair(t, m)
testDirStat(t, m)
testClone(t, m)
testACL(t, m)
base.conf.ReadOnly = true
testReadOnly(t, m)
}

func testACL(t *testing.T, m Meta) {
if err := m.Init(testFormat(), false); err != nil {
t.Fatalf("test acl failed: %s", err)
}

ctx := Background
testDir := "test_dir"
var testDirIno Ino
attr1 := &Attr{}

if st := m.Mkdir(ctx, RootInode, testDir, 0644, 0, 0, &testDirIno, attr1); st != 0 {
t.Fatalf("create %s: %s", testDir, st)
}

rule := &aclAPI.Rule{
Owner: 7,
Group: 7,
Mask: 7,
Other: 7,
NamedUsers: []aclAPI.Entry{
{
Id: 1001,
Perm: 4,
},
},
NamedGroups: nil,
}

// case: setfacl
if st := m.SetFacl(ctx, testDirIno, aclAPI.TypeAccess, rule); st != 0 {
t.Fatalf("setfacl error: %s", st)
}

// case: getfacl
rule2 := &aclAPI.Rule{}
if st := m.GetFacl(ctx, testDirIno, aclAPI.TypeAccess, rule2); st != 0 {
t.Fatalf("getfacl error: %s", st)
}
assert.True(t, rule.IsEqual(rule2))

// case: setfacl will sync mode (group class is mask)
attr2 := &Attr{}
if st := m.GetAttr(ctx, testDirIno, attr2); st != 0 {
t.Fatalf("getattr error: %s", st)
}
assert.Equal(t, uint16(0777), attr2.Mode)

// case: setattr will sync acl
set := uint16(0) | SetAttrMode
attr2 = &Attr{
Mode: 0555,
}
if st := m.SetAttr(ctx, testDirIno, set, 0, attr2); st != 0 {
t.Fatalf("setattr error: %s", st)
}

rule3 := &aclAPI.Rule{}
if st := m.GetFacl(ctx, testDirIno, aclAPI.TypeAccess, rule3); st != 0 {
t.Fatalf("getfacl error: %s", st)
}
rule2.Owner = 5
rule2.Mask = 5
rule2.Other = 5
assert.True(t, rule3.IsEqual(rule2))

// case: remove acl
rule3.Mask = 0xFFFF
rule3.NamedUsers = nil
rule3.NamedGroups = nil
if st := m.SetFacl(ctx, testDirIno, aclAPI.TypeAccess, rule3); st != 0 {
t.Fatalf("setattr error: %s", st)
}

st := m.GetFacl(ctx, testDirIno, aclAPI.TypeAccess, nil)
assert.Equal(t, ENOATTR, st)

attr2 = &Attr{}
if st := m.GetAttr(ctx, testDirIno, attr2); st != 0 {
t.Fatalf("getattr error: %s", st)
}
assert.Equal(t, uint16(0575), attr2.Mode)

// case: set normal default acl
if st := m.SetFacl(ctx, testDirIno, aclAPI.TypeDefault, rule); st != 0 {
t.Fatalf("setfacl error: %s", st)
}

// case: get normal default acl
rule2 = &aclAPI.Rule{}
if st := m.GetFacl(ctx, testDirIno, aclAPI.TypeDefault, rule2); st != 0 {
t.Fatalf("getfacl error: %s", st)
}
assert.True(t, rule2.IsEqual(rule))

// case: mk subdir with normal default acl
subDir := "sub_dir"
var subDirIno Ino
attr2 = &Attr{}

mode := uint16(0222)
// cumask will be ignored
if st := m.Mkdir(ctx, testDirIno, subDir, mode, 0022, 0, &subDirIno, attr2); st != 0 {
t.Fatalf("create %s: %s", subDir, st)
}

// subdir inherit default acl
rule3 = &aclAPI.Rule{}
if st := m.GetFacl(ctx, subDirIno, aclAPI.TypeDefault, rule3); st != 0 {
t.Fatalf("getfacl error: %s", st)
}
assert.True(t, rule3.IsEqual(rule2))

// subdir access acl
rule3 = &aclAPI.Rule{}
if st := m.GetFacl(ctx, subDirIno, aclAPI.TypeAccess, rule3); st != 0 {
t.Fatalf("getfacl error: %s", st)
}
rule2.Owner &= (mode >> 6) & 7
rule2.Mask &= (mode >> 3) & 7
rule2.Other &= mode & 7
assert.True(t, rule3.IsEqual(rule2))

// case: set minimal default acl
rule = &aclAPI.Rule{
Owner: 5,
Group: 5,
Mask: 0xFFFF,
Other: 5,
NamedUsers: nil,
NamedGroups: nil,
}
if st := m.SetFacl(ctx, testDirIno, aclAPI.TypeDefault, rule); st != 0 {
t.Fatalf("setfacl error: %s", st)
}

// case: get minimal default acl
rule2 = &aclAPI.Rule{}
if st := m.GetFacl(ctx, testDirIno, aclAPI.TypeDefault, rule2); st != 0 {
t.Fatalf("getfacl error: %s", st)
}
assert.True(t, rule2.IsEqual(rule))

// case: mk subdir with minimal default acl
subDir2 := "sub_dir2"
var subDirIno2 Ino
attr2 = &Attr{}

mode = uint16(0222)
if st := m.Mkdir(ctx, testDirIno, subDir2, mode, 0022, 0, &subDirIno2, attr2); st != 0 {
t.Fatalf("create %s: %s", subDir, st)
}

// subdir inherit default acl
rule3 = &aclAPI.Rule{}
if st := m.GetFacl(ctx, subDirIno2, aclAPI.TypeDefault, rule3); st != 0 {
t.Fatalf("getfacl error: %s", st)
}
assert.True(t, rule3.IsEqual(rule2))

// subdir have no access acl
rule3 = &aclAPI.Rule{}
st = m.GetFacl(ctx, subDirIno2, aclAPI.TypeAccess, rule3)
assert.Equal(t, ENOATTR, st)

attr2 = &Attr{}
if st := m.GetAttr(ctx, subDirIno2, attr2); st != 0 {
t.Fatalf("getattr error: %s", st)
}
assert.Equal(t, rule.GetMode(), attr2.Mode)
}

func testMetaClient(t *testing.T, m Meta) {
m.OnMsg(DeleteSlice, func(args ...interface{}) error { return nil })
ctx := Background
Expand Down
Loading
Loading