diff --git a/.env.example b/.env.example index 59c7535..54809c2 100644 --- a/.env.example +++ b/.env.example @@ -3,3 +3,6 @@ AUTH_DOMAIN=auth.lab.cazzzer.com AUTH_CLIENT_ID= AUTH_CLIENT_SECRET= AUTH_REDIRECT_URI=http://localhost:5173/auth/authentik/callback +OPNSENSE_API_URL=https://opnsense.home +OPNSENSE_API_KEY= +OPNSENSE_API_SECRET= diff --git a/bruno/bruno.json b/bruno/bruno.json new file mode 100644 index 0000000..d9ce628 --- /dev/null +++ b/bruno/bruno.json @@ -0,0 +1,9 @@ +{ + "version": "1", + "name": "vpgen", + "type": "collection", + "ignore": [ + "node_modules", + ".git" + ] +} \ No newline at end of file diff --git a/bruno/collection.bru b/bruno/collection.bru new file mode 100644 index 0000000..d5edef6 --- /dev/null +++ b/bruno/collection.bru @@ -0,0 +1,8 @@ +auth { + mode: basic +} + +auth:basic { + username: {{opnsense_key}} + password: {{opnsense_secret}} +} diff --git a/bruno/environments/dev.bru b/bruno/environments/dev.bru new file mode 100644 index 0000000..ccf903c --- /dev/null +++ b/bruno/environments/dev.bru @@ -0,0 +1,6 @@ +vars { + opnsense_key: 33NhXqaJwrWy1T4Qi60GK90RXJuS3PWIYwlwYPnQ8f5YPe/J1q/g6/l4bZ2/kJk71MFhwP+9mr+IiQPi +} +vars:secret [ + opnsense_secret +] diff --git a/bruno/opnsense-api/Get Interfaces.bru b/bruno/opnsense-api/Get Interfaces.bru new file mode 100644 index 0000000..a2780a3 --- /dev/null +++ b/bruno/opnsense-api/Get Interfaces.bru @@ -0,0 +1,28 @@ +meta { + name: Get Interfaces + type: http + seq: 2 +} + +post { + url: https://opnsense.home/api/wireguard/service/show + body: json + auth: inherit +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "current": 1, + "rowCount": 7, + "sort": {}, + "searchPhrase": "", + "type": [ + "interface" + ] + } +} diff --git a/bruno/opnsense-api/Get Peers.bru b/bruno/opnsense-api/Get Peers.bru new file mode 100644 index 0000000..dbe024f --- /dev/null +++ b/bruno/opnsense-api/Get Peers.bru @@ -0,0 +1,28 @@ +meta { + name: Get Peers + type: http + seq: 1 +} + +post { + url: https://opnsense.home/api/wireguard/service/show + body: json + auth: inherit +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "current": 1, + "rowCount": 7, + "sort": {}, + "searchPhrase": "", + "type": [ + "peer" + ] + } +} diff --git a/src/hooks.server.ts b/src/hooks.server.ts index c04ae53..ae3130b 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -34,6 +34,7 @@ const handleAuth: Handle = async ({ event, resolve }) => { const authRequired = new Set([ '/user', '/connections', + '/api/connections', ]); const handleProtectedPaths: Handle = ({ event, resolve }) => { if (authRequired.has(event.url.pathname) && !event.locals.user) { diff --git a/src/lib/opnsense/wg.ts b/src/lib/opnsense/wg.ts new file mode 100644 index 0000000..59872fd --- /dev/null +++ b/src/lib/opnsense/wg.ts @@ -0,0 +1,56 @@ +export interface PeerRow { + if: string; + type: "peer"; + 'public-key': string; + endpoint: string; + 'allowed-ips': string; + 'latest-handshake': number; + 'transfer-rx': number; + 'transfer-tx': number; + 'persistent-keepalive': string; + name: string; + ifname: string; +} + +export interface OpnsenseWgPeers { + total: number; + rowCount: number; + current: number; + rows: PeerRow[]; +} + +// Sample request to OPNsense WireGuard API +// const url = 'https://opnsense.home/api/wireguard/service/show'; +// const options = { +// method: 'POST', +// headers: { +// Authorization: 'Basic ...', +// 'Content-Type': 'application/json', +// Accept: 'application/json', +// 'content-type': 'application/json' +// }, +// body: '{"current":1,"rowCount":7,"sort":{},"searchPhrase":"","type":["peer"]}' +// }; + + +// Sample response from OPNsense WireGuard API +// { +// "total": 17, +// "rowCount": 7, +// "current": 1, +// "rows": [ +// { +// "if": "wg0", +// "type": "peer", +// "public-key": "iJa5JmJbMHNlbEluNwoB2Q8LyrPAfb7S/mluanMcI08=", +// "endpoint": "10.17.20.107:42516", +// "allowed-ips": "fd00::1/112,10.6.0.3/32", +// "latest-handshake": 1729319339, +// "transfer-rx": 1052194743, +// "transfer-tx": 25203263456, +// "persistent-keepalive": "off", +// "name": "Yura-TPX13", +// "ifname": "wg0" +// } +// ] +// } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 6a6b74c..8fbaf53 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -5,16 +5,20 @@ const { data, children } = $props(); const { user } = data; + + function getNavClass(path: string) { + return cn("hover:text-foreground/80 transition-colors", + $page.url.pathname === path ? "text-foreground" : "text-foreground/60"); + }
My App
diff --git a/src/routes/api/connections/+server.ts b/src/routes/api/connections/+server.ts new file mode 100644 index 0000000..67b9cf4 --- /dev/null +++ b/src/routes/api/connections/+server.ts @@ -0,0 +1,32 @@ +import { error } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { env } from '$env/dynamic/private'; +import type { OpnsenseWgPeers } from '$lib/opnsense/wg'; + +export const GET: RequestHandler = async ({ url }) => { + const apiUrl = `${env.OPNSENSE_API_URL}/api/wireguard/service/show`; + const options: RequestInit = { + method: 'POST', + headers: { + Authorization: `Basic ${Buffer.from(`${env.OPNSENSE_API_KEY}:${env.OPNSENSE_API_SECRET}`).toString('base64')}`, + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + "current": 1, + // "rowCount": 7, + "sort": {}, + "searchPhrase": "", + "type": ["peer"], + }), + }; + console.log("Fetching peers from OPNsense WireGuard API: ", apiUrl, options) + + const res = await fetch(apiUrl, options); + const peers = await res.json() as OpnsenseWgPeers; + + if (!peers) { + error(500, "Error getting info from OPNsense API"); + } + return new Response(JSON.stringify(peers)); +}; diff --git a/src/routes/connections/+page.svelte b/src/routes/connections/+page.svelte new file mode 100644 index 0000000..6084fc7 --- /dev/null +++ b/src/routes/connections/+page.svelte @@ -0,0 +1,66 @@ + + + + Connections + + + + + + + + + + + + + + + + + + {#each data.peers.rows as peer} + + + + + {#if peer['latest-handshake']} + + {:else} + + {/if} + + + + + + + {/each} + +
Public KeyEndpointAllowed IPsLatest HandshakeRXTXPersistent KeepaliveNameInterface Name
{peer['public-key'].substring(0, 10)}{peer.endpoint}{peer['allowed-ips']}{new Date(peer['latest-handshake'] * 1000)}Never{getSize(peer['transfer-rx'])}{getSize(peer['transfer-tx'])}{peer['persistent-keepalive']}{peer.name}{peer.ifname}
diff --git a/src/routes/connections/+page.ts b/src/routes/connections/+page.ts new file mode 100644 index 0000000..5aa0bc0 --- /dev/null +++ b/src/routes/connections/+page.ts @@ -0,0 +1,9 @@ +import type { PageLoad } from './$types'; +import type { OpnsenseWgPeers } from '$lib/opnsense/wg'; + +export const load: PageLoad = async ({ fetch, params }) => { + const res = await fetch('/api/connections'); + const peers = await res.json() as OpnsenseWgPeers; + + return { peers }; +};