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

344 lines
15 KiB
YAML

# 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"