feat(api): update mikrotik-install script for JSON policy handling
- Refactored the `mikrotik-install.rsc` script to fetch and process the agent policy as JSON instead of using the previous `.rsc` format, improving reliability and performance during policy synchronization. - Introduced a new global variable `EvofwLastHash` to track the hash of the fetched policy, enabling efficient updates and preventing unnecessary changes. - Updated the logging and error handling to provide clearer insights during the synchronization process. - Adjusted related test assertions in `install-links.test.ts` to reflect the new policy endpoint and ensure accurate testing of the installation logic. - Enhanced documentation in `agents.md` to clarify the new synchronization method and its implications for agent configuration. These changes enhance the robustness and clarity of the agent installation and synchronization process, ensuring better performance and user experience.
This commit is contained in:
@@ -4,14 +4,15 @@
|
||||
#
|
||||
# Re-import on an already-enrolled router: skips enroll, refreshes sync + scheduler (token kept).
|
||||
#
|
||||
# Blacklist: drop EVOFW_DENY on input+forward
|
||||
# Whitelist: accept EVOFW_ALLOW + drop others on forward only (input stays open for Winbox/SSH)
|
||||
# Sync uses GET /v1/agent/policy (JSON) + address-list rebuild — not /import of huge .rsc
|
||||
# (large /import often fails silently while apply-report still looks "ok").
|
||||
|
||||
:global EvofwCpUrl
|
||||
:global EvofwSeed
|
||||
:global EvofwName
|
||||
:global EvofwInstallLinkId
|
||||
:global EvofwToken
|
||||
:global EvofwLastHash
|
||||
|
||||
:if ([:typeof $EvofwCpUrl] = "nothing" || [:len $EvofwCpUrl] = 0) do={ :error "EvofwCpUrl required" }
|
||||
:if ([:typeof $EvofwSeed] = "nothing" || [:len $EvofwSeed] = 0) do={ :error "EvofwSeed required" }
|
||||
@@ -30,7 +31,7 @@
|
||||
:set token ("evofw_" . [:tostr [/system clock get time]] . [:tostr [/system resource get cpu-load]] . [:tostr [/system resource get free-memory]] . [:tostr [:rndnum from=100000 to=999999]])
|
||||
}
|
||||
|
||||
:local body ("{\"name\":\"" . $EvofwName . "\",\"hostname\":\"" . [/system identity get name] . "\",\"platform\":\"mikrotik\",\"token\":\"" . $token . "\",\"client_version\":\"rsc/1\"")
|
||||
:local body ("{\"name\":\"" . $EvofwName . "\",\"hostname\":\"" . [/system identity get name] . "\",\"platform\":\"mikrotik\",\"token\":\"" . $token . "\",\"client_version\":\"rsc/2\"")
|
||||
:if ([:typeof $EvofwInstallLinkId] != "nothing" && [:len $EvofwInstallLinkId] > 0) do={
|
||||
:set body ($body . ",\"install_link_id\":\"" . $EvofwInstallLinkId . "\"")
|
||||
}
|
||||
@@ -43,15 +44,17 @@
|
||||
}
|
||||
|
||||
:do { /system script remove [find name="evofw-env"] } on-error={}
|
||||
/system script add name=evofw-env policy=read,write,policy,test source=(" :global EvofwCpUrl \"" . $EvofwCpUrl . "\"; :global EvofwToken \"" . $token . "\" ")
|
||||
/system script add name=evofw-env policy=read,write,policy,test source=(" :global EvofwCpUrl \"" . $EvofwCpUrl . "\"; :global EvofwToken \"" . $token . "\"; :global EvofwLastHash \"\" ")
|
||||
:set EvofwToken $token
|
||||
:set EvofwLastHash ""
|
||||
} else={
|
||||
:put "evofw: already enrolled — updating sync script (token kept)"
|
||||
:put "evofw: already enrolled — updating sync script (token kept); forcing full list rebuild"
|
||||
:set EvofwLastHash ""
|
||||
:do { /system script remove [find name="evofw-env"] } on-error={}
|
||||
/system script add name=evofw-env policy=read,write,policy,test source=(" :global EvofwCpUrl \"" . $EvofwCpUrl . "\"; :global EvofwToken \"" . $EvofwToken . "\" ")
|
||||
/system script add name=evofw-env policy=read,write,policy,test source=(" :global EvofwCpUrl \"" . $EvofwCpUrl . "\"; :global EvofwToken \"" . $EvofwToken . "\"; :global EvofwLastHash \"\" ")
|
||||
}
|
||||
|
||||
# Filter rules (idempotent by comment) — names must match policy.rsc (mikrotik-rsc.ts)
|
||||
# Filter rules (idempotent by comment) — names must match policy toggles below
|
||||
:do { /ip firewall filter remove [find comment~"^evofw-"] } on-error={}
|
||||
|
||||
/ip firewall filter add chain=input action=drop src-address-list=EVOFW_DENY comment=evofw-deny-drop-input disabled=no
|
||||
@@ -59,11 +62,12 @@
|
||||
/ip firewall filter add chain=forward action=accept src-address-list=EVOFW_ALLOW comment=evofw-allow-accept-forward disabled=no
|
||||
/ip firewall filter add chain=forward action=drop comment=evofw-default-drop-forward disabled=yes
|
||||
|
||||
# Sync: fetch policy.rsc → import address-lists + toggle mode
|
||||
# Sync: JSON policy → rebuild address-lists (reliable for ~1k CIDRs; no nested /import)
|
||||
:do { /system script remove [find name="evofw-sync"] } on-error={}
|
||||
/system script add name=evofw-sync policy=read,write,policy,test source={
|
||||
:global EvofwCpUrl
|
||||
:global EvofwToken
|
||||
:global EvofwLastHash
|
||||
:if ([:typeof $EvofwCpUrl] = "nothing" || [:typeof $EvofwToken] = "nothing") do={
|
||||
/system script run evofw-env
|
||||
}
|
||||
@@ -71,23 +75,67 @@
|
||||
:log error "evofw: missing EvofwCpUrl/EvofwToken"
|
||||
:error "evofw env missing"
|
||||
}
|
||||
:if ([:typeof $EvofwLastHash] = "nothing") do={ :set EvofwLastHash "" }
|
||||
|
||||
:local syncOk 0
|
||||
:do {
|
||||
/tool fetch url=($EvofwCpUrl . "/v1/agent/policy.rsc") http-header-field=("Authorization: Bearer " . $EvofwToken) dst-path=evofw-policy.rsc
|
||||
/import file-name=evofw-policy.rsc
|
||||
:local result [/tool fetch url=($EvofwCpUrl . "/v1/agent/policy") http-header-field=("Authorization: Bearer " . $EvofwToken . ",Accept: application/json") output=user as-value]
|
||||
:if (($result->"status") != "finished") do={
|
||||
:log warning ("evofw: policy fetch status=" . ($result->"status"))
|
||||
:error "policy fetch failed"
|
||||
}
|
||||
:local raw ($result->"data")
|
||||
:if ([:len $raw] < 10) do={ :error "empty policy body" }
|
||||
:local p [:deserialize $raw from=json]
|
||||
:local hash ($p->"hash")
|
||||
:if ([:typeof $hash] = "nothing" || [:len $hash] = 0) do={ :error "policy missing hash (pending?)" }
|
||||
|
||||
:if ($hash = $EvofwLastHash) do={
|
||||
:log info ("evofw: unchanged " . $hash)
|
||||
:set syncOk 1
|
||||
} else={
|
||||
:local deny ($p->"deny_cidrs")
|
||||
:local allow ($p->"allow_cidrs")
|
||||
:do { /ip firewall address-list remove [find list=EVOFW_DENY] } on-error={}
|
||||
:do { /ip firewall address-list remove [find list=EVOFW_ALLOW] } on-error={}
|
||||
:foreach a in=$deny do={
|
||||
:do { /ip firewall address-list add list=EVOFW_DENY address=$a comment=evofw } on-error={}
|
||||
}
|
||||
:foreach a in=$allow do={
|
||||
:do { /ip firewall address-list add list=EVOFW_ALLOW address=$a comment=evofw } on-error={}
|
||||
}
|
||||
:local da ($p->"default_action")
|
||||
:do { /ip firewall filter set [find comment=evofw-deny-drop-input] disabled=no } on-error={}
|
||||
:do { /ip firewall filter set [find comment=evofw-deny-drop-forward] disabled=no } on-error={}
|
||||
:do { /ip firewall filter set [find comment=evofw-allow-accept-forward] disabled=no } on-error={}
|
||||
:if ($da = "drop") do={
|
||||
:do { /ip firewall filter set [find comment=evofw-default-drop-forward] disabled=no } on-error={}
|
||||
} else={
|
||||
:do { /ip firewall filter set [find comment=evofw-default-drop-forward] disabled=yes } on-error={}
|
||||
}
|
||||
:set EvofwLastHash $hash
|
||||
:do { /system script remove [find name="evofw-env"] } on-error={}
|
||||
/system script add name=evofw-env policy=read,write,policy,test source=(" :global EvofwCpUrl \"" . $EvofwCpUrl . "\"; :global EvofwToken \"" . $EvofwToken . "\"; :global EvofwLastHash \"" . $hash . "\" ")
|
||||
:log info ("evofw: applied " . $hash)
|
||||
:set syncOk 1
|
||||
}
|
||||
} on-error={
|
||||
:log warning "evofw: policy sync failed (pending approval or network)"
|
||||
:log warning "evofw: policy sync failed (pending approval, network, or JSON)"
|
||||
}
|
||||
|
||||
:if ($syncOk = 1) do={
|
||||
:local denyCnt [:len [/ip firewall address-list find list=EVOFW_DENY]]
|
||||
:local allowCnt [:len [/ip firewall address-list find list=EVOFW_ALLOW]]
|
||||
:local cnt ($denyCnt + $allowCnt)
|
||||
:local report ("{\"status\":\"ok\",\"prefix_count\":" . $cnt . ",\"kernel_method\":\"address-list\",\"source\":\"mikrotik\"}")
|
||||
:do {
|
||||
/tool fetch url=($EvofwCpUrl . "/v1/agent/apply-report") http-method=post http-header-field=("Authorization: Bearer " . $EvofwToken . ",Content-Type: application/json") http-data=$report keep-result=no
|
||||
} on-error={}
|
||||
:do {
|
||||
/tool fetch url=($EvofwCpUrl . "/v1/agent/heartbeat") http-method=post http-header-field=("Authorization: Bearer " . $EvofwToken . ",Content-Type: application/json") http-data="{\"source\":\"mikrotik\"}" keep-result=no
|
||||
} on-error={}
|
||||
:log info ("evofw: sync done deny=" . $denyCnt . " allow=" . $allowCnt)
|
||||
}
|
||||
:local denyCnt [:len [/ip firewall address-list find list=EVOFW_DENY]]
|
||||
:local allowCnt [:len [/ip firewall address-list find list=EVOFW_ALLOW]]
|
||||
:local cnt ($denyCnt + $allowCnt)
|
||||
:local report ("{\"status\":\"ok\",\"prefix_count\":" . $cnt . ",\"kernel_method\":\"address-list\",\"source\":\"mikrotik\"}")
|
||||
:do {
|
||||
/tool fetch url=($EvofwCpUrl . "/v1/agent/apply-report") http-method=post http-header-field=("Authorization: Bearer " . $EvofwToken . ",Content-Type: application/json") http-data=$report keep-result=no
|
||||
} on-error={}
|
||||
:do {
|
||||
/tool fetch url=($EvofwCpUrl . "/v1/agent/heartbeat") http-method=post http-header-field=("Authorization: Bearer " . $EvofwToken . ",Content-Type: application/json") http-data="{\"source\":\"mikrotik\"}" keep-result=no
|
||||
} on-error={}
|
||||
:log info ("evofw: sync done deny=" . $denyCnt . " allow=" . $allowCnt)
|
||||
}
|
||||
|
||||
:do { /system scheduler remove [find name="evofw-sync"] } on-error={}
|
||||
|
||||
@@ -141,7 +141,8 @@ describe('install-links', () => {
|
||||
expect(byId.body).toContain(':global EvofwCpUrl "https://fw.example.com"')
|
||||
expect(byId.body).toContain(`:global EvofwInstallLinkId "${body.id}"`)
|
||||
expect(byId.body).toContain('evofw-deny-drop-input')
|
||||
expect(byId.body).toContain('/v1/agent/policy.rsc')
|
||||
expect(byId.body).toContain('/v1/agent/policy')
|
||||
expect(byId.body).toContain(':deserialize')
|
||||
})
|
||||
|
||||
it('approved agent can fetch empty policy without rule sets', async () => {
|
||||
|
||||
+3
-1
@@ -67,7 +67,9 @@ Install RSC:
|
||||
|
||||
1. Enroll (с `install_link_id` → агент Invited → Pending).
|
||||
2. Создаёт filter-правила `evofw-*` и address-list `EVOFW_DENY` / `EVOFW_ALLOW`.
|
||||
3. Scheduler `evofw-sync` каждую минуту: `GET /v1/agent/policy.rsc` → `/import` (списки + default).
|
||||
3. Scheduler `evofw-sync` каждую минуту: `GET /v1/agent/policy` (JSON) → rebuild address-list + toggle default. Не использует `/import` огромного `.rsc` (на больших списках часто падает молча).
|
||||
|
||||
Лог: `/log print where message~"evofw"`. Ручной sync: `/system script run evofw-sync`.
|
||||
|
||||
**Default action** задаётся на **агенте** (`default_action: accept | drop`):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user