minio/cmd/sftp-server.go

512 lines
16 KiB
Go
Raw Normal View History

// Copyright (c) 2015-2024 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"
"errors"
"fmt"
"net"
"os"
"strconv"
"strings"
"time"
"github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/logger"
xldap "github.com/minio/pkg/v3/ldap"
xsftp "github.com/minio/pkg/v3/sftp"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
Allow custom SFTP algorithm selection (#19636) Algorithms are comma separated. Note that valid values does not in all cases represent default values. `--sftp=pub-key-algos=...` specifies the supported client public key authentication algorithms. Note that this doesn't include certificate types since those use the underlying algorithm. This list is sent to the client if it supports the server-sig-algs extension. Order is irrelevant. Valid values ``` ssh-ed25519 sk-ssh-ed25519@openssh.com sk-ecdsa-sha2-nistp256@openssh.com ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 rsa-sha2-256 rsa-sha2-512 ssh-rsa ssh-dss ``` `--sftp=kex-algos=...` specifies the supported key-exchange algorithms in preference order. Valid values: ``` curve25519-sha256 curve25519-sha256@libssh.org ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 diffie-hellman-group14-sha256 diffie-hellman-group16-sha512 diffie-hellman-group14-sha1 diffie-hellman-group1-sha1 ``` `--sftp=cipher-algos=...` specifies the allowed cipher algorithms. If unspecified then a sensible default is used. Valid values: ``` aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com arcfour256 arcfour128 arcfour aes128-cbc 3des-cbc ``` `--sftp=mac-algos=...` specifies a default set of MAC algorithms in preference order. This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed because they have reached the end of their useful life. Valid values: ``` hmac-sha2-256-etm@openssh.com hmac-sha2-512-etm@openssh.com hmac-sha2-256 hmac-sha2-512 hmac-sha1 hmac-sha1-96 ```
2024-04-30 11:15:45 -04:00
const (
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
kexAlgoDH14SHA256 = "diffie-hellman-group14-sha256"
kexAlgoDH16SHA512 = "diffie-hellman-group16-sha512"
kexAlgoECDH256 = "ecdh-sha2-nistp256"
kexAlgoECDH384 = "ecdh-sha2-nistp384"
kexAlgoECDH521 = "ecdh-sha2-nistp521"
kexAlgoCurve25519SHA256LibSSH = "curve25519-sha256@libssh.org"
kexAlgoCurve25519SHA256 = "curve25519-sha256"
chacha20Poly1305ID = "chacha20-poly1305@openssh.com"
gcm256CipherID = "aes256-gcm@openssh.com"
aes128cbcID = "aes128-cbc"
tripledescbcID = "3des-cbc"
)
var (
errSFTPPublicKeyBadFormat = errors.New("the public key provided could not be parsed")
errSFTPUserHasNoPolicies = errors.New("no policies present on this account")
errSFTPLDAPNotEnabled = errors.New("ldap authentication is not enabled")
)
// if the sftp parameter --trusted-user-ca-key is set, then
// the final form of the key file will be set as this variable.
var caPublicKey ssh.PublicKey
Allow custom SFTP algorithm selection (#19636) Algorithms are comma separated. Note that valid values does not in all cases represent default values. `--sftp=pub-key-algos=...` specifies the supported client public key authentication algorithms. Note that this doesn't include certificate types since those use the underlying algorithm. This list is sent to the client if it supports the server-sig-algs extension. Order is irrelevant. Valid values ``` ssh-ed25519 sk-ssh-ed25519@openssh.com sk-ecdsa-sha2-nistp256@openssh.com ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 rsa-sha2-256 rsa-sha2-512 ssh-rsa ssh-dss ``` `--sftp=kex-algos=...` specifies the supported key-exchange algorithms in preference order. Valid values: ``` curve25519-sha256 curve25519-sha256@libssh.org ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 diffie-hellman-group14-sha256 diffie-hellman-group16-sha512 diffie-hellman-group14-sha1 diffie-hellman-group1-sha1 ``` `--sftp=cipher-algos=...` specifies the allowed cipher algorithms. If unspecified then a sensible default is used. Valid values: ``` aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com arcfour256 arcfour128 arcfour aes128-cbc 3des-cbc ``` `--sftp=mac-algos=...` specifies a default set of MAC algorithms in preference order. This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed because they have reached the end of their useful life. Valid values: ``` hmac-sha2-256-etm@openssh.com hmac-sha2-512-etm@openssh.com hmac-sha2-256 hmac-sha2-512 hmac-sha1 hmac-sha1-96 ```
2024-04-30 11:15:45 -04:00
// https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.22.0:ssh/common.go;l=46
// preferredKexAlgos specifies the default preference for key-exchange
// algorithms in preference order. The diffie-hellman-group16-sha512 algorithm
// is disabled by default because it is a bit slower than the others.
var preferredKexAlgos = []string{
kexAlgoCurve25519SHA256, kexAlgoCurve25519SHA256LibSSH,
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
kexAlgoDH14SHA256, kexAlgoDH14SHA1,
}
// supportedKexAlgos specifies the supported key-exchange algorithms in
// preference order.
// https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.22.0:ssh/common.go;l=44
var supportedKexAlgos = []string{
kexAlgoCurve25519SHA256, kexAlgoCurve25519SHA256LibSSH,
// P384 and P521 are not constant-time yet, but since we don't
// reuse ephemeral keys, using them for ECDH should be OK.
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
kexAlgoDH14SHA256, kexAlgoDH16SHA512, kexAlgoDH14SHA1,
kexAlgoDH1SHA1,
}
// supportedPubKeyAuthAlgos specifies the supported client public key
// authentication algorithms. Note that this doesn't include certificate types
// since those use the underlying algorithm. This list is sent to the client if
// it supports the server-sig-algs extension. Order is irrelevant.
// https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.22.0:ssh/common.go;l=142
var supportedPubKeyAuthAlgos = []string{
ssh.KeyAlgoED25519,
ssh.KeyAlgoSKED25519, ssh.KeyAlgoSKECDSA256,
ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521,
ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512, ssh.KeyAlgoRSA,
ssh.KeyAlgoDSA,
}
// supportedCiphers lists ciphers we support but might not recommend.
// https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.22.0:ssh/common.go;l=28
var supportedCiphers = []string{
"aes128-ctr", "aes192-ctr", "aes256-ctr",
"aes128-gcm@openssh.com", gcm256CipherID,
chacha20Poly1305ID,
"arcfour256", "arcfour128", "arcfour",
aes128cbcID,
tripledescbcID,
}
// preferredCiphers specifies the default preference for ciphers.
// https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.22.0:ssh/common.go;l=37
var preferredCiphers = []string{
"aes128-gcm@openssh.com", gcm256CipherID,
chacha20Poly1305ID,
"aes128-ctr", "aes192-ctr", "aes256-ctr",
}
// supportedMACs specifies a default set of MAC algorithms in preference order.
// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed
// because they have reached the end of their useful life.
// https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.22.0:ssh/common.go;l=85
var supportedMACs = []string{
"hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com", "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1", "hmac-sha1-96",
}
func sshPubKeyAuth(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
return authenticateSSHConnection(c, key, nil)
}
func sshPasswordAuth(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
return authenticateSSHConnection(c, nil, pass)
}
func authenticateSSHConnection(c ssh.ConnMetadata, key ssh.PublicKey, pass []byte) (*ssh.Permissions, error) {
user, found := strings.CutSuffix(c.User(), "=ldap")
if found {
if !globalIAMSys.LDAPConfig.Enabled() {
return nil, errSFTPLDAPNotEnabled
}
return processLDAPAuthentication(key, pass, user)
}
user, found = strings.CutSuffix(c.User(), "=svc")
if found {
goto internalAuth
}
if globalIAMSys.LDAPConfig.Enabled() {
perms, _ := processLDAPAuthentication(key, pass, user)
if perms != nil {
return perms, nil
}
}
internalAuth:
ui, ok := globalIAMSys.GetUser(context.Background(), user)
if !ok {
return nil, errNoSuchUser
}
if caPublicKey != nil {
err := validateKey(c, key)
if err != nil {
return nil, errAuthentication
}
} else {
// Temporary credentials are not allowed.
if ui.Credentials.IsTemp() {
return nil, errAuthentication
}
if subtle.ConstantTimeCompare([]byte(ui.Credentials.SecretKey), pass) != 1 {
return nil, errAuthentication
}
}
return &ssh.Permissions{
CriticalOptions: map[string]string{
"AccessKey": ui.Credentials.AccessKey,
"SecretKey": ui.Credentials.SecretKey,
"SessionToken": ui.Credentials.SessionToken,
},
Extensions: make(map[string]string),
}, nil
}
func processLDAPAuthentication(key ssh.PublicKey, pass []byte, user string) (perms *ssh.Permissions, err error) {
var lookupResult *xldap.DNSearchResult
var targetGroups []string
if pass == nil && key == nil {
return nil, errAuthentication
}
if pass != nil {
sa, _, err := globalIAMSys.getServiceAccount(context.Background(), user)
if err == nil {
if subtle.ConstantTimeCompare([]byte(sa.Credentials.SecretKey), pass) != 1 {
return nil, errAuthentication
}
return &ssh.Permissions{
CriticalOptions: map[string]string{
"AccessKey": sa.Credentials.AccessKey,
"SecretKey": sa.Credentials.SecretKey,
"SessionToken": sa.Credentials.SessionToken,
},
Extensions: make(map[string]string),
}, nil
}
if !errors.Is(err, errNoSuchServiceAccount) {
return nil, err
}
lookupResult, targetGroups, err = globalIAMSys.LDAPConfig.Bind(user, string(pass))
if err != nil {
return nil, err
}
} else if key != nil {
lookupResult, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(user)
if err != nil {
return nil, err
}
}
if lookupResult == nil {
return nil, errNoSuchUser
}
ldapPolicies, _ := globalIAMSys.PolicyDBGet(lookupResult.NormDN, targetGroups...)
if len(ldapPolicies) == 0 {
return nil, errSFTPUserHasNoPolicies
}
claims := make(map[string]interface{})
for attribKey, attribValue := range lookupResult.Attributes {
// we skip multi-value attributes here, as they cannot
// be stored in the critical options.
if len(attribValue) != 1 {
continue
}
if attribKey == "sshPublicKey" && key != nil {
key2, _, _, _, err := ssh.ParseAuthorizedKey([]byte(attribValue[0]))
if err != nil {
return nil, errSFTPPublicKeyBadFormat
}
if subtle.ConstantTimeCompare(key2.Marshal(), key.Marshal()) != 1 {
return nil, errAuthentication
}
}
claims[ldapAttribPrefix+attribKey] = attribValue[0]
}
expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration("")
if err != nil {
return nil, err
}
claims[expClaim] = UTCNow().Add(expiryDur).Unix()
claims[ldapUserN] = user
claims[ldapUser] = lookupResult.NormDN
cred, err := auth.GetNewCredentialsWithMetadata(claims, globalActiveCred.SecretKey)
if err != nil {
return nil, err
}
// Set the parent of the temporary access key, this is useful
// in obtaining service accounts by this cred.
cred.ParentUser = lookupResult.NormDN
// Set this value to LDAP groups, LDAP user can be part
// of large number of groups
cred.Groups = targetGroups
// Set the newly generated credentials, policyName is empty on purpose
// LDAP policies are applied automatically using their ldapUser, ldapGroups
// mapping.
updatedAt, err := globalIAMSys.SetTempUser(context.Background(), cred.AccessKey, cred, "")
if err != nil {
return nil, err
}
replLogIf(context.Background(), globalSiteReplicationSys.IAMChangeHook(context.Background(), madmin.SRIAMItem{
Type: madmin.SRIAMItemSTSAcc,
STSCredential: &madmin.SRSTSCredential{
AccessKey: cred.AccessKey,
SecretKey: cred.SecretKey,
SessionToken: cred.SessionToken,
ParentUser: cred.ParentUser,
},
UpdatedAt: updatedAt,
}))
return &ssh.Permissions{
CriticalOptions: map[string]string{
"AccessKey": cred.AccessKey,
"SecretKey": cred.SecretKey,
"SessionToken": cred.SessionToken,
},
Extensions: make(map[string]string),
}, nil
}
func validateKey(c ssh.ConnMetadata, clientKey ssh.PublicKey) (err error) {
if caPublicKey == nil {
return errors.New("public key authority validation requested but no ca public key specified.")
}
cert, ok := clientKey.(*ssh.Certificate)
if !ok {
return errSftpPublicKeyWithoutCert
}
// ssh.CheckCert called by ssh.Authenticate accepts certificates
// with empty principles list so we block those in here.
if len(cert.ValidPrincipals) == 0 {
return errSftpCertWithoutPrincipals
}
// Verify that certificate provided by user is issued by trusted CA,
// username in authentication request matches to identities in certificate
// and that certificate type is correct.
checker := ssh.CertChecker{}
checker.IsUserAuthority = func(k ssh.PublicKey) bool {
return subtle.ConstantTimeCompare(caPublicKey.Marshal(), k.Marshal()) == 1
}
_, err = checker.Authenticate(c, clientKey)
return
}
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:
sftpLogOnceIf(context.Background(), err, "accept-limit-sftp")
case xsftp.AcceptChannelError:
sftpLogOnceIf(context.Background(), err, "accept-channel-sftp")
case xsftp.SSHKeyExchangeError:
sftpLogOnceIf(context.Background(), err, "key-exchange-sftp")
default:
sftpLogOnceIf(context.Background(), err, "unknown-error-sftp")
}
}
Allow custom SFTP algorithm selection (#19636) Algorithms are comma separated. Note that valid values does not in all cases represent default values. `--sftp=pub-key-algos=...` specifies the supported client public key authentication algorithms. Note that this doesn't include certificate types since those use the underlying algorithm. This list is sent to the client if it supports the server-sig-algs extension. Order is irrelevant. Valid values ``` ssh-ed25519 sk-ssh-ed25519@openssh.com sk-ecdsa-sha2-nistp256@openssh.com ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 rsa-sha2-256 rsa-sha2-512 ssh-rsa ssh-dss ``` `--sftp=kex-algos=...` specifies the supported key-exchange algorithms in preference order. Valid values: ``` curve25519-sha256 curve25519-sha256@libssh.org ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 diffie-hellman-group14-sha256 diffie-hellman-group16-sha512 diffie-hellman-group14-sha1 diffie-hellman-group1-sha1 ``` `--sftp=cipher-algos=...` specifies the allowed cipher algorithms. If unspecified then a sensible default is used. Valid values: ``` aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com arcfour256 arcfour128 arcfour aes128-cbc 3des-cbc ``` `--sftp=mac-algos=...` specifies a default set of MAC algorithms in preference order. This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed because they have reached the end of their useful life. Valid values: ``` hmac-sha2-256-etm@openssh.com hmac-sha2-512-etm@openssh.com hmac-sha2-256 hmac-sha2-512 hmac-sha1 hmac-sha1-96 ```
2024-04-30 11:15:45 -04:00
func filterAlgos(arg string, want []string, allowed []string) []string {
var filteredAlgos []string
found := false
for _, algo := range want {
if len(algo) == 0 {
continue
}
for _, allowedAlgo := range allowed {
algo := strings.ToLower(strings.TrimSpace(algo))
if algo == allowedAlgo {
filteredAlgos = append(filteredAlgos, algo)
found = true
break
}
}
if !found {
logger.Fatal(fmt.Errorf("unknown algorithm %q passed to --sftp=%s\nValid algorithms: %v", algo, arg, strings.Join(allowed, ", ")), "unable to start SFTP server")
}
}
if len(filteredAlgos) == 0 {
logger.Fatal(fmt.Errorf("no valid algorithms passed to --sftp=%s\nValid algorithms: %v", arg, strings.Join(allowed, ", ")), "unable to start SFTP server")
}
return filteredAlgos
}
func startSFTPServer(args []string) {
var (
port int
publicIP string
sshPrivateKey string
userCaKeyFile string
disablePassAuth bool
)
Allow custom SFTP algorithm selection (#19636) Algorithms are comma separated. Note that valid values does not in all cases represent default values. `--sftp=pub-key-algos=...` specifies the supported client public key authentication algorithms. Note that this doesn't include certificate types since those use the underlying algorithm. This list is sent to the client if it supports the server-sig-algs extension. Order is irrelevant. Valid values ``` ssh-ed25519 sk-ssh-ed25519@openssh.com sk-ecdsa-sha2-nistp256@openssh.com ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 rsa-sha2-256 rsa-sha2-512 ssh-rsa ssh-dss ``` `--sftp=kex-algos=...` specifies the supported key-exchange algorithms in preference order. Valid values: ``` curve25519-sha256 curve25519-sha256@libssh.org ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 diffie-hellman-group14-sha256 diffie-hellman-group16-sha512 diffie-hellman-group14-sha1 diffie-hellman-group1-sha1 ``` `--sftp=cipher-algos=...` specifies the allowed cipher algorithms. If unspecified then a sensible default is used. Valid values: ``` aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com arcfour256 arcfour128 arcfour aes128-cbc 3des-cbc ``` `--sftp=mac-algos=...` specifies a default set of MAC algorithms in preference order. This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed because they have reached the end of their useful life. Valid values: ``` hmac-sha2-256-etm@openssh.com hmac-sha2-512-etm@openssh.com hmac-sha2-256 hmac-sha2-512 hmac-sha1 hmac-sha1-96 ```
2024-04-30 11:15:45 -04:00
allowPubKeys := supportedPubKeyAuthAlgos
allowKexAlgos := preferredKexAlgos
allowCiphers := preferredCiphers
allowMACs := supportedMACs
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]
Allow custom SFTP algorithm selection (#19636) Algorithms are comma separated. Note that valid values does not in all cases represent default values. `--sftp=pub-key-algos=...` specifies the supported client public key authentication algorithms. Note that this doesn't include certificate types since those use the underlying algorithm. This list is sent to the client if it supports the server-sig-algs extension. Order is irrelevant. Valid values ``` ssh-ed25519 sk-ssh-ed25519@openssh.com sk-ecdsa-sha2-nistp256@openssh.com ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 rsa-sha2-256 rsa-sha2-512 ssh-rsa ssh-dss ``` `--sftp=kex-algos=...` specifies the supported key-exchange algorithms in preference order. Valid values: ``` curve25519-sha256 curve25519-sha256@libssh.org ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 diffie-hellman-group14-sha256 diffie-hellman-group16-sha512 diffie-hellman-group14-sha1 diffie-hellman-group1-sha1 ``` `--sftp=cipher-algos=...` specifies the allowed cipher algorithms. If unspecified then a sensible default is used. Valid values: ``` aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com arcfour256 arcfour128 arcfour aes128-cbc 3des-cbc ``` `--sftp=mac-algos=...` specifies a default set of MAC algorithms in preference order. This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed because they have reached the end of their useful life. Valid values: ``` hmac-sha2-256-etm@openssh.com hmac-sha2-512-etm@openssh.com hmac-sha2-256 hmac-sha2-512 hmac-sha1 hmac-sha1-96 ```
2024-04-30 11:15:45 -04:00
case "pub-key-algos":
allowPubKeys = filterAlgos(arg, strings.Split(tokens[1], ","), supportedPubKeyAuthAlgos)
case "kex-algos":
allowKexAlgos = filterAlgos(arg, strings.Split(tokens[1], ","), supportedKexAlgos)
case "cipher-algos":
allowCiphers = filterAlgos(arg, strings.Split(tokens[1], ","), supportedCiphers)
case "mac-algos":
allowMACs = filterAlgos(arg, strings.Split(tokens[1], ","), supportedMACs)
case "trusted-user-ca-key":
userCaKeyFile = tokens[1]
case "password-auth":
disablePassAuth, _ = strconv.ParseBool(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")
}
if userCaKeyFile != "" {
keyBytes, err := os.ReadFile(userCaKeyFile)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed, trusted user certificate authority public key file is not accessible: %v", err), "unable to start SFTP server")
}
caPublicKey, _, _, _, err = ssh.ParseAuthorizedKey(keyBytes)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed, trusted user certificate authority public 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{
Config: ssh.Config{
KeyExchanges: allowKexAlgos,
Ciphers: allowCiphers,
MACs: allowMACs,
},
PublicKeyAuthAlgorithms: allowPubKeys,
PublicKeyCallback: sshPubKeyAuth,
}
if !disablePassAuth {
sshConfig.PasswordCallback = sshPasswordAuth
} else {
sshConfig.PasswordCallback = nil
}
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")
}
}