mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 17:10:28 +00:00
Add client IP address to external auth, pre-login and keyboard interactive hooks
This commit is contained in:
parent
fa41bfd06a
commit
91dcc349de
10 changed files with 59 additions and 40 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 "<http_url>?login_method=password".
|
||||
// The login method and the ip address of the user trying to login are added to
|
||||
// the query string, for example "<http_url>?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)
|
||||
}
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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 `<http_url>?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 `<http_url>?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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue