employee-workstation-compli.../playbooks/windows.yaml

450 lines
17 KiB
YAML

# yaml-language-server: $schema=https://github.com/benedictjohannes/crobe/releases/latest/download/playbook.schema.json
title: "Windows Compliance Playbook"
sections:
- title: "1. OS Maintenance & Security"
description:
- "Checks for OS updates, official verification, and authentication security."
assertions:
- code: OS_RECENTLY_UPDATED
title: "OS Recently Updated"
description: "Verify the system has been updated within the last 30 days."
cmds:
- exec:
shell: "powershell"
script: |
$history = (New-Object -ComObject Microsoft.Update.Session).CreateUpdateSearcher().QueryHistory(0,1)
if ($history) {
[Math]::Floor((New-TimeSpan -Start '1970-01-01' -End $history[0].Date.ToUniversalTime()).TotalSeconds)
} else {
echo "Unknown"
}
gather:
- key: "OS_LAST_UPDATE_TIME"
func: |
(stdout) => {
const val = parseInt(stdout.trim());
return isNaN(val) ? "Unknown" : new Date(val * 1000).toISOString();
}
stdOutRule:
func: |
(stdout, stderr) => {
const lastUpdate = parseInt(stdout.trim());
if (isNaN(lastUpdate)) return -1;
const thirtyDaysInSeconds = 30 * 24 * 60 * 60;
const nowInSeconds = Math.floor(Date.now() / 1000);
return (nowInSeconds - lastUpdate) < thirtyDaysInSeconds ? 1 : -1;
}
passDescription: "System was updated in the last 30 days."
failDescription: "System has not been updated in the last 30 days."
- code: OS_INTEGRITY
title: "OS Integrity Verification"
description: "Checks if the OS is recognized from official sources."
cmds:
- exec:
shell: "powershell"
script: "(Get-WmiObject Win32_OperatingSystem).Caption"
stdOutRule:
regex: "Windows"
passDescription: "OS is identified as a recognized distribution."
failDescription: "OS distribution is not recognized or is non-standard."
- code: DEVICE_INFO
title: "Device Identification"
description: "Gathering hardware identities (Brand, Model, and Serial)."
cmds:
- exec:
script: |
$sys = Get-CimInstance Win32_ComputerSystem
$bios = Get-CimInstance Win32_BIOS
Write-Output "$($sys.Manufacturer)|||$($sys.Model)|||$($bios.SerialNumber)"
gather:
- key: "DEVICE_BRAND"
func: "(stdout) => stdout.split('|||')[0]"
- key: "DEVICE_MODEL"
func: "(stdout) => stdout.split('|||')[1]"
- key: "DEVICE_SERIAL"
func: "(stdout) => stdout.split('|||')[2].trim()"
stdOutRule:
func: (stdout) => stdout.split('|||').length === 3
passDescription: Device information successfully gathered
failDescription: Fail to gather device information
- title: "2. Identity & Access Control"
description:
- "Audits login configurations, screen lock, and administrative privileges."
assertions:
- code: NO_AUTO_LOGIN
title: "No Auto Login"
description: "Ensures automatic login is disabled."
cmds:
- exec:
shell: "powershell"
script: |
$val = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name AutoAdminLogon -ErrorAction SilentlyContinue
if ($val) { $val.AutoAdminLogon } else { "0" }
stdOutRule:
regex: "^0$"
passDescription: "Auto login is disabled."
failDescription: "Auto login appears to be enabled."
- code: SCREEN_LOCK_TIMEOUT
title: "Screen Lock Timeout"
description: "Verify screen lock timeout is under 30 minutes (1800 seconds)."
cmds:
- exec:
shell: "powershell"
script: |
$policyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
$policy = Get-ItemProperty -Path $policyPath -Name InactivityTimeoutSecs -ErrorAction SilentlyContinue
if ($policy -and $policy.InactivityTimeoutSecs) {
Write-Output "POLICY:$($policy.InactivityTimeoutSecs)"
exit
}
$ssPath = "HKCU:\Control Panel\Desktop"
$timeout = Get-ItemProperty -Path $ssPath -Name ScreenSaveTimeOut -ErrorAction SilentlyContinue
$secure = Get-ItemProperty -Path $ssPath -Name ScreenSaverIsSecure -ErrorAction SilentlyContinue
if ($timeout -and $secure.ScreenSaverIsSecure -eq "1") {
Write-Output "SCREENSAVER:$($timeout.ScreenSaveTimeOut)"
exit
}
# Modern "Require sign-in" lock detection (DelayLockInterval)
$delayLock = Get-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name DelayLockInterval -ErrorAction SilentlyContinue
if ($null -ne $delayLock) {
$videoIdle = powercfg /q SCHEME_CURRENT SUB_VIDEO VIDEOIDLE
$acOff = 0
foreach ($line in $videoIdle) {
if ($line -like "*Current AC Power Setting Index:*") {
$val = $line.Split(':')[-1].Trim()
$acOff = [Convert]::ToInt32($val, 16)
}
}
$totalLock = $acOff + [int]$delayLock.DelayLockInterval
Write-Output "MODERN:$totalLock"
exit
}
Write-Output "NOT_CONFIGURED"
gather:
- key: "SCREEN_LOCK_TIMEOUT_SECONDS"
func: |
(stdout) => {
if (!stdout.includes(":")) return "Unknown";
const val = parseInt(stdout.split(":")[1]);
return isNaN(val) ? "Unknown" : val;
}
stdOutRule:
func: |
(stdout) => {
const out = stdout.trim();
if (out === "NOT_CONFIGURED") return -1;
const parts = out.split(":");
if (parts.length !== 2) return -1;
const val = parseInt(parts[1]);
if (isNaN(val)) return -1;
return val <= 1800 ? 1 : -1;
}
passDescription: "Screen lock timeout is configured within safe limits."
failDescription: "Screen lock timeout is too long or disabled."
- code: USER_PRIVILEGE
title: "User is not running as local administrator"
description: "Ensures the current user is not local administrator/root."
cmds:
- exec:
shell: "powershell"
script: |
try {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$sid = $identity.User.Value
$username = $identity.Name
# Check for the RID 500 (Built-in Administrator)
if ($sid -match "-500$") {
Write-Output "BUILTIN_ADMIN|$username|$sid"
} else {
Write-Output "STANDARD|$username|$sid"
}
} catch {
Write-Output "UNKNOWN"
}
gather:
- key: "CURRENT_USER_ID"
func: |
(stdout) => {
const val = stdout.trim();
if (!val.includes("|")) return val;
return val.split("|")[0];
}
stdOutRule:
func: |
(stdout) => {
const val = stdout.trim();
if (val === "UNKNOWN") return -1;
if (val.startsWith("BUILTIN_ADMIN")) return -1;
return 1;
}
passDescription: "User is operating with standard privileges."
failDescription: "User is operating with administrative privileges."
- title: "3. Disk Security"
description:
- "Checks for disk encryption status."
assertions:
- code: ENCRYPTION_STATUS
title: "Disk / Storage is Encrypted"
description: "Verify that at least one block device is encrypted."
cmds:
- exec:
shell: "powershell"
script: |
$ErrorActionPreference = "Stop"
try {
$volumes = Get-BitLockerVolume
if ($volumes) {
$encrypted = $volumes | Where-Object { $_.ProtectionStatus -eq 'On' }
if ($encrypted) {
Write-Output "ENCRYPTED"
} else {
Write-Output "NOT_ENCRYPTED"
}
exit
}
} catch {}
try {
$reg = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\BitLocker" -ErrorAction Stop
if ($reg) {
Write-Output "ENCRYPTED"
exit
}
} catch {}
Write-Output "INSUFFICIENT_PRIVILEGE"
gather:
- key: "ENCRYPTED_DEVICES"
func: "(stdout) => stdout.trim()"
stdOutRule:
func: |
(stdout) => {
const val = stdout.trim();
if (val === "ENCRYPTED") return 1;
if (val === "NOT_ENCRYPTED") return -1;
if (val === "INSUFFICIENT_PRIVILEGE") return 0;
return -1;
}
passDescription: "Encrypted volumes detected."
failDescription: "No encrypted volumes found."
- title: "4. Threat and Malware Protection"
description:
- "Checks for Antivirus installation, update status, and real-time protection."
assertions:
- code: ANTIVIRUS_ACTIVE
title: "Antivirus Active"
description: "Verify that antivirus is running."
cmds:
- exec:
shell: "powershell"
script: "(Get-MpComputerStatus).RealTimeProtectionEnabled"
stdOutRule:
regex: "True"
passDescription: "Antivirus is active"
failDescription: "Antivirus is not active or not installed."
- code: ANTIVIRUS_UPDATED
title: "Antivirus Definitions Up-to-Date"
description: "Check if antivirus definitions were updated in the last 30 days."
cmds:
- exec:
shell: "powershell"
script: |
$date = (Get-MpComputerStatus).AntivirusSignatureLastUpdated
if ($date) {
[Math]::Floor((New-TimeSpan -Start '1970-01-01' -End $date.ToUniversalTime()).TotalSeconds)
} else {
echo "Unknown"
}
gather:
- key: "ANTIVIRUS_LAST_UPDATE"
func: |
(stdout) => {
const val = parseInt(stdout.trim());
return isNaN(val) ? "Unknown" : new Date(val * 1000).toISOString();
}
stdOutRule:
func: |
(stdout) => {
const stamp = parseInt(stdout.trim());
if (isNaN(stamp)) return -1;
const thirtyDays = 30 * 24 * 60 * 60;
const now = Math.floor(Date.now() / 1000);
return (now - stamp) < thirtyDays ? 1 : -1;
}
passDescription: "Definitions are up to date."
failDescription: "Definitions are older than 30 days or missing."
- title: "5. Network Security"
description:
- "Checks firewall and network protection status."
assertions:
- code: FIREWALL_ACTIVE
title: "Firewall Status"
description: "Verify that a firewall is active."
cmds:
- exec:
shell: "powershell"
script: |
$profiles = Get-NetFirewallProfile -Profile Domain,Public,Private
$active = $profiles | Where-Object {$_.Enabled -eq 'True'}
if ($active) { "Active (" + ($active | ForEach-Object {$_.Name} | Out-String).Trim() + ")" } else { "Inactive" }
gather:
- key: "FIREWALL_STATUS"
func: "(stdout) => stdout.trim()"
stdOutRule:
regex: "Active"
passDescription: "Firewall is active."
failDescription: "No active firewall detected."
- title: "6. Boot Security"
description:
- "Verification of firmware security settings."
assertions:
- code: SECURE_BOOT_ENABLED
title: "Secure Boot State"
description: "Check if UEFI Secure Boot is enabled."
cmds:
- exec:
shell: "powershell"
script: |
try {
$result = Confirm-SecureBootUEFI
if ($result -eq $true) {
"ENABLED"
} else {
"DISABLED"
}
} catch {
if ($_.Exception.Message -like "*Access was denied*") {
"INSUFFICIENT_PRIVILEGE"
} else {
"NOT_SUPPORTED"
}
}
gather: []
stdOutRule:
func: |
(stdout) => {
const val = stdout.trim();
if (val === "ENABLED") return 1;
if (val === "DISABLED") return -1;
if (val === "INSUFFICIENT_PRIVILEGE") return 0;
if (val === "NOT_SUPPORTED") return 1;
return -1;
}
passDescription: "Secure Boot is enabled."
failDescription: "Secure Boot is disabled."
- title: "7. Application Integrity"
description:
- "Audits browser update status."
assertions:
- code: BROWSER_UP_TO_DATE
title: "Browser Updated"
description: "Verify if the primary web browsers has been updated within the last 30 days."
cmds:
- exec:
shell: "powershell"
script: |
$paths = @(
"C:\Program Files\Google\Chrome\Application\chrome.exe",
"$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe",
"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
"C:\Program Files\Mozilla Firefox\firefox.exe"
)
foreach ($p in $paths) {
if (Test-Path $p) {
$file = Get-Item $p
$time = [int](New-TimeSpan -Start '1970-01-01' -End $file.LastWriteTimeUtc).TotalSeconds
Write-Output "$p|$time"
}
}
gather:
- key: "FIREFOX_LAST_UPDATE"
func: |
(stdout) => {
const line = stdout.trim().split('\n').find(l => l.includes('firefox'));
if (!line) return "Not Installed";
return new Date(parseInt(line.split('|')[1]) * 1000).toISOString();
}
- key: "CHROME_LAST_UPDATE"
func: |
(stdout) => {
const line = stdout.trim().split('\n').find(l => l.includes('chrome') || l.includes('chromium'));
if (!line) return "Not Installed";
return new Date(parseInt(line.split('|')[1]) * 1000).toISOString();
}
stdOutRule:
func: |
(stdout) => {
const lines = stdout.trim().split('\n').filter(Boolean);
if (lines.length === 0) return -1;
const now = Date.now() / 1000;
const thirtyDays = 30 * 24 * 60 * 60;
let pass = false;
lines.forEach(line => {
const parts = line.split('|');
const ts = parseInt(parts[1]);
if (!isNaN(ts) && (now - ts) < thirtyDays) {
pass = true;
}
});
return pass ? 1 : -1;
}
passDescription: "At least one browser appears recently updated."
failDescription: "No recently updated browsers found."
- title: "8. Monitoring & Logging"
description:
- "Ensures system event logging is active."
assertions:
- code: LOGGING_ACTIVE
title: "System has active logging service"
description: "Verify that system logging service is active."
cmds:
- exec:
shell: "powershell"
script: "(Get-Service EventLog).Status"
stdOutRule:
regex: "Running"
passDescription: "System logging is active."
failDescription: "System logging service is not running."