feat(api): unify policy handling with default action updates
Build and Push EvoFirewall Docker Image / build-and-push (push) Successful in 1m53s
Build and Push EvoFirewall Docker Image / create-release (push) Skipped

- Updated `evofw-firewall.sh` and related scripts to replace `policy_mode` with `default_action`, enhancing clarity and consistency in policy management.
- Adjusted agent routes and evaluation logic to accommodate the new default action structure, ensuring backward compatibility with legacy modes.
- Enhanced tests to validate the new default action behavior and its integration within the agent policy framework.
- Refactored related components in the web interface to align with the updated policy handling, improving user experience and reducing confusion around policy modes.
This commit is contained in:
Denozordec
2026-07-23 10:52:28 +07:00
parent a6eb21a10d
commit 1f7273f38d
30 changed files with 1469 additions and 621 deletions
@@ -0,0 +1,64 @@
-- Replace exclusive blacklist/whitelist with default_action (accept|drop).
-- Unified kernel chain: deny → allow → default_action.
PRAGMA foreign_keys = OFF;
CREATE TABLE agents_v3 (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
hostname TEXT,
platform TEXT NOT NULL DEFAULT 'linux',
token_prefix TEXT NOT NULL,
token_hash TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
default_action TEXT NOT NULL DEFAULT 'accept',
policy_generation INTEGER NOT NULL DEFAULT 1,
last_seen_at TEXT,
last_seen_ip TEXT,
last_apply_at TEXT,
last_apply_status TEXT,
last_apply_error TEXT,
last_apply_prefix_count INTEGER DEFAULT 0,
last_apply_packets_dropped INTEGER NOT NULL DEFAULT 0,
last_apply_packets_accepted INTEGER NOT NULL DEFAULT 0,
last_apply_kernel_method TEXT,
client_version TEXT,
settings_json TEXT NOT NULL DEFAULT '{}',
created_by_user_id TEXT,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
approved_at TEXT,
revoked_at TEXT,
CHECK (status IN ('invited', 'pending', 'approved', 'revoked')),
CHECK (platform IN ('linux', 'mikrotik')),
CHECK (default_action IN ('accept', 'drop')),
CHECK (length(trim(name)) > 0)
);
INSERT INTO agents_v3 (
id, name, hostname, platform, token_prefix, token_hash, status, default_action,
policy_generation, last_seen_at, last_seen_ip, last_apply_at, last_apply_status,
last_apply_error, last_apply_prefix_count, last_apply_packets_dropped,
last_apply_packets_accepted, last_apply_kernel_method, client_version,
settings_json, created_by_user_id, created_at, approved_at, revoked_at
)
SELECT
id, name, hostname, platform, token_prefix, token_hash, status,
CASE
WHEN policy_mode = 'whitelist' THEN 'drop'
WHEN policy_mode = 'drop' THEN 'drop'
WHEN policy_mode = 'accept' THEN 'accept'
ELSE 'accept'
END,
policy_generation, last_seen_at, last_seen_ip, last_apply_at, last_apply_status,
last_apply_error, last_apply_prefix_count, last_apply_packets_dropped,
last_apply_packets_accepted, last_apply_kernel_method, client_version,
settings_json, created_by_user_id, created_at, approved_at, revoked_at
FROM agents;
DROP TABLE agents;
ALTER TABLE agents_v3 RENAME TO agents;
CREATE UNIQUE INDEX IF NOT EXISTS idx_agents_token_hash ON agents (token_hash);
CREATE INDEX IF NOT EXISTS idx_agents_status ON agents (status);
PRAGMA foreign_keys = ON;
+5 -15
View File
@@ -215,22 +215,12 @@ export function listSetsForAgent(db: Db, agentId: string) {
.all()
}
/** Replace agent↔set assignments; set_ids order = sort. All sets must share policy_mode. */
/** Replace agent↔set assignments; set_ids order = sort. */
export function setAgentPolicySets(db: Db, agentId: string, setIds: string[]) {
if (setIds.length > 0) {
const modes = new Set<string>()
for (const setId of setIds) {
const s = getPolicySet(db, setId)
if (!s) throw new Error(`policy set not found: ${setId}`)
modes.add(s.policyMode === 'whitelist' ? 'whitelist' : 'blacklist')
for (const setId of setIds) {
if (!getPolicySet(db, setId)) {
throw new Error(`policy set not found: ${setId}`)
}
if (modes.size > 1) {
throw new Error(
'все наборы агента должны иметь один режим (blacklist или whitelist)',
)
}
const mode = [...modes][0] ?? 'blacklist'
updateAgent(db, agentId, { policyMode: mode })
}
db.delete(agentPolicySets).where(eq(agentPolicySets.agentId, agentId)).run()
@@ -498,7 +488,7 @@ export function cloneRulesFrom(
}
updateAgent(db, targetAgentId, {
policyMode: source.policyMode,
defaultAction: source.defaultAction,
policyGeneration: (target.policyGeneration ?? 1) + 1,
})
return getAgent(db, targetAgentId)
+4 -2
View File
@@ -19,7 +19,8 @@ export const agents = sqliteTable(
tokenPrefix: text('token_prefix').notNull(),
tokenHash: text('token_hash').notNull(),
status: text('status').notNull().default('pending'), // invited | pending | approved | revoked
policyMode: text('policy_mode').notNull().default('blacklist'), // blacklist | whitelist
/** Packet default when not in deny/allow sets: accept | drop */
defaultAction: text('default_action').notNull().default('accept'),
policyGeneration: integer('policy_generation').notNull().default(1),
lastSeenAt: text('last_seen_at'),
lastSeenIp: text('last_seen_ip'),
@@ -84,7 +85,8 @@ export const policySets = sqliteTable('policy_sets', {
name: text('name').notNull(),
description: text('description'),
enabled: integer('enabled').notNull().default(1),
policyMode: text('policy_mode').notNull().default('blacklist'), // blacklist | whitelist
/** Legacy unused; sets no longer carry exclusive mode. */
policyMode: text('policy_mode').notNull().default('blacklist'),
createdAt: text('created_at')
.notNull()
.default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`),