Skip to content

Commit

Permalink
No more root in eos-grpc
Browse files Browse the repository at this point in the history
* We now do not use root anymore in eos-grpc, but use the user, daemon or cbox
* Bugfix: when EOS token is provided, pass this on in the gRPC request
* For creating version folders, we impersonate the owner
* Some restructuring
  • Loading branch information
Jesse Geens committed Dec 9, 2024
1 parent 4820f8c commit 373287b
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 279 deletions.
10 changes: 10 additions & 0 deletions changelog/unreleased/rootless-auth.md
Original file line number Diff line number Diff line change
@@ -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/
191 changes: 117 additions & 74 deletions pkg/eosclient/eosgrpc/eosgrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"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"
Expand All @@ -58,27 +59,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.
Expand Down Expand Up @@ -131,15 +117,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"
Expand All @@ -154,12 +131,36 @@ 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 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)
}

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.
Expand Down Expand Up @@ -230,28 +231,40 @@ 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

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
}
}
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
}
Expand All @@ -263,23 +276,33 @@ 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
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
}
}

return mdrq, nil
return rq, nil
}

// AddACL adds an new acl to EOS with the given aclType.
Expand Down Expand Up @@ -711,12 +734,16 @@ 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")

daemonAuth := utils.GetDaemonAuth()

// Initialize the common fields of the MDReq
mdrq, err := c.initMDRequest(ctx, auth)
// 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)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -756,15 +783,24 @@ 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
}
info.Inode = inode
}

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, auth, info), nil
return c.fixupACLs(ctx, daemonAuth, info), nil
}

// GetFileInfoByFXID returns the FileInfo by the given file id in hexadecimal.
Expand Down Expand Up @@ -1193,9 +1229,9 @@ 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) {
func (c *Client) List(ctx context.Context, userAuth 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("")
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)
Expand All @@ -1206,6 +1242,13 @@ func (c *Client) List(ctx context.Context, auth eosclient.Authorization, dpath s

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)
if err != nil {
return nil, err
Expand Down Expand Up @@ -1661,17 +1704,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
}
Expand Down
Loading

0 comments on commit 373287b

Please sign in to comment.