diff --git a/tunnel-server/docker-compose.tls.yml b/tunnel-server/docker-compose.tls.yml index 8ca09e12..5f013db5 100644 --- a/tunnel-server/docker-compose.tls.yml +++ b/tunnel-server/docker-compose.tls.yml @@ -6,26 +6,18 @@ secrets: configs: tls-cert: file: ./tls/cert.pem - sslh-config: - file: ./tls/sslh.conf services: proxy: environment: - BASE_URL: ${BASE_URL:-https://local.livecycle.run:8044} + BASE_URL: ${BASE_URL:-https://local.livecycle.run:8443} secrets: - source: tls-key target: /app/tls/key.pem configs: - source: tls-cert target: /app/tls/cert.pem - healthcheck: - test: wget --no-verbose --tries=1 --spider https://localhost:3000/healthz || exit 1 - sslh: - image: oorabona/sslh:v2.0-rc1 - command: [sslh-ev, --config=/etc/sslh/config] - configs: - - source: sslh-config - target: /etc/sslh/config ports: - - '8044:2443' + - '8030:3000' + - '8443:8443' + - '2223:2222' diff --git a/tunnel-server/index.ts b/tunnel-server/index.ts index 8d94f2b7..51277d33 100644 --- a/tunnel-server/index.ts +++ b/tunnel-server/index.ts @@ -14,6 +14,7 @@ import { cookieSessionStore } from './src/session.js' import { IdentityProvider, claimsSchema, cliIdentityProvider, jwtAuthenticator, saasIdentityProvider } from './src/auth.js' import { createSshServer } from './src/ssh/index.js' import { calcLoginUrl } from './src/app/urls.js' +import { createTlsServer } from './src/tls-server.js' const log = pino.default(appLoggerFromEnv()) @@ -86,7 +87,6 @@ const authFactory = ( const activeTunnelStore = inMemoryActiveTunnelStore({ log }) const sessionStore = cookieSessionStore({ domain: BASE_URL.hostname, schema: claimsSchema, keys: process.env.COOKIE_SECRETS?.split(' ') }) const app = await createApp({ - tlsConfig, sessionStore, activeTunnelStore, baseUrl: BASE_URL, @@ -132,6 +132,21 @@ app.listen({ host: LISTEN_HOST, port: PORT }).catch(err => { process.exit(1) }) +const TLS_PORT = numberFromEnv('TLS_PORT') ?? 8443 +const tlsLog = log.child({ name: 'tls_server' }) +const tlsServer = tlsConfig + ? createTlsServer({ + log: tlsLog, + tlsConfig, + sshServer, + httpServer: + app.server, + sshHostnames: new Set([BASE_URL.hostname]), + }) + : undefined + +tlsServer?.listen({ host: LISTEN_HOST, port: TLS_PORT }, () => { tlsLog.info('TLS server listening on port %j', TLS_PORT) }) + runMetricsServer(8888).catch(err => { app.log.error(err) }); @@ -139,7 +154,11 @@ runMetricsServer(8888).catch(err => { ['SIGTERM', 'SIGINT'].forEach(signal => { process.once(signal, () => { app.log.info(`shutting down on ${signal}`) - Promise.all([promisify(sshServer.close).call(sshServer), app.close()]) + Promise.all([ + promisify(sshServer.close).call(sshServer), + app.close(), + tlsServer ? promisify(tlsServer.close).call(tlsServer) : undefined, + ]) .catch(err => { app.log.error(err) process.exit(1) diff --git a/tunnel-server/src/app/index.ts b/tunnel-server/src/app/index.ts index 0df66320..3fed14d5 100644 --- a/tunnel-server/src/app/index.ts +++ b/tunnel-server/src/app/index.ts @@ -1,7 +1,6 @@ import fastify, { FastifyServerFactory, RawServerDefault } from 'fastify' import { fastifyRequestContext } from '@fastify/request-context' import http from 'http' -import https from 'https' import { Logger } from 'pino' import { KeyObject } from 'crypto' import { validatorCompiler, serializerCompiler, ZodTypeProvider } from 'fastify-type-provider-zod' @@ -17,12 +16,10 @@ const HEALTZ_URL = '/healthz' const serverFactory = ({ log, - tlsConfig, baseUrl, proxy, }: { log: Logger - tlsConfig?: https.ServerOptions baseUrl: URL proxy: Proxy }): FastifyServerFactory => handler => { @@ -58,12 +55,10 @@ const serverFactory = ({ return undefined } - return (tlsConfig ? https.createServer(tlsConfig, serverHandler) : http.createServer(serverHandler)) - .on('upgrade', serverUpgradeHandler) + return http.createServer(serverHandler).on('upgrade', serverUpgradeHandler) } export const createApp = async ({ - tlsConfig, proxy, sessionStore, baseUrl, @@ -73,7 +68,6 @@ export const createApp = async ({ authFactory, }: { log: Logger - tlsConfig?: https.ServerOptions baseUrl: URL saasBaseUrl?: URL sessionStore: SessionStore @@ -81,7 +75,7 @@ export const createApp = async ({ authFactory: (client: { publicKey: KeyObject; publicKeyThumbprint: string }) => Authenticator proxy: Proxy }) => { - const app = await fastify({ logger: log, serverFactory: serverFactory({ log, baseUrl, tlsConfig, proxy }) }) + const app = await fastify({ logger: log, serverFactory: serverFactory({ log, baseUrl, proxy }) }) app.setValidatorCompiler(validatorCompiler) app.setSerializerCompiler(serializerCompiler) app.withTypeProvider() diff --git a/tunnel-server/src/env.ts b/tunnel-server/src/env.ts index ee13f2c3..0c6d4908 100644 --- a/tunnel-server/src/env.ts +++ b/tunnel-server/src/env.ts @@ -8,5 +8,12 @@ export const requiredEnv = (key: string): string => { export const numberFromEnv = (key: string) => { const s = process.env[key] - return s === undefined ? undefined : Number(s) + if (!s) { + return undefined + } + const result = Number(s) + if (Number.isNaN(result)) { + throw new Error(`env var ${key} is not a number: "${s}"`) + } + return result } diff --git a/tunnel-server/src/tls-server.ts b/tunnel-server/src/tls-server.ts new file mode 100644 index 00000000..ba4cce33 --- /dev/null +++ b/tunnel-server/src/tls-server.ts @@ -0,0 +1,22 @@ +import { Logger } from 'pino' +import http from 'http' +import ssh from 'ssh2' +import tls from 'tls' + +export const createTlsServer = ({ log, httpServer, sshServer, tlsConfig, sshHostnames }: { + log: Logger + httpServer: Pick + sshServer: Pick + tlsConfig: tls.TlsOptions + sshHostnames: Set +}) => tls.createServer(tlsConfig) + .on('error', err => { log.error(err) }) + .on('secureConnection', socket => { + const { servername } = (socket as { servername?: string }) + log.debug('TLS connection: %j', servername) + if (servername && sshHostnames.has(servername)) { + sshServer.injectSocket(socket) + } else { + httpServer.emit('connection', socket) + } + }) diff --git a/tunnel-server/tls/sslh.conf b/tunnel-server/tls/sslh.conf deleted file mode 100644 index 0b8f4f12..00000000 --- a/tunnel-server/tls/sslh.conf +++ /dev/null @@ -1,16 +0,0 @@ -foreground: true; -verbose-config: 1; # print configuration at startup -verbose-config-error: 1; # print configuration errors -verbose-connections-error: 1; # connection errors -verbose-probe-error: 1; # failures and problems during probing -verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing -verbose-int-error: 1; # internal errors, the kind that should never happen -listen: -( - { host: "0.0.0.0"; port: "2443"; } -); -protocols: -( - { name: "ssh"; service: "ssh"; host: "proxy"; port: "2222"; }, - { name: "tls"; host: "proxy"; port: "3000"; }, -); \ No newline at end of file