add playbooks
parent
70657505da
commit
bee8763c67
|
|
@ -0,0 +1,344 @@
|
||||||
|
# yaml-language-server: $schema=https://github.com/benedictjohannes/crobe/releases/latest/download/playbook.schema.json
|
||||||
|
title: "Lixus Desktop Compliance Report (Arch Linux)"
|
||||||
|
|
||||||
|
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:
|
||||||
|
# On Debian/Ubuntu, check last modification of pkgcache.
|
||||||
|
# On others, checking /var/log/dnf.log or /var/log/yum.log might work.
|
||||||
|
# We use a JS function to try multiple methods.
|
||||||
|
func: |
|
||||||
|
({ os }) => {
|
||||||
|
return `
|
||||||
|
if [ -f /var/cache/apt/pkgcache.bin ]; then
|
||||||
|
stat -c %Y /var/cache/apt/pkgcache.bin
|
||||||
|
elif [ -f /var/log/dnf.log ]; then
|
||||||
|
stat -c %Y /var/log/dnf.log
|
||||||
|
elif [ -f /var/log/pacman.log ]; then
|
||||||
|
stat -c %Y /var/log/pacman.log
|
||||||
|
else
|
||||||
|
echo "Unknown"
|
||||||
|
fi
|
||||||
|
`
|
||||||
|
}
|
||||||
|
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:
|
||||||
|
script: "cat /etc/os-release"
|
||||||
|
stdOutRule:
|
||||||
|
# Check ID or ID_LIKE for arch linux
|
||||||
|
regex: "(ID|ID_LIKE)=.*(arch)"
|
||||||
|
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: |
|
||||||
|
BRAND=$(cat /sys/class/dmi/id/sys_vendor 2>/dev/null || echo "Unknown")
|
||||||
|
MODEL=$(cat /sys/class/dmi/id/product_name 2>/dev/null || echo "Unknown")
|
||||||
|
# Serial often requires sudo
|
||||||
|
SERIAL=$(sudo cat /sys/class/dmi/id/product_serial 2>/dev/null || echo "Unknown")
|
||||||
|
echo "$BRAND|||$MODEL|||$SERIAL"
|
||||||
|
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:
|
||||||
|
# Checks GDM, LightDM, and SDDM for auto-login configuration
|
||||||
|
script: |
|
||||||
|
grep -i "AutomaticLoginEnable=true" /etc/gdm3/custom.conf /etc/gdm/custom.conf 2>/dev/null | grep -v "^#"
|
||||||
|
grep -i "autologin-user=" /etc/lightdm/lightdm.conf 2>/dev/null | grep -v "^#"
|
||||||
|
awk '/\[Autologin\]/{flag=1; next} /^\[/{flag=0} flag && /^User=/{print $0}' /etc/sddm.conf /etc/sddm.conf.d/*.conf 2>/dev/null | grep -v "^#"
|
||||||
|
exitCodeRules:
|
||||||
|
- min: 1 # No match found (good)
|
||||||
|
max: 1
|
||||||
|
result: 1
|
||||||
|
- min: 0 # Match found (bad)
|
||||||
|
max: 0
|
||||||
|
result: -1
|
||||||
|
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:
|
||||||
|
# Check GNOME and KDE for screen lock timeout
|
||||||
|
script: |
|
||||||
|
if command -v gsettings >/dev/null 2>&1; then
|
||||||
|
VAL=$(gsettings get org.gnome.desktop.session idle-delay 2>/dev/null | awk '{print $NF}')
|
||||||
|
if [ -n "$VAL" ] && [ "$VAL" != "0" ] && [ "$VAL" != "uint32" ]; then
|
||||||
|
echo "$VAL"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
CUR_USER=$(who | awk '{print $1}' | head -n 1)
|
||||||
|
if [ -n "$CUR_USER" ]; then
|
||||||
|
KDE_VAL=$(sudo -u "$CUR_USER" kreadconfig6 --group Daemon --key Timeout 2>/dev/null)
|
||||||
|
[ -z "$KDE_VAL" ] && KDE_VAL=$(sudo -u "$CUR_USER" kreadconfig5 --group Daemon --key Timeout 2>/dev/null)
|
||||||
|
if [ -n "$KDE_VAL" ] && [ "$KDE_VAL" != "0" ]; then
|
||||||
|
echo $((KDE_VAL * 60))
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
KDE_CFG_VAL=$(find /home -maxdepth 3 -name kscreenlockerrc -exec grep "^Timeout=" {} + 2>/dev/null | head -n 1 | cut -d'=' -f2)
|
||||||
|
if [ -n "$KDE_CFG_VAL" ] && [ "$KDE_CFG_VAL" != "0" ]; then
|
||||||
|
echo $((KDE_CFG_VAL * 60))
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "0"
|
||||||
|
gather:
|
||||||
|
- key: "SCREEN_LOCK_TIMEOUT_SECONDS"
|
||||||
|
func: |
|
||||||
|
(stdout) => {
|
||||||
|
const val = stdout.replace("uint32 ", "").trim();
|
||||||
|
return isNaN(parseInt(val)) ? "Unknown" : val;
|
||||||
|
}
|
||||||
|
stdOutRule:
|
||||||
|
func: |
|
||||||
|
(stdout) => {
|
||||||
|
const val = stdout.replace("uint32 ", "").trim();
|
||||||
|
const timeout = parseInt(val);
|
||||||
|
// 1800s = 30m. Note: 0 usually means 'never' if not careful, but we treat it as passing if strictly < 1800.
|
||||||
|
// However, '0' in GNOME often means Disabled.
|
||||||
|
if (timeout === 0) return -1;
|
||||||
|
return timeout <= 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:
|
||||||
|
script: "id -u"
|
||||||
|
gather:
|
||||||
|
- key: "CURRENT_USER_ID"
|
||||||
|
func: "(stdout) => stdout.trim()"
|
||||||
|
stdOutRule:
|
||||||
|
# Current user should not be root (0)
|
||||||
|
regex: "^[1-9][0-9]*$"
|
||||||
|
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:
|
||||||
|
script: "lsblk -p -l -n -o NAME,FSTYPE | grep -i 'crypto_LUKS' | awk '{print $1}'"
|
||||||
|
gather:
|
||||||
|
- key: "ENCRYPTED_DEVICES"
|
||||||
|
func: |
|
||||||
|
(stdout) => {
|
||||||
|
return stdout.trim().split('\n').map(s => s.trim()).filter(Boolean).join(', ');
|
||||||
|
}
|
||||||
|
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."
|
||||||
|
preCmds:
|
||||||
|
- script: sudo systemctl start clamav-daemon
|
||||||
|
excludeFromReport: true
|
||||||
|
cmds:
|
||||||
|
- exec:
|
||||||
|
script: "systemctl is-active clamav-daemon"
|
||||||
|
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."
|
||||||
|
preCmds:
|
||||||
|
- script: sudo freshclam
|
||||||
|
excludeFromReport: true
|
||||||
|
cmds:
|
||||||
|
- exec:
|
||||||
|
# Check freshclam log or /var/lib/clamav database age
|
||||||
|
script: "stat -c %Y /var/lib/clamav/daily.cld 2>/dev/null || stat -c %Y /var/lib/clamav/daily.cvd 2>/dev/null || stat -c %Y /var/lib/clamav/main.cvd 2>/dev/null"
|
||||||
|
gather:
|
||||||
|
- key: "ANTIVIRUS_LAST_UPDATE"
|
||||||
|
includeStdErr: false
|
||||||
|
func: |
|
||||||
|
(stdout) => {
|
||||||
|
return new Date(Number(stdout.trim()) * 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:
|
||||||
|
# systemctl is-active doesn't require root and can check multiple services
|
||||||
|
script: |
|
||||||
|
if systemctl is-active --quiet ufw; then
|
||||||
|
echo "active (ufw)"
|
||||||
|
elif systemctl is-active --quiet firewalld; then
|
||||||
|
echo "active (firewalld)"
|
||||||
|
else
|
||||||
|
echo "inactive"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
gather:
|
||||||
|
- key: "FIREWALL_STATUS"
|
||||||
|
func: "(stdout) => stdout.trim() || 'Inactive/Unknown'"
|
||||||
|
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:
|
||||||
|
script: "sbctl status"
|
||||||
|
stdOutRule:
|
||||||
|
regex: "Secure Boot:.*Enabled"
|
||||||
|
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:
|
||||||
|
# Check modification time of browser binary as a proxy for update
|
||||||
|
script: |
|
||||||
|
for b in /usr/bin/firefox /usr/bin/google-chrome /usr/bin/chromium /usr/bin/brave-browser; do
|
||||||
|
if [ -f "$b" ]; then
|
||||||
|
echo "$b:$(stat -c %Y "$b")"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
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);
|
||||||
|
let latest = 0;
|
||||||
|
lines.forEach(line => {
|
||||||
|
const stamp = parseInt(line.split(':')[1]);
|
||||||
|
if (stamp > latest) latest = stamp;
|
||||||
|
});
|
||||||
|
if (latest === 0) return -1;
|
||||||
|
const thirtyDays = 30 * 24 * 60 * 60;
|
||||||
|
const now = Date.now() / 1000;
|
||||||
|
return (now - latest) < thirtyDays ? 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:
|
||||||
|
script: "systemctl is-active systemd-journald"
|
||||||
|
passDescription: "System logging is active."
|
||||||
|
failDescription: "System logging service is not running."
|
||||||
|
|
||||||
|
reportDestination: folder
|
||||||
|
#reportDestinationFolder: MyCustomFolder01
|
||||||
|
reportDestinationHttps:
|
||||||
|
url: https://gway.lixus.id/campaignhub/api/log/complianceProbe04
|
||||||
|
signatureSecret: DontWorryBeHappy
|
||||||
|
format: json
|
||||||
|
additionalHeaders:
|
||||||
|
X-Custom-Header: "Custom Value"
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
# yaml-language-server: $schema=https://github.com/benedictjohannes/crobe/releases/latest/download/playbook.schema.json
|
||||||
|
title: "Lixus Desktop Compliance Report (MacOS)"
|
||||||
|
|
||||||
|
reportFrontmatter:
|
||||||
|
author: "SRE Team"
|
||||||
|
|
||||||
|
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:
|
||||||
|
script: |
|
||||||
|
# Get the last update time from SystemVersion.plist modification date
|
||||||
|
last_update=$(stat -f %m /System/Library/CoreServices/SystemVersion.plist 2>/dev/null)
|
||||||
|
if [ -n "$last_update" ]; then
|
||||||
|
echo "$last_update"
|
||||||
|
else
|
||||||
|
echo "Unknown"
|
||||||
|
fi
|
||||||
|
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:
|
||||||
|
script: "sw_vers -productName"
|
||||||
|
stdOutRule:
|
||||||
|
regex: "macOS"
|
||||||
|
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:
|
||||||
|
func: |
|
||||||
|
({ os }) => `
|
||||||
|
MODEL=$(sysctl -n hw.model)
|
||||||
|
SERIAL=$(system_profiler SPHardwareDataType | awk '/Serial Number/ {print $NF}')
|
||||||
|
echo "${ os === 'mac' ? 'Apple' : 'Unknown' }|||$MODEL|||$SERIAL"
|
||||||
|
`
|
||||||
|
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:
|
||||||
|
script: "if [ -f /etc/kcpassword ]; then echo 'Enabled'; else echo 'None'; fi"
|
||||||
|
stdOutRule:
|
||||||
|
regex: "None"
|
||||||
|
passDescription: "Auto login is disabled."
|
||||||
|
failDescription: "Auto login appears to be enabled in system preferences."
|
||||||
|
|
||||||
|
- code: SCREEN_LOCK_TIMEOUT
|
||||||
|
title: "Screen Lock Timeout"
|
||||||
|
description: "Verify screen lock timeout is under 30 minutes (1800 seconds)."
|
||||||
|
cmds:
|
||||||
|
- exec:
|
||||||
|
script: |
|
||||||
|
idle=$(defaults -currentHost read com.apple.screensaver idleTime 2>/dev/null || echo 0)
|
||||||
|
display_sleep=$(pmset -g custom | awk '/displaysleep/ {print $2}' | sort -n | tail -n 1)
|
||||||
|
display_sleep_sec=$((display_sleep * 60))
|
||||||
|
if [ "$idle" -eq 0 ] && [ "$display_sleep_sec" -eq 0 ]; then
|
||||||
|
echo 0
|
||||||
|
elif [ "$idle" -eq 0 ]; then
|
||||||
|
echo "$display_sleep_sec"
|
||||||
|
elif [ "$display_sleep_sec" -eq 0 ]; then
|
||||||
|
echo "$idle"
|
||||||
|
elif [ "$idle" -lt "$display_sleep_sec" ]; then
|
||||||
|
echo "$idle"
|
||||||
|
else
|
||||||
|
echo "$display_sleep_sec"
|
||||||
|
fi
|
||||||
|
gather:
|
||||||
|
- key: "SCREEN_LOCK_TIMEOUT_SECONDS"
|
||||||
|
func: |
|
||||||
|
(stdout) => {
|
||||||
|
const val = stdout.trim();
|
||||||
|
return isNaN(parseInt(val)) ? "Unknown" : val;
|
||||||
|
}
|
||||||
|
stdOutRule:
|
||||||
|
func: |
|
||||||
|
(stdout) => {
|
||||||
|
const timeout = parseInt(stdout.trim());
|
||||||
|
if (timeout === 0) return -1;
|
||||||
|
return timeout <= 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:
|
||||||
|
script: "id -u"
|
||||||
|
gather:
|
||||||
|
- key: "CURRENT_USER_ID"
|
||||||
|
func: "(stdout) => stdout.trim()"
|
||||||
|
stdOutRule:
|
||||||
|
# Current user should not be root (0)
|
||||||
|
regex: "^[1-9][0-9]*$"
|
||||||
|
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:
|
||||||
|
script: "fdesetup status"
|
||||||
|
gather:
|
||||||
|
- key: "ENCRYPTED_DEVICES"
|
||||||
|
func: "(stdout) => stdout.includes('FileVault is On') ? 'Main Disk (FileVault)' : 'None'"
|
||||||
|
stdOutRule:
|
||||||
|
regex: "FileVault is On"
|
||||||
|
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:
|
||||||
|
script: |
|
||||||
|
GK=$(spctl --status | grep -o "enabled")
|
||||||
|
SIP=$(csrutil status | grep -o "enabled")
|
||||||
|
echo "Gatekeeper: $GK, SIP: $SIP"
|
||||||
|
stdOutRule:
|
||||||
|
regex: "Gatekeeper: enabled, SIP: enabled"
|
||||||
|
passDescription: "System protection services are active"
|
||||||
|
failDescription: "One or more system protection services are disabled."
|
||||||
|
|
||||||
|
- code: ANTIVIRUS_UPDATED
|
||||||
|
title: "Antivirus Definitions Up-to-Date"
|
||||||
|
description: "Check if antivirus definitions were updated in the last 30 days."
|
||||||
|
cmds:
|
||||||
|
- exec:
|
||||||
|
script: "stat -f %m /Library/Apple/System/Library/CoreServices/XProtect.bundle/Contents/Resources/XProtect.yara 2>/dev/null || 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:
|
||||||
|
script: "/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate"
|
||||||
|
gather:
|
||||||
|
- key: "FIREWALL_STATUS"
|
||||||
|
func: "(stdout) => stdout.trim()"
|
||||||
|
stdOutRule:
|
||||||
|
regex: "Firewall is enabled"
|
||||||
|
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:
|
||||||
|
script: "system_profiler SPiBridgeDataType SPSoftwareDataType 2>/dev/null | grep 'Secure Boot' | awk -F':' '{print $2}' | xargs"
|
||||||
|
stdOutRule:
|
||||||
|
regex: "(Full Security|Medium Security)"
|
||||||
|
passDescription: "Secure Boot is enabled."
|
||||||
|
failDescription: "Secure Boot is disabled or set to No Security."
|
||||||
|
|
||||||
|
- 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:
|
||||||
|
script: |
|
||||||
|
for b in /Applications/Safari.app /Applications/Google\ Chrome.app /Applications/Firefox.app; do
|
||||||
|
if [ -d "$b" ]; then
|
||||||
|
echo "$b:$(stat -f %m "$b")"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
gather:
|
||||||
|
- key: "SAFARI_LAST_UPDATE"
|
||||||
|
func: |
|
||||||
|
(stdout) => {
|
||||||
|
const line = stdout.trim().split('\n').find(l => l.includes('Safari'));
|
||||||
|
if (!line) return "Not Installed";
|
||||||
|
return new Date(parseInt(line.split(':')[1]) * 1000).toISOString();
|
||||||
|
}
|
||||||
|
- 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'));
|
||||||
|
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);
|
||||||
|
let latest = 0;
|
||||||
|
lines.forEach(line => {
|
||||||
|
const stamp = parseInt(line.split(':')[1]);
|
||||||
|
if (stamp > latest) latest = stamp;
|
||||||
|
});
|
||||||
|
if (latest === 0) return -1;
|
||||||
|
const thirtyDays = 30 * 24 * 60 * 60;
|
||||||
|
const now = Date.now() / 1000;
|
||||||
|
return (now - latest) < thirtyDays ? 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:
|
||||||
|
script: "log show --last 1m >/dev/null 2>&1 && echo 'active' || echo 'inactive'"
|
||||||
|
stdOutRule:
|
||||||
|
regex: "active"
|
||||||
|
passDescription: "System logging is active."
|
||||||
|
failDescription: "System logging service is not running."
|
||||||
|
|
@ -0,0 +1,449 @@
|
||||||
|
# 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."
|
||||||
Loading…
Reference in New Issue