From abb9cffe4a9b36c124b1ef8fad4a00eff4ba36de Mon Sep 17 00:00:00 2001
From: Eric Lesiuta <elesiuta@gmail.com>
Date: Tue, 8 Dec 2020 12:54:33 -0500
Subject: [PATCH] Log IP on SSH authentication failure for Built-in SSH server
 (#13150)

* Log IP on SSH authentication failure

fixes https://github.com/go-gitea/gitea/issues/13094

* include string 'Failed authentication attempt' in error

* update fail2ban docs

also match failed authentication over command line

* better logging of authentication errors with IP addresses

* format ...

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: 6543 <6543@obermui.de>
---
 docs/content/doc/usage/fail2ban-setup.en-us.md | 10 ++++++++--
 modules/ssh/ssh.go                             |  2 +-
 routers/private/serv.go                        |  2 ++
 routers/repo/http.go                           |  1 +
 4 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/docs/content/doc/usage/fail2ban-setup.en-us.md b/docs/content/doc/usage/fail2ban-setup.en-us.md
index ad99ca9e28..42b61416be 100644
--- a/docs/content/doc/usage/fail2ban-setup.en-us.md
+++ b/docs/content/doc/usage/fail2ban-setup.en-us.md
@@ -20,18 +20,24 @@ sure to test this before relying on it so you don't lock yourself out.**
 
 Gitea returns an HTTP 200 for bad logins in the web logs, but if you have logging options on in
 `app.ini`, then you should be able to go off of `log/gitea.log`, which gives you something like this
-on a bad authentication:
+on a bad authentication from the web or CLI using SSH or HTTP respectively:
 
 ```log
 2018/04/26 18:15:54 [I] Failed authentication attempt for user from xxx.xxx.xxx.xxx
 ```
+```log
+2020/10/15 16:05:09 modules/ssh/ssh.go:188:publicKeyHandler() [E] SearchPublicKeyByContent: public key does not exist [id: 0] Failed authentication attempt from xxx.xxx.xxx.xxx
+```
+```log
+2020/10/15 16:08:44 ...s/context/context.go:204:HandleText() [E] invalid credentials from xxx.xxx.xxx.xxx
+```
 
 Add our filter in `/etc/fail2ban/filter.d/gitea.conf`:
 
 ```ini
 # gitea.conf
 [Definition]
-failregex =  .*Failed authentication attempt for .* from <HOST>
+failregex =  .*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from <HOST>
 ignoreregex =
 ```
 
diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go
index 761164caa0..9bfa39ef42 100644
--- a/modules/ssh/ssh.go
+++ b/modules/ssh/ssh.go
@@ -186,7 +186,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
 
 	pkey, err := models.SearchPublicKeyByContent(strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key))))
 	if err != nil {
-		log.Error("SearchPublicKeyByContent: %v", err)
+		log.Error("SearchPublicKeyByContent: %v Failed authentication attempt from %s", err, ctx.RemoteAddr())
 		return false
 	}
 
diff --git a/routers/private/serv.go b/routers/private/serv.go
index 182fe27245..90e1d30b01 100644
--- a/routers/private/serv.go
+++ b/routers/private/serv.go
@@ -132,6 +132,7 @@ func ServCommand(ctx *macaron.Context) {
 			for _, verb := range ctx.QueryStrings("verb") {
 				if "git-upload-pack" == verb {
 					// User is fetching/cloning a non-existent repository
+					log.Error("Failed authentication attempt (cannot find repository: %s/%s) from %s", results.OwnerName, results.RepoName, ctx.RemoteAddr())
 					ctx.JSON(http.StatusNotFound, map[string]interface{}{
 						"results": results,
 						"type":    "ErrRepoNotExist",
@@ -317,6 +318,7 @@ func ServCommand(ctx *macaron.Context) {
 			userMode := perm.UnitAccessMode(unitType)
 
 			if userMode < mode {
+				log.Error("Failed authentication attempt for %s with key %s (not authorized to %s %s/%s) from %s", user.Name, key.Name, modeString, ownerName, repoName, ctx.RemoteAddr())
 				ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
 					"results": results,
 					"type":    "ErrUnauthorized",
diff --git a/routers/repo/http.go b/routers/repo/http.go
index 40c1f36bc3..d4464ec62e 100644
--- a/routers/repo/http.go
+++ b/routers/repo/http.go
@@ -102,6 +102,7 @@ func HTTP(ctx *context.Context) {
 
 	owner, err := models.GetUserByName(username)
 	if err != nil {
+		log.Error("Attempted access of unknown user from %s", ctx.RemoteAddr())
 		ctx.NotFoundOrServerError("GetUserByName", models.IsErrUserNotExist, err)
 		return
 	}