Add admin page overhaul and compliance schema drift check specs, compliance upload improvements, drift checker helper

This commit is contained in:
root
2026-04-20 20:12:12 +00:00
parent 6082721452
commit 043c85cc69
20 changed files with 56814 additions and 59 deletions

View File

@@ -0,0 +1,44 @@
{
"metric_categories": {
"1.1.1": "Logging & Monitoring",
"1.1.3": "Logging & Monitoring",
"1.4.1": "Logging & Monitoring",
"2.3.4i": "Vulnerability Management",
"2.3.6i": "Vulnerability Management",
"2.3.8i": "Vulnerability Management",
"5.2.4": "Access & MFA",
"5.2.5": "Access & MFA",
"5.2.6": "Access & MFA",
"5.2.7": "Access & MFA",
"5.2.8": "Access & MFA",
"5.3.4": "Endpoint Protection",
"5.5.4i": "Vulnerability Management",
"5.5.5": "Decommissioned Assets",
"5.8.1": "Application Security",
"7.1.1": "Logging & Monitoring",
"7.1.4": "Logging & Monitoring",
"7.6.13": "Disaster Recovery",
"7.6.16": "Disaster Recovery",
"Missing_AppID": "Asset Data Quality",
"Missing_DF": "Asset Data Quality",
"Missing_OS": "Asset Data Quality",
"5.5.2": "Other"
},
"core_cols": [
"Preferred - Hostname",
"GRANITE - IPv4_Address",
"GRANITE - Type",
"Team",
"Compliant",
"Source_Network",
"Vertical",
"GRANITE - Equip_Inst_ID",
"GRANITE - RESPONSIBLE_TEAM"
],
"skip_sheets": [
"Summary",
"CMDB_9box",
"Vulns",
"Aging Dashboard"
]
}

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""
Extract the structural schema of a compliance xlsx file as JSON.
Usage: python3 extract_xlsx_schema.py <path_to_xlsx>
Output:
{
"sheets": [
{
"name": "Summary",
"columns": ["Metric", "Non-Compliant", "..."],
"metric_values": ["2.3.4i", "5.2.4", "..."]
},
{
"name": "2.3.4i",
"columns": ["Preferred - Hostname", "GRANITE - IPv4_Address", "..."]
}
]
}
- Uses openpyxl in read-only mode.
- Extracts sheet names, first-row column headers per sheet, and unique metric
values from the Summary sheet (header at row 4, data from row 5 onward).
- On error, returns { "error": "..." } on stdout and exits with non-zero code.
Dependencies: openpyxl (already in requirements.txt)
"""
import sys
import json
from openpyxl import load_workbook
def main():
if len(sys.argv) < 2:
print(json.dumps({"error": "No file path provided"}))
sys.exit(1)
filepath = sys.argv[1]
try:
wb = load_workbook(filepath, read_only=True, data_only=True)
except Exception as e:
print(json.dumps({"error": f"Cannot open file: {str(e)}"}))
sys.exit(1)
if not wb.sheetnames:
print(json.dumps({"error": "Workbook contains no sheets"}))
wb.close()
sys.exit(1)
sheets = []
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
# Extract first-row column headers
rows = list(ws.iter_rows(max_row=1, values_only=True))
columns = [str(c).strip() for c in rows[0] if c is not None] if rows else []
entry = {
"name": sheet_name,
"columns": columns,
}
# Extract metric values from the Summary sheet
# Summary has header at row 4, data from row 5 onward
if sheet_name == "Summary":
metric_values = []
header_rows = list(ws.iter_rows(min_row=4, max_row=4, values_only=True))
if header_rows:
summary_cols = [str(c).strip() if c else "" for c in header_rows[0]]
metric_idx = None
for i, col in enumerate(summary_cols):
if col == "Metric":
metric_idx = i
break
if metric_idx is not None:
for row in ws.iter_rows(min_row=5, values_only=True):
if row[metric_idx] is not None:
val = str(row[metric_idx]).strip()
if val and val != "Metric":
metric_values.append(val)
entry["metric_values"] = sorted(set(metric_values))
sheets.append(entry)
wb.close()
print(json.dumps({"sheets": sheets}))
if __name__ == "__main__":
main()

View File

@@ -12,45 +12,35 @@ Output:
}
"""
import sys
import os
import json
import re
import pandas as pd
from pathlib import Path
METRIC_CATEGORIES = {
'2.3.4i': 'Vulnerability Management',
'2.3.6i': 'Vulnerability Management',
'2.3.8i': 'Vulnerability Management',
'5.2.4': 'Access & MFA',
'5.2.5': 'Access & MFA',
'5.2.6': 'Access & MFA',
'5.3.4': 'Endpoint Protection',
'5.5.2': 'End-of-Life OS',
'5.5.4i': 'Vulnerability Management',
'5.5.5': 'Decommissioned Assets',
'5.8.1': 'Application Security',
'7.1.1': 'Logging & Monitoring',
'7.6.13': 'Disaster Recovery',
'7.6.16': 'Disaster Recovery',
'1.1.1': 'Logging & Monitoring',
'1.1.3': 'Logging & Monitoring',
'1.4.1': 'Logging & Monitoring',
'5.2.7': 'Access & MFA',
'5.2.8': 'Access & MFA',
'7.1.4': 'Logging & Monitoring',
'Missing_AppID': 'Asset Data Quality',
'Missing_DF': 'Asset Data Quality',
'Missing_OS': 'Asset Data Quality',
}
# Columns that go into the main item fields — everything else becomes extra_json
CORE_COLS = {
'Preferred - Hostname', 'GRANITE - IPv4_Address', 'GRANITE - Type',
'Team', 'Compliant', 'Source_Network', 'Vertical',
'GRANITE - Equip_Inst_ID', 'GRANITE - RESPONSIBLE_TEAM',
}
def load_config():
"""Load parser configuration from compliance_config.json."""
script_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(script_dir, 'compliance_config.json')
SKIP_SHEETS = {'Summary', 'CMDB_9box', 'Vulns', 'Aging Dashboard'}
try:
with open(config_path, 'r') as f:
config = json.load(f)
except FileNotFoundError:
print(f"Error: Configuration file not found: {config_path}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in configuration file {config_path}: {e}", file=sys.stderr)
sys.exit(1)
return config
_config = load_config()
METRIC_CATEGORIES = _config['metric_categories']
CORE_COLS = set(_config['core_cols'])
SKIP_SHEETS = set(_config['skip_sheets'])
def safe_str(val):