fix: Improved tcp proxy monitoring for databases/ftp

This commit is contained in:
Andras Bacsai
2022-04-14 00:04:46 +02:00
parent ce2757f514
commit 2bd3802a6f
8 changed files with 86 additions and 19 deletions

View File

@@ -9,6 +9,7 @@ import { default as ProdPrisma } from '@prisma/client';
import type { Database, DatabaseSettings } from '@prisma/client'; import type { Database, DatabaseSettings } from '@prisma/client';
import generator from 'generate-password'; import generator from 'generate-password';
import forge from 'node-forge'; import forge from 'node-forge';
import getPort, { portNumbers } from 'get-port';
export function generatePassword(length = 24): string { export function generatePassword(length = 24): string {
return generator.generate({ return generator.generate({
@@ -251,3 +252,29 @@ export function generateDatabaseConfiguration(database: Database & { settings: D
}; };
} }
} }
export async function getFreePort() {
const data = await prisma.setting.findFirst();
const { minPort, maxPort } = data;
const dbUsed = await (
await prisma.database.findMany({
where: { publicPort: { not: null } },
select: { publicPort: true }
})
).map((a) => a.publicPort);
const wpFtpUsed = await (
await prisma.wordpress.findMany({
where: { ftpPublicPort: { not: null } },
select: { ftpPublicPort: true }
})
).map((a) => a.ftpPublicPort);
const wpUsed = await (
await prisma.wordpress.findMany({
where: { mysqlPublicPort: { not: null } },
select: { mysqlPublicPort: true }
})
).map((a) => a.mysqlPublicPort);
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed];
return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts });
}

View File

@@ -7,6 +7,7 @@ import builder from './builder';
import logger from './logger'; import logger from './logger';
import cleanup from './cleanup'; import cleanup from './cleanup';
import proxy from './proxy'; import proxy from './proxy';
import proxyTcpHttp from './proxyTcpHttp';
import ssl from './ssl'; import ssl from './ssl';
import sslrenewal from './sslrenewal'; import sslrenewal from './sslrenewal';
@@ -29,17 +30,20 @@ const connectionOptions = {
const cron = async (): Promise<void> => { const cron = async (): Promise<void> => {
new QueueScheduler('proxy', connectionOptions); new QueueScheduler('proxy', connectionOptions);
new QueueScheduler('proxyTcpHttp', connectionOptions);
new QueueScheduler('cleanup', connectionOptions); new QueueScheduler('cleanup', connectionOptions);
new QueueScheduler('ssl', connectionOptions); new QueueScheduler('ssl', connectionOptions);
new QueueScheduler('sslRenew', connectionOptions); new QueueScheduler('sslRenew', connectionOptions);
const queue = { const queue = {
proxy: new Queue('proxy', { ...connectionOptions }), proxy: new Queue('proxy', { ...connectionOptions }),
proxyTcpHttp: new Queue('proxyTcpHttp', { ...connectionOptions }),
cleanup: new Queue('cleanup', { ...connectionOptions }), cleanup: new Queue('cleanup', { ...connectionOptions }),
ssl: new Queue('ssl', { ...connectionOptions }), ssl: new Queue('ssl', { ...connectionOptions }),
sslRenew: new Queue('sslRenew', { ...connectionOptions }) sslRenew: new Queue('sslRenew', { ...connectionOptions })
}; };
await queue.proxy.drain(); await queue.proxy.drain();
await queue.proxyTcpHttp.drain();
await queue.cleanup.drain(); await queue.cleanup.drain();
await queue.ssl.drain(); await queue.ssl.drain();
await queue.sslRenew.drain(); await queue.sslRenew.drain();
@@ -54,6 +58,16 @@ const cron = async (): Promise<void> => {
} }
); );
new Worker(
'proxyTcpHttp',
async () => {
await proxyTcpHttp();
},
{
...connectionOptions
}
);
new Worker( new Worker(
'ssl', 'ssl',
async () => { async () => {
@@ -85,6 +99,7 @@ const cron = async (): Promise<void> => {
); );
await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } }); await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } });
await queue.proxyTcpHttp.add('proxyTcpHttp', {}, { repeat: { every: 10000 } });
await queue.ssl.add('ssl', {}, { repeat: { every: dev ? 10000 : 60000 } }); await queue.ssl.add('ssl', {}, { repeat: { every: dev ? 10000 : 60000 } });
if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } }); if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } });
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } }); await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });

View File

