Skip to content

Commit 651b185

Browse files
committed
- Code refactor
- fix Dockerfile - enrich logging with audit messages
1 parent 67396bb commit 651b185

File tree

11 files changed

+90
-25
lines changed

11 files changed

+90
-25
lines changed

src/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ RUN apt update && \
2727
ln -s /usr/lib/x86_64-linux-gnu/liblber-2.5.so.0 /usr/lib/liblber.so.2 && \
2828
pip3 install dpapi-ng[kerberos] --break-system-packages
2929
COPY --from=publish /app/publish .
30+
RUN chown app -R /app/
3031
HEALTHCHECK CMD curl --fail http://localhost:8080/healthz || exit
3132
USER app
3233
ENTRYPOINT ["dotnet", "LAPS-WebUI.dll"]

src/Interfaces/ISessionManagerService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@ public interface ISessionManagerService
88
public Task<LdapForNet.LdapCredential> GetLdapCredentialsAsync();
99
public Task<List<string>> GetDomainsAsync();
1010
public Task<string> GetDomainAsync();
11+
public Task<string> GetUsernameAsync();
12+
1113
}
1214
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace LAPS_WebUI.LogEnrichers;
2+
3+
using Serilog.Core;
4+
using Serilog.Events;
5+
6+
public class AuditPrefixEnricher : ILogEventEnricher
7+
{
8+
private const string PropertyName = "AuditPrefix";
9+
10+
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
11+
{
12+
if (logEvent.Properties.TryGetValue("Audit", out LogEventPropertyValue? auditProp) &&
13+
auditProp is ScalarValue { Value: bool and true })
14+
{
15+
LogEventProperty auditPrefix = propertyFactory.CreateProperty(PropertyName, "[AUDIT] ");
16+
logEvent.AddPropertyIfAbsent(auditPrefix);
17+
}
18+
else
19+
{
20+
LogEventProperty empty = propertyFactory.CreateProperty(PropertyName, "");
21+
logEvent.AddPropertyIfAbsent(empty);
22+
}
23+
}
24+
}

src/Pages/LAPS.razor.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ private async Task ClearLapsPassword(AdComputer computer)
5959
_ => LAPSVersion.v1
6060
};
6161

62-
DialogParameters parameters = new DialogParameters { ["ContentText"] = $"Clear LAPS {version} Password on Computer '{computer.Name}' ?{Environment.NewLine}You have to invoke gpupdate /force on computer '{computer.Name}' in order so set a new LAPS password", ["CancelButtonText"] = "Cancel", ["ConfirmButtonText"] = "Clear", ["ConfirmButtonColor"] = Color.Error };
62+
DialogParameters parameters = new() { ["ContentText"] = $"Clear LAPS {version} Password on Computer '{computer.Name}' ?{Environment.NewLine}You have to invoke gpupdate /force on computer '{computer.Name}' in order so set a new LAPS password", ["CancelButtonText"] = "Cancel", ["ConfirmButtonText"] = "Clear", ["ConfirmButtonColor"] = Color.Error };
6363
IDialogReference dialog = await Dialog.ShowAsync<Confirmation>("Clear LAPS Password", parameters,new DialogOptions() { NoHeader = true });
6464
DialogResult? result = await dialog.Result;
6565

@@ -68,7 +68,10 @@ private async Task ClearLapsPassword(AdComputer computer)
6868
computer.LapsInformations.Clear();
6969
await InvokeAsync(StateHasChanged);
7070
await LdapService.ClearLapsPassword(DomainName ?? await SessionManager.GetDomainAsync(), LdapCredential ?? await SessionManager.GetLdapCredentialsAsync(), computer.DistinguishedName, version);
71-
Snackbar.Add($"LAPS {version} Password for computer '{computer.Name}' successfully cleared! - Please invoke gpupdate on {computer.Name} to set a new LAPS Password", Severity.Success);
71+
Snackbar.Add($"LAPS {version} Password for computer '{computer.Name}' successfully cleared! - Please invoke 'gpupdate' on {computer.Name} to set a new LAPS Password", Severity.Success);
72+
string currentUsername = await SessionManager.GetUsernameAsync();
73+
Log.ForContext("Audit", true)
74+
.Information("LAPS password cleared for computer '{ComputerName}' (LAPS version: {LAPSVersion}) by user '{UserName}'", computer.Name, version, currentUsername);
7275
}
7376
}
7477
}
@@ -118,10 +121,7 @@ private async Task RefreshComputerDetailsAsync(AdComputer computer, bool supress
118121
{
119122
Log.Error("{ErrorMessage}", ex.Message);
120123

121-
if (placeHolder != null)
122-
{
123-
placeHolder.LapsInformations = backup;
124-
}
124+
placeHolder?.LapsInformations = backup;
125125

126126
if (!supressNotify)
127127
{
@@ -157,7 +157,7 @@ private async Task FetchComputerDetailsAsync(string distinguishedName, string co
157157
if (!selectedComputer.FailedToRetrieveLapsDetails && tab != null)
158158
{
159159
await InvokeAsync(StateHasChanged);
160-
tab.ActivatePanel(tab.Panels.First(x => !x.Disabled));
160+
await tab.ActivatePanelAsync(tab.Panels.First(x => !x.Disabled));
161161
}
162162

163163
}

src/Pages/Login.razor.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using LAPS_WebUI.Models;
22
using Microsoft.AspNetCore.Components.Forms;
3+
using Serilog;
34

45
namespace LAPS_WebUI.Pages
56
{
@@ -28,10 +29,12 @@ private async Task OnValidSubmitAsync(EditContext context)
2829
{
2930
if (await SessionManager.LoginAsync(_loginRequest.DomainName ?? string.Empty, _loginRequest.Username ?? string.Empty, _loginRequest.Password ?? string.Empty))
3031
{
32+
Log.ForContext("Audit",true).Information("User '{Username}' successfully logged in.", _loginRequest.Username);
3133
NavigationManager.NavigateTo("/laps");
3234
}
3335
else
3436
{
37+
Log.ForContext("Audit",true).Warning("User '{Username}' failed logged in.", _loginRequest.Username);
3538
throw new Exception("Login failed!");
3639
}
3740
}

src/Pages/Logout.razor.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
66
{
77
if (await SessionManager.IsUserLoggedInAsync())
88
{
9+
string username = await SessionManager.GetUsernameAsync();
10+
Serilog.Log.Information("User logged out: {Username}", username);
911
await SessionManager.LogoutAsync();
1012
}
1113

src/Program.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Blazored.SessionStorage;
22
using CurrieTechnologies.Razor.Clipboard;
33
using LAPS_WebUI.Interfaces;
4+
using LAPS_WebUI.LogEnrichers;
45
using LAPS_WebUI.Models;
56
using LAPS_WebUI.Services;
67
using MudBlazor;
@@ -17,6 +18,8 @@
1718
// Add services to the container.
1819
builder.Services.AddSerilog((services, lc) => lc
1920
.ReadFrom.Configuration(builder.Configuration)
21+
.Enrich.With<AuditPrefixEnricher>()
22+
.Enrich.FromLogContext()
2023
.ReadFrom.Services(services));
2124

2225
builder.Services.AddRazorPages();

src/Services/LDAPService.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ private static string EscapeLdapSearchFilter(string searchFilter)
139139
AdComputer? adComputer;
140140
Domain domain = _domains.Value.SingleOrDefault(x => x.Name == domainName) ?? throw new Exception($"No configured domain found with name {domainName}");
141141

142+
Log.ForContext("Audit", true)
143+
.Information("Retrieving Active Directory computer details for DN '{DistinguishedName}' using user '{Username}'", distinguishedName, ldapCredential.UserName);
144+
142145
if (ldapCredential is null)
143146
{
144147
throw new Exception("Failed to get LDAP Credentials");
@@ -178,6 +181,9 @@ private static string EscapeLdapSearchFilter(string searchFilter)
178181
PasswordSetDate = null
179182
};
180183

184+
Log.ForContext("Audit", true)
185+
.Information("Successfully retrieved LAPS v1 password for computer '{ComputerName}' (DN: '{DistinguishedName}') by user '{Username}'", adComputer.Name, distinguishedName, ldapCredential.UserName);
186+
181187
adComputer.LapsInformations.Add(lapsInformationEntry);
182188
}
183189

@@ -217,6 +223,8 @@ private static string EscapeLdapSearchFilter(string searchFilter)
217223
};
218224

219225
adComputer.LapsInformations.Add(lapsInformationEntry);
226+
Log.ForContext("Audit", true)
227+
.Information("Successfully retrieved LAPS v2 password for computer '{ComputerName}' (DN: '{DistinguishedName}') by user '{Username}'", adComputer.Name, distinguishedName, ldapCredential.UserName);
220228

221229
if (ldapSearchResult.DirectoryAttributes.Any(x => x.Name == "msLAPS-EncryptedPasswordHistory"))
222230
{
@@ -240,10 +248,13 @@ private static string EscapeLdapSearchFilter(string searchFilter)
240248
};
241249

242250
adComputer.LapsInformations.Add(historicLapsInformationEntry);
251+
Log.ForContext("Audit", true)
252+
.Information("Successfully retrieved LAPS v2 password history for computer '{ComputerName}' (DN: '{DistinguishedName}') by user '{Username}'", adComputer.Name, distinguishedName, ldapCredential.UserName);
243253
}
244254
else
245255
{
246-
Log.Warning("Failed to decrypt LAPS History entry");
256+
Log.ForContext("Audit", true)
257+
.Warning("Failed decrypt LAPS v2 password history for computer '{ComputerName}' (DN: '{DistinguishedName}') by user '{Username}'", adComputer.Name, distinguishedName, ldapCredential.UserName);
247258
}
248259
}
249260
}
@@ -300,9 +311,10 @@ private static async Task<string> DecryptLapsPayload(byte[] value, LdapCredentia
300311
}
301312
catch (Exception ex)
302313
{
303-
Log.Error("Decrypt LAPS Password failed. Please check if Domain Controllers are reachable and your python environment is setup correctly");
314+
Log.ForContext("Audit", true)
315+
.Error("Failed to decrypt LAPS password on behalf for user '{Username}'", ldapCredential.UserName);
304316
Log.Debug("Error Stacktrace => {ErrorMessage}", ex.Message);
305-
throw new ArgumentException("Failed to decrypt LAPSv2 Password");
317+
throw new ArgumentException("Decrypt LAPS Password failed. Please check if Domain Controllers are reachable and your python environment is setup correctly");
306318
}
307319

