Add configuration policy monitors and action picker for computers
Configuration policies need a way to attach process/service monitors so drift can be evaluated against a machine's desired state. This wires the policy resolver and routes to the shared monitor catalog and adds an action picker UI that can create process monitors with an optional launch path.
This commit is contained in:
@@ -40,6 +40,23 @@ def _known_actions():
|
||||
return set(COMMAND_ALLOWLIST.keys())
|
||||
|
||||
|
||||
def _is_valid_action(name):
|
||||
"""True when an action name is either an allowlisted command or a monitor
|
||||
attachment (monitor:<id>) pointing at an existing monitor definition.
|
||||
|
||||
Monitor actions are intentionally kept out of the command allowlist so they
|
||||
are never enqueued as commands; they are only valid as policy attachments.
|
||||
"""
|
||||
if name in _known_actions():
|
||||
return True
|
||||
from plugins.endpoints.commands import monitor_id_from_action
|
||||
from plugins.endpoints.models import EndpointMonitor
|
||||
monitorid = monitor_id_from_action(name)
|
||||
if monitorid is None:
|
||||
return False
|
||||
return db.session.get(EndpointMonitor, monitorid) is not None
|
||||
|
||||
|
||||
def _configpolicy_dict(policy):
|
||||
data = policy.to_dict()
|
||||
data['actions'] = [a.to_dict() for a in policy.actions]
|
||||
@@ -89,11 +106,15 @@ def _apply_actions(policy, actions):
|
||||
|
||||
Returns an error_response on failure, else None.
|
||||
"""
|
||||
known = _known_actions()
|
||||
policy.actions.clear()
|
||||
# Flush the deletes before re-appending. The (configurationpolicyid, action)
|
||||
# unique constraint collides if SQLAlchemy orders a re-added action's INSERT
|
||||
# before the old row's DELETE in the same flush, which surfaces as a generic
|
||||
# "value already in use" IntegrityError on an otherwise valid edit.
|
||||
db.session.flush()
|
||||
for row in actions or []:
|
||||
name = (row.get('action') or '').strip()
|
||||
if name not in known:
|
||||
if not _is_valid_action(name):
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR,
|
||||
f'Unknown action: {name!r}')
|
||||
mode = (row.get('mode') or ACTION_MODE_GATE).strip()
|
||||
@@ -259,6 +280,10 @@ def get_business_policy(policy_id: int):
|
||||
def _apply_items(policy, items):
|
||||
"""Replace a business policy's configuration-policy items, validating each."""
|
||||
policy.items.clear()
|
||||
# Flush the deletes before re-appending so the (businesspolicyid,
|
||||
# configurationpolicyid) unique constraint does not collide with a re-added
|
||||
# item whose INSERT would otherwise race ahead of the old row's DELETE.
|
||||
db.session.flush()
|
||||
seen = set()
|
||||
for order, row in enumerate(items or []):
|
||||
cpid = row.get('configurationpolicyid')
|
||||
@@ -375,14 +400,19 @@ def delete_business_policy(policy_id: int):
|
||||
@computers_bp.route('/policies/actions', methods=['GET'])
|
||||
@require_permission('computers.view')
|
||||
def list_policy_actions():
|
||||
"""The allowlisted action names + labels a policy may reference."""
|
||||
"""The allowlisted action names + labels a policy may reference.
|
||||
|
||||
Returns command actions tagged kind='command'. Monitor attachments are not
|
||||
listed here: the UI builds that branch from the endpoints monitor catalog
|
||||
(endpointsApi.monitors) so newly created monitors appear without a reload.
|
||||
"""
|
||||
gate = _feature_gate()
|
||||
if gate:
|
||||
return gate
|
||||
from plugins.endpoints.commands import COMMAND_ALLOWLIST
|
||||
return success_response([
|
||||
{'action': name, 'label': meta.get('label'),
|
||||
'description': meta.get('description')}
|
||||
'description': meta.get('description'), 'kind': 'command'}
|
||||
for name, meta in COMMAND_ALLOWLIST.items()
|
||||
])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user