import { EndpointType } from "$lib/server/enums/endpoints" import { httpVersion, protocolToString, proxyProtocol, secureProtocol, stringToProtocol, type NginxProtocol } from "$lib/server/enums/protocols" import { validatePort } from "$lib/server/utils/ports-utils" import type { Stats } from "fs" import type { FSHeader, IEndpointFS } from "./endpoints" import { createHeader, parseDefaultHeader, parseGenericHeader } from "../utils" import { hashUtil } from "$lib/server/utils/filesystem-utils" import type { IEndpoint } from "$lib/server/classes/endpoints/endpoints-interfaces" import { SSLTermination } from "$lib/server/classes/endpoints/ssl-termination-endpoint" // TODO: add broker implementation export class SSLTerminationFS implements IEndpointFS { private static __type = EndpointType.SSL_TERMINATION public get type() { return SSLTerminationFS.__type } public name: string public stats: Stats public get path() { return `${this.protocol}/name.conf` } public hash: string public sslPort: number public clearPort: number public servicePort: number public serviceEndpoint: string public protocol: NginxProtocol public certificateURI: string public privateKeyURI: string constructor( name: string, stats: Stats, hash: string, sslPort: number, clearPort: number, servicePort: number, serviceEndpoint: string, protocol: NginxProtocol, certificateURI: string, privateKeyURI: string ) { this.name = name this.stats = stats this.hash = hash this.sslPort = sslPort this.clearPort = clearPort this.servicePort = servicePort this.serviceEndpoint = serviceEndpoint this.protocol = protocol this.certificateURI = certificateURI this.privateKeyURI = privateKeyURI } public toIEndpoint(): IEndpoint { return new SSLTermination( this.name, this.path, this.sslPort, this.clearPort, this.servicePort, this.serviceEndpoint, this.protocol, this.certificateURI, this.privateKeyURI ) } headerHash(): string { return hashUtil( this.createHeader() ) } toConf(): string { const HEADER = this.createHeader() const SSL_SERVER = this.createSSLServer() const CLEAR_SERVER = this.createClearServer() const CONF = HEADER + SSL_SERVER + CLEAR_SERVER return CONF } ports(): number[] { return [ this.sslPort, this.clearPort ] } public static parseConf(fsHeader: FSHeader, conf: string): SSLTerminationFS { // TODO: parse header const defHeader = parseDefaultHeader(conf) const keyValue = parseGenericHeader(conf) const name = keyValue.get("NAME") if (!name) { throw new Error("Could not parse") } const protocol = stringToProtocol( keyValue.get("PROTOCOL") ?? "" ) const sslPort = Number.parseInt( keyValue.get( "SSL_PORT" ) ?? "" ) validatePort(sslPort) const clearPort = Number.parseInt( keyValue.get( "CLEAR_PORT" ) ?? "" ) validatePort(clearPort) const servicePort = Number.parseInt( keyValue.get( "SERVICE_PORT" ) ?? "" ) validatePort(servicePort) const serviceEndpoint = keyValue.get("SERVICE_ENDPOINT") if (!serviceEndpoint) { throw new Error("Could not parse") } const certificateURI = keyValue.get("CERTIFICATE_PATH") if (!certificateURI) { throw new Error("Could not parse") } const privateKeyURI = keyValue.get("KEY_PATH") if (!privateKeyURI) { throw new Error("Could not parse") } return new SSLTerminationFS( name, fsHeader.stats, fsHeader.hash, sslPort, clearPort, servicePort, serviceEndpoint, protocol, certificateURI, privateKeyURI ) } private createHeader() { return createHeader( this, [ { key: "NAME", value: this.name }, { key: "PROTOCOL", value: protocolToString(this.protocol) }, { key: "SSL_PORT", value: this.sslPort }, { key: "CLEAR_PORT", value: this.clearPort }, { key: "SERVICE_PORT", value: this.servicePort }, { key: "SERVICE_ENDPOINT", value: this.serviceEndpoint }, { key: "CERTIFICATE_PATH", value: this.certificateURI }, { key: "KEY_PATH", value: this.privateKeyURI } ] ) } // UGLY: refactor into a flexible method private createSSLServer() { const CLEAR_PROTOCOL = `${this.protocol}` const HTTP_VERSION = httpVersion(this.protocol) const PROXY_OPTION = proxyProtocol(this.protocol) // UGLY: put to constants let conf = [ "server {\n", "\tmore_clear_headers Server;\n", `\tlisten ${this.sslPort};` ] if (HTTP_VERSION !== 1) { conf.push( `\thttp${HTTP_VERSION} on;` ) } // TODO: check if we should support less protocols conf.push( "\n", "\tlocation / {", `\t\t${PROXY_OPTION} ${CLEAR_PROTOCOL}://127.0.0.1:${this.clearPort};`, "\t}", "\n", `ssl_certificate ${this.certificateURI};`, `ssl_certificate_key ${this.privateKeyURI};`, "\tssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;", "\n", "}" ) return conf.join("\n") } private createClearServer() { const SSL_PROTOCOL = secureProtocol(this.protocol) const HTTP_VERSION = httpVersion(this.protocol) const PROXY_OPTION = proxyProtocol(this.protocol) // UGLY: put to constants let conf = [ "server {\n", "\tmore_clear_headers Server;\n", `\tlisten ${this.clearPort};` ] if (HTTP_VERSION !== 1) { conf.push( `\thttp${HTTP_VERSION} on;` ) } // TODO: check if we should support less protocols conf.push( "\n", "\tlocation / {", `\t\t${PROXY_OPTION} ${SSL_PROTOCOL}://${this.serviceEndpoint}:${this.servicePort};`, "\t}", "\n", "}" ) return conf.join("\n") } }