server.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. package sftpd
  2. import (
  3. "bytes"
  4. "encoding/hex"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "io/ioutil"
  10. "net"
  11. "os"
  12. "path/filepath"
  13. "strings"
  14. "time"
  15. "github.com/pkg/sftp"
  16. "golang.org/x/crypto/ssh"
  17. "github.com/drakkan/sftpgo/common"
  18. "github.com/drakkan/sftpgo/dataprovider"
  19. "github.com/drakkan/sftpgo/logger"
  20. "github.com/drakkan/sftpgo/metrics"
  21. "github.com/drakkan/sftpgo/utils"
  22. )
  23. const (
  24. defaultPrivateRSAKeyName = "id_rsa"
  25. defaultPrivateECDSAKeyName = "id_ecdsa"
  26. sourceAddressCriticalOption = "source-address"
  27. )
  28. var (
  29. sftpExtensions = []string{"[email protected]"}
  30. )
  31. // Configuration for the SFTP server
  32. type Configuration struct {
  33. // Identification string used by the server
  34. Banner string `json:"banner" mapstructure:"banner"`
  35. // The port used for serving SFTP requests
  36. BindPort int `json:"bind_port" mapstructure:"bind_port"`
  37. // The address to listen on. A blank value means listen on all available network interfaces.
  38. BindAddress string `json:"bind_address" mapstructure:"bind_address"`
  39. // Deprecated: please use the same key in common configuration
  40. IdleTimeout int `json:"idle_timeout" mapstructure:"idle_timeout"`
  41. // Maximum number of authentication attempts permitted per connection.
  42. // If set to a negative number, the number of attempts is unlimited.
  43. // If set to zero, the number of attempts are limited to 6.
  44. MaxAuthTries int `json:"max_auth_tries" mapstructure:"max_auth_tries"`
  45. // Deprecated: please use the same key in common configuration
  46. UploadMode int `json:"upload_mode" mapstructure:"upload_mode"`
  47. // Actions to execute on file operations and SSH commands
  48. Actions common.ProtocolActions `json:"actions" mapstructure:"actions"`
  49. // Deprecated: please use HostKeys
  50. Keys []Key `json:"keys" mapstructure:"keys"`
  51. // HostKeys define the daemon's private host keys.
  52. // Each host key can be defined as a path relative to the configuration directory or an absolute one.
  53. // If empty or missing, the daemon will search or try to generate "id_rsa" and "id_ecdsa" host keys
  54. // inside the configuration directory.
  55. HostKeys []string `json:"host_keys" mapstructure:"host_keys"`
  56. // KexAlgorithms specifies the available KEX (Key Exchange) algorithms in
  57. // preference order.
  58. KexAlgorithms []string `json:"kex_algorithms" mapstructure:"kex_algorithms"`
  59. // Ciphers specifies the ciphers allowed
  60. Ciphers []string `json:"ciphers" mapstructure:"ciphers"`
  61. // MACs Specifies the available MAC (message authentication code) algorithms
  62. // in preference order
  63. MACs []string `json:"macs" mapstructure:"macs"`
  64. // TrustedUserCAKeys specifies a list of public keys paths of certificate authorities
  65. // that are trusted to sign user certificates for authentication.
  66. // The paths can be absolute or relative to the configuration directory
  67. TrustedUserCAKeys []string `json:"trusted_user_ca_keys" mapstructure:"trusted_user_ca_keys"`
  68. // LoginBannerFile the contents of the specified file, if any, are sent to
  69. // the remote user before authentication is allowed.
  70. LoginBannerFile string `json:"login_banner_file" mapstructure:"login_banner_file"`
  71. // Deprecated: please use the same key in common configuration
  72. SetstatMode int `json:"setstat_mode" mapstructure:"setstat_mode"`
  73. // List of enabled SSH commands.
  74. // We support the following SSH commands:
  75. // - "scp". SCP is an experimental feature, we have our own SCP implementation since
  76. // we can't rely on scp system command to proper handle permissions, quota and
  77. // user's home dir restrictions.
  78. // The SCP protocol is quite simple but there is no official docs about it,
  79. // so we need more testing and feedbacks before enabling it by default.
  80. // We may not handle some borderline cases or have sneaky bugs.
  81. // Please do accurate tests yourself before enabling SCP and let us known
  82. // if something does not work as expected for your use cases.
  83. // SCP between two remote hosts is supported using the `-3` scp option.
  84. // - "md5sum", "sha1sum", "sha256sum", "sha384sum", "sha512sum". Useful to check message
  85. // digests for uploaded files. These commands are implemented inside SFTPGo so they
  86. // work even if the matching system commands are not available, for example on Windows.
  87. // - "cd", "pwd". Some mobile SFTP clients does not support the SFTP SSH_FXP_REALPATH and so
  88. // they use "cd" and "pwd" SSH commands to get the initial directory.
  89. // Currently `cd` do nothing and `pwd` always returns the "/" path.
  90. //
  91. // The following SSH commands are enabled by default: "md5sum", "sha1sum", "cd", "pwd".
  92. // "*" enables all supported SSH commands.
  93. EnabledSSHCommands []string `json:"enabled_ssh_commands" mapstructure:"enabled_ssh_commands"`
  94. // Absolute path to an external program or an HTTP URL to invoke for keyboard interactive authentication.
  95. // Leave empty to disable this authentication mode.
  96. KeyboardInteractiveHook string `json:"keyboard_interactive_auth_hook" mapstructure:"keyboard_interactive_auth_hook"`
  97. // Deprecated: please use the same key in common configuration
  98. ProxyProtocol int `json:"proxy_protocol" mapstructure:"proxy_protocol"`
  99. // Deprecated: please use the same key in common configuration
  100. ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
  101. certChecker *ssh.CertChecker
  102. parsedUserCAKeys []ssh.PublicKey
  103. }
  104. // Key contains information about host keys
  105. // Deprecated: please use HostKeys
  106. type Key struct {
  107. // The private key path as absolute path or relative to the configuration directory
  108. PrivateKey string `json:"private_key" mapstructure:"private_key"`
  109. }
  110. type authenticationError struct {
  111. err string
  112. }
  113. func (e *authenticationError) Error() string {
  114. return fmt.Sprintf("Authentication error: %s", e.err)
  115. }
  116. // Initialize the SFTP server and add a persistent listener to handle inbound SFTP connections.
  117. func (c Configuration) Initialize(configDir string) error {
  118. serverConfig := &ssh.ServerConfig{
  119. NoClientAuth: false,
  120. MaxAuthTries: c.MaxAuthTries,
  121. PasswordCallback: func(conn ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
  122. sp, err := c.validatePasswordCredentials(conn, pass)
  123. if err != nil {
  124. return nil, &authenticationError{err: fmt.Sprintf("could not validate password credentials: %v", err)}
  125. }
  126. return sp, nil
  127. },
  128. PublicKeyCallback: func(conn ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
  129. sp, err := c.validatePublicKeyCredentials(conn, pubKey)
  130. if err == ssh.ErrPartialSuccess {
  131. return sp, err
  132. }
  133. if err != nil {
  134. return nil, &authenticationError{err: fmt.Sprintf("could not validate public key credentials: %v", err)}
  135. }
  136. return sp, nil
  137. },
  138. NextAuthMethodsCallback: func(conn ssh.ConnMetadata) []string {
  139. var nextMethods []string
  140. user, err := dataprovider.UserExists(conn.User())
  141. if err == nil {
  142. nextMethods = user.GetNextAuthMethods(conn.PartialSuccessMethods())
  143. }
  144. return nextMethods
  145. },
  146. ServerVersion: fmt.Sprintf("SSH-2.0-%v", c.Banner),
  147. }
  148. if err := c.checkAndLoadHostKeys(configDir, serverConfig); err != nil {
  149. return err
  150. }
  151. if err := c.initializeCertChecker(configDir); err != nil {
  152. return err
  153. }
  154. sftp.SetSFTPExtensions(sftpExtensions...) //nolint:errcheck // we configure valid SFTP Extensions so we cannot get an error
  155. c.configureSecurityOptions(serverConfig)
  156. c.configureKeyboardInteractiveAuth(serverConfig)
  157. c.configureLoginBanner(serverConfig, configDir)
  158. c.checkSSHCommands()
  159. listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", c.BindAddress, c.BindPort))
  160. if err != nil {
  161. logger.Warn(logSender, "", "error starting listener on address %s:%d: %v", c.BindAddress, c.BindPort, err)
  162. return err
  163. }
  164. proxyListener, err := common.Config.GetProxyListener(listener)
  165. if err != nil {
  166. logger.Warn(logSender, "", "error enabling proxy listener: %v", err)
  167. return err
  168. }
  169. logger.Info(logSender, "", "server listener registered address: %v", listener.Addr().String())
  170. for {
  171. var conn net.Conn
  172. if proxyListener != nil {
  173. conn, err = proxyListener.Accept()
  174. } else {
  175. conn, err = listener.Accept()
  176. }
  177. if conn != nil && err == nil {
  178. go c.AcceptInboundConnection(conn, serverConfig)
  179. }
  180. }
  181. }
  182. func (c Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig) {
  183. if len(c.KexAlgorithms) > 0 {
  184. serverConfig.KeyExchanges = c.KexAlgorithms
  185. }
  186. if len(c.Ciphers) > 0 {
  187. serverConfig.Ciphers = c.Ciphers
  188. }
  189. if len(c.MACs) > 0 {
  190. serverConfig.MACs = c.MACs
  191. }
  192. }
  193. func (c Configuration) configureLoginBanner(serverConfig *ssh.ServerConfig, configDir string) {
  194. if len(c.LoginBannerFile) > 0 {
  195. bannerFilePath := c.LoginBannerFile
  196. if !filepath.IsAbs(bannerFilePath) {
  197. bannerFilePath = filepath.Join(configDir, bannerFilePath)
  198. }
  199. bannerContent, err := ioutil.ReadFile(bannerFilePath)
  200. if err == nil {
  201. banner := string(bannerContent)
  202. serverConfig.BannerCallback = func(conn ssh.ConnMetadata) string {
  203. return banner
  204. }
  205. } else {
  206. logger.WarnToConsole("unable to read SFTPD login banner file: %v", err)
  207. logger.Warn(logSender, "", "unable to read login banner file: %v", err)
  208. }
  209. }
  210. }
  211. func (c Configuration) configureKeyboardInteractiveAuth(serverConfig *ssh.ServerConfig) {
  212. if len(c.KeyboardInteractiveHook) == 0 {
  213. return
  214. }
  215. if !strings.HasPrefix(c.KeyboardInteractiveHook, "http") {
  216. if !filepath.IsAbs(c.KeyboardInteractiveHook) {
  217. logger.WarnToConsole("invalid keyboard interactive authentication program: %#v must be an absolute path",
  218. c.KeyboardInteractiveHook)
  219. logger.Warn(logSender, "", "invalid keyboard interactive authentication program: %#v must be an absolute path",
  220. c.KeyboardInteractiveHook)
  221. return
  222. }
  223. _, err := os.Stat(c.KeyboardInteractiveHook)
  224. if err != nil {
  225. logger.WarnToConsole("invalid keyboard interactive authentication program:: %v", err)
  226. logger.Warn(logSender, "", "invalid keyboard interactive authentication program:: %v", err)
  227. return
  228. }
  229. }
  230. serverConfig.KeyboardInteractiveCallback = func(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) {
  231. sp, err := c.validateKeyboardInteractiveCredentials(conn, client)
  232. if err != nil {
  233. return nil, &authenticationError{err: fmt.Sprintf("could not validate keyboard interactive credentials: %v", err)}
  234. }
  235. return sp, nil
  236. }
  237. }
  238. // AcceptInboundConnection handles an inbound connection to the server instance and determines if the request should be served or not.
  239. func (c Configuration) AcceptInboundConnection(conn net.Conn, config *ssh.ServerConfig) {
  240. // Before beginning a handshake must be performed on the incoming net.Conn
  241. // we'll set a Deadline for handshake to complete, the default is 2 minutes as OpenSSH
  242. conn.SetDeadline(time.Now().Add(handshakeTimeout)) //nolint:errcheck
  243. remoteAddr := conn.RemoteAddr()
  244. sconn, chans, reqs, err := ssh.NewServerConn(conn, config)
  245. if err != nil {
  246. logger.Warn(logSender, "", "failed to accept an incoming connection: %v", err)
  247. if _, ok := err.(*ssh.ServerAuthError); !ok {
  248. logger.ConnectionFailedLog("", utils.GetIPFromRemoteAddress(remoteAddr.String()), "no_auth_tryed", err.Error())
  249. metrics.AddNoAuthTryed()
  250. }
  251. return
  252. }
  253. // handshake completed so remove the deadline, we'll use IdleTimeout configuration from now on
  254. conn.SetDeadline(time.Time{}) //nolint:errcheck
  255. var user dataprovider.User
  256. // Unmarshal cannot fails here and even if it fails we'll have a user with no permissions
  257. json.Unmarshal([]byte(sconn.Permissions.Extensions["sftpgo_user"]), &user) //nolint:errcheck
  258. loginType := sconn.Permissions.Extensions["sftpgo_login_method"]
  259. connectionID := hex.EncodeToString(sconn.SessionID())
  260. fs, err := user.GetFilesystem(connectionID)
  261. if err != nil {
  262. logger.Warn(logSender, "", "could create filesystem for user %#v err: %v", user.Username, err)
  263. conn.Close()
  264. return
  265. }
  266. connection := Connection{
  267. BaseConnection: common.NewBaseConnection(connectionID, "sftpd", user, fs),
  268. ClientVersion: string(sconn.ClientVersion()),
  269. RemoteAddr: remoteAddr,
  270. netConn: conn,
  271. channel: nil,
  272. }
  273. connection.Fs.CheckRootPath(user.Username, user.GetUID(), user.GetGID())
  274. connection.Log(logger.LevelInfo, "User id: %d, logged in with: %#v, username: %#v, home_dir: %#v remote addr: %#v",
  275. user.ID, loginType, user.Username, user.HomeDir, remoteAddr.String())
  276. dataprovider.UpdateLastLogin(user) //nolint:errcheck
  277. go ssh.DiscardRequests(reqs)
  278. for newChannel := range chans {
  279. // If its not a session channel we just move on because its not something we
  280. // know how to handle at this point.
  281. if newChannel.ChannelType() != "session" {
  282. connection.Log(logger.LevelDebug, "received an unknown channel type: %v", newChannel.ChannelType())
  283. newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") //nolint:errcheck
  284. continue
  285. }
  286. channel, requests, err := newChannel.Accept()
  287. if err != nil {
  288. connection.Log(logger.LevelWarn, "could not accept a channel: %v", err)
  289. continue
  290. }
  291. // Channels have a type that is dependent on the protocol. For SFTP this is "subsystem"
  292. // with a payload that (should) be "sftp". Discard anything else we receive ("pty", "shell", etc)
  293. go func(in <-chan *ssh.Request) {
  294. for req := range in {
  295. ok := false
  296. switch req.Type {
  297. case "subsystem":
  298. if string(req.Payload[4:]) == "sftp" {
  299. ok = true
  300. connection.SetProtocol(common.ProtocolSFTP)
  301. connection.channel = channel
  302. go c.handleSftpConnection(channel, &connection)
  303. }
  304. case "exec":
  305. connection.SetProtocol(common.ProtocolSSH)
  306. ok = processSSHCommand(req.Payload, &connection, channel, c.EnabledSSHCommands)
  307. }
  308. req.Reply(ok, nil) //nolint:errcheck
  309. }
  310. }(requests)
  311. }
  312. }
  313. func (c Configuration) handleSftpConnection(channel ssh.Channel, connection *Connection) {
  314. common.Connections.Add(connection)
  315. defer common.Connections.Remove(connection.GetID())
  316. // Create a new handler for the currently logged in user's server.
  317. handler := c.createHandler(connection)
  318. // Create the server instance for the channel using the handler we created above.
  319. server := sftp.NewRequestServer(channel, handler, sftp.WithRSAllocator())
  320. if err := server.Serve(); err == io.EOF {
  321. connection.Log(logger.LevelDebug, "connection closed, sending exit status")
  322. exitStatus := sshSubsystemExitStatus{Status: uint32(0)}
  323. _, err = channel.SendRequest("exit-status", false, ssh.Marshal(&exitStatus))
  324. connection.Log(logger.LevelDebug, "sent exit status %+v error: %v", exitStatus, err)
  325. server.Close()
  326. } else if err != nil {
  327. connection.Log(logger.LevelWarn, "connection closed with error: %v", err)
  328. }
  329. }
  330. func (c Configuration) createHandler(connection *Connection) sftp.Handlers {
  331. return sftp.Handlers{
  332. FileGet: connection,
  333. FilePut: connection,
  334. FileCmd: connection,
  335. FileList: connection,
  336. }
  337. }
  338. func loginUser(user dataprovider.User, loginMethod, publicKey string, conn ssh.ConnMetadata) (*ssh.Permissions, error) {
  339. connectionID := ""
  340. if conn != nil {
  341. connectionID = hex.EncodeToString(conn.SessionID())
  342. }
  343. if !filepath.IsAbs(user.HomeDir) {
  344. logger.Warn(logSender, connectionID, "user %#v has an invalid home dir: %#v. Home dir must be an absolute path, login not allowed",
  345. user.Username, user.HomeDir)
  346. return nil, fmt.Errorf("cannot login user with invalid home dir: %#v", user.HomeDir)
  347. }
  348. if user.MaxSessions > 0 {
  349. activeSessions := common.Connections.GetActiveSessions(user.Username)
  350. if activeSessions >= user.MaxSessions {
  351. logger.Debug(logSender, "", "authentication refused for user: %#v, too many open sessions: %v/%v", user.Username,
  352. activeSessions, user.MaxSessions)
  353. return nil, fmt.Errorf("too many open sessions: %v", activeSessions)
  354. }
  355. }
  356. if !user.IsLoginMethodAllowed(loginMethod, conn.PartialSuccessMethods()) {
  357. logger.Debug(logSender, connectionID, "cannot login user %#v, login method %#v is not allowed", user.Username, loginMethod)
  358. return nil, fmt.Errorf("Login method %#v is not allowed for user %#v", loginMethod, user.Username)
  359. }
  360. if dataprovider.GetQuotaTracking() > 0 && user.HasOverlappedMappedPaths() {
  361. logger.Debug(logSender, connectionID, "cannot login user %#v, overlapping mapped folders are allowed only with quota tracking disabled",
  362. user.Username)
  363. return nil, errors.New("overlapping mapped folders are allowed only with quota tracking disabled")
  364. }
  365. remoteAddr := conn.RemoteAddr().String()
  366. if !user.IsLoginFromAddrAllowed(remoteAddr) {
  367. logger.Debug(logSender, connectionID, "cannot login user %#v, remote address is not allowed: %v", user.Username, remoteAddr)
  368. return nil, fmt.Errorf("Login for user %#v is not allowed from this address: %v", user.Username, remoteAddr)
  369. }
  370. json, err := json.Marshal(user)
  371. if err != nil {
  372. logger.Warn(logSender, connectionID, "error serializing user info: %v, authentication rejected", err)
  373. return nil, err
  374. }
  375. if len(publicKey) > 0 {
  376. loginMethod = fmt.Sprintf("%v: %v", loginMethod, publicKey)
  377. }
  378. p := &ssh.Permissions{}
  379. p.Extensions = make(map[string]string)
  380. p.Extensions["sftpgo_user"] = string(json)
  381. p.Extensions["sftpgo_login_method"] = loginMethod
  382. return p, nil
  383. }
  384. func (c *Configuration) checkSSHCommands() {
  385. if utils.IsStringInSlice("*", c.EnabledSSHCommands) {
  386. c.EnabledSSHCommands = GetSupportedSSHCommands()
  387. return
  388. }
  389. sshCommands := []string{}
  390. for _, command := range c.EnabledSSHCommands {
  391. if utils.IsStringInSlice(command, supportedSSHCommands) {
  392. sshCommands = append(sshCommands, command)
  393. } else {
  394. logger.Warn(logSender, "", "unsupported ssh command: %#v ignored", command)
  395. logger.WarnToConsole("unsupported ssh command: %#v ignored", command)
  396. }
  397. }
  398. c.EnabledSSHCommands = sshCommands
  399. }
  400. func (c *Configuration) checkHostKeyAutoGeneration(configDir string) error {
  401. for _, k := range c.HostKeys {
  402. if filepath.IsAbs(k) {
  403. if _, err := os.Stat(k); os.IsNotExist(err) {
  404. keyName := filepath.Base(k)
  405. switch keyName {
  406. case defaultPrivateRSAKeyName:
  407. logger.Info(logSender, "", "try to create non-existent host key %#v", k)
  408. logger.InfoToConsole("try to create non-existent host key %#v", k)
  409. err = utils.GenerateRSAKeys(k)
  410. if err != nil {
  411. return err
  412. }
  413. case defaultPrivateECDSAKeyName:
  414. logger.Info(logSender, "", "try to create non-existent host key %#v", k)
  415. logger.InfoToConsole("try to create non-existent host key %#v", k)
  416. err = utils.GenerateECDSAKeys(k)
  417. if err != nil {
  418. return err
  419. }
  420. default:
  421. logger.Warn(logSender, "", "non-existent host key %#v will not be created", k)
  422. logger.WarnToConsole("non-existent host key %#v will not be created", k)
  423. }
  424. }
  425. }
  426. }
  427. if len(c.HostKeys) == 0 {
  428. defaultKeys := []string{defaultPrivateRSAKeyName, defaultPrivateECDSAKeyName}
  429. for _, k := range defaultKeys {
  430. autoFile := filepath.Join(configDir, k)
  431. if _, err := os.Stat(autoFile); os.IsNotExist(err) {
  432. logger.Info(logSender, "", "No host keys configured and %#v does not exist; try to create a new host key", autoFile)
  433. logger.InfoToConsole("No host keys configured and %#v does not exist; try to create a new host key", autoFile)
  434. if k == defaultPrivateRSAKeyName {
  435. err = utils.GenerateRSAKeys(autoFile)
  436. } else {
  437. err = utils.GenerateECDSAKeys(autoFile)
  438. }
  439. if err != nil {
  440. return err
  441. }
  442. }
  443. c.HostKeys = append(c.HostKeys, k)
  444. }
  445. }
  446. return nil
  447. }
  448. // If no host keys are defined we try to use or generate the default ones.
  449. func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh.ServerConfig) error {
  450. if err := c.checkHostKeyAutoGeneration(configDir); err != nil {
  451. return err
  452. }
  453. for _, k := range c.HostKeys {
  454. hostKey := k
  455. if !utils.IsFileInputValid(hostKey) {
  456. logger.Warn(logSender, "", "unable to load invalid host key: %#v", hostKey)
  457. logger.WarnToConsole("unable to load invalid host key: %#v", hostKey)
  458. continue
  459. }
  460. if !filepath.IsAbs(hostKey) {
  461. hostKey = filepath.Join(configDir, hostKey)
  462. }
  463. logger.Info(logSender, "", "Loading private host key: %s", hostKey)
  464. privateBytes, err := ioutil.ReadFile(hostKey)
  465. if err != nil {
  466. return err
  467. }
  468. private, err := ssh.ParsePrivateKey(privateBytes)
  469. if err != nil {
  470. return err
  471. }
  472. // Add private key to the server configuration.
  473. serverConfig.AddHostKey(private)
  474. }
  475. return nil
  476. }
  477. func (c *Configuration) initializeCertChecker(configDir string) error {
  478. for _, keyPath := range c.TrustedUserCAKeys {
  479. if !utils.IsFileInputValid(keyPath) {
  480. logger.Warn(logSender, "", "unable to load invalid trusted user CA key: %#v", keyPath)
  481. logger.WarnToConsole("unable to load invalid trusted user CA key: %#v", keyPath)
  482. continue
  483. }
  484. if !filepath.IsAbs(keyPath) {
  485. keyPath = filepath.Join(configDir, keyPath)
  486. }
  487. keyBytes, err := ioutil.ReadFile(keyPath)
  488. if err != nil {
  489. logger.Warn(logSender, "", "error loading trusted user CA key %#v: %v", keyPath, err)
  490. logger.WarnToConsole("error loading trusted user CA key %#v: %v", keyPath, err)
  491. return err
  492. }
  493. parsedKey, _, _, _, err := ssh.ParseAuthorizedKey(keyBytes)
  494. if err != nil {
  495. logger.Warn(logSender, "", "error parsing trusted user CA key %#v: %v", keyPath, err)
  496. logger.WarnToConsole("error parsing trusted user CA key %#v: %v", keyPath, err)
  497. return err
  498. }
  499. c.parsedUserCAKeys = append(c.parsedUserCAKeys, parsedKey)
  500. }
  501. c.certChecker = &ssh.CertChecker{
  502. SupportedCriticalOptions: []string{
  503. sourceAddressCriticalOption,
  504. },
  505. IsUserAuthority: func(k ssh.PublicKey) bool {
  506. for _, key := range c.parsedUserCAKeys {
  507. if bytes.Equal(k.Marshal(), key.Marshal()) {
  508. return true
  509. }
  510. }
  511. return false
  512. },
  513. }
  514. return nil
  515. }
  516. func (c Configuration) validatePublicKeyCredentials(conn ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
  517. var err error
  518. var user dataprovider.User
  519. var keyID string
  520. var sshPerm *ssh.Permissions
  521. var certPerm *ssh.Permissions
  522. connectionID := hex.EncodeToString(conn.SessionID())
  523. method := dataprovider.SSHLoginMethodPublicKey
  524. cert, ok := pubKey.(*ssh.Certificate)
  525. if ok {
  526. if cert.CertType != ssh.UserCert {
  527. err = fmt.Errorf("ssh: cert has type %d", cert.CertType)
  528. updateLoginMetrics(conn, method, err)
  529. return nil, err
  530. }
  531. if !c.certChecker.IsUserAuthority(cert.SignatureKey) {
  532. err = fmt.Errorf("ssh: certificate signed by unrecognized authority")
  533. updateLoginMetrics(conn, method, err)
  534. return nil, err
  535. }
  536. if err := c.certChecker.CheckCert(conn.User(), cert); err != nil {
  537. updateLoginMetrics(conn, method, err)
  538. return nil, err
  539. }
  540. certPerm = &cert.Permissions
  541. }
  542. if user, keyID, err = dataprovider.CheckUserAndPubKey(conn.User(), pubKey.Marshal()); err == nil {
  543. if user.IsPartialAuth(method) {
  544. logger.Debug(logSender, connectionID, "user %#v authenticated with partial success", conn.User())
  545. return certPerm, ssh.ErrPartialSuccess
  546. }
  547. sshPerm, err = loginUser(user, method, keyID, conn)
  548. if err == nil && certPerm != nil {
  549. // if we have a SSH user cert we need to merge certificate permissions with our ones
  550. // we only set Extensions, so CriticalOptions are always the ones from the certificate
  551. sshPerm.CriticalOptions = certPerm.CriticalOptions
  552. if certPerm.Extensions != nil {
  553. for k, v := range certPerm.Extensions {
  554. sshPerm.Extensions[k] = v
  555. }
  556. }
  557. }
  558. }
  559. updateLoginMetrics(conn, method, err)
  560. return sshPerm, err
  561. }
  562. func (c Configuration) validatePasswordCredentials(conn ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
  563. var err error
  564. var user dataprovider.User
  565. var sshPerm *ssh.Permissions
  566. method := dataprovider.SSHLoginMethodPassword
  567. if len(conn.PartialSuccessMethods()) == 1 {
  568. method = dataprovider.SSHLoginMethodKeyAndPassword
  569. }
  570. if user, err = dataprovider.CheckUserAndPass(conn.User(), string(pass)); err == nil {
  571. sshPerm, err = loginUser(user, method, "", conn)
  572. }
  573. updateLoginMetrics(conn, method, err)
  574. return sshPerm, err
  575. }
  576. func (c Configuration) validateKeyboardInteractiveCredentials(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) {
  577. var err error
  578. var user dataprovider.User
  579. var sshPerm *ssh.Permissions
  580. method := dataprovider.SSHLoginMethodKeyboardInteractive
  581. if len(conn.PartialSuccessMethods()) == 1 {
  582. method = dataprovider.SSHLoginMethodKeyAndKeyboardInt
  583. }
  584. if user, err = dataprovider.CheckKeyboardInteractiveAuth(conn.User(), c.KeyboardInteractiveHook, client); err == nil {
  585. sshPerm, err = loginUser(user, method, "", conn)
  586. }
  587. updateLoginMetrics(conn, method, err)
  588. return sshPerm, err
  589. }
  590. func updateLoginMetrics(conn ssh.ConnMetadata, method string, err error) {
  591. metrics.AddLoginAttempt(method)
  592. if err != nil {
  593. logger.ConnectionFailedLog(conn.User(), utils.GetIPFromRemoteAddress(conn.RemoteAddr().String()), method, err.Error())
  594. }
  595. metrics.AddLoginResult(method, err)
  596. }