fix: ssl + sslrenew

This commit is contained in:
Andras Bacsai 2022-03-01 13:07:34 +01:00
parent 6da78cd3e5
commit 23e12c9c44
5 changed files with 103 additions and 102 deletions

View File

@ -6,7 +6,6 @@ import crypto from 'crypto';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { checkContainer, checkHAProxy } from '.'; import { checkContainer, checkHAProxy } from '.';
import { asyncExecShell, getDomain, getEngine } from '$lib/common'; import { asyncExecShell, getDomain, getEngine } from '$lib/common';
import { letsEncrypt } from '$lib/letsencrypt';
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555'; const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
@ -115,7 +114,6 @@ export async function haproxyInstance() {
export async function configureHAProxy() { export async function configureHAProxy() {
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
await checkHAProxy(haproxy); await checkHAProxy(haproxy);
const ssls = [];
const data = { const data = {
applications: [], applications: [],
services: [], services: [],
@ -147,7 +145,6 @@ export async function configureHAProxy() {
redirectValue, redirectValue,
redirectTo: isWWW ? domain : 'www.' + domain redirectTo: isWWW ? domain : 'www.' + domain
}); });
if (isHttps) ssls.push({ domain, id, isCoolify: false });
} }
if (previews) { if (previews) {
const host = getEngine(engine); const host = getEngine(engine);
@ -171,7 +168,6 @@ export async function configureHAProxy() {
redirectValue, redirectValue,
redirectTo: isWWW ? previewDomain : 'www.' + previewDomain redirectTo: isWWW ? previewDomain : 'www.' + previewDomain
}); });
if (isHttps) ssls.push({ domain: previewDomain, id, isCoolify: false });
} }
} }
} }
@ -202,17 +198,18 @@ export async function configureHAProxy() {
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.'); const isWWW = fqdn.includes('www.');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`; const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
data.services.push({ if (isRunning) {
id, data.services.push({
port, id,
publicPort, port,
domain, publicPort,
isRunning, domain,
isHttps, isRunning,
redirectValue, isHttps,
redirectTo: isWWW ? domain : 'www.' + domain redirectValue,
}); redirectTo: isWWW ? domain : 'www.' + domain
if (isHttps) ssls.push({ domain, id, isCoolify: false }); });
}
} }
} }
const { fqdn } = await db.prisma.setting.findFirst(); const { fqdn } = await db.prisma.setting.findFirst();
@ -229,7 +226,6 @@ export async function configureHAProxy() {
redirectValue, redirectValue,
redirectTo: isWWW ? domain : 'www.' + domain redirectTo: isWWW ? domain : 'www.' + domain
}); });
if (!dev && isHttps) ssls.push({ domain, id: 'coolify', isCoolify: true });
} }
const output = mustache.render(template, data); const output = mustache.render(template, data);
const newHash = crypto.createHash('md5').update(output).digest('hex'); const newHash = crypto.createHash('md5').update(output).digest('hex');
@ -249,13 +245,4 @@ export async function configureHAProxy() {
} else { } else {
// console.log('HAProxy configuration is up to date'); // console.log('HAProxy configuration is up to date');
} }
if (ssls.length > 0) {
for (const ssl of ssls) {
if (!dev) {
await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
} else {
// console.log('Generate ssl for', ssl.domain);
}
}
}
} }

View File

