diff --git a/src/Runtime/CollectionTask.cs b/src/Runtime/CollectionTask.cs index c0ada8f..e186b08 100644 --- a/src/Runtime/CollectionTask.cs +++ b/src/Runtime/CollectionTask.cs @@ -99,7 +99,6 @@ internal async Task StartCollection() await _outputChannel.Writer.WriteAsync(wkp); } - _outputChannel.Writer.Complete(); _compStatusChannel?.Writer.Complete(); _log.LogInformation("Output channel closed, waiting for output task to complete"); @@ -112,7 +111,7 @@ internal async Task StartCollection() internal async Task ConsumeSearchResults() { var log = _context.Logger; - var processor = new ObjectProcessors(_context, log); + var processor = new ObjectProcessors(_context, log, _compStatusChannel); var watch = new Stopwatch(); var threadId = Thread.CurrentThread.ManagedThreadId; @@ -130,7 +129,7 @@ internal async Task ConsumeSearchResults() log.LogTrace("Consumer {ThreadID} started processing {obj} ({type})", threadId, res.DisplayName, res.ObjectType); watch.Start(); - var processed = await processor.ProcessObject(item, res, _compStatusChannel); + var processed = await processor.ProcessObject(item, res); watch.Stop(); log.LogTrace("Consumer {ThreadID} took {time} ms to process {obj}", threadId, watch.Elapsed.TotalMilliseconds, res.DisplayName); @@ -147,6 +146,8 @@ internal async Task ConsumeSearchResults() { log.LogError(e, "error in consumer"); } + + processor.ClearEventHandlers(); log.LogDebug("Consumer task on thread {id} completed", Thread.CurrentThread.ManagedThreadId); } diff --git a/src/Runtime/LDAPConsumer.cs b/src/Runtime/LDAPConsumer.cs index 952b0f9..ced13f0 100644 --- a/src/Runtime/LDAPConsumer.cs +++ b/src/Runtime/LDAPConsumer.cs @@ -18,7 +18,7 @@ internal static async Task ConsumeSearchResults(Channel inputC int id) { var log = context.Logger; - var processor = new ObjectProcessors(context, log); + var processor = new ObjectProcessors(context, log, computerStatusChannel); var watch = new Stopwatch(); var threadId = Thread.CurrentThread.ManagedThreadId; @@ -34,7 +34,7 @@ internal static async Task ConsumeSearchResults(Channel inputC log.LogTrace("Consumer {ThreadID} started processing {obj} ({type})", threadId, res.DisplayName, res.ObjectType); watch.Start(); - var processed = await processor.ProcessObject(item, res, computerStatusChannel); + var processed = await processor.ProcessObject(item, res); watch.Stop(); log.LogTrace("Consumer {ThreadID} took {time} ms to process {obj}", threadId, watch.Elapsed.TotalMilliseconds, res.DisplayName); @@ -51,6 +51,8 @@ internal static async Task ConsumeSearchResults(Channel inputC { log.LogError(e, "error in consumer"); } + + processor.ClearEventHandlers(); log.LogDebug("Consumer task on thread {id} completed", Thread.CurrentThread.ManagedThreadId); } diff --git a/src/Runtime/ObjectProcessors.cs b/src/Runtime/ObjectProcessors.cs index f5571af..995ac55 100644 --- a/src/Runtime/ObjectProcessors.cs +++ b/src/Runtime/ObjectProcessors.cs @@ -40,7 +40,9 @@ public class ObjectProcessors { private readonly WebClientServiceProcessor _webClientProcessor; private readonly SmbProcessor _smbProcessor; private readonly ConcurrentDictionary _registryProcessorMap = new(); - public ObjectProcessors(IContext context, ILogger log) { + private readonly Channel _compStatusChannel; + + public ObjectProcessors(IContext context, ILogger log, Channel compStatusChannel) { _context = context; _aclProcessor = new ACLProcessor(context.LDAPUtils); _spnProcessor = new SPNProcessors(context.LDAPUtils); @@ -48,7 +50,7 @@ public ObjectProcessors(IContext context, ILogger log) { _domainTrustProcessor = new DomainTrustProcessor(context.LDAPUtils); _computerAvailability = new ComputerAvailability(context.PortScanTimeout, skipPortScan: context.Flags.SkipPortScan, skipPasswordCheck: context.Flags.SkipPasswordAgeCheck); - _certAbuseProcessor = new CertAbuseProcessor(context.LDAPUtils); + _certAbuseProcessor = new CertAbuseProcessor(context.LDAPUtils, new RegistryAccessor()); _dcRegistryProcessor = new DCRegistryProcessor(context.LDAPUtils); _computerSessionProcessor = new ComputerSessionProcessor(context.LDAPUtils, doLocalAdminSessionEnum: context.Flags.DoLocalAdminSessionEnum, @@ -63,15 +65,41 @@ public ObjectProcessors(IContext context, ILogger log) { _methods = context.ResolvedCollectionMethods; _cancellationToken = context.CancellationTokenSource.Token; _log = log; + _compStatusChannel = compStatusChannel; + + _localGroupProcessor.ComputerStatusEvent += HandleCompStatusEvent; + _computerSessionProcessor.ComputerStatusEvent += HandleCompStatusEvent; + _userRightsAssignmentProcessor.ComputerStatusEvent += HandleCompStatusEvent; + _computerAvailability.ComputerStatusEvent += HandleCompStatusEvent; + _spnProcessor.ComputerStatusEvent += HandleCompStatusEvent; + _ldapPropertyProcessor.ComputerStatusEvent += HandleCompStatusEvent; + _certAbuseProcessor.ComputerStatusEvent += HandleCompStatusEvent; } - internal async Task ProcessObject(IDirectoryObject entry, - ResolvedSearchResult resolvedSearchResult, Channel compStatusChannel) { + internal void ClearEventHandlers() { + _localGroupProcessor.ComputerStatusEvent -= HandleCompStatusEvent; + _computerSessionProcessor.ComputerStatusEvent -= HandleCompStatusEvent; + _userRightsAssignmentProcessor.ComputerStatusEvent -= HandleCompStatusEvent; + _computerAvailability.ComputerStatusEvent -= HandleCompStatusEvent; + _spnProcessor.ComputerStatusEvent -= HandleCompStatusEvent; + _ldapPropertyProcessor.ComputerStatusEvent -= HandleCompStatusEvent; + _certAbuseProcessor.ComputerStatusEvent -= HandleCompStatusEvent; + } + + private async Task HandleCompStatusEvent(CSVComputerStatus status) { + try { + await _compStatusChannel.Writer.WriteAsync(status, _cancellationToken); + } catch (Exception e) { + _log.LogWarning(e, "Caught exception writing to compstatus writer"); + } + } + + internal async Task ProcessObject(IDirectoryObject entry, ResolvedSearchResult resolvedSearchResult) { switch (resolvedSearchResult.ObjectType) { case Label.User: return await ProcessUserObject(entry, resolvedSearchResult); case Label.Computer: - return await ProcessComputerObject(entry, resolvedSearchResult, compStatusChannel); + return await ProcessComputerObject(entry, resolvedSearchResult); case Label.Group: return await ProcessGroupObject(entry, resolvedSearchResult); case Label.GPO: @@ -88,7 +116,7 @@ internal async Task ProcessObject(IDirectoryObject entry, case Label.AIACA: return await ProcessAIACA(entry, resolvedSearchResult); case Label.EnterpriseCA: - return await ProcessEnterpriseCA(entry, resolvedSearchResult, compStatusChannel); + return await ProcessEnterpriseCA(entry, resolvedSearchResult); case Label.NTAuthStore: return await ProcessNTAuthStore(entry, resolvedSearchResult); case Label.CertTemplate: @@ -208,8 +236,7 @@ await _containerProcessor.GetContainingObject(dn) is (true, var container)) private async Task ProcessComputerObject( IDirectoryObject entry, - ResolvedSearchResult resolvedSearchResult, - Channel compStatusChannel + ResolvedSearchResult resolvedSearchResult ) { var ret = new Computer { ObjectIdentifier = resolvedSearchResult.ObjectId, @@ -280,8 +307,7 @@ await _containerProcessor.GetContainingObject(dn) is (true, var container)) { var availability = await _computerAvailability.IsComputerAvailable(resolvedSearchResult, entry); if (!availability.Connectable) { - await compStatusChannel.Writer.WriteAsync(availability.GetCSVStatus(resolvedSearchResult.DisplayName), - _cancellationToken); + await HandleCompStatusEvent(availability.GetCSVStatus(resolvedSearchResult.DisplayName)); ret.Status = availability; return ret; } @@ -297,12 +323,12 @@ await compStatusChannel.Writer.WriteAsync(availability.GetCSVStatus(resolvedSear resolvedSearchResult.ObjectId, resolvedSearchResult.Domain); ret.Sessions = sessionResult; if (_context.Flags.DumpComputerStatus) - await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus { + await HandleCompStatusEvent(new CSVComputerStatus { Status = sessionResult.Collected ? StatusSuccess : sessionResult.FailureReason, Task = "NetSessionEnum", ComputerName = resolvedSearchResult.DisplayName, ObjectId = resolvedSearchResult.ObjectId, - }, _cancellationToken); + }); } if (_methods.HasFlag(CollectionMethod.LoggedOn)) { @@ -313,12 +339,12 @@ await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus { ret.PrivilegedSessions = privSessionResult; if (_context.Flags.DumpComputerStatus) - await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus { + await HandleCompStatusEvent(new CSVComputerStatus { Status = privSessionResult.Collected ? StatusSuccess : privSessionResult.FailureReason, Task = "NetWkstaUserEnum", ComputerName = resolvedSearchResult.DisplayName, ObjectId = resolvedSearchResult.ObjectId, - }, _cancellationToken); + }); if (!_context.Flags.NoRegistryLoggedOn) { await _context.DoDelay(); @@ -326,12 +352,12 @@ await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus { resolvedSearchResult.Domain, resolvedSearchResult.ObjectId); ret.RegistrySessions = registrySessionResult; if (_context.Flags.DumpComputerStatus) - await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus { + await HandleCompStatusEvent(new CSVComputerStatus { Status = registrySessionResult.Collected ? StatusSuccess : registrySessionResult.FailureReason, Task = "RegistrySessions", ComputerName = resolvedSearchResult.DisplayName, ObjectId = resolvedSearchResult.ObjectId, - }, _cancellationToken); + }); } } @@ -704,9 +730,7 @@ private async Task ProcessAIACA(IDirectoryObject entry, ResolvedSearchRes return ret; } - private async Task ProcessEnterpriseCA(IDirectoryObject entry, - ResolvedSearchResult resolvedSearchResult, - Channel compStatusChannel) { + private async Task ProcessEnterpriseCA(IDirectoryObject entry, ResolvedSearchResult resolvedSearchResult) { var ret = new EnterpriseCA { ObjectIdentifier = resolvedSearchResult.ObjectId, Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult)) @@ -749,14 +773,13 @@ private async Task ProcessEnterpriseCA(IDirectoryObject entry, if (await _context.LDAPUtils.ResolveHostToSid(dnsHostName, resolvedSearchResult.DomainSid) is (true, var sid) && sid.StartsWith("S-1-")) { ret.HostingComputer = sid; - await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus + await HandleCompStatusEvent(new CSVComputerStatus { Status = ComputerStatus.Success, ComputerName = resolvedSearchResult.DisplayName, Task = nameof(ProcessEnterpriseCA), ObjectId = resolvedSearchResult.ObjectId, - }, - _cancellationToken); + }); } else { _log.LogWarning("CA {Name} host ({Dns}) could not be resolved to a SID.", caName, dnsHostName); } @@ -779,24 +802,23 @@ await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus if (caName != null && dnsHostName != null) { if (await _context.LDAPUtils.ResolveHostToSid(dnsHostName, resolvedSearchResult.DomainSid) is (true, var sid) && sid.StartsWith("S-1-")) { - await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus + await HandleCompStatusEvent(new CSVComputerStatus { Status = ComputerStatus.Success, ComputerName = resolvedSearchResult.DisplayName, Task = nameof(ProcessEnterpriseCA), ObjectId = sid, - }, - _cancellationToken); + }); ret.HostingComputer = sid; } else { _log.LogWarning("CA {Name} host ({Dns}) could not be resolved to a SID.", caName, dnsHostName); } CARegistryData cARegistryData = new() { - IsUserSpecifiesSanEnabled = _certAbuseProcessor.IsUserSpecifiesSanEnabled(dnsHostName, caName), + IsUserSpecifiesSanEnabled = await _certAbuseProcessor.IsUserSpecifiesSanEnabled(dnsHostName, caName, ret.HostingComputer), EnrollmentAgentRestrictions = await _certAbuseProcessor.ProcessEAPermissions(caName, resolvedSearchResult.Domain, dnsHostName, ret.HostingComputer), - RoleSeparationEnabled = _certAbuseProcessor.RoleSeparationEnabled(dnsHostName, caName), + RoleSeparationEnabled = await _certAbuseProcessor.IsRoleSeparationEnabled(dnsHostName, caName, ret.HostingComputer), // The CASecurity exist in the AD object DACL and in registry of the CA server. We prefer to use the values from registry as they are the ground truth. // If changes are made on the CA server, registry and the AD object is updated. If changes are made directly on the AD object, the CA server registry is not updated.