Lets share-less (Intune/local-account) PCs pull installers the manifest references over HTTPS instead of SMB - the general capability the whole fleet migrates toward. New ManifestBlob registry (migration 0002) with bytes on disk at instance/geenforce/payloads/<sha256> (deduped by content); service.store_blob + blob_path; client-facing GET /api/geenforce/payload/<sha256> (geenforce.fetch token, ETag=hash, serves the blob store or an inline DB payload by hash). The serializer now emits PayloadSource/PayloadSha256/PayloadRef for http/inline entries only (smb entries round-trip unchanged - parity green). CLI 'flask geenforce add-payload <file>' registers a blob and prints its sha256. This is the shopdb half (B1); the PS client/engine fetch is B2.
143 lines
5.5 KiB
Python
143 lines
5.5 KiB
Python
"""Rebuild manifest JSON from DB rows, and canonicalize entries for parity.
|
|
|
|
`scope_to_manifest` renders a ManifestScope back into the exact JSON structure
|
|
GE-Enforce consumes (Version / _comment / Applications, plus Site for
|
|
preinstall). `canonical_entry` reduces an Applications[] entry (from either the
|
|
on-share file or a rebuilt scope) to only the fields the engine reads, dropping
|
|
`_comment` and key order, so the parity harness can compare behavior not bytes.
|
|
"""
|
|
|
|
import json
|
|
|
|
# Scalar entry fields: model attribute -> manifest key. Emitted only when set,
|
|
# so a rebuilt entry has the same sparse key presence as the original.
|
|
_SCALAR_FIELDS = [
|
|
('name', 'Name'),
|
|
('entrytype', 'Type'),
|
|
('installer', 'Installer'),
|
|
('installargs', 'InstallArgs'),
|
|
('scriptpath', 'Script'),
|
|
('scriptargs', 'Args'),
|
|
('sourcepath', 'Source'),
|
|
('destination', 'Destination'),
|
|
('regpath', 'RegPath'),
|
|
('regname', 'RegName'),
|
|
('regtype', 'RegType'),
|
|
('detectionmethod', 'DetectionMethod'),
|
|
('detectionpath', 'DetectionPath'),
|
|
('detectionname', 'DetectionName'),
|
|
('detectionvalue', 'DetectionValue'),
|
|
('detectionpattern', 'DetectionPattern'),
|
|
('cmmversion', '_CmmVersion'),
|
|
('logfile', 'LogFile'),
|
|
('waittimeoutsec', 'WaitTimeoutSec'),
|
|
('applymode', 'ApplyMode'),
|
|
('updatewindow', 'UpdateWindow'),
|
|
]
|
|
|
|
# Boolean flags emitted only when True (matches how preinstall.json carries them).
|
|
_BOOL_FLAGS = [
|
|
('preenrollment', 'PreEnrollment'),
|
|
('killafterdetection', 'KillAfterDetection'),
|
|
('pctypesstrict', 'PCTypesStrict'),
|
|
]
|
|
|
|
# The complete set of engine-relevant keys the parity check compares.
|
|
ENGINE_KEYS = (
|
|
[mk for _, mk in _SCALAR_FIELDS]
|
|
+ [mk for _, mk in _BOOL_FLAGS]
|
|
+ ['PCTypes', 'TargetHostnames', 'TargetMachineNumbers', 'RegValue', 'InUseCheck']
|
|
)
|
|
|
|
|
|
def entry_to_dict(entry):
|
|
"""Render a ManifestEntry model back into a manifest Applications[] entry."""
|
|
result = {}
|
|
if entry.comment:
|
|
result['_comment'] = entry.comment
|
|
for attr, key in _SCALAR_FIELDS:
|
|
value = getattr(entry, attr)
|
|
if value is not None and value != '':
|
|
result[key] = value
|
|
# RegValue: stored as a raw JSON literal, reconstitute its real type.
|
|
if entry.regvalue is not None:
|
|
result['RegValue'] = json.loads(entry.regvalue)
|
|
# Multi-value filters (only when present).
|
|
if entry.pctypes:
|
|
result['PCTypes'] = [p.pctypevalue for p in entry.pctypes]
|
|
if entry.hostnames:
|
|
result['TargetHostnames'] = [h.hostnamepattern for h in entry.hostnames]
|
|
if entry.machinenumbers:
|
|
result['TargetMachineNumbers'] = [m.machinenumber
|
|
for m in entry.machinenumbers]
|
|
for attr, key in _BOOL_FLAGS:
|
|
if getattr(entry, attr):
|
|
result[key] = True
|
|
# Payload transport for share-less (http/inline) delivery. Omitted for the
|
|
# default 'smb' source so existing share manifests round-trip unchanged; the
|
|
# client fetches GET /api/geenforce/payload/<PayloadSha256> for http/inline.
|
|
if entry.payloadsource and entry.payloadsource != 'smb':
|
|
result['PayloadSource'] = entry.payloadsource
|
|
if entry.payloadsha256:
|
|
result['PayloadSha256'] = entry.payloadsha256
|
|
if entry.payloadref:
|
|
result['PayloadRef'] = entry.payloadref
|
|
# InUseCheck (nested object + Processes[]).
|
|
if entry.inusecheck:
|
|
procs = []
|
|
for proc in entry.inusecheck.processes:
|
|
pd = {'Name': proc.processname}
|
|
if proc.exepath is not None:
|
|
pd['ExePath'] = proc.exepath
|
|
if proc.gracefulclosetimeoutsec is not None:
|
|
pd['GracefulCloseTimeoutSec'] = proc.gracefulclosetimeoutsec
|
|
procs.append(pd)
|
|
result['InUseCheck'] = {
|
|
'Behavior': entry.inusecheck.behavior,
|
|
'Processes': procs,
|
|
}
|
|
return result
|
|
|
|
|
|
def scope_to_manifest(scope):
|
|
"""Render a ManifestScope back into a full manifest.json structure."""
|
|
manifest = {'Version': scope.manifestversion}
|
|
if scope.topcomment:
|
|
manifest['_comment'] = scope.topcomment
|
|
if scope.site:
|
|
manifest['Site'] = scope.site
|
|
manifest['Applications'] = [entry_to_dict(e) for e in scope.entries]
|
|
return manifest
|
|
|
|
|
|
def scope_to_json(scope, indent=2):
|
|
"""The serialized JSON text a client/export would receive."""
|
|
return json.dumps(scope_to_manifest(scope), indent=indent)
|
|
|
|
|
|
def canonical_entry(entry_dict):
|
|
"""Reduce an Applications[] entry to engine-relevant fields for parity.
|
|
|
|
Drops `_comment` and key order. Normalizes InUseCheck and the process list
|
|
so two entries that behave identically compare equal regardless of source.
|
|
"""
|
|
canon = {}
|
|
for key in ENGINE_KEYS:
|
|
if key == 'InUseCheck':
|
|
continue
|
|
if key in entry_dict and entry_dict[key] is not None and entry_dict[key] != '':
|
|
canon[key] = entry_dict[key]
|
|
inuse = entry_dict.get('InUseCheck')
|
|
if inuse:
|
|
procs = []
|
|
for proc in inuse.get('Processes', []):
|
|
pd = {'Name': proc.get('Name')}
|
|
if proc.get('ExePath') is not None:
|
|
pd['ExePath'] = proc.get('ExePath')
|
|
if proc.get('GracefulCloseTimeoutSec') is not None:
|
|
pd['GracefulCloseTimeoutSec'] = proc.get('GracefulCloseTimeoutSec')
|
|
procs.append(pd)
|
|
canon['InUseCheck'] = {'Behavior': inuse.get('Behavior'),
|
|
'Processes': procs}
|
|
return canon
|