// Settings: policies, built-ins, antispoofing, flowtable, probation. import { h, mount, field, select, checkbox, input, errorBox, readOnly } from "../ui.js"; import { get, change, can } from "../api.js"; import { rerender } from "../app.js"; import { listEditor, fmtSeconds, parseSeconds } from "../widgets.js"; const builtinText = [ ["loopback", "Accept everything on lo"], ["established", "Accept established and related connections (input and forward)"], ["drop_invalid", "Drop invalid packets (input and forward)"], ["icmpv6", "Accept the ICMPv6 IPv6 needs: errors, echo, neighbour discovery, MLD (RFC 4890)"], ["ping_internal", "Accept ping to the firewall from internal zones"], ["dhcpv6_client", "Accept DHCPv6 replies on external zones (your ISP's prefix delegation)"], ["antispoof", "Drop impossible sources arriving on external zones (below)"], ["mss_clamp", "Clamp TCP MSS to the route MTU on external zones (needed with PPPoE)"], ]; export async function render(el) { const [s, ifs] = await Promise.all([get("/settings"), get("/interfaces")]); const write = can("config", true); const pol = (v) => select([["drop", "drop"], ["accept", "accept"]], v); const input_ = pol(s.input_policy), forward = pol(s.forward_policy), output = pol(s.output_policy); const logDrops = checkbox("log what the input and forward policies drop (rate-limited; the Log page)", s.log_policy_drops); const bis = builtinText.map(([k, text]) => [k, checkbox(text, s.builtins[k])]); const own = listEditor(s.own_prefixes, { placeholder: "2001:db8::/48", addLabel: "Add prefix" }); const v4 = listEditor(s.antispoof_v4, { addLabel: "Add" }); const v6 = listEditor(s.antispoof_v6, { addLabel: "Add" }); const flow = listEditor(s.flowtable_devices, { suggestions: ifs.map((i) => i.name), placeholder: "wan", addLabel: "Add device" }); const probation = input({ value: fmtSeconds(s.probation_seconds || 120), class: "short" }); const manual = checkbox("a person confirms each change (otherwise the browser or hc that made it confirms it by itself, once it reaches Housecarl again over a new connection)", s.manual_confirm); const err = h("div"); const save = async (dry) => { mount(err); const p = parseSeconds(probation.value); if (p === null) { mount(err, h("div", { class: "msg err" }, "Write the probation as 2m or 90s.")); return; } const builtins = {}; for (const [k, c] of bis) builtins[k] = c.input.checked; try { const r = await change("PATCH", "/settings", { input_policy: input_.value, forward_policy: forward.value, output_policy: output.value, log_policy_drops: logDrops.input.checked, builtins, own_prefixes: own.value(), antispoof_v4: v4.value(), antispoof_v6: v6.value(), flowtable_devices: flow.value(), probation_seconds: p === 120 ? 0 : p, manual_confirm: manual.input.checked }, { query: dry ? { dry_run: 1 } : undefined }); if (!dry && (r.applied || r.staged)) rerender(); } catch (e) { mount(err, errorBox(e)); } }; mount(el, h("div", { class: "page-head" }, h("div", { class: "grow" }, h("h1", null, "Settings and built-ins"), h("div", { class: "muted" }, "What happens to packets no rule decided, and the rules Housecarl adds before yours."))), h("div", { class: "panel" }, h("div", { class: "form-grid" }, field("Input policy", input_, "Traffic to the firewall itself."), field("Forward policy", forward, "Traffic through it."), field("Output policy", output, "Traffic from it."), h("label"), logDrops, field("Built-ins", h("div", { class: "checks" }, bis.map(([, c]) => c))), field("Your prefixes", own, "Your public prefixes (your delegated IPv6 /48): they can't come from outside."), field("Antispoof IPv4", v4), field("Antispoof IPv6", v6), field("Flowtable", flow, "Forwarded TCP and UDP between these devices skip the rules once established (fast path). Exact names; none: off."), field("Probation", probation, "How long a change made over the network has to be confirmed before it is rolled back."), h("label"), manual), err, write ? h("div", { class: "actions form-actions" }, h("button", { class: "primary", onclick: () => save(false) }, "Save"), h("button", { onclick: () => save(true), title: "Check without saving" }, "Check")) : null)); if (!write) readOnly(el.querySelector(".panel")); }