Fix SFTP file upload failures with JWT filer tokens (#8448)
* Fix SFTP file upload failures with JWT filer tokens (issue #8425) When JWT authentication is enabled for filer operations via jwt.filer_signing.* configuration, SFTP server file upload requests were rejected because they lacked JWT authorization headers. Changes: - Added JWT signing key and expiration fields to SftpServer struct - Modified putFile() to generate and include JWT tokens in upload requests - Enhanced SFTPServiceOptions with JWT configuration fields - Updated SFTP command startup to load and pass JWT config to service This allows SFTP uploads to authenticate with JWT-enabled filers, consistent with how other SeaweedFS components (S3 API, file browser) handle filer auth. Fixes #8425 * Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
@@ -101,6 +101,12 @@ func (sftpOpt *SftpOptions) startSftpServer() bool {
|
|||||||
filerAddress := pb.ServerAddress(*sftpOpt.filer)
|
filerAddress := pb.ServerAddress(*sftpOpt.filer)
|
||||||
grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.client")
|
grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.client")
|
||||||
|
|
||||||
|
// Load JWT configuration for filer signing
|
||||||
|
v := util.GetViper()
|
||||||
|
filerSigningKey := v.GetString("jwt.filer_signing.key")
|
||||||
|
v.SetDefault("jwt.filer_signing.expires_after_seconds", 600)
|
||||||
|
filerSigningExpiresAfter := v.GetInt("jwt.filer_signing.expires_after_seconds")
|
||||||
|
|
||||||
// metrics read from the filer
|
// metrics read from the filer
|
||||||
var metricsAddress string
|
var metricsAddress string
|
||||||
var metricsIntervalSec int
|
var metricsIntervalSec int
|
||||||
@@ -137,19 +143,21 @@ func (sftpOpt *SftpOptions) startSftpServer() bool {
|
|||||||
|
|
||||||
// Create a new SFTP service instance with all options
|
// Create a new SFTP service instance with all options
|
||||||
service := sftpd.NewSFTPService(&sftpd.SFTPServiceOptions{
|
service := sftpd.NewSFTPService(&sftpd.SFTPServiceOptions{
|
||||||
GrpcDialOption: grpcDialOption,
|
GrpcDialOption: grpcDialOption,
|
||||||
DataCenter: *sftpOpt.dataCenter,
|
DataCenter: *sftpOpt.dataCenter,
|
||||||
FilerGroup: filerGroup,
|
FilerGroup: filerGroup,
|
||||||
Filer: filerAddress,
|
Filer: filerAddress,
|
||||||
SshPrivateKey: *sftpOpt.sshPrivateKey,
|
SshPrivateKey: *sftpOpt.sshPrivateKey,
|
||||||
HostKeysFolder: *sftpOpt.hostKeysFolder,
|
HostKeysFolder: *sftpOpt.hostKeysFolder,
|
||||||
AuthMethods: authMethods,
|
AuthMethods: authMethods,
|
||||||
MaxAuthTries: *sftpOpt.maxAuthTries,
|
MaxAuthTries: *sftpOpt.maxAuthTries,
|
||||||
BannerMessage: *sftpOpt.bannerMessage,
|
BannerMessage: *sftpOpt.bannerMessage,
|
||||||
LoginGraceTime: *sftpOpt.loginGraceTime,
|
LoginGraceTime: *sftpOpt.loginGraceTime,
|
||||||
ClientAliveInterval: *sftpOpt.clientAliveInterval,
|
ClientAliveInterval: *sftpOpt.clientAliveInterval,
|
||||||
ClientAliveCountMax: *sftpOpt.clientAliveCountMax,
|
ClientAliveCountMax: *sftpOpt.clientAliveCountMax,
|
||||||
UserStoreFile: *sftpOpt.userStoreFile,
|
UserStoreFile: *sftpOpt.userStoreFile,
|
||||||
|
FilerSigningKey: []byte(filerSigningKey),
|
||||||
|
FilerSigningExpiresAfter: filerSigningExpiresAfter,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Register reload hook for HUP signal
|
// Register reload hook for HUP signal
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||||
filer_pb "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
filer_pb "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||||
|
"github.com/seaweedfs/seaweedfs/weed/security"
|
||||||
weed_server "github.com/seaweedfs/seaweedfs/weed/server"
|
weed_server "github.com/seaweedfs/seaweedfs/weed/server"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/sftpd/user"
|
"github.com/seaweedfs/seaweedfs/weed/sftpd/user"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||||
@@ -333,6 +334,14 @@ func (fs *SftpServer) putFile(filepath string, reader io.Reader, user *user.User
|
|||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/octet-stream")
|
req.Header.Set("Content-Type", "application/octet-stream")
|
||||||
|
|
||||||
|
// Add JWT authorization if filer signing key is configured
|
||||||
|
if len(fs.filerSigningKey) > 0 {
|
||||||
|
jwt := security.GenJwtForFilerServer(security.SigningKey(fs.filerSigningKey), fs.filerSigningExpiresAfter)
|
||||||
|
if jwt != "" {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+string(jwt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("upload to filer: %w", err)
|
return fmt.Errorf("upload to filer: %w", err)
|
||||||
|
|||||||
@@ -20,22 +20,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SftpServer struct {
|
type SftpServer struct {
|
||||||
filerAddr pb.ServerAddress
|
filerAddr pb.ServerAddress
|
||||||
grpcDialOption grpc.DialOption
|
grpcDialOption grpc.DialOption
|
||||||
dataCenter string
|
dataCenter string
|
||||||
filerGroup string
|
filerGroup string
|
||||||
user *user.User
|
user *user.User
|
||||||
|
filerSigningKey []byte
|
||||||
|
filerSigningExpiresAfter int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSftpServer constructs the server.
|
// NewSftpServer constructs the server.
|
||||||
func NewSftpServer(filerAddr pb.ServerAddress, grpcDialOption grpc.DialOption, dataCenter, filerGroup string, user *user.User) SftpServer {
|
func NewSftpServer(filerAddr pb.ServerAddress, grpcDialOption grpc.DialOption, dataCenter, filerGroup string, user *user.User, filerSigningKey []byte, filerSigningExpiresAfter int) SftpServer {
|
||||||
|
|
||||||
return SftpServer{
|
return SftpServer{
|
||||||
filerAddr: filerAddr,
|
filerAddr: filerAddr,
|
||||||
grpcDialOption: grpcDialOption,
|
grpcDialOption: grpcDialOption,
|
||||||
dataCenter: dataCenter,
|
dataCenter: dataCenter,
|
||||||
filerGroup: filerGroup,
|
filerGroup: filerGroup,
|
||||||
user: user,
|
user: user,
|
||||||
|
filerSigningKey: filerSigningKey,
|
||||||
|
filerSigningExpiresAfter: filerSigningExpiresAfter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ type SFTPServiceOptions struct {
|
|||||||
|
|
||||||
// User Management
|
// User Management
|
||||||
UserStoreFile string // Path to user store file
|
UserStoreFile string // Path to user store file
|
||||||
|
|
||||||
|
// JWT Configuration for Filer
|
||||||
|
FilerSigningKey []byte // JWT signing key for filer uploads
|
||||||
|
FilerSigningExpiresAfter int // JWT token expiration time in seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSFTPService creates a new service instance.
|
// NewSFTPService creates a new service instance.
|
||||||
@@ -201,6 +205,8 @@ func (s *SFTPService) handleSSHConnection(conn net.Conn, config *ssh.ServerConfi
|
|||||||
s.options.DataCenter,
|
s.options.DataCenter,
|
||||||
s.options.FilerGroup,
|
s.options.FilerGroup,
|
||||||
sftpUser,
|
sftpUser,
|
||||||
|
s.options.FilerSigningKey,
|
||||||
|
s.options.FilerSigningExpiresAfter,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure home directory exists with proper permissions
|
// Ensure home directory exists with proper permissions
|
||||||
|
|||||||
Reference in New Issue
Block a user