Seafile server version 11.0.12 running on FreeBSD with WebDAV extension enabled.
The seafdav.log file records all client requests as coming from 127.0.0.1 (the Apache reverse proxy) rather than the real client IP address. Given that Seafile will be running behind an Apache or Nginx proxy 99% of the time, I can’t imagine any case where this would be at all useful. Expected behaviour is to look for and log the X-Forwarded-For header, but it does not.
This also matters because I want a Fail2Ban filter to protect the WebDAV service, and need the remote IP address.
I have this in the Apache virtual host:
<Location /seafdav>
ProxyPass http://127.0.0.1:8081/seafdav
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-For %{REMOTE_ADDR}s
</Location>
Apache supposedly sets X-Forwarded-For automatically when proxying, but I found it wasn’t, so I added it in the Location block.
By searching the code I found the routine responsible for logging the client requests is /usr/local/www/haiwen/seafile-server/seahub/thirdpart/wsgidav/wsgidav_app.py and patched it to add xff as shown below.
class WsgiDAVApp:
...
def __call__(self, environ, start_response):
# util.log(“SCRIPT_NAME={!r}, PATH_INFO={!r}”.format(
# environ.get(“SCRIPT_NAME”), environ.get(“PATH_INFO”)))
path = environ["PATH_INFO"]
# --- Added ---
xff = environ.get("HTTP_X_FORWARDED_FOR")
if xff:
environ["REMOTE_ADDR"] = xff.split(",")[0].strip()
# --- End ---
Now seafdav.log shows the real client IP address. I think this change should be added to the software.
Update:
While this fixes the issue of the client IP address, more needs to be done to properly protect SeafDAV with Fail2Ban…
Due to the default behaviour of WebDAV, valid clients will generate “401 Not Authorized” errors in the log before they authenticate. When processed by Fail2Ban, these false positives could block legitimate users. A more reliable indicator for Fail2Ban would be the “Authentication (basic) failed for user” messages, but these do not include the IP address.
The file /usr/local/www/haiwen/seafile-server/seahub/thirdpart/wsgidav/http_authenticator.py is responsible for logging authentication failures. The code was patched to read REMOTE_ADDR and append it to the log entries.
class HTTPAuthenticator(BaseMiddleware):
...
def handle_basic_auth_request(self, environ, start_response):
realm = self.domain_controller.get_domain_realm(environ["PATH_INFO"], environ)
auth_header = environ["HTTP_AUTHORIZATION"]
auth_value = ""
try:
auth_value = auth_header[len("Basic ") :].strip()
except Exception:
auth_value = ""
auth_value = base64.decodebytes(util.to_bytes(auth_value))
auth_value = util.to_str(auth_value)
user_name, password = auth_value.split(":", 1)
# --- Get client IP ---
client_ip = environ.get("REMOTE_ADDR", "unknown")
if self.domain_controller.basic_auth_user(realm, user_name, password, environ):
environ["wsgidav.auth.realm"] = realm
environ["wsgidav.auth.user_name"] = user_name
return self.next_app(environ, start_response)
# --- Append client IP ---
_logger.warning(
"Authentication (basic) failed for user {!r}, realm {!r}, IP {!r}.".format(
user_name, realm, client_ip
)
)
The seafdav.log can now be parsed with a Fail2Ban filter like this:
[Definition]
failregex = ^.*WARNING\s+:\s+Authentication \([^)]*\) failed for user '[^']*', realm '[^']*', IP '<HOST>'\.$
ignoreregex =
As Fail2Ban is recommended in the manual to protect Seafile, I think the SeafDAV code should be permanently fixed to make it work with Fail2Ban also.