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

EOS: Parse sys ACLs to generate CS3 resource permissions #1402

Merged
merged 4 commits into from
Feb 2, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions changelog/unreleased/eos-parse-acls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enhancement: Parse EOS sys ACLs to generate CS3 resource permissions

https://github.com/cs3org/reva/pull/1402
69 changes: 42 additions & 27 deletions pkg/eosclient/eosbinary/eosbinary.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,18 @@ const (
)

func serializeAttribute(a *eosclient.Attribute) string {
return fmt.Sprintf("%d.%s=%s", a.Type, a.Key, a.Val)
return fmt.Sprintf("%s.%s=%s", attrTypeToString(a.Type), a.Key, a.Val)
}

func attrTypeToString(at eosclient.AttrType) string {
switch at {
case SystemAttr:
return "sys"
case UserAttr:
return "user"
default:
return "invalid"
}
}

func isValidAttribute(a *eosclient.Attribute) bool {
Expand Down Expand Up @@ -377,7 +388,7 @@ func (c *Client) getACLForPath(ctx context.Context, uid, gid, path string) (*acl
return nil, err
}

return acl.Parse(finfo.SysACL, acl.ShortTextForm)
return finfo.SysACL, nil
}

// GetFileInfoByInode returns the FileInfo by the given inode
Expand Down Expand Up @@ -413,6 +424,29 @@ func (c *Client) GetFileInfoByFXID(ctx context.Context, uid, gid string, fxid st
return c.parseFileInfo(stdout)
}

// GetFileInfoByPath returns the FilInfo at the given path
func (c *Client) GetFileInfoByPath(ctx context.Context, uid, gid, path string) (*eosclient.FileInfo, error) {
cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "file", "info", path, "-m")
stdout, _, err := c.executeEOS(ctx, cmd)
if err != nil {
return nil, err
}
info, err := c.parseFileInfo(stdout)
if err != nil {
return nil, err
}

if c.opt.VersionInvariant && !isVersionFolder(path) && !info.IsDir {
inode, err := c.getVersionFolderInode(ctx, uid, gid, path)
if err != nil {
return nil, err
}
info.Inode = inode
}

return info, nil
}

// SetAttr sets an extended attributes on a path.
func (c *Client) SetAttr(ctx context.Context, uid, gid string, attr *eosclient.Attribute, recursive bool, path string) error {
if !isValidAttribute(attr) {
Expand Down Expand Up @@ -445,29 +479,6 @@ func (c *Client) UnsetAttr(ctx context.Context, uid, gid string, attr *eosclient
return nil
}

// GetFileInfoByPath returns the FilInfo at the given path
func (c *Client) GetFileInfoByPath(ctx context.Context, uid, gid, path string) (*eosclient.FileInfo, error) {
cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "file", "info", path, "-m")
stdout, _, err := c.executeEOS(ctx, cmd)
if err != nil {
return nil, err
}
info, err := c.parseFileInfo(stdout)
if err != nil {
return nil, err
}

if c.opt.VersionInvariant && !isVersionFolder(path) && !info.IsDir {
inode, err := c.getVersionFolderInode(ctx, uid, gid, path)
if err != nil {
return nil, err
}
info.Inode = inode
}

return info, nil
}

// GetQuota gets the quota of a user on the quota node defined by path
func (c *Client) GetQuota(ctx context.Context, username, rootUID, rootGID, path string) (*eosclient.QuotaInfo, error) {
cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", rootUID, rootGID, "quota", "ls", "-u", username, "-m")
Expand Down Expand Up @@ -830,7 +841,6 @@ func (c *Client) parseFileInfo(raw string) (*eosclient.FileInfo, error) {
}
}
}

