Add UseHttpLogging option in appsettings. (#562)

* Add UseHttpLogging option.

* Default to false for UseHttpLogging.

* Fix log downloads.
This commit is contained in:
Jared Goodwin 2023-01-14 15:55:16 -08:00 committed by GitHub
parent 56ee561ca2
commit 6ddc20b322
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 155 additions and 67 deletions

View File

@ -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<bool>("DeviceCameOnline", device);
if (!await _hubConnection.InvokeAsync<bool>("DeviceCameOnline", device))
{
await Connect();
return;
}
if (await CheckForServerMigration())
{
await Connect();
return;
}
await _updater.CheckForUpdates();
}
private void RegisterMessageHandlers()

View File

@ -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

View File

@ -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<ClientDownloadsController> _logger;
public ClientDownloadsController(
IWebHostEnvironment hostEnv,
IEmbeddedServerDataSearcher embeddedDataSearcher)
IEmbeddedServerDataSearcher embeddedDataSearcher,
IApplicationConfig appConfig,
ILogger<ClientDownloadsController> logger)
{
_hostEnv = hostEnv;
_embeddedDataSearcher = embeddedDataSearcher;
_appConfig = appConfig;
_logger = logger;
}
[HttpGet("desktop/{platformID}")]
@ -138,6 +143,8 @@ namespace Remotely.Server.API
private async Task<IActionResult> 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<IActionResult> 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}");
}
}
}

View File

@ -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");
}
}

View File

@ -338,10 +338,12 @@
<ValidationMessage For="() => Input.UseHsts" />
</div>
<div class="form-group">
<label>ICE Servers</label>
<div class="form-group">
<label>Use HTTP Logging</label>
<br />
<span class="text-muted">Must be edited in appsettings.json.</span>
<InputCheckbox @bind-Value="Input.UseHttpLogging" autocomplete="off" />
<br />
<ValidationMessage For="() => Input.UseHttpLogging" />
</div>
<h4>Connection Strings</h4>

View File

@ -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

View File

@ -19,13 +19,13 @@
</div>
<div>
<button class="btn btn-primary"
onclick="location.assign('/API/ServerLogs/Download')">
onclick="window.open('/API/ServerLogs/Download', '_blank')">
Download Logs
</button>
</div>
<div>
<button class="btn btn-primary"
onclick="location.assign('/API/ScriptResults')">
onclick="window.open('/API/ScriptResults', '_blank')">
Download Script History
</button>
</div>
@ -75,7 +75,15 @@
<tr @key="eventLog">
<td>@eventLog.EventType</td>
<td>@eventLog.TimeStamp</td>
<td>@eventLog.Message</td>
<td>
@if (!string.IsNullOrWhiteSpace(eventLog.Message))
{
foreach (var line in eventLog.Message.Split("\n", StringSplitOptions.RemoveEmptyEntries))
{
<div>@line</div>
}
}
</td>
<td>@eventLog.Source</td>
<td>@eventLog.StackTrace</td>
</tr>

View File

@ -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<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<RemotelyUser>>();
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<string[]>();
@ -166,7 +177,7 @@ services.AddScoped<IEmailSenderEx, EmailSenderEx>();
services.AddScoped<IEmailSender, EmailSender>();
services.AddScoped<IAppDbFactory, AppDbFactory>();
services.AddTransient<IDataService, DataService>();
services.AddScoped<IApplicationConfig, ApplicationConfig>();
services.AddSingleton<IApplicationConfig, ApplicationConfig>();
services.AddScoped<ApiAuthorizationFilter>();
services.AddScoped<ExpiringTokenFilter>();
services.AddHostedService<DbCleanupService>();
@ -200,9 +211,15 @@ services.AddScoped<IHubEventHandler>(s => s.GetRequiredService<IHubEventHandlerE
services.AddSingleton<IServiceHubSessionCache, ServiceHubSessionCache>();
var app = builder.Build();
var appConfig = app.Services.GetRequiredService<IApplicationConfig>();
app.UseForwardedHeaders();
if (appConfig.UseHttpLogging)
{
app.UseHttpLogging();
}
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();

View File

@ -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<Theme>(Config["ApplicationOptions:Theme"] ?? "Dark", true);
public string[] TrustedCorsOrigins => Config.GetSection("ApplicationOptions:TrustedCorsOrigins").Get<string[]>() ?? System.Array.Empty<string>();
public bool UseHsts => bool.Parse(Config["ApplicationOptions:UseHsts"] ?? "false");
public bool UseHttpLogging => bool.Parse(Config["ApplicationOptions:UseHttpLogging"] ?? "false");
private IConfiguration Config { get; set; }
}
}

View File

@ -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();
}

View File

@ -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
}
}