feat(api, web): implement policy mode management for agents and rules
- Added support for policy modes ('blacklist' and 'whitelist') in agent and policy set management.
- Updated API endpoints to handle policy mode during agent assignment and rule operations.
- Enhanced the web UI to display and manage policy modes for agents and rules, ensuring all assigned sets share a consistent mode.
- Introduced new validation to enforce single policy mode across assigned sets for agents.
- Improved error handling for policy mode conflicts and updated documentation accordingly.
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -7,6 +7,8 @@ import {
|
||||
createPolicySetBodySchema,
|
||||
createInstallLinkBodySchema,
|
||||
patchPolicySetBodySchema,
|
||||
patchPolicyRuleBodySchema,
|
||||
reorderPolicyRulesBodySchema,
|
||||
putAgentPolicySetsBodySchema,
|
||||
patchAgentBodySchema,
|
||||
cloneFromBodySchema,
|
||||
@@ -74,6 +76,8 @@ function mapPolicySet(
|
||||
name: s.name,
|
||||
description: s.description,
|
||||
enabled: s.enabled === 1,
|
||||
policy_mode:
|
||||
s.policyMode === 'whitelist' ? ('whitelist' as const) : ('blacklist' as const),
|
||||
rules_count: repos.countRulesInSet(db, s.id),
|
||||
agents_count: repos.countAgentsForSet(db, s.id),
|
||||
created_at: s.createdAt,
|
||||
@@ -90,6 +94,7 @@ function mapPolicyRule(
|
||||
set_id: r.setId,
|
||||
priority: r.priority,
|
||||
action: r.action,
|
||||
enabled: r.enabled !== 0,
|
||||
list_id: r.listId,
|
||||
cidr: r.cidr,
|
||||
hostname: r.hostname,
|
||||
@@ -496,6 +501,7 @@ export const controlRoutes: FastifyPluginAsync<{ config: AppConfig }> = async (
|
||||
name: body.name.trim(),
|
||||
description: body.description ?? null,
|
||||
enabled: body.enabled === false ? 0 : 1,
|
||||
policyMode: body.policy_mode === 'whitelist' ? 'whitelist' : 'blacklist',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
@@ -510,8 +516,37 @@ export const controlRoutes: FastifyPluginAsync<{ config: AppConfig }> = async (
|
||||
name: body.name?.trim(),
|
||||
description: body.description,
|
||||
enabled: body.enabled === undefined ? undefined : body.enabled ? 1 : 0,
|
||||
policyMode: body.policy_mode,
|
||||
})
|
||||
if (body.enabled !== undefined) repos.bumpAgentsForSet(app.db, s.id)
|
||||
if (body.enabled !== undefined || body.policy_mode !== undefined) {
|
||||
repos.bumpAgentsForSet(app.db, s.id)
|
||||
}
|
||||
// Sync agent.policy_mode cache when set mode changes
|
||||
if (body.policy_mode) {
|
||||
for (const agentId of repos.listAgentIdsForSet(app.db, s.id)) {
|
||||
try {
|
||||
const sets = repos.listSetsForAgent(app.db, agentId)
|
||||
const modes = new Set(
|
||||
sets
|
||||
.filter((x) => x.enabled === 1)
|
||||
.map((x) =>
|
||||
x.policyMode === 'whitelist' ? 'whitelist' : 'blacklist',
|
||||
),
|
||||
)
|
||||
if (modes.size > 1) {
|
||||
throw new AppError(
|
||||
'VALIDATION_ERROR',
|
||||
'агент имеет наборы с разными режимами — выровняйте mode',
|
||||
400,
|
||||
)
|
||||
}
|
||||
const mode = [...modes][0] ?? body.policy_mode
|
||||
repos.updateAgent(app.db, agentId, { policyMode: mode })
|
||||
} catch (err) {
|
||||
if (err instanceof AppError) throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
return mapPolicySet(updated!, app.db)
|
||||
})
|
||||
|
||||
@@ -554,7 +589,15 @@ export const controlRoutes: FastifyPluginAsync<{ config: AppConfig }> = async (
|
||||
throw new AppError('NOT_FOUND', `Policy set not found: ${setId}`, 404)
|
||||
}
|
||||
}
|
||||
repos.setAgentPolicySets(app.db, a.id, body.set_ids)
|
||||
try {
|
||||
repos.setAgentPolicySets(app.db, a.id, body.set_ids)
|
||||
} catch (err) {
|
||||
throw new AppError(
|
||||
'VALIDATION_ERROR',
|
||||
err instanceof Error ? err.message : String(err),
|
||||
400,
|
||||
)
|
||||
}
|
||||
return {
|
||||
items: repos.listSetsForAgent(app.db, a.id).map((s) => ({
|
||||
set_id: s.setId,
|
||||
@@ -562,6 +605,8 @@ export const controlRoutes: FastifyPluginAsync<{ config: AppConfig }> = async (
|
||||
name: s.name,
|
||||
description: s.description,
|
||||
enabled: s.enabled === 1,
|
||||
policy_mode:
|
||||
s.policyMode === 'whitelist' ? 'whitelist' : 'blacklist',
|
||||
})),
|
||||
}
|
||||
},
|
||||
@@ -579,6 +624,8 @@ export const controlRoutes: FastifyPluginAsync<{ config: AppConfig }> = async (
|
||||
name: s.name,
|
||||
description: s.description,
|
||||
enabled: s.enabled === 1,
|
||||
policy_mode:
|
||||
s.policyMode === 'whitelist' ? 'whitelist' : 'blacklist',
|
||||
})),
|
||||
}
|
||||
},
|
||||
@@ -627,12 +674,16 @@ export const controlRoutes: FastifyPluginAsync<{ config: AppConfig }> = async (
|
||||
throw new AppError('NOT_FOUND', 'IP list not found', 404)
|
||||
}
|
||||
|
||||
const priority =
|
||||
body.priority ?? repos.nextRulePriority(app.db, body.set_id)
|
||||
|
||||
const id = crypto.randomUUID()
|
||||
const row = repos.insertPolicyRule(app.db, {
|
||||
id,
|
||||
setId: body.set_id,
|
||||
priority: body.priority,
|
||||
priority,
|
||||
action: body.action,
|
||||
enabled: body.enabled === false ? 0 : 1,
|
||||
listId,
|
||||
cidr,
|
||||
hostname,
|
||||
@@ -659,6 +710,44 @@ export const controlRoutes: FastifyPluginAsync<{ config: AppConfig }> = async (
|
||||
return mapPolicyRule(row!, app.db)
|
||||
})
|
||||
|
||||
app.patch<{ Params: { id: string } }>('/rules/:id', async (req) => {
|
||||
const body = patchPolicyRuleBodySchema.parse(req.body)
|
||||
const rule = repos.getPolicyRule(app.db, req.params.id)
|
||||
if (!rule) throw new AppError('NOT_FOUND', 'Rule not found', 404)
|
||||
const updated = repos.updatePolicyRule(app.db, rule.id, {
|
||||
enabled: body.enabled === undefined ? undefined : body.enabled ? 1 : 0,
|
||||
action: body.action,
|
||||
comment: body.comment,
|
||||
priority: body.priority,
|
||||
})
|
||||
repos.bumpAgentsForSet(app.db, rule.setId)
|
||||
return mapPolicyRule(updated!, app.db)
|
||||
})
|
||||
|
||||
app.put<{ Params: { id: string } }>(
|
||||
'/policy-sets/:id/rules/reorder',
|
||||
async (req) => {
|
||||
const body = reorderPolicyRulesBodySchema.parse(req.body)
|
||||
const s = repos.getPolicySet(app.db, req.params.id)
|
||||
if (!s) throw new AppError('NOT_FOUND', 'Policy set not found', 404)
|
||||
try {
|
||||
repos.reorderPolicyRules(app.db, s.id, body.ordered_ids)
|
||||
} catch (err) {
|
||||
throw new AppError(
|
||||
'VALIDATION_ERROR',
|
||||
err instanceof Error ? err.message : String(err),
|
||||
400,
|
||||
)
|
||||
}
|
||||
repos.bumpAgentsForSet(app.db, s.id)
|
||||
return {
|
||||
items: repos
|
||||
.listPolicyRules(app.db, s.id)
|
||||
.map((r) => mapPolicyRule(r, app.db)),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
app.delete<{ Params: { id: string } }>('/rules/:id', async (req) => {
|
||||
const rule = repos.getPolicyRule(app.db, req.params.id)
|
||||
if (!rule) throw new AppError('NOT_FOUND', 'Rule not found', 404)
|
||||
|
||||
Reference in New Issue
Block a user