fi, err := c.mapToFileInfo(kv)
if err != nil {
return nil, err
Expand Down Expand Up @@ -922,6 +932,11 @@ func (c *Client) mapToFileInfo(kv map[string]string) (*eosclient.FileInfo, error
isDir = true
}

sysACL, err := acl.Parse(kv["sys.acl"], acl.ShortTextForm)
if err != nil {
return nil, err
}

fi := &eosclient.FileInfo{
File: kv["file"],
Inode: inode,
Expand All @@ -935,7 +950,7 @@ func (c *Client) mapToFileInfo(kv map[string]string) (*eosclient.FileInfo, error
MTimeNanos: uint32(mtimenanos),
IsDir: isDir,
Instance: c.opt.URL,
SysACL: kv["sys.acl"],
SysACL: sysACL,
TreeCount: treeCount,
Attrs: kv,
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/eosclient/eosclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ type FileInfo struct {
File string `json:"eos_file"`
ETag string `json:"etag"`
Instance string `json:"instance"`
SysACL string `json:"sys_acl"`
SysACL *acl.ACLs `json:"sys_acl"`
Attrs map[string]string `json:"attrs"`
}

Expand Down
112 changes: 68 additions & 44 deletions pkg/storage/utils/eosfs/eosfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,6 @@ func (fs *eosfs) listShareFolderRoot(ctx context.Context, p string) (finfos []*p
if err != nil {
return nil, errors.Wrap(err, "eos: no user in ctx")
}

uid, gid, err := fs.getUserUIDAndGID(ctx, u)
if err != nil {
return nil, err
Expand Down Expand Up @@ -805,7 +804,7 @@ func (fs *eosfs) createShadowHome(ctx context.Context) error {

shadowFolders := []string{fs.conf.ShareFolder}
for _, sf := range shadowFolders {
err = fs.c.CreateDir(ctx, uid, gid, path.Join(home, sf))
err = fs.createUserDir(ctx, u, path.Join(home, sf))
if err != nil {
return err
}
Expand Down Expand Up @@ -939,6 +938,10 @@ func (fs *eosfs) CreateDir(ctx context.Context, p string) error {
func (fs *eosfs) CreateReference(ctx context.Context, p string, targetURI *url.URL) error {
// TODO(labkode): for the time being we only allow to create references
// on the virtual share folder to not pollute the nominal user tree.
u, err := getUser(ctx)
if err != nil {
return errors.Wrap(err, "eos: no user in ctx")
}

if !fs.isShareFolder(ctx, p) {
return errtypes.PermissionDenied("eos: cannot create references outside the share folder: share_folder=" + fs.conf.ShareFolder + " path=" + p)
Expand All @@ -954,7 +957,7 @@ func (fs *eosfs) CreateReference(ctx context.Context, p string, targetURI *url.U
if err != nil {
return nil
}
if err := fs.c.CreateDir(ctx, uid, gid, tmp); err != nil {
if err := fs.createUserDir(ctx, u, tmp); err != nil {
err = errors.Wrapf(err, "eos: error creating temporary ref file")
return err
}
Expand Down Expand Up @@ -1311,18 +1314,14 @@ func (fs *eosfs) convertToFileReference(ctx context.Context, eosFileInfo *eoscli
}

// permissionSet returns the permission set for the current user
func (fs *eosfs) permissionSet(ctx context.Context, owner *userpb.UserId) *provider.ResourcePermissions {
func (fs *eosfs) permissionSet(ctx context.Context, eosFileInfo *eosclient.FileInfo, owner *userpb.UserId) *provider.ResourcePermissions {
u, ok := user.ContextGetUser(ctx)
if !ok {
return &provider.ResourcePermissions{
// no permissions
}
}
if u.Id == nil {
if !ok || owner == nil || u.Id == nil {
return &provider.ResourcePermissions{
// no permissions
}
}

if u.Id.OpaqueId == owner.OpaqueId && u.Id.Idp == owner.Idp {
return &provider.ResourcePermissions{
// owner has all permissions
Expand All @@ -1346,27 +1345,63 @@ func (fs *eosfs) permissionSet(ctx context.Context, owner *userpb.UserId) *provi
UpdateGrant: true,
}
}
// TODO fix permissions for share recipients by traversing reading acls up to the root? cache acls for the parent node and reuse it
return &provider.ResourcePermissions{
AddGrant: true,
CreateContainer: true,
Delete: true,
GetPath: true,
GetQuota: true,
InitiateFileDownload: true,
InitiateFileUpload: true,
ListContainer: true,
ListFileVersions: true,
ListGrants: true,
ListRecycle: true,
Move: true,
PurgeRecycle: true,
RemoveGrant: true,
RestoreFileVersion: true,
RestoreRecycleItem: true,
Stat: true,
UpdateGrant: true,

uid, gid, err := fs.getUserUIDAndGID(ctx, u)
if err != nil {
return &provider.ResourcePermissions{
// no permissions
}
}

var perm string
var rp provider.ResourcePermissions
for _, e := range eosFileInfo.SysACL.Entries {
if e.Qualifier == uid || e.Qualifier == gid {
perm = e.Permissions
}
}

// Rules adapted from https://github.com/cern-eos/eos/blob/master/doc/configuration/permission.rst
if strings.Contains(perm, "r") && !strings.Contains(perm, "!r") {
rp.GetPath = true
rp.Stat = true
rp.ListFileVersions = true
rp.InitiateFileDownload = true
if eosFileInfo.IsDir {
rp.ListContainer = true
}
}

if strings.Contains(perm, "w") && !strings.Contains(perm, "!w") {
rp.Move = true
rp.Delete = true
rp.InitiateFileUpload = true
rp.RestoreFileVersion = true
if eosFileInfo.IsDir {
rp.CreateContainer = true
}
}

if strings.Contains(perm, "!x") {
rp.ListContainer = false
rp.ListFileVersions = false
}

if strings.Contains(perm, "!d") {
rp.Delete = false
}

if strings.Contains(perm, "m") && !strings.Contains(perm, "!m") {
rp.AddGrant = true
rp.ListGrants = true
rp.RemoveGrant = true
}

if strings.Contains(perm, "q") && !strings.Contains(perm, "!q") {
rp.GetQuota = true
}

return &rp
}

func (fs *eosfs) convert(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.ResourceInfo, error) {
Expand All @@ -1382,8 +1417,8 @@ func (fs *eosfs) convert(ctx context.Context, eosFileInfo *eosclient.FileInfo) (

owner, err := fs.getUserIDGateway(ctx, strconv.FormatUint(eosFileInfo.UID, 10))
if err != nil {
log := appctx.GetLogger(ctx)
log.Warn().Uint64("uid", eosFileInfo.UID).Msg("could not lookup userid, leaving empty")
sublog := appctx.GetLogger(ctx).With().Logger()
sublog.Warn().Uint64("uid", eosFileInfo.UID).Msg("could not lookup userid, leaving empty")
owner = &userpb.UserId{}
}

Expand All @@ -1394,7 +1429,7 @@ func (fs *eosfs) convert(ctx context.Context, eosFileInfo *eosclient.FileInfo) (
Etag: fmt.Sprintf("\"%s\"", strings.Trim(eosFileInfo.ETag, "\"")),
MimeType: mime.Detect(eosFileInfo.IsDir, path),
Size: size,
PermissionSet: fs.permissionSet(ctx, owner),
PermissionSet: fs.permissionSet(ctx, eosFileInfo, owner),
Mtime: &types.Timestamp{
Seconds: eosFileInfo.MTimeSec,
Nanos: eosFileInfo.MTimeNanos,
Expand Down Expand Up @@ -1514,17 +1549,6 @@ func (fs *eosfs) getRootUIDAndGID(ctx context.Context) (string, string, error) {
return "0", "0", nil
}

//func attrTypeToString(at eosclient.AttrType) string {
// switch at {
// case SystemAttr:
// return "sys"
// case UserAttr:
// return "user"
// default:
// return "invalid"
// }
//}

type eosSysMetadata struct {
TreeSize uint64 `json:"tree_size"`
TreeCount uint64 `json:"tree_count"`
Expand Down