@ -1,7 +1,7 @@
import { dev } from '$app/env'; import { asyncExecShell, getDomain, getEngine } from '$lib/common';
import { forceSSLOffApplication, forceSSLOnApplication } from '$lib/haproxy'; import { checkContainer } from '$lib/haproxy';
import { asyncExecShell, getEngine } from './common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { dev } from '$app/env';
import cuid from 'cuid'; import cuid from 'cuid';
import getPort, { portNumbers } from 'get-port'; import getPort, { portNumbers } from 'get-port';
@ -13,7 +13,7 @@ export async function letsEncrypt(domain, id = null, isCoolify = false) {
const nakedDomain = domain.replace('www.', ''); const nakedDomain = domain.replace('www.', '');
const wwwDomain = `www.${nakedDomain}`; const wwwDomain = `www.${nakedDomain}`;
const randomCuid = cuid(); const randomCuid = cuid();
const randomPort = 9000; const randomPort = await getPort({ port: portNumbers(minPort, maxPort) });
let host; let host;
let dualCerts = false; let dualCerts = false;
@ -72,3 +72,83 @@ export async function letsEncrypt(domain, id = null, isCoolify = false) {
} }
} }
} }
export async function generateSSLCerts() {
const ssls = [];
const applications = await db.prisma.application.findMany({
include: { destinationDocker: true, settings: true }
});
for (const application of applications) {
const {
fqdn,
id,
destinationDocker: { engine, network },
settings: { previews }
} = application;
const isRunning = await checkContainer(engine, id);
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
if (isRunning) {
if (isHttps) ssls.push({ domain, id, isCoolify: false });
}
if (previews) {
const host = getEngine(engine);
const { stdout } = await asyncExecShell(
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
);
const containers = stdout
.trim()
.split('\n')
.filter((a) => a)
.map((c) => c.replace(/"/g, ''));
if (containers.length > 0) {
for (const container of containers) {
let previewDomain = `${container.split('-')[1]}.${domain}`;
if (isHttps) ssls.push({ domain: previewDomain, id, isCoolify: false });
}
}
}
}
const services = await db.prisma.service.findMany({
include: {
destinationDocker: true,
minio: true,
plausibleAnalytics: true,
vscodeserver: true,
wordpress: true
}
});
for (const service of services) {
const {
fqdn,
id,
type,
destinationDocker: { engine }
} = service;
const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
if (found) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const isRunning = await checkContainer(engine, id);
if (isRunning) {
if (isHttps) ssls.push({ domain, id, isCoolify: false });
}
}
}
const { fqdn } = await db.prisma.setting.findFirst();
if (fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
if (isHttps) ssls.push({ domain, id: 'coolify', isCoolify: true });
}
if (ssls.length > 0) {
for (const ssl of ssls) {
if (!dev) {
await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
} else {
console.log('Generate ssl for', ssl.domain);
}
}
}
}

View File

@ -86,9 +86,9 @@ const cron = async () => {
); );
await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } }); await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } });
// await queue.ssl.add('ssl', {}, { repeat: { every: 10000 } }); await queue.ssl.add('ssl', {}, { repeat: { every: 60000 } });
// await queue.cleanup.add('cleanup', {}, { repeat: { every: 600000 } }); await queue.cleanup.add('cleanup', {}, { repeat: { every: 600000 } });
// await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } }); await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
const events = { const events = {
proxy: new QueueEvents('proxy', { ...connectionOptions }), proxy: new QueueEvents('proxy', { ...connectionOptions }),

View File

@ -1,76 +1,9 @@
import { asyncExecShell, getDomain, getEngine } from '$lib/common'; import { generateSSLCerts } from '$lib/letsencrypt';
import { prisma } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { forceSSLOnApplication } from '$lib/haproxy';
import * as db from '$lib/database';
import { dev } from '$app/env';
import getPort, { portNumbers } from 'get-port';
import cuid from 'cuid';
export default async function () { export default async function () {
try { try {
const data = await db.prisma.setting.findFirst(); return await generateSSLCerts();
const { minPort, maxPort } = data;
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
const randomCuid = cuid();
const destinationDockers = await prisma.destinationDocker.findMany({});
for (const destination of destinationDockers) {
if (destination.isCoolifyProxyUsed) {
const docker = dockerInstance({ destinationDocker: destination });
const containers = await docker.engine.listContainers();
const configurations = containers.filter(
(container) => container.Labels['coolify.managed']
);
for (const configuration of configurations) {
const parsedConfiguration = JSON.parse(
Buffer.from(configuration.Labels['coolify.configuration'], 'base64').toString()
);
if (configuration.Labels['coolify.type'] === 'standalone-application') {
const { fqdn } = parsedConfiguration;
if (fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
if (isHttps) {
if (dev) {
console.log('DEV MODE: SSL is enabled');
} else {
const host = getEngine(destination.engine);
await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${publicPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${publicPort} -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
);
const { stderr } = await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem`
);
if (stderr) throw new Error(stderr);
}
}
}
}
}
}
}
const { fqdn } = await db.listSettings();
if (fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
if (isHttps) {
if (dev) {
console.log('DEV MODE: SSL is enabled');
} else {
await asyncExecShell(
`docker run --rm --name certbot-${randomCuid} -p 9080:${publicPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${publicPort} -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
);
const { stderr } = await asyncExecShell(
`docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem`
);
if (stderr) throw new Error(stderr);
}
}
}
} catch (error) { } catch (error) {
console.log(error);
throw error; throw error;
} }
} }

View File

@ -78,6 +78,7 @@
if (fqdn !== settings.fqdn) { if (fqdn !== settings.fqdn) {
await post(`/settings/check.json`, { fqdn }); await post(`/settings/check.json`, { fqdn });
await post(`/settings.json`, { fqdn }); await post(`/settings.json`, { fqdn });
return window.location.reload();
} }
if (minPort !== settings.minPort || maxPort !== settings.maxPort) { if (minPort !== settings.minPort || maxPort !== settings.maxPort) {
await post(`/settings.json`, { minPort, maxPort }); await post(`/settings.json`, { minPort, maxPort });
@ -98,7 +99,7 @@
{#if $session.teamId === '0'} {#if $session.teamId === '0'}
<div class="mx-auto max-w-4xl px-6"> <div class="mx-auto max-w-4xl px-6">
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4"> <form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex space-x-1 py-6"> <div class="flex space-x-1 pb-6">
<div class="title font-bold">Global Settings</div> <div class="title font-bold">Global Settings</div>
<button <button
type="submit" type="submit"