fix (plg_backend_nfs): support auxiliary gids

This commit is contained in:
MickaelK
2024-04-15 19:14:33 +10:00
parent ee2ce3f5c5
commit 0a728c695b
3 changed files with 96 additions and 46 deletions

View File

@ -9,7 +9,10 @@ import (
. "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/common"
) )
var cacheForEtc AppCache var (
cacheForEtc AppCache
cacheForGroup AppCache
)
const ( const (
DEFAULT_UID = 1000 DEFAULT_UID = 1000
@ -18,28 +21,35 @@ const (
func init() { func init() {
cacheForEtc = NewAppCache(120, 60) cacheForEtc = NewAppCache(120, 60)
cacheForGroup = NewAppCache(120, 60)
} }
func getUid(hint string) uint32 { func extractUserInfo(uidHint string, gidHint string, gidsHint string) (uint32, uint32, []uint32) {
if hint == "" { // case 1: everything is being sent as "uid=number, gid=number and gids=number,number,number"
return DEFAULT_UID if _uid, err := strconv.Atoi(uidHint); err == nil {
} else if _uid, err := strconv.Atoi(hint); err == nil { var (
return uint32(_uid) uid uint32 = uint32(_uid)
} else if uid, _, err := extractFromEtcPasswd(hint); err == nil { gid uint32
return uid gids []uint32
)
if _gid, err := strconv.Atoi(gidHint); err == nil {
gid = uint32(_gid)
} else {
gid = uid
} }
return DEFAULT_UID for _, t := range strings.Split(gidsHint, ",") {
} if gid, err := strconv.Atoi(strings.TrimSpace(t)); err == nil {
gids = append(gids, uint32(gid))
func getGid(hint string) uint32 {
if hint == "" {
return DEFAULT_UID
} else if _gid, err := strconv.Atoi(hint); err == nil {
return uint32(_gid)
} else if _, gid, err := extractFromEtcPasswd(hint); err == nil {
return gid
} }
return DEFAULT_GID }
return uid, gid, gids
}
// case 2: auto detect everything, aka "uid=www-data gid=www-data gids=..." based on uid=www-data
if _uid, _gid, err := extractFromEtcPasswd(uidHint); err == nil {
return _uid, _gid, extractFromEtcGroup(uidHint, _gid)
}
// case 3: base case
return DEFAULT_UID, DEFAULT_GID, []uint32{}
} }
func extractFromEtcPasswd(username string) (uint32, uint32, error) { func extractFromEtcPasswd(username string) (uint32, uint32, error) {
@ -61,7 +71,9 @@ func extractFromEtcPasswd(username string) (uint32, uint32, error) {
s := strings.Split(string(line), ":") s := strings.Split(string(line), ":")
if len(s) != 7 { if len(s) != 7 {
continue continue
} else if username == s[0] { } else if username != s[0] {
continue
}
u, err := strconv.Atoi(s[2]) u, err := strconv.Atoi(s[2])
if err != nil { if err != nil {
continue continue
@ -73,6 +85,38 @@ func extractFromEtcPasswd(username string) (uint32, uint32, error) {
cacheForEtc.Set(map[string]string{"username": username}, []int{u, g}) cacheForEtc.Set(map[string]string{"username": username}, []int{u, g})
return uint32(u), uint32(g), nil return uint32(u), uint32(g), nil
} }
}
return DEFAULT_UID, DEFAULT_GID, ErrNotFound return DEFAULT_UID, DEFAULT_GID, ErrNotFound
} }
func extractFromEtcGroup(username string, primary uint32) []uint32 {
if v := cacheForGroup.Get(map[string]string{"username": username}); v != nil {
return v.([]uint32)
}
f, err := os.OpenFile("/etc/group", os.O_RDONLY, os.ModePerm)
if err != nil {
return []uint32{}
}
defer f.Close()
gids := []uint32{}
lines := bufio.NewReader(f)
for {
line, _, err := lines.ReadLine()
if err != nil {
break
}
s := strings.Split(string(line), ":")
if len(s) != 4 {
continue
} else if username != s[3] {
continue
}
if gid, err := strconv.Atoi(s[2]); err == nil {
ugid := uint32(gid)
if ugid != primary {
gids = append(gids, ugid)
}
}
cacheForGroup.Set(map[string]string{"username": username}, gids)
}
return gids
}

View File

@ -9,26 +9,28 @@ import (
"github.com/vmware/go-nfs-client/nfs/xdr" "github.com/vmware/go-nfs-client/nfs/xdr"
) )
// ref: https://datatracker.ietf.org/doc/html/rfc5531#section-8.2
// so far we only have implemented AUTH_SYS but one day we might want to add support
// for RPCSEC_GSS as detailed in https://datatracker.ietf.org/doc/html/rfc2203
type AuthUnix struct { type AuthUnix struct {
Stamp uint32 Stamp uint32
Machinename string Machinename string
Uid uint32 Uid uint32
Gid uint32 Gid uint32
GidLen uint32
Gids []uint32 Gids []uint32
} }
func NewAuth(machineName string, uid, gid uint32) rpc.Auth { func NewUnixAuth(machineName string, uid, gid uint32, gids []uint32) rpc.Auth {
w := new(bytes.Buffer) w := new(bytes.Buffer)
xdr.Write(w, AuthUnix{ xdr.Write(w, AuthUnix{
Stamp: rand.New(rand.NewSource(time.Now().UnixNano())).Uint32(), Stamp: rand.New(rand.NewSource(time.Now().UnixNano())).Uint32(),
Machinename: machineName, Machinename: machineName,
Uid: uid, Uid: 1000,
Gid: gid, Gid: 1000,
GidLen: 1, Gids: gids,
}) })
return rpc.Auth{ return rpc.Auth{
1, 1, // = AUTH_SYS in RFC5531
w.Bytes(), w.Bytes(),
} }
} }

View File

@ -33,18 +33,16 @@ func (this NfsShare) Init(params map[string]string, app *App) (IBackend, error)
if params["hostname"] == "" { if params["hostname"] == "" {
return nil, ErrNotFound return nil, ErrNotFound
} }
if params["machine_name"] == "" { if params["machine_name"] == "" {
params["machine_name"] = "filestash" params["machine_name"] = "Filestash"
} }
uid, gid, gids := extractUserInfo(params["uid"], params["gid"], params["gids"])
uid := getUid(params["uid"])
gid := getGid(params["gid"])
mount, err := nfs.DialMount(params["hostname"]) mount, err := nfs.DialMount(params["hostname"])
if err != nil { if err != nil {
return nil, err return nil, err
} }
auth := NewAuth(params["machine_name"], uid, gid) auth := NewUnixAuth(params["machine_name"], uid, gid, gids)
v, err := mount.Mount( v, err := mount.Mount(
params["target"], params["target"],
auth, auth,
@ -77,31 +75,37 @@ func (this NfsShare) LoginForm() Form {
Name: "advanced", Name: "advanced",
Type: "enable", Type: "enable",
Placeholder: "Advanced", Placeholder: "Advanced",
Target: []string{"nfs_uid", "nfs_gid", "nfs_machinename", "nfs_chroot"}, Target: []string{"nfs_uid", "nfs_gid", "nfs_gids", "nfs_machinename", "nfs_chroot"},
}, },
FormElement{ FormElement{
Id: "nfs_uid", Id: "nfs_uid",
Name: "uid", Name: "uid",
Type: "text", Type: "text",
Placeholder: "uid", Placeholder: "UID",
}, },
FormElement{ FormElement{
Id: "nfs_gid", Id: "nfs_gid",
Name: "gid", Name: "gid",
Type: "text", Type: "text",
Placeholder: "gid", Placeholder: "GID",
},
FormElement{
Id: "nfs_gids",
Name: "gids",
Type: "text",
Placeholder: "Auxiliary GIDs",
}, },
FormElement{ FormElement{
Id: "nfs_machinename", Id: "nfs_machinename",
Name: "machine_name", Name: "machine_name",
Type: "text", Type: "text",
Placeholder: "machine name", Placeholder: "Machine Name",
}, },
FormElement{ FormElement{
Id: "nfs_chroot", Id: "nfs_chroot",
Name: "path", Name: "path",
Type: "text", Type: "text",
Placeholder: "chroot", Placeholder: "Chroot",
}, },
}, },
} }