geenforce: HTTPS payload delivery (content-addressed blob store + endpoint)

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.
This commit is contained in:
cproudlock
2026-07-21 10:10:59 -04:00
parent 60e2947fc7
commit b00ef72581
8 changed files with 268 additions and 6 deletions

View File

@@ -0,0 +1,91 @@
"""GE-Enforce HTTPS payload delivery: content-addressed blob store + the
client-facing GET /api/geenforce/payload/<sha256> endpoint.
Lets share-less (Intune/local-account) PCs pull installers the manifest
references over HTTPS instead of SMB. Auth = a geenforce.fetch service token
(same as /manifest). The sha256 IS the integrity guarantee (client re-verifies).
"""
import hashlib
from plugins.geenforce import service
from plugins.geenforce.models import ManifestBlob
def _fetch_key(client, auth_headers):
resp = client.post('/api/apitokens',
json={'name': 'svc', 'scopes': ['geenforce.fetch']},
headers=auth_headers)
assert resp.status_code == 201, resp.get_json()
return {'X-API-Key': resp.get_json()['data']['secret']}
def _store(app, raw, filename='setup.exe'):
with app.app_context():
sha = service.store_blob(raw, filename, 'application/octet-stream')
service.db.session.commit()
return sha
def test_store_blob_dedups_and_registers(app, db):
raw = b'MZ fake installer bytes'
sha1 = _store(app, raw)
sha2 = _store(app, raw) # same content
assert sha1 == sha2 == hashlib.sha256(raw).hexdigest()
with app.app_context():
assert ManifestBlob.query.count() == 1
blob = service.db.session.get(ManifestBlob, sha1)
assert blob.sizebytes == len(raw)
def test_download_returns_exact_bytes(client, app, auth_headers):
raw = b'\x00\x01 big binary installer \xff\xfe'
sha = _store(app, raw, 'kiosk.exe')
headers = _fetch_key(client, auth_headers)
resp = client.get(f'/api/geenforce/payload/{sha}', headers=headers)
assert resp.status_code == 200
assert resp.data == raw
assert resp.headers['ETag'] == f'"{sha}"'
assert 'kiosk.exe' in resp.headers.get('Content-Disposition', '')
def test_download_requires_fetch_token(client, app, auth_headers):
sha = _store(app, b'data')
# no key
assert client.get(f'/api/geenforce/payload/{sha}').status_code == 401
def test_download_unknown_hash_404(client, auth_headers):
headers = _fetch_key(client, auth_headers)
sha = 'a' * 64
assert client.get(f'/api/geenforce/payload/{sha}', headers=headers).status_code == 404
def test_download_bad_hash_400(client, auth_headers):
headers = _fetch_key(client, auth_headers)
assert client.get('/api/geenforce/payload/not-a-hash', headers=headers).status_code == 400
def test_etag_304(client, app, auth_headers):
sha = _store(app, b'cacheable')
headers = _fetch_key(client, auth_headers)
resp = client.get(f'/api/geenforce/payload/{sha}',
headers={**headers, 'If-None-Match': f'"{sha}"'})
assert resp.status_code == 304
def test_serializer_emits_payload_transport_for_http_only(app):
"""http/inline entries emit PayloadSource + PayloadSha256; smb stays bare."""
from plugins.geenforce.serializer import entry_to_dict
from plugins.geenforce.models import ManifestEntry
smb = ManifestEntry(name='a', entrytype='EXE', payloadsource='smb')
assert 'PayloadSource' not in entry_to_dict(smb)
http = ManifestEntry(name='b', entrytype='EXE', payloadsource='http',
payloadsha256='f' * 64, payloadref='kiosk.exe')
out = entry_to_dict(http)
assert out['PayloadSource'] == 'http'
assert out['PayloadSha256'] == 'f' * 64
assert out['PayloadRef'] == 'kiosk.exe'