mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
Updated rules to new events etc.
This commit is contained in:
@@ -16,8 +16,9 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes gather rules received from the backend API
|
||||
/// Supports registry, eventlog, wmi, file, command (allowlisted), and logparser collector types
|
||||
/// </summary>
|
||||
/// Supports registry, eventlog, wmi, file, command_allowlisted, and logparser collector types
|
||||
/// NOTE: "command" is accepted as a legacy alias for "command_allowlisted" - both enforce the allowlist
|
||||
///</summary>
|
||||
public class GatherRuleExecutor : IDisposable
|
||||
{
|
||||
private readonly AgentLogger _logger;
|
||||
@@ -167,7 +168,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
|
||||
result = ExecuteWmiRule(rule);
|
||||
break;
|
||||
|
||||
case "command":
|
||||
case "command_allowlisted":
|
||||
case "command": // legacy alias - both enforce the allowlist
|
||||
result = ExecuteCommandRule(rule);
|
||||
break;
|
||||
|
||||
|
||||
@@ -29,9 +29,16 @@ namespace AutopilotMonitor.Functions.Services
|
||||
CreateDeviceBitlockerEscrowRule(),
|
||||
CreateDeviceSecureBootRule(),
|
||||
|
||||
// ===== DEVICE RULES (continued) =====
|
||||
CreateDeviceAadJoinNotDetectedRule(),
|
||||
|
||||
// ===== APP RULES (continued) =====
|
||||
CreateAppTrackingSummaryErrorsRule(),
|
||||
|
||||
// ===== CORRELATION RULES (cross-event analysis) =====
|
||||
CreateCorrelationNetworkCausedInstallFailureRule(),
|
||||
CreateCorrelationDiskSpaceCausedInstallFailureRule(),
|
||||
CreateCorrelationProxyCausedDownloadFailureRule(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -151,7 +158,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
BaseConfidence = 70,
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new RuleCondition { Signal = "reboot_loop", Source = "event_count", EventType = "app_install_start", Operator = "count_gte", Value = "3", Required = true }
|
||||
new RuleCondition { Signal = "reboot_loop", Source = "event_count", EventType = "app_install_started", Operator = "count_gte", Value = "3", Required = true }
|
||||
},
|
||||
Explanation = "An app appears to be in a reboot loop - it has been attempted multiple times. This may indicate that the app requires a reboot that is not being honored by the ESP.",
|
||||
Remediation = new List<RemediationStep>
|
||||
@@ -173,7 +180,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
BaseConfidence = 60,
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new RuleCondition { Signal = "esp_stalled", Source = "phase_duration", EventType = "phase_changed", DataField = "currentPhase", Operator = "equals", Value = "EspDeviceSetup", Required = true }
|
||||
new RuleCondition { Signal = "esp_stalled", Source = "phase_duration", EventType = "esp_phase_changed", DataField = "espPhase", Operator = "equals", Value = "DeviceSetup", Required = true }
|
||||
},
|
||||
ConfidenceFactors = new List<ConfidenceFactor>
|
||||
{
|
||||
@@ -259,7 +266,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
BaseConfidence = 60,
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new RuleCondition { Signal = "secureboot_disabled", Source = "event_type", EventType = "gather_secureboot_status", DataField = "UEFISecureBootEnabled", Operator = "equals", Value = "0", Required = true }
|
||||
new RuleCondition { Signal = "secureboot_disabled", Source = "event_type", EventType = "secureboot_status", DataField = "uefiSecureBootEnabled", Operator = "equals", Value = "False", Required = true }
|
||||
},
|
||||
Explanation = "Secure Boot is disabled on this device. Some compliance policies require Secure Boot to be enabled.",
|
||||
Remediation = new List<RemediationStep>
|
||||
@@ -272,6 +279,61 @@ namespace AutopilotMonitor.Functions.Services
|
||||
// ===== CORRELATION RULES =====
|
||||
// These combine signals from multiple event types for root-cause analysis
|
||||
|
||||
private static AnalyzeRule CreateDeviceAadJoinNotDetectedRule() => new AnalyzeRule
|
||||
{
|
||||
RuleId = "ANALYZE-DEV-004",
|
||||
Title = "Azure AD Join Not Detected",
|
||||
Description = "Detects when the device is not Azure AD joined at the time of enrollment.",
|
||||
Severity = "critical",
|
||||
Category = "device",
|
||||
BaseConfidence = 85,
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new RuleCondition { Signal = "aad_not_joined", Source = "event_type", EventType = "aad_join_status", DataField = "joinType", Operator = "equals", Value = "Not Joined", Required = true }
|
||||
},
|
||||
Explanation = "The device was not Azure AD joined at the start of enrollment. Autopilot requires the device to join Azure AD. This will prevent Intune enrollment and policy delivery from succeeding.",
|
||||
Remediation = new List<RemediationStep>
|
||||
{
|
||||
new RemediationStep { Title = "Verify Azure AD connectivity", Steps = new List<string> {
|
||||
"Ensure the device can reach login.microsoftonline.com",
|
||||
"Check that the Autopilot profile's tenant matches the signing-in user's tenant",
|
||||
"Verify the device is not already joined to an on-premises domain (Hybrid Join may be required)"
|
||||
}}
|
||||
},
|
||||
Tags = new[] { "device", "aad", "join", "critical" }
|
||||
};
|
||||
|
||||
private static AnalyzeRule CreateAppTrackingSummaryErrorsRule() => new AnalyzeRule
|
||||
{
|
||||
RuleId = "ANALYZE-APP-007",
|
||||
Title = "Multiple App Installation Failures",
|
||||
Description = "Detects when the app tracking summary reports multiple failed app installations.",
|
||||
Severity = "high",
|
||||
Category = "apps",
|
||||
BaseConfidence = 75,
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new RuleCondition { Signal = "summary_errors", Source = "event_type", EventType = "app_tracking_summary", DataField = "errorCount", Operator = "gte", Value = "2", Required = true }
|
||||
},
|
||||
ConfidenceFactors = new List<ConfidenceFactor>
|
||||
{
|
||||
new ConfidenceFactor { Signal = "enrollment_failed", Condition = "exists", Weight = 15 }
|
||||
},
|
||||
Explanation = "The app tracking summary reports 2 or more failed app installations during enrollment. This suggests a systemic issue rather than an isolated app failure.",
|
||||
Remediation = new List<RemediationStep>
|
||||
{
|
||||
new RemediationStep { Title = "Review failed apps", Steps = new List<string> {
|
||||
"Check the individual app_install_failed events for specific error codes",
|
||||
"Look for a common pattern (all MSI apps, all large apps, all from a specific publisher)",
|
||||
"Consider whether a shared prerequisite or dependency is failing"
|
||||
}}
|
||||
},
|
||||
Tags = new[] { "apps", "summary", "multiple-failures" }
|
||||
};
|
||||
|
||||
// ===== CORRELATION RULES =====
|
||||
// These combine signals from multiple event types for root-cause analysis
|
||||
|
||||
private static AnalyzeRule CreateCorrelationNetworkCausedInstallFailureRule() => new AnalyzeRule
|
||||
{
|
||||
RuleId = "ANALYZE-CORR-003",
|
||||
@@ -347,5 +409,44 @@ namespace AutopilotMonitor.Functions.Services
|
||||
},
|
||||
Tags = new[] { "correlation", "apps", "disk-space", "performance", "root-cause" }
|
||||
};
|
||||
|
||||
private static AnalyzeRule CreateCorrelationProxyCausedDownloadFailureRule() => new AnalyzeRule
|
||||
{
|
||||
RuleId = "ANALYZE-CORR-005",
|
||||
Title = "Proxy Configuration Causing Download Failure",
|
||||
Description = "Correlates an active proxy configuration with app download failures, suggesting the proxy is blocking or throttling Intune content delivery.",
|
||||
Severity = "high",
|
||||
Category = "apps",
|
||||
Trigger = "correlation",
|
||||
BaseConfidence = 60,
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new RuleCondition { Signal = "proxy_active", Source = "event_type", EventType = "proxy_configuration", DataField = "proxyType", Operator = "regex", Value = "Proxy|PAC", Required = true },
|
||||
new RuleCondition { Signal = "app_failed", Source = "event_type", EventType = "app_install_failed", Operator = "exists", Value = "", Required = true }
|
||||
},
|
||||
ConfidenceFactors = new List<ConfidenceFactor>
|
||||
{
|
||||
new ConfidenceFactor { Signal = "download_timeout", Condition = "exists", Weight = 20 },
|
||||
new ConfidenceFactor { Signal = "multiple_failures", Condition = "count >= 2", Weight = 15 },
|
||||
new ConfidenceFactor { Signal = "enrollment_failed", Condition = "exists", Weight = 10 }
|
||||
},
|
||||
ConfidenceThreshold = 50,
|
||||
Explanation = "A proxy (explicit or PAC-based) is configured on this device and app installations failed. Proxies can block or throttle downloads from *.delivery.mp.microsoft.com and other Intune CDN endpoints.\n\nThis is a common root cause when apps fail consistently on proxy-connected corporate networks.",
|
||||
Remediation = new List<RemediationStep>
|
||||
{
|
||||
new RemediationStep { Title = "Verify proxy bypass for Intune endpoints", Steps = new List<string> {
|
||||
"Add *.delivery.mp.microsoft.com to proxy bypass list",
|
||||
"Add *.do.dsp.mp.microsoft.com (Delivery Optimization) to bypass list",
|
||||
"Add login.microsoftonline.com and *.manage.microsoft.com to bypass list",
|
||||
"Verify the PAC file is reachable at enrollment time (before user login)"
|
||||
}},
|
||||
new RemediationStep { Title = "Check proxy authentication", Steps = new List<string> {
|
||||
"Ensure the proxy does not require user authentication during OOBE/device phase",
|
||||
"Consider using machine certificate-based proxy authentication",
|
||||
"Test with proxy temporarily bypassed to confirm it is the root cause"
|
||||
}}
|
||||
},
|
||||
Tags = new[] { "correlation", "apps", "proxy", "network", "download", "root-cause" }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
{
|
||||
// Find phase change events to calculate phase duration
|
||||
var phaseEvents = events
|
||||
.Where(e => e.EventType == "phase_changed")
|
||||
.Where(e => e.EventType == "esp_phase_changed")
|
||||
.OrderBy(e => e.Timestamp)
|
||||
.ToList();
|
||||
|
||||
@@ -233,8 +233,8 @@ namespace AutopilotMonitor.Functions.Services
|
||||
for (int i = 0; i < phaseEvents.Count; i++)
|
||||
{
|
||||
var phaseData = phaseEvents[i].Data;
|
||||
var currentPhase = phaseData?.ContainsKey("currentPhase") == true
|
||||
? phaseData["currentPhase"]?.ToString()
|
||||
var currentPhase = phaseData?.ContainsKey("espPhase") == true
|
||||
? phaseData["espPhase"]?.ToString()
|
||||
: null;
|
||||
|
||||
if (currentPhase == targetPhase)
|
||||
|
||||
@@ -59,29 +59,38 @@ namespace AutopilotMonitor.Shared.Models
|
||||
// ===== WHAT TO COLLECT =====
|
||||
|
||||
/// <summary>
|
||||
/// Type of data collection: "registry", "eventlog", "wmi", "file", "command", "logparser"
|
||||
/// Type of data collection:
|
||||
/// - "registry": Read values from the Windows Registry
|
||||
/// - "eventlog": Read entries from a Windows Event Log
|
||||
/// - "wmi": Execute a WMI/CIM query
|
||||
/// - "file": Check file/directory existence and optionally read content
|
||||
/// - "command_allowlisted": Run a pre-approved command (PowerShell or CLI). Only commands on
|
||||
/// the agent's hardcoded allowlist in GatherRuleExecutor.cs are permitted. Unlisted commands
|
||||
/// are blocked and generate a security_warning event. See the allowlist in GatherRuleExecutor.cs
|
||||
/// for the full list of approved commands.
|
||||
/// - "logparser": Parse a CMTrace-format log file using a regex pattern with named capture groups
|
||||
/// </summary>
|
||||
public string CollectorType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Target for collection:
|
||||
/// - registry: Registry path (e.g., "HKLM:\SOFTWARE\Microsoft\...")
|
||||
/// - eventlog: Event log name (e.g., "Microsoft-Windows-DeviceManagement...")
|
||||
/// - wmi: WMI query (e.g., "SELECT * FROM Win32_TPM")
|
||||
/// - file: File path (e.g., "C:\Windows\INF\setupapi.dev.log")
|
||||
/// - command: Command name from allowlist (e.g., "Get-TpmStatus")
|
||||
/// - registry: Registry path (e.g., "HKLM\SOFTWARE\Microsoft\...")
|
||||
/// - eventlog: Event log name (e.g., "Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin")
|
||||
/// - wmi: WMI query (e.g., "SELECT * FROM Win32_TPM WHERE __NAMESPACE='root\\CIMV2\\Security\\MicrosoftTpm'")
|
||||
/// - file: File or directory path with env vars (e.g., "C:\Windows\INF\setupapi.dev.log")
|
||||
/// - command_allowlisted: Exact command string as it appears in the allowlist (e.g., "Get-Tpm", "dsregcmd /status")
|
||||
/// - logparser: Log file path with env vars (e.g., "%ProgramData%\Microsoft\IntuneManagementExtension\Logs\AppWorkload.log")
|
||||
/// </summary>
|
||||
public string Target { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional parameters for the collector
|
||||
/// - registry: { "values": "ProxyServer,ProxyEnable" }
|
||||
/// - eventlog: { "eventIds": "407,12029", "maxEvents": "10" }
|
||||
/// Additional parameters for the collector:
|
||||
/// - registry: { "valueName": "ProxyServer" } — omit to read all values
|
||||
/// - eventlog: { "maxEntries": "10", "source": "Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics" }
|
||||
/// - wmi: { "namespace": "root\\CIMV2\\Security\\MicrosoftTpm" }
|
||||
/// - file: { "maxLines": "100", "pattern": "error|fail" }
|
||||
/// - command: { "arguments": "-Detailed" }
|
||||
/// - logparser: { "pattern": "regex with (?<named>groups)", "logFormat": "cmtrace", "trackPosition": "true", "maxLines": "1000" }
|
||||
/// - file: { "readContent": "true" } — only reads files <50 KB
|
||||
/// - command_allowlisted: (no additional parameters — command is the full string in Target)
|
||||
/// - logparser: { "pattern": "regex with (?<namedGroups>)", "trackPosition": "true", "maxLines": "1000" }
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Parameters { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ interface NewRuleForm {
|
||||
}
|
||||
|
||||
const CATEGORIES = ["network", "identity", "apps", "device", "esp", "enrollment"] as const;
|
||||
const COLLECTOR_TYPES = ["registry", "eventlog", "wmi", "file", "command"] as const;
|
||||
const COLLECTOR_TYPES = ["registry", "eventlog", "wmi", "file", "command_allowlisted", "logparser"] as const;
|
||||
const TRIGGERS = ["startup", "phase_change", "interval", "on_event"] as const;
|
||||
const SEVERITIES = ["info", "warning", "error", "critical"] as const;
|
||||
|
||||
@@ -65,7 +65,9 @@ const COLLECTOR_TYPE_LABELS: Record<string, string> = {
|
||||
eventlog: "Event Log",
|
||||
wmi: "WMI Query",
|
||||
file: "File",
|
||||
command: "Command",
|
||||
command_allowlisted: "Command (Allowlisted)",
|
||||
command: "Command (Allowlisted)", // legacy alias
|
||||
logparser: "Log Parser",
|
||||
};
|
||||
|
||||
const EMPTY_FORM: NewRuleForm = {
|
||||
@@ -506,7 +508,7 @@ export default function GatherRulesPage() {
|
||||
>
|
||||
{COLLECTOR_TYPES.map((ct) => (
|
||||
<option key={ct} value={ct}>
|
||||
{ct.charAt(0).toUpperCase() + ct.slice(1)}
|
||||
{COLLECTOR_TYPE_LABELS[ct] || (ct.charAt(0).toUpperCase() + ct.slice(1))}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
Reference in New Issue
Block a user