New: Added Auth-* log entries for fail2ban purposes

closes #2760
This commit is contained in:
Taloth Saldono 2019-08-27 23:29:16 +02:00
parent 2238ac5d17
commit 95ee7daf21
4 changed files with 128 additions and 7 deletions

View File

@ -57,6 +57,8 @@ namespace NzbDrone.Common.Instrumentation
RegisterAppFile(appFolderInfo); RegisterAppFile(appFolderInfo);
} }
RegisterAuthLogger();
LogManager.ReconfigExistingLoggers(); LogManager.ReconfigExistingLoggers();
} }
@ -195,6 +197,23 @@ namespace NzbDrone.Common.Instrumentation
LogManager.Configuration.LoggingRules.Add(loggingRule); LogManager.Configuration.LoggingRules.Add(loggingRule);
} }
private static void RegisterAuthLogger()
{
var consoleTarget = LogManager.Configuration.FindTargetByName("console");
var fileTarget = LogManager.Configuration.FindTargetByName("appFileInfo");
var target = consoleTarget ?? fileTarget ?? new NullTarget();
// Send Auth to Console and info app file, but not the log database
var rule = new LoggingRule("Auth", LogLevel.Info, target) { Final = true };
if (consoleTarget != null && fileTarget != null)
{
rule.Targets.Add(fileTarget);
}
LogManager.Configuration.LoggingRules.Insert(0, rule);
}
public static Logger GetLogger(Type obj) public static Logger GetLogger(Type obj)
{ {
return LogManager.GetLogger(obj.Name.Replace("NzbDrone.", "")); return LogManager.GetLogger(obj.Name.Replace("NzbDrone.", ""));

View File

@ -3,6 +3,8 @@ using Nancy;
using Nancy.Authentication.Forms; using Nancy.Authentication.Forms;
using Nancy.Extensions; using Nancy.Extensions;
using Nancy.ModelBinding; using Nancy.ModelBinding;
using NLog;
using NzbDrone.Common.Instrumentation;
using NzbDrone.Core.Authentication; using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
@ -10,12 +12,12 @@ namespace Sonarr.Http.Authentication
{ {
public class AuthenticationModule : NancyModule public class AuthenticationModule : NancyModule
{ {
private readonly IUserService _userService; private readonly IAuthenticationService _authService;
private readonly IConfigFileProvider _configFileProvider; private readonly IConfigFileProvider _configFileProvider;
public AuthenticationModule(IUserService userService, IConfigFileProvider configFileProvider) public AuthenticationModule(IAuthenticationService authService, IConfigFileProvider configFileProvider)
{ {
_userService = userService; _authService = authService;
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
Post["/login"] = x => Login(this.Bind<LoginResource>()); Post["/login"] = x => Login(this.Bind<LoginResource>());
Get["/logout"] = x => Logout(); Get["/logout"] = x => Logout();
@ -23,7 +25,7 @@ namespace Sonarr.Http.Authentication
private Response Login(LoginResource resource) private Response Login(LoginResource resource)
{ {
var user = _userService.FindUser(resource.Username, resource.Password); var user = _authService.Login(Context, resource.Username, resource.Password);
if (user == null) if (user == null)
{ {
@ -43,6 +45,8 @@ namespace Sonarr.Http.Authentication
private Response Logout() private Response Logout()
{ {
_authService.Logout(Context);
return this.LogoutAndRedirect(_configFileProvider.UrlBase + "/"); return this.LogoutAndRedirect(_configFileProvider.UrlBase + "/");
} }
} }

View File

@ -4,7 +4,9 @@ using Nancy;
using Nancy.Authentication.Basic; using Nancy.Authentication.Basic;
using Nancy.Authentication.Forms; using Nancy.Authentication.Forms;
using Nancy.Security; using Nancy.Security;
using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation;
using NzbDrone.Core.Authentication; using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using Sonarr.Http.Extensions; using Sonarr.Http.Extensions;
@ -13,24 +15,75 @@ namespace Sonarr.Http.Authentication
{ {
public interface IAuthenticationService : IUserValidator, IUserMapper public interface IAuthenticationService : IUserValidator, IUserMapper
{ {
void SetContext(NancyContext context);
void LogUnauthorized(NancyContext context);
User Login(NancyContext context, string username, string password);
void Logout(NancyContext context);
bool IsAuthenticated(NancyContext context); bool IsAuthenticated(NancyContext context);
} }
public class AuthenticationService : IAuthenticationService public class AuthenticationService : IAuthenticationService
{ {
private readonly IUserService _userService; private static readonly Logger _authLogger = LogManager.GetLogger("Auth");
private static readonly NzbDroneUser AnonymousUser = new NzbDroneUser { UserName = "Anonymous" }; private static readonly NzbDroneUser AnonymousUser = new NzbDroneUser { UserName = "Anonymous" };
private readonly IUserService _userService;
private readonly NancyContext _nancyContext;
private static string API_KEY; private static string API_KEY;
private static AuthenticationType AUTH_METHOD; private static AuthenticationType AUTH_METHOD;
public AuthenticationService(IConfigFileProvider configFileProvider, IUserService userService) [ThreadStatic]
private static NancyContext _context;
public AuthenticationService(IConfigFileProvider configFileProvider, IUserService userService, NancyContext nancyContext)
{ {
_userService = userService; _userService = userService;
_nancyContext = nancyContext;
API_KEY = configFileProvider.ApiKey; API_KEY = configFileProvider.ApiKey;
AUTH_METHOD = configFileProvider.AuthenticationMethod; AUTH_METHOD = configFileProvider.AuthenticationMethod;
} }
public void SetContext(NancyContext context)
{
// Validate and GetUserIdentifier don't have access to the NancyContext so get it from the pipeline earlier
_context = context;
}
public User Login(NancyContext context, string username, string password)
{
if (AUTH_METHOD == AuthenticationType.None)
{
return null;
}
var user = _userService.FindUser(username, password);
if (user != null)
{
LogSuccess(context, username);
return user;
}
LogFailure(context, username);
return null;
}
public void Logout(NancyContext context)
{
if (AUTH_METHOD == AuthenticationType.None)
{
return;
}
if (context.CurrentUser != null)
{
LogLogout(context, context.CurrentUser.UserName);
}
}
public IUserIdentity Validate(string username, string password) public IUserIdentity Validate(string username, string password)
{ {
if (AUTH_METHOD == AuthenticationType.None) if (AUTH_METHOD == AuthenticationType.None)
@ -42,9 +95,17 @@ namespace Sonarr.Http.Authentication
if (user != null) if (user != null)
{ {
if (AUTH_METHOD != AuthenticationType.Basic)
{
// Don't log success for basic auth
LogSuccess(_context, username);
}
return new NzbDroneUser { UserName = user.Username }; return new NzbDroneUser { UserName = user.Username };
} }
LogFailure(_context, username);
return null; return null;
} }
@ -62,6 +123,8 @@ namespace Sonarr.Http.Authentication
return new NzbDroneUser { UserName = user.Username }; return new NzbDroneUser { UserName = user.Username };
} }
LogInvalidated(_context);
return null; return null;
} }
@ -138,5 +201,30 @@ namespace Sonarr.Http.Authentication
return context.Request.Headers.Authorization; return context.Request.Headers.Authorization;
} }
public void LogUnauthorized(NancyContext context)
{
_authLogger.Info("Auth-Unauthorized ip {0} url '{1}'", context.Request.UserHostAddress, context.Request.Url.ToString());
}
private void LogInvalidated(NancyContext context)
{
_authLogger.Info("Auth-Invalidated ip {0}", context.Request.UserHostAddress);
}
private void LogFailure(NancyContext context, string username)
{
_authLogger.Warn("Auth-Failure ip {0} username '{1}'", context.Request.UserHostAddress, username);
}
private void LogSuccess(NancyContext context, string username)
{
_authLogger.Info("Auth-Success ip {0} username '{1}'", context.Request.UserHostAddress, username);
}
private void LogLogout(NancyContext context, string username)
{
_authLogger.Info("Auth-Logout ip {0} username '{1}'", context.Request.UserHostAddress, username);
}
} }
} }

View File

@ -43,18 +43,28 @@ namespace Sonarr.Http.Authentication
else if (_configFileProvider.AuthenticationMethod == AuthenticationType.Basic) else if (_configFileProvider.AuthenticationMethod == AuthenticationType.Basic)
{ {
pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr")); pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr"));
pipelines.BeforeRequest.AddItemToStartOfPipeline(CaptureContext);
} }
pipelines.BeforeRequest.AddItemToEndOfPipeline((Func<NancyContext, Response>)RequiresAuthentication); pipelines.BeforeRequest.AddItemToEndOfPipeline((Func<NancyContext, Response>)RequiresAuthentication);
pipelines.AfterRequest.AddItemToEndOfPipeline((Action<NancyContext>)RemoveLoginHooksForApiCalls); pipelines.AfterRequest.AddItemToEndOfPipeline((Action<NancyContext>)RemoveLoginHooksForApiCalls);
} }
private Response CaptureContext(NancyContext context)
{
_authenticationService.SetContext(context);
return null;
}
private Response RequiresAuthentication(NancyContext context) private Response RequiresAuthentication(NancyContext context)
{ {
Response response = null; Response response = null;
if (!_authenticationService.IsAuthenticated(context)) if (!_authenticationService.IsAuthenticated(context))
{ {
_authenticationService.LogUnauthorized(context);
response = new Response { StatusCode = HttpStatusCode.Unauthorized }; response = new Response { StatusCode = HttpStatusCode.Unauthorized };
} }