mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-22 07:30:25 +00:00
parent
54321c5240
commit
46ab8f8d78
9 changed files with 107 additions and 127 deletions
|
@ -606,7 +606,7 @@ func (conns *ActiveConnections) checkIdles() {
|
||||||
logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTryed, c.GetProtocol(), "client idle")
|
logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTryed, c.GetProtocol(), "client idle")
|
||||||
metrics.AddNoAuthTryed()
|
metrics.AddNoAuthTryed()
|
||||||
AddDefenderEvent(ip, HostEventNoLoginTried)
|
AddDefenderEvent(ip, HostEventNoLoginTried)
|
||||||
dataprovider.ExecutePostLoginHook("", dataprovider.LoginMethodNoAuthTryed, ip, c.GetProtocol(),
|
dataprovider.ExecutePostLoginHook(&dataprovider.User{}, dataprovider.LoginMethodNoAuthTryed, ip, c.GetProtocol(),
|
||||||
dataprovider.ErrNoAuthTryed)
|
dataprovider.ErrNoAuthTryed)
|
||||||
}
|
}
|
||||||
}(c, isUnauthenticatedFTPUser)
|
}(c, isUnauthenticatedFTPUser)
|
||||||
|
|
|
@ -712,7 +712,7 @@ func UserExists(username string) (User, error) {
|
||||||
func AddUser(user *User) error {
|
func AddUser(user *User) error {
|
||||||
err := provider.addUser(user)
|
err := provider.addUser(user)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
go executeAction(operationAdd, *user)
|
executeAction(operationAdd, user)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -722,7 +722,7 @@ func UpdateUser(user *User) error {
|
||||||
err := provider.updateUser(user)
|
err := provider.updateUser(user)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
RemoveCachedWebDAVUser(user.Username)
|
RemoveCachedWebDAVUser(user.Username)
|
||||||
go executeAction(operationUpdate, *user)
|
executeAction(operationUpdate, user)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -736,7 +736,7 @@ func DeleteUser(username string) error {
|
||||||
err = provider.deleteUser(&user)
|
err = provider.deleteUser(&user)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
RemoveCachedWebDAVUser(user.Username)
|
RemoveCachedWebDAVUser(user.Username)
|
||||||
go executeAction(operationDelete, user)
|
executeAction(operationDelete, &user)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1970,7 +1970,7 @@ func executePreLoginHook(username, loginMethod, ip, protocol string) (User, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecutePostLoginHook executes the post login hook if defined
|
// ExecutePostLoginHook executes the post login hook if defined
|
||||||
func ExecutePostLoginHook(username, loginMethod, ip, protocol string, err error) {
|
func ExecutePostLoginHook(user *User, loginMethod, ip, protocol string, err error) {
|
||||||
if config.PostLoginHook == "" {
|
if config.PostLoginHook == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1981,10 +1981,17 @@ func ExecutePostLoginHook(username, loginMethod, ip, protocol string, err error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func(username, loginMethod, ip, protocol string, err error) {
|
go func() {
|
||||||
status := 0
|
status := "0"
|
||||||
if err == nil {
|
if err == nil {
|
||||||
status = 1
|
status = "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
user.HideConfidentialData()
|
||||||
|
userAsJSON, err := json.Marshal(user)
|
||||||
|
if err != nil {
|
||||||
|
providerLog(logger.LevelWarn, "error serializing user in post login hook: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(config.PostLoginHook, "http") {
|
if strings.HasPrefix(config.PostLoginHook, "http") {
|
||||||
var url *url.URL
|
var url *url.URL
|
||||||
|
@ -1993,22 +2000,17 @@ func ExecutePostLoginHook(username, loginMethod, ip, protocol string, err error)
|
||||||
providerLog(logger.LevelDebug, "Invalid post-login hook %#v", config.PostLoginHook)
|
providerLog(logger.LevelDebug, "Invalid post-login hook %#v", config.PostLoginHook)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
postReq := make(map[string]interface{})
|
q := url.Query()
|
||||||
postReq["username"] = username
|
q.Add("login_method", loginMethod)
|
||||||
postReq["login_method"] = loginMethod
|
q.Add("ip", ip)
|
||||||
postReq["ip"] = ip
|
q.Add("protocol", protocol)
|
||||||
postReq["protocol"] = protocol
|
q.Add("status", status)
|
||||||
postReq["status"] = status
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
postAsJSON, err := json.Marshal(postReq)
|
|
||||||
if err != nil {
|
|
||||||
providerLog(logger.LevelWarn, "error serializing post login request: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
respCode := 0
|
respCode := 0
|
||||||
httpClient := httpclient.GetHTTPClient()
|
httpClient := httpclient.GetHTTPClient()
|
||||||
resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(postAsJSON))
|
resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(userAsJSON))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
respCode = resp.StatusCode
|
respCode = resp.StatusCode
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
@ -2021,7 +2023,7 @@ func ExecutePostLoginHook(username, loginMethod, ip, protocol string, err error)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
cmd := exec.CommandContext(ctx, config.PostLoginHook)
|
cmd := exec.CommandContext(ctx, config.PostLoginHook)
|
||||||
cmd.Env = append(os.Environ(),
|
cmd.Env = append(os.Environ(),
|
||||||
fmt.Sprintf("SFTPGO_LOGIND_USER=%v", username),
|
fmt.Sprintf("SFTPGO_LOGIND_USER=%v", string(userAsJSON)),
|
||||||
fmt.Sprintf("SFTPGO_LOGIND_IP=%v", ip),
|
fmt.Sprintf("SFTPGO_LOGIND_IP=%v", ip),
|
||||||
fmt.Sprintf("SFTPGO_LOGIND_METHOD=%v", loginMethod),
|
fmt.Sprintf("SFTPGO_LOGIND_METHOD=%v", loginMethod),
|
||||||
fmt.Sprintf("SFTPGO_LOGIND_STATUS=%v", status),
|
fmt.Sprintf("SFTPGO_LOGIND_STATUS=%v", status),
|
||||||
|
@ -2029,7 +2031,7 @@ func ExecutePostLoginHook(username, loginMethod, ip, protocol string, err error)
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
providerLog(logger.LevelDebug, "post login hook executed, elapsed %v err: %v", time.Since(startTime), err)
|
providerLog(logger.LevelDebug, "post login hook executed, elapsed %v err: %v", time.Since(startTime), err)
|
||||||
}(username, loginMethod, ip, protocol, err)
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExternalAuthResponse(username, password, pkey, keyboardInteractive, ip, protocol string) ([]byte, error) {
|
func getExternalAuthResponse(username, password, pkey, keyboardInteractive, ip, protocol string) ([]byte, error) {
|
||||||
|
@ -2130,17 +2132,21 @@ func providerLog(level logger.LogLevel, format string, v ...interface{}) {
|
||||||
logger.Log(level, logSender, "", format, v...)
|
logger.Log(level, logSender, "", format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func executeNotificationCommand(operation string, user User) error {
|
func executeNotificationCommand(operation string, commandArgs []string, userAsJSON []byte) error {
|
||||||
if !filepath.IsAbs(config.Actions.Hook) {
|
if !filepath.IsAbs(config.Actions.Hook) {
|
||||||
err := fmt.Errorf("invalid notification command %#v", config.Actions.Hook)
|
err := fmt.Errorf("invalid notification command %#v", config.Actions.Hook)
|
||||||
logger.Warn(logSender, "", "unable to execute notification command: %v", err)
|
logger.Warn(logSender, "", "unable to execute notification command: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
commandArgs := user.getNotificationFieldsAsSlice(operation)
|
|
||||||
cmd := exec.CommandContext(ctx, config.Actions.Hook, commandArgs...)
|
cmd := exec.CommandContext(ctx, config.Actions.Hook, commandArgs...)
|
||||||
cmd.Env = append(os.Environ(), user.getNotificationFieldsAsEnvVars(operation)...)
|
cmd.Env = append(os.Environ(),
|
||||||
|
fmt.Sprintf("SFTPGO_USER_ACTION=%v", operation),
|
||||||
|
fmt.Sprintf("SFTPGO_USER=%v", string(userAsJSON)))
|
||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
providerLog(logger.LevelDebug, "executed command %#v with arguments: %+v, elapsed: %v, error: %v",
|
providerLog(logger.LevelDebug, "executed command %#v with arguments: %+v, elapsed: %v, error: %v",
|
||||||
|
@ -2148,50 +2154,54 @@ func executeNotificationCommand(operation string, user User) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// executed in a goroutine
|
func executeAction(operation string, user *User) {
|
||||||
func executeAction(operation string, user User) {
|
|
||||||
if !utils.IsStringInSlice(operation, config.Actions.ExecuteOn) {
|
if !utils.IsStringInSlice(operation, config.Actions.ExecuteOn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(config.Actions.Hook) == 0 {
|
if config.Actions.Hook == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if operation != operationDelete {
|
|
||||||
var err error
|
go func() {
|
||||||
user, err = provider.userExists(user.Username)
|
if operation != operationDelete {
|
||||||
if err != nil {
|
var err error
|
||||||
providerLog(logger.LevelWarn, "unable to get the user to notify for operation %#v: %v", operation, err)
|
u, err := provider.userExists(user.Username)
|
||||||
return
|
if err != nil {
|
||||||
|
providerLog(logger.LevelWarn, "unable to get the user to notify for operation %#v: %v", operation, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user = &u
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if strings.HasPrefix(config.Actions.Hook, "http") {
|
|
||||||
var url *url.URL
|
|
||||||
url, err := url.Parse(config.Actions.Hook)
|
|
||||||
if err != nil {
|
|
||||||
providerLog(logger.LevelWarn, "Invalid http_notification_url %#v for operation %#v: %v", config.Actions.Hook, operation, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
q := url.Query()
|
|
||||||
q.Add("action", operation)
|
|
||||||
url.RawQuery = q.Encode()
|
|
||||||
user.HideConfidentialData()
|
user.HideConfidentialData()
|
||||||
userAsJSON, err := json.Marshal(user)
|
userAsJSON, err := json.Marshal(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
providerLog(logger.LevelWarn, "unable to serialize user as JSON for operation %#v: %v", operation, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
startTime := time.Now()
|
if strings.HasPrefix(config.Actions.Hook, "http") {
|
||||||
httpClient := httpclient.GetHTTPClient()
|
var url *url.URL
|
||||||
resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(userAsJSON))
|
url, err := url.Parse(config.Actions.Hook)
|
||||||
respCode := 0
|
if err != nil {
|
||||||
if err == nil {
|
providerLog(logger.LevelWarn, "Invalid http_notification_url %#v for operation %#v: %v", config.Actions.Hook, operation, err)
|
||||||
respCode = resp.StatusCode
|
return
|
||||||
resp.Body.Close()
|
}
|
||||||
|
q := url.Query()
|
||||||
|
q.Add("action", operation)
|
||||||
|
url.RawQuery = q.Encode()
|
||||||
|
startTime := time.Now()
|
||||||
|
httpClient := httpclient.GetHTTPClient()
|
||||||
|
resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(userAsJSON))
|
||||||
|
respCode := 0
|
||||||
|
if err == nil {
|
||||||
|
respCode = resp.StatusCode
|
||||||
|
resp.Body.Close()
|
||||||
|
}
|
||||||
|
providerLog(logger.LevelDebug, "notified operation %#v to URL: %v status code: %v, elapsed: %v err: %v",
|
||||||
|
operation, url.String(), respCode, time.Since(startTime), err)
|
||||||
|
} else {
|
||||||
|
executeNotificationCommand(operation, user.getNotificationFieldsAsSlice(operation), userAsJSON) //nolint:errcheck // the error is used in test cases only
|
||||||
}
|
}
|
||||||
providerLog(logger.LevelDebug, "notified operation %#v to URL: %v status code: %v, elapsed: %v err: %v",
|
}()
|
||||||
operation, url.String(), respCode, time.Since(startTime), err)
|
|
||||||
} else {
|
|
||||||
executeNotificationCommand(operation, user) //nolint:errcheck // the error is used in test cases only
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// after migrating database to v4 we have to update the quota for the imported folders
|
// after migrating database to v4 we have to update the quota for the imported folders
|
||||||
|
|
|
@ -955,24 +955,6 @@ func (u *User) getNotificationFieldsAsSlice(action string) []string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) getNotificationFieldsAsEnvVars(action string) []string {
|
|
||||||
return []string{fmt.Sprintf("SFTPGO_USER_ACTION=%v", action),
|
|
||||||
fmt.Sprintf("SFTPGO_USER_USERNAME=%v", u.Username),
|
|
||||||
fmt.Sprintf("SFTPGO_USER_PASSWORD=%v", u.Password),
|
|
||||||
fmt.Sprintf("SFTPGO_USER_ID=%v", u.ID),
|
|
||||||
fmt.Sprintf("SFTPGO_USER_STATUS=%v", u.Status),
|
|
||||||
fmt.Sprintf("SFTPGO_USER_EXPIRATION_DATE=%v", u.ExpirationDate),
|
|
||||||
fmt.Sprintf("SFTPGO_USER_HOME_DIR=%v", u.HomeDir),
|
|
||||||
fmt.Sprintf("SFTPGO_USER_UID=%v", u.UID),
|
|
||||||
fmt.Sprintf("SFTPGO_USER_GID=%v", u.GID),
|
|
||||||
fmt.Sprintf("SFTPGO_USER_QUOTA_FILES=%v", u.QuotaFiles),
|
|
||||||
fmt.Sprintf("SFTPGO_USER_QUOTA_SIZE=%v", u.QuotaSize),
|
|
||||||
fmt.Sprintf("SFTPGO_USER_UPLOAD_BANDWIDTH=%v", u.UploadBandwidth),
|
|
||||||
fmt.Sprintf("SFTPGO_USER_DOWNLOAD_BANDWIDTH=%v", u.DownloadBandwidth),
|
|
||||||
fmt.Sprintf("SFTPGO_USER_MAX_SESSIONS=%v", u.MaxSessions),
|
|
||||||
fmt.Sprintf("SFTPGO_USER_FS_PROVIDER=%v", u.FsConfig.Provider)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) getGCSCredentialsFilePath() string {
|
func (u *User) getGCSCredentialsFilePath() string {
|
||||||
return filepath.Join(credentialsDirPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username))
|
return filepath.Join(credentialsDirPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username))
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,20 +66,7 @@ If the `hook` defines a path to an external program, then this program is invoke
|
||||||
The external program can also read the following environment variables:
|
The external program can also read the following environment variables:
|
||||||
|
|
||||||
- `SFTPGO_USER_ACTION`
|
- `SFTPGO_USER_ACTION`
|
||||||
- `SFTPGO_USER_USERNAME`
|
- `SFTPGO_USER`, user serialized as JSON with sensitive fields removed
|
||||||
- `SFTPGO_USER_PASSWORD`, hashed password as stored inside the data provider, can be empty if the user does not login using a password
|
|
||||||
- `SFTPGO_USER_ID`
|
|
||||||
- `SFTPGO_USER_STATUS`
|
|
||||||
- `SFTPGO_USER_EXPIRATION_DATE`
|
|
||||||
- `SFTPGO_USER_HOME_DIR`
|
|
||||||
- `SFTPGO_USER_UID`
|
|
||||||
- `SFTPGO_USER_GID`
|
|
||||||
- `SFTPGO_USER_QUOTA_FILES`
|
|
||||||
- `SFTPGO_USER_QUOTA_SIZE`
|
|
||||||
- `SFTPGO_USER_UPLOAD_BANDWIDTH`
|
|
||||||
- `SFTPGO_USER_DOWNLOAD_BANDWIDTH`
|
|
||||||
- `SFTPGO_USER_MAX_SESSIONS`
|
|
||||||
- `SFTPGO_USER_FS_PROVIDER`
|
|
||||||
|
|
||||||
Previous global environment variables aren't cleared when the script is called.
|
Previous global environment variables aren't cleared when the script is called.
|
||||||
The program must finish within 15 seconds.
|
The program must finish within 15 seconds.
|
||||||
|
|
|
@ -2,15 +2,13 @@
|
||||||
|
|
||||||
This hook is executed after a login or after closing a connection for authentication timeout. Defining an appropriate `post_login_scope` you can get notifications for failed logins, successful logins or both.
|
This hook is executed after a login or after closing a connection for authentication timeout. Defining an appropriate `post_login_scope` you can get notifications for failed logins, successful logins or both.
|
||||||
|
|
||||||
Combining this hook with the [Post-connect hook](./post-connect-hook.md) you can implement your own (even for Protocol) blacklist/whitelist of IP addresses.
|
Please keep in mind that executing a hook after each login can be heavy.
|
||||||
|
|
||||||
Please keep in mind that you can easily configure specialized program such as [Fail2ban](http://www.fail2ban.org/) for brute force protection. Executing a hook after each login can be heavy.
|
|
||||||
|
|
||||||
The `post-login-hook` can be defined as the absolute path of your program or an HTTP URL.
|
The `post-login-hook` can be defined as the absolute path of your program or an HTTP URL.
|
||||||
|
|
||||||
If the hook defines an external program it can reads the following environment variables:
|
If the hook defines an external program it can reads the following environment variables:
|
||||||
|
|
||||||
- `SFTPGO_LOGIND_USER`, username, can be empty if the connection is closed for authentication timeout
|
- `SFTPGO_LOGIND_USER`, it contains the user serialized as JSON. The username is empty if the connection is closed for authentication timeout
|
||||||
- `SFTPGO_LOGIND_IP`
|
- `SFTPGO_LOGIND_IP`
|
||||||
- `SFTPGO_LOGIND_METHOD`, possible values are `publickey`, `password`, `keyboard-interactive`, `publickey+password`, `publickey+keyboard-interactive` or `no_auth_tryed`
|
- `SFTPGO_LOGIND_METHOD`, possible values are `publickey`, `password`, `keyboard-interactive`, `publickey+password`, `publickey+keyboard-interactive` or `no_auth_tryed`
|
||||||
- `SFTPGO_LOGIND_STATUS`, 1 means login OK, 0 login KO
|
- `SFTPGO_LOGIND_STATUS`, 1 means login OK, 0 login KO
|
||||||
|
@ -19,18 +17,13 @@ If the hook defines an external program it can reads the following environment v
|
||||||
Previous global environment variables aren't cleared when the script is called.
|
Previous global environment variables aren't cleared when the script is called.
|
||||||
The program must finish within 20 seconds.
|
The program must finish within 20 seconds.
|
||||||
|
|
||||||
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:
|
If the hook is an HTTP URL then it will be invoked as HTTP POST. The login method, the used protocol, the ip address and the status of the user are added to the query string, for example `<http_url>?login_method=password&ip=1.2.3.4&protocol=SSH&status=1`.
|
||||||
|
The request body will contain the user serialized as JSON.
|
||||||
- `username`
|
|
||||||
- `login_method`
|
|
||||||
- `ip`
|
|
||||||
- `protocol`
|
|
||||||
- `status`
|
|
||||||
|
|
||||||
The HTTP request will use the global configuration for HTTP clients.
|
The HTTP request will use the global configuration for HTTP clients.
|
||||||
|
|
||||||
The `post_login_scope` supports the following configuration values:
|
The `post_login_scope` supports the following configuration values:
|
||||||
|
|
||||||
- `0` means notify both failed and successful logins
|
- `0` means notify both failed and successful logins
|
||||||
- `1` means notify failed logins. Connections closed for authentication timeout are notified as failed connections. You will get an empty username in this case
|
- `1` means notify failed logins. Connections closed for authentication timeout are notified as failed logins. You will get an empty username in this case
|
||||||
- `2` means notify successful logins
|
- `2` means notify successful logins
|
||||||
|
|
|
@ -137,13 +137,14 @@ func (s *Server) AuthUser(cc ftpserver.ClientContext, username, password string)
|
||||||
ipAddr := utils.GetIPFromRemoteAddress(cc.RemoteAddr().String())
|
ipAddr := utils.GetIPFromRemoteAddress(cc.RemoteAddr().String())
|
||||||
user, err := dataprovider.CheckUserAndPass(username, password, ipAddr, common.ProtocolFTP)
|
user, err := dataprovider.CheckUserAndPass(username, password, ipAddr, common.ProtocolFTP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
updateLoginMetrics(username, ipAddr, err)
|
user.Username = username
|
||||||
|
updateLoginMetrics(&user, ipAddr, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
connection, err := s.validateUser(user, cc)
|
connection, err := s.validateUser(user, cc)
|
||||||
|
|
||||||
defer updateLoginMetrics(username, ipAddr, err)
|
defer updateLoginMetrics(&user, ipAddr, err)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -247,10 +248,10 @@ func (s *Server) validateUser(user dataprovider.User, cc ftpserver.ClientContext
|
||||||
return connection, nil
|
return connection, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLoginMetrics(username, ip string, err error) {
|
func updateLoginMetrics(user *dataprovider.User, ip string, err error) {
|
||||||
metrics.AddLoginAttempt(dataprovider.LoginMethodPassword)
|
metrics.AddLoginAttempt(dataprovider.LoginMethodPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ConnectionFailedLog(username, ip, dataprovider.LoginMethodPassword,
|
logger.ConnectionFailedLog(user.Username, ip, dataprovider.LoginMethodPassword,
|
||||||
common.ProtocolFTP, err.Error())
|
common.ProtocolFTP, err.Error())
|
||||||
event := common.HostEventLoginFailed
|
event := common.HostEventLoginFailed
|
||||||
if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
|
if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
|
||||||
|
@ -259,5 +260,5 @@ func updateLoginMetrics(username, ip string, err error) {
|
||||||
common.AddDefenderEvent(ip, event)
|
common.AddDefenderEvent(ip, event)
|
||||||
}
|
}
|
||||||
metrics.AddLoginResult(dataprovider.LoginMethodPassword, err)
|
metrics.AddLoginResult(dataprovider.LoginMethodPassword, err)
|
||||||
dataprovider.ExecutePostLoginHook(username, dataprovider.LoginMethodPassword, ip, common.ProtocolFTP, err)
|
dataprovider.ExecutePostLoginHook(user, dataprovider.LoginMethodPassword, ip, common.ProtocolFTP, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -534,7 +534,7 @@ func checkAuthError(ip string, err error) {
|
||||||
logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTryed, common.ProtocolSSH, err.Error())
|
logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTryed, common.ProtocolSSH, err.Error())
|
||||||
metrics.AddNoAuthTryed()
|
metrics.AddNoAuthTryed()
|
||||||
common.AddDefenderEvent(ip, common.HostEventNoLoginTried)
|
common.AddDefenderEvent(ip, common.HostEventNoLoginTried)
|
||||||
dataprovider.ExecutePostLoginHook("", dataprovider.LoginMethodNoAuthTryed, ip, common.ProtocolSSH, err)
|
dataprovider.ExecutePostLoginHook(&dataprovider.User{}, dataprovider.LoginMethodNoAuthTryed, ip, common.ProtocolSSH, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -791,16 +791,19 @@ func (c *Configuration) validatePublicKeyCredentials(conn ssh.ConnMetadata, pubK
|
||||||
if ok {
|
if ok {
|
||||||
if cert.CertType != ssh.UserCert {
|
if cert.CertType != ssh.UserCert {
|
||||||
err = fmt.Errorf("ssh: cert has type %d", cert.CertType)
|
err = fmt.Errorf("ssh: cert has type %d", cert.CertType)
|
||||||
updateLoginMetrics(conn, ipAddr, method, err)
|
user.Username = conn.User()
|
||||||
|
updateLoginMetrics(&user, ipAddr, method, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !c.certChecker.IsUserAuthority(cert.SignatureKey) {
|
if !c.certChecker.IsUserAuthority(cert.SignatureKey) {
|
||||||
err = fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
err = fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
||||||
updateLoginMetrics(conn, ipAddr, method, err)
|
user.Username = conn.User()
|
||||||
|
updateLoginMetrics(&user, ipAddr, method, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := c.certChecker.CheckCert(conn.User(), cert); err != nil {
|
if err := c.certChecker.CheckCert(conn.User(), cert); err != nil {
|
||||||
updateLoginMetrics(conn, ipAddr, method, err)
|
user.Username = conn.User()
|
||||||
|
updateLoginMetrics(&user, ipAddr, method, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
certPerm = &cert.Permissions
|
certPerm = &cert.Permissions
|
||||||
|
@ -822,7 +825,8 @@ func (c *Configuration) validatePublicKeyCredentials(conn ssh.ConnMetadata, pubK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateLoginMetrics(conn, ipAddr, method, err)
|
user.Username = conn.User()
|
||||||
|
updateLoginMetrics(&user, ipAddr, method, err)
|
||||||
return sshPerm, err
|
return sshPerm, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -839,7 +843,8 @@ func (c *Configuration) validatePasswordCredentials(conn ssh.ConnMetadata, pass
|
||||||
if user, err = dataprovider.CheckUserAndPass(conn.User(), string(pass), ipAddr, common.ProtocolSSH); err == nil {
|
if user, err = dataprovider.CheckUserAndPass(conn.User(), string(pass), ipAddr, common.ProtocolSSH); err == nil {
|
||||||
sshPerm, err = loginUser(user, method, "", conn)
|
sshPerm, err = loginUser(user, method, "", conn)
|
||||||
}
|
}
|
||||||
updateLoginMetrics(conn, ipAddr, method, err)
|
user.Username = conn.User()
|
||||||
|
updateLoginMetrics(&user, ipAddr, method, err)
|
||||||
return sshPerm, err
|
return sshPerm, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -857,14 +862,15 @@ func (c *Configuration) validateKeyboardInteractiveCredentials(conn ssh.ConnMeta
|
||||||
ipAddr, common.ProtocolSSH); err == nil {
|
ipAddr, common.ProtocolSSH); err == nil {
|
||||||
sshPerm, err = loginUser(user, method, "", conn)
|
sshPerm, err = loginUser(user, method, "", conn)
|
||||||
}
|
}
|
||||||
updateLoginMetrics(conn, ipAddr, method, err)
|
user.Username = conn.User()
|
||||||
|
updateLoginMetrics(&user, ipAddr, method, err)
|
||||||
return sshPerm, err
|
return sshPerm, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLoginMetrics(conn ssh.ConnMetadata, ip, method string, err error) {
|
func updateLoginMetrics(user *dataprovider.User, ip, method string, err error) {
|
||||||
metrics.AddLoginAttempt(method)
|
metrics.AddLoginAttempt(method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ConnectionFailedLog(conn.User(), ip, method, common.ProtocolSSH, err.Error())
|
logger.ConnectionFailedLog(user.Username, ip, method, common.ProtocolSSH, err.Error())
|
||||||
if method != dataprovider.SSHLoginMethodPublicKey {
|
if method != dataprovider.SSHLoginMethodPublicKey {
|
||||||
// some clients try all available public keys for a user, we
|
// some clients try all available public keys for a user, we
|
||||||
// record failed login key auth only once for session if the
|
// record failed login key auth only once for session if the
|
||||||
|
@ -877,5 +883,5 @@ func updateLoginMetrics(conn ssh.ConnMetadata, ip, method string, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metrics.AddLoginResult(method, err)
|
metrics.AddLoginResult(method, err)
|
||||||
dataprovider.ExecutePostLoginHook(conn.User(), method, ip, common.ProtocolSSH, err)
|
dataprovider.ExecutePostLoginHook(user, method, ip, common.ProtocolSSH, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -379,7 +379,7 @@ func TestOrderDirsToRemove(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserInvalidParams(t *testing.T) {
|
func TestUserInvalidParams(t *testing.T) {
|
||||||
u := dataprovider.User{
|
u := &dataprovider.User{
|
||||||
Username: "username",
|
Username: "username",
|
||||||
HomeDir: "invalid",
|
HomeDir: "invalid",
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,21 +154,21 @@ func (s *webDavServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionID, err := s.validateUser(user, r)
|
connectionID, err := s.validateUser(&user, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
updateLoginMetrics(user.Username, ipAddr, err)
|
updateLoginMetrics(&user, ipAddr, err)
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fs, err := user.GetFilesystem(connectionID)
|
fs, err := user.GetFilesystem(connectionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
updateLoginMetrics(user.Username, ipAddr, err)
|
updateLoginMetrics(&user, ipAddr, err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLoginMetrics(user.Username, ipAddr, err)
|
updateLoginMetrics(&user, ipAddr, err)
|
||||||
|
|
||||||
ctx := context.WithValue(r.Context(), requestIDKey, connectionID)
|
ctx := context.WithValue(r.Context(), requestIDKey, connectionID)
|
||||||
ctx = context.WithValue(ctx, requestStartKey, time.Now())
|
ctx = context.WithValue(ctx, requestStartKey, time.Now())
|
||||||
|
@ -210,13 +210,14 @@ func (s *webDavServer) authenticate(r *http.Request, ip string) (dataprovider.Us
|
||||||
if password != "" && cachedUser.Password == password {
|
if password != "" && cachedUser.Password == password {
|
||||||
return cachedUser.User, true, cachedUser.LockSystem, nil
|
return cachedUser.User, true, cachedUser.LockSystem, nil
|
||||||
}
|
}
|
||||||
updateLoginMetrics(username, ip, dataprovider.ErrInvalidCredentials)
|
updateLoginMetrics(&cachedUser.User, ip, dataprovider.ErrInvalidCredentials)
|
||||||
return user, false, nil, dataprovider.ErrInvalidCredentials
|
return user, false, nil, dataprovider.ErrInvalidCredentials
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
user, err = dataprovider.CheckUserAndPass(username, password, ip, common.ProtocolWebDAV)
|
user, err = dataprovider.CheckUserAndPass(username, password, ip, common.ProtocolWebDAV)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
updateLoginMetrics(username, ip, err)
|
user.Username = username
|
||||||
|
updateLoginMetrics(&user, ip, err)
|
||||||
return user, false, nil, err
|
return user, false, nil, err
|
||||||
}
|
}
|
||||||
lockSystem := webdav.NewMemLS()
|
lockSystem := webdav.NewMemLS()
|
||||||
|
@ -242,7 +243,7 @@ func (s *webDavServer) authenticate(r *http.Request, ip string) (dataprovider.Us
|
||||||
return user, false, lockSystem, nil
|
return user, false, lockSystem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *webDavServer) validateUser(user dataprovider.User, r *http.Request) (string, error) {
|
func (s *webDavServer) validateUser(user *dataprovider.User, r *http.Request) (string, error) {
|
||||||
connID := xid.New().String()
|
connID := xid.New().String()
|
||||||
connectionID := fmt.Sprintf("%v_%v", common.ProtocolWebDAV, connID)
|
connectionID := fmt.Sprintf("%v_%v", common.ProtocolWebDAV, connID)
|
||||||
|
|
||||||
|
@ -332,10 +333,10 @@ func checkRemoteAddress(r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLoginMetrics(username, ip string, err error) {
|
func updateLoginMetrics(user *dataprovider.User, ip string, err error) {
|
||||||
metrics.AddLoginAttempt(dataprovider.LoginMethodPassword)
|
metrics.AddLoginAttempt(dataprovider.LoginMethodPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ConnectionFailedLog(username, ip, dataprovider.LoginMethodPassword, common.ProtocolWebDAV, err.Error())
|
logger.ConnectionFailedLog(user.Username, ip, dataprovider.LoginMethodPassword, common.ProtocolWebDAV, err.Error())
|
||||||
event := common.HostEventLoginFailed
|
event := common.HostEventLoginFailed
|
||||||
if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
|
if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
|
||||||
event = common.HostEventUserNotFound
|
event = common.HostEventUserNotFound
|
||||||
|
@ -343,5 +344,5 @@ func updateLoginMetrics(username, ip string, err error) {
|
||||||
common.AddDefenderEvent(ip, event)
|
common.AddDefenderEvent(ip, event)
|
||||||
}
|
}
|
||||||
metrics.AddLoginResult(dataprovider.LoginMethodPassword, err)
|
metrics.AddLoginResult(dataprovider.LoginMethodPassword, err)
|
||||||
dataprovider.ExecutePostLoginHook(username, dataprovider.LoginMethodPassword, ip, common.ProtocolWebDAV, err)
|
dataprovider.ExecutePostLoginHook(user, dataprovider.LoginMethodPassword, ip, common.ProtocolWebDAV, err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue