From 6ddc20b322d7995fc4cd89de6beca9de2ec92678 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Sat, 14 Jan 2023 15:55:16 -0800 Subject: [PATCH] Add UseHttpLogging option in appsettings. (#562) * Add UseHttpLogging option. * Default to false for UseHttpLogging. * Fix log downloads. --- Agent/Services/AgentHubConnection.cs | 14 ++- README.md | 1 + Server/API/ClientDownloadsController.cs | 117 ++++++++++++++---------- Server/API/ServerLogsController.cs | 13 ++- Server/Pages/ServerConfig.razor | 8 +- Server/Pages/ServerConfig.razor.cs | 3 + Server/Pages/ServerLogs.razor | 14 ++- Server/Program.cs | 21 ++++- Server/Services/ApplicationConfig.cs | 2 + Server/Services/DataService.cs | 23 ++++- Server/appsettings.json | 6 +- 11 files changed, 155 insertions(+), 67 deletions(-) diff --git a/Agent/Services/AgentHubConnection.cs b/Agent/Services/AgentHubConnection.cs index 0b170651..8b3b8f38 100644 --- a/Agent/Services/AgentHubConnection.cs +++ b/Agent/Services/AgentHubConnection.cs @@ -196,7 +196,19 @@ namespace Remotely.Agent.Services { _logger.LogInformation("Reconnected to server."); var device = await _deviceInfoService.CreateDevice(_connectionInfo.DeviceID, _connectionInfo.OrganizationID); - _ = await _hubConnection.InvokeAsync("DeviceCameOnline", device); + + if (!await _hubConnection.InvokeAsync("DeviceCameOnline", device)) + { + await Connect(); + return; + } + + if (await CheckForServerMigration()) + { + await Connect(); + return; + } + await _updater.CheckForUpdates(); } private void RegisterMessageHandlers() diff --git a/README.md b/README.md index afec7b1c..94ab70db 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ For more information on configuring ASP.NET Core, see https://docs.microsoft.com * Theme: The color theme to use for the site. Values are "Light" or "Dark". This can also be configured per-user in Account - Options. * TrustedCorsOrigins: For cross-origin API requests via JavaScript. The websites listed in this array with be allowed to make requests to the API. This does not grant authentication, which is still required on most endpoints. * UseHsts: Whether ASP.NET Core will use HTTP Strict Transport Security. +* UseHttpLogging: Enables logging for all HTTP requests. Also enables additional log entries in `ClientDownloadsController` regarding the effective scheme, host, and remote IP address as a result of processing forwarded headers. ## Changing the Database diff --git a/Server/API/ClientDownloadsController.cs b/Server/API/ClientDownloadsController.cs index c1113093..afcc2899 100644 --- a/Server/API/ClientDownloadsController.cs +++ b/Server/API/ClientDownloadsController.cs @@ -25,16 +25,21 @@ namespace Remotely.Server.API [ApiController] public class ClientDownloadsController : ControllerBase { + private readonly IApplicationConfig _appConfig; private readonly IEmbeddedServerDataSearcher _embeddedDataSearcher; - private readonly SemaphoreSlim _fileLock = new(1,1); + private readonly SemaphoreSlim _fileLock = new(1, 1); private readonly IWebHostEnvironment _hostEnv; - + private readonly ILogger _logger; public ClientDownloadsController( IWebHostEnvironment hostEnv, - IEmbeddedServerDataSearcher embeddedDataSearcher) + IEmbeddedServerDataSearcher embeddedDataSearcher, + IApplicationConfig appConfig, + ILogger logger) { _hostEnv = hostEnv; _embeddedDataSearcher = embeddedDataSearcher; + _appConfig = appConfig; + _logger = logger; } [HttpGet("desktop/{platformID}")] @@ -138,6 +143,8 @@ namespace Remotely.Server.API private async Task GetDesktopFile(string filePath, string organizationId = null) { + LogRequest(nameof(GetDesktopFile)); + var serverUrl = $"{Request.Scheme}://{Request.Host}"; var embeddedData = new EmbeddedServerData(new Uri(serverUrl), organizationId); var result = await _embeddedDataSearcher.GetRewrittenStream(filePath, embeddedData); @@ -152,65 +159,81 @@ namespace Remotely.Server.API private async Task GetInstallFile(string organizationId, string platformID) { + LogRequest(nameof(GetInstallFile)); + + if (!await _fileLock.WaitAsync(TimeSpan.FromSeconds(15))) + { + return StatusCode(StatusCodes.Status408RequestTimeout); + } + try { - if (await _fileLock.WaitAsync(TimeSpan.FromSeconds(15))) + switch (platformID) { - switch (platformID) - { - case "WindowsInstaller": + case "WindowsInstaller": + { + var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Remotely_Installer.exe"); + var serverUrl = $"{Request.Scheme}://{Request.Host}"; + var embeddedData = new EmbeddedServerData(new Uri(serverUrl), organizationId); + var result = await _embeddedDataSearcher.GetRewrittenStream(filePath, embeddedData); + + if (!result.IsSuccess) { - var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Remotely_Installer.exe"); - var serverUrl = $"{Request.Scheme}://{Request.Host}"; - var embeddedData = new EmbeddedServerData(new Uri(serverUrl), organizationId); - var result = await _embeddedDataSearcher.GetRewrittenStream(filePath, embeddedData); - - if (!result.IsSuccess) - { - throw result.Exception; - } - - return File(result.Value, "application/octet-stream", "Remotely_Installer.exe"); + throw result.Exception; } - case "ManjaroInstaller-x64": - { - var fileName = "Install-Manjaro-x64.sh"; - return await GetBashInstaller(fileName, organizationId); - } - case "UbuntuInstaller-x64": - { - var fileName = "Install-Ubuntu-x64.sh"; + return File(result.Value, "application/octet-stream", "Remotely_Installer.exe"); + } + case "ManjaroInstaller-x64": + { + var fileName = "Install-Manjaro-x64.sh"; - return await GetBashInstaller(fileName, organizationId); - } - case "MacOSInstaller-x64": - { - var fileName = "Install-MacOS-x64.sh"; + return await GetBashInstaller(fileName, organizationId); + } + case "UbuntuInstaller-x64": + { + var fileName = "Install-Ubuntu-x64.sh"; - return await GetBashInstaller(fileName, organizationId); - } - case "MacOSInstaller-arm64": - { - var fileName = "Install-MacOS-arm64.sh"; + return await GetBashInstaller(fileName, organizationId); + } + case "MacOSInstaller-x64": + { + var fileName = "Install-MacOS-x64.sh"; - return await GetBashInstaller(fileName, organizationId); - } - default: - return BadRequest(); - } - } - else - { - return StatusCode(StatusCodes.Status408RequestTimeout); + return await GetBashInstaller(fileName, organizationId); + } + case "MacOSInstaller-arm64": + { + var fileName = "Install-MacOS-arm64.sh"; + + return await GetBashInstaller(fileName, organizationId); + } + default: + return BadRequest(); } } finally { - if (_fileLock.CurrentCount == 0) + _fileLock.Release(); + } + } + + private void LogRequest(string methodName) + { + if (_appConfig.UseHttpLogging) + { + var ip = Request.HttpContext.Connection.RemoteIpAddress; + if (ip?.IsIPv4MappedToIPv6 == true) { - _fileLock.Release(); + ip = ip.MapToIPv4(); } + + _logger.LogInformation( + "Started client download via {methodName}. Effective Scheme: {scheme}. Effective Host: {host}. Remote IP: {ip}.", + methodName, + Request.Scheme, + Request.Host, + $"{ip}"); } } } diff --git a/Server/API/ServerLogsController.cs b/Server/API/ServerLogsController.cs index a87b40bc..aec96757 100644 --- a/Server/API/ServerLogsController.cs +++ b/Server/API/ServerLogsController.cs @@ -3,6 +3,7 @@ using Remotely.Server.Auth; using Remotely.Server.Services; using System.Text; using System.Text.Json; +using System; namespace Remotely.Server.API { @@ -10,20 +11,22 @@ namespace Remotely.Server.API [ApiController] public class ServerLogsController : ControllerBase { + private readonly IDataService _dataService; + private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions() { WriteIndented = true }; public ServerLogsController(IDataService dataService) { - DataService = dataService; + _dataService = dataService; } - public IDataService DataService { get; set; } [ServiceFilter(typeof(ApiAuthorizationFilter))] [HttpGet("Download")] public ActionResult Download() { - Request.Headers.TryGetValue("OrganizationID", out var orgID); - var logs = DataService.GetAllEventLogs(orgID); - var fileBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(logs)); + Request.Headers.TryGetValue("OrganizationID", out var orgId); + + var logs = _dataService.GetAllEventLogs(User.Identity?.Name, orgId); + var fileBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(logs, _jsonOptions)); return File(fileBytes, "application/octet-stream", "ServerLogs.json"); } } diff --git a/Server/Pages/ServerConfig.razor b/Server/Pages/ServerConfig.razor index 123f3df8..24c639e0 100644 --- a/Server/Pages/ServerConfig.razor +++ b/Server/Pages/ServerConfig.razor @@ -338,10 +338,12 @@ -
- +
+
- Must be edited in appsettings.json. + +
+

Connection Strings

diff --git a/Server/Pages/ServerConfig.razor.cs b/Server/Pages/ServerConfig.razor.cs index 2e00330b..8af3073f 100644 --- a/Server/Pages/ServerConfig.razor.cs +++ b/Server/Pages/ServerConfig.razor.cs @@ -103,6 +103,9 @@ namespace Remotely.Server.Pages [Display(Name = "Use HSTS")] public bool UseHsts { get; set; } + + [Display(Name = "Use HTTP Logging")] + public bool UseHttpLogging { get; set; } } public class ConnectionStringsModel diff --git a/Server/Pages/ServerLogs.razor b/Server/Pages/ServerLogs.razor index 58ea34a1..cd6b4187 100644 --- a/Server/Pages/ServerLogs.razor +++ b/Server/Pages/ServerLogs.razor @@ -19,13 +19,13 @@
@@ -75,7 +75,15 @@ @eventLog.EventType @eventLog.TimeStamp - @eventLog.Message + + @if (!string.IsNullOrWhiteSpace(eventLog.Message)) + { + foreach (var line in eventLog.Message.Split("\n", StringSplitOptions.RemoveEmptyEntries)) + { +
@line
+ } + } + @eventLog.Source @eventLog.StackTrace diff --git a/Server/Program.cs b/Server/Program.cs index 9d8f3643..9362e353 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -33,6 +33,7 @@ using Remotely.Server.Services.RcImplementations; using Immense.RemoteControl.Server.Abstractions; using Microsoft.Extensions.DependencyInjection.Extensions; using Remotely.Shared.Services; +using System; var builder = WebApplication.CreateBuilder(args); var configuration = builder.Configuration; @@ -43,7 +44,7 @@ builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging")); builder.Logging.AddConsole(); builder.Logging.AddDebug(); -if (EnvironmentHelper.IsWindows && +if (OperatingSystem.IsWindows() && bool.TryParse(builder.Configuration["ApplicationOptions:EnableWindowsEventLog"], out var enableEventLog) && enableEventLog) { @@ -109,6 +110,16 @@ services.AddRazorPages(); services.AddServerSideBlazor(); services.AddScoped>(); services.AddDatabaseDeveloperPageExceptionFilter(); +services.AddHttpLogging(options => +{ + options.RequestHeaders.Add("X-Forwarded-For"); + options.RequestHeaders.Add("X-Forwarded-Proto"); + options.RequestHeaders.Add("X-Forwarded-Host"); + options.RequestHeaders.Add("X-Original-For"); + options.RequestHeaders.Add("X-Original-Proto"); + options.RequestHeaders.Add("X-Original-Host"); + options.RequestHeaders.Add("Host"); +}); var trustedOrigins = configuration.GetSection("ApplicationOptions:TrustedCorsOrigins").Get(); @@ -166,7 +177,7 @@ services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddTransient(); -services.AddScoped(); +services.AddSingleton(); services.AddScoped(); services.AddScoped(); services.AddHostedService(); @@ -200,9 +211,15 @@ services.AddScoped(s => s.GetRequiredService(); var app = builder.Build(); +var appConfig = app.Services.GetRequiredService(); app.UseForwardedHeaders(); +if (appConfig.UseHttpLogging) +{ + app.UseHttpLogging(); +} + if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); diff --git a/Server/Services/ApplicationConfig.cs b/Server/Services/ApplicationConfig.cs index d9aa5a5b..777540ab 100644 --- a/Server/Services/ApplicationConfig.cs +++ b/Server/Services/ApplicationConfig.cs @@ -34,6 +34,7 @@ namespace Remotely.Server.Services Theme Theme { get; } string[] TrustedCorsOrigins { get; } bool UseHsts { get; } + bool UseHttpLogging { get; } } public class ApplicationConfig : IApplicationConfig @@ -70,6 +71,7 @@ namespace Remotely.Server.Services public Theme Theme => Enum.Parse(Config["ApplicationOptions:Theme"] ?? "Dark", true); public string[] TrustedCorsOrigins => Config.GetSection("ApplicationOptions:TrustedCorsOrigins").Get() ?? System.Array.Empty(); public bool UseHsts => bool.Parse(Config["ApplicationOptions:UseHsts"] ?? "false"); + public bool UseHttpLogging => bool.Parse(Config["ApplicationOptions:UseHttpLogging"] ?? "false"); private IConfiguration Config { get; set; } } } diff --git a/Server/Services/DataService.cs b/Server/Services/DataService.cs index 19d44a94..a52485ea 100644 --- a/Server/Services/DataService.cs +++ b/Server/Services/DataService.cs @@ -101,7 +101,7 @@ namespace Remotely.Server.Services Device[] GetAllDevices(string orgID); - EventLog[] GetAllEventLogs(string orgID); + EventLog[] GetAllEventLogs(string username, string orgId); InviteLink[] GetAllInviteLinks(string organizationId); @@ -1069,12 +1069,27 @@ namespace Remotely.Server.Services return dbContext.Devices.Where(x => x.OrganizationID == orgID).ToArray(); } - public EventLog[] GetAllEventLogs(string orgID) + public EventLog[] GetAllEventLogs(string username, string orgId) { using var dbContext = _appDbFactory.GetContext(); - return dbContext.EventLogs - .Where(x => x.OrganizationID == orgID) + var query = dbContext.EventLogs + .AsNoTracking() + .AsQueryable(); + + if (!string.IsNullOrWhiteSpace(username)) + { + var user = dbContext.Users.FirstOrDefault(x => x.UserName == username); + if (user?.IsAdministrator == true) + { + return query + .OrderByDescending(x => x.TimeStamp) + .ToArray(); + } + } + + return query + .Where(x => x.OrganizationID == orgId) .OrderByDescending(x => x.TimeStamp) .ToArray(); } diff --git a/Server/appsettings.json b/Server/appsettings.json index 3999604f..63e9e50f 100644 --- a/Server/appsettings.json +++ b/Server/appsettings.json @@ -6,7 +6,8 @@ }, "Logging": { "LogLevel": { - "Default": "Warning" + "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information", + "Default": "Information" } }, "ApplicationOptions": { @@ -35,6 +36,7 @@ "SmtpUserName": "", "Theme": "Dark", "TrustedCorsOrigins": [], - "UseHsts": false + "UseHsts": false, + "UseHttpLogging": false } }