diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index 330f93e47..de6fa47a9 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -476,9 +476,28 @@ export const supportedDatabaseTypesAndVersions = [ }, { name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.2'] } ]; + +export async function getFreeSSHLocalPort(id: string): Promise { + const { default: getPort, portNumbers } = await import('get-port'); + const { remoteIpAddress, sshLocalPort } = await prisma.destinationDocker.findUnique({ where: { id } }) + if (sshLocalPort) { + return Number(sshLocalPort) + } + const ports = await prisma.destinationDocker.findMany({ where: { sshLocalPort: { not: null }, remoteIpAddress: { not: remoteIpAddress } } }) + const alreadyConfigured = await prisma.destinationDocker.findFirst({ where: { remoteIpAddress, id: { not: id }, sshLocalPort: { not: null } } }) + if (alreadyConfigured?.sshLocalPort) { + await prisma.destinationDocker.update({ where: { id }, data: { sshLocalPort: alreadyConfigured.sshLocalPort } }) + return Number(alreadyConfigured.sshLocalPort) + } + const availablePort = await getPort({ port: portNumbers(10000, 10100), exclude: ports.map(p => p.sshLocalPort) }) + await prisma.destinationDocker.update({ where: { id }, data: { sshLocalPort: Number(availablePort) } }) + return Number(availablePort) +} + export async function createRemoteEngineConfiguration(id: string) { const homedir = os.homedir(); const sshKeyFile = `/tmp/id_rsa-${id}` + const localPort = await getFreeSSHLocalPort(id); const { sshKey: { privateKey }, remoteIpAddress, remotePort, remoteUser } = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } }) await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 }) // Needed for remote docker compose @@ -488,24 +507,23 @@ export async function createRemoteEngineConfiguration(id: string) { } await asyncExecShell(`SSH_AUTH_SOCK=/tmp/ssh-agent.pid ssh-add -q ${sshKeyFile}`) - const { stdout: numberOfSSHTunnelsRunning } = await asyncExecShell(`ps ax | grep 'ssh -o StrictHostKeyChecking no -fNL 11122:localhost:${remotePort}' | grep -v grep | wc -l`) + const { stdout: numberOfSSHTunnelsRunning } = await asyncExecShell(`ps ax | grep 'ssh -F /dev/null -o StrictHostKeyChecking no -fNL ${localPort}:localhost:${remotePort}' | grep -v grep | wc -l`) if (numberOfSSHTunnelsRunning !== '' && Number(numberOfSSHTunnelsRunning.trim()) == 0) { try { - await asyncExecShell(`SSH_AUTH_SOCK=/tmp/ssh-agent.pid ssh -o "StrictHostKeyChecking no" -fNL 11122:localhost:${remotePort} ${remoteUser}@${remoteIpAddress}`) + await asyncExecShell(`SSH_AUTH_SOCK=/tmp/ssh-agent.pid ssh -F /dev/null -o "StrictHostKeyChecking no" -fNL ${localPort}:localhost:${remotePort} ${remoteUser}@${remoteIpAddress}`) - } catch(error){ + } catch (error) { console.log(error) } } - const config = sshConfig.parse('') const found = config.find({ Host: remoteIpAddress }) if (!found) { config.append({ Host: remoteIpAddress, Hostname: 'localhost', - Port: '11122', + Port: Number(localPort), User: remoteUser, IdentityFile: sshKeyFile, StrictHostKeyChecking: 'no' @@ -516,11 +534,7 @@ export async function createRemoteEngineConfiguration(id: string) { } catch (error) { await fs.mkdir(`${homedir}/.ssh/`) } - - await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config)) - - return - + return await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config)) } export async function executeDockerCmd({ dockerId, command }: { dockerId: string, command: string }) { let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } }) diff --git a/apps/api/src/routes/api/v1/destinations/handlers.ts b/apps/api/src/routes/api/v1/destinations/handlers.ts index 7662d474c..11b790316 100644 --- a/apps/api/src/routes/api/v1/destinations/handlers.ts +++ b/apps/api/src/routes/api/v1/destinations/handlers.ts @@ -4,7 +4,7 @@ import sshConfig from 'ssh-config' import fs from 'fs/promises' import os from 'os'; -import { asyncExecShell, decrypt, errorHandler, executeDockerCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common'; +import { asyncExecShell, createRemoteEngineConfiguration, decrypt, errorHandler, executeDockerCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common'; import { checkContainer } from '../../../../lib/docker'; import type { OnlyId } from '../../../../types'; @@ -30,7 +30,7 @@ export async function listDestinations(request: FastifyRequest destinations } } catch ({ status, message }) { - console.log({status, message}) + console.log({ status, message }) return errorHandler({ status, message }) } } @@ -209,32 +209,10 @@ export async function assignSSHKey(request: FastifyRequest) { export async function verifyRemoteDockerEngine(request: FastifyRequest, reply: FastifyReply) { try { const { id } = request.params; - const homedir = os.homedir(); - - const { sshKey: { privateKey }, remoteIpAddress, remotePort, remoteUser, network } = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } }) - - await fs.writeFile(`/tmp/id_rsa_verification_${id}`, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 }) + await createRemoteEngineConfiguration(id); + const { remoteIpAddress, remoteUser, network } = await prisma.destinationDocker.findFirst({ where: { id } }) const host = `ssh://${remoteUser}@${remoteIpAddress}` - - const config = sshConfig.parse('') - const found = config.find({ Host: remoteIpAddress }) - if (!found) { - config.append({ - Host: remoteIpAddress, - Port: remotePort.toString(), - User: remoteUser, - IdentityFile: `/tmp/id_rsa_verification_${id}`, - StrictHostKeyChecking: 'no' - }) - } - try { - await fs.stat(`${homedir}/.ssh/`) - } catch (error) { - await fs.mkdir(`${homedir}/.ssh/`) - } - await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config)) - const { stdout } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`); if (!stdout) { diff --git a/apps/ui/src/lib/components/Setting.svelte b/apps/ui/src/lib/components/Setting.svelte index d1cf55dc0..eaaaf17e8 100644 --- a/apps/ui/src/lib/components/Setting.svelte +++ b/apps/ui/src/lib/components/Setting.svelte @@ -23,7 +23,6 @@ class="flex justify-center" >