From 930fefe21511b9134e44f96b3a390609c2dd42df Mon Sep 17 00:00:00 2001 From: Jesse Geens Date: Mon, 9 Dec 2024 11:40:37 +0100 Subject: [PATCH] Wrote helper function for getting uid/gid, function for setting user or daemon auth --- changelog/unreleased/no-more-shadow-ns.md | 14 ++++ pkg/eosclient/eosgrpc/eosgrpc.go | 89 +++++++---------------- pkg/storage/utils/eosfs/eosfs.go | 69 +++++++++--------- pkg/utils/utils.go | 43 +++++++++++ 4 files changed, 118 insertions(+), 97 deletions(-) create mode 100644 changelog/unreleased/no-more-shadow-ns.md 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/pkg/eosclient/eosgrpc/eosgrpc.go b/pkg/eosclient/eosgrpc/eosgrpc.go index 296289f75b..c2ca7a8d78 100644 --- a/pkg/eosclient/eosgrpc/eosgrpc.go +++ b/pkg/eosclient/eosgrpc/eosgrpc.go @@ -34,7 +34,6 @@ 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" @@ -131,15 +130,6 @@ func (opt *Options) init() { } } -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 serializeAttribute(a *eosclient.Attribute) string { return fmt.Sprintf("%s.%s=%s", attrTypeToString(a.Type), a.Key, a.Val) } @@ -247,17 +237,10 @@ func (c *Client) initNSRequest(ctx context.Context, auth eosclient.Authorization // cbox is a sudo'er, so we become the user specified in UID/GID, if it is set rq.Authkey = c.opt.Authkey - if auth.Role.UID != "" && auth.Role.GID != "" { - 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 - } - rq.Role.Uid = uidInt - rq.Role.Gid = gidInt + uid, gid, err := utils.ExtractUidGid(auth) + if err == nil { + rq.Role.Uid = uid + rq.Role.Gid = gid } } @@ -288,17 +271,10 @@ func (c *Client) initMDRequest(ctx context.Context, auth eosclient.Authorization // cbox is a sudo'er, so we become the user specified in UID/GID, if it is set rq.Authkey = c.opt.Authkey - if auth.Role.UID != "" && auth.Role.GID != "" { - 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 - } - rq.Role.Uid = uidInt - rq.Role.Gid = gidInt + uid, gid, err := utils.ExtractUidGid(auth) + if err == nil { + rq.Role.Uid = uid + rq.Role.Gid = gid } } @@ -738,12 +714,13 @@ func (c *Client) GetFileInfoByPath(ctx context.Context, userAuth eosclient.Autho log := appctx.GetLogger(ctx) log.Debug().Str("func", "GetFileInfoByPath").Str("uid,gid", userAuth.Role.UID+","+userAuth.Role.GID).Str("path", path).Msg("entering") - daemonAuth := utils.GetDaemonAuth() + // 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 - // We do this as the daemon account, because the user may not have access to the file - // e.g. in the case of a guest account - mdrq, err := c.initMDRequest(ctx, daemonAuth) + mdrq, err := c.initMDRequest(ctx, auth) if err != nil { return nil, err } @@ -800,7 +777,7 @@ func (c *Client) GetFileInfoByPath(ctx context.Context, userAuth eosclient.Autho } log.Info().Str("func", "GetFileInfoByPath").Str("path", path).Uint64("info.Inode", info.Inode).Uint64("size", info.Size).Str("etag", info.ETag).Msg("result") - return c.fixupACLs(ctx, daemonAuth, info), nil + return c.fixupACLs(ctx, auth, info), nil } // GetFileInfoByFXID returns the FileInfo by the given file id in hexadecimal. @@ -986,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) @@ -1225,9 +1200,8 @@ 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, userAuth eosclient.Authorization, dpath string) ([]*eosclient.FileInfo, error) { +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", userAuth.Role.UID+","+userAuth.Role.GID).Str("dpath", dpath).Msg("") // Stuff filename, uid, gid into the FindRequest type fdrq := new(erpc.FindRequest) @@ -1238,23 +1212,12 @@ func (c *Client) List(ctx context.Context, userAuth eosclient.Authorization, dpa fdrq.Role = new(erpc.RoleId) - var auth eosclient.Authorization - if userAuth.Role.UID == "" || userAuth.Role.GID == "" { - auth = utils.GetDaemonAuth() - } else { - auth = userAuth - } - - uidInt, err := strconv.ParseUint(auth.Role.UID, 10, 64) + uid, gid, err := utils.ExtractUidGid(auth) if err != nil { - return nil, err - } - gidInt, err := strconv.ParseUint(auth.Role.GID, 10, 64) - 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 @@ -1386,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") } @@ -1422,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") } diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 0ad876fd2c..8267b671b7 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -258,15 +258,6 @@ 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 := appctx.ContextMustGetUser(ctx) @@ -351,7 +342,7 @@ func (fs *eosfs) resolveRefAndGetAuth(ctx context.Context, ref *provider.Referen return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: error resolving reference") } - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: no user in ctx") } @@ -388,7 +379,10 @@ 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 := utils.GetDaemonAuth() + auth, err := fs.getDaemonAuth(ctx) + if err != nil { + return "", err + } eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid) if err != nil { @@ -404,19 +398,19 @@ func (fs *eosfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (stri 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") } var auth eosclient.Authorization if utils.IsLightweightUser(u) { - auth = utils.GetDaemonAuth() + auth, err = fs.getDaemonAuth(ctx) } else { auth, err = fs.getUserAuth(ctx, u, "") - if err != nil { - return "", err - } + } + if err != nil { + return "", err } eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid) @@ -589,7 +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") } - user, err := getUser(ctx) + user, err := utils.GetUser(ctx) if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") } @@ -652,7 +646,7 @@ func (fs *eosfs) SetLock(ctx context.Context, ref *provider.Reference, l *provid return errors.Wrap(err, "eosfs: error resolving reference") } - user, err := getUser(ctx) + user, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } @@ -791,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") } @@ -857,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") } @@ -1125,7 +1119,7 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st // 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 := utils.GetDaemonAuth() + auth, err := fs.getDaemonAuth(ctx) if ref.ResourceId != nil { fid, err := strconv.ParseUint(ref.ResourceId.OpaqueId, 10, 64) @@ -1168,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 { @@ -1208,7 +1203,7 @@ func (fs *eosfs) CreateStorageSpace(ctx context.Context, req *provider.CreateSto } 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") } @@ -1241,7 +1236,7 @@ func (fs *eosfs) GetHome(ctx context.Context) (string, error) { 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") } @@ -1357,7 +1352,7 @@ func (fs *eosfs) CreateDir(ctx context.Context, ref *provider.Reference) error { if err != nil { return errors.Wrap(err, "eosfs: error resolving reference") } - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } @@ -1388,7 +1383,7 @@ func (fs *eosfs) TouchFile(ctx context.Context, ref *provider.Reference) error { } func (fs *eosfs) CreateReference(ctx context.Context, p string, targetURI *url.URL) error { - _, err := getUser(ctx) + _, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } @@ -1431,7 +1426,7 @@ func (fs *eosfs) Delete(ctx context.Context, ref *provider.Reference) error { if err != nil { return errors.Wrap(err, "eosfs: error resolving reference") } - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } @@ -1446,7 +1441,7 @@ func (fs *eosfs) Delete(ctx context.Context, ref *provider.Reference) error { } func (fs *eosfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) error { - u, err := getUser(ctx) + u, err := utils.GetUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } @@ -1503,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) @@ -1593,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") } @@ -1626,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") } @@ -1696,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") } @@ -2092,7 +2089,7 @@ func (fs *eosfs) getEOSToken(ctx context.Context, u *userpb.User, fn string) (eo return eosclient.Authorization{}, errtypes.BadRequest("eosfs: path cannot be empty") } - daemonAuth := utils.GetDaemonAuth() + daemonAuth, err := fs.getDaemonAuth(ctx) info, err := fs.c.GetFileInfoByPath(ctx, daemonAuth, fn) if err != nil { return eosclient.Authorization{}, err @@ -2148,6 +2145,10 @@ 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 != "" { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 1ce62e4e06..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,9 +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. @@ -456,3 +461,41 @@ func GetDaemonAuth() eosclient.Authorization { 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 +}