Unpick one site's conventions from the shipped bundle, and bind the launch-path
check to the file it actually runs Two clusters the five-agent review found. Both are about a product that another plant has to be able to install. ADR-015: the shipped monitor bundle was the originating plant's fleet convention frozen into product seed. The business policy was seeded as "RPCA Shopfloor App Monitors". RPCA is that plant's facility designator, the row is source='shipped', and the API refused to rename it - so every adopting site would carry another plant's label at the top of its policy list forever. 0007 now seeds the neutral name, and 0010 renames it on installs that already stamped 0007, guarded so it cannot collide with a policy a site made itself. Downgrade is one-way on purpose: putting another site's designator back is not a state worth reaching. Which PC type the bundle attaches to was the literal 'Shopfloor PC'. A plant whose type is "Shop Floor Workstation" silently got no shipped monitors at all, and one using that name for something else got all of them. It is a setting now, defaulting to the seeded name so nothing changes here, and a blank value attaches the bundle to nothing - the right neutral answer for a site that does not want it. Shipped rows refused every edit, including isactive and launchpath. A plant whose eDNC is on D:, or that runs no UDC, could not correct the path or switch the monitor off, and then carried permanent false drift for it. ADR-015 allows seed data BECAUSE a site can edit it afterwards. Paths and the active flag are now editable; kind and displayname stay fixed, because that pair is the identity 0007's bundle matches on. Also corrected two false claims in 0007's docstring: the policy tables belong to the computers plugin, not the core baseline, and a guarded no-op does NOT reconcile later - Alembic stamps the revision either way, so the bundle would simply never seed. LaunchPathPolicy was purely lexical. GetFullPath is string normalization that never touches the disk, so the root check constrained the NAME while File.Exists and CreateProcess followed reparse points to somewhere else. A junction under a writable directory in an approved root - creating one needs no privilege, only write access to the parent, and vendor trees under Program Files are not reliably locked down - turned WebUI monitor-edit rights into code execution as the console operator. It now resolves the real target, leaf and directory chain both, and re-checks THAT against the roots, rejecting if resolution fails. Alternate data streams passed the whole policy on net8.0: an interior colon no longer throws in GetFullPath, so "readme.txt:payload.exe" reported an .exe extension, passed the root check, and existed. Rejected explicitly now. Six tests added for the shipped-row and pctype behaviour. The Core project compiles clean. The launch-path change has NOT been exercised against real junctions, ADS or 8.3 names - that needs the win11 VM, and per the reviewer it wants a planted-violation suite there rather than my say-so. Still open from the review: monitor revocation does not take effect until agent restart, ActionPicker is keyboard-inoperable, the lightbox has no focus trap, one attached monitor grants generic any-target process control, and screenshots are retained unencrypted in the user profile. Scope: product
This commit is contained in:
@@ -197,3 +197,86 @@ def test_resolved_monitors_unknown_hostname_is_404(client, db, app):
|
||||
response = client.get(RESOLVED_URL, query_string={'hostname': 'NOSUCHBOX'},
|
||||
headers=_collector_headers(app))
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
# --- shipped rows are product-owned IDENTITY, not a fixed opinion about disks --
|
||||
#
|
||||
# The shipped bundle used to be uneditable in every field and attached to a
|
||||
# hardcoded PC-type name. A plant whose eDNC sits on another drive, or whose
|
||||
# type is called something else, could neither correct it nor turn it off, and
|
||||
# then carried permanent false drift for it. ADR-015 allows seed data because a
|
||||
# site can edit it afterwards.
|
||||
|
||||
def _shipped(db, displayname='eDNC', **kwargs):
|
||||
from plugins.endpoints.models import EndpointMonitor
|
||||
|
||||
monitor = EndpointMonitor(
|
||||
kind=kwargs.pop('kind', 'process'),
|
||||
displayname=displayname,
|
||||
processname=kwargs.pop('processname', 'DncMain'),
|
||||
launchpath=kwargs.pop('launchpath', r'C:\Program Files\Dnc\bin\DncMain.exe'),
|
||||
source='shipped', isactive=True, **kwargs)
|
||||
db.session.add(monitor)
|
||||
db.session.flush()
|
||||
return monitor
|
||||
|
||||
|
||||
def test_a_site_can_correct_a_shipped_monitors_launch_path(
|
||||
client, db, auth_headers):
|
||||
monitor = _shipped(db)
|
||||
response = client.put(
|
||||
'/api/endpoints/monitors/%d' % monitor.endpointmonitorid,
|
||||
json={'launchpath': r'D:\Dnc\bin\DncMain.exe'}, headers=auth_headers)
|
||||
assert response.status_code == 200
|
||||
assert response.get_json()['data']['launchpath'] == r'D:\Dnc\bin\DncMain.exe'
|
||||
|
||||
|
||||
def test_a_site_can_switch_off_a_shipped_monitor_it_does_not_run(
|
||||
client, db, auth_headers):
|
||||
monitor = _shipped(db, displayname='Universal Data Collector',
|
||||
processname='UDC')
|
||||
response = client.put(
|
||||
'/api/endpoints/monitors/%d' % monitor.endpointmonitorid,
|
||||
json={'isactive': False}, headers=auth_headers)
|
||||
assert response.status_code == 200
|
||||
assert response.get_json()['data']['isactive'] is False
|
||||
|
||||
|
||||
def test_a_shipped_monitor_keeps_the_identity_the_policy_matches_on(
|
||||
client, db, auth_headers):
|
||||
"""0007 attaches its bundle by (kind, displayname), so those stay put."""
|
||||
monitor = _shipped(db)
|
||||
response = client.put(
|
||||
'/api/endpoints/monitors/%d' % monitor.endpointmonitorid,
|
||||
json={'displayname': 'Renamed'}, headers=auth_headers)
|
||||
assert response.status_code == 403
|
||||
assert monitor.displayname == 'eDNC'
|
||||
|
||||
|
||||
# --- which PC type the shipped bundle attaches to is a setting ---------------
|
||||
|
||||
def test_shipped_monitors_follow_the_configured_pctype(client, db, app):
|
||||
from plugins.endpoints.api.monitors import (
|
||||
SHIPPED_PCTYPE_SETTING, _resolve_pctype_monitors)
|
||||
|
||||
_shipped(db)
|
||||
Setting.set(SHIPPED_PCTYPE_SETTING, 'Shop Floor Workstation')
|
||||
db.session.commit()
|
||||
|
||||
# The plant's own name for the type resolves the bundle...
|
||||
assert [m.displayname for m in
|
||||
_resolve_pctype_monitors('Shop Floor Workstation')] == ['eDNC']
|
||||
# ...and the name this shipped with no longer does.
|
||||
assert _resolve_pctype_monitors('Shopfloor PC') == []
|
||||
|
||||
|
||||
def test_a_blank_shipped_pctype_attaches_the_bundle_to_nothing(client, db, app):
|
||||
"""The neutral behaviour for a site that does not want the bundle: off,
|
||||
rather than silently attached to whatever happens to match."""
|
||||
from plugins.endpoints.api.monitors import (
|
||||
SHIPPED_PCTYPE_SETTING, _resolve_pctype_monitors)
|
||||
|
||||
_shipped(db)
|
||||
Setting.set(SHIPPED_PCTYPE_SETTING, '')
|
||||
db.session.commit()
|
||||
assert _resolve_pctype_monitors('Shopfloor PC') == []
|
||||
|
||||
@@ -4,7 +4,7 @@ Migration endpoints0007shippedpolicy offloads the eDNC/UDC/MTConnect monitors
|
||||
from the agent's hardcoded appsettings.json into a product-owned policy bundle:
|
||||
a shipped ConfigurationPolicy "Shopfloor App Monitors" (global scope, one
|
||||
desired-state monitor action per shipped monitor) wrapped in a shipped
|
||||
BusinessPolicy "RPCA Shopfloor App Monitors".
|
||||
BusinessPolicy "Shopfloor App Monitors".
|
||||
|
||||
Run the endpoints chain against a fresh engine, alongside the computers policy
|
||||
tables the bundle writes into, and assert the bundle exists, is source='shipped'
|
||||
@@ -25,7 +25,7 @@ _ENDPOINTS_VERSIONS = _ROOT / 'plugins' / 'endpoints' / 'migrations' / 'versions
|
||||
_COMPUTERS_VERSIONS = _ROOT / 'plugins' / 'computers' / 'migrations' / 'versions'
|
||||
|
||||
CONFIG_POLICY_NAME = 'Shopfloor App Monitors'
|
||||
BUSINESS_POLICY_NAME = 'RPCA Shopfloor App Monitors'
|
||||
BUSINESS_POLICY_NAME = 'Shopfloor App Monitors'
|
||||
|
||||
|
||||
def _load(path, prefix):
|
||||
|
||||
Reference in New Issue
Block a user