Add local Windows Server inventory collection and validation; include core package
This commit is contained in:
@@ -2,22 +2,9 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from jsonschema import Draft202012Validator, FormatChecker
|
||||
from samsara.models.inventory import validate_inventory, restrict_inventory_fields
|
||||
|
||||
MAX_IMPORT_BYTES = 10 * 1024 * 1024
|
||||
SCHEMA = Path(__file__).resolve().parents[2] / 'schemas' / 'inventory-v1.schema.json'
|
||||
# Inventory fields allowed through the report boundary. Command arguments, environment
|
||||
# variables and private key material are deliberately excluded.
|
||||
ALLOWED = {
|
||||
'roles': {'name', 'state'},
|
||||
'services': {'name', 'display_name', 'state', 'startup', 'account'},
|
||||
'network': {'state', 'local_address', 'local_port', 'remote_address', 'remote_port', 'pid', 'process', 'direction'},
|
||||
'tasks': {'name', 'path', 'enabled', 'account', 'triggers', 'state', 'associated_unit'},
|
||||
'software': {'name', 'publisher', 'version', 'installation_date'},
|
||||
'storage': {'type', 'name', 'path', 'capacity_gib', 'free_gib'},
|
||||
'certificates': {'subject', 'issuer', 'expires', 'thumbprint'},
|
||||
}
|
||||
SERVER_FIELDS = {'hostname', 'platform', 'operating_system', 'os_version', 'os_build', 'last_boot', 'ip_addresses', 'network_adapters', 'domain'}
|
||||
|
||||
|
||||
def load_inventory(path):
|
||||
@@ -32,16 +19,35 @@ def load_inventory(path):
|
||||
# A report wrapper is accepted, but its precomputed assessment is never trusted.
|
||||
if isinstance(data, dict) and 'inventory_result' in data:
|
||||
data = data['inventory_result']
|
||||
schema = json.loads(SCHEMA.read_text(encoding='utf-8'))
|
||||
errors = list(Draft202012Validator(schema, format_checker=FormatChecker()).iter_errors(data))
|
||||
if errors:
|
||||
raise ValueError('Inventory does not match the v1 schema; required fields, types or check statuses are invalid.')
|
||||
validate_inventory(data)
|
||||
# Keep diagnostic contents out of logs and exported reports. Diagnostics may
|
||||
# contain command lines or credentials from external collection tools.
|
||||
data['checks'] = {name: {'status': check['status'], 'message':
|
||||
'Imported check status. Consult collector diagnostics separately; details excluded to limit secret exposure.'}
|
||||
for name, check in data['checks'].items()}
|
||||
data['server'] = {key: value for key, value in data['server'].items() if key in SERVER_FIELDS}
|
||||
data['inventory'] = {section: [{key: value for key, value in row.items() if key in ALLOWED[section]}
|
||||
for row in rows] for section, rows in data['inventory'].items()}
|
||||
return data
|
||||
safe_errors = {
|
||||
'access_denied': 'Current account could not read this scope.',
|
||||
'command_unavailable': 'Required command was unavailable.',
|
||||
'timeout': 'Section time limit exceeded.',
|
||||
'overall_timeout': 'Overall collection time limit exceeded.',
|
||||
'output_limit': 'Collection output limit exceeded.',
|
||||
'powershell_failed': 'PowerShell could not execute the collector.',
|
||||
'invalid_output': 'Invalid section output.',
|
||||
'collection_failed': 'A local query failed.'
|
||||
}
|
||||
checks = {}
|
||||
for name, check in data['checks'].items():
|
||||
clean = {'status': check['status'], 'message':
|
||||
'Imported check status. Source assertions are unverified; raw diagnostics excluded.'}
|
||||
for field in ('started_at', 'completed_at'):
|
||||
if field in check:
|
||||
clean[field] = check[field]
|
||||
if 'errors' in check:
|
||||
clean['errors'] = []
|
||||
for error in check['errors']:
|
||||
code = error['code'] if error['code'] in safe_errors else 'collection_failed'
|
||||
clean['errors'].append({'code': code, 'scope': 'Imported check', 'message': safe_errors[code]})
|
||||
if 'permission_limitations' in check:
|
||||
clean['permission_limitations'] = ['Collector reported limited read access; details excluded.'] if check['permission_limitations'] else []
|
||||
if 'limitations' in check:
|
||||
clean['limitations'] = ['Collector reported scope limitations; consult the inventory owner.'] if check['limitations'] else []
|
||||
checks[name] = clean
|
||||
data['checks'] = checks
|
||||
return restrict_inventory_fields(data)
|
||||
|
||||
Reference in New Issue
Block a user