Make mesh verification universal (CIDR check, not NetBird-specific)

Replace the NetBird-adapter-based "reachable" check with a vendor-agnostic
one: the admin supplies the mesh's IP range (CIDR), and verification just
confirms this host has an address inside it. Works identically for
NetBird, WireGuard, ZeroTier, Tailscale, or any other mesh tech, with no
integration record or vendor API call required.
This commit is contained in:
Claude 2026-06-20 21:22:06 +00:00
parent 46d95fca61
commit 0409159327
No known key found for this signature in database
3 changed files with 55 additions and 59 deletions

View file

@ -1,27 +1,34 @@
import type { FastifyInstance } from 'fastify'
import { networkInterfaces } from 'node:os'
import { z } from 'zod'
import { db, getConfig, setConfig, logEvent } from '../db/index.js'
import { loadSecrets } from '../db/secrets.js'
import { adapterRegistry } from '../integrations/registry.js'
import type { IntegrationType } from '../integrations/types.js'
import { getConfig, setConfig, logEvent } from '../db/index.js'
interface IntegrationRow {
id: number
type: string
name: string
config_json: string
/** Parses "a.b.c.d/n" into a 32-bit base int + prefix length. */
function parseCidr(cidr: string): { base: number; prefix: number } | null {
const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/(\d{1,2})$/.exec(cidr.trim())
if (!match) return null
const octets = match.slice(1, 5).map(Number)
const prefix = Number(match[5])
if (octets.some((o) => o < 0 || o > 255) || prefix < 0 || prefix > 32) return null
const base = (octets[0]! << 24) | (octets[1]! << 16) | (octets[2]! << 8) | octets[3]!
return { base, prefix }
}
/** NetBird's default CGNAT mesh range (100.64.0.0/10) — informational only (DECIDE B2). */
function detectHostMeshIp(): string | null {
function ipToInt(ip: string): number | null {
const parts = ip.split('.').map(Number)
if (parts.length !== 4 || parts.some((p) => Number.isNaN(p) || p < 0 || p > 255)) return null
return (parts[0]! << 24) | (parts[1]! << 16) | (parts[2]! << 8) | parts[3]!
}
/** Finds this host's own IPv4 address that falls within the given mesh CIDR, if any. */
function findHostIpInCidr(cidr: { base: number; prefix: number }): string | null {
const mask = cidr.prefix === 0 ? 0 : (-1 << (32 - cidr.prefix)) >>> 0
for (const addrs of Object.values(networkInterfaces())) {
for (const addr of addrs ?? []) {
if (addr.family !== 'IPv4') continue
const parts = addr.address.split('.').map(Number)
if (parts[0] === 100 && parts[1] !== undefined && parts[1] >= 64 && parts[1] <= 127) {
return addr.address
}
const ipInt = ipToInt(addr.address)
if (ipInt === null) continue
if (((ipInt & mask) >>> 0) === ((cidr.base & mask) >>> 0)) return addr.address
}
}
return null
@ -29,56 +36,53 @@ function detectHostMeshIp(): string | null {
function meshStatusPayload() {
const required = getConfig('mesh.required') === 'true'
const meshIntegrationId = getConfig('mesh.integrationId')
const cidr = getConfig('mesh.cidr') ?? null
const verifiedAt = getConfig('mesh.verifiedAt')
const overridden = getConfig('mesh.overrideUntil') === 'permanent'
const parsed = cidr ? parseCidr(cidr) : null
return {
required,
verified: !!verifiedAt,
verifiedAt: verifiedAt ?? null,
overridden,
meshIntegrationId: meshIntegrationId ? Number(meshIntegrationId) : null,
hostMeshIp: detectHostMeshIp(),
cidr,
hostMeshIp: parsed ? findHostIpInCidr(parsed) : null,
}
}
const verifySchema = z.object({ integrationId: z.number().int() })
const verifySchema = z.object({ cidr: z.string().min(1) })
export async function systemRoutes(app: FastifyInstance) {
app.get('/api/system/mesh-status', { onRequest: [app.authenticate] }, async () => {
return meshStatusPayload()
})
// Verification is mesh-tech-agnostic: any overlay network (NetBird, WireGuard,
// ZeroTier, Tailscale...) assigns this host an IP in its own range. We just check
// the host has an address inside the admin-supplied CIDR — no vendor API needed.
app.post('/api/system/mesh/verify', { onRequest: [app.requireAdmin] }, async (req, reply) => {
const parsed = verifySchema.safeParse(req.body)
if (!parsed.success) {
return reply.code(400).send({ error: parsed.error.issues[0]?.message ?? 'Invalid input' })
}
const row = db.prepare('SELECT * FROM integrations WHERE id = ? AND type = ?').get(
parsed.data.integrationId,
'netbird'
) as IntegrationRow | undefined
if (!row) return reply.code(404).send({ error: 'NetBird integration not found' })
const cidr = parseCidr(parsed.data.cidr)
if (!cidr) return reply.code(400).send({ error: 'Invalid CIDR — expected format like 100.64.0.0/10' })
const adapter = adapterRegistry[row.type as IntegrationType]
const config = JSON.parse(row.config_json)
const secrets = loadSecrets(row.id)
const result = await adapter.testConnection(config, secrets)
const hostMeshIp = findHostIpInCidr(cidr)
setConfig('mesh.cidr', parsed.data.cidr)
db.prepare("UPDATE integrations SET status = ?, last_checked_at = datetime('now') WHERE id = ?").run(
result.ok ? 'connected' : 'error',
row.id
)
if (result.ok) {
setConfig('mesh.integrationId', String(row.id))
if (hostMeshIp) {
setConfig('mesh.verifiedAt', new Date().toISOString())
logEvent('mesh_verified', `Mesh verified via ${row.name}`, row.type)
logEvent('mesh_verified', `Mesh verified — host has ${hostMeshIp} in ${parsed.data.cidr}`)
} else {
logEvent('mesh_verify_failed', `Mesh verification failed via ${row.name}: ${result.message}`, row.type)
logEvent('mesh_verify_failed', `Mesh verification failed — no host IP found in ${parsed.data.cidr}`)
}
return { ...result, hostMeshIp: detectHostMeshIp() }
return {
ok: !!hostMeshIp,
message: hostMeshIp ? `Host is on the mesh (${hostMeshIp})` : 'No host IP found in that range',
hostMeshIp,
}
})
app.post('/api/system/mesh/override', { onRequest: [app.requireAdmin] }, async (req) => {

View file

@ -59,10 +59,10 @@ export const api = {
listLoginEvents: (limit = 20) => apiFetch<{ events: LoginEvent[] }>(`/auth/login-events?limit=${limit}`),
getMeshStatus: () => apiFetch<MeshStatus>('/system/mesh-status'),
verifyMesh: (integrationId: number) =>
verifyMesh: (cidr: string) =>
apiFetch<{ ok: boolean; message: string; hostMeshIp: string | null }>('/system/mesh/verify', {
method: 'POST',
body: JSON.stringify({ integrationId }),
body: JSON.stringify({ cidr }),
}),
overrideMesh: () => apiFetch<MeshStatus>('/system/mesh/override', { method: 'POST' }),
setMeshRequired: (required: boolean) =>
@ -252,7 +252,7 @@ export interface MeshStatus {
verified: boolean
verifiedAt: string | null
overridden: boolean
meshIntegrationId: number | null
cidr: string | null
hostMeshIp: string | null
}

View file

@ -43,8 +43,7 @@ const goldButton: React.CSSProperties = {
export default function MeshGate() {
const { refreshMeshStatus } = useAuth()
const [baseUrl, setBaseUrl] = useState('')
const [apiKey, setApiKey] = useState('')
const [cidr, setCidr] = useState('')
const [error, setError] = useState<string | null>(null)
const [testResult, setTestResult] = useState<{ ok: boolean; message: string; hostMeshIp: string | null } | null>(null)
const [submitting, setSubmitting] = useState(false)
@ -56,13 +55,7 @@ export default function MeshGate() {
setTestResult(null)
setSubmitting(true)
try {
const { integration } = await api.createIntegration({
type: 'netbird',
name: 'NetBird',
config: baseUrl ? { baseUrl } : {},
secrets: apiKey ? { apiKey } : {},
})
const result = await api.verifyMesh(integration.id)
const result = await api.verifyMesh(cidr)
setTestResult(result)
if (result.ok) await refreshMeshStatus()
} catch (err) {
@ -103,21 +96,20 @@ export default function MeshGate() {
</h1>
</div>
<p style={{ fontSize: '12px', color: '#7A7D85', marginBottom: '24px' }}>
ArchNest expects a verified NetBird mesh before the rest of the app can be configured.
Connect your mesh below to continue.
ArchNest expects this host to be on a private mesh network before the rest of the app
can be configured. Works with any mesh NetBird, WireGuard, ZeroTier, Tailscale, etc.
Enter the mesh's IP range below; we verify by checking this host has an address in it.
</p>
<label style={fieldLabel}>NetBird Base URL</label>
<label style={fieldLabel}>Mesh Network CIDR</label>
<input
style={fieldInput}
value={baseUrl}
onChange={(e) => setBaseUrl(e.target.value)}
placeholder="https://api.netbird.io"
value={cidr}
onChange={(e) => setCidr(e.target.value)}
placeholder="e.g. 100.64.0.0/10 (NetBird/Tailscale CGNAT range)"
required
/>
<label style={{ ...fieldLabel, marginTop: '14px' }}>API Key</label>
<input style={fieldInput} type="password" value={apiKey} onChange={(e) => setApiKey(e.target.value)} required />
{error && <p style={{ fontSize: '12px', color: '#E74C3C', marginTop: '12px' }}>{error}</p>}
{testResult && (
<p style={{ fontSize: '12px', color: testResult.ok ? '#2ECC71' : '#E74C3C', marginTop: '12px' }}>