feat(policy): именованные наборы правил с DNS и M:N привязкой к агентам
Правила живут в policy_sets; evaluate мержит назначенные наборы; источник list|CIDR|hostname с кэшем DNS. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
-- Policy sets + DNS hostname rules (v2)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS policy_sets (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
CHECK (enabled IN (0, 1)),
|
||||
CHECK (length(trim(name)) > 0)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS agent_policy_sets (
|
||||
agent_id TEXT NOT NULL REFERENCES agents (id) ON DELETE CASCADE,
|
||||
set_id TEXT NOT NULL REFERENCES policy_sets (id) ON DELETE CASCADE,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (agent_id, set_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_policy_sets_set ON agent_policy_sets (set_id);
|
||||
|
||||
-- Rebuild policy_rules with set_id + hostname (drop agent_id)
|
||||
CREATE TABLE IF NOT EXISTS policy_rules_v2 (
|
||||
id TEXT PRIMARY KEY,
|
||||
set_id TEXT NOT NULL REFERENCES policy_sets (id) ON DELETE CASCADE,
|
||||
priority INTEGER NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
list_id TEXT REFERENCES ip_lists (id) ON DELETE CASCADE,
|
||||
cidr TEXT,
|
||||
hostname TEXT,
|
||||
comment TEXT,
|
||||
created_by_user_id TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
CHECK (action IN ('allow', 'deny')),
|
||||
CHECK (priority >= 1 AND priority <= 10000)
|
||||
);
|
||||
|
||||
-- Shared default set
|
||||
INSERT OR IGNORE INTO policy_sets (id, name, description, enabled)
|
||||
VALUES (
|
||||
'set-shared-default',
|
||||
'Общие',
|
||||
'Набор по умолчанию (бывшие tenant-правила). Назначается approved-агентам.',
|
||||
1
|
||||
);
|
||||
|
||||
-- Per-agent sets for agents that already have rules
|
||||
INSERT OR IGNORE INTO policy_sets (id, name, description, enabled)
|
||||
SELECT
|
||||
'set-agent-' || a.id,
|
||||
'Агент: ' || a.name,
|
||||
'Мигрировано из правил агента',
|
||||
1
|
||||
FROM agents a
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM policy_rules r WHERE r.agent_id = a.id
|
||||
);
|
||||
|
||||
-- Copy tenant rules → Общие
|
||||
INSERT INTO policy_rules_v2 (
|
||||
id, set_id, priority, action, list_id, cidr, hostname, comment,
|
||||
created_by_user_id, created_at, updated_at
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
'set-shared-default',
|
||||
priority,
|
||||
action,
|
||||
list_id,
|
||||
cidr,
|
||||
NULL,
|
||||
comment,
|
||||
created_by_user_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM policy_rules
|
||||
WHERE agent_id IS NULL;
|
||||
|
||||
-- Copy agent rules → per-agent sets
|
||||
INSERT INTO policy_rules_v2 (
|
||||
id, set_id, priority, action, list_id, cidr, hostname, comment,
|
||||
created_by_user_id, created_at, updated_at
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
'set-agent-' || agent_id,
|
||||
priority,
|
||||
action,
|
||||
list_id,
|
||||
cidr,
|
||||
NULL,
|
||||
comment,
|
||||
created_by_user_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM policy_rules
|
||||
WHERE agent_id IS NOT NULL;
|
||||
|
||||
DROP TABLE policy_rules;
|
||||
ALTER TABLE policy_rules_v2 RENAME TO policy_rules;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_policy_rules_set_priority
|
||||
ON policy_rules (set_id, priority);
|
||||
|
||||
-- Assign «Общие» to all approved agents
|
||||
INSERT OR IGNORE INTO agent_policy_sets (agent_id, set_id, sort)
|
||||
SELECT id, 'set-shared-default', 0
|
||||
FROM agents
|
||||
WHERE status = 'approved';
|
||||
|
||||
-- Assign per-agent migrated sets
|
||||
INSERT OR IGNORE INTO agent_policy_sets (agent_id, set_id, sort)
|
||||
SELECT
|
||||
substr(id, length('set-agent-') + 1),
|
||||
id,
|
||||
10
|
||||
FROM policy_sets
|
||||
WHERE id LIKE 'set-agent-%';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS policy_rule_resolved (
|
||||
id TEXT PRIMARY KEY,
|
||||
rule_id TEXT NOT NULL REFERENCES policy_rules (id) ON DELETE CASCADE,
|
||||
cidr TEXT NOT NULL,
|
||||
resolved_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_policy_rule_resolved_rule_cidr
|
||||
ON policy_rule_resolved (rule_id, cidr);
|
||||
CREATE INDEX IF NOT EXISTS idx_policy_rule_resolved_rule
|
||||
ON policy_rule_resolved (rule_id);
|
||||
@@ -1,13 +1,17 @@
|
||||
import { eq, and, desc, isNull, sql } from 'drizzle-orm'
|
||||
import { eq, and, desc, asc, sql, count, inArray } from 'drizzle-orm'
|
||||
import type { Db } from '../client.js'
|
||||
import {
|
||||
agents,
|
||||
ipLists,
|
||||
ipListEntries,
|
||||
policySets,
|
||||
agentPolicySets,
|
||||
policyRules,
|
||||
policyRuleResolved,
|
||||
ipOverrides,
|
||||
agentStatsSamples,
|
||||
settings,
|
||||
SHARED_POLICY_SET_ID,
|
||||
} from '../schema.js'
|
||||
|
||||
export function listAgents(db: Db) {
|
||||
@@ -22,10 +26,7 @@ export function getAgentByTokenHash(db: Db, tokenHash: string) {
|
||||
return db.select().from(agents).where(eq(agents.tokenHash, tokenHash)).get()
|
||||
}
|
||||
|
||||
export function insertAgent(
|
||||
db: Db,
|
||||
row: typeof agents.$inferInsert,
|
||||
) {
|
||||
export function insertAgent(db: Db, row: typeof agents.$inferInsert) {
|
||||
db.insert(agents).values(row).run()
|
||||
return getAgent(db, row.id)
|
||||
}
|
||||
@@ -50,6 +51,24 @@ export function bumpAgentGeneration(db: Db, id: string) {
|
||||
.run()
|
||||
}
|
||||
|
||||
export function bumpAgentsForSet(db: Db, setId: string) {
|
||||
const rows = db
|
||||
.select({ agentId: agentPolicySets.agentId })
|
||||
.from(agentPolicySets)
|
||||
.where(eq(agentPolicySets.setId, setId))
|
||||
.all()
|
||||
for (const r of rows) bumpAgentGeneration(db, r.agentId)
|
||||
}
|
||||
|
||||
export function bumpAllApprovedAgents(db: Db) {
|
||||
const rows = db
|
||||
.select({ id: agents.id })
|
||||
.from(agents)
|
||||
.where(eq(agents.status, 'approved'))
|
||||
.all()
|
||||
for (const r of rows) bumpAgentGeneration(db, r.id)
|
||||
}
|
||||
|
||||
export function listIpLists(db: Db) {
|
||||
return db.select().from(ipLists).orderBy(desc(ipLists.createdAt)).all()
|
||||
}
|
||||
@@ -102,24 +121,144 @@ export function replaceIpListEntries(db: Db, listId: string, cidrs: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
export function listPolicyRules(db: Db, agentId?: string | null) {
|
||||
if (agentId === undefined) {
|
||||
return db.select().from(policyRules).orderBy(policyRules.priority).all()
|
||||
/* ── Policy sets ── */
|
||||
|
||||
export function listPolicySets(db: Db) {
|
||||
return db.select().from(policySets).orderBy(asc(policySets.name)).all()
|
||||
}
|
||||
|
||||
export function getPolicySet(db: Db, id: string) {
|
||||
return db.select().from(policySets).where(eq(policySets.id, id)).get()
|
||||
}
|
||||
|
||||
export function insertPolicySet(db: Db, row: typeof policySets.$inferInsert) {
|
||||
db.insert(policySets).values(row).run()
|
||||
return getPolicySet(db, row.id)
|
||||
}
|
||||
|
||||
export function updatePolicySet(
|
||||
db: Db,
|
||||
id: string,
|
||||
patch: Partial<typeof policySets.$inferInsert>,
|
||||
) {
|
||||
db.update(policySets)
|
||||
.set({ ...patch, updatedAt: new Date().toISOString() })
|
||||
.where(eq(policySets.id, id))
|
||||
.run()
|
||||
return getPolicySet(db, id)
|
||||
}
|
||||
|
||||
export function deletePolicySet(db: Db, id: string) {
|
||||
if (id === SHARED_POLICY_SET_ID) {
|
||||
throw new Error('cannot delete shared default set')
|
||||
}
|
||||
if (agentId === null) {
|
||||
db.delete(policySets).where(eq(policySets.id, id)).run()
|
||||
}
|
||||
|
||||
export function countRulesInSet(db: Db, setId: string): number {
|
||||
const row = db
|
||||
.select({ n: count() })
|
||||
.from(policyRules)
|
||||
.where(eq(policyRules.setId, setId))
|
||||
.get()
|
||||
return row?.n ?? 0
|
||||
}
|
||||
|
||||
export function countAgentsForSet(db: Db, setId: string): number {
|
||||
const row = db
|
||||
.select({ n: count() })
|
||||
.from(agentPolicySets)
|
||||
.where(eq(agentPolicySets.setId, setId))
|
||||
.get()
|
||||
return row?.n ?? 0
|
||||
}
|
||||
|
||||
export function listAgentIdsForSet(db: Db, setId: string): string[] {
|
||||
return db
|
||||
.select({ agentId: agentPolicySets.agentId })
|
||||
.from(agentPolicySets)
|
||||
.where(eq(agentPolicySets.setId, setId))
|
||||
.all()
|
||||
.map((r) => r.agentId)
|
||||
}
|
||||
|
||||
export function listSetsForAgent(db: Db, agentId: string) {
|
||||
return db
|
||||
.select({
|
||||
setId: agentPolicySets.setId,
|
||||
sort: agentPolicySets.sort,
|
||||
name: policySets.name,
|
||||
description: policySets.description,
|
||||
enabled: policySets.enabled,
|
||||
})
|
||||
.from(agentPolicySets)
|
||||
.innerJoin(policySets, eq(agentPolicySets.setId, policySets.id))
|
||||
.where(eq(agentPolicySets.agentId, agentId))
|
||||
.orderBy(asc(agentPolicySets.sort), asc(policySets.name))
|
||||
.all()
|
||||
}
|
||||
|
||||
/** Replace agent↔set assignments; set_ids order = sort. */
|
||||
export function setAgentPolicySets(db: Db, agentId: string, setIds: string[]) {
|
||||
db.delete(agentPolicySets).where(eq(agentPolicySets.agentId, agentId)).run()
|
||||
setIds.forEach((setId, i) => {
|
||||
db.insert(agentPolicySets)
|
||||
.values({ agentId, setId, sort: i * 10 })
|
||||
.run()
|
||||
})
|
||||
bumpAgentGeneration(db, agentId)
|
||||
}
|
||||
|
||||
export function ensureSharedSetAssigned(db: Db, agentId: string) {
|
||||
const existing = db
|
||||
.select()
|
||||
.from(agentPolicySets)
|
||||
.where(
|
||||
and(
|
||||
eq(agentPolicySets.agentId, agentId),
|
||||
eq(agentPolicySets.setId, SHARED_POLICY_SET_ID),
|
||||
),
|
||||
)
|
||||
.get()
|
||||
if (existing) return
|
||||
db.insert(agentPolicySets)
|
||||
.values({ agentId, setId: SHARED_POLICY_SET_ID, sort: 0 })
|
||||
.run()
|
||||
}
|
||||
|
||||
/* ── Policy rules ── */
|
||||
|
||||
export function listPolicyRules(db: Db, setId?: string) {
|
||||
if (setId) {
|
||||
return db
|
||||
.select()
|
||||
.from(policyRules)
|
||||
.where(isNull(policyRules.agentId))
|
||||
.orderBy(policyRules.priority)
|
||||
.where(eq(policyRules.setId, setId))
|
||||
.orderBy(asc(policyRules.priority))
|
||||
.all()
|
||||
}
|
||||
return db
|
||||
return db.select().from(policyRules).orderBy(asc(policyRules.priority)).all()
|
||||
}
|
||||
|
||||
export function listPolicyRulesForAgent(db: Db, agentId: string) {
|
||||
const assignments = listSetsForAgent(db, agentId).filter((s) => s.enabled === 1)
|
||||
if (assignments.length === 0) return []
|
||||
|
||||
const setIds = assignments.map((a) => a.setId)
|
||||
const sortBySet = new Map(assignments.map((a) => [a.setId, a.sort]))
|
||||
|
||||
const rules = db
|
||||
.select()
|
||||
.from(policyRules)
|
||||
.where(eq(policyRules.agentId, agentId))
|
||||
.orderBy(policyRules.priority)
|
||||
.where(inArray(policyRules.setId, setIds))
|
||||
.all()
|
||||
|
||||
return rules
|
||||
.map((r) => ({
|
||||
...r,
|
||||
_setSort: sortBySet.get(r.setId) ?? 0,
|
||||
}))
|
||||
.sort((a, b) => a._setSort - b._setSort || a.priority - b.priority)
|
||||
}
|
||||
|
||||
export function getPolicyRule(db: Db, id: string) {
|
||||
@@ -138,6 +277,39 @@ export function deletePolicyRule(db: Db, id: string) {
|
||||
db.delete(policyRules).where(eq(policyRules.id, id)).run()
|
||||
}
|
||||
|
||||
export function listHostnameRules(db: Db) {
|
||||
return db
|
||||
.select()
|
||||
.from(policyRules)
|
||||
.where(sql`${policyRules.hostname} IS NOT NULL AND trim(${policyRules.hostname}) != ''`)
|
||||
.all()
|
||||
}
|
||||
|
||||
export function listResolvedForRule(db: Db, ruleId: string) {
|
||||
return db
|
||||
.select()
|
||||
.from(policyRuleResolved)
|
||||
.where(eq(policyRuleResolved.ruleId, ruleId))
|
||||
.all()
|
||||
}
|
||||
|
||||
export function replaceResolvedForRule(db: Db, ruleId: string, cidrs: string[]) {
|
||||
db.delete(policyRuleResolved)
|
||||
.where(eq(policyRuleResolved.ruleId, ruleId))
|
||||
.run()
|
||||
const now = new Date().toISOString()
|
||||
for (const cidr of cidrs) {
|
||||
db.insert(policyRuleResolved)
|
||||
.values({
|
||||
id: crypto.randomUUID(),
|
||||
ruleId,
|
||||
cidr,
|
||||
resolvedAt: now,
|
||||
})
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
||||
export function listOverrides(db: Db, agentId: string) {
|
||||
return db
|
||||
.select()
|
||||
@@ -206,6 +378,7 @@ export function listSettings(db: Db) {
|
||||
return db.select().from(settings).all()
|
||||
}
|
||||
|
||||
/** Clone set assignments (+ optional overrides) and policy mode from source agent. */
|
||||
export function cloneRulesFrom(
|
||||
db: Db,
|
||||
sourceAgentId: string,
|
||||
@@ -216,23 +389,12 @@ export function cloneRulesFrom(
|
||||
const target = getAgent(db, targetAgentId)
|
||||
if (!source || !target) return null
|
||||
|
||||
db.delete(policyRules).where(eq(policyRules.agentId, targetAgentId)).run()
|
||||
const rules = listPolicyRules(db, sourceAgentId)
|
||||
for (const r of rules) {
|
||||
db.insert(policyRules)
|
||||
.values({
|
||||
id: crypto.randomUUID(),
|
||||
agentId: targetAgentId,
|
||||
priority: r.priority,
|
||||
action: r.action,
|
||||
listId: r.listId,
|
||||
cidr: r.cidr,
|
||||
comment: r.comment,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
.run()
|
||||
}
|
||||
const sourceSets = listSetsForAgent(db, sourceAgentId)
|
||||
setAgentPolicySets(
|
||||
db,
|
||||
targetAgentId,
|
||||
sourceSets.map((s) => s.setId),
|
||||
)
|
||||
|
||||
if (includeOverrides) {
|
||||
db.delete(ipOverrides).where(eq(ipOverrides.agentId, targetAgentId)).run()
|
||||
@@ -265,6 +427,8 @@ export const repos = {
|
||||
updateAgent,
|
||||
deleteAgent,
|
||||
bumpAgentGeneration,
|
||||
bumpAgentsForSet,
|
||||
bumpAllApprovedAgents,
|
||||
listIpLists,
|
||||
getIpList,
|
||||
insertIpList,
|
||||
@@ -272,10 +436,25 @@ export const repos = {
|
||||
deleteIpList,
|
||||
listIpListEntries,
|
||||
replaceIpListEntries,
|
||||
listPolicySets,
|
||||
getPolicySet,
|
||||
insertPolicySet,
|
||||
updatePolicySet,
|
||||
deletePolicySet,
|
||||
countRulesInSet,
|
||||
countAgentsForSet,
|
||||
listAgentIdsForSet,
|
||||
listSetsForAgent,
|
||||
setAgentPolicySets,
|
||||
ensureSharedSetAssigned,
|
||||
listPolicyRules,
|
||||
listPolicyRulesForAgent,
|
||||
getPolicyRule,
|
||||
insertPolicyRule,
|
||||
deletePolicyRule,
|
||||
listHostnameRules,
|
||||
listResolvedForRule,
|
||||
replaceResolvedForRule,
|
||||
listOverrides,
|
||||
insertOverride,
|
||||
deleteOverride,
|
||||
@@ -287,3 +466,5 @@ export const repos = {
|
||||
listSettings,
|
||||
cloneRulesFrom,
|
||||
}
|
||||
|
||||
export { SHARED_POLICY_SET_ID }
|
||||
|
||||
@@ -78,15 +78,49 @@ export const ipListEntries = sqliteTable(
|
||||
}),
|
||||
)
|
||||
|
||||
/** Named reusable policy sets (M:N with agents). */
|
||||
export const policySets = sqliteTable('policy_sets', {
|
||||
id: text('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
enabled: integer('enabled').notNull().default(1),
|
||||
createdAt: text('created_at')
|
||||
.notNull()
|
||||
.default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`),
|
||||
updatedAt: text('updated_at')
|
||||
.notNull()
|
||||
.default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`),
|
||||
})
|
||||
|
||||
export const agentPolicySets = sqliteTable(
|
||||
'agent_policy_sets',
|
||||
{
|
||||
agentId: text('agent_id')
|
||||
.notNull()
|
||||
.references(() => agents.id, { onDelete: 'cascade' }),
|
||||
setId: text('set_id')
|
||||
.notNull()
|
||||
.references(() => policySets.id, { onDelete: 'cascade' }),
|
||||
sort: integer('sort').notNull().default(0),
|
||||
},
|
||||
(t) => ({
|
||||
pk: uniqueIndex('idx_agent_policy_sets_pk').on(t.agentId, t.setId),
|
||||
setIdx: index('idx_agent_policy_sets_set').on(t.setId),
|
||||
}),
|
||||
)
|
||||
|
||||
export const policyRules = sqliteTable(
|
||||
'policy_rules',
|
||||
{
|
||||
id: text('id').primaryKey(),
|
||||
agentId: text('agent_id').references(() => agents.id, { onDelete: 'cascade' }), // null = tenant default
|
||||
setId: text('set_id')
|
||||
.notNull()
|
||||
.references(() => policySets.id, { onDelete: 'cascade' }),
|
||||
priority: integer('priority').notNull(),
|
||||
action: text('action').notNull(), // allow | deny
|
||||
listId: text('list_id').references(() => ipLists.id, { onDelete: 'cascade' }),
|
||||
cidr: text('cidr'),
|
||||
hostname: text('hostname'),
|
||||
comment: text('comment'),
|
||||
createdByUserId: text('created_by_user_id'),
|
||||
createdAt: text('created_at')
|
||||
@@ -97,7 +131,26 @@ export const policyRules = sqliteTable(
|
||||
.default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`),
|
||||
},
|
||||
(t) => ({
|
||||
agentPriority: uniqueIndex('idx_policy_rules_agent_priority').on(t.agentId, t.priority),
|
||||
setPriority: uniqueIndex('idx_policy_rules_set_priority').on(t.setId, t.priority),
|
||||
}),
|
||||
)
|
||||
|
||||
/** DNS resolve cache for hostname rules. */
|
||||
export const policyRuleResolved = sqliteTable(
|
||||
'policy_rule_resolved',
|
||||
{
|
||||
id: text('id').primaryKey(),
|
||||
ruleId: text('rule_id')
|
||||
.notNull()
|
||||
.references(() => policyRules.id, { onDelete: 'cascade' }),
|
||||
cidr: text('cidr').notNull(),
|
||||
resolvedAt: text('resolved_at')
|
||||
.notNull()
|
||||
.default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`),
|
||||
},
|
||||
(t) => ({
|
||||
ruleCidr: uniqueIndex('idx_policy_rule_resolved_rule_cidr').on(t.ruleId, t.cidr),
|
||||
ruleIdx: index('idx_policy_rule_resolved_rule').on(t.ruleId),
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -141,12 +194,17 @@ export const agentStatsSamples = sqliteTable(
|
||||
}),
|
||||
)
|
||||
|
||||
export const SHARED_POLICY_SET_ID = 'set-shared-default'
|
||||
|
||||
export const schema = {
|
||||
settings,
|
||||
agents,
|
||||
ipLists,
|
||||
ipListEntries,
|
||||
policySets,
|
||||
agentPolicySets,
|
||||
policyRules,
|
||||
policyRuleResolved,
|
||||
ipOverrides,
|
||||
agentStatsSamples,
|
||||
}
|
||||
|
||||
@@ -51,16 +51,29 @@ export const ipListSchema = z.object({
|
||||
|
||||
export const policyRuleSchema = z.object({
|
||||
id: z.string(),
|
||||
agent_id: z.string().nullable().optional(),
|
||||
set_id: z.string(),
|
||||
priority: z.number().int(),
|
||||
action: policyActionSchema,
|
||||
list_id: z.string().nullable().optional(),
|
||||
cidr: z.string().nullable().optional(),
|
||||
hostname: z.string().nullable().optional(),
|
||||
resolved_count: z.number().int().optional(),
|
||||
comment: z.string().nullable().optional(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
})
|
||||
|
||||
export const policySetSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string().nullable().optional(),
|
||||
enabled: z.boolean(),
|
||||
rules_count: z.number().int().optional(),
|
||||
agents_count: z.number().int().optional(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
})
|
||||
|
||||
export const ipOverrideSchema = z.object({
|
||||
id: z.string(),
|
||||
agent_id: z.string(),
|
||||
@@ -77,13 +90,42 @@ export const createIpListBodySchema = z.object({
|
||||
entries: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
export const createPolicyRuleBodySchema = z.object({
|
||||
agent_id: z.string().nullable().optional(),
|
||||
priority: z.number().int().min(1).max(10000),
|
||||
action: policyActionSchema,
|
||||
list_id: z.string().nullable().optional(),
|
||||
cidr: z.string().nullable().optional(),
|
||||
comment: z.string().nullable().optional(),
|
||||
export const createPolicySetBodySchema = z.object({
|
||||
name: z.string().min(1),
|
||||
description: z.string().nullable().optional(),
|
||||
enabled: z.boolean().optional().default(true),
|
||||
})
|
||||
|
||||
export const patchPolicySetBodySchema = z.object({
|
||||
name: z.string().min(1).optional(),
|
||||
description: z.string().nullable().optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const createPolicyRuleBodySchema = z
|
||||
.object({
|
||||
set_id: z.string().min(1),
|
||||
priority: z.number().int().min(1).max(10000),
|
||||
action: policyActionSchema,
|
||||
list_id: z.string().nullable().optional(),
|
||||
cidr: z.string().nullable().optional(),
|
||||
hostname: z.string().nullable().optional(),
|
||||
comment: z.string().nullable().optional(),
|
||||
})
|
||||
.superRefine((v, ctx) => {
|
||||
const sources = [v.list_id, v.cidr, v.hostname].filter(
|
||||
(x) => typeof x === 'string' && x.trim().length > 0,
|
||||
)
|
||||
if (sources.length !== 1) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Укажите ровно один источник: list_id, cidr или hostname',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export const putAgentPolicySetsBodySchema = z.object({
|
||||
set_ids: z.array(z.string()),
|
||||
})
|
||||
|
||||
export const createOverrideBodySchema = z.object({
|
||||
@@ -142,6 +184,7 @@ export const dashboardStatsSchema = z.object({
|
||||
export type Agent = z.infer<typeof agentSchema>
|
||||
export type IpList = z.infer<typeof ipListSchema>
|
||||
export type PolicyRule = z.infer<typeof policyRuleSchema>
|
||||
export type PolicySet = z.infer<typeof policySetSchema>
|
||||
export type IpOverride = z.infer<typeof ipOverrideSchema>
|
||||
export type AgentPolicy = z.infer<typeof agentPolicySchema>
|
||||
export type DashboardStats = z.infer<typeof dashboardStatsSchema>
|
||||
|
||||
Reference in New Issue
Block a user