agent: secureboot certs incl. analyze rule, timezone auto set, NTP offset check

This commit is contained in:
Oliver Kieselbach
2026-03-26 09:23:12 +01:00
parent e8e586aa19
commit f6ed4660d1
14 changed files with 608 additions and 4 deletions
+58
View File
@@ -0,0 +1,58 @@
{
"$schema": "../schema/analyze-rule.schema.json",
"ruleId": "ANALYZE-SEC-001",
"title": "Secure Boot UEFI CA 2023 certificate not deployed",
"description": "Detects devices where the Windows UEFI CA 2023 certificate has not been deployed to the Secure Boot DB. These devices will lose Secure Boot security update support when the 2011 certificates expire in June 2026.",
"severity": "warning",
"category": "security",
"enabled": true,
"baseConfidence": 70,
"conditions": [
{
"signal": "ca2023_not_updated",
"source": "event_data",
"eventType": "secureboot_status",
"dataField": "data.uefiCA2023Status",
"operator": "not_equals",
"value": "Updated",
"required": true
},
{
"signal": "secureboot_enabled",
"source": "event_data",
"eventType": "secureboot_status",
"dataField": "data.uefiSecureBootEnabled",
"operator": "equals",
"value": "true",
"required": true
}
],
"confidenceFactors": [
{ "signal": "ca2023_notfound", "condition": "equals:notfound", "weight": 20 },
{ "signal": "ca2023_error", "condition": "exists", "weight": 10 }
],
"explanation": "This device has Secure Boot enabled but the **Windows UEFI CA 2023** certificate has not been deployed to the Secure Boot DB yet.\n\nThe original Windows UEFI CA 2011 certificates expire on **June 27, 2026**. After expiration, devices without the 2023 certificate will no longer receive Secure Boot security updates and may fail to boot with updated boot managers.\n\nCurrent UEFI CA 2023 status: `{{data.uefiCA2023Status}}`",
"remediation": [
{
"title": "Deploy UEFI CA 2023 certificate",
"steps": [
"Deploy KB5036210 via Windows Update or WSUS",
"Use Intune policy to enable Secure Boot DB update (Windows UEFI CA 2023)",
"Alternatively, use the WinCS CLI tool from Microsoft for manual deployment",
"Verify deployment: check registry HKLM\\SYSTEM\\CurrentControlSet\\Control\\SecureBoot\\Servicing\\UEFICA2023Status = 'Updated'",
"After DB update: ensure the boot manager is updated to a 2023-signed version via monthly cumulative update"
]
}
],
"relatedDocs": [
{
"title": "Microsoft: Secure Boot certificates expire June 2026",
"url": "https://techcommunity.microsoft.com/blog/windows-itpro-blog/act-now-secure-boot-certificates-expire-in-june-2026/4426856"
},
{
"title": "KB5036210: Deploying Windows UEFI CA 2023",
"url": "https://support.microsoft.com/en-us/topic/kb5036210"
}
],
"tags": ["security", "secureboot", "certificate", "ca2023", "compliance"]
}
+71
View File
@@ -1187,6 +1187,77 @@
"critical",
"security"
]
},
{
"ruleId": "ANALYZE-SEC-001",
"title": "Secure Boot UEFI CA 2023 certificate not deployed",
"description": "Detects devices where the Windows UEFI CA 2023 certificate has not been deployed to the Secure Boot DB. These devices will lose Secure Boot security update support when the 2011 certificates expire in June 2026.",
"severity": "warning",
"category": "security",
"enabled": true,
"baseConfidence": 70,
"conditions": [
{
"signal": "ca2023_not_updated",
"source": "event_data",
"eventType": "secureboot_status",
"dataField": "data.uefiCA2023Status",
"operator": "not_equals",
"value": "Updated",
"required": true
},
{
"signal": "secureboot_enabled",
"source": "event_data",
"eventType": "secureboot_status",
"dataField": "data.uefiSecureBootEnabled",
"operator": "equals",
"value": "true",
"required": true
}
],
"confidenceFactors": [
{
"signal": "ca2023_notfound",
"condition": "equals:notfound",
"weight": 20
},
{
"signal": "ca2023_error",
"condition": "exists",
"weight": 10
}
],
"explanation": "This device has Secure Boot enabled but the **Windows UEFI CA 2023** certificate has not been deployed to the Secure Boot DB yet.\n\nThe original Windows UEFI CA 2011 certificates expire on **June 27, 2026**. After expiration, devices without the 2023 certificate will no longer receive Secure Boot security updates and may fail to boot with updated boot managers.\n\nCurrent UEFI CA 2023 status: `{{data.uefiCA2023Status}}`",
"remediation": [
{
"title": "Deploy UEFI CA 2023 certificate",
"steps": [
"Deploy KB5036210 via Windows Update or WSUS",
"Use Intune policy to enable Secure Boot DB update (Windows UEFI CA 2023)",
"Alternatively, use the WinCS CLI tool from Microsoft for manual deployment",
"Verify deployment: check registry HKLM\\SYSTEM\\CurrentControlSet\\Control\\SecureBoot\\Servicing\\UEFICA2023Status = 'Updated'",
"After DB update: ensure the boot manager is updated to a 2023-signed version via monthly cumulative update"
]
}
],
"relatedDocs": [
{
"title": "Microsoft: Secure Boot certificates expire June 2026",
"url": "https://techcommunity.microsoft.com/blog/windows-itpro-blog/act-now-secure-boot-certificates-expire-in-june-2026/4426856"
},
{
"title": "KB5036210: Deploying Windows UEFI CA 2023",
"url": "https://support.microsoft.com/en-us/topic/kb5036210"
}
],
"tags": [
"security",
"secureboot",
"certificate",
"ca2023",
"compliance"
]
}
]
}
+6
View File
@@ -53,6 +53,12 @@
"SOFTWARE\\Microsoft\\Tpm"
]
},
{
"category": "Secure Boot",
"prefixes": [
"SYSTEM\\CurrentControlSet\\Control\\SecureBoot"
]
},
{
"category": "Intune IME",
"prefixes": [
@@ -100,6 +100,19 @@ namespace AutopilotMonitor.Agent.Core.Configuration
/// </summary>
public bool EnableGeoLocation { get; set; } = true;
/// <summary>
/// NTP server address for time check during enrollment.
/// Default: "time.windows.com"
/// </summary>
public string NtpServer { get; set; } = "time.windows.com";
/// <summary>
/// Whether to automatically set the device timezone based on IP geolocation.
/// Requires EnableGeoLocation to be true. Uses tzutil /s to apply.
/// Default: false
/// </summary>
public bool EnableTimezoneAutoSet { get; set; } = false;
/// <summary>
/// Optional custom path to IME logs directory for testing.
/// If set, overrides the default %ProgramData%\Microsoft\IntuneManagementExtension\Logs path.
@@ -301,7 +301,27 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
}
}
EmitDeviceInfoEvent("secureboot_status", $"SecureBoot: {data["uefiSecureBootEnabled"]}", data);
// UEFI CA 2023 certificate deployment status (expires June 2026)
using (var servicingKey = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing"))
{
if (servicingKey != null)
{
data["uefiCA2023Status"] = servicingKey.GetValue("UEFICA2023Status")?.ToString() ?? "unknown";
var capable = servicingKey.GetValue("WindowsUEFICA2023Capable");
if (capable != null)
data["windowsUefiCA2023Capable"] = Convert.ToInt32(capable);
var error = servicingKey.GetValue("UEFICA2023Error");
if (error != null && Convert.ToInt32(error) != 0)
data["uefiCA2023Error"] = Convert.ToInt32(error);
data["confidenceLevel"] = servicingKey.GetValue("ConfidenceLevel")?.ToString() ?? "unknown";
}
else
{
data["uefiCA2023Status"] = "notfound";
}
}
EmitDeviceInfoEvent("secureboot_status", $"SecureBoot: {data["uefiSecureBootEnabled"]}, CA2023: {data["uefiCA2023Status"]}", data);
}
catch (Exception ex)
{
@@ -263,6 +263,9 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
Task.Run(EmitGeoLocationEvent);
}
// NTP time check — diagnostic, not critical path
Task.Run(EmitNtpTimeCheckEvent);
// Start event collectors (HelloCollector + optional based on remote config)
StartEventCollectors();
@@ -323,6 +326,39 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
}
});
}
// Auto-set timezone if enabled and IANA timezone available from geolocation
if (_configuration.EnableTimezoneAutoSet && !string.IsNullOrEmpty(attempt.Location?.Timezone))
{
try
{
var tzResult = TimezoneService.TrySetTimezone(attempt.Location.Timezone, _logger);
EmitEvent(new EnrollmentEvent
{
SessionId = _configuration.SessionId,
TenantId = _configuration.TenantId,
EventType = "timezone_auto_set",
Severity = tzResult.Success ? EventSeverity.Info : EventSeverity.Warning,
Source = "Network",
Phase = EnrollmentPhase.Unknown,
Message = tzResult.Success
? $"Timezone set to {tzResult.WindowsTimezoneId} (from {tzResult.IanaTimezone})"
: $"Timezone auto-set failed: {tzResult.Error}",
Data = new Dictionary<string, object>
{
{ "ianaTimezone", tzResult.IanaTimezone ?? "" },
{ "windowsTimezoneId", tzResult.WindowsTimezoneId ?? "unknown" },
{ "previousTimezone", tzResult.PreviousTimezone ?? "unknown" },
{ "success", tzResult.Success },
{ "error", tzResult.Error ?? "" }
}
});
}
catch (Exception tzEx)
{
_logger.Warning($"Timezone auto-set failed: {tzEx.Message}");
}
}
}
catch (Exception ex)
{
@@ -330,6 +366,62 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
}
}
private void EmitNtpTimeCheckEvent()
{
try
{
var ntpServer = _configuration.NtpServer ?? "time.windows.com";
var result = NtpTimeCheckService.CheckTime(ntpServer, _logger);
if (result.Success)
{
var severity = Math.Abs(result.OffsetSeconds) > 60
? EventSeverity.Warning
: EventSeverity.Info;
EmitEvent(new EnrollmentEvent
{
SessionId = _configuration.SessionId,
TenantId = _configuration.TenantId,
EventType = "ntp_time_check",
Severity = severity,
Source = "Network",
Phase = EnrollmentPhase.Unknown,
Message = $"NTP time check: offset {result.OffsetSeconds:F2}s from {ntpServer}",
Data = new Dictionary<string, object>
{
{ "ntpServer", ntpServer },
{ "offsetSeconds", result.OffsetSeconds },
{ "ntpTimeUtc", result.NtpTime?.ToString("o") ?? "" },
{ "localTimeUtc", result.LocalTime?.ToString("o") ?? "" }
}
});
}
else
{
EmitEvent(new EnrollmentEvent
{
SessionId = _configuration.SessionId,
TenantId = _configuration.TenantId,
EventType = "ntp_time_check",
Severity = EventSeverity.Warning,
Source = "Network",
Phase = EnrollmentPhase.Unknown,
Message = $"NTP time check failed: {result.Error}",
Data = new Dictionary<string, object>
{
{ "ntpServer", ntpServer },
{ "error", result.Error ?? "unknown" }
}
});
}
}
catch (Exception ex)
{
_logger.Warning($"NTP time check failed: {ex.Message}");
}
}
/// <summary>
/// Starts all event collection components
/// </summary>
@@ -432,6 +524,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
_configuration.EnrollmentSummaryTimeoutSeconds = config.EnrollmentSummaryTimeoutSeconds;
_configuration.EnrollmentSummaryBrandingImageUrl = config.EnrollmentSummaryBrandingImageUrl;
_configuration.EnrollmentSummaryLaunchRetrySeconds = config.EnrollmentSummaryLaunchRetrySeconds;
_configuration.NtpServer = config.NtpServer;
_configuration.EnableTimezoneAutoSet = config.EnableTimezoneAutoSet;
_configuration.SendTraceEvents = config.SendTraceEvents;
AuditUnrestrictedModeChange(config);
_enrollmentTracker?.UpdateSendTraceEvents(config.SendTraceEvents);
@@ -442,6 +536,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
_logger.Info($" maxAuthFailures={_configuration.MaxAuthFailures}, authFailureTimeoutMinutes={_configuration.AuthFailureTimeoutMinutes}");
_logger.Info($" logLevel={_configuration.LogLevel}, rebootOnComplete={_configuration.RebootOnComplete}, maxBatchSize={_configuration.MaxBatchSize}");
_logger.Info($" showEnrollmentSummary={_configuration.ShowEnrollmentSummary}, summaryTimeoutSeconds={_configuration.EnrollmentSummaryTimeoutSeconds}");
_logger.Info($" ntpServer={_configuration.NtpServer}, enableTimezoneAutoSet={_configuration.EnableTimezoneAutoSet}");
}
private void AuditUnrestrictedModeChange(AgentConfigResponse config)
@@ -0,0 +1,87 @@
using System;
using System.Net;
using System.Net.Sockets;
using AutopilotMonitor.Agent.Core.Logging;
namespace AutopilotMonitor.Agent.Core.Monitoring.Network
{
public class NtpCheckResult
{
public bool Success { get; set; }
public double OffsetSeconds { get; set; }
public string NtpServer { get; set; }
public string Error { get; set; }
public DateTime? NtpTime { get; set; }
public DateTime? LocalTime { get; set; }
}
public static class NtpTimeCheckService
{
private static readonly int ReceiveTimeoutMs = 3000;
/// <summary>
/// Queries an NTP server and returns the time offset between local clock and NTP time.
/// Based on NTP v3 client mode (LI=0, VN=3, Mode=3).
/// </summary>
public static NtpCheckResult CheckTime(string ntpServer, AgentLogger logger)
{
var result = new NtpCheckResult { NtpServer = ntpServer };
try
{
logger.Info($"NTP time check: querying {ntpServer}");
// NTP request packet: 48 bytes, first byte = 0x1B (LI=0, VN=3, Mode=3)
var ntpData = new byte[48];
ntpData[0] = 0x1B;
var addresses = Dns.GetHostEntry(ntpServer).AddressList;
if (addresses.Length == 0)
{
result.Error = $"DNS resolution returned no addresses for {ntpServer}";
logger.Warning($"NTP time check failed: {result.Error}");
return result;
}
var ipEndPoint = new IPEndPoint(addresses[0], 123);
using (var socket = new Socket(addresses[0].AddressFamily, SocketType.Dgram, ProtocolType.Udp))
{
socket.ReceiveTimeout = ReceiveTimeoutMs;
socket.Connect(ipEndPoint);
socket.Send(ntpData);
socket.Receive(ntpData);
}
// Parse transmit timestamp from bytes 40-47 (NTP epoch: 1900-01-01)
ulong intPart = (ulong)ntpData[40] << 24
| (ulong)ntpData[41] << 16
| (ulong)ntpData[42] << 8
| (ulong)ntpData[43];
ulong fractPart = (ulong)ntpData[44] << 24
| (ulong)ntpData[45] << 16
| (ulong)ntpData[46] << 8
| (ulong)ntpData[47];
var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
var ntpTime = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(milliseconds);
var localTime = DateTime.UtcNow;
var offset = ntpTime.Subtract(localTime);
result.Success = true;
result.NtpTime = ntpTime;
result.LocalTime = localTime;
result.OffsetSeconds = offset.TotalSeconds;
logger.Info($"NTP time check: offset={offset.TotalSeconds:F2}s (NTP={ntpTime:o}, Local={localTime:o})");
return result;
}
catch (Exception ex)
{
result.Error = ex.Message;
logger.Warning($"NTP time check failed for {ntpServer}: {ex.Message}");
return result;
}
}
}
}
@@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using AutopilotMonitor.Agent.Core.Logging;
namespace AutopilotMonitor.Agent.Core.Monitoring.Network
{
public class TimezoneSetResult
{
public bool Success { get; set; }
public string IanaTimezone { get; set; }
public string WindowsTimezoneId { get; set; }
public string PreviousTimezone { get; set; }
public string Error { get; set; }
}
public static class TimezoneService
{
/// <summary>
/// Attempts to set the Windows timezone based on an IANA timezone identifier.
/// Uses tzutil /s to apply the timezone (works from SYSTEM context).
/// </summary>
public static TimezoneSetResult TrySetTimezone(string ianaTimezone, AgentLogger logger)
{
var result = new TimezoneSetResult { IanaTimezone = ianaTimezone };
try
{
// Map IANA timezone to Windows timezone ID
if (!IanaToWindowsMap.TryGetValue(ianaTimezone, out var windowsId))
{
result.Error = $"No Windows timezone mapping found for IANA timezone '{ianaTimezone}'";
logger.Warning($"Timezone auto-set: {result.Error}");
return result;
}
result.WindowsTimezoneId = windowsId;
result.PreviousTimezone = TimeZoneInfo.Local.Id;
// Skip if already set to the target timezone
if (string.Equals(result.PreviousTimezone, windowsId, StringComparison.OrdinalIgnoreCase))
{
result.Success = true;
logger.Info($"Timezone auto-set: already set to {windowsId} (IANA: {ianaTimezone})");
return result;
}
// Execute tzutil /s to set the timezone
logger.Info($"Timezone auto-set: setting timezone to {windowsId} (IANA: {ianaTimezone}, previous: {result.PreviousTimezone})");
var psi = new ProcessStartInfo
{
FileName = "tzutil",
Arguments = $"/s \"{windowsId}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (var process = Process.Start(psi))
{
var stderr = process.StandardError.ReadToEnd();
process.WaitForExit(10000); // 10s timeout
if (process.ExitCode != 0)
{
result.Error = $"tzutil exited with code {process.ExitCode}: {stderr.Trim()}";
logger.Warning($"Timezone auto-set failed: {result.Error}");
return result;
}
}
result.Success = true;
logger.Info($"Timezone auto-set: successfully set to {windowsId}");
return result;
}
catch (Exception ex)
{
result.Error = ex.Message;
logger.Warning($"Timezone auto-set failed: {ex.Message}");
return result;
}
}
/// <summary>
/// IANA timezone to Windows timezone ID mapping.
/// Sourced from Unicode CLDR / IANA timezone database.
/// Covers all major regions and practical timezones.
/// </summary>
private static readonly Dictionary<string, string> IanaToWindowsMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
// UTC
{ "Etc/UTC", "UTC" },
{ "Etc/GMT", "UTC" },
{ "UTC", "UTC" },
// Europe
{ "Europe/London", "GMT Standard Time" },
{ "Europe/Dublin", "GMT Standard Time" },
{ "Europe/Lisbon", "GMT Standard Time" },
{ "Europe/Berlin", "W. Europe Standard Time" },
{ "Europe/Amsterdam", "W. Europe Standard Time" },
{ "Europe/Zurich", "W. Europe Standard Time" },
{ "Europe/Vienna", "W. Europe Standard Time" },
{ "Europe/Rome", "W. Europe Standard Time" },
{ "Europe/Stockholm", "W. Europe Standard Time" },
{ "Europe/Oslo", "W. Europe Standard Time" },
{ "Europe/Copenhagen", "Romance Standard Time" },
{ "Europe/Paris", "Romance Standard Time" },
{ "Europe/Brussels", "Romance Standard Time" },
{ "Europe/Madrid", "Romance Standard Time" },
{ "Europe/Warsaw", "Central European Standard Time" },
{ "Europe/Prague", "Central Europe Standard Time" },
{ "Europe/Budapest", "Central Europe Standard Time" },
{ "Europe/Belgrade", "Central Europe Standard Time" },
{ "Europe/Bratislava", "Central Europe Standard Time" },
{ "Europe/Ljubljana", "Central Europe Standard Time" },
{ "Europe/Zagreb", "Central Europe Standard Time" },
{ "Europe/Athens", "GTB Standard Time" },
{ "Europe/Bucharest", "GTB Standard Time" },
{ "Europe/Helsinki", "FLE Standard Time" },
{ "Europe/Kiev", "FLE Standard Time" },
{ "Europe/Kyiv", "FLE Standard Time" },
{ "Europe/Riga", "FLE Standard Time" },
{ "Europe/Sofia", "FLE Standard Time" },
{ "Europe/Tallinn", "FLE Standard Time" },
{ "Europe/Vilnius", "FLE Standard Time" },
{ "Europe/Istanbul", "Turkey Standard Time" },
{ "Europe/Moscow", "Russian Standard Time" },
{ "Europe/Minsk", "Belarus Standard Time" },
{ "Europe/Samara", "Russia Time Zone 3" },
// Americas
{ "America/New_York", "Eastern Standard Time" },
{ "America/Detroit", "Eastern Standard Time" },
{ "America/Toronto", "Eastern Standard Time" },
{ "America/Chicago", "Central Standard Time" },
{ "America/Winnipeg", "Central Standard Time" },
{ "America/Denver", "Mountain Standard Time" },
{ "America/Edmonton", "Mountain Standard Time" },
{ "America/Phoenix", "US Mountain Standard Time" },
{ "America/Los_Angeles", "Pacific Standard Time" },
{ "America/Vancouver", "Pacific Standard Time" },
{ "America/Anchorage", "Alaskan Standard Time" },
{ "Pacific/Honolulu", "Hawaiian Standard Time" },
{ "America/Halifax", "Atlantic Standard Time" },
{ "America/St_Johns", "Newfoundland Standard Time" },
{ "America/Mexico_City", "Central Standard Time (Mexico)" },
{ "America/Tijuana", "Pacific Standard Time (Mexico)" },
{ "America/Bogota", "SA Pacific Standard Time" },
{ "America/Lima", "SA Pacific Standard Time" },
{ "America/Santiago", "Pacific SA Standard Time" },
{ "America/Buenos_Aires", "Argentina Standard Time" },
{ "America/Argentina/Buenos_Aires", "Argentina Standard Time" },
{ "America/Sao_Paulo", "E. South America Standard Time" },
{ "America/Caracas", "Venezuela Standard Time" },
// Asia
{ "Asia/Tokyo", "Tokyo Standard Time" },
{ "Asia/Seoul", "Korea Standard Time" },
{ "Asia/Shanghai", "China Standard Time" },
{ "Asia/Hong_Kong", "China Standard Time" },
{ "Asia/Taipei", "Taipei Standard Time" },
{ "Asia/Singapore", "Singapore Standard Time" },
{ "Asia/Kuala_Lumpur", "Singapore Standard Time" },
{ "Asia/Bangkok", "SE Asia Standard Time" },
{ "Asia/Jakarta", "SE Asia Standard Time" },
{ "Asia/Ho_Chi_Minh", "SE Asia Standard Time" },
{ "Asia/Kolkata", "India Standard Time" },
{ "Asia/Calcutta", "India Standard Time" },
{ "Asia/Colombo", "Sri Lanka Standard Time" },
{ "Asia/Karachi", "Pakistan Standard Time" },
{ "Asia/Dhaka", "Bangladesh Standard Time" },
{ "Asia/Kathmandu", "Nepal Standard Time" },
{ "Asia/Almaty", "Central Asia Standard Time" },
{ "Asia/Tashkent", "West Asia Standard Time" },
{ "Asia/Dubai", "Arabian Standard Time" },
{ "Asia/Muscat", "Arabian Standard Time" },
{ "Asia/Riyadh", "Arab Standard Time" },
{ "Asia/Kuwait", "Arab Standard Time" },
{ "Asia/Baghdad", "Arabic Standard Time" },
{ "Asia/Tehran", "Iran Standard Time" },
{ "Asia/Kabul", "Afghanistan Standard Time" },
{ "Asia/Baku", "Azerbaijan Standard Time" },
{ "Asia/Tbilisi", "Georgian Standard Time" },
{ "Asia/Yerevan", "Caucasus Standard Time" },
{ "Asia/Jerusalem", "Israel Standard Time" },
{ "Asia/Beirut", "Middle East Standard Time" },
{ "Asia/Yangon", "Myanmar Standard Time" },
{ "Asia/Vladivostok", "Vladivostok Standard Time" },
{ "Asia/Yakutsk", "Yakutsk Standard Time" },
{ "Asia/Krasnoyarsk", "North Asia Standard Time" },
{ "Asia/Novosibirsk", "N. Central Asia Standard Time" },
{ "Asia/Irkutsk", "North Asia East Standard Time" },
// Oceania
{ "Australia/Sydney", "AUS Eastern Standard Time" },
{ "Australia/Melbourne", "AUS Eastern Standard Time" },
{ "Australia/Brisbane", "E. Australia Standard Time" },
{ "Australia/Perth", "W. Australia Standard Time" },
{ "Australia/Adelaide", "Cen. Australia Standard Time" },
{ "Australia/Darwin", "AUS Central Standard Time" },
{ "Australia/Hobart", "Tasmania Standard Time" },
{ "Pacific/Auckland", "New Zealand Standard Time" },
{ "Pacific/Fiji", "Fiji Standard Time" },
{ "Pacific/Guam", "West Pacific Standard Time" },
{ "Pacific/Port_Moresby", "West Pacific Standard Time" },
// Africa
{ "Africa/Cairo", "Egypt Standard Time" },
{ "Africa/Johannesburg", "South Africa Standard Time" },
{ "Africa/Lagos", "W. Central Africa Standard Time" },
{ "Africa/Nairobi", "E. Africa Standard Time" },
{ "Africa/Casablanca", "Morocco Standard Time" },
{ "Africa/Windhoek", "Namibia Standard Time" },
};
}
}
@@ -1 +1 @@
581
584
@@ -136,7 +136,7 @@ namespace AutopilotMonitor.Functions.Functions.Config
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(new AgentConfigResponse
{
ConfigVersion = 20, // Software inventory analyzer
ConfigVersion = 21, // NTP time check + timezone auto-set
UploadIntervalSeconds = 10,
SelfDestructOnComplete = tenantConfig.SelfDestructOnComplete ?? true,
KeepLogFile = tenantConfig.KeepLogFile ?? false,
@@ -162,6 +162,8 @@ namespace AutopilotMonitor.Functions.Functions.Config
LocalAdminAllowedAccounts = tenantConfig.GetLocalAdminAllowedAccounts(),
EnableSoftwareInventoryAnalyzer = tenantConfig.EnableSoftwareInventoryAnalyzer ?? false
},
NtpServer = tenantConfig.NtpServer ?? "time.windows.com",
EnableTimezoneAutoSet = tenantConfig.EnableTimezoneAutoSet ?? false,
SendTraceEvents = tenantConfig.SendTraceEvents,
UnrestrictedMode = tenantConfig.UnrestrictedModeEnabled && tenantConfig.UnrestrictedMode,
GatherRules = gatherRules,
@@ -159,6 +159,19 @@ namespace AutopilotMonitor.Shared.Models
/// Default: false.
/// </summary>
public bool UnrestrictedMode { get; set; } = false;
/// <summary>
/// NTP server address for time check during enrollment.
/// Default: "time.windows.com"
/// </summary>
public string NtpServer { get; set; } = "time.windows.com";
/// <summary>
/// Whether to automatically set the device timezone based on IP geolocation.
/// Requires EnableGeoLocation to be true. Uses tzutil /s to apply.
/// Default: false
/// </summary>
public bool EnableTimezoneAutoSet { get; set; } = false;
}
/// <summary>
@@ -211,6 +211,19 @@ namespace AutopilotMonitor.Shared.Models
/// </summary>
public bool? EnableGeoLocation { get; set; }
/// <summary>
/// NTP server address for time check during enrollment.
/// null = use agent default ("time.windows.com").
/// </summary>
public string NtpServer { get; set; }
/// <summary>
/// Whether to automatically set the device timezone based on IP geolocation.
/// Requires EnableGeoLocation to be true. Uses tzutil /s to apply.
/// null = use agent default (false).
/// </summary>
public bool? EnableTimezoneAutoSet { get; set; }
/// <summary>
/// Whether to write a match log for every IME log line matched by a pattern.
/// When true, the agent writes to the default path Constants.ImeMatchLogPath.
@@ -66,6 +66,12 @@ export const REGISTRY_PREFIX_CATEGORIES: readonly GuardrailCategory[] = [
"SOFTWARE\\Microsoft\\Tpm",
],
},
{
category: "Secure Boot",
items: [
"SYSTEM\\CurrentControlSet\\Control\\SecureBoot",
],
},
{
category: "Intune IME",
items: [
@@ -157,6 +163,7 @@ export const ALLOWED_REGISTRY_PREFIXES: readonly string[] = [
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon",
"SYSTEM\\CurrentControlSet\\Services\\TPM",
"SOFTWARE\\Microsoft\\Tpm",
"SYSTEM\\CurrentControlSet\\Control\\SecureBoot",
"SOFTWARE\\Microsoft\\IntuneManagementExtension",
"SOFTWARE\\Microsoft\\SystemCertificates",
"SOFTWARE\\Policies\\Microsoft\\SystemCertificates",
File diff suppressed because one or more lines are too long