Initial Commit

This commit is contained in:
Christian Risi
2025-06-28 17:07:53 +00:00
parent 779367a4b8
commit ddac609b97
36 changed files with 1373 additions and 80 deletions

14
src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

12
src/app.html Normal file
View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

0
src/db/.gitkeep Normal file
View File

7
src/hooks.server.ts Normal file
View File

@@ -0,0 +1,7 @@
import type { ServerInit } from '@sveltejs/kit';
export const init: ServerInit = async () => {
// TODO: initialize db
};

119
src/lib/classes/endpoint.ts Normal file
View File

@@ -0,0 +1,119 @@
import type { NginxProtocol } from "$lib/enums/protocols"
import { isPortAvailable, validatePort } from "$lib/utils/ports-utils"
// TODO: inherit from a super class
/**
* This class represents an SSL Termination Endpoint.
*
* While it's possible to create it directly, it is
* discouraged in favor of the Factory methods as it does
* more checks than this class
*/
export class SSLTerminationEndpoint {
public sslPort: number
public clearPort: number
public servicePort: number
public protocol: NginxProtocol
public certificateURI: string
public privateKeyURI: string
constructor(
sslPort: number,
clearPort: number,
servicePort: number,
protocol: NginxProtocol,
certificateURI: string,
privateKeyURI: string
) {
validatePort(sslPort)
validatePort(clearPort)
validatePort(servicePort)
// TODO: check for file existance
this.sslPort = sslPort
this.clearPort = clearPort
this.servicePort = servicePort
this.protocol = protocol
this.certificateURI = certificateURI
this.privateKeyURI = privateKeyURI
}
}
export class SSLTerminationEndpointFactory {
/**
* Creates an SSLEndpoint and chooses ports
* automatically
*
* @param servicePort
* @param protocol
* @param certificateURI
* @param privateKeyURI
*/
public static async createSSLEndpoint(
servicePort: number,
protocol: NginxProtocol,
certificateURI: string,
privateKeyURI: string
): Promise<SSLTerminationEndpoint>
public static async createSSLEndpoint(
servicePort: number,
protocol: NginxProtocol,
certificateURI: string,
privateKeyURI: string,
sslPort?: number,
clearPort?: number,
): Promise<SSLTerminationEndpoint> {
if (!sslPort) {
sslPort = await SSLTerminationEndpointFactory.generatePort()
}
if (!clearPort) {
clearPort = await SSLTerminationEndpointFactory.generatePort()
}
return new SSLTerminationEndpoint(
sslPort,
clearPort,
servicePort,
protocol,
certificateURI,
privateKeyURI
)
}
private static async generatePort() {
const MIN_PORT = 1025
const MAX_PORT = 65535
const MAX_RETRIES = 100
let counter = 0
while (counter < MAX_RETRIES) {
let candidatePort = Math.trunc(
Math.random() * (MAX_PORT - MIN_PORT) + MIN_PORT
)
if (await isPortAvailable(candidatePort)) {
return candidatePort
}
counter++
}
// UGLY: change this to a more specific error
throw Error(`Couldn't find an available port in less than ${MAX_RETRIES}`)
}
}

View File

@@ -0,0 +1,47 @@
import { DB_PATH } from "$lib/utils/constants";
import { Database, Statement, Statement } from "bun:sqlite";
export class SSLSnifferDatabase {
private static initialized = false
private db: Database
// UGLY: should be more flexible
constructor() {
this.db = SSLSnifferDatabase.initDatabase()
SSLSnifferDatabase.initialized = true
}
// This needs to be static as it doesn't need the object
private static initDatabase() {
const db = new Database(
DB_PATH,
{
create: true,
strict: true
}
)
// Improve performance
db.exec("PRAGMA journal_mode = WAL;");
return db
}
private closeDatabase(graceful?: boolean) {
if (!graceful) {
graceful = true
}
// Change of variable to make it more readable
const forceful = !graceful
this.db.close(forceful)
}
public executeQuery(query: Statement): any {
}
}

View File

@@ -0,0 +1,6 @@
export enum NginxProtocol {
HTTP = "http",
HTTP2 = "http",
QUIC = "http",
GRPC = "grpc"
}

2
src/lib/index.ts Normal file
View File

@@ -0,0 +1,2 @@
// place files you want to import through the `$lib` alias in this folder.
export {PKG} from "./utils/constants";

View File

@@ -0,0 +1,17 @@
export interface IEndpointDatabase {
// TODO: change return type to endpoint
initDB(): boolean
getEndpoint(name: string): boolean
addEndpoint(name: string): boolean
deleteEndpoint(name: string): boolean
modifyEndpoint(name: string): boolean
}
export interface IApplicationSettingsDatabase {
// TODO: specify settings, if any
initDB(): boolean
getSetting(name: string): any
setSetting(name: string, value: any)
}

View File