308320
}

src/Services/SessionManagerService.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ public class SessionManagerService(
1010
ICryptService cryptService)
1111
: ISessionManagerService
1212
{
13+
14+
public async Task<string> GetUsernameAsync()
15+
{
16+
return await sessionStorageService.GetItemAsync<string>("username");
17+
}
18+
1319
public async Task<List<string>> GetDomainsAsync()
1420
{
1521
return (await ldapService.GetDomainsAsync()).Select(x => x.Name).ToList();
@@ -43,14 +49,13 @@ public async Task<bool> LoginAsync(string domainName, string username, string pa
4349
{
4450
return false;
4551
}
46-
else
47-
{
48-
await sessionStorageService.SetItemAsync("loggedIn", bindResult);
49-
await sessionStorageService.SetItemAsync("domainName", domainName);
50-
await sessionStorageService.SetItemAsync("ldapCredentials", new LdapCredential() { UserName = cryptService.EncryptString(username), Password = cryptService.EncryptString(password) });
5152

52-
return true;
53-
}
53+
await sessionStorageService.SetItemAsync("loggedIn", bindResult);
54+
await sessionStorageService.SetItemAsync("username", username);
55+
await sessionStorageService.SetItemAsync("domainName", domainName);
56+
await sessionStorageService.SetItemAsync("ldapCredentials", new LdapCredential() { UserName = cryptService.EncryptString(username), Password = cryptService.EncryptString(password) });
57+
58+
return true;
5459
}
5560
public async Task<bool> LogoutAsync()
5661
{

src/appsettings.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
}
88
},
99
"WriteTo": [
10-
{ "Name": "Console" }
10+
{ "Name": "Console",
11+
"Args": {
12+
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {AuditPrefix}{Message:lj}{NewLine}{Exception}"
13+
}
14+
}
1115
],
12-
"Enrich": [ "FromLogContext"]
16+
"Enrich": [ "FromLogContext", "WithAuditPrefix"]
1317
},
1418
"AllowedHosts": "*"
1519
}

0 commit comments

Comments
 (0)