initial opnsense connections page

This commit is contained in:
Yuri Tatishchev 2024-11-02 01:26:41 -07:00
parent 3b2ed4ddea
commit 922e4c0580
Signed by: CaZzzer
GPG Key ID: E0EBF441EA424369
12 changed files with 254 additions and 4 deletions

View File

@ -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=

9
bruno/bruno.json Normal file
View File

@ -0,0 +1,9 @@
{
"version": "1",
"name": "vpgen",
"type": "collection",
"ignore": [
"node_modules",
".git"
]
}

8
bruno/collection.bru Normal file
View File

@ -0,0 +1,8 @@
auth {
mode: basic
}
auth:basic {
username: {{opnsense_key}}
password: {{opnsense_secret}}
}

View File

@ -0,0 +1,6 @@
vars {
opnsense_key: 33NhXqaJwrWy1T4Qi60GK90RXJuS3PWIYwlwYPnQ8f5YPe/J1q/g6/l4bZ2/kJk71MFhwP+9mr+IiQPi
}
vars:secret [
opnsense_secret
]

View File

@ -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"
]
}
}

View File

@ -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"
]
}
}

View File

@ -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) {

56
src/lib/opnsense/wg.ts Normal file
View File

@ -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"
// }
// ]
// }

View File

@ -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");
}
</script>
<header class="p-4 sm:flex">
<span class=" mr-6 font-bold sm:inline-block">My App</span>
<nav class="flex items-center gap-6 text-sm">
<a href="/" class={cn("hover:text-foreground/80 transition-colors",
$page.url.pathname === "/" ? "text-foreground" : "text-foreground/60")}>Home</a>
<a href="/" class={getNavClass("/")}>Home</a>
{#if user}
<a href="/user" class={cn("hover:text-foreground/80 transition-colors",
$page.url.pathname === "/user" ? "text-foreground" : "text-foreground/60")}>Profile</a>
<a href="/user" class={getNavClass("/user")}>Profile</a>
<a href="/connections" class={getNavClass("/connections")}>Connections</a>
{/if}
</nav>
</header>

View File

@ -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));
};

View File

@ -0,0 +1,66 @@
<script lang="ts">
import type { PageData } from './$types';
import { invalidate } from '$app/navigation';
import { onMount } from 'svelte';
const { data }: { data: PageData } = $props();
onMount(() => {
// refresh every 5 seconds
setInterval(() => {
console.log('Refreshing connections');
invalidate('/api/connections');
}, 5000);
});
function getSize(size: number) {
let sizes = ['Bytes', 'KiB', 'MiB', 'GiB',
'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
for (let i = 1; i < sizes.length; i++) {
if (size < Math.pow(1024, i))
return (Math.round((size / Math.pow(
1024, i - 1)) * 100) / 100) + ' ' + sizes[i - 1];
}
return size;
}
</script>
<svelte:head>
<title>Connections</title>
</svelte:head>
<table>
<thead>
<tr>
<th>Public Key</th>
<th>Endpoint</th>
<th>Allowed IPs</th>
<th>Latest Handshake</th>
<th>RX</th>
<th>TX</th>
<th>Persistent Keepalive</th>
<th>Name</th>
<th>Interface Name</th>
</tr>
</thead>
<tbody>
{#each data.peers.rows as peer}
<tr>
<td>{peer['public-key'].substring(0, 10)}</td>
<td>{peer.endpoint}</td>
<td>{peer['allowed-ips']}</td>
{#if peer['latest-handshake']}
<td>{new Date(peer['latest-handshake'] * 1000)}</td>
{:else}
<td>Never</td>
{/if}
<td>{getSize(peer['transfer-rx'])}</td>
<td>{getSize(peer['transfer-tx'])}</td>
<td>{peer['persistent-keepalive']}</td>
<td>{peer.name}</td>
<td>{peer.ifname}</td>
</tr>
{/each}
</tbody>
</table>

View File

@ -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 };
};