Skip to content

Instantly share code, notes, and snippets.

@zicklag
Last active January 25, 2024 07:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save zicklag/5618a0cb0428f8ef9d8354abdc4651eb to your computer and use it in GitHub Desktop.
Save zicklag/5618a0cb0428f8ef9d8354abdc4651eb to your computer and use it in GitHub Desktop.
Deno TLS Proxy Using Traefik `acme.json` File For Certificates
import { copy } from "https://deno.land/std@0.153.0/streams/conversion.ts";
const DATA_FILE = Deno.args[0];
const LISTEN_PORT = Deno.args[1];
const TARGET_ADDR = Deno.args[2];
const DOMAIN = Deno.args[3];
interface AcmeJsonFile {
letsencrypt: {
Certificates: {
domain: {
main: string;
};
certificate: string;
key: string;
}[];
};
}
interface ServerData {
server?: Deno.TlsListener;
cert?: string;
key?: string;
reload: boolean;
}
async function loadCert(): Promise<{ cert: string; key: string }> {
const acmeData: AcmeJsonFile = JSON.parse(await Deno.readTextFile(DATA_FILE));
const domainInfo = acmeData.letsencrypt.Certificates.filter(
(x) => x.domain.main == DOMAIN
)[0];
if (!domainInfo) {
console.error(`Could not find domain in acme.json file: ${DOMAIN}`);
Deno.exit(1);
}
const cert = atob(domainInfo.certificate);
const key = atob(domainInfo.key);
return { cert, key };
}
const serverData: ServerData = {
reload: true,
};
async function serve(data: ServerData) {
console.info("Starting server");
data.server = Deno.listenTls({
port: parseInt(LISTEN_PORT),
cert: data.cert,
key: data.key,
});
while (true) {
if (!data.server) {
break;
}
try {
let input_conn: Deno.TlsConn;
try {
input_conn = await data.server.accept();
} catch (e) {
console.info("Could not accept:", e);
break;
}
console.log(`accepted connection`);
const [hostname, port] = TARGET_ADDR.split(":");
console.log(`Connecting to server ${TARGET_ADDR}`);
const output_conn = await Deno.connect({
hostname,
port: parseInt(port),
});
console.log(`Connected to target`);
// Proxy input and output
(async () => {
try {
await copy(input_conn, output_conn);
console.log("Connection closed");
output_conn.close();
} catch {
console.log("disconnect input -> output");
}
})();
(async () => {
try {
await copy(output_conn, input_conn);
} catch {
// Disconnect is normal when we close it after the client disconnects.
}
})();
} catch (err) {
console.error("connection error", err);
}
}
}
// Watch file for changes
const watcher = Deno.watchFs(DATA_FILE);
(async (data: ServerData) => {
for await (const event of watcher) {
if (event.kind == "modify" && data.server) {
data.reload = true;
}
}
})(serverData);
let firstRun = new Boolean(true);
const reload = async () => {
if (serverData.reload) {
if (firstRun) {
firstRun = false;
} else {
console.log("Certs updated: restarting server");
}
if (serverData.server) {
console.log("Closing server");
serverData.server.close();
serverData.server = undefined;
}
try {
const certData = await loadCert();
serverData.cert = certData.cert;
serverData.key = certData.key;
serve(serverData);
serverData.reload = false;
} catch {
setTimeout(reload, 200);
}
}
setTimeout(reload, 1000);
};
reload();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment