Fix sftp performances and add seaweedfs all-in-one deployment (#6792)
* improve perfs & fix rclone & refactoring Signed-off-by: Mohamed Sekour <mohamed.sekour@exfo.com> * improve perfs on download + add seaweedfs all-in-one deployment Signed-off-by: Mohamed Sekour <mohamed.sekour@exfo.com> * use helper for topologySpreadConstraints and fix create home dir of sftp users Signed-off-by: Mohamed Sekour <mohamed.sekour@exfo.com> * fix helm lint Signed-off-by: Mohamed Sekour <mohamed.sekour@exfo.com> * add missing ctx param Signed-off-by: Mohamed Sekour <mohamed.sekour@exfo.com> --------- Signed-off-by: Mohamed Sekour <mohamed.sekour@exfo.com>
This commit is contained in:
@@ -17,12 +17,11 @@ type Manager struct {
|
||||
userStore user.Store
|
||||
passwordAuth *PasswordAuthenticator
|
||||
publicKeyAuth *PublicKeyAuthenticator
|
||||
permissionChecker *PermissionChecker
|
||||
enabledAuthMethods []string
|
||||
}
|
||||
|
||||
// NewManager creates a new authentication manager
|
||||
func NewManager(userStore user.Store, fsHelper FileSystemHelper, enabledAuthMethods []string) *Manager {
|
||||
func NewManager(userStore user.Store, enabledAuthMethods []string) *Manager {
|
||||
manager := &Manager{
|
||||
userStore: userStore,
|
||||
enabledAuthMethods: enabledAuthMethods,
|
||||
@@ -43,7 +42,6 @@ func NewManager(userStore user.Store, fsHelper FileSystemHelper, enabledAuthMeth
|
||||
|
||||
manager.passwordAuth = NewPasswordAuthenticator(userStore, passwordEnabled)
|
||||
manager.publicKeyAuth = NewPublicKeyAuthenticator(userStore, publicKeyEnabled)
|
||||
manager.permissionChecker = NewPermissionChecker(fsHelper)
|
||||
|
||||
return manager
|
||||
}
|
||||
@@ -65,11 +63,6 @@ func (m *Manager) GetSSHServerConfig() *ssh.ServerConfig {
|
||||
return config
|
||||
}
|
||||
|
||||
// CheckPermission checks if a user has the required permission on a path
|
||||
func (m *Manager) CheckPermission(user *user.User, path, permission string) error {
|
||||
return m.permissionChecker.CheckFilePermission(user, path, permission)
|
||||
}
|
||||
|
||||
// GetUser retrieves a user from the user store
|
||||
func (m *Manager) GetUser(username string) (*user.User, error) {
|
||||
return m.userStore.GetUser(username)
|
||||
|
||||
@@ -51,14 +51,3 @@ func (a *PasswordAuthenticator) Authenticate(conn ssh.ConnMetadata, password []b
|
||||
|
||||
return nil, fmt.Errorf("authentication failed")
|
||||
}
|
||||
|
||||
// ValidatePassword checks if the provided password is valid for the user
|
||||
func ValidatePassword(store user.Store, username string, password []byte) bool {
|
||||
user, err := store.GetUser(username)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compare plaintext password
|
||||
return string(password) == user.Password
|
||||
}
|
||||
|
||||
@@ -1,267 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/sftpd/user"
|
||||
)
|
||||
|
||||
// Permission constants for clarity and consistency
|
||||
const (
|
||||
PermRead = "read"
|
||||
PermWrite = "write"
|
||||
PermExecute = "execute"
|
||||
PermList = "list"
|
||||
PermDelete = "delete"
|
||||
PermMkdir = "mkdir"
|
||||
PermTraverse = "traverse"
|
||||
PermAll = "*"
|
||||
PermAdmin = "admin"
|
||||
PermReadWrite = "readwrite"
|
||||
)
|
||||
|
||||
// PermissionChecker handles permission checking for file operations
|
||||
// It verifies both Unix-style permissions and explicit ACLs defined in user configuration.
|
||||
type PermissionChecker struct {
|
||||
fsHelper FileSystemHelper
|
||||
}
|
||||
|
||||
// FileSystemHelper provides necessary filesystem operations for permission checking
|
||||
type FileSystemHelper interface {
|
||||
GetEntry(path string) (*Entry, error)
|
||||
}
|
||||
|
||||
// Entry represents a filesystem entry with attributes
|
||||
type Entry struct {
|
||||
IsDirectory bool
|
||||
Attributes *EntryAttributes
|
||||
IsSymlink bool // Added to track symlinks
|
||||
Target string // For symlinks, stores the target path
|
||||
}
|
||||
|
||||
// EntryAttributes contains file attributes
|
||||
type EntryAttributes struct {
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
FileMode uint32
|
||||
SymlinkTarget string
|
||||
}
|
||||
|
||||
// PermissionError represents a permission-related error
|
||||
type PermissionError struct {
|
||||
Path string
|
||||
Perm string
|
||||
User string
|
||||
}
|
||||
|
||||
func (e *PermissionError) Error() string {
|
||||
return fmt.Sprintf("permission denied: %s required on %s for user %s", e.Perm, e.Path, e.User)
|
||||
}
|
||||
|
||||
// NewPermissionChecker creates a new permission checker
|
||||
func NewPermissionChecker(fsHelper FileSystemHelper) *PermissionChecker {
|
||||
return &PermissionChecker{
|
||||
fsHelper: fsHelper,
|
||||
}
|
||||
}
|
||||
|
||||
// CheckFilePermission verifies if a user has the required permission on a path
|
||||
// It first checks if the path is in the user's home directory with explicit permissions.
|
||||
// If not, it falls back to Unix permission checking followed by explicit permission checking.
|
||||
// Parameters:
|
||||
// - user: The user requesting access
|
||||
// - path: The filesystem path to check
|
||||
// - perm: The permission being requested (read, write, execute, etc.)
|
||||
//
|
||||
// Returns:
|
||||
// - nil if permission is granted, error otherwise
|
||||
func (pc *PermissionChecker) CheckFilePermission(user *user.User, path string, perm string) error {
|
||||
if user == nil {
|
||||
return &PermissionError{Path: path, Perm: perm, User: "unknown"}
|
||||
}
|
||||
|
||||
// Retrieve metadata via helper
|
||||
entry, err := pc.fsHelper.GetEntry(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get entry for path %s: %w", path, err)
|
||||
}
|
||||
|
||||
// Handle symlinks by resolving them
|
||||
if entry.IsSymlink {
|
||||
// Get the actual entry for the resolved path
|
||||
entry, err = pc.fsHelper.GetEntry(entry.Attributes.SymlinkTarget)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get entry for resolved path %s: %w", entry.Attributes.SymlinkTarget, err)
|
||||
}
|
||||
|
||||
// Store the original target
|
||||
entry.Target = entry.Attributes.SymlinkTarget
|
||||
}
|
||||
|
||||
// Special case: root user always has permission
|
||||
if user.Username == "root" || user.Uid == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if path is within user's home directory and has explicit permissions
|
||||
if isPathInHomeDirectory(user, path) {
|
||||
// Check if user has explicit permissions for this path
|
||||
if HasExplicitPermission(user, path, perm, entry.IsDirectory) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// For paths outside home directory or without explicit home permissions,
|
||||
// check UNIX-style perms first
|
||||
isOwner := user.Uid == entry.Attributes.Uid
|
||||
isGroup := user.Gid == entry.Attributes.Gid
|
||||
mode := os.FileMode(entry.Attributes.FileMode)
|
||||
|
||||
if HasUnixPermission(isOwner, isGroup, mode, entry.IsDirectory, perm) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Then check explicit ACLs
|
||||
if HasExplicitPermission(user, path, perm, entry.IsDirectory) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return &PermissionError{Path: path, Perm: perm, User: user.Username}
|
||||
}
|
||||
|
||||
// CheckFilePermissionWithContext is a context-aware version of CheckFilePermission
|
||||
// that supports cancellation and timeouts
|
||||
func (pc *PermissionChecker) CheckFilePermissionWithContext(ctx context.Context, user *user.User, path string, perm string) error {
|
||||
// Check for context cancellation
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return pc.CheckFilePermission(user, path, perm)
|
||||
}
|
||||
|
||||
// isPathInHomeDirectory checks if a path is in the user's home directory
|
||||
func isPathInHomeDirectory(user *user.User, path string) bool {
|
||||
return strings.HasPrefix(path, user.HomeDir)
|
||||
}
|
||||
|
||||
// HasUnixPermission checks if the user has the required Unix permission
|
||||
// Uses bit masks for clarity and maintainability
|
||||
func HasUnixPermission(isOwner, isGroup bool, fileMode os.FileMode, isDirectory bool, requiredPerm string) bool {
|
||||
const (
|
||||
ownerRead = 0400
|
||||
ownerWrite = 0200
|
||||
ownerExec = 0100
|
||||
groupRead = 0040
|
||||
groupWrite = 0020
|
||||
groupExec = 0010
|
||||
otherRead = 0004
|
||||
otherWrite = 0002
|
||||
otherExec = 0001
|
||||
)
|
||||
|
||||
// Check read permission
|
||||
hasRead := (isOwner && (fileMode&ownerRead != 0)) ||
|
||||
(isGroup && (fileMode&groupRead != 0)) ||
|
||||
(fileMode&otherRead != 0)
|
||||
|
||||
// Check write permission
|
||||
hasWrite := (isOwner && (fileMode&ownerWrite != 0)) ||
|
||||
(isGroup && (fileMode&groupWrite != 0)) ||
|
||||
(fileMode&otherWrite != 0)
|
||||
|
||||
// Check execute permission
|
||||
hasExec := (isOwner && (fileMode&ownerExec != 0)) ||
|
||||
(isGroup && (fileMode&groupExec != 0)) ||
|
||||
(fileMode&otherExec != 0)
|
||||
|
||||
switch requiredPerm {
|
||||
case PermRead:
|
||||
return hasRead
|
||||
case PermWrite:
|
||||
return hasWrite
|
||||
case PermExecute:
|
||||
return hasExec
|
||||
case PermList:
|
||||
if isDirectory {
|
||||
return hasRead && hasExec
|
||||
}
|
||||
return hasRead
|
||||
case PermDelete:
|
||||
return hasWrite
|
||||
case PermMkdir:
|
||||
return isDirectory && hasWrite
|
||||
case PermTraverse:
|
||||
return isDirectory && hasExec
|
||||
case PermReadWrite:
|
||||
return hasRead && hasWrite
|
||||
case PermAll, PermAdmin:
|
||||
return hasRead && hasWrite && hasExec
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasExplicitPermission checks if the user has explicit permission from user config
|
||||
func HasExplicitPermission(user *user.User, filepath, requiredPerm string, isDirectory bool) bool {
|
||||
// Find the most specific permission that applies to this path
|
||||
var bestMatch string
|
||||
var perms []string
|
||||
|
||||
for p, userPerms := range user.Permissions {
|
||||
// Check if the path is either the permission path exactly or is under that path
|
||||
if strings.HasPrefix(filepath, p) && len(p) > len(bestMatch) {
|
||||
bestMatch = p
|
||||
perms = userPerms
|
||||
}
|
||||
}
|
||||
|
||||
// No matching permissions found
|
||||
if bestMatch == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if user has admin role
|
||||
if containsString(perms, PermAdmin) {
|
||||
return true
|
||||
}
|
||||
|
||||
// If user has list permission and is requesting traverse/execute permission, grant it
|
||||
if isDirectory && requiredPerm == PermExecute && containsString(perms, PermList) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if the required permission is in the list
|
||||
for _, perm := range perms {
|
||||
if perm == requiredPerm || perm == PermAll {
|
||||
return true
|
||||
}
|
||||
|
||||
// Handle combined permissions
|
||||
if perm == PermReadWrite && (requiredPerm == PermRead || requiredPerm == PermWrite) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Directory-specific permissions
|
||||
if isDirectory && perm == PermList && requiredPerm == PermRead {
|
||||
return true
|
||||
}
|
||||
if isDirectory && perm == PermTraverse && requiredPerm == PermExecute {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Helper function to check if a string is in a slice
|
||||
func containsString(slice []string, s string) bool {
|
||||
for _, item := range slice {
|
||||
if item == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/sftpd/user"
|
||||
@@ -40,7 +39,7 @@ func (a *PublicKeyAuthenticator) Authenticate(conn ssh.ConnMetadata, key ssh.Pub
|
||||
keyData := string(key.Marshal())
|
||||
|
||||
// Validate public key
|
||||
if ValidatePublicKey(a.userStore, username, keyData) {
|
||||
if a.userStore.ValidatePublicKey(username, keyData) {
|
||||
return &ssh.Permissions{
|
||||
Extensions: map[string]string{
|
||||
"username": username,
|
||||
@@ -50,19 +49,3 @@ func (a *PublicKeyAuthenticator) Authenticate(conn ssh.ConnMetadata, key ssh.Pub
|
||||
|
||||
return nil, fmt.Errorf("authentication failed")
|
||||
}
|
||||
|
||||
// ValidatePublicKey checks if the provided public key is valid for the user
|
||||
func ValidatePublicKey(store user.Store, username string, keyData string) bool {
|
||||
user, err := store.GetUser(username)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, key := range user.PublicKeys {
|
||||
if subtle.ConstantTimeCompare([]byte(key), []byte(keyData)) == 1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user