Files
T

177 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""
BitLocker Compliance Checker
Checks BitLocker encryption status across endpoints and generates
compliance reports for audit purposes.
"""
import json
import subprocess
import sys
import os
import csv
from datetime import datetime
def check_bitlocker_status() -> dict:
"""Check BitLocker status on local Windows endpoint."""
ps_cmd = """
$volumes = Get-BitLockerVolume
$results = @()
foreach ($vol in $volumes) {
$protectors = @()
foreach ($kp in $vol.KeyProtector) {
$protectors += @{
Type = $kp.KeyProtectorType.ToString()
Id = $kp.KeyProtectorId
}
}
$results += @{
MountPoint = $vol.MountPoint
VolumeStatus = $vol.VolumeStatus.ToString()
ProtectionStatus = $vol.ProtectionStatus.ToString()
EncryptionMethod = $vol.EncryptionMethod.ToString()
EncryptionPercentage = $vol.EncryptionPercentage
VolumeType = $vol.VolumeType.ToString()
KeyProtectors = $protectors
AutoUnlockEnabled = $vol.AutoUnlockEnabled
AutoUnlockKeyStored = $vol.AutoUnlockKeyStored
}
}
$tpm = Get-Tpm
@{
Hostname = $env:COMPUTERNAME
Volumes = $results
TPM = @{
Present = $tpm.TpmPresent
Ready = $tpm.TpmReady
Enabled = $tpm.TpmEnabled
Activated = $tpm.TpmActivated
}
} | ConvertTo-Json -Depth 4
"""
try:
result = subprocess.run(
["powershell", "-NoProfile", "-Command", ps_cmd],
capture_output=True, text=True, timeout=30,
)
if result.returncode == 0 and result.stdout.strip():
return json.loads(result.stdout)
return {"error": result.stderr or "Failed to check BitLocker status"}
except FileNotFoundError:
return {"error": "PowerShell not available (requires Windows)"}
except subprocess.TimeoutExpired:
return {"error": "Command timed out"}
except json.JSONDecodeError as e:
return {"error": f"Parse error: {e}"}
def assess_compliance(status: dict) -> dict:
"""Assess BitLocker compliance against security baseline."""
findings = {
"hostname": status.get("Hostname", "unknown"),
"overall_compliant": True,
"tpm_status": "compliant",
"volume_findings": [],
}
tpm = status.get("TPM", {})
if not tpm.get("Present") or not tpm.get("Ready"):
findings["tpm_status"] = "non-compliant"
findings["overall_compliant"] = False
for vol in status.get("Volumes", []):
vol_finding = {
"mount_point": vol["MountPoint"],
"compliant": True,
"issues": [],
}
if vol["VolumeStatus"] != "FullyEncrypted":
vol_finding["compliant"] = False
vol_finding["issues"].append(f"Not fully encrypted: {vol['VolumeStatus']}")
if vol["ProtectionStatus"] != "On":
vol_finding["compliant"] = False
vol_finding["issues"].append(f"Protection not enabled: {vol['ProtectionStatus']}")
if vol["EncryptionMethod"] not in ("XtsAes256", "XtsAes128"):
vol_finding["issues"].append(f"Weak encryption method: {vol['EncryptionMethod']}")
protector_types = [kp["Type"] for kp in vol.get("KeyProtectors", [])]
has_recovery = "RecoveryPassword" in protector_types or "NumericalPassword" in protector_types
if not has_recovery:
vol_finding["compliant"] = False
vol_finding["issues"].append("No recovery password protector configured")
has_tpm = any(t in protector_types for t in ["Tpm", "TpmPin", "TpmPinStartupKey"])
if vol["VolumeType"] == "OperatingSystem" and not has_tpm:
vol_finding["compliant"] = False
vol_finding["issues"].append("OS volume missing TPM protector")
if not vol_finding["compliant"]:
findings["overall_compliant"] = False
findings["volume_findings"].append(vol_finding)
return findings
def generate_report(status: dict, compliance: dict, output_path: str) -> None:
"""Generate BitLocker compliance report."""
report = {
"report_generated": datetime.utcnow().isoformat() + "Z",
"hostname": status.get("Hostname", "unknown"),
"overall_compliance": "COMPLIANT" if compliance["overall_compliant"] else "NON-COMPLIANT",
"tpm_status": compliance["tpm_status"],
"tpm_details": status.get("TPM", {}),
"volumes": [],
}
for vol, finding in zip(status.get("Volumes", []), compliance["volume_findings"]):
report["volumes"].append({
"mount_point": vol["MountPoint"],
"status": vol["VolumeStatus"],
"protection": vol["ProtectionStatus"],
"encryption_method": vol["EncryptionMethod"],
"encryption_percent": vol["EncryptionPercentage"],
"key_protectors": [kp["Type"] for kp in vol.get("KeyProtectors", [])],
"compliant": finding["compliant"],
"issues": finding["issues"],
})
with open(output_path, "w", encoding="utf-8") as f:
json.dump(report, f, indent=2)
if __name__ == "__main__":
output_dir = sys.argv[1] if len(sys.argv) > 1 else "."
print("Checking BitLocker status...")
status = check_bitlocker_status()
if "error" in status:
print(f"Error: {status['error']}")
print("This tool requires Windows with BitLocker support.")
sys.exit(1)
print("Assessing compliance...")
compliance = assess_compliance(status)
report_path = os.path.join(output_dir, "bitlocker_compliance.json")
generate_report(status, compliance, report_path)
print(f"Compliance report: {report_path}")
print(f"\n--- BitLocker Compliance ---")
print(f"Hostname: {compliance['hostname']}")
print(f"Overall: {'COMPLIANT' if compliance['overall_compliant'] else 'NON-COMPLIANT'}")
print(f"TPM: {compliance['tpm_status']}")
for vf in compliance["volume_findings"]:
status_icon = "PASS" if vf["compliant"] else "FAIL"
print(f"\n [{status_icon}] {vf['mount_point']}")
for issue in vf["issues"]:
print(f" - {issue}")