@@ -0,0 +1,2 @@
export const PKG = __PKG__
export const DB_PATH = "src/db/db.sqlite"

View File

0
src/lib/utils/firegex.ts Normal file
View File

View File

@@ -0,0 +1,43 @@
import { $ } from "bun";
export async function reloadNginx() {
if (!await validateSchema()) {
const output = await $`nginx -t 2>&1`
.nothrow()
.text()
// UGLY: make this a specific error
throw new Error(output)
}
// Start nginx, should be side-effect free
startNginx()
const output = await $`rc-service nginx reload`.text()
}
export async function startNginx() {
if (!await validateSchema()) {
const output = await $`nginx -t 2>&1`
.nothrow()
.text()
// UGLY: make this a specific error
throw new Error(output)
}
const output = await $`rc-service nginx start`.text()
}
export async function validateSchema() {
const output = await $`nginx -t 2>&1`.nothrow().text()
console.log(output)
const successRegex = new RegExp("test is successful", "gm")
const result = successRegex.test(output)
return result
}

View File

@@ -0,0 +1,70 @@
import { $ } from "bun";
/**
* This methods runs `netstat -ltun`
* to take all ports occupied by
* other services on the host machine
*
* Since this is run in docker with
* network mode `host`, it takes all
* ports
*
* @returns occupied ports
*/
export async function portScan(): Promise<Set<number>> {
const portRegex = new RegExp("(?:\:)(?<port_number>[0-9]+)", "gm")
const netstatOutput : string = await $`netstat -ltun`.text()
const ports = netstatOutput.matchAll(portRegex)
const portArray : Set<number>= new Set()
for (const match of ports) {
portArray.add(
Number(match[1])
)
}
return portArray
}
/**
* This method checks if the port is actually a port,
* throws otherwise
* @param port_number port to validate
*/
export function validatePort(port_number: number): void {
// Validate against Float
if (port_number % 1 !== 0) {
throw new Error("The specified port is not an Integer")
}
// Validate for range
if (port_number < 1 || port_number > 65535 ) {
throw new Error("The specified port is not in the range 1...65535")
}
}
/**
* This method checks for the availability of a port
*
* Throws if the port is not a valid port
*
* @param port_number port to check
* @returns `true` if port is available, `false` otherwise
*/
export async function isPortAvailable(port_number: number): Promise<boolean> {
validatePort(port_number)
const occupied_ports = await portScan()
// Validate for availability
if (occupied_ports.has(port_number)) {
return false
}
return true
}

11
src/routes/+page.svelte Normal file
View File

@@ -0,0 +1,11 @@
<script lang="ts">
</script>
<h1>SSL-Sniffer</h1>
<p>A Sniffer for all your needs</p>
<style>
</style>

View File

@@ -0,0 +1,47 @@
import { error, json, text } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
/***********************************************************
*
* Author: Christian Risi 26/06/2025
*
* There is no POST handling here as it's not
* idempotent, so semantically it's better a
* PUT instead of a POST
*
***********************************************************/
export const GET: RequestHandler = async ({ request }) => {
return json(1)
}
export const PUT: RequestHandler = async ({ request }) => {
return json(1)
}
export const PATCH: RequestHandler = async ({ request }) => {
return json(1)
}
export const DELETE: RequestHandler = async ({ request, }) => {
// TODO: make it delete the resource
return json(1)
}
export const fallback: RequestHandler = async ({ request }) => {
// TODO: return method not allowed
const res = new Response(
null,
{
status: 405,
statusText: "Method Not Allowed",
headers: {
Allow: "GET, PUT, PATCH, DELETE"
}
}
)
return res
};

View File

@@ -0,0 +1,69 @@
import { error, type json, type RequestHandler } from "@sveltejs/kit";
// UGLY: this should be more flexible
class ReloadNginxReq {
public auth: string
public nginx: string
constructor(
json: any
) {
if (!json) {
throw new Error("This is an invalid JSON")
}
if (!json.nginx || !json.auth) {
throw new Error("Can't parse this JSON")
}
this.auth = json.auth
this.nginx = json.nginx
}
}
export const POST: RequestHandler = async ({ request }) => {
let parsedReq : ReloadNginxReq
try {
parsedReq = new ReloadNginxReq(
await request.json()
)
} catch (error) {
return new Response(null, {
status: 400,
statusText: "Bad Request"
})
}
// TODO: Reload Data about Nginx
// TODO: Notify frontends
return new Response(null, {
status: 200,
statusText: "OK"
})
}
export const fallback: RequestHandler = async () => {
// TODO: return method not allowed
const res = new Response(
null,
{
status: 405,
statusText: "Method Not Allowed",
headers: {
Allow: "POST"
}
}
)
return res
};

View File

@@ -0,0 +1,9 @@
import { PKG } from '$lib';
import { json } from '@sveltejs/kit';
export function GET() {
const version = `SSL-Sniffer version ${PKG.version}`
return json(version)
}