Initial commit: detekční skript, remediační skript a README
- Invoke-SecureBootAudit.ps1: audit Secure Boot certifikátů (Fáze 1) - detekce prostředí (Physical/Hyper-V VM/VMware VM) - parsování EFI_SIGNATURE_LIST, detekce 2011/2023 certifikátů - registry stav, Event Log analýza, kategorizace, JSON výstup - Set-SecureBootCertificateUpdate.ps1: remediace dle KB5068202 - AvailableUpdates = 0x5944 - scheduled task s wait smyčkou (timeout 120s) - WhatIf, Force, VerifyOnly, WinRM - README.md: popis problému, detekce stavů, remediační postup
This commit is contained in:
@@ -0,0 +1,655 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Audituje stav Secure Boot certifikátů na Windows Serveru.
|
||||
|
||||
.DESCRIPTION
|
||||
Detekuje podporu Secure Boot, stav povolení, přítomnost certifikátů (expirující 2011
|
||||
vs. nové 2023 náhrady), stav v registrech a relevantní záznamy v Event Logu.
|
||||
Vrací strukturovaný JSON výstup + human-readable souhrn.
|
||||
|
||||
Skript nevyžaduje žádné externí moduly (pure PowerShell 5.1+).
|
||||
Toleruje prostředí bez Secure Boot (Legacy BIOS, Gen1 VM apod.).
|
||||
|
||||
Spouštění na vzdáleném serveru:
|
||||
Invoke-Command -ComputerName SERVER01 -FilePath .\Invoke-SecureBootAudit.ps1
|
||||
|
||||
Spouštění lokálně s uložením výsledku:
|
||||
.\Invoke-SecureBootAudit.ps1 -OutputPath C:\Audit\result.json
|
||||
|
||||
.PARAMETER OutputPath
|
||||
Cesta pro uložení JSON výsledku. Pokud není zadána, JSON se nevypisuje na konzoli
|
||||
(použijte -JsonOnly nebo -PassThru pro programatické zpracování).
|
||||
|
||||
.PARAMETER JsonOnly
|
||||
Vypíše pouze JSON na stdout, bez human-readable souhrnue.
|
||||
|
||||
.PARAMETER PassThru
|
||||
Vrátí výsledek jako PowerShell objekt (vhodné pro Invoke-Command pipeline).
|
||||
|
||||
.EXAMPLE
|
||||
.\Invoke-SecureBootAudit.ps1
|
||||
|
||||
.EXAMPLE
|
||||
.\Invoke-SecureBootAudit.ps1 -OutputPath C:\Audit\SERVER01.json
|
||||
|
||||
.EXAMPLE
|
||||
Invoke-Command -ComputerName SERVER01 -FilePath .\Invoke-SecureBootAudit.ps1 -ArgumentList $null,$false,$true
|
||||
|
||||
.NOTES
|
||||
Vyžaduje spuštění jako Administrator pro přístup k UEFI databázím a Event Logu.
|
||||
Relevantní KB: KB5062710, KB5068202, KB5085046
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$OutputPath,
|
||||
[switch]$JsonOnly,
|
||||
[switch]$PassThru
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
|
||||
#region ── Helper functions ──────────────────────────────────────────────────
|
||||
|
||||
function Get-EnvironmentType {
|
||||
try {
|
||||
$cs = Get-WmiObject Win32_ComputerSystem -ErrorAction Stop
|
||||
$mfr = [string]$cs.Manufacturer
|
||||
$model = [string]$cs.Model
|
||||
|
||||
if ($mfr -like '*VMware*') { return 'VMware VM' }
|
||||
if ($mfr -like '*Microsoft*' -and $model -eq 'Virtual Machine') { return 'Hyper-V VM' }
|
||||
if ($model -like '*Virtual*' -or $mfr -like '*QEMU*' -or $mfr -like '*Xen*') {
|
||||
return 'Other VM'
|
||||
}
|
||||
return 'Physical'
|
||||
} catch {
|
||||
return 'Unknown'
|
||||
}
|
||||
}
|
||||
|
||||
function Get-HardwareInfo {
|
||||
$hw = [ordered]@{
|
||||
Manufacturer = 'Unknown'
|
||||
Model = 'Unknown'
|
||||
SerialNumber = 'Unknown'
|
||||
FirmwareType = 'Unknown'
|
||||
BiosVersion = 'Unknown'
|
||||
BiosReleaseDate = 'Unknown'
|
||||
}
|
||||
|
||||
try {
|
||||
$cs = Get-WmiObject Win32_ComputerSystem -ErrorAction Stop
|
||||
$hw.Manufacturer = [string]$cs.Manufacturer
|
||||
$hw.Model = [string]$cs.Model
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
$bios = Get-WmiObject Win32_BIOS -ErrorAction Stop
|
||||
$hw.BiosVersion = [string]$bios.SMBIOSBIOSVersion
|
||||
$hw.SerialNumber = [string]$bios.SerialNumber
|
||||
if ($bios.ReleaseDate) {
|
||||
$hw.BiosReleaseDate = [Management.ManagementDateTimeConverter]::ToDateTime(
|
||||
$bios.ReleaseDate).ToString('yyyy-MM-dd')
|
||||
}
|
||||
} catch {}
|
||||
|
||||
# Firmware type — primárně z registry, fallback přes přítomnost SecureBoot klíče
|
||||
$fwTypeKey = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' `
|
||||
-Name PEFirmwareType -ErrorAction SilentlyContinue
|
||||
if ($fwTypeKey) {
|
||||
$hw.FirmwareType = if ($fwTypeKey.PEFirmwareType -eq 2) { 'UEFI' } else { 'Legacy BIOS' }
|
||||
} elseif (Test-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot') {
|
||||
$hw.FirmwareType = 'UEFI'
|
||||
} else {
|
||||
$hw.FirmwareType = 'Legacy BIOS'
|
||||
}
|
||||
|
||||
return $hw
|
||||
}
|
||||
|
||||
function Get-SecureBootState {
|
||||
$state = [ordered]@{
|
||||
IsUEFI = $false
|
||||
IsSupported = $false
|
||||
IsEnabled = $false
|
||||
ConfirmResult = 'Unknown'
|
||||
Error = $null
|
||||
}
|
||||
|
||||
$fwTypeKey = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' `
|
||||
-Name PEFirmwareType -ErrorAction SilentlyContinue
|
||||
$state.IsUEFI = ($fwTypeKey -and $fwTypeKey.PEFirmwareType -eq 2) `
|
||||
-or (Test-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot')
|
||||
|
||||
if (-not $state.IsUEFI) { return $state }
|
||||
|
||||
try {
|
||||
$result = Confirm-SecureBootUEFI -ErrorAction Stop
|
||||
$state.IsSupported = $true
|
||||
$state.IsEnabled = [bool]$result
|
||||
$state.ConfirmResult = $result.ToString()
|
||||
} catch {
|
||||
$msg = $_.Exception.Message
|
||||
if ($msg -like '*not supported*' -or $msg -like '*Cmdlet not supported*') {
|
||||
$state.IsSupported = $false
|
||||
$state.ConfirmResult = 'NotSupported'
|
||||
} elseif ($msg -like '*disabled*') {
|
||||
$state.IsSupported = $true
|
||||
$state.IsEnabled = $false
|
||||
$state.ConfirmResult = 'Disabled'
|
||||
} else {
|
||||
$state.IsSupported = $true
|
||||
$state.IsEnabled = $false
|
||||
$state.ConfirmResult = "Error"
|
||||
$state.Error = $msg
|
||||
}
|
||||
}
|
||||
|
||||
return $state
|
||||
}
|
||||
|
||||
function Parse-EFISignatureList {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Parsuje EFI_SIGNATURE_LIST strukturu a vrátí X.509 certifikáty.
|
||||
#>
|
||||
param([byte[]]$Bytes)
|
||||
|
||||
$certs = @()
|
||||
if (-not $Bytes -or $Bytes.Length -lt 28) { return $certs }
|
||||
|
||||
# EFI_CERT_X509_GUID {a5c059a1-94e4-4aa7-87b5-ab155c2bf072} v little-endian
|
||||
$X509_GUID = [byte[]](
|
||||
0xa1,0x59,0xc0,0xa5, # Data1 LE
|
||||
0xe4,0x94, # Data2 LE
|
||||
0xa7,0x4a, # Data3 LE
|
||||
0x87,0xb5,0xab,0x15,0x5c,0x2b,0xf0,0x72 # Data4 BE
|
||||
)
|
||||
|
||||
$offset = 0
|
||||
while ($offset + 28 -le $Bytes.Length) {
|
||||
$sigTypeGUID = $Bytes[$offset..($offset + 15)]
|
||||
$sigListSize = [BitConverter]::ToUInt32($Bytes, $offset + 16)
|
||||
$sigHeaderSize = [BitConverter]::ToUInt32($Bytes, $offset + 20)
|
||||
$sigSize = [BitConverter]::ToUInt32($Bytes, $offset + 24)
|
||||
|
||||
if ($sigListSize -lt 28 -or $sigListSize -gt ($Bytes.Length - $offset)) { break }
|
||||
|
||||
# Porovnat GUID
|
||||
$isX509 = $true
|
||||
for ($i = 0; $i -lt 16; $i++) {
|
||||
if ($sigTypeGUID[$i] -ne $X509_GUID[$i]) { $isX509 = $false; break }
|
||||
}
|
||||
|
||||
if ($isX509 -and $sigSize -gt 16) {
|
||||
$sigOffset = $offset + 28 + $sigHeaderSize
|
||||
$listEnd = $offset + $sigListSize
|
||||
|
||||
while ($sigOffset + $sigSize -le $listEnd) {
|
||||
# Přeskočit 16 B SignatureOwner GUID, zbytek je DER certifikát
|
||||
$certOffset = $sigOffset + 16
|
||||
$certSize = [int]$sigSize - 16
|
||||
|
||||
if ($certOffset + $certSize -le $Bytes.Length -and $certSize -gt 0) {
|
||||
$certBytes = $Bytes[$certOffset..($certOffset + $certSize - 1)]
|
||||
try {
|
||||
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(
|
||||
, [byte[]]$certBytes)
|
||||
$certs += $cert
|
||||
} catch { }
|
||||
}
|
||||
$sigOffset += $sigSize
|
||||
}
|
||||
}
|
||||
|
||||
$offset += $sigListSize
|
||||
}
|
||||
|
||||
return $certs
|
||||
}
|
||||
|
||||
function Convert-CertToInfo {
|
||||
param([System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert)
|
||||
return [ordered]@{
|
||||
Subject = $Cert.Subject
|
||||
Thumbprint = $Cert.Thumbprint
|
||||
NotBefore = $Cert.NotBefore.ToString('yyyy-MM-dd')
|
||||
NotAfter = $Cert.NotAfter.ToString('yyyy-MM-dd')
|
||||
}
|
||||
}
|
||||
|
||||
function Get-CertificateStatus {
|
||||
$status = [ordered]@{
|
||||
KEK = [ordered]@{
|
||||
Has2011 = $false
|
||||
Has2023 = $false
|
||||
Certs2011 = @()
|
||||
Certs2023 = @()
|
||||
Error = $null
|
||||
}
|
||||
DB = [ordered]@{
|
||||
Has2011UEFI = $false
|
||||
Has2011WindowsPCA = $false
|
||||
Has2023UEFI = $false
|
||||
Has2023OptionROM = $false
|
||||
Has2023WindowsUEFI = $false
|
||||
Certs2011 = @()
|
||||
Certs2023 = @()
|
||||
Error = $null
|
||||
}
|
||||
AnyExpiring2011 = $false
|
||||
AllReplacements2023 = $false
|
||||
}
|
||||
|
||||
# ── KEK databáze ──
|
||||
try {
|
||||
$kekObj = Get-SecureBootUEFI -Name KEK -ErrorAction Stop
|
||||
$kekCerts = Parse-EFISignatureList -Bytes $kekObj.Bytes
|
||||
|
||||
foreach ($cert in $kekCerts) {
|
||||
$subj = $cert.Subject
|
||||
$info = Convert-CertToInfo $cert
|
||||
|
||||
if ($subj -like '*KEK CA 2011*') {
|
||||
$status.KEK.Has2011 = $true
|
||||
$status.KEK.Certs2011 += $info
|
||||
}
|
||||
if ($subj -like '*KEK 2K CA 2023*' -or ($subj -like '*KEK*CA 2023*')) {
|
||||
$status.KEK.Has2023 = $true
|
||||
$status.KEK.Certs2023 += $info
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$status.KEK.Error = $_.Exception.Message
|
||||
}
|
||||
|
||||
# ── DB databáze ──
|
||||
try {
|
||||
$dbObj = Get-SecureBootUEFI -Name db -ErrorAction Stop
|
||||
$dbCerts = Parse-EFISignatureList -Bytes $dbObj.Bytes
|
||||
|
||||
foreach ($cert in $dbCerts) {
|
||||
$subj = $cert.Subject
|
||||
$info = Convert-CertToInfo $cert
|
||||
|
||||
# 2011 expirující certifikáty
|
||||
if ($subj -like '*UEFI CA 2011*') {
|
||||
$status.DB.Has2011UEFI = $true
|
||||
$status.DB.Certs2011 += $info
|
||||
}
|
||||
if ($subj -like '*Windows Production PCA 2011*' -or $subj -like '*Windows PCA 2011*') {
|
||||
$status.DB.Has2011WindowsPCA = $true
|
||||
$status.DB.Certs2011 += $info
|
||||
}
|
||||
|
||||
# 2023 náhradní certifikáty
|
||||
if ($subj -like '*UEFI CA 2023*' -and
|
||||
$subj -notlike '*Option ROM*' -and
|
||||
$subj -notlike '*Windows UEFI*') {
|
||||
$status.DB.Has2023UEFI = $true
|
||||
$status.DB.Certs2023 += $info
|
||||
}
|
||||
if ($subj -like '*Option ROM UEFI CA 2023*') {
|
||||
$status.DB.Has2023OptionROM = $true
|
||||
$status.DB.Certs2023 += $info
|
||||
}
|
||||
if ($subj -like '*Windows UEFI CA 2023*') {
|
||||
$status.DB.Has2023WindowsUEFI = $true
|
||||
$status.DB.Certs2023 += $info
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$status.DB.Error = $_.Exception.Message
|
||||
}
|
||||
|
||||
$status.AnyExpiring2011 = $status.KEK.Has2011 -or
|
||||
$status.DB.Has2011UEFI -or
|
||||
$status.DB.Has2011WindowsPCA
|
||||
|
||||
$status.AllReplacements2023 = $status.KEK.Has2023 -and
|
||||
$status.DB.Has2023UEFI -and
|
||||
$status.DB.Has2023OptionROM -and
|
||||
$status.DB.Has2023WindowsUEFI
|
||||
|
||||
return $status
|
||||
}
|
||||
|
||||
function Get-RegistryStatus {
|
||||
$reg = [ordered]@{
|
||||
SecureBootEnabled = $null
|
||||
AvailableUpdates = $null
|
||||
HighConfidenceOptOut = $null
|
||||
ServicingKeyExists = $false
|
||||
UEFICA2023Status = $null
|
||||
UEFICA2023StatusText = 'KeyNotPresent'
|
||||
UEFICA2023Error = $null
|
||||
WindowsUEFICA2023Capable = $null
|
||||
}
|
||||
|
||||
# Hlavní klíč
|
||||
$mainProps = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot' `
|
||||
-ErrorAction SilentlyContinue
|
||||
if ($mainProps) {
|
||||
$reg.SecureBootEnabled = $mainProps.SecureBootEnabled
|
||||
$reg.AvailableUpdates = $mainProps.AvailableUpdates
|
||||
$reg.HighConfidenceOptOut = $mainProps.HighConfidenceOptOut
|
||||
}
|
||||
|
||||
# Servicing podklíč
|
||||
$svcProps = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing' `
|
||||
-ErrorAction SilentlyContinue
|
||||
if ($svcProps) {
|
||||
$reg.ServicingKeyExists = $true
|
||||
$reg.UEFICA2023Status = $svcProps.UEFICA2023Status
|
||||
$reg.UEFICA2023Error = $svcProps.UEFICA2023Error
|
||||
$reg.WindowsUEFICA2023Capable = $svcProps.WindowsUEFICA2023Capable
|
||||
|
||||
$reg.UEFICA2023StatusText = switch ($reg.UEFICA2023Status) {
|
||||
0 { 'NotStarted' }
|
||||
1 { 'InProgress' }
|
||||
2 { 'Success' }
|
||||
3 { 'Failed' }
|
||||
$null { 'KeyNotPresent' }
|
||||
default { "Unknown ($($reg.UEFICA2023Status))" }
|
||||
}
|
||||
}
|
||||
|
||||
return $reg
|
||||
}
|
||||
|
||||
function Get-EventLogStatus {
|
||||
$evtStatus = [ordered]@{
|
||||
LastEventId = $null
|
||||
LastEventTime = $null
|
||||
LastEventMessage = $null
|
||||
ConfidenceLevel = 'NoRelevantEvents'
|
||||
RelevantEvents = @()
|
||||
Error = $null
|
||||
}
|
||||
|
||||
$relevantIds = @(1795, 1796, 1800, 1801, 1802, 1803, 1808)
|
||||
|
||||
try {
|
||||
$events = Get-WinEvent -FilterHashtable @{
|
||||
LogName = 'System'
|
||||
Id = $relevantIds
|
||||
} -MaxEvents 20 -ErrorAction Stop
|
||||
|
||||
if ($events) {
|
||||
$sorted = $events | Sort-Object TimeCreated -Descending
|
||||
|
||||
foreach ($evt in ($sorted | Select-Object -First 10)) {
|
||||
$msgShort = ($evt.Message -replace '\s+', ' ').TrimStart()
|
||||
$msgShort = if ($msgShort.Length -gt 250) { $msgShort.Substring(0, 250) + '...' } else { $msgShort }
|
||||
|
||||
$evtStatus.RelevantEvents += [ordered]@{
|
||||
EventId = $evt.Id
|
||||
TimeCreated = $evt.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')
|
||||
Level = $evt.LevelDisplayName
|
||||
Message = $msgShort
|
||||
}
|
||||
}
|
||||
|
||||
$last = $sorted | Select-Object -First 1
|
||||
$evtStatus.LastEventId = $last.Id
|
||||
$evtStatus.LastEventTime = $last.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')
|
||||
$lastMsg = ($last.Message -replace '\s+', ' ').TrimStart()
|
||||
$evtStatus.LastEventMessage = if ($lastMsg.Length -gt 300) {
|
||||
$lastMsg.Substring(0, 300) + '...'
|
||||
} else { $lastMsg }
|
||||
|
||||
$evtStatus.ConfidenceLevel = switch ($last.Id) {
|
||||
1808 { 'HighConfidence-Success' }
|
||||
1801 { 'HighConfidence-Failed' }
|
||||
1795 { 'Failed-HyperVKnownIssue' }
|
||||
1802 { 'Pending' }
|
||||
1803 { 'InProgress' }
|
||||
default { 'Informational' }
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$msg = $_.Exception.Message
|
||||
if ($msg -like '*No events*' -or $_.CategoryInfo.Reason -eq 'NoMatchingEventsException') {
|
||||
$evtStatus.ConfidenceLevel = 'NoRelevantEvents'
|
||||
} else {
|
||||
$evtStatus.Error = $msg
|
||||
$evtStatus.ConfidenceLevel = 'EventLogError'
|
||||
}
|
||||
}
|
||||
|
||||
return $evtStatus
|
||||
}
|
||||
|
||||
function Get-RemediationCategory {
|
||||
param($Result)
|
||||
|
||||
$sb = $Result.SecureBoot
|
||||
$cert = $Result.Certificates
|
||||
$reg = $Result.Registry
|
||||
$env = $Result.EnvironmentType
|
||||
|
||||
# Bez UEFI / Secure Boot není podporováno
|
||||
if (-not $sb.IsUEFI -or -not $sb.IsSupported) {
|
||||
if ($env -like '*VM*') {
|
||||
return @{ Code = 'NO_SECUREBOOT_VM'; Emoji = '❌'; Label = 'Secure Boot nepodporováno (VM bez vTPM/UEFI)' }
|
||||
}
|
||||
return @{ Code = 'NO_SECUREBOOT'; Emoji = '❌'; Label = 'Secure Boot nepodporováno (Legacy BIOS)' }
|
||||
}
|
||||
|
||||
if (-not $sb.IsEnabled) {
|
||||
return @{ Code = 'SECUREBOOT_DISABLED'; Emoji = '⏸️'; Label = 'Secure Boot vypnuto' }
|
||||
}
|
||||
|
||||
# Secure Boot je zapnutý — zkontrolovat certifikáty
|
||||
if ($cert.AllReplacements2023 -and -not $cert.AnyExpiring2011) {
|
||||
return @{ Code = 'OK'; Emoji = '✅'; Label = 'OK — má nové 2023 certifikáty' }
|
||||
}
|
||||
if ($cert.AllReplacements2023 -and $cert.AnyExpiring2011) {
|
||||
return @{ Code = 'OK_TRANSITION'; Emoji = '✅'; Label = 'OK — přechodný stav (2023 i 2011 certifikáty)' }
|
||||
}
|
||||
|
||||
# Selhání aktualizace
|
||||
if ($reg.UEFICA2023Status -eq 3 -or
|
||||
$Result.EventLog.LastEventId -eq 1795 -or
|
||||
$Result.EventLog.LastEventId -eq 1801) {
|
||||
return @{ Code = 'UPDATE_FAILED'; Emoji = '🔴'; Label = 'Selhání aktualizace certifikátů' }
|
||||
}
|
||||
|
||||
# Aktualizace právě probíhá / čeká na restart
|
||||
if ($reg.UEFICA2023Status -eq 1 -or $reg.UEFICA2023Status -eq 2) {
|
||||
return @{ Code = 'UPDATE_PENDING'; Emoji = '⏳'; Label = 'Aktualizace dokončena, čeká na restart' }
|
||||
}
|
||||
|
||||
# Firmware nepodporuje nové certifikáty
|
||||
if ($null -ne $reg.WindowsUEFICA2023Capable -and $reg.WindowsUEFICA2023Capable -eq 0) {
|
||||
return @{ Code = 'FIRMWARE_UPDATE_NEEDED'; Emoji = '🔧'; Label = 'Čeká na firmware update (OEM)' }
|
||||
}
|
||||
|
||||
# Nutná aktualizace certifikátů
|
||||
return @{ Code = 'UPDATE_NEEDED'; Emoji = '⚠️'; Label = 'Nutná aktualizace certifikátů' }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ── Main ──────────────────────────────────────────────────────────────
|
||||
|
||||
$auditStart = Get-Date
|
||||
|
||||
# Předpočítat hodnoty, které by způsobily parse error uvnitř hashtable (try/catch v PS 5.1)
|
||||
$_fqdn = try { [Net.Dns]::GetHostEntry('').HostName } catch { $env:COMPUTERNAME }
|
||||
$_osInfo = Get-WmiObject Win32_OperatingSystem -ErrorAction SilentlyContinue
|
||||
$_osCaption = if ($_osInfo) { $_osInfo.Caption } else { $null }
|
||||
$_osBuild = if ($_osInfo) { $_osInfo.BuildNumber } else { $null }
|
||||
|
||||
$result = [ordered]@{
|
||||
AuditTimestamp = $auditStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
||||
Hostname = $env:COMPUTERNAME
|
||||
FQDN = $_fqdn
|
||||
OSCaption = $_osCaption
|
||||
OSBuild = $_osBuild
|
||||
Architecture = $env:PROCESSOR_ARCHITECTURE
|
||||
EnvironmentType = Get-EnvironmentType
|
||||
Hardware = Get-HardwareInfo
|
||||
SecureBoot = Get-SecureBootState
|
||||
Certificates = $null
|
||||
Registry = Get-RegistryStatus
|
||||
EventLog = Get-EventLogStatus
|
||||
Category = $null
|
||||
CategoryLabel = $null
|
||||
RemediationAction = $null
|
||||
}
|
||||
|
||||
# Certifikáty parsovat pouze pokud je Secure Boot dostupný
|
||||
if ($result.SecureBoot.IsUEFI -and $result.SecureBoot.IsSupported) {
|
||||
$result.Certificates = Get-CertificateStatus
|
||||
} else {
|
||||
$result.Certificates = [ordered]@{
|
||||
KEK = [ordered]@{
|
||||
Has2011 = $false; Has2023 = $false
|
||||
Certs2011 = @(); Certs2023 = @()
|
||||
Error = 'Secure Boot not available — certificate check skipped'
|
||||
}
|
||||
DB = [ordered]@{
|
||||
Has2011UEFI = $false; Has2011WindowsPCA = $false
|
||||
Has2023UEFI = $false; Has2023OptionROM = $false; Has2023WindowsUEFI = $false
|
||||
Certs2011 = @(); Certs2023 = @()
|
||||
Error = 'Secure Boot not available — certificate check skipped'
|
||||
}
|
||||
AnyExpiring2011 = $false
|
||||
AllReplacements2023 = $false
|
||||
}
|
||||
}
|
||||
|
||||
# Kategorie
|
||||
$cat = Get-RemediationCategory -Result $result
|
||||
$result.Category = $cat.Code
|
||||
$result.CategoryLabel = "$($cat.Emoji) $($cat.Label)"
|
||||
|
||||
$remediationMap = @{
|
||||
'OK' = 'Žádná akce'
|
||||
'OK_TRANSITION' = 'Žádná akce (monitorovat dokud nezmizí 2011 certifikáty)'
|
||||
'UPDATE_NEEDED' = 'Naplánovat rollout nových certifikátů (KB5068202)'
|
||||
'UPDATE_FAILED' = 'Troubleshooting selhání — zkontrolovat Event 1795/1801 (KB5085046)'
|
||||
'UPDATE_PENDING' = 'Provést restart serveru pro dokončení aktualizace'
|
||||
'FIRMWARE_UPDATE_NEEDED'= 'Kontaktovat OEM / aktualizovat firmware serveru'
|
||||
'SECUREBOOT_DISABLED' = 'Rozhodnutí o zapnutí Secure Boot (mimo scope skriptu)'
|
||||
'NO_SECUREBOOT' = 'Dokumentovat jako výjimku — Legacy BIOS, Secure Boot nelze'
|
||||
'NO_SECUREBOOT_VM' = 'Dokumentovat jako výjimku — VM bez Secure Boot (Gen1 / bez vTPM)'
|
||||
}
|
||||
$result.RemediationAction = $remediationMap[$result.Category]
|
||||
|
||||
$result.AuditDurationMs = [int](Get-Date).Subtract($auditStart).TotalMilliseconds
|
||||
|
||||
#endregion
|
||||
|
||||
#region ── Output ────────────────────────────────────────────────────────────
|
||||
|
||||
$jsonOutput = $result | ConvertTo-Json -Depth 10
|
||||
|
||||
if ($OutputPath) {
|
||||
$jsonOutput | Out-File -FilePath $OutputPath -Encoding UTF8 -Force
|
||||
Write-Host "JSON uložen: $OutputPath" -ForegroundColor Green
|
||||
}
|
||||
|
||||
if (-not $JsonOnly) {
|
||||
$sb = $result.SecureBoot
|
||||
$cert = $result.Certificates
|
||||
$reg = $result.Registry
|
||||
$evt = $result.EventLog
|
||||
$hw = $result.Hardware
|
||||
|
||||
$catColor = switch -Wildcard ($result.Category) {
|
||||
'OK*' { 'Green' }
|
||||
'UPDATE_NEEDED' { 'Yellow' }
|
||||
'UPDATE_PENDING' { 'Cyan' }
|
||||
'UPDATE_FAILED' { 'Red' }
|
||||
'FIRMWARE*' { 'Magenta' }
|
||||
'SECUREBOOT_DISABLED' { 'Yellow' }
|
||||
default { 'Gray' }
|
||||
}
|
||||
|
||||
$line = '=' * 60
|
||||
Write-Host ''
|
||||
Write-Host $line -ForegroundColor Cyan
|
||||
Write-Host " SECURE BOOT AUDIT — $($result.Hostname)" -ForegroundColor Cyan
|
||||
Write-Host " $($result.AuditTimestamp)" -ForegroundColor DarkGray
|
||||
Write-Host $line -ForegroundColor Cyan
|
||||
Write-Host ''
|
||||
|
||||
Write-Host 'SERVER' -ForegroundColor Yellow
|
||||
Write-Host " Hostname : $($result.Hostname)"
|
||||
Write-Host " FQDN : $($result.FQDN)"
|
||||
Write-Host " OS : $($result.OSCaption) (Build $($result.OSBuild))"
|
||||
Write-Host " Prostředí : $($result.EnvironmentType)"
|
||||
Write-Host " Hardware : $($hw.Manufacturer) $($hw.Model) [S/N: $($hw.SerialNumber)]"
|
||||
Write-Host " BIOS/FW : $($hw.BiosVersion) ($($hw.BiosReleaseDate))"
|
||||
Write-Host " Firmware typ: $($hw.FirmwareType)"
|
||||
Write-Host ''
|
||||
|
||||
Write-Host 'SECURE BOOT' -ForegroundColor Yellow
|
||||
Write-Host " UEFI firmware : $($sb.IsUEFI)"
|
||||
Write-Host " Podporováno : $($sb.IsSupported)"
|
||||
Write-Host " Povoleno : $($sb.IsEnabled)"
|
||||
if ($sb.Error) { Write-Host " ! Chyba : $($sb.Error)" -ForegroundColor Red }
|
||||
Write-Host ''
|
||||
|
||||
Write-Host 'CERTIFIKÁTY' -ForegroundColor Yellow
|
||||
$c = $cert
|
||||
Write-Host " ── Expirující 2011 ──────────────────────────────────"
|
||||
Write-Host " KEK CA 2011 (exp. 24.6.2026) : $(if ($c.KEK.Has2011) {'PŘÍTOMEN ⚠️'} else {'chybí'})"
|
||||
Write-Host " DB UEFI CA 2011 (exp. 27.6.2026): $(if ($c.DB.Has2011UEFI) {'PŘÍTOMEN ⚠️'} else {'chybí'})"
|
||||
Write-Host " DB Windows PCA 2011 (19.10.2026) : $(if ($c.DB.Has2011WindowsPCA) {'PŘÍTOMEN ⚠️'} else {'chybí'})"
|
||||
Write-Host " ── Nové náhrady 2023 ────────────────────────────────"
|
||||
Write-Host " KEK 2K CA 2023 : $(if ($c.KEK.Has2023) {'přítomen ✓'} else {'CHYBÍ'})"
|
||||
Write-Host " DB UEFI CA 2023 : $(if ($c.DB.Has2023UEFI) {'přítomen ✓'} else {'CHYBÍ'})"
|
||||
Write-Host " DB Option ROM UEFI CA 2023 : $(if ($c.DB.Has2023OptionROM) {'přítomen ✓'} else {'CHYBÍ'})"
|
||||
Write-Host " DB Windows UEFI CA 2023 : $(if ($c.DB.Has2023WindowsUEFI) {'přítomen ✓'} else {'CHYBÍ'})"
|
||||
if ($c.KEK.Error) { Write-Host " ! KEK chyba : $($c.KEK.Error)" -ForegroundColor Red }
|
||||
if ($c.DB.Error) { Write-Host " ! DB chyba : $($c.DB.Error)" -ForegroundColor Red }
|
||||
Write-Host ''
|
||||
|
||||
Write-Host 'REGISTRY STAV' -ForegroundColor Yellow
|
||||
Write-Host " UEFICA2023Status : $($reg.UEFICA2023Status) ($($reg.UEFICA2023StatusText))"
|
||||
Write-Host " AvailableUpdates : $($reg.AvailableUpdates)"
|
||||
Write-Host " WindowsUEFICA2023Capable: $($reg.WindowsUEFICA2023Capable)"
|
||||
Write-Host " HighConfidenceOptOut : $($reg.HighConfidenceOptOut)"
|
||||
if ($reg.UEFICA2023Error) {
|
||||
Write-Host " ! UEFICA2023Error : $($reg.UEFICA2023Error)" -ForegroundColor Red
|
||||
}
|
||||
Write-Host ''
|
||||
|
||||
Write-Host 'EVENT LOG (relevantní EventID: 1795/1796/1800-1803/1808)' -ForegroundColor Yellow
|
||||
Write-Host " Poslední event : EventID $($evt.LastEventId) ($($evt.LastEventTime))"
|
||||
Write-Host " Confidence level : $($evt.ConfidenceLevel)"
|
||||
if ($evt.RelevantEvents.Count -gt 0) {
|
||||
Write-Host " Posledních $([Math]::Min($evt.RelevantEvents.Count,5)) záznamů:"
|
||||
foreach ($e in ($evt.RelevantEvents | Select-Object -First 5)) {
|
||||
$ec = if ($e.EventId -eq 1808) { 'Green' }
|
||||
elseif ($e.EventId -in @(1795,1801)) { 'Red' }
|
||||
else { 'DarkGray' }
|
||||
Write-Host " [$($e.TimeCreated)] EventID $($e.EventId) $($e.Level)" -ForegroundColor $ec
|
||||
}
|
||||
} else {
|
||||
Write-Host ' Žádné relevantní záznamy nenalezeny.' -ForegroundColor DarkGray
|
||||
}
|
||||
Write-Host ''
|
||||
|
||||
Write-Host 'VÝSLEDEK' -ForegroundColor Yellow
|
||||
Write-Host " $($result.CategoryLabel)" -ForegroundColor $catColor
|
||||
Write-Host " Doporučená akce: $($result.RemediationAction)" -ForegroundColor White
|
||||
Write-Host ''
|
||||
Write-Host $line -ForegroundColor Cyan
|
||||
Write-Host ''
|
||||
}
|
||||
|
||||
if ($JsonOnly) {
|
||||
Write-Output $jsonOutput
|
||||
}
|
||||
|
||||
if ($PassThru) {
|
||||
return $result
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1,310 @@
|
||||
# Secure Boot Certificate Remediation
|
||||
|
||||
Sada PowerShell skriptů pro audit a remediari expirujících Secure Boot certifikátů na Windows Serverech (fyzické servery, Hyper-V VM, VMware VM).
|
||||
|
||||
---
|
||||
|
||||
## 1. Co řešíme
|
||||
|
||||
### Problém
|
||||
|
||||
Microsoft oznámil prostřednictvím [KB5062710](https://support.microsoft.com/en-us/topic/windows-secure-boot-certificate-expiration-and-ca-updates-7ff40d33-95dc-4c3c-8725-a9b95457578e), že původní Secure Boot certifikáty z roku 2011 expirují v průběhu roku 2026. Tyto certifikáty jsou uloženy přímo ve firmware serveru (UEFI KEK a DB databázích) a zajišťují důvěryhodnost celého boot procesu.
|
||||
|
||||
### Expirující certifikáty a termíny
|
||||
|
||||
| Certifikát | Umístění | Expirace | Náhrada |
|
||||
|---|---|---|---|
|
||||
| Microsoft Corporation KEK CA 2011 | KEK | **24. června 2026** | Microsoft Corporation KEK 2K CA 2023 |
|
||||
| Microsoft UEFI CA 2011 | DB | **27. června 2026** | Microsoft UEFI CA 2023 + Microsoft Option ROM UEFI CA 2023 |
|
||||
| Microsoft Windows Production PCA 2011 | DB | 19. října 2026 | Windows UEFI CA 2023 |
|
||||
|
||||
> **Urgentní:** První dvě expirace jsou 24. a 27. června 2026 — audit je nutné provést okamžitě.
|
||||
|
||||
### Dopad pokud se nic neudělá
|
||||
|
||||
Servery bez nových certifikátů budou **nadále normálně bootovat** — certifikáty sice expirují, ale UEFI firmware je při bootu nevyhodnocuje jako neplatné. Problém nastane postupně:
|
||||
|
||||
- Server přestane být schopen přijímat nové aktualizace Secure Boot databází a revokačních seznamů (DBX)
|
||||
- Nelze aplikovat mitigace nově objevených boot-level zranitelností
|
||||
- Mohou být ovlivněny scénáře závislé na Secure Boot důvěře: BitLocker hardening, third-party bootloadery, attestation
|
||||
- Při případném resetu UEFI na factory defaults nebo reinstalaci firmware může boot selhat
|
||||
|
||||
### Které servery jsou ohroženy
|
||||
|
||||
Ohroženy jsou servery, které splňují **všechny** následující podmínky:
|
||||
|
||||
- Mají UEFI firmware (ne Legacy BIOS)
|
||||
- Mají zapnutý Secure Boot
|
||||
- Ještě neobdržely nové 2023 certifikáty (typicky přes Windows Update nebo manuální rollout)
|
||||
|
||||
Servery s Legacy BIOS nebo vypnutým Secure Boot nejsou přímo ohroženy, ale je vhodné je zdokumentovat.
|
||||
|
||||
---
|
||||
|
||||
## 2. Detekce — jak zjistit aktuální stav
|
||||
|
||||
### Skripty
|
||||
|
||||
| Skript | Účel |
|
||||
|---|---|
|
||||
| `Invoke-SecureBootAudit.ps1` | Audit jednoho serveru — vrací JSON + human-readable souhrn |
|
||||
| `Start-SecureBootFleetAudit.ps1` | Hromadný audit celé sítě — vstup CSV, výstup agregovaný CSV/JSON |
|
||||
|
||||
### Spuštění auditu
|
||||
|
||||
**Lokálně na serveru** (spustit jako Administrator):
|
||||
|
||||
```powershell
|
||||
.\Invoke-SecureBootAudit.ps1
|
||||
```
|
||||
|
||||
**Vzdáleně z workstation:**
|
||||
|
||||
```powershell
|
||||
Invoke-Command -ComputerName SERVER01 -FilePath .\Invoke-SecureBootAudit.ps1 -ArgumentList $null, $false, $true
|
||||
```
|
||||
|
||||
**Hromadně ze seznamu serverů (CSV soubor se sloupcem `ServerName`):**
|
||||
|
||||
```powershell
|
||||
.\Start-SecureBootFleetAudit.ps1 -ServerListPath .\servers.csv -OutputPath .\audit_results\
|
||||
```
|
||||
|
||||
**Hromadně přímo ze seznamu:**
|
||||
|
||||
```powershell
|
||||
.\Start-SecureBootFleetAudit.ps1 -ServerName SERVER01,SERVER02,SERVER03
|
||||
```
|
||||
|
||||
### Výsledné kategorie
|
||||
|
||||
Každý server je zařazen do jedné z těchto kategorií:
|
||||
|
||||
| Kategorie | Popis | Doporučená akce |
|
||||
|---|---|---|
|
||||
| ✅ **OK** | Má nové 2023 certifikáty, Secure Boot aktivní | Žádná akce |
|
||||
| ✅ **OK_TRANSITION** | Má 2023 i stará 2011 certifikáty (přechodný stav) | Žádná akce, monitorovat |
|
||||
| ⚠️ **UPDATE_NEEDED** | Má pouze 2011 certifikáty, Secure Boot aktivní | Naplánovat rollout |
|
||||
| ⏳ **UPDATE_PENDING** | Registry nastaveny, čeká na restart | Restartovat server |
|
||||
| 🔧 **FIRMWARE_UPDATE_NEEDED** | `WindowsUEFICA2023Capable=0` — firmware nepodporuje nové certifikáty | Update firmware u OEM |
|
||||
| ⏸️ **SECUREBOOT_DISABLED** | Secure Boot existuje v UEFI, ale není zapnuté | Rozhodnutí o enablementu |
|
||||
| ❌ **NO_SECUREBOOT** | Legacy BIOS — Secure Boot vůbec neexistuje | Dokumentovat jako výjimku |
|
||||
| ❌ **NO_SECUREBOOT_VM** | VM bez Secure Boot (Gen1 / bez vTPM / BIOS mode) | Dokumentovat jako výjimku |
|
||||
| 🔴 **UPDATE_FAILED** | Selhání aktualizace (viz EventID níže) | Troubleshooting |
|
||||
|
||||
### Registry hodnoty a jejich výklad
|
||||
|
||||
Klíč `HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing`:
|
||||
|
||||
| Hodnota | Typ | Výklad |
|
||||
|---|---|---|
|
||||
| `UEFICA2023Status = 0` | NotStarted | Aktualizace ještě nebyla spuštěna |
|
||||
| `UEFICA2023Status = 1` | InProgress | Aktualizace probíhá |
|
||||
| `UEFICA2023Status = 2` | Success | Aktualizace proběhla, čeká se na restart |
|
||||
| `UEFICA2023Status = 3` | Failed | Aktualizace selhala — viz `UEFICA2023Error` |
|
||||
| `WindowsUEFICA2023Capable = 0` | — | Firmware nepodporuje nové certifikáty (potřeba update OEM firmware) |
|
||||
| `UEFICA2023Error` | HRESULT kód | Chybový kód při selhání |
|
||||
|
||||
Klíč `HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot`:
|
||||
|
||||
| Hodnota | Výklad |
|
||||
|---|---|
|
||||
| `AvailableUpdates` | Bitmaska dostupných aktualizací; 0x5944 = plná sada (nastavíme při remediaci) |
|
||||
| `HighConfidenceOptOut = 1` | Server se odhlásil z high-confidence update path — **zablokuje aktualizaci** |
|
||||
|
||||
### EventID v System logu
|
||||
|
||||
| EventID | Zdroj | Typ | Výklad |
|
||||
|---|---|---|---|
|
||||
| **1808** | Microsoft-Windows-Eventlog | Information | ✅ Certifikáty úspěšně aplikovány — aktualizace proběhla v pořádku |
|
||||
| **1801** | Microsoft-Windows-Eventlog | Error | ❌ Certifikáty NEBYLY aplikovány — selhání high-confidence update |
|
||||
| **1795** | Microsoft-Windows-Eventlog | Error | ❌ Selhání aktualizace na Hyper-V VM (viz níže) |
|
||||
| **1796** | Microsoft-Windows-Eventlog | Information | Průběhová informace |
|
||||
| **1800** | Microsoft-Windows-Eventlog | Information | Průběhová informace |
|
||||
| **1802** | Microsoft-Windows-Eventlog | Information | Aktualizace čeká na restart |
|
||||
| **1803** | Microsoft-Windows-Eventlog | Information | Aktualizace probíhá |
|
||||
|
||||
Rychlé ověření z PowerShell:
|
||||
|
||||
```powershell
|
||||
Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = @(1795,1801,1808) } -MaxEvents 10 |
|
||||
Select-Object TimeCreated, Id, Message | Format-List
|
||||
```
|
||||
|
||||
### Specifika prostředí
|
||||
|
||||
#### Hyper-V VM
|
||||
|
||||
- Virtuální stroje **Generation 1** (BIOS mode) nemají Secure Boot — kategorie `NO_SECUREBOOT_VM`, není co řešit.
|
||||
- Virtuální stroje **Generation 2** mají virtualizovaný UEFI s Secure Boot, ale aktualizace certifikátů závisí na verzi hypervizoru.
|
||||
- **Známý problém (Event 1795):** Na starších hostitelích selhávala aktualizace certifikátů ve VM s EventID 1795. Microsoft vydal opravu v březnu 2026 (KB5085790). Postup:
|
||||
1. Ověřit verzi Windows Server na Hyper-V hostiteli
|
||||
2. Aplikovat příslušné kumulativní aktualizace na hostitele
|
||||
3. Spustit audit znovu na VM
|
||||
|
||||
#### VMware ESXi
|
||||
|
||||
- Secure Boot ve VMware VM závisí na konfiguraci VM hardware (EFI firmware + Secure Boot zaškrtnuto v nastavení VM).
|
||||
- VMware **nepředává** nové certifikáty automaticky — aktualizace musí proběhnout přes Windows stejně jako na fyzickém stroji.
|
||||
- Kategorie `NO_SECUREBOOT_VM` u VMware VM = VM nemá Secure Boot vůbec povoleno.
|
||||
|
||||
#### Fyzické servery s Legacy BIOS
|
||||
|
||||
- Kategorie `NO_SECUREBOOT` — Secure Boot není podporováno. Firmware je třeba překonfigurovat (boot mode UEFI) nebo server dokumentovat jako výjimku.
|
||||
|
||||
#### Starší firmware
|
||||
|
||||
- Servery s firmwarem z doby před cca 2018 mohou vyžadovat update firmware od výrobce (Dell, HP, Lenovo, Supermicro) před tím, než lze nové certifikáty aplikovat.
|
||||
- Poznávacím znamením je `WindowsUEFICA2023Capable = 0` nebo hodnota `UEFICA2023Error`.
|
||||
- Servery end-of-life bez dostupného firmware update je nutné dokumentovat jako trvalou výjimku.
|
||||
|
||||
---
|
||||
|
||||
## 3. Remediace — odstranění problému
|
||||
|
||||
### Skript
|
||||
|
||||
```
|
||||
Set-SecureBootCertificateUpdate.ps1
|
||||
```
|
||||
|
||||
Metoda: Registry key dle **KB5068202** — nastaví `AvailableUpdates = 0x5944`, čímž Windows signalizuje, že má aplikovat kompletní sadu nových certifikátů (KEK + UEFI CA + Windows UEFI CA + Boot Manager).
|
||||
|
||||
### Doporučený postup rollout (staging)
|
||||
|
||||
**Fáze 1 — Pilotní test (1–2 servery každého typu)**
|
||||
|
||||
```powershell
|
||||
# Nejdřív WhatIf — ověřit co se stane bez provedení změn
|
||||
.\Set-SecureBootCertificateUpdate.ps1 -ServerName PILOT-SERVER01 -WhatIf
|
||||
|
||||
# Aplikovat na pilotní server
|
||||
.\Set-SecureBootCertificateUpdate.ps1 -ServerName PILOT-SERVER01
|
||||
|
||||
# Restartovat pilotní server
|
||||
Restart-Computer -ComputerName PILOT-SERVER01 -Force
|
||||
```
|
||||
|
||||
**Fáze 2 — Ověření pilotu (po restartu)**
|
||||
|
||||
```powershell
|
||||
# Zkontrolovat EventID 1808 (úspěch) nebo 1801 (chyba)
|
||||
Invoke-Command -ComputerName PILOT-SERVER01 -ScriptBlock {
|
||||
Get-WinEvent -FilterHashtable @{ LogName='System'; Id=@(1801,1808) } -MaxEvents 5 |
|
||||
Select-Object TimeCreated, Id, LevelDisplayName, Message | Format-List
|
||||
}
|
||||
|
||||
# Spustit plný audit pro ověření kategorie OK
|
||||
Invoke-Command -ComputerName PILOT-SERVER01 -FilePath .\Invoke-SecureBootAudit.ps1 -ArgumentList $null, $false, $true
|
||||
```
|
||||
|
||||
**Fáze 3 — Rozšíření na další servery**
|
||||
|
||||
```powershell
|
||||
# Hromadně ze CSV (sloupec ServerName)
|
||||
.\Set-SecureBootCertificateUpdate.ps1 -ServerName (Import-Csv .\servery-wave1.csv).ServerName
|
||||
|
||||
# Po restartech ověřit hromadně
|
||||
.\Start-SecureBootFleetAudit.ps1 -ServerListPath .\servery-wave1.csv -OutputPath .\audit_post\
|
||||
```
|
||||
|
||||
### Co se děje po spuštění skriptu
|
||||
|
||||
1. Skript nastaví `AvailableUpdates = 0x5944` v registry a resetuje `HighConfidenceOptOut = 0`
|
||||
2. Spustí scheduled task `\Microsoft\Windows\PI\Secure-Boot-Update` (pokud existuje; jinak se task spustí sám do 12 hodin)
|
||||
3. Task zpracuje registry a připraví certifikáty — status přejde z `NotStarted` → `InProgress` → `Success`
|
||||
4. **Certifikáty se aplikují až při příštím restartu** — Boot Manager zapíše nové certifikáty do UEFI firmware
|
||||
5. Po restartu se v System logu objeví EventID **1808** (úspěch) nebo **1801** (chyba)
|
||||
|
||||
### Dopad na server
|
||||
|
||||
| Aspekt | Detail |
|
||||
|---|---|
|
||||
| **Dostupnost** | Žádný výpadek při nastavení registry. Restart je standardní plánovaný restart. |
|
||||
| **Riziko selhání bootu** | **Minimální** — neúspěšná aktualizace certifikátů neohrozí boot. Server nastartuje i se starými certifikáty. |
|
||||
| **Počet restartů** | Obvykle 1 restart. Ve výjimečných případech může být vyžadován druhý. |
|
||||
| **Čas po restartu** | Certifikáty jsou aplikovány v rané fázi bootu — uživatelé to nepocítí. |
|
||||
| **BitLocker** | Pokud je BitLocker aktivní s PCR7 (Secure Boot measurement), ověřit recovery key před restartem. |
|
||||
| **Monitoring** | Po restartu čekat min. 48 hodin než prohlásíme server za stabilní. |
|
||||
|
||||
> **Doporučení pro BitLocker:** Před restartem na serverech s BitLockerem ověřit dostupnost recovery key: `manage-bde -protectors -get C:`
|
||||
|
||||
### Ověření úspěchu
|
||||
|
||||
**1. Event log — primární ověření:**
|
||||
|
||||
```powershell
|
||||
Get-WinEvent -FilterHashtable @{ LogName='System'; Id=1808 } -MaxEvents 3 |
|
||||
Select-Object TimeCreated, Message
|
||||
```
|
||||
|
||||
EventID **1808** potvrzuje úspěšnou aplikaci certifikátů.
|
||||
|
||||
**2. Audit skript — kompletní ověření:**
|
||||
|
||||
```powershell
|
||||
.\Invoke-SecureBootAudit.ps1
|
||||
# Očekávaný výstup: kategorie "✅ OK" nebo "✅ OK_TRANSITION"
|
||||
```
|
||||
|
||||
**3. Ruční kontrola přítomnosti certifikátů:**
|
||||
|
||||
```powershell
|
||||
# KEK databáze — hledat 2023 certifikáty
|
||||
$kek = Get-SecureBootUEFI KEK
|
||||
# DB databáze
|
||||
$db = Get-SecureBootUEFI db
|
||||
```
|
||||
|
||||
**4. Registry stav:**
|
||||
|
||||
```powershell
|
||||
Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing' |
|
||||
Select-Object UEFICA2023Status, UEFICA2023Error, WindowsUEFICA2023Capable
|
||||
# UEFICA2023Status = 2 znamená Success
|
||||
```
|
||||
|
||||
### Troubleshooting časté chyby
|
||||
|
||||
**Event 1801 — high-confidence update selhal:**
|
||||
```powershell
|
||||
# Zkontrolovat UEFICA2023Error
|
||||
(Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing').UEFICA2023Error
|
||||
|
||||
# Zkusit znovu s -Force
|
||||
.\Set-SecureBootCertificateUpdate.ps1 -ServerName <server> -Force
|
||||
```
|
||||
|
||||
**Event 1795 — Hyper-V VM selhání:**
|
||||
- Zkontrolovat verzi Windows Server hostitele a aplikovat kumulativní update (KB5085790)
|
||||
- Po aktualizaci hostitele spustit remediaci na VM znovu
|
||||
|
||||
**WindowsUEFICA2023Capable = 0 — firmware nepodporuje:**
|
||||
- Zkontrolovat dostupnost firmware update u výrobce serveru
|
||||
- Dell: Dell Update Package / iDRAC
|
||||
- HP: HP Service Pack for ProLiant (SPP)
|
||||
- Lenovo: Lenovo XClarity
|
||||
- Supermicro: BIOS update přes IPMI
|
||||
- Pokud firmware update není dostupný → dokumentovat jako trvalou výjimku
|
||||
|
||||
**HighConfidenceOptOut = 1 — server se odhlásil:**
|
||||
```powershell
|
||||
# Resetovat OptOut
|
||||
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot' `
|
||||
-Name HighConfidenceOptOut -Value 0 -Type DWord
|
||||
# Pak spustit remediaci znovu
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
| KB | Popis |
|
||||
|---|---|
|
||||
| [KB5062710](https://support.microsoft.com/en-us/topic/windows-secure-boot-certificate-expiration-and-ca-updates-7ff40d33-95dc-4c3c-8725-a9b95457578e) | Přehled: expirace certifikátů a CA aktualizace |
|
||||
| [KB5062713](https://support.microsoft.com/en-us/help/5062713) | IT Pro guidance |
|
||||
| [KB5068202](https://support.microsoft.com/en-us/help/5068202) | Metoda registry klíčů (použita v tomto projektu) |
|
||||
| [KB5068198](https://support.microsoft.com/en-us/help/5068198) | Metoda Group Policy |
|
||||
| [KB5068197](https://support.microsoft.com/en-us/help/5068197) | Metoda WinCS CLI |
|
||||
| [KB5085046](https://support.microsoft.com/en-us/help/5085046) | Troubleshooting guide |
|
||||
| [KB5085790](https://support.microsoft.com/en-us/help/5085790) | Known issues a resolutions (vč. Hyper-V fix) |
|
||||
@@ -0,0 +1,519 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Remediační skript pro aktualizaci Secure Boot certifikátů (KB5068202).
|
||||
|
||||
.DESCRIPTION
|
||||
Nastaví registry klíče pro zahájení aktualizace Secure Boot certifikátů
|
||||
(přechod z 2011 na 2023 sadu) a spustí scheduled task.
|
||||
|
||||
Metoda: Registry key (KB5068202)
|
||||
Alternativy: GPO (KB5068198), WinCS CLI (KB5068197)
|
||||
|
||||
Postup remediace:
|
||||
1. Skript nastaví AvailableUpdates = 0x5944 v registry
|
||||
2. Scheduled task \Microsoft\Windows\PI\Secure-Boot-Update se spustí
|
||||
(nebo čeká na příští plánovaný běh každých 12 hodin)
|
||||
3. Certifikáty se aplikují při příštím restartu
|
||||
4. Výsledek ověřit přes EventID 1808 (úspěch) nebo 1801/1795 (chyba)
|
||||
|
||||
DŮLEŽITÉ:
|
||||
- Aktualizace vyžaduje RESTART pro aplikaci certifikátů v Boot Manageru
|
||||
- Neúspěšná aktualizace NEOHROZÍ boot serveru — server běží dál
|
||||
- Po aplikaci čekat min. 48 hodin a jeden nebo více restartů
|
||||
- Na Hyper-V VM s Event 1795: ověřit verzi hostitele (fix dostupný od 3/2026)
|
||||
|
||||
.PARAMETER ServerName
|
||||
Název nebo pole názvů serverů pro vzdálené spuštění přes WinRM.
|
||||
Pokud není zadán, skript se spustí lokálně.
|
||||
|
||||
.PARAMETER Credential
|
||||
Přihlašovací údaje pro vzdálené připojení.
|
||||
|
||||
.PARAMETER Force
|
||||
Aplikuje registry klíče i na serverech, které již mají 2023 certifikáty
|
||||
nebo kde již aktualizace proběhla (UEFICA2023Status = 2).
|
||||
|
||||
.PARAMETER SkipScheduledTask
|
||||
Nespustí scheduled task po nastavení registry. Task se spustí sám
|
||||
při příštím plánovaném běhu (každých 12 hodin).
|
||||
|
||||
.PARAMETER VerifyOnly
|
||||
Pouze zobrazí aktuální stav bez provedení změn (read-only, ekvivalent -WhatIf).
|
||||
|
||||
.PARAMETER LogPath
|
||||
Cesta k logovacímu souboru. Default: .\SecureBootRemediation-<datum>.log
|
||||
|
||||
.EXAMPLE
|
||||
# WhatIf — zobrazí co by se stalo, bez změn
|
||||
.\Set-SecureBootCertificateUpdate.ps1 -WhatIf
|
||||
|
||||
.EXAMPLE
|
||||
# Lokální spuštění
|
||||
.\Set-SecureBootCertificateUpdate.ps1
|
||||
|
||||
.EXAMPLE
|
||||
# Vzdálené spuštění na jednom serveru
|
||||
.\Set-SecureBootCertificateUpdate.ps1 -ServerName SERVER01
|
||||
|
||||
.EXAMPLE
|
||||
# Vzdálené spuštění na více serverech
|
||||
.\Set-SecureBootCertificateUpdate.ps1 -ServerName SERVER01,SERVER02,SERVER03
|
||||
|
||||
.EXAMPLE
|
||||
# Pouze ověření bez změn
|
||||
.\Set-SecureBootCertificateUpdate.ps1 -ServerName SERVER01 -VerifyOnly
|
||||
|
||||
.NOTES
|
||||
Reference: KB5062710, KB5068202, KB5085046, KB5085790
|
||||
Vyžaduje spuštění jako Administrator (lokálně i vzdáleně).
|
||||
Vzdálené spuštění vyžaduje WinRM (standardně aktivní na Windows Server).
|
||||
AvailableUpdates = 0x5944 dle KB5068202 (KEK + UEFI CA + Windows UEFI CA + Boot Manager).
|
||||
#>
|
||||
|
||||
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
|
||||
param(
|
||||
[Parameter(Position = 0)]
|
||||
[string[]]$ServerName,
|
||||
|
||||
[System.Management.Automation.PSCredential]$Credential,
|
||||
|
||||
[switch]$Force,
|
||||
[switch]$SkipScheduledTask,
|
||||
[switch]$VerifyOnly,
|
||||
|
||||
[string]$LogPath = ".\SecureBootRemediation-$(Get-Date -Format 'yyyyMMdd-HHmmss').log"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
Set-StrictMode -Off
|
||||
|
||||
#region ── Logging ────────────────────────────────────────────────────────────
|
||||
|
||||
function Write-Log {
|
||||
param(
|
||||
[string]$Message,
|
||||
[ValidateSet('INFO','WARN','ERROR','SUCCESS','ACTION')]
|
||||
[string]$Level = 'INFO'
|
||||
)
|
||||
$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
$line = "[$ts] [$Level] $Message"
|
||||
|
||||
$color = switch ($Level) {
|
||||
'SUCCESS' { 'Green' }
|
||||
'WARN' { 'Yellow' }
|
||||
'ERROR' { 'Red' }
|
||||
'ACTION' { 'Cyan' }
|
||||
default { 'Gray' }
|
||||
}
|
||||
Write-Host $line -ForegroundColor $color
|
||||
|
||||
try {
|
||||
$line | Out-File -FilePath $LogPath -Append -Encoding UTF8 -Force
|
||||
} catch { }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ── Core remediation logic (spouští se lokálně nebo přes Invoke-Command) ──
|
||||
|
||||
$RemediationScriptBlock = {
|
||||
param(
|
||||
[bool]$WhatIfMode,
|
||||
[bool]$ForceMode,
|
||||
[bool]$VerifyOnlyMode,
|
||||
[bool]$SkipTask
|
||||
)
|
||||
|
||||
$result = [ordered]@{
|
||||
Hostname = $env:COMPUTERNAME
|
||||
Timestamp = (Get-Date).ToString('yyyy-MM-ddTHH:mm:ssZ')
|
||||
PreState = $null
|
||||
ActionsApplied = @()
|
||||
PostState = $null
|
||||
Status = 'Unknown'
|
||||
Message = ''
|
||||
RequiresRestart = $false
|
||||
}
|
||||
|
||||
# Registry konstanty (KB5068202)
|
||||
$REG_SECUREBOOT = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot'
|
||||
$REG_SERVICING = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing'
|
||||
$AVAILABLE_UPDATES_VALUE = 0x5944 # KEK + UEFI CA + Windows UEFI CA + Boot Manager (KB5068202)
|
||||
$TASK_PATH = '\Microsoft\Windows\PI\Secure-Boot-Update'
|
||||
|
||||
#-- Funkce: zjistit aktuální stav -----------------------------------------
|
||||
function Get-State {
|
||||
$s = [ordered]@{
|
||||
FirmwareType = 'Unknown'
|
||||
SecureBootEnabled = $false
|
||||
SecureBootSupported = $false
|
||||
AvailableUpdates = $null
|
||||
HighConfidenceOptOut = $null
|
||||
UEFICA2023Status = $null
|
||||
UEFICA2023StatusText = 'Unknown'
|
||||
UEFICA2023Error = $null
|
||||
WindowsUEFICA2023Capable = $null
|
||||
LastRelevantEvent = $null
|
||||
LastRelevantEventTime = $null
|
||||
}
|
||||
|
||||
# Firmware type
|
||||
$fw = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' -Name PEFirmwareType -ErrorAction SilentlyContinue
|
||||
if ($fw) {
|
||||
$s.FirmwareType = if ($fw.PEFirmwareType -eq 2) { 'UEFI' } else { 'Legacy BIOS' }
|
||||
} elseif (Test-Path $REG_SECUREBOOT) {
|
||||
$s.FirmwareType = 'UEFI'
|
||||
} else {
|
||||
$s.FirmwareType = 'Legacy BIOS'
|
||||
}
|
||||
|
||||
# Secure Boot stav
|
||||
try {
|
||||
$r = Confirm-SecureBootUEFI -ErrorAction Stop
|
||||
$s.SecureBootSupported = $true
|
||||
$s.SecureBootEnabled = [bool]$r
|
||||
} catch { }
|
||||
|
||||
# Registry hodnoty
|
||||
$mainProps = Get-ItemProperty $REG_SECUREBOOT -ErrorAction SilentlyContinue
|
||||
if ($mainProps) {
|
||||
$s.AvailableUpdates = $mainProps.AvailableUpdates
|
||||
$s.HighConfidenceOptOut = $mainProps.HighConfidenceOptOut
|
||||
}
|
||||
|
||||
$svcProps = Get-ItemProperty $REG_SERVICING -ErrorAction SilentlyContinue
|
||||
if ($svcProps) {
|
||||
$s.UEFICA2023Status = $svcProps.UEFICA2023Status
|
||||
$s.UEFICA2023Error = $svcProps.UEFICA2023Error
|
||||
$s.WindowsUEFICA2023Capable = $svcProps.WindowsUEFICA2023Capable
|
||||
|
||||
$s.UEFICA2023StatusText = switch ($s.UEFICA2023Status) {
|
||||
0 { 'NotStarted' }
|
||||
1 { 'InProgress' }
|
||||
2 { 'Success' }
|
||||
3 { 'Failed' }
|
||||
$null { 'KeyNotPresent' }
|
||||
default { "Unknown ($($s.UEFICA2023Status))" }
|
||||
}
|
||||
}
|
||||
|
||||
# Poslední relevantní event
|
||||
try {
|
||||
$evt = Get-WinEvent -FilterHashtable @{
|
||||
LogName = 'System'
|
||||
Id = @(1795,1801,1808)
|
||||
} -MaxEvents 1 -ErrorAction Stop | Select-Object -First 1
|
||||
|
||||
if ($evt) {
|
||||
$s.LastRelevantEvent = $evt.Id
|
||||
$s.LastRelevantEventTime = $evt.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')
|
||||
}
|
||||
} catch { }
|
||||
|
||||
return $s
|
||||
}
|
||||
|
||||
#-- Představ aktuální stav ------------------------------------------------
|
||||
$result.PreState = Get-State
|
||||
|
||||
$pre = $result.PreState
|
||||
|
||||
# Abort podmínky (bez -Force)
|
||||
if (-not $VerifyOnlyMode -and -not $WhatIfMode) {
|
||||
if ($pre.FirmwareType -ne 'UEFI') {
|
||||
$result.Status = 'Skipped'
|
||||
$result.Message = 'Legacy BIOS — Secure Boot není podporováno, remediace není možná.'
|
||||
return $result
|
||||
}
|
||||
if (-not $pre.SecureBootSupported) {
|
||||
$result.Status = 'Skipped'
|
||||
$result.Message = 'Secure Boot není podporováno nebo povoleno — remediace přeskočena.'
|
||||
return $result
|
||||
}
|
||||
if ($pre.UEFICA2023Status -eq 2 -and -not $ForceMode) {
|
||||
$result.Status = 'Skipped'
|
||||
$result.Message = "Aktualizace již byla úspěšně dokončena (UEFICA2023Status=2). Použijte -Force pro opakované spuštění."
|
||||
return $result
|
||||
}
|
||||
if ($pre.WindowsUEFICA2023Capable -eq 0 -and -not $ForceMode) {
|
||||
$result.Status = 'Warning'
|
||||
$result.Message = "WindowsUEFICA2023Capable=0 — firmware nemusí podporovat nové certifikáty. Ověřte firmware u výrobce. Použijte -Force pro vynucení."
|
||||
return $result
|
||||
}
|
||||
if ($pre.LastRelevantEvent -eq 1795) {
|
||||
$result.Status = 'Warning'
|
||||
$result.Message = "Detekován Event 1795 (Hyper-V known issue). Ověřte verzi hostitele — fix je dostupný od 3/2026 (KB5085790). Použijte -Force pro pokračování."
|
||||
if (-not $ForceMode) { return $result }
|
||||
}
|
||||
}
|
||||
|
||||
if ($VerifyOnlyMode) {
|
||||
$result.Status = 'VerifyOnly'
|
||||
$result.Message = 'VerifyOnly mode — žádné změny neprovedeny.'
|
||||
return $result
|
||||
}
|
||||
|
||||
if ($WhatIfMode) {
|
||||
$result.ActionsApplied += "WhatIf: Nastavil by HKLM:\...\SecureBoot\AvailableUpdates = 0x$($AVAILABLE_UPDATES_VALUE.ToString('X4'))"
|
||||
$result.ActionsApplied += "WhatIf: Zajistil by HighConfidenceOptOut = 0"
|
||||
if (-not $SkipTask) {
|
||||
$result.ActionsApplied += "WhatIf: Spustil by scheduled task '$TASK_PATH'"
|
||||
}
|
||||
$result.Status = 'WhatIf'
|
||||
$result.Message = 'WhatIf mode — žádné změny neprovedeny.'
|
||||
return $result
|
||||
}
|
||||
|
||||
#-- Aplikovat registry klíče (KB5068202) ----------------------------------
|
||||
|
||||
# 1) Zajistit existenci SecureBoot klíče (obvykle existuje)
|
||||
if (-not (Test-Path $REG_SECUREBOOT)) {
|
||||
try {
|
||||
New-Item -Path $REG_SECUREBOOT -Force -ErrorAction Stop | Out-Null
|
||||
$result.ActionsApplied += "Vytvořen registry klíč: $REG_SECUREBOOT"
|
||||
} catch {
|
||||
$result.Status = 'Error'
|
||||
$result.Message = "Nelze vytvořit registry klíč $REG_SECUREBOOT : $($_.Exception.Message)"
|
||||
return $result
|
||||
}
|
||||
}
|
||||
|
||||
# 2) Nastavit AvailableUpdates = 0x5944
|
||||
try {
|
||||
Set-ItemProperty -Path $REG_SECUREBOOT -Name 'AvailableUpdates' `
|
||||
-Value $AVAILABLE_UPDATES_VALUE -Type DWord -Force -ErrorAction Stop
|
||||
$result.ActionsApplied += "Nastaveno AvailableUpdates = 0x$($AVAILABLE_UPDATES_VALUE.ToString('X4')) (KB5068202)"
|
||||
} catch {
|
||||
$result.Status = 'Error'
|
||||
$result.Message = "Chyba při nastavení AvailableUpdates: $($_.Exception.Message)"
|
||||
return $result
|
||||
}
|
||||
|
||||
# 3) HighConfidenceOptOut musí být 0 (nebo neexistovat) — jinak se update neprovede
|
||||
$optOut = (Get-ItemProperty $REG_SECUREBOOT -Name 'HighConfidenceOptOut' -ErrorAction SilentlyContinue)
|
||||
if ($optOut -and $optOut.HighConfidenceOptOut -ne 0) {
|
||||
try {
|
||||
Set-ItemProperty -Path $REG_SECUREBOOT -Name 'HighConfidenceOptOut' `
|
||||
-Value 0 -Type DWord -Force -ErrorAction Stop
|
||||
$result.ActionsApplied += "Resetován HighConfidenceOptOut = 0 (byl $($optOut.HighConfidenceOptOut))"
|
||||
} catch {
|
||||
$result.ActionsApplied += "WARN: Nepodařilo se resetovat HighConfidenceOptOut: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# 4) Zajistit existenci Servicing podklíče
|
||||
if (-not (Test-Path $REG_SERVICING)) {
|
||||
try {
|
||||
New-Item -Path $REG_SERVICING -Force -ErrorAction Stop | Out-Null
|
||||
$result.ActionsApplied += "Vytvořen podklíč: $REG_SERVICING"
|
||||
} catch { }
|
||||
}
|
||||
|
||||
#-- Spustit scheduled task ------------------------------------------------
|
||||
if (-not $SkipTask) {
|
||||
try {
|
||||
$task = Get-ScheduledTask -TaskPath '\Microsoft\Windows\PI\' `
|
||||
-TaskName 'Secure-Boot-Update' -ErrorAction Stop
|
||||
|
||||
if ($task) {
|
||||
Start-ScheduledTask -TaskPath '\Microsoft\Windows\PI\' `
|
||||
-TaskName 'Secure-Boot-Update' -ErrorAction Stop
|
||||
$result.ActionsApplied += "Spuštěn scheduled task: $TASK_PATH"
|
||||
|
||||
# Čekat na dokončení tasku (timeout 120 sekund)
|
||||
$timeoutSec = 120
|
||||
$intervalSec = 3
|
||||
$elapsed = 0
|
||||
$finalState = 'Unknown'
|
||||
|
||||
do {
|
||||
Start-Sleep -Seconds $intervalSec
|
||||
$elapsed += $intervalSec
|
||||
$finalState = (Get-ScheduledTask -TaskPath '\Microsoft\Windows\PI\' `
|
||||
-TaskName 'Secure-Boot-Update' `
|
||||
-ErrorAction SilentlyContinue).State
|
||||
} while ($finalState -eq 'Running' -and $elapsed -lt $timeoutSec)
|
||||
|
||||
if ($elapsed -ge $timeoutSec -and $finalState -eq 'Running') {
|
||||
$result.ActionsApplied += "WARN: Task stále běží po $timeoutSec s — pokračuji bez čekání. Ověřit stav ručně."
|
||||
} else {
|
||||
$result.ActionsApplied += "Task dokončen za ${elapsed}s — stav: $finalState"
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$result.ActionsApplied += "INFO: Scheduled task '$TASK_PATH' nebyl nalezen nebo se nepodařilo spustit: $($_.Exception.Message)"
|
||||
$result.ActionsApplied += "INFO: Task se spustí automaticky při příštím plánovaném běhu (každých 12 hodin)."
|
||||
}
|
||||
} else {
|
||||
$result.ActionsApplied += "INFO: Spuštění scheduled task přeskočeno (-SkipScheduledTask). Task se spustí sám (každých 12 hodin)."
|
||||
}
|
||||
|
||||
#-- Post-stav (okamžitě po aplikaci, před restartem) ---------------------
|
||||
Start-Sleep -Seconds 2
|
||||
$result.PostState = Get-State
|
||||
|
||||
$result.RequiresRestart = $true
|
||||
$result.Status = 'Applied'
|
||||
$result.Message = "Registry klíče nastaveny. Server vyžaduje RESTART pro aplikaci certifikátů. Po restartu ověřit EventID 1808 v System logu."
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ── Výstup výsledku ────────────────────────────────────────────────────
|
||||
|
||||
function Write-ResultSummary {
|
||||
param($res)
|
||||
|
||||
$line = '-' * 60
|
||||
Write-Log $line
|
||||
Write-Log "SERVER: $($res.Hostname) | $($res.Timestamp)"
|
||||
$_statusLevel = switch ($res.Status) {
|
||||
'Applied' { 'SUCCESS' }
|
||||
'Error' { 'ERROR' }
|
||||
'Warning' { 'WARN' }
|
||||
'Skipped' { 'WARN' }
|
||||
default { 'INFO' }
|
||||
}
|
||||
Write-Log "STATUS: $($res.Status) — $($res.Message)" -Level $_statusLevel
|
||||
|
||||
$pre = $res.PreState
|
||||
if ($pre) {
|
||||
Write-Log " Pre-state:"
|
||||
Write-Log " Firmware : $($pre.FirmwareType)"
|
||||
Write-Log " Secure Boot : podporováno=$($pre.SecureBootSupported), povoleno=$($pre.SecureBootEnabled)"
|
||||
Write-Log " AvailableUpdates : $($pre.AvailableUpdates)"
|
||||
Write-Log " UEFICA2023Status : $($pre.UEFICA2023Status) ($($pre.UEFICA2023StatusText))"
|
||||
Write-Log " WindowsCapable : $($pre.WindowsUEFICA2023Capable)"
|
||||
if ($pre.UEFICA2023Error) {
|
||||
Write-Log " ! UEFICA2023Error : $($pre.UEFICA2023Error)" -Level 'WARN'
|
||||
}
|
||||
if ($pre.LastRelevantEvent) {
|
||||
Write-Log " Poslední event : EventID $($pre.LastRelevantEvent) ($($pre.LastRelevantEventTime))"
|
||||
}
|
||||
}
|
||||
|
||||
if ($res.ActionsApplied -and $res.ActionsApplied.Count -gt 0) {
|
||||
Write-Log " Provedené akce:"
|
||||
foreach ($action in $res.ActionsApplied) {
|
||||
$lvl = if ($action -like 'WhatIf:*' -or $action -like 'INFO:*') { 'INFO' }
|
||||
elseif ($action -like 'WARN:*') { 'WARN' }
|
||||
else { 'ACTION' }
|
||||
Write-Log " $action" -Level $lvl
|
||||
}
|
||||
}
|
||||
|
||||
$post = $res.PostState
|
||||
if ($post -and $res.Status -eq 'Applied') {
|
||||
Write-Log " Post-state (před restartem):"
|
||||
Write-Log " AvailableUpdates : $($post.AvailableUpdates)"
|
||||
Write-Log " UEFICA2023Status : $($post.UEFICA2023Status) ($($post.UEFICA2023StatusText))"
|
||||
}
|
||||
|
||||
if ($res.RequiresRestart) {
|
||||
Write-Log " *** VYŽADOVÁN RESTART pro aplikaci certifikátů ***" -Level 'WARN'
|
||||
Write-Log " Po restartu ověřit: Get-WinEvent -FilterHashtable @{LogName='System';Id=1808} -MaxEvents 5"
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ── Main ───────────────────────────────────────────────────────────────
|
||||
|
||||
$isWhatIf = $WhatIfPreference -eq [Management.Automation.ActionPreference]::Continue
|
||||
$isVerify = $VerifyOnly.IsPresent
|
||||
$isForce = $Force.IsPresent
|
||||
$isSkipTask = $SkipScheduledTask.IsPresent
|
||||
|
||||
Write-Log ('=' * 60)
|
||||
Write-Log "Set-SecureBootCertificateUpdate — START"
|
||||
Write-Log "Parametry: WhatIf=$isWhatIf, Force=$isForce, VerifyOnly=$isVerify, SkipTask=$isSkipTask"
|
||||
Write-Log "Log: $LogPath"
|
||||
Write-Log ('=' * 60)
|
||||
|
||||
$results = @()
|
||||
|
||||
if (-not $ServerName -or $ServerName.Count -eq 0) {
|
||||
# ── Lokální spuštění ──
|
||||
Write-Log "Spouštím lokálně na: $env:COMPUTERNAME" -Level 'INFO'
|
||||
|
||||
if (-not $isWhatIf -and -not $isVerify) {
|
||||
if (-not $PSCmdlet.ShouldProcess($env:COMPUTERNAME, "Nastavit Secure Boot registry klíče (AvailableUpdates=0x5944)")) {
|
||||
Write-Log "Uživatel zamítl akci — konec." -Level 'WARN'
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
$res = & $RemediationScriptBlock -WhatIfMode $isWhatIf -ForceMode $isForce `
|
||||
-VerifyOnlyMode $isVerify -SkipTask $isSkipTask
|
||||
Write-ResultSummary $res
|
||||
$results += $res
|
||||
|
||||
} else {
|
||||
# ── Vzdálené spuštění ──
|
||||
Write-Log "Cílové servery ($($ServerName.Count)): $($ServerName -join ', ')" -Level 'INFO'
|
||||
|
||||
$invokeParams = @{
|
||||
ComputerName = $ServerName
|
||||
ScriptBlock = $RemediationScriptBlock
|
||||
ArgumentList = @($isWhatIf, $isForce, $isVerify, $isSkipTask)
|
||||
ErrorAction = 'SilentlyContinue'
|
||||
ThrottleLimit = 10
|
||||
}
|
||||
if ($Credential) { $invokeParams['Credential'] = $Credential }
|
||||
|
||||
$remoteResults = Invoke-Command @invokeParams
|
||||
|
||||
foreach ($res in $remoteResults) {
|
||||
Write-ResultSummary $res
|
||||
$results += $res
|
||||
}
|
||||
|
||||
# Servery, které neodpověděly
|
||||
$respondedHosts = $remoteResults | ForEach-Object { $_.Hostname }
|
||||
foreach ($srv in $ServerName) {
|
||||
if ($srv -notin $respondedHosts) {
|
||||
Write-Log " ! Server '$srv' neodpověděl (WinRM nedostupný nebo přihlášení selhalo)" -Level 'ERROR'
|
||||
$results += [ordered]@{
|
||||
Hostname = $srv; Status = 'Unreachable'; Message = 'WinRM nedostupný'
|
||||
PreState = $null; PostState = $null; ActionsApplied = @(); RequiresRestart = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#-- Souhrn ------------------------------------------------------------------
|
||||
Write-Log ('=' * 60)
|
||||
Write-Log "SOUHRN REMEDIACE"
|
||||
|
||||
$grouped = $results | Group-Object Status
|
||||
foreach ($g in $grouped) {
|
||||
$lvl = switch ($g.Name) {
|
||||
'Applied' { 'SUCCESS' }
|
||||
'Error' { 'ERROR' }
|
||||
'Unreachable' { 'ERROR' }
|
||||
'Skipped' { 'WARN' }
|
||||
'Warning' { 'WARN' }
|
||||
default { 'INFO' }
|
||||
}
|
||||
Write-Log " $($g.Name): $($g.Count) server(ů) [$(($g.Group.Hostname) -join ', ')]" -Level $lvl
|
||||
}
|
||||
|
||||
$needRestart = @($results | Where-Object { $_.RequiresRestart })
|
||||
if ($needRestart.Count -gt 0) {
|
||||
Write-Log ''
|
||||
Write-Log "VYŽADOVÁN RESTART ($($needRestart.Count) server(ů)): $($needRestart.Hostname -join ', ')" -Level 'WARN'
|
||||
Write-Log "Po restartu ověřit EventID 1808 v System logu nebo spustit:"
|
||||
Write-Log " Invoke-Command -ComputerName <server> -ScriptBlock { Get-WinEvent -FilterHashtable @{LogName='System';Id=@(1801,1808)} -MaxEvents 5 | Select TimeCreated,Id,Message }" -Level 'INFO'
|
||||
}
|
||||
|
||||
Write-Log ('=' * 60)
|
||||
Write-Log "Set-SecureBootCertificateUpdate — KONEC | Log: $LogPath"
|
||||
Write-Log ('=' * 60)
|
||||
|
||||
# Vrátit výsledky jako objekty pro pipeline
|
||||
$results
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1,230 @@
|
||||
# Projekt: Audit a remediace Secure Boot certifikátů na Windows Serverech
|
||||
|
||||
## Kontext a popis problematiky
|
||||
|
||||
### Co se děje
|
||||
|
||||
Microsoft vydal v červnu 2025 upozornění (KB5062710), že původní Secure Boot certifikáty z roku 2011 expirují v průběhu roku 2026. Jde o certifikáty uložené v UEFI firmware (KEK a DB databázích), které zajišťují důvěryhodnost boot procesu.
|
||||
|
||||
**Expirující certifikáty a termíny:**
|
||||
|
||||
| Certifikát | Expirace | Náhrada |
|
||||
|---|---|---|
|
||||
| Microsoft Corporation KEK CA 2011 | **24. června 2026** | Microsoft Corporation KEK 2K CA 2023 |
|
||||
| Microsoft UEFI CA 2011 | **27. června 2026** | Microsoft UEFI CA 2023 + Microsoft Option ROM UEFI CA 2023 |
|
||||
| Microsoft Windows Production PCA 2011 | 19. října 2026 | Windows UEFI CA 2023 |
|
||||
|
||||
### Dopad na servery
|
||||
|
||||
Servery, které neobdrží nové 2023 certifikáty, budou dál fungovat a bootovat normálně, ale **přestanou být schopné přijímat nové bezpečnostní ochrany boot procesu** — včetně aktualizací Windows Boot Manageru, Secure Boot databází, revokačních seznamů a mitigací nově objevených boot-level zranitelností. Postupem času to omezí ochranu před hrozbami a může ovlivnit scénáře závislé na Secure Boot důvěře, jako je BitLocker hardening nebo third-party bootloadery.
|
||||
|
||||
### Specifika prostředí (MSP kontext)
|
||||
|
||||
- Windows Server 2016, 2019, 2022, 2025
|
||||
- Fyzické servery různých výrobců (Dell, HP, Lenovo, Supermicro, ...)
|
||||
- Virtuální stroje na **Hyper-V** (na Windows Server hostiteli)
|
||||
- Virtuální stroje na **VMware ESXi**
|
||||
- Prostředí spravovaná přes AD/GPO, bez Intune
|
||||
- ~100–150 klientských prostředí různé velikosti
|
||||
|
||||
### Klíčové technické detaily
|
||||
|
||||
**Registry klíče relevantní pro stav:**
|
||||
- `HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot` — hlavní klíč (SecureBootEnabled, AvailableUpdates, HighConfidenceOptOut)
|
||||
- `HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing` — stav aktualizace (UEFICA2023Status, UEFICA2023Error, WindowsUEFICA2023Capable)
|
||||
- `HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing\DeviceAttributes` — atributy zařízení
|
||||
|
||||
**Relevantní Event IDs (System log):**
|
||||
- **1801** — chybová událost, certifikáty NEBYLY aplikovány
|
||||
- **1808** — informační událost, certifikáty BYLY úspěšně aplikovány
|
||||
- **1795** — selhání aktualizace (známý problém zejména u Hyper-V VM)
|
||||
- **1796**, **1800**, **1802**, **1803** — doplňkové události průběhu
|
||||
|
||||
**PowerShell cmdlet pro základní check:**
|
||||
```powershell
|
||||
Confirm-SecureBootUEFI
|
||||
```
|
||||
Vrací `True` (Secure Boot zapnutý), `False`, nebo exception pokud UEFI Secure Boot není podporován.
|
||||
|
||||
**Hyper-V specifikum:** U VM běžících na Hyper-V může selhávat aktualizace certifikátů s Event ID 1795. Tento known issue byl Microsoft řešen (resolved March 30, 2026) — ověřit verzi Hyper-V hostitele a aplikovat příslušné opravy.
|
||||
|
||||
**VMware ESXi specifikum:** Záleží, zda VM má Secure Boot vůbec povolený (nastavení VM hardware). U Generation 1 ekvivalentu (BIOS mode) Secure Boot neexistuje.
|
||||
|
||||
---
|
||||
|
||||
## Cíl projektu
|
||||
|
||||
### Fáze 1 — Detekce (inventář)
|
||||
|
||||
Vytvořit PowerShell detekční skript, který lze spustit vzdáleně přes `Invoke-Command` nebo lokálně na každém serveru a vrátit strukturovaný přehled stavu.
|
||||
|
||||
**Výstup skriptu musí obsahovat:**
|
||||
|
||||
1. Základní identifikace serveru (hostname, OS verze, architektura)
|
||||
2. Typ prostředí (fyzický / Hyper-V VM / VMware VM / jiné)
|
||||
3. Je Secure Boot podporováno? (UEFI vs. Legacy BIOS)
|
||||
4. Je Secure Boot povoleno?
|
||||
5. Aktuální stav certifikátů:
|
||||
- Jsou přítomny expirující 2011 certifikáty? (KEK CA 2011, UEFI CA 2011, Windows PCA 2011)
|
||||
- Jsou přítomny nové 2023 certifikáty?
|
||||
- Stav z registry (`UEFICA2023Status`)
|
||||
6. Confidence level (z event logu 1801/1808)
|
||||
7. Poslední relevantní Event ID a čas
|
||||
8. Hardware info (výrobce, model, firmware verze, datum firmware)
|
||||
9. Dostupné aktualizace (`AvailableUpdates` registry hodnota)
|
||||
10. Případné chyby (`UEFICA2023Error`)
|
||||
|
||||
**Formát výstupu:** JSON (pro snadné zpracování a agregaci) + human-readable summary
|
||||
|
||||
**Požadavky na skript:**
|
||||
- Běží bez instalace dalších modulů (pure PowerShell 5.1+)
|
||||
- Tolerantní vůči chybám — pokud Secure Boot není podporován/povolen, nezhavaruje
|
||||
- Rozlišuje fyzický vs. VM (Hyper-V / VMware) přes WMI
|
||||
- Exportuje výsledek lokálně i umožní vzdálené volání
|
||||
- Wrapper skript pro hromadné spuštění přes seznam serverů z CSV/textového souboru
|
||||
|
||||
### Fáze 2 — Analýza a kategorizace
|
||||
|
||||
Na základě dat z detekce kategorizovat každý server do skupin:
|
||||
|
||||
| Kategorie | Popis | Doporučená akce |
|
||||
|---|---|---|
|
||||
| **✅ OK** | Má nové 2023 certifikáty, Secure Boot aktivní | Žádná akce |
|
||||
| **⚠️ Nutná aktualizace** | Má staré 2011 certifikáty, Secure Boot aktivní, firmware OK | Naplánovat rollout |
|
||||
| **🔧 Čeká na firmware** | Secure Boot aktivní, ale firmware nepodporuje update | Kontaktovat OEM / update firmware |
|
||||
| **⏸️ Secure Boot vypnuto** | Secure Boot existuje ale není zapnuto | Rozhodnutí o enablementu |
|
||||
| **❌ Secure Boot nepodporováno** | Legacy BIOS nebo VM bez vTPM | Dokumentovat jako výjimku |
|
||||
| **🔴 Selhání aktualizace** | Event 1795/chyba v registry | Troubleshooting |
|
||||
|
||||
### Fáze 3 — Remediace
|
||||
|
||||
Navrhnout a implementovat remediační postup pro prostředí bez Intune:
|
||||
|
||||
**Metody rollout (zvážit dle prostředí klienta):**
|
||||
|
||||
1. **Registry key metoda** — přímé nastavení klíčů přes PowerShell/GPO (KB5068202)
|
||||
2. **GPO metoda** — Group Policy Objects pro nastavení (KB5068198)
|
||||
3. **WinCS CLI** — Windows Configuration System nástroj pro domain-joined stroje (KB5068197)
|
||||
4. **Ruční / skriptovaný rollout** — pro izolovaná prostředí
|
||||
|
||||
**Postup rollout (staging):**
|
||||
1. Test na 1–2 reprezentativních serverech každého typu (výrobce + firmware verze)
|
||||
2. Ověření úspěchu přes Event ID 1808 a registry
|
||||
3. Rozšíření na další skupinu
|
||||
4. Monitoring (min. 48 hodin po aplikaci, čekat na restart)
|
||||
|
||||
**Remediační PowerShell** musí umět:
|
||||
- Nastavit registry klíče pro zahájení aktualizace
|
||||
- Spustit/zkontrolovat scheduled task pro Secure Boot update
|
||||
- Ověřit výsledek po restartu
|
||||
|
||||
---
|
||||
|
||||
## Technické požadavky na kód
|
||||
|
||||
### Detekční skript
|
||||
|
||||
```
|
||||
Soubor: Invoke-SecureBootAudit.ps1
|
||||
```
|
||||
|
||||
**Logika detekce certifikátů** — kontrolovat přítomnost certifikátů přes `Get-SecureBootUEFI`:
|
||||
- KEK databáze: hledat thumbprinty/subject odpovídající 2011 a 2023 certifikátům
|
||||
- DB databáze: hledat Windows Production PCA 2011 / UEFI CA 2011 / jejich 2023 náhrady
|
||||
|
||||
**Detekce typu prostředí:**
|
||||
```powershell
|
||||
# Hyper-V VM
|
||||
(Get-WmiObject Win32_ComputerSystem).Model -eq "Virtual Machine" -and
|
||||
(Get-WmiObject Win32_ComputerSystem).Manufacturer -like "*Microsoft*"
|
||||
|
||||
# VMware VM
|
||||
(Get-WmiObject Win32_ComputerSystem).Manufacturer -like "*VMware*"
|
||||
```
|
||||
|
||||
**Ošetřit výjimky:**
|
||||
- `Get-SecureBootUEFI` hodí exception pokud Secure Boot není podporováno nebo povoleno — zachytit a zalogovat
|
||||
- Na Legacy BIOS strojích `Confirm-SecureBootUEFI` vrátí exception nebo False
|
||||
|
||||
### Wrapper pro hromadné spuštění
|
||||
|
||||
```
|
||||
Soubor: Start-SecureBootFleetAudit.ps1
|
||||
```
|
||||
|
||||
- Vstup: seznam serverů (parametr nebo CSV soubor s kolonkou `ServerName`)
|
||||
- Výstup: agregovaný JSON + CSV soubor pro import do Excelu
|
||||
- Paralelizace: `Invoke-Command` s `-ThrottleLimit` (doporučeno max 20 paralelně)
|
||||
- Logování: výpis chyb nedostupných serverů do separátního souboru
|
||||
|
||||
### Remediační skript
|
||||
|
||||
```
|
||||
Soubor: Set-SecureBootCertificateUpdate.ps1
|
||||
```
|
||||
|
||||
- Parametry: `-ServerName`, `-WhatIf`, `-Force`
|
||||
- WhatIf mode pro ověření před spuštěním
|
||||
- Nastavit příslušné registry klíče dle KB5068202
|
||||
- Spustit nebo počkat na scheduled task
|
||||
- Zalogovat výsledek
|
||||
|
||||
---
|
||||
|
||||
## Omezení a rizika
|
||||
|
||||
### Hyper-V VM
|
||||
- Aktualizace Secure Boot certifikátů ve VM závisí na tom, zda **hypervizor poskytuje virtualizovaný UEFI** podporující aktualizace
|
||||
- Generation 1 VM (BIOS) — Secure Boot nepodporují vůbec
|
||||
- Generation 2 VM — mají virtualizovaný UEFI, ale aktualizace certifikátů závisí na verzi hypervizoru
|
||||
- Known issue s Event 1795 byl opraven (březen 2026) — ověřit verzi Windows Server hostitele
|
||||
|
||||
### VMware ESXi
|
||||
- Secure Boot ve VMware VM závisí na konfiguraci VM (EFI firmware + Secure Boot v nastavení VM)
|
||||
- VMware neposkytuje automatické předání nových certifikátů do VM — aktualizace musí proběhnout přes Windows jako na fyzickém stroji
|
||||
- Na starších ESXi verzích může být virtualizovaný UEFI omezený
|
||||
|
||||
### Fyzické servery
|
||||
- Starší servery (zejména pre-2018) mohou vyžadovat firmware update od OEM před aplikací nových Secure Boot certifikátů
|
||||
- Servery end-of-life od výrobce mohou nemít dostupný firmware update — dokumentovat jako výjimku
|
||||
|
||||
### Obecná rizika
|
||||
- Neúspěšná aktualizace certifikátů **NEOHROZÍ boot serveru** — server bude dál fungovat se starými certifikáty
|
||||
- Aktualizace vyžaduje restart pro aplikaci Boot Manageru (ne jen certifikátů)
|
||||
- Po aplikaci počítat s min. 48 hodinami a jedním nebo více restarty
|
||||
|
||||
---
|
||||
|
||||
## Reference dokumentace
|
||||
|
||||
- KB5062710 — [Přehled: Windows Secure Boot certificate expiration and CA updates](https://support.microsoft.com/en-us/topic/windows-secure-boot-certificate-expiration-and-ca-updates-7ff40d33-95dc-4c3c-8725-a9b95457578e)
|
||||
- KB5062713 — [IT Pro guidance](https://support.microsoft.com/en-us/help/5062713)
|
||||
- KB5068202 — [Registry key updates](https://support.microsoft.com/en-us/help/5068202)
|
||||
- KB5068198 — [GPO metoda](https://support.microsoft.com/en-us/help/5068198)
|
||||
- KB5068197 — [WinCS APIs](https://support.microsoft.com/en-us/help/5068197)
|
||||
- KB5085046 — [Troubleshooting](https://support.microsoft.com/en-us/help/5085046)
|
||||
- KB5085790 — [Known issues and resolutions](https://support.microsoft.com/en-us/help/5085790)
|
||||
- KB5086397 — [Azure Local / HyperConverged Infrastructure](https://support.microsoft.com/en-us/help/5086397)
|
||||
- [Secure Boot DB and DBX variable update events](https://support.microsoft.com/topic/37e47cf8-608b-4a87-8175-bdead630eb69)
|
||||
|
||||
---
|
||||
|
||||
## Výstupy projektu (deliverables)
|
||||
|
||||
1. **`Invoke-SecureBootAudit.ps1`** — detekční skript pro jednotlivý server
|
||||
2. **`Start-SecureBootFleetAudit.ps1`** — wrapper pro hromadný audit celé sítě
|
||||
3. **`Set-SecureBootCertificateUpdate.ps1`** — remediační skript s WhatIf podporou
|
||||
4. **`README.md`** — dokumentace použití všech skriptů
|
||||
5. **Vzorový výstup** — příklad JSON a CSV výstupu auditu
|
||||
|
||||
---
|
||||
|
||||
## Priorita a časový rámec
|
||||
|
||||
**Urgentní** — první dvě expirace jsou **24. a 27. června 2026** (týden od dnešního data). Audit je nutné provést okamžitě, aby bylo jasné, na kolika serverech je nutná akce.
|
||||
|
||||
**Doporučené pořadí práce:**
|
||||
1. Detekční skript (Fáze 1) — okamžitě
|
||||
2. Hromadný wrapper + export do CSV — obratem po detekci
|
||||
3. Analýza výsledků a kategorizace
|
||||
4. Remediační skript — po otestování na pilotních serverech
|
||||
Reference in New Issue
Block a user