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:
Chris Lu
2026-02-25 14:30:21 -08:00
committed by GitHub
parent a92e9baddf
commit 7f6e58b791
4 changed files with 51 additions and 24 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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,
} }
} }

View File

@@ -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