diff --git a/changelog/unreleased/no-more-shadow-ns.md b/changelog/unreleased/no-more-shadow-ns.md new file mode 100644 index 0000000000..53ecff506e --- /dev/null +++ b/changelog/unreleased/no-more-shadow-ns.md @@ -0,0 +1,14 @@ +Enhancement: drop shadow namespaces + +This comes as part of the effort to operate EOS without being root, see https://github.com/cs3org/reva/pull/4977 + +In this PR the post-home creation hook (and corresponding flag) is replaced by a create_home_hook, and the following configuration parameters are suppressed: + + shadow_namespace + share_folder + default_quota_bytes + default_secondary_quota_bytes + default_quota_files + uploads_namespace (unused) + +https://github.com/cs3org/reva/pull/4984 \ No newline at end of file diff --git a/changelog/unreleased/rootless-auth.md b/changelog/unreleased/rootless-auth.md new file mode 100644 index 0000000000..09a907f62d --- /dev/null +++ b/changelog/unreleased/rootless-auth.md @@ -0,0 +1,10 @@ +Enhancement: do not use root on EOS + +Currently, the EOS drivers use root authentication for many different operations. This has now been changed to use one of the following: +* cbox, which is a sudo'er +* daemon, for read-only operations +* the user himselft + +Note that home creation is excluded here as this will be tackled in a different PR. + +https://github.com/cs3org/reva/pull/4977/ \ No newline at end of file diff --git a/pkg/eosclient/eosgrpc/eosgrpc.go b/pkg/eosclient/eosgrpc/eosgrpc.go index fb867c5a95..c2ca7a8d78 100644 --- a/pkg/eosclient/eosgrpc/eosgrpc.go +++ b/pkg/eosclient/eosgrpc/eosgrpc.go @@ -34,11 +34,11 @@ import ( "time" erpc "github.com/cern-eos/go-eosgrpc" - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/eosclient" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/storage/utils/acl" + "github.com/cs3org/reva/pkg/utils" "github.com/google/uuid" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -58,27 +58,12 @@ const ( UserAttr ) -func serializeAttribute(a *eosclient.Attribute) string { - return fmt.Sprintf("%s.%s=%s", attrTypeToString(a.Type), a.Key, a.Val) -} - -func attrTypeToString(at eosclient.AttrType) string { - switch at { - case eosclient.SystemAttr: - return "sys" - case eosclient.UserAttr: - return "user" - default: - return "invalid" - } -} - -func isValidAttribute(a *eosclient.Attribute) bool { - // validate that an attribute is correct. - if (a.Type != eosclient.SystemAttr && a.Type != eosclient.UserAttr) || a.Key == "" { - return false - } - return true +// Client performs actions against a EOS management node (MGM) +// using the EOS GRPC interface. +type Client struct { + opt *Options + httpcl *EOSHTTPClient + cl erpc.EosClient } // Options to configure the Client. @@ -131,15 +116,6 @@ type Options struct { TokenExpiry int } -func getUser(ctx context.Context) (*userpb.User, error) { - u, ok := appctx.ContextGetUser(ctx) - if !ok { - err := errors.Wrap(errtypes.UserRequired(""), "eosfs: error getting user from ctx") - return nil, err - } - return u, nil -} - func (opt *Options) init() { if opt.XrdcopyBinary == "" { opt.XrdcopyBinary = "/opt/eos/xrootd/bin/xrdcopy" @@ -154,12 +130,27 @@ func (opt *Options) init() { } } -// Client performs actions against a EOS management node (MGM) -// using the EOS GRPC interface. -type Client struct { - opt *Options - httpcl *EOSHTTPClient - cl erpc.EosClient +func serializeAttribute(a *eosclient.Attribute) string { + return fmt.Sprintf("%s.%s=%s", attrTypeToString(a.Type), a.Key, a.Val) +} + +func attrTypeToString(at eosclient.AttrType) string { + switch at { + case eosclient.SystemAttr: + return "sys" + case eosclient.UserAttr: + return "user" + default: + return "invalid" + } +} + +func isValidAttribute(a *eosclient.Attribute) bool { + // validate that an attribute is correct. + if (a.Type != eosclient.SystemAttr && a.Type != eosclient.UserAttr) || a.Key == "" { + return false + } + return true } // Create and connect a grpc eos Client. @@ -230,28 +221,33 @@ func (c *Client) getRespError(rsp *erpc.NSResponse, err error) error { // Common code to create and initialize a NSRequest. func (c *Client) initNSRequest(ctx context.Context, auth eosclient.Authorization, app string) (*erpc.NSRequest, error) { - // Stuff filename, uid, gid into the MDRequest type - log := appctx.GetLogger(ctx) log.Debug().Str("(uid,gid)", "("+auth.Role.UID+","+auth.Role.GID+")").Msg("New grpcNS req") rq := new(erpc.NSRequest) rq.Role = new(erpc.RoleId) - uidInt, err := strconv.ParseUint(auth.Role.UID, 10, 64) - if err != nil { - return nil, err - } - gidInt, err := strconv.ParseUint(auth.Role.GID, 10, 64) - if err != nil { - return nil, err + // Let's put in the authentication info + if auth.Token != "" { + // Map to owner using EOSAUTHZ token + // We do not become cbox + rq.Authkey = auth.Token + } else { + // We take the secret key from the config, which maps on EOS to cbox + // cbox is a sudo'er, so we become the user specified in UID/GID, if it is set + rq.Authkey = c.opt.Authkey + + uid, gid, err := utils.ExtractUidGid(auth) + if err == nil { + rq.Role.Uid = uid + rq.Role.Gid = gid + } } - rq.Role.Uid = uidInt - rq.Role.Gid = gidInt + + // For NS operations, specifically for locking, we also need to provide the app if app != "" { rq.Role.App = app } - rq.Authkey = c.opt.Authkey return rq, nil } @@ -263,23 +259,26 @@ func (c *Client) initMDRequest(ctx context.Context, auth eosclient.Authorization log := appctx.GetLogger(ctx) log.Debug().Str("(uid,gid)", "("+auth.Role.UID+","+auth.Role.GID+")").Msg("New grpcMD req") - mdrq := new(erpc.MDRequest) - mdrq.Role = new(erpc.RoleId) + rq := new(erpc.MDRequest) + rq.Role = new(erpc.RoleId) - uidInt, err := strconv.ParseUint(auth.Role.UID, 10, 64) - if err != nil { - return nil, err - } - gidInt, err := strconv.ParseUint(auth.Role.GID, 10, 64) - if err != nil { - return nil, err - } - mdrq.Role.Uid = uidInt - mdrq.Role.Gid = gidInt + if auth.Token != "" { + // Map to owner using EOSAUTHZ token + // We do not become cbox + rq.Authkey = auth.Token + } else { + // We take the secret key from the config, which maps on EOS to cbox + // cbox is a sudo'er, so we become the user specified in UID/GID, if it is set + rq.Authkey = c.opt.Authkey - mdrq.Authkey = c.opt.Authkey + uid, gid, err := utils.ExtractUidGid(auth) + if err == nil { + rq.Role.Uid = uid + rq.Role.Gid = gid + } + } - return mdrq, nil + return rq, nil } // AddACL adds an new acl to EOS with the given aclType. @@ -711,9 +710,14 @@ func getAttribute(key, val string) (*eosclient.Attribute, error) { } // GetFileInfoByPath returns the FilInfo at the given path. -func (c *Client) GetFileInfoByPath(ctx context.Context, auth eosclient.Authorization, path string) (*eosclient.FileInfo, error) { +func (c *Client) GetFileInfoByPath(ctx context.Context, userAuth eosclient.Authorization, path string) (*eosclient.FileInfo, error) { log := appctx.GetLogger(ctx) - log.Debug().Str("func", "GetFileInfoByPath").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("entering") + log.Debug().Str("func", "GetFileInfoByPath").Str("uid,gid", userAuth.Role.UID+","+userAuth.Role.GID).Str("path", path).Msg("entering") + + // UserAuth may not be sufficient, because the user may not have access to the file + // e.g. in the case of a guest account. So we check if a uid/gid is set, and if not, + // revert to the daemon account + auth := utils.GetUserOrDaemonAuth(userAuth) // Initialize the common fields of the MDReq mdrq, err := c.initMDRequest(ctx, auth) @@ -756,7 +760,16 @@ func (c *Client) GetFileInfoByPath(ctx context.Context, auth eosclient.Authoriza } if c.opt.VersionInvariant && !isVersionFolder(path) && !info.IsDir { - inode, err := c.getVersionFolderInode(ctx, auth, path) + // Here we have to create a missing version folder, irrespective from the user (that could be a sharee, or a lw account, or...) + // Therefore, we impersonate the owner of the file + ownerAuth := eosclient.Authorization{ + Role: eosclient.Role{ + UID: strconv.FormatUint(info.UID, 10), + GID: strconv.FormatUint(info.GID, 10), + }, + } + + inode, err := c.getOrCreateVersionFolderInode(ctx, ownerAuth, path) if err != nil { return nil, err } @@ -817,13 +830,9 @@ func (c *Client) GetQuota(ctx context.Context, username string, rootAuth eosclie return nil, errtypes.InternalError(fmt.Sprintf("Quota error from eos. info: '%#v'", resp.Quota)) } - qi := new(eosclient.QuotaInfo) - if resp == nil { - return nil, errtypes.InternalError("Out of memory") - } - // Let's loop on all the quotas that match this uid (apparently there can be many) // If there are many for this node, we sum them up + qi := new(eosclient.QuotaInfo) for i := 0; i < len(resp.Quota.Quotanode); i++ { log.Debug().Str("func", "GetQuota").Str("quotanode:", fmt.Sprintf("%d: %#v", i, resp.Quota.Quotanode[i])).Msg("") @@ -954,13 +963,11 @@ func (c *Client) Chown(ctx context.Context, auth, chownAuth eosclient.Authorizat msg := new(erpc.NSRequest_ChownRequest) msg.Owner = new(erpc.RoleId) - msg.Owner.Uid, err = strconv.ParseUint(chownAuth.Role.UID, 10, 64) - if err != nil { - return err - } - msg.Owner.Gid, err = strconv.ParseUint(chownAuth.Role.GID, 10, 64) - if err != nil { - return err + + uid, gid, err := utils.ExtractUidGid(chownAuth) + if err == nil { + msg.Owner.Uid = uid + msg.Owner.Gid = gid } msg.Id = new(erpc.MDId) @@ -1195,7 +1202,6 @@ func (c *Client) Rename(ctx context.Context, auth eosclient.Authorization, oldPa // List the contents of the directory given by path. func (c *Client) List(ctx context.Context, auth eosclient.Authorization, dpath string) ([]*eosclient.FileInfo, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "List").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("dpath", dpath).Msg("") // Stuff filename, uid, gid into the FindRequest type fdrq := new(erpc.FindRequest) @@ -1206,16 +1212,12 @@ func (c *Client) List(ctx context.Context, auth eosclient.Authorization, dpath s fdrq.Role = new(erpc.RoleId) - uidInt, err := strconv.ParseUint(auth.Role.UID, 10, 64) - if err != nil { - return nil, err - } - gidInt, err := strconv.ParseUint(auth.Role.GID, 10, 64) + uid, gid, err := utils.ExtractUidGid(auth) if err != nil { - return nil, err + return nil, errors.Wrap(err, "Failed to extract uid/gid from auth") } - fdrq.Role.Uid = uidInt - fdrq.Role.Gid = gidInt + fdrq.Role.Uid = uid + fdrq.Role.Gid = gid fdrq.Authkey = c.opt.Authkey @@ -1347,7 +1349,7 @@ func (c *Client) Read(ctx context.Context, auth eosclient.Authorization, path st var localfile io.WriteCloser localfile = nil - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return nil, errors.Wrap(err, "eos: no user in ctx") } @@ -1383,7 +1385,7 @@ func (c *Client) Write(ctx context.Context, auth eosclient.Authorization, path s var length int64 length = -1 - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eos: no user in ctx") } @@ -1661,17 +1663,17 @@ func (c *Client) GenerateToken(ctx context.Context, auth eosclient.Authorization return "", err } -func (c *Client) getVersionFolderInode(ctx context.Context, auth eosclient.Authorization, p string) (uint64, error) { +func (c *Client) getOrCreateVersionFolderInode(ctx context.Context, ownerAuth eosclient.Authorization, p string) (uint64, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "getVersionFolderInode").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("p", p).Msg("") + log.Info().Str("func", "getOrCreateVersionFolderInode").Str("uid,gid", ownerAuth.Role.UID+","+ownerAuth.Role.GID).Str("p", p).Msg("") versionFolder := getVersionFolder(p) - md, err := c.GetFileInfoByPath(ctx, auth, versionFolder) + md, err := c.GetFileInfoByPath(ctx, ownerAuth, versionFolder) if err != nil { - if err = c.CreateDir(ctx, auth, versionFolder); err != nil { + if err = c.CreateDir(ctx, ownerAuth, versionFolder); err != nil { return 0, err } - md, err = c.GetFileInfoByPath(ctx, auth, versionFolder) + md, err = c.GetFileInfoByPath(ctx, ownerAuth, versionFolder) if err != nil { return 0, err } diff --git a/pkg/eosclient/eosgrpc/eoshttp.go b/pkg/eosclient/eosgrpc/eoshttp.go index bab34cf5b5..c69c069241 100644 --- a/pkg/eosclient/eosgrpc/eoshttp.go +++ b/pkg/eosclient/eosgrpc/eoshttp.go @@ -429,14 +429,6 @@ func (c *EOSHTTPClient) PUTFile(ctx context.Context, remoteuser string, auth eos log.Debug().Str("func", "PUTFile").Int64("Content-Length", length).Msg("setting header") req.Header.Set("Content-Length", strconv.FormatInt(length, 10)) } - if err != nil { - log.Error().Str("func", "PUTFile").Str("url", loc.String()).Str("err", err.Error()).Msg("can't create redirected request") - return err - } - if length >= 0 { - log.Debug().Str("func", "PUTFile").Int64("Content-Length", length).Msg("setting header") - req.Header.Set("Content-Length", strconv.FormatInt(length, 10)) - } log.Debug().Str("func", "PUTFile").Str("location", loc.String()).Msg("redirection") nredirs++ diff --git a/pkg/storage/utils/eosfs/config.go b/pkg/storage/utils/eosfs/config.go index 6017edf671..86b54c7496 100644 --- a/pkg/storage/utils/eosfs/config.go +++ b/pkg/storage/utils/eosfs/config.go @@ -26,25 +26,6 @@ type Config struct { // QuotaNode for storing quota information QuotaNode string `mapstructure:"quota_node"` - // DefaultQuotaBytes sets the default maximum bytes available for a user - DefaultQuotaBytes uint64 `mapstructure:"default_quota_bytes"` - - // DefaultSecondaryQuotaBytes sets the default maximum bytes available for a secondary user - DefaultSecondaryQuotaBytes uint64 `mapstructure:"default_secondary_quota_bytes"` - - // DefaultQuotaFiles sets the default maximum files available for a user - DefaultQuotaFiles uint64 `mapstructure:"default_quota_files"` - - // ShadowNamespace for storing shadow data - ShadowNamespace string `mapstructure:"shadow_namespace"` - - // UploadsNamespace for storing upload data - UploadsNamespace string `mapstructure:"uploads_namespace"` - - // ShareFolder defines the name of the folder in the - // shadowed namespace. Ex: /eos/user/.shadow/h/hugo/MyShares - ShareFolder string `mapstructure:"share_folder"` - // Location of the eos binary. // Default is /usr/bin/eos. EosBinary string `mapstructure:"eos_binary"` @@ -149,9 +130,6 @@ type Config struct { // revisions-related operations. ImpersonateOwnerforRevisions bool `mapstructure:"impersonate_owner_for_revisions"` - // Whether to enable the post create home hook - EnablePostCreateHomeHook bool `mapstructure:"enable_post_create_home_hook"` - // HTTP connections to EOS: max number of idle conns MaxIdleConns int `mapstructure:"max_idle_conns"` @@ -177,8 +155,9 @@ type Config struct { // Default is 3600 TokenExpiry int - // Path of the script to run after an user home folder has been created - OnPostCreateHomeHook string `mapstructure:"on_post_create_home_hook"` + // Path of the script to run in order to create a user home folder + // TODO(lopresti): to be replaced by a call to the Resource Lifecycle API being developed + CreateHomeHook string `mapstructure:"create_home_hook"` // Maximum entries count a ListRecycle call may return: if exceeded, ListRecycle // will return a BadRequest error diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index d231224756..8267b671b7 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -86,31 +86,11 @@ func (c *Config) ApplyDefaults() { c.Namespace = "/" } - if c.ShadowNamespace == "" { - c.ShadowNamespace = path.Join(c.Namespace, ".shadow") - } - // Quota node defaults to namespace if empty if c.QuotaNode == "" { c.QuotaNode = c.Namespace } - if c.DefaultQuotaBytes == 0 { - c.DefaultQuotaBytes = 2000000000000 // 1 TB logical - } - if c.DefaultSecondaryQuotaBytes == 0 { - c.DefaultSecondaryQuotaBytes = c.DefaultQuotaBytes - } - if c.DefaultQuotaFiles == 0 { - c.DefaultQuotaFiles = 1000000 // 1 Million - } - - if c.ShareFolder == "" { - c.ShareFolder = "/MyShares" - } - // ensure share folder always starts with slash - c.ShareFolder = path.Join("/", c.ShareFolder) - if c.EosBinary == "" { c.EosBinary = "/usr/bin/eos" } @@ -257,12 +237,11 @@ func (fs *eosfs) userIDcacheWarmup() { time.Sleep(2 * time.Second) ctx := context.Background() paths := []string{fs.wrap(ctx, "/")} - auth, _ := fs.getRootAuth(ctx) for i := 0; i < fs.conf.UserIDCacheWarmupDepth; i++ { var newPaths []string for _, fn := range paths { - if eosFileInfos, err := fs.c.List(ctx, auth, fn); err == nil { + if eosFileInfos, err := fs.c.List(ctx, utils.GetEmptyAuth(), fn); err == nil { for _, f := range eosFileInfos { _, _ = fs.getUserIDGateway(ctx, strconv.FormatUint(f.UID, 10)) newPaths = append(newPaths, f.File) @@ -279,61 +258,29 @@ func (fs *eosfs) Shutdown(ctx context.Context) error { return nil } -func getUser(ctx context.Context) (*userpb.User, error) { - u, ok := appctx.ContextGetUser(ctx) - if !ok { - err := errors.Wrap(errtypes.UserRequired(""), "eosfs: error getting user from ctx") - return nil, err - } - return u, nil -} - func (fs *eosfs) getLayout(ctx context.Context) (layout string) { if fs.conf.EnableHome { - u, err := getUser(ctx) - if err != nil { - panic(err) - } + u := appctx.ContextMustGetUser(ctx) layout = templates.WithUser(u, fs.conf.UserLayout) } return } -func (fs *eosfs) getInternalHome(ctx context.Context) (string, error) { +func (fs *eosfs) getInternalHome(ctx context.Context) string { if !fs.conf.EnableHome { - return "", errtypes.NotSupported("eos: get home not supported") - } - - u, err := getUser(ctx) - if err != nil { - err = errors.Wrap(err, "eosfs: wrap: no user in ctx and home is enabled") - return "", err + // TODO(lopresti): this is to be removed as we always want to support home, + // cf. https://github.com/cs3org/reva/pull/4940 + return "/" } + u := appctx.ContextMustGetUser(ctx) relativeHome := templates.WithUser(u, fs.conf.UserLayout) - return relativeHome, nil -} - -func (fs *eosfs) wrapShadow(ctx context.Context, fn string) (internal string) { - if fs.conf.EnableHome { - layout, err := fs.getInternalHome(ctx) - if err != nil { - panic(err) - } - internal = path.Join(fs.conf.ShadowNamespace, layout, fn) - } else { - internal = path.Join(fs.conf.ShadowNamespace, fn) - } - return + return relativeHome } func (fs *eosfs) wrap(ctx context.Context, fn string) (internal string) { if fs.conf.EnableHome { - layout, err := fs.getInternalHome(ctx) - if err != nil { - panic(err) - } - internal = path.Join(fs.conf.Namespace, layout, fn) + internal = path.Join(fs.conf.Namespace, fs.getInternalHome(ctx), fn) } else { internal = path.Join(fs.conf.Namespace, fn) } @@ -345,7 +292,7 @@ func (fs *eosfs) wrap(ctx context.Context, fn string) (internal string) { func (fs *eosfs) unwrap(ctx context.Context, internal string) (string, error) { log := appctx.GetLogger(ctx) layout := fs.getLayout(ctx) - ns, err := fs.getNsMatch(internal, []string{fs.conf.Namespace, fs.conf.ShadowNamespace}) + ns, err := fs.getNsMatch(internal, []string{fs.conf.Namespace}) if err != nil { return "", err } @@ -389,39 +336,17 @@ func (fs *eosfs) unwrapInternal(ctx context.Context, ns, np, layout string) (str return external, nil } -func (fs *eosfs) resolveRefForbidShareFolder(ctx context.Context, ref *provider.Reference) (string, eosclient.Authorization, error) { - p, err := fs.resolve(ctx, ref) - if err != nil { - return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: error resolving reference") - } - if fs.isShareFolder(ctx, p) { - return "", eosclient.Authorization{}, errtypes.PermissionDenied("eosfs: cannot perform operation under the virtual share folder") - } - fn := fs.wrap(ctx, p) - - u, err := getUser(ctx) - if err != nil { - return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) - if err != nil { - return "", eosclient.Authorization{}, err - } - - return fn, auth, nil -} - func (fs *eosfs) resolveRefAndGetAuth(ctx context.Context, ref *provider.Reference) (string, eosclient.Authorization, error) { p, err := fs.resolve(ctx, ref) if err != nil { return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: error resolving reference") } - fn := fs.wrap(ctx, p) - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: no user in ctx") } + fn := fs.wrap(ctx, p) auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { return "", eosclient.Authorization{}, err @@ -454,7 +379,7 @@ func (fs *eosfs) getPath(ctx context.Context, id *provider.ResourceId) (string, return "", fmt.Errorf("error converting string to int for eos fileid: %s", id.OpaqueId) } - auth, err := fs.getRootAuth(ctx) + auth, err := fs.getDaemonAuth(ctx) if err != nil { return "", err } @@ -467,46 +392,23 @@ func (fs *eosfs) getPath(ctx context.Context, id *provider.ResourceId) (string, return fs.unwrap(ctx, eosFileInfo.File) } -func (fs *eosfs) isShareFolder(ctx context.Context, p string) bool { - return strings.HasPrefix(p, fs.conf.ShareFolder) -} - -func (fs *eosfs) isShareFolderRoot(ctx context.Context, p string) bool { - return path.Clean(p) == fs.conf.ShareFolder -} - -func (fs *eosfs) isShareFolderChild(ctx context.Context, p string) bool { - p = path.Clean(p) - vals := strings.Split(p, fs.conf.ShareFolder+"/") - return len(vals) > 1 && vals[1] != "" -} - func (fs *eosfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) { fid, err := strconv.ParseUint(id.OpaqueId, 10, 64) if err != nil { return "", errors.Wrap(err, "eosfs: error parsing fileid string") } - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return "", errors.Wrap(err, "eosfs: no user in ctx") } - if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED { - auth, err := fs.getRootAuth(ctx) - if err != nil { - return "", err - } - eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid) - if err != nil { - return "", errors.Wrap(err, "eosfs: error getting file info by inode") - } - if perm := fs.permissionSet(ctx, eosFileInfo, nil); perm.GetPath { - return fs.unwrap(ctx, eosFileInfo.File) - } - return "", errtypes.PermissionDenied("eosfs: getting path for id not allowed") - } - auth, err := fs.getUserAuth(ctx, u, "") + var auth eosclient.Authorization + if utils.IsLightweightUser(u) { + auth, err = fs.getDaemonAuth(ctx) + } else { + auth, err = fs.getUserAuth(ctx, u, "") + } if err != nil { return "", err } @@ -516,6 +418,10 @@ func (fs *eosfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (stri return "", errors.Wrap(err, "eosfs: error getting file info by inode") } + if perm := fs.permissionSet(ctx, eosFileInfo, nil); !perm.GetPath { + return "", errtypes.PermissionDenied("eosfs: getting path for id not allowed") + } + return fs.unwrap(ctx, eosFileInfo.File) } @@ -529,10 +435,7 @@ func (fs *eosfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Referen return err } - rootAuth, err := fs.getRootAuth(ctx) - if err != nil { - return err - } + cboxAuth := utils.GetEmptyAuth() for k, v := range md.Metadata { if k == "" || v == "" { @@ -552,7 +455,7 @@ func (fs *eosfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Referen // TODO(labkode): SetArbitraryMetadata does not have semantics for recursivity. // We set it to false - err := fs.c.SetAttr(ctx, rootAuth, attr, false, false, fn, "") + err := fs.c.SetAttr(ctx, cboxAuth, attr, false, false, fn, "") if err != nil { return errors.Wrap(err, "eosfs: error setting xattr in eos driver") } @@ -570,10 +473,7 @@ func (fs *eosfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Refer return err } - rootAuth, err := fs.getRootAuth(ctx) - if err != nil { - return err - } + cboxAuth := utils.GetEmptyAuth() for _, k := range keys { if k == "" { @@ -585,7 +485,7 @@ func (fs *eosfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Refer Key: k, } - err := fs.c.UnsetAttr(ctx, rootAuth, attr, false, fn, "") + err := fs.c.UnsetAttr(ctx, cboxAuth, attr, false, fn, "") if err != nil { if errors.Is(err, eosclient.AttrNotExistsError) { continue @@ -606,16 +506,14 @@ func (fs *eosfs) EncodeAppName(a string) string { func (fs *eosfs) getLockPayloads(ctx context.Context, path string) (string, string, error) { // sys attributes want root auth, buddy - rootauth, err := fs.getRootAuth(ctx) - if err != nil { - return "", "", err - } - data, err := fs.c.GetAttr(ctx, rootauth, "sys."+lockPayloadKey, path) + cboxAuth := utils.GetEmptyAuth() + + data, err := fs.c.GetAttr(ctx, cboxAuth, "sys."+lockPayloadKey, path) if err != nil { return "", "", err } - eoslock, err := fs.c.GetAttr(ctx, rootauth, "sys."+eosLockKey, path) + eoslock, err := fs.c.GetAttr(ctx, cboxAuth, "sys."+eosLockKey, path) if err != nil { return "", "", err } @@ -624,12 +522,9 @@ func (fs *eosfs) getLockPayloads(ctx context.Context, path string) (string, stri } func (fs *eosfs) removeLockAttrs(ctx context.Context, path, app string) error { - rootAuth, err := fs.getRootAuth(ctx) - if err != nil { - return err - } + cboxAuth := utils.GetEmptyAuth() - err = fs.c.UnsetAttr(ctx, rootAuth, &eosclient.Attribute{ + err := fs.c.UnsetAttr(ctx, cboxAuth, &eosclient.Attribute{ Type: SystemAttr, Key: eosLockKey, }, false, path, app) @@ -637,7 +532,7 @@ func (fs *eosfs) removeLockAttrs(ctx context.Context, path, app string) error { return errors.Wrap(err, "eosfs: error unsetting the eos lock") } - err = fs.c.UnsetAttr(ctx, rootAuth, &eosclient.Attribute{ + err = fs.c.UnsetAttr(ctx, cboxAuth, &eosclient.Attribute{ Type: SystemAttr, Key: lockPayloadKey, }, false, path, app) @@ -688,9 +583,7 @@ func (fs *eosfs) GetLock(ctx context.Context, ref *provider.Reference) (*provide if err != nil { return nil, errors.Wrap(err, "eosfs: error resolving reference") } - path = fs.wrap(ctx, path) - - user, err := getUser(ctx) + user, err := utils.GetUser(ctx) if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") } @@ -705,14 +598,12 @@ func (fs *eosfs) GetLock(ctx context.Context, ref *provider.Reference) (*provide return nil, errtypes.BadRequest("user has no read access on resource") } + path = fs.wrap(ctx, path) return fs.getLock(ctx, user, path, ref) } func (fs *eosfs) setLock(ctx context.Context, lock *provider.Lock, path string) error { - auth, err := fs.getRootAuth(ctx) - if err != nil { - return err - } + cboxAuth := utils.GetEmptyAuth() encodedLock, eosLock, err := fs.encodeLock(lock) if err != nil { @@ -720,7 +611,7 @@ func (fs *eosfs) setLock(ctx context.Context, lock *provider.Lock, path string) } // set eos lock - err = fs.c.SetAttr(ctx, auth, &eosclient.Attribute{ + err = fs.c.SetAttr(ctx, cboxAuth, &eosclient.Attribute{ Type: SystemAttr, Key: eosLockKey, Val: eosLock, @@ -733,7 +624,7 @@ func (fs *eosfs) setLock(ctx context.Context, lock *provider.Lock, path string) } // set payload - err = fs.c.SetAttr(ctx, auth, &eosclient.Attribute{ + err = fs.c.SetAttr(ctx, cboxAuth, &eosclient.Attribute{ Type: SystemAttr, Key: lockPayloadKey, Val: encodedLock, @@ -754,9 +645,8 @@ func (fs *eosfs) SetLock(ctx context.Context, ref *provider.Reference, l *provid if err != nil { return errors.Wrap(err, "eosfs: error resolving reference") } - path = fs.wrap(ctx, path) - user, err := getUser(ctx) + user, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } @@ -785,6 +675,7 @@ func (fs *eosfs) SetLock(ctx context.Context, ref *provider.Reference, l *provid } } + path = fs.wrap(ctx, path) return fs.setLock(ctx, l, path) } @@ -894,7 +785,7 @@ func (fs *eosfs) RefreshLock(ctx context.Context, ref *provider.Reference, newLo } } - user, err := getUser(ctx) + user, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: error getting user") } @@ -960,7 +851,7 @@ func (fs *eosfs) Unlock(ctx context.Context, ref *provider.Reference, lock *prov return errtypes.BadRequest("caller does not hold the lock") } - user, err := getUser(ctx) + user, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: error getting user") } @@ -990,10 +881,7 @@ func (fs *eosfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provi return err } - rootAuth, err := fs.getRootAuth(ctx) - if err != nil { - return err - } + cboxAuth := utils.GetEmptyAuth() eosACL, err := fs.getEosACL(ctx, g) if err != nil { @@ -1010,13 +898,13 @@ func (fs *eosfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provi Key: fmt.Sprintf("%s.%s", lwShareAttrKey, eosACL.Qualifier), Val: eosACL.Permissions, } - if err := fs.c.SetAttr(ctx, rootAuth, attr, false, true, fn, ""); err != nil { + if err := fs.c.SetAttr(ctx, cboxAuth, attr, false, true, fn, ""); err != nil { return errors.Wrap(err, "eosfs: error adding acl for lightweight account") } return nil } - err = fs.c.AddACL(ctx, auth, rootAuth, fn, eosclient.StartPosition, eosACL) + err = fs.c.AddACL(ctx, auth, cboxAuth, fn, eosclient.StartPosition, eosACL) if err != nil { return errors.Wrap(err, "eosfs: error adding acl") } @@ -1031,10 +919,7 @@ func (fs *eosfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *prov position := eosclient.EndPosition - rootAuth, err := fs.getRootAuth(ctx) - if err != nil { - return err - } + cboxAuth := utils.GetEmptyAuth() // empty permissions => deny grant := &provider.Grant{ @@ -1047,7 +932,7 @@ func (fs *eosfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *prov return err } - err = fs.c.AddACL(ctx, auth, rootAuth, fn, position, eosACL) + err = fs.c.AddACL(ctx, auth, cboxAuth, fn, position, eosACL) if err != nil { return errors.Wrap(err, "eosfs: error adding acl") } @@ -1098,10 +983,7 @@ func (fs *eosfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *pr return err } - rootAuth, err := fs.getRootAuth(ctx) - if err != nil { - return err - } + cboxAuth := utils.GetEmptyAuth() eosACL, err := fs.getEosACL(ctx, g) if err != nil { @@ -1113,13 +995,13 @@ func (fs *eosfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *pr Type: SystemAttr, Key: fmt.Sprintf("%s.%s", lwShareAttrKey, eosACL.Qualifier), } - if err := fs.c.UnsetAttr(ctx, rootAuth, attr, true, fn, ""); err != nil { + if err := fs.c.UnsetAttr(ctx, cboxAuth, attr, true, fn, ""); err != nil { return errors.Wrap(err, "eosfs: error removing acl for lightweight account") } return nil } - err = fs.c.RemoveACL(ctx, auth, rootAuth, fn, eosACL) + err = fs.c.RemoveACL(ctx, auth, cboxAuth, fn, eosACL) if err != nil { return errors.Wrap(err, "eosfs: error removing acl") } @@ -1198,11 +1080,9 @@ func (fs *eosfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*pr } // Now we get the real info, I know, it's ugly - auth, err = fs.getRootAuth(ctx) - if err != nil { - return nil, err - } - attrs, err := fs.c.GetAttrs(ctx, auth, fn) + cboxAuth := utils.GetEmptyAuth() + + attrs, err := fs.c.GetAttrs(ctx, cboxAuth, fn) if err != nil { return nil, err } @@ -1233,15 +1113,13 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st log := appctx.GetLogger(ctx) log.Info().Msg("eosfs: get md for ref:" + ref.String()) - _, err := getUser(ctx) - if err != nil { - return nil, err - } + p := ref.Path + fn := fs.wrap(ctx, p) - auth, err := fs.getRootAuth(ctx) - if err != nil { - return nil, err - } + // We use daemon for auth because we need access to the file in order to stat it + // We cannot use the current user, because the file may be a shared file + // and lightweight accounts don't have a uid + auth, err := fs.getDaemonAuth(ctx) if ref.ResourceId != nil { fid, err := strconv.ParseUint(ref.ResourceId.OpaqueId, 10, 64) @@ -1263,16 +1141,6 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st return fs.convertToResourceInfo(ctx, eosFileInfo) } - p := ref.Path - - // if path is home we need to add in the response any shadow folder in the shadow homedirectory. - if fs.conf.EnableHome { - if fs.isShareFolder(ctx, p) { - return fs.getMDShareFolder(ctx, p, mdKeys) - } - } - - fn := fs.wrap(ctx, p) eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, auth, fn) if err != nil { return nil, err @@ -1281,48 +1149,12 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st return fs.convertToResourceInfo(ctx, eosFileInfo) } -func (fs *eosfs) getMDShareFolder(ctx context.Context, p string, mdKeys []string) (*provider.ResourceInfo, error) { - fn := fs.wrapShadow(ctx, p) - - auth, err := fs.getRootAuth(ctx) - if err != nil { - return nil, err - } - - eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, auth, fn) - if err != nil { - return nil, err - } - - if fs.isShareFolderRoot(ctx, p) { - return fs.convertToResourceInfo(ctx, eosFileInfo) - } - return fs.convertToFileReference(ctx, eosFileInfo) -} - func (fs *eosfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys []string) ([]*provider.ResourceInfo, error) { p, err := fs.resolve(ctx, ref) if err != nil { return nil, errors.Wrap(err, "eosfs: error resolving reference") } - if fs.conf.EnableHome { - return fs.listWithHome(ctx, p) - } - - return fs.listWithNominalHome(ctx, p) -} - -func (fs *eosfs) listWithHome(ctx context.Context, p string) ([]*provider.ResourceInfo, error) { - if fs.isShareFolderRoot(ctx, p) { - return fs.listShareFolderRoot(ctx, p) - } - - if fs.isShareFolderChild(ctx, p) { - return nil, errtypes.PermissionDenied("eos: error listing folders inside the shared folder, only file references are stored inside") - } - - // path points to a resource in the nominal home return fs.listWithNominalHome(ctx, p) } @@ -1330,14 +1162,15 @@ func (fs *eosfs) listWithNominalHome(ctx context.Context, p string) (finfos []*p log := appctx.GetLogger(ctx) fn := fs.wrap(ctx, p) - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserAuth(ctx, u, fn) + userAuth, err := fs.getUserAuth(ctx, u, fn) if err != nil { return nil, err } + auth := utils.GetUserOrDaemonAuth(userAuth) eosFileInfos, err := fs.c.List(ctx, auth, fn) if err != nil { @@ -1364,48 +1197,13 @@ func (fs *eosfs) listWithNominalHome(ctx context.Context, p string) (finfos []*p return finfos, nil } -func (fs *eosfs) listShareFolderRoot(ctx context.Context, p string) (finfos []*provider.ResourceInfo, err error) { - fn := fs.wrapShadow(ctx, p) - - u, err := getUser(ctx) - if err != nil { - return nil, errors.Wrap(err, "eosfs: no user in ctx") - } - // lightweight accounts don't have share folders, so we're passing an empty string as path - auth, err := fs.getUserAuth(ctx, u, "") - if err != nil { - return nil, err - } - - eosFileInfos, err := fs.c.List(ctx, auth, fn) - if err != nil { - return nil, errors.Wrap(err, "eosfs: error listing") - } - - for _, eosFileInfo := range eosFileInfos { - // filter out sys files - if !fs.conf.ShowHiddenSysFiles { - base := path.Base(eosFileInfo.File) - if hiddenReg.MatchString(base) { - continue - } - } - - if finfo, err := fs.convertToFileReference(ctx, eosFileInfo); err == nil { - finfos = append(finfos, finfo) - } - } - - return finfos, nil -} - // CreateStorageSpace creates a storage space. func (fs *eosfs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) { return nil, fmt.Errorf("unimplemented: CreateStorageSpace") } func (fs *eosfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, error) { - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return 0, 0, errors.Wrap(err, "eosfs: no user in ctx") } @@ -1415,12 +1213,9 @@ func (fs *eosfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, return 0, 0, err } - rootAuth, err := fs.getRootAuth(ctx) - if err != nil { - return 0, 0, err - } + cboxAuth := utils.GetEmptyAuth() - qi, err := fs.c.GetQuota(ctx, auth.Role.UID, rootAuth, fs.conf.QuotaNode) + qi, err := fs.c.GetQuota(ctx, auth.Role.UID, cboxAuth, fs.conf.QuotaNode) if err != nil { err := errors.Wrap(err, "eosfs: error getting quota") return 0, 0, err @@ -1438,56 +1233,20 @@ func (fs *eosfs) GetHome(ctx context.Context) (string, error) { return "/", nil } -func (fs *eosfs) createShadowHome(ctx context.Context) error { - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - rootAuth, err := fs.getRootAuth(ctx) - if err != nil { - return nil - } - home := fs.wrapShadow(ctx, "/") - shadowFolders := []string{fs.conf.ShareFolder} - - for _, sf := range shadowFolders { - fn := path.Join(home, sf) - _, err = fs.c.GetFileInfoByPath(ctx, rootAuth, fn) - if err != nil { - if _, ok := err.(errtypes.IsNotFound); !ok { - return errors.Wrap(err, "eosfs: error verifying if shadow directory exists") - } - err = fs.createUserDir(ctx, u, fn, false) - if err != nil { - return err - } - } - } - - log := appctx.GetLogger(ctx) - log.Info().Str("home", home).Interface("user", u.Id).Msg("created shadow home") - - return nil -} - func (fs *eosfs) createNominalHome(ctx context.Context) error { home := fs.wrap(ctx, "/") - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } + auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return err } - rootAuth, err := fs.getRootAuth(ctx) - if err != nil { - return nil - } - - _, err = fs.c.GetFileInfoByPath(ctx, rootAuth, home) + _, err = fs.c.GetFileInfoByPath(ctx, auth, home) if err == nil { // home already exists return nil } @@ -1496,41 +1255,21 @@ func (fs *eosfs) createNominalHome(ctx context.Context) error { return errors.Wrap(err, "eosfs: error verifying if user home directory exists") } - err = fs.createUserDir(ctx, u, home, false) - if err != nil { - err := errors.Wrap(err, "eosfs: error creating user dir") - return err - } - - // set quota for user, depending on its type - quotaBytes := fs.conf.DefaultQuotaBytes - if u.Id.Type != userpb.UserType_USER_TYPE_PRIMARY { - quotaBytes = fs.conf.DefaultSecondaryQuotaBytes - } - quotaInfo := &eosclient.SetQuotaInfo{ - Username: u.Username, - UID: auth.Role.UID, - GID: auth.Role.GID, - MaxBytes: quotaBytes, - MaxFiles: fs.conf.DefaultQuotaFiles, - QuotaNode: fs.conf.QuotaNode, - } - - err = fs.c.SetQuota(ctx, rootAuth, quotaInfo) - if err != nil { - err := errors.Wrap(err, "eosfs: error setting quota") - return err - } + log := appctx.GetLogger(ctx) + log.Info().Interface("user", u.Id).Msg("creating user home") - if fs.conf.EnablePostCreateHomeHook { - if err := fs.runPostCreateHomeHook(ctx); err != nil { - return errors.Wrap(err, "eosfs: error running post create home hook") + if fs.conf.CreateHomeHook != "" { + hook := exec.Command(fs.conf.CreateHomeHook, u.Username, utils.UserTypeToString(u.Id.Type)) + err = hook.Run() + log.Info().Interface("output", hook.Stdout).Err(err).Msg("create_home_hook output") + if err != nil { + return errors.Wrap(err, "eosfs: error running create home hook") } + } else { + log.Fatal().Msg("create_home_hook not configured") + return errtypes.NotFound("eosfs: create home hook not configured") } - log := appctx.GetLogger(ctx) - log.Info().Interface("quotaInfo", quotaInfo).Interface("user", u.Id).Msg("created nominal home") - return nil } @@ -1543,18 +1282,9 @@ func (fs *eosfs) CreateHome(ctx context.Context) error { return errors.Wrap(err, "eosfs: error creating nominal home") } - if err := fs.createShadowHome(ctx); err != nil { - return errors.Wrap(err, "eosfs: error creating shadow home") - } - return nil } -func (fs *eosfs) runPostCreateHomeHook(ctx context.Context) error { - user := appctx.ContextMustGetUser(ctx) - return exec.Command(fs.conf.OnPostCreateHomeHook, user.Username).Run() -} - func (fs *eosfs) createUserDir(ctx context.Context, u *userpb.User, path string, recursiveAttr bool) error { rootAuth, err := fs.getRootAuth(ctx) if err != nil { @@ -1617,22 +1347,19 @@ func (fs *eosfs) createUserDir(ctx context.Context, u *userpb.User, path string, func (fs *eosfs) CreateDir(ctx context.Context, ref *provider.Reference) error { log := appctx.GetLogger(ctx) + p, err := fs.resolve(ctx, ref) if err != nil { return errors.Wrap(err, "eosfs: error resolving reference") } - if fs.isShareFolder(ctx, p) { - return errtypes.PermissionDenied("eosfs: cannot perform operation under the virtual share folder") - } - fn := fs.wrap(ctx, p) - - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } // We need the auth corresponding to the parent directory // as the file might not exist at the moment + fn := fs.wrap(ctx, p) auth, err := fs.getUserAuth(ctx, u, path.Dir(fn)) if err != nil { return err @@ -1656,28 +1383,19 @@ func (fs *eosfs) TouchFile(ctx context.Context, ref *provider.Reference) error { } func (fs *eosfs) CreateReference(ctx context.Context, p string, targetURI *url.URL) error { - // TODO(labkode): for the time being we only allow creating references - // in the virtual share folder to not pollute the nominal user tree. - if !fs.isShareFolder(ctx, p) { - return errtypes.PermissionDenied("eosfs: cannot create references outside the share folder: share_folder=" + fs.conf.ShareFolder + " path=" + p) - } - u, err := getUser(ctx) + _, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } - fn := fs.wrapShadow(ctx, p) - // TODO(labkode): with the grpc plugin we can create a file touching with xattrs. - // Current mechanism is: touch to hidden dir, set xattr, rename. + // Current mechanism is: touch to hidden location, set xattr, rename. + fn := fs.wrap(ctx, p) dir, base := path.Split(fn) tmp := path.Join(dir, fmt.Sprintf(".sys.reva#.%s", base)) - rootAuth, err := fs.getRootAuth(ctx) - if err != nil { - return nil - } + cboxAuth := utils.GetEmptyAuth() - if err := fs.createUserDir(ctx, u, tmp, false); err != nil { + if err := fs.c.CreateDir(ctx, cboxAuth, tmp); err != nil { err = errors.Wrapf(err, "eosfs: error creating temporary ref file") return err } @@ -1689,13 +1407,13 @@ func (fs *eosfs) CreateReference(ctx context.Context, p string, targetURI *url.U Val: targetURI.String(), } - if err := fs.c.SetAttr(ctx, rootAuth, attr, false, false, tmp, ""); err != nil { + if err := fs.c.SetAttr(ctx, cboxAuth, attr, false, false, tmp, ""); err != nil { err = errors.Wrapf(err, "eosfs: error setting reva.ref attr on file: %q", tmp) return err } // rename to have the file visible in user space. - if err := fs.c.Rename(ctx, rootAuth, tmp, fn); err != nil { + if err := fs.c.Rename(ctx, cboxAuth, tmp, fn); err != nil { err = errors.Wrapf(err, "eosfs: error renaming from: %q to %q", tmp, fn) return err } @@ -1708,17 +1426,12 @@ func (fs *eosfs) Delete(ctx context.Context, ref *provider.Reference) error { if err != nil { return errors.Wrap(err, "eosfs: error resolving reference") } - - if fs.isShareFolder(ctx, p) { - return fs.deleteShadow(ctx, p) - } - - fn := fs.wrap(ctx, p) - - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } + + fn := fs.wrap(ctx, p) auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { return err @@ -1727,50 +1440,23 @@ func (fs *eosfs) Delete(ctx context.Context, ref *provider.Reference) error { return fs.c.Remove(ctx, auth, fn, false) } -func (fs *eosfs) deleteShadow(ctx context.Context, p string) error { - if fs.isShareFolderRoot(ctx, p) { - return errtypes.PermissionDenied("eosfs: cannot delete the virtual share folder") - } - - if fs.isShareFolderChild(ctx, p) { - fn := fs.wrapShadow(ctx, p) - - // in order to remove the folder or the file without - // moving it to the recycle bin, we should take - // the privileges of the root - auth, err := fs.getRootAuth(ctx) - if err != nil { - return err - } - - return fs.c.Remove(ctx, auth, fn, true) +func (fs *eosfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) error { + u, err := utils.GetUser(ctx) + if err != nil { + return errors.Wrap(err, "eosfs: no user in ctx") } - return errors.New("eosfs: shadow delete of share folder that is neither root nor child. path=" + p) -} - -func (fs *eosfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) error { oldPath, err := fs.resolve(ctx, oldRef) if err != nil { return errors.Wrap(err, "eosfs: error resolving reference") } - newPath, err := fs.resolve(ctx, newRef) if err != nil { return errors.Wrap(err, "eosfs: error resolving reference") } - if fs.isShareFolder(ctx, oldPath) || fs.isShareFolder(ctx, newPath) { - return fs.moveShadow(ctx, oldPath, newPath) - } - oldFn := fs.wrap(ctx, oldPath) newFn := fs.wrap(ctx, newPath) - - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } auth, err := fs.getUserAuth(ctx, u, oldFn) if err != nil { return err @@ -1779,36 +1465,8 @@ func (fs *eosfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) e return fs.c.Rename(ctx, auth, oldFn, newFn) } -func (fs *eosfs) moveShadow(ctx context.Context, oldPath, newPath string) error { - if fs.isShareFolderRoot(ctx, oldPath) || fs.isShareFolderRoot(ctx, newPath) { - return errtypes.PermissionDenied("eosfs: cannot move/rename the virtual share folder") - } - - // only rename of the reference is allowed, hence having the same basedir - bold, _ := path.Split(oldPath) - bnew, _ := path.Split(newPath) - - if bold != bnew { - return errtypes.PermissionDenied("eosfs: cannot move references under the virtual share folder") - } - - oldfn := fs.wrapShadow(ctx, oldPath) - newfn := fs.wrapShadow(ctx, newPath) - - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, "") - if err != nil { - return err - } - - return fs.c.Rename(ctx, auth, oldfn, newfn) -} - func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) { - fn, auth, err := fs.resolveRefForbidShareFolder(ctx, ref) + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) if err != nil { return nil, err } @@ -1840,10 +1498,12 @@ func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([] return nil, errtypes.PermissionDenied("eosfs: user doesn't have permissions to list revisions") } } else { - fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref) + var userAuth eosclient.Authorization + fn, userAuth, err = fs.resolveRefAndGetAuth(ctx, ref) if err != nil { return nil, err } + auth = utils.GetUserOrDaemonAuth(userAuth) } eosRevisions, err := fs.c.ListVersions(ctx, auth, fn) @@ -1883,7 +1543,7 @@ func (fs *eosfs) DownloadRevision(ctx context.Context, ref *provider.Reference, return nil, errtypes.PermissionDenied("eosfs: user doesn't have permissions to download revisions") } } else { - fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref) + fn, auth, err = fs.resolveRefAndGetAuth(ctx, ref) if err != nil { return nil, err } @@ -1916,7 +1576,7 @@ func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, r return errtypes.PermissionDenied("eosfs: user doesn't have permissions to restore revisions") } } else { - fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref) + fn, auth, err = fs.resolveRefAndGetAuth(ctx, ref) if err != nil { return err } @@ -1930,7 +1590,7 @@ func (fs *eosfs) PurgeRecycleItem(ctx context.Context, basePath, key, relativePa } func (fs *eosfs) EmptyRecycle(ctx context.Context) error { - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } @@ -1963,7 +1623,7 @@ func (fs *eosfs) ListRecycle(ctx context.Context, basePath, key, relativePath st } } else { // We just act on the logged-in user's recycle bin - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") } @@ -2033,7 +1693,7 @@ func (fs *eosfs) RestoreRecycleItem(ctx context.Context, basePath, key, relative } } else { // We just act on the logged-in user's recycle bin - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } @@ -2174,7 +1834,7 @@ func (fs *eosfs) permissionSet(ctx context.Context, eosFileInfo *eosclient.FileI // from the parent folder, as these, when creating a new // file are not inherited - if utils.UserIsLightweight(u) && !eosFileInfo.IsDir { + if utils.IsLightweightUser(u) && !eosFileInfo.IsDir { if parentPath, err := fs.unwrap(ctx, filepath.Dir(eosFileInfo.File)); err == nil { if parent, err := fs.GetMD(ctx, &provider.Reference{Path: parentPath}, nil); err == nil { mergePermissions(&perm, parent.PermissionSet) @@ -2417,8 +2077,7 @@ func (fs *eosfs) getUserAuth(ctx context.Context, u *userpb.User, fn string) (eo return fs.singleUserAuth, err } - if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || - u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED { + if utils.IsLightweightUser(u) { return fs.getEOSToken(ctx, u, fn) } @@ -2430,11 +2089,8 @@ func (fs *eosfs) getEOSToken(ctx context.Context, u *userpb.User, fn string) (eo return eosclient.Authorization{}, errtypes.BadRequest("eosfs: path cannot be empty") } - rootAuth, err := fs.getRootAuth(ctx) - if err != nil { - return eosclient.Authorization{}, err - } - info, err := fs.c.GetFileInfoByPath(ctx, rootAuth, fn) + daemonAuth, err := fs.getDaemonAuth(ctx) + info, err := fs.c.GetFileInfoByPath(ctx, daemonAuth, fn) if err != nil { return eosclient.Authorization{}, err } @@ -2489,6 +2145,22 @@ func (fs *eosfs) getRootAuth(ctx context.Context) (eosclient.Authorization, erro return eosclient.Authorization{Role: eosclient.Role{UID: "0", GID: "0"}}, nil } +// Returns an eosclient.Authorization object with the uid/gid of the daemon user +// This is a system user with read-only access to files. +// We use it e.g. when retrieving metadata from a file when accessing through a guest account, +// so we can look up which user to impersonate (i.e. the owner) +func (fs *eosfs) getDaemonAuth(ctx context.Context) (eosclient.Authorization, error) { + if fs.conf.ForceSingleUserMode { + if fs.singleUserAuth.Role.UID != "" && fs.singleUserAuth.Role.GID != "" { + return fs.singleUserAuth, nil + } + var err error + fs.singleUserAuth, err = fs.getUIDGateway(ctx, &userpb.UserId{OpaqueId: fs.conf.SingleUsername}) + return fs.singleUserAuth, err + } + return utils.GetDaemonAuth(), nil +} + type eosSysMetadata struct { TreeSize uint64 `json:"tree_size"` TreeCount uint64 `json:"tree_count"` @@ -2533,96 +2205,3 @@ func parseAndSetFavoriteAttr(ctx context.Context, attrs map[string]string) { // Delete the favorite attr from the response delete(attrs, FavoritesKey) } - -/* - Merge shadow on requests for /home ? - - No - GetHome(ctx context.Context) (string, error) - No -CreateHome(ctx context.Context) error - No - CreateDir(ctx context.Context, fn string) error - No -Delete(ctx context.Context, ref *provider.Reference) error - No -Move(ctx context.Context, oldRef, newRef *provider.Reference) error - No -GetMD(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) - Yes -ListFolder(ctx context.Context, ref *provider.Reference) ([]*provider.ResourceInfo, error) - No -Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error - No -Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) - No -ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) - No -DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) - No -RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error - No ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error) - No RestoreRecycleItem(ctx context.Context, key string) error - No PurgeRecycleItem(ctx context.Context, key string) error - No EmptyRecycle(ctx context.Context) error - ? GetPathByID(ctx context.Context, id *provider.Reference) (string, error) - No AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error - No RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error - No UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error - No ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) - No GetQuota(ctx context.Context) (int, int, error) - No CreateReference(ctx context.Context, path string, targetURI *url.URL) error - No Shutdown(ctx context.Context) error - No SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error - No UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error -*/ - -/* - Merge shadow on requests for /home/MyShares ? - - No - GetHome(ctx context.Context) (string, error) - No -CreateHome(ctx context.Context) error - No - CreateDir(ctx context.Context, fn string) error - Maybe -Delete(ctx context.Context, ref *provider.Reference) error - No -Move(ctx context.Context, oldRef, newRef *provider.Reference) error - Yes -GetMD(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) - Yes -ListFolder(ctx context.Context, ref *provider.Reference) ([]*provider.ResourceInfo, error) - No -Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error - No -Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) - No -ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) - No -DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) - No -RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error - No ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error) - No RestoreRecycleItem(ctx context.Context, key string) error - No PurgeRecycleItem(ctx context.Context, key string) error - No EmptyRecycle(ctx context.Context) error - ? GetPathByID(ctx context.Context, id *provider.Reference) (string, error) - No AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error - No RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error - No UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error - No ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) - No GetQuota(ctx context.Context) (int, int, error) - No CreateReference(ctx context.Context, path string, targetURI *url.URL) error - No Shutdown(ctx context.Context) error - No SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error - No UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error -*/ - -/* - Merge shadow on requests for /home/MyShares/file-reference ? - - No - GetHome(ctx context.Context) (string, error) - No -CreateHome(ctx context.Context) error - No - CreateDir(ctx context.Context, fn string) error - Maybe -Delete(ctx context.Context, ref *provider.Reference) error - Yes -Move(ctx context.Context, oldRef, newRef *provider.Reference) error - Yes -GetMD(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) - No -ListFolder(ctx context.Context, ref *provider.Reference) ([]*provider.ResourceInfo, error) - No -Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error - No -Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) - No -ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) - No -DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) - No -RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error - No ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error) - No RestoreRecycleItem(ctx context.Context, key string) error - No PurgeRecycleItem(ctx context.Context, key string) error - No EmptyRecycle(ctx context.Context) error - ? GetPathByID(ctx context.Context, id *provider.Reference) (string, error) - No AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error - No RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error - No UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error - No ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) - No GetQuota(ctx context.Context) (int, int, error) - No CreateReference(ctx context.Context, path string, targetURI *url.URL) error - No Shutdown(ctx context.Context) error - Maybe SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error - Maybe UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error -*/ diff --git a/pkg/storage/utils/eosfs/upload.go b/pkg/storage/utils/eosfs/upload.go index fb46c2699d..142ad779f4 100644 --- a/pkg/storage/utils/eosfs/upload.go +++ b/pkg/storage/utils/eosfs/upload.go @@ -27,6 +27,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/storage/utils/chunking" + "github.com/cs3org/reva/pkg/utils" "github.com/pkg/errors" ) @@ -36,10 +37,6 @@ func (fs *eosfs) Upload(ctx context.Context, ref *provider.Reference, r io.ReadC return errors.Wrap(err, "eos: error resolving reference") } - if fs.isShareFolder(ctx, p) { - return errtypes.PermissionDenied("eos: cannot upload under the virtual share folder") - } - ok, err := chunking.IsChunked(p) if err != nil { return errors.Wrap(err, "eos: error checking path") @@ -64,7 +61,7 @@ func (fs *eosfs) Upload(ctx context.Context, ref *provider.Reference, r io.ReadC fn := fs.wrap(ctx, p) - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eos: no user in ctx") } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 20b69847c0..d14551ba7b 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -19,6 +19,7 @@ package utils import ( + "context" "fmt" "net" "net/http" @@ -28,6 +29,7 @@ import ( "path/filepath" "reflect" "regexp" + "strconv" "strings" "time" @@ -37,8 +39,12 @@ import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/eosclient" + "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/registry" "github.com/cs3org/reva/pkg/registry/memory" + "github.com/pkg/errors" "go.step.sm/crypto/randutil" // gocritic is disabled because google.golang.org/protobuf/proto does not provide a method to convert MessageV1 to MessageV2. @@ -423,9 +429,9 @@ func HasPermissions(target, toCheck *provider.ResourcePermissions) bool { return true } -// UserIsLightweight returns true if the user is a lightweight +// IsLightweightUser returns true if the user is a lightweight // or federated account. -func UserIsLightweight(u *userpb.User) bool { +func IsLightweightUser(u *userpb.User) bool { return u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED || u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT } @@ -443,3 +449,53 @@ func Cast(v any, to any) { toVal = toVal.Elem() toVal.Set(reflect.ValueOf(v)) } + +func GetDaemonAuth() eosclient.Authorization { + return eosclient.Authorization{Role: eosclient.Role{UID: "2", GID: "2"}} +} + +// This function is used when we don't want to pass any additional auth info. +// Because we later populate the secret key for gRPC, we will be automatically +// mapped to cbox. +// So, in other words, use this function if you want to use the cbox account. +func GetEmptyAuth() eosclient.Authorization { + return eosclient.Authorization{} +} + +// Returns the userAuth if this is a valid auth object, +// otherwise returns daemonAuth +func GetUserOrDaemonAuth(userAuth eosclient.Authorization) eosclient.Authorization { + if userAuth.Role.UID == "" || userAuth.Role.GID == "" { + return GetDaemonAuth() + } else { + return userAuth + } +} + +// Extract uid and gid from auth object +func ExtractUidGid(auth eosclient.Authorization) (uid, gid uint64, err error) { + // $ id nobody + // uid=65534(nobody) gid=65534(nobody) groups=65534(nobody) + nobody := uint64(65534) + + uid, err = strconv.ParseUint(auth.Role.UID, 10, 64) + if err != nil { + return nobody, nobody, err + } + gid, err = strconv.ParseUint(auth.Role.GID, 10, 64) + if err != nil { + return nobody, nobody, err + } + + return uid, gid, nil +} + +// Retrieve current user fromt he context +func GetUser(ctx context.Context) (*userpb.User, error) { + u, ok := appctx.ContextGetUser(ctx) + if !ok { + err := errors.Wrap(errtypes.UserRequired(""), "eosfs: error getting user from ctx") + return nil, err + } + return u, nil +}