@@ -0,0 +1,34 @@
import { ErrorHandler, generateDatabaseConfiguration, prisma } from '$lib/database';
import { checkContainer, startTcpProxy } from '$lib/haproxy';
export default async function (): Promise<void | {
status: number;
body: { message: string; error: string };
}> {
try {
const databasesWithPublicPort = await prisma.database.findMany({
where: { publicPort: { not: null } },
include: { settings: true, destinationDocker: true }
});
for (const database of databasesWithPublicPort) {
const { destinationDockerId, destinationDocker, publicPort, id } = database;
if (destinationDockerId) {
const { privatePort } = generateDatabaseConfiguration(database);
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
}
}
const wordpressWithFtp = await prisma.wordpress.findMany({
where: { ftpPublicPort: { not: null } },
include: { service: { include: { destinationDocker: true } } }
});
for (const ftp of wordpressWithFtp) {
const { service, ftpPublicPort, id } = ftp;
const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) {
await startTcpProxy(destinationDocker, `${id}-ftp`, ftpPublicPort, 22);
}
}
} catch (error) {
return ErrorHandler(error.response?.body || error);
}
}

View File

@@ -34,6 +34,7 @@
name="rootUserPassword" name="rootUserPassword"
bind:value={database.rootUserPassword} bind:value={database.rootUserPassword}
/> />
<Explainer text="Could be changed while the database is running." />
</div> </div>
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<label for="dbUser" class="text-base font-bold text-stone-100">User</label> <label for="dbUser" class="text-base font-bold text-stone-100">User</label>

View File

@@ -1,7 +1,7 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { ErrorHandler, stopDatabase } from '$lib/database'; import { ErrorHandler, stopDatabase } from '$lib/database';
import { deleteProxy } from '$lib/haproxy'; import { stopTcpHttpProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const del: RequestHandler = async (event) => { export const del: RequestHandler = async (event) => {
@@ -12,7 +12,7 @@ export const del: RequestHandler = async (event) => {
const database = await db.getDatabase({ id, teamId }); const database = await db.getDatabase({ id, teamId });
if (database.destinationDockerId) { if (database.destinationDockerId) {
const everStarted = await stopDatabase(database); const everStarted = await stopDatabase(database);
if (everStarted) await deleteProxy({ id }); if (everStarted) await stopTcpHttpProxy(database.destinationDocker, database.publicPort);
} }
await db.removeDatabase({ id }); await db.removeDatabase({ id });
return { status: 200 }; return { status: 200 };

View File

@@ -1,20 +1,16 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { generateDatabaseConfiguration, ErrorHandler } from '$lib/database'; import { generateDatabaseConfiguration, ErrorHandler, getFreePort } from '$lib/database';
import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy'; import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import getPort, { portNumbers } from 'get-port';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
const { status, body, teamId } = await getUserDetails(event); const { status, body, teamId } = await getUserDetails(event);
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const { id } = event.params; const { id } = event.params;
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
const { isPublic, appendOnly = true } = await event.request.json(); const { isPublic, appendOnly = true } = await event.request.json();
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) }); const publicPort = await getFreePort();
try { try {
await db.setDatabase({ id, isPublic, appendOnly }); await db.setDatabase({ id, isPublic, appendOnly });

View File

@@ -4,9 +4,7 @@ import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { startHttpProxy } from '$lib/haproxy'; import { startHttpProxy } from '$lib/haproxy';
import getPort, { portNumbers } from 'get-port'; import { ErrorHandler, getFreePort, getServiceImage } from '$lib/database';
import { getDomain } from '$lib/components/common';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common'; import { makeLabelForServices } from '$lib/buildPacks/common';
import type { ComposeFile } from '$lib/types/composeFile'; import type { ComposeFile } from '$lib/types/composeFile';
@@ -28,13 +26,10 @@ export const post: RequestHandler = async (event) => {
serviceSecret serviceSecret
} = service; } = service;
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine); const host = getEngine(destinationDocker.engine);
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) }); const publicPort = await getFreePort();
const consolePort = 9001; const consolePort = 9001;
const apiPort = 9000; const apiPort = 9000;

View File

@@ -2,7 +2,7 @@ import { dev } from '$app/env';
import { asyncExecShell, getEngine, getUserDetails } from '$lib/common'; import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
import { decrypt, encrypt } from '$lib/crypto'; import { decrypt, encrypt } from '$lib/crypto';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { generateDatabaseConfiguration, ErrorHandler, generatePassword } from '$lib/database'; import { ErrorHandler, generatePassword, getFreePort } from '$lib/database';
import { checkContainer, startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy'; import { checkContainer, startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
import type { ComposeFile } from '$lib/types/composeFile'; import type { ComposeFile } from '$lib/types/composeFile';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -16,11 +16,10 @@ export const post: RequestHandler = async (event) => {
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const { id } = event.params; const { id } = event.params;
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
const { ftpEnabled } = await event.request.json(); const { ftpEnabled } = await event.request.json();
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) }); const publicPort = await getFreePort();
let ftpUser = cuid(); let ftpUser = cuid();
let ftpPassword = generatePassword(); let ftpPassword = generatePassword();