From 91dcc349de5bede66857cda56b1e8d10922006a6 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Tue, 4 Aug 2020 18:03:28 +0200 Subject: [PATCH] Add client IP address to external auth, pre-login and keyboard interactive hooks --- common/common.go | 2 +- common/connection.go | 4 +-- common/connection_test.go | 2 +- dataprovider/dataprovider.go | 60 ++++++++++++++++++++------------- docker/sftpgo/debian/Dockerfile | 7 ++-- docs/dynamic-user-mod.md | 3 +- docs/external-auth.md | 2 ++ docs/keyboard-interactive.md | 8 +++-- ftpd/server.go | 2 +- sftpd/server.go | 9 +++-- 10 files changed, 59 insertions(+), 40 deletions(-) diff --git a/common/common.go b/common/common.go index 45a8937e..704be328 100644 --- a/common/common.go +++ b/common/common.go @@ -94,7 +94,7 @@ var ( QuotaScans ActiveScans idleTimeoutTicker *time.Ticker idleTimeoutTickerDone chan bool - supportedProcols = []string{ProtocolSFTP, ProtocolSCP, ProtocolSSH, ProtocolFTP} + supportedProtocols = []string{ProtocolSFTP, ProtocolSCP, ProtocolSSH, ProtocolFTP} ) // Initialize sets the common configuration diff --git a/common/connection.go b/common/connection.go index a74893cb..64bd45bf 100644 --- a/common/connection.go +++ b/common/connection.go @@ -37,7 +37,7 @@ type BaseConnection struct { // NewBaseConnection returns a new BaseConnection func NewBaseConnection(ID, protocol string, user dataprovider.User, fs vfs.Fs) *BaseConnection { connID := ID - if utils.IsStringInSlice(protocol, supportedProcols) { + if utils.IsStringInSlice(protocol, supportedProtocols) { connID = fmt.Sprintf("%v_%v", protocol, ID) } return &BaseConnection{ @@ -79,7 +79,7 @@ func (c *BaseConnection) GetProtocol() string { // SetProtocol sets the protocol for this connection func (c *BaseConnection) SetProtocol(protocol string) { c.protocol = protocol - if utils.IsStringInSlice(c.protocol, supportedProcols) { + if utils.IsStringInSlice(c.protocol, supportedProtocols) { c.ID = fmt.Sprintf("%v_%v", c.protocol, c.ID) } } diff --git a/common/connection_test.go b/common/connection_test.go index e4b33e23..5679d20b 100644 --- a/common/connection_test.go +++ b/common/connection_test.go @@ -1030,7 +1030,7 @@ func TestUpdateQuotaMoveVFolders(t *testing.T) { func TestErrorsMapping(t *testing.T) { fs := vfs.NewOsFs("", os.TempDir(), nil) conn := NewBaseConnection("", ProtocolSFTP, dataprovider.User{}, fs) - for _, protocol := range supportedProcols { + for _, protocol := range supportedProtocols { conn.SetProtocol(protocol) err := conn.GetFsError(os.ErrNotExist) if protocol == ProtocolSFTP { diff --git a/dataprovider/dataprovider.go b/dataprovider/dataprovider.go index 8d08f00d..fdacf00b 100644 --- a/dataprovider/dataprovider.go +++ b/dataprovider/dataprovider.go @@ -177,6 +177,7 @@ type Config struct { // to authenticate: // // - SFTPGO_AUTHD_USERNAME + // - SFTPGO_AUTHD_IP // - SFTPGO_AUTHD_PASSWORD, not empty for password authentication // - SFTPGO_AUTHD_PUBLIC_KEY, not empty for public key authentication // - SFTPGO_AUTHD_KEYBOARD_INTERACTIVE, not empty for keyboard interactive authentication @@ -190,6 +191,7 @@ type Config struct { // The request body will contain a JSON serialized struct with the following fields: // // - username + // - ip // - password, not empty for password authentication // - public_key, not empty for public key authentication // - keyboard_interactive, not empty for keyboard interactive authentication @@ -227,15 +229,18 @@ type Config struct { // // - SFTPGO_LOGIND_USER, it contains the user trying to login serialized as JSON // - SFTPGO_LOGIND_METHOD, possible values are: "password", "publickey" and "keyboard-interactive" + // - SFTPGO_LOGIND_IP, ip address of the user trying to login // // The program must write on its standard output an empty string if no user update is needed // or a valid SFTPGo user serialized as JSON. // // If the hook is an HTTP URL then it will be invoked as HTTP POST. - // The login method is added to the query string, for example "?login_method=password". + // The login method and the ip address of the user trying to login are added to + // the query string, for example "?login_method=password&ip=1.2.3.4" // The request body will contain the user trying to login serialized as JSON. - // If no modification is needed the HTTP response code must be 204, otherwise the response code - // must be 200 and the response body a valid SFTPGo user serialized as JSON. + // If no modification is needed the HTTP response code must be 204, otherwise + // the response code must be 200 and the response body a valid SFTPGo user + // serialized as JSON. // // The JSON response can include only the fields to update instead of the full user, // for example if you want to disable the user you can return a response like this: @@ -262,6 +267,7 @@ type BackupData struct { type keyboardAuthHookRequest struct { RequestID string `json:"request_id"` Username string `json:"username,omitempty"` + IP string `json:"ip,omitempty"` Password string `json:"password,omitempty"` Answers []string `json:"answers,omitempty"` Questions []string `json:"questions,omitempty"` @@ -432,16 +438,16 @@ func InitializeDatabase(cnf Config, basePath string) error { } // CheckUserAndPass retrieves the SFTP user with the given username and password if a match is found or an error -func CheckUserAndPass(username string, password string) (User, error) { +func CheckUserAndPass(username, password, ip string) (User, error) { if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&1 != 0) { - user, err := doExternalAuth(username, password, nil, "") + user, err := doExternalAuth(username, password, nil, "", ip) if err != nil { return user, err } return checkUserAndPass(user, password) } if len(config.PreLoginHook) > 0 { - user, err := executePreLoginHook(username, SSHLoginMethodPassword) + user, err := executePreLoginHook(username, SSHLoginMethodPassword, ip) if err != nil { return user, err } @@ -451,16 +457,16 @@ func CheckUserAndPass(username string, password string) (User, error) { } // CheckUserAndPubKey retrieves the SFTP user with the given username and public key if a match is found or an error -func CheckUserAndPubKey(username string, pubKey []byte) (User, string, error) { +func CheckUserAndPubKey(username string, pubKey []byte, ip string) (User, string, error) { if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&2 != 0) { - user, err := doExternalAuth(username, "", pubKey, "") + user, err := doExternalAuth(username, "", pubKey, "", ip) if err != nil { return user, "", err } return checkUserAndPubKey(user, pubKey) } if len(config.PreLoginHook) > 0 { - user, err := executePreLoginHook(username, SSHLoginMethodPublicKey) + user, err := executePreLoginHook(username, SSHLoginMethodPublicKey, ip) if err != nil { return user, "", err } @@ -471,20 +477,20 @@ func CheckUserAndPubKey(username string, pubKey []byte) (User, string, error) { // CheckKeyboardInteractiveAuth checks the keyboard interactive authentication and returns // the authenticated user or an error -func CheckKeyboardInteractiveAuth(username, authHook string, client ssh.KeyboardInteractiveChallenge) (User, error) { +func CheckKeyboardInteractiveAuth(username, authHook string, client ssh.KeyboardInteractiveChallenge, ip string) (User, error) { var user User var err error if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&4 != 0) { - user, err = doExternalAuth(username, "", nil, "1") + user, err = doExternalAuth(username, "", nil, "1", ip) } else if len(config.PreLoginHook) > 0 { - user, err = executePreLoginHook(username, SSHLoginMethodKeyboardInteractive) + user, err = executePreLoginHook(username, SSHLoginMethodKeyboardInteractive, ip) } else { user, err = provider.userExists(username) } if err != nil { return user, err } - return doKeyboardInteractiveAuth(user, authHook, client) + return doKeyboardInteractiveAuth(user, authHook, client, ip) } // UpdateLastLogin updates the last login fields for the given SFTP user @@ -1303,7 +1309,7 @@ func sendKeyboardAuthHTTPReq(url *url.URL, request keyboardAuthHookRequest) (key return response, err } -func executeKeyboardInteractiveHTTPHook(user User, authHook string, client ssh.KeyboardInteractiveChallenge) (int, error) { +func executeKeyboardInteractiveHTTPHook(user User, authHook string, client ssh.KeyboardInteractiveChallenge, ip string) (int, error) { authResult := 0 var url *url.URL url, err := url.Parse(authHook) @@ -1314,6 +1320,7 @@ func executeKeyboardInteractiveHTTPHook(user User, authHook string, client ssh.K requestID := xid.New().String() req := keyboardAuthHookRequest{ Username: user.Username, + IP: ip, Password: user.Password, RequestID: requestID, } @@ -1388,13 +1395,14 @@ func handleProgramInteractiveQuestions(client ssh.KeyboardInteractiveChallenge, return nil } -func executeKeyboardInteractiveProgram(user User, authHook string, client ssh.KeyboardInteractiveChallenge) (int, error) { +func executeKeyboardInteractiveProgram(user User, authHook string, client ssh.KeyboardInteractiveChallenge, ip string) (int, error) { authResult := 0 ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() cmd := exec.CommandContext(ctx, authHook) cmd.Env = append(os.Environ(), fmt.Sprintf("SFTPGO_AUTHD_USERNAME=%v", user.Username), + fmt.Sprintf("SFTPGO_AUTHD_IP=%v", ip), fmt.Sprintf("SFTPGO_AUTHD_PASSWORD=%v", user.Password)) stdout, err := cmd.StdoutPipe() if err != nil { @@ -1445,13 +1453,13 @@ func executeKeyboardInteractiveProgram(user User, authHook string, client ssh.Ke return authResult, err } -func doKeyboardInteractiveAuth(user User, authHook string, client ssh.KeyboardInteractiveChallenge) (User, error) { +func doKeyboardInteractiveAuth(user User, authHook string, client ssh.KeyboardInteractiveChallenge, ip string) (User, error) { var authResult int var err error if strings.HasPrefix(authHook, "http") { - authResult, err = executeKeyboardInteractiveHTTPHook(user, authHook, client) + authResult, err = executeKeyboardInteractiveHTTPHook(user, authHook, client, ip) } else { - authResult, err = executeKeyboardInteractiveProgram(user, authHook, client) + authResult, err = executeKeyboardInteractiveProgram(user, authHook, client, ip) } if err != nil { return user, err @@ -1466,7 +1474,7 @@ func doKeyboardInteractiveAuth(user User, authHook string, client ssh.KeyboardIn return user, nil } -func getPreLoginHookResponse(loginMethod string, userAsJSON []byte) ([]byte, error) { +func getPreLoginHookResponse(loginMethod, ip string, userAsJSON []byte) ([]byte, error) { if strings.HasPrefix(config.PreLoginHook, "http") { var url *url.URL var result []byte @@ -1477,6 +1485,7 @@ func getPreLoginHookResponse(loginMethod string, userAsJSON []byte) ([]byte, err } q := url.Query() q.Add("login_method", loginMethod) + q.Add("ip", ip) url.RawQuery = q.Encode() httpClient := httpclient.GetHTTPClient() resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(userAsJSON)) @@ -1499,11 +1508,12 @@ func getPreLoginHookResponse(loginMethod string, userAsJSON []byte) ([]byte, err cmd.Env = append(os.Environ(), fmt.Sprintf("SFTPGO_LOGIND_USER=%v", string(userAsJSON)), fmt.Sprintf("SFTPGO_LOGIND_METHOD=%v", loginMethod), + fmt.Sprintf("SFTPGO_LOGIND_IP=%v", ip), ) return cmd.Output() } -func executePreLoginHook(username, loginMethod string) (User, error) { +func executePreLoginHook(username, loginMethod, ip string) (User, error) { u, err := provider.userExists(username) if err != nil { if _, ok := err.(*RecordNotFoundError); !ok { @@ -1518,7 +1528,7 @@ func executePreLoginHook(username, loginMethod string) (User, error) { if err != nil { return u, err } - out, err := getPreLoginHookResponse(loginMethod, userAsJSON) + out, err := getPreLoginHookResponse(loginMethod, ip, userAsJSON) if err != nil { return u, fmt.Errorf("Pre-login hook error: %v", err) } @@ -1557,7 +1567,7 @@ func executePreLoginHook(username, loginMethod string) (User, error) { return provider.userExists(username) } -func getExternalAuthResponse(username, password, pkey, keyboardInteractive string) ([]byte, error) { +func getExternalAuthResponse(username, password, pkey, keyboardInteractive, ip string) ([]byte, error) { if strings.HasPrefix(config.ExternalAuthHook, "http") { var url *url.URL var result []byte @@ -1569,6 +1579,7 @@ func getExternalAuthResponse(username, password, pkey, keyboardInteractive strin httpClient := httpclient.GetHTTPClient() authRequest := make(map[string]string) authRequest["username"] = username + authRequest["ip"] = ip authRequest["password"] = password authRequest["public_key"] = pkey authRequest["keyboard_interactive"] = keyboardInteractive @@ -1593,13 +1604,14 @@ func getExternalAuthResponse(username, password, pkey, keyboardInteractive strin cmd := exec.CommandContext(ctx, config.ExternalAuthHook) cmd.Env = append(os.Environ(), fmt.Sprintf("SFTPGO_AUTHD_USERNAME=%v", username), + fmt.Sprintf("SFTPGO_AUTHD_IP=%v", ip), fmt.Sprintf("SFTPGO_AUTHD_PASSWORD=%v", password), fmt.Sprintf("SFTPGO_AUTHD_PUBLIC_KEY=%v", pkey), fmt.Sprintf("SFTPGO_AUTHD_KEYBOARD_INTERACTIVE=%v", keyboardInteractive)) return cmd.Output() } -func doExternalAuth(username, password string, pubKey []byte, keyboardInteractive string) (User, error) { +func doExternalAuth(username, password string, pubKey []byte, keyboardInteractive, ip string) (User, error) { var user User pkey := "" if len(pubKey) > 0 { @@ -1609,7 +1621,7 @@ func doExternalAuth(username, password string, pubKey []byte, keyboardInteractiv } pkey = string(ssh.MarshalAuthorizedKey(k)) } - out, err := getExternalAuthResponse(username, password, pkey, keyboardInteractive) + out, err := getExternalAuthResponse(username, password, pkey, keyboardInteractive, ip) if err != nil { return user, fmt.Errorf("External auth error: %v", err) } diff --git a/docker/sftpgo/debian/Dockerfile b/docker/sftpgo/debian/Dockerfile index 4831b248..f8c4080e 100644 --- a/docker/sftpgo/debian/Dockerfile +++ b/docker/sftpgo/debian/Dockerfile @@ -12,11 +12,11 @@ RUN go build $(if [ -n "${FEATURES}" ]; then echo "-tags ${FEATURES}"; fi) -ldfl # now define the run environment FROM debian:latest -# ca-certificates is needed for Cloud Storage Support and to expose the REST API over HTTPS. -RUN apt-get update && apt-get install -y ca-certificates +# ca-certificates is needed for Cloud Storage Support and for HTTPS/FTPS. +RUN apt-get update && apt-get install -y ca-certificates && apt-get clean # git and rsync are optional, uncomment the next line to add support for them if needed. -#RUN apt-get update && apt-get install -y git rsync +#RUN apt-get update && apt-get install -y git rsync && apt-get clean ARG BASE_DIR=/app ARG DATA_REL_DIR=data @@ -82,6 +82,5 @@ ENV SFTPGO_HTTPD__BACKUPS_PATH=${BACKUPS_DIR} #ENV SFTPGO_FTPD__CERTIFICATE_FILE=${CONFIG_DIR}/mycert.crt #ENV SFTPGO_FTPD__CERTIFICATE_KEY_FILE=${CONFIG_DIR}/mycert.key - ENTRYPOINT ["sftpgo"] CMD ["serve"] diff --git a/docs/dynamic-user-mod.md b/docs/dynamic-user-mod.md index 1fccef04..be7b02d6 100644 --- a/docs/dynamic-user-mod.md +++ b/docs/dynamic-user-mod.md @@ -7,13 +7,14 @@ The external program can read the following environment variables to get info ab - `SFTPGO_LOGIND_USER`, it contains the user trying to login serialized as JSON. A JSON serialized user id equal to zero means the user does not exists inside SFTPGo - `SFTPGO_LOGIND_METHOD`, possible values are: `password`, `publickey` and `keyboard-interactive` +- `SFTPGO_LOGIND_IP`, ip address of the user trying to login The program must write, on its the standard output: - an empty string (or no response at all) if the user should not be created/updated - or the SFTPGo user, JSON serialized, if you want create or update the given user -If the hook is an HTTP URL then it will be invoked as HTTP POST. The login method is added to the query string, for example `?login_method=password`. +If the hook is an HTTP URL then it will be invoked as HTTP POST. The login method and the ip address of the user trying to login are added to the query string, for example `?login_method=password&ip=1.2.3.4`. The request body will contain the user trying to login serialized as JSON. If no modification is needed the HTTP response code must be 204, otherwise the response code must be 200 and the response body a valid SFTPGo user serialized as JSON. Actions defined for user's updates will not be executed in this case. diff --git a/docs/external-auth.md b/docs/external-auth.md index afe32bce..cc1b3b57 100644 --- a/docs/external-auth.md +++ b/docs/external-auth.md @@ -5,6 +5,7 @@ To enable external authentication, you must set the absolute path of your authen The external program can read the following environment variables to get info about the user trying to authenticate: - `SFTPGO_AUTHD_USERNAME` +- `SFTPGO_AUTHD_IP` - `SFTPGO_AUTHD_PASSWORD`, not empty for password authentication - `SFTPGO_AUTHD_PUBLIC_KEY`, not empty for public key authentication - `SFTPGO_AUTHD_KEYBOARD_INTERACTIVE`, not empty for keyboard interactive authentication @@ -15,6 +16,7 @@ The program must write, on its standard output, a valid SFTPGo user serialized a If the hook is an HTTP URL then it will be invoked as HTTP POST. The request body will contain a JSON serialized struct with the following fields: - `username` +- `ip` - `password`, not empty for password authentication - `public_key`, not empty for public key authentication - `keyboard_interactive`, not empty for keyboard interactive authentication diff --git a/docs/keyboard-interactive.md b/docs/keyboard-interactive.md index 6a1bf6aa..b1c56c4d 100644 --- a/docs/keyboard-interactive.md +++ b/docs/keyboard-interactive.md @@ -9,6 +9,7 @@ To enable keyboard interactive authentication, you must set the absolute path of The external program can read the following environment variables to get info about the user trying to authenticate: - `SFTPGO_AUTHD_USERNAME` +- `SFTPGO_AUTHD_IP` - `SFTPGO_AUTHD_PASSWORD`, this is the hashed password as stored inside the data provider Previous global environment variables aren't cleared when the script is called. The content of these variables is _not_ quoted. They may contain special characters. @@ -77,6 +78,7 @@ The request body will contain a JSON struct with the following fields: - `request_id`, string. Unique request identifier - `username`, string +- `ip`, string - `password`, string. This is the hashed password as stored inside the data provider - `answers`, list of string. It will be null for the first request - `questions`, list of string. It will contains the previous asked questions. It will be null for the first request @@ -93,7 +95,7 @@ Content-Length: 189 Content-Type: application/json Accept-Encoding: gzip -{"request_id":"bq1r5r7cdrpd2qtn25ng","username":"a","password":"$pbkdf2-sha512$150000$ClOPkLNujMTL$XktKy0xuJsOfMYBz+f2bIyPTdbvDTSnJ1q+7+zp/HPq5Qojwp6kcpSIiVHiwvbi8P6HFXI/D3UJv9BLcnQFqPA=="} +{"request_id":"bq1r5r7cdrpd2qtn25ng","username":"a","ip":"127.0.0.1","password":"$pbkdf2-sha512$150000$ClOPkLNujMTL$XktKy0xuJsOfMYBz+f2bIyPTdbvDTSnJ1q+7+zp/HPq5Qojwp6kcpSIiVHiwvbi8P6HFXI/D3UJv9BLcnQFqPA=="} ``` as you can see in this first requests `answers` and `questions` are null. @@ -121,7 +123,7 @@ Content-Length: 233 Content-Type: application/json Accept-Encoding: gzip -{"request_id":"bq1r5r7cdrpd2qtn25ng","username":"a","password":"$pbkdf2-sha512$150000$ClOPkLNujMTL$XktKy0xuJsOfMYBz+f2bIyPTdbvDTSnJ1q+7+zp/HPq5Qojwp6kcpSIiVHiwvbi8P6HFXI/D3UJv9BLcnQFqPA==","answers":["OK"],"questions":["Password: "]} +{"request_id":"bq1r5r7cdrpd2qtn25ng","username":"a","ip":"127.0.0.1","password":"$pbkdf2-sha512$150000$ClOPkLNujMTL$XktKy0xuJsOfMYBz+f2bIyPTdbvDTSnJ1q+7+zp/HPq5Qojwp6kcpSIiVHiwvbi8P6HFXI/D3UJv9BLcnQFqPA==","answers":["OK"],"questions":["Password: "]} ``` Here is the HTTP response that istructs SFTPGo to ask for a new question: @@ -147,7 +149,7 @@ Content-Length: 239 Content-Type: application/json Accept-Encoding: gzip -{"request_id":"bq1r5r7cdrpd2qtn25ng","username":"a","password":"$pbkdf2-sha512$150000$ClOPkLNujMTL$XktKy0xuJsOfMYBz+f2bIyPTdbvDTSnJ1q+7+zp/HPq5Qojwp6kcpSIiVHiwvbi8P6HFXI/D3UJv9BLcnQFqPA==","answers":["answer2"],"questions":["Question2: "]} +{"request_id":"bq1r5r7cdrpd2qtn25ng","username":"a","ip":"127.0.0.1","password":"$pbkdf2-sha512$150000$ClOPkLNujMTL$XktKy0xuJsOfMYBz+f2bIyPTdbvDTSnJ1q+7+zp/HPq5Qojwp6kcpSIiVHiwvbi8P6HFXI/D3UJv9BLcnQFqPA==","answers":["answer2"],"questions":["Question2: "]} ``` Here is the final HTTP response that allows the user login: diff --git a/ftpd/server.go b/ftpd/server.go index b7c349a4..713d7660 100644 --- a/ftpd/server.go +++ b/ftpd/server.go @@ -118,7 +118,7 @@ func (s *Server) ClientDisconnected(cc ftpserver.ClientContext) { // AuthUser authenticates the user and selects an handling driver func (s *Server) AuthUser(cc ftpserver.ClientContext, username, password string) (ftpserver.ClientDriver, error) { remoteAddr := cc.RemoteAddr().String() - user, err := dataprovider.CheckUserAndPass(username, password) + user, err := dataprovider.CheckUserAndPass(username, password, utils.GetIPFromRemoteAddress(remoteAddr)) if err != nil { updateLoginMetrics(username, remoteAddr, dataprovider.FTPLoginMethodPassword, err) return nil, err diff --git a/sftpd/server.go b/sftpd/server.go index ab1f6b8d..b104e95c 100644 --- a/sftpd/server.go +++ b/sftpd/server.go @@ -595,7 +595,8 @@ func (c Configuration) validatePublicKeyCredentials(conn ssh.ConnMetadata, pubKe } certPerm = &cert.Permissions } - if user, keyID, err = dataprovider.CheckUserAndPubKey(conn.User(), pubKey.Marshal()); err == nil { + ipAddr := utils.GetIPFromRemoteAddress(conn.RemoteAddr().String()) + if user, keyID, err = dataprovider.CheckUserAndPubKey(conn.User(), pubKey.Marshal(), ipAddr); err == nil { if user.IsPartialAuth(method) { logger.Debug(logSender, connectionID, "user %#v authenticated with partial success", conn.User()) return certPerm, ssh.ErrPartialSuccess @@ -625,7 +626,8 @@ func (c Configuration) validatePasswordCredentials(conn ssh.ConnMetadata, pass [ if len(conn.PartialSuccessMethods()) == 1 { method = dataprovider.SSHLoginMethodKeyAndPassword } - if user, err = dataprovider.CheckUserAndPass(conn.User(), string(pass)); err == nil { + ipAddr := utils.GetIPFromRemoteAddress(conn.RemoteAddr().String()) + if user, err = dataprovider.CheckUserAndPass(conn.User(), string(pass), ipAddr); err == nil { sshPerm, err = loginUser(user, method, "", conn) } updateLoginMetrics(conn, method, err) @@ -641,7 +643,8 @@ func (c Configuration) validateKeyboardInteractiveCredentials(conn ssh.ConnMetad if len(conn.PartialSuccessMethods()) == 1 { method = dataprovider.SSHLoginMethodKeyAndKeyboardInt } - if user, err = dataprovider.CheckKeyboardInteractiveAuth(conn.User(), c.KeyboardInteractiveHook, client); err == nil { + ipAddr := utils.GetIPFromRemoteAddress(conn.RemoteAddr().String()) + if user, err = dataprovider.CheckKeyboardInteractiveAuth(conn.User(), c.KeyboardInteractiveHook, client, ipAddr); err == nil { sshPerm, err = loginUser(user, method, "", conn) } updateLoginMetrics(conn, method, err)