2023-11-08 12:47:05 -05:00
// Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"context"
"crypto/subtle"
2023-12-06 07:31:35 -05:00
"errors"
2023-11-08 12:47:05 -05:00
"fmt"
"net"
"os"
"strconv"
"strings"
"time"
"github.com/minio/minio/internal/logger"
xsftp "github.com/minio/pkg/v2/sftp"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
type sftpLogger struct { }
func ( s * sftpLogger ) Info ( tag xsftp . LogType , msg string ) {
logger . Info ( msg )
}
func ( s * sftpLogger ) Error ( tag xsftp . LogType , err error ) {
switch tag {
case xsftp . AcceptNetworkError :
2024-04-04 08:04:40 -04:00
sftpLogOnceIf ( context . Background ( ) , err , "accept-limit-sftp" )
2023-11-08 12:47:05 -05:00
case xsftp . AcceptChannelError :
2024-04-04 08:04:40 -04:00
sftpLogOnceIf ( context . Background ( ) , err , "accept-channel-sftp" )
2023-11-08 12:47:05 -05:00
case xsftp . SSHKeyExchangeError :
2024-04-04 08:04:40 -04:00
sftpLogOnceIf ( context . Background ( ) , err , "key-exchange-sftp" )
2023-11-08 12:47:05 -05:00
default :
2024-04-04 08:04:40 -04:00
sftpLogOnceIf ( context . Background ( ) , err , "unknown-error-sftp" )
2023-11-08 12:47:05 -05:00
}
}
2023-12-07 04:33:56 -05:00
func startSFTPServer ( args [ ] string ) {
2023-11-08 12:47:05 -05:00
var (
port int
publicIP string
sshPrivateKey string
)
var err error
for _ , arg := range args {
tokens := strings . SplitN ( arg , "=" , 2 )
if len ( tokens ) != 2 {
logger . Fatal ( fmt . Errorf ( "invalid arguments passed to --sftp=%s" , arg ) , "unable to start SFTP server" )
}
switch tokens [ 0 ] {
case "address" :
host , portStr , err := net . SplitHostPort ( tokens [ 1 ] )
if err != nil {
logger . Fatal ( fmt . Errorf ( "invalid arguments passed to --sftp=%s (%v)" , arg , err ) , "unable to start SFTP server" )
}
port , err = strconv . Atoi ( portStr )
if err != nil {
logger . Fatal ( fmt . Errorf ( "invalid arguments passed to --sftp=%s (%v)" , arg , err ) , "unable to start SFTP server" )
}
if port < 1 || port > 65535 {
logger . Fatal ( fmt . Errorf ( "invalid arguments passed to --sftp=%s, (port number must be between 1 to 65535)" , arg ) , "unable to start SFTP server" )
}
publicIP = host
case "ssh-private-key" :
sshPrivateKey = tokens [ 1 ]
}
}
if port == 0 {
port = 8022 // Default SFTP port, since no port was given.
}
if sshPrivateKey == "" {
logger . Fatal ( fmt . Errorf ( "invalid arguments passed, private key file is mandatory for --sftp='ssh-private-key=path/to/id_ecdsa'" ) , "unable to start SFTP server" )
}
privateBytes , err := os . ReadFile ( sshPrivateKey )
if err != nil {
logger . Fatal ( fmt . Errorf ( "invalid arguments passed, private key file is not accessible: %v" , err ) , "unable to start SFTP server" )
}
private , err := ssh . ParsePrivateKey ( privateBytes )
if err != nil {
logger . Fatal ( fmt . Errorf ( "invalid arguments passed, private key file is not parseable: %v" , err ) , "unable to start SFTP server" )
}
// An SSH server is represented by a ServerConfig, which holds
// certificate details and handles authentication of ServerConns.
sshConfig := & ssh . ServerConfig {
PasswordCallback : func ( c ssh . ConnMetadata , pass [ ] byte ) ( * ssh . Permissions , error ) {
if globalIAMSys . LDAPConfig . Enabled ( ) {
2023-12-06 07:31:35 -05:00
sa , _ , err := globalIAMSys . getServiceAccount ( context . Background ( ) , c . User ( ) )
if err != nil && ! errors . Is ( err , errNoSuchServiceAccount ) {
2023-11-08 12:47:05 -05:00
return nil , err
}
2023-12-06 07:31:35 -05:00
if errors . Is ( err , errNoSuchServiceAccount ) {
targetUser , targetGroups , err := globalIAMSys . LDAPConfig . Bind ( c . User ( ) , string ( pass ) )
if err != nil {
return nil , err
}
ldapPolicies , _ := globalIAMSys . PolicyDBGet ( targetUser , targetGroups ... )
if len ( ldapPolicies ) == 0 {
return nil , errAuthentication
}
return & ssh . Permissions {
CriticalOptions : map [ string ] string {
ldapUser : targetUser ,
ldapUserN : c . User ( ) ,
} ,
Extensions : make ( map [ string ] string ) ,
} , nil
2023-11-08 12:47:05 -05:00
}
2023-12-06 07:31:35 -05:00
if subtle . ConstantTimeCompare ( [ ] byte ( sa . Credentials . SecretKey ) , pass ) == 1 {
return & ssh . Permissions {
CriticalOptions : map [ string ] string {
"accessKey" : c . User ( ) ,
} ,
Extensions : make ( map [ string ] string ) ,
} , nil
}
return nil , errAuthentication
2023-11-08 12:47:05 -05:00
}
ui , ok := globalIAMSys . GetUser ( context . Background ( ) , c . User ( ) )
if ! ok {
return nil , errNoSuchUser
}
if subtle . ConstantTimeCompare ( [ ] byte ( ui . Credentials . SecretKey ) , pass ) == 1 {
return & ssh . Permissions {
CriticalOptions : map [ string ] string {
"accessKey" : c . User ( ) ,
} ,
Extensions : make ( map [ string ] string ) ,
} , nil
}
return nil , errAuthentication
} ,
}
sshConfig . AddHostKey ( private )
handleSFTPSession := func ( channel ssh . Channel , sconn * ssh . ServerConn ) {
server := sftp . NewRequestServer ( channel , NewSFTPDriver ( sconn . Permissions ) , sftp . WithRSAllocator ( ) )
defer server . Close ( )
server . Serve ( )
}
sftpServer , err := xsftp . NewServer ( & xsftp . Options {
PublicIP : publicIP ,
Port : port ,
// OpensSSH default handshake timeout is 2 minutes.
SSHHandshakeDeadline : 2 * time . Minute ,
Logger : new ( sftpLogger ) ,
SSHConfig : sshConfig ,
HandleSFTPSession : handleSFTPSession ,
} )
if err != nil {
logger . Fatal ( err , "Unable to start SFTP Server" )
}
err = sftpServer . Listen ( )
if err != nil {
logger . Fatal ( err , "SFTP Server had an unrecoverable error while accepting connections" )
}
}