feat: DNS check settings for SSL generation

This commit is contained in:
Andras Bacsai
2022-04-30 13:21:18 +02:00
parent 646d92757a
commit 0eb7f4526e
6 changed files with 145 additions and 69 deletions

View File

@@ -0,0 +1,23 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Setting" (
"id" TEXT NOT NULL PRIMARY KEY,
"fqdn" TEXT,
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
"minPort" INTEGER NOT NULL DEFAULT 9000,
"maxPort" INTEGER NOT NULL DEFAULT 9100,
"proxyPassword" TEXT NOT NULL,
"proxyUser" TEXT NOT NULL,
"proxyHash" TEXT,
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_Setting" ("createdAt", "dualCerts", "fqdn", "id", "isAutoUpdateEnabled", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt") SELECT "createdAt", "dualCerts", "fqdn", "id", "isAutoUpdateEnabled", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting";
DROP TABLE "Setting";
ALTER TABLE "new_Setting" RENAME TO "Setting";
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -19,6 +19,7 @@ model Setting {
proxyUser String proxyUser String
proxyHash String? proxyHash String?
isAutoUpdateEnabled Boolean @default(false) isAutoUpdateEnabled Boolean @default(false)
isDNSCheckEnabled Boolean @default(true)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }

View File

@@ -109,6 +109,7 @@ export async function generateSSLCerts(): Promise<void> {
include: { destinationDocker: true, settings: true }, include: { destinationDocker: true, settings: true },
orderBy: { createdAt: 'desc' } orderBy: { createdAt: 'desc' }
}); });
const { fqdn, isDNSCheckEnabled } = await db.prisma.setting.findFirst();
for (const application of applications) { for (const application of applications) {
try { try {
if (application.fqdn && application.destinationDockerId) { if (application.fqdn && application.destinationDockerId) {
@@ -147,7 +148,6 @@ export async function generateSSLCerts(): Promise<void> {
} }
} }
const services = await listServicesWithIncludes(); const services = await listServicesWithIncludes();
for (const service of services) { for (const service of services) {
try { try {
if (service.fqdn && service.destinationDockerId) { if (service.fqdn && service.destinationDockerId) {
@@ -171,7 +171,6 @@ export async function generateSSLCerts(): Promise<void> {
console.log(`Error during generateSSLCerts with ${service.fqdn}: ${error}`); console.log(`Error during generateSSLCerts with ${service.fqdn}: ${error}`);
} }
} }
const { fqdn } = await db.prisma.setting.findFirst();
if (fqdn) { if (fqdn) {
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
@@ -193,73 +192,99 @@ export async function generateSSLCerts(): Promise<void> {
file.endsWith('.pem') && certificates.push(file.replace(/\.pem$/, '')); file.endsWith('.pem') && certificates.push(file.replace(/\.pem$/, ''));
} }
} }
const resolver = new dns.Resolver({ timeout: 2000 }); if (isDNSCheckEnabled) {
resolver.setServers(['8.8.8.8', '1.1.1.1']); const resolver = new dns.Resolver({ timeout: 2000 });
let ipv4, ipv6; resolver.setServers(['8.8.8.8', '1.1.1.1']);
try { let ipv4, ipv6;
ipv4 = await (await asyncExecShell(`curl -4s https://ifconfig.io`)).stdout; try {
} catch (error) {} ipv4 = await (await asyncExecShell(`curl -4s https://ifconfig.io`)).stdout;
try { } catch (error) {}
ipv6 = await (await asyncExecShell(`curl -6s https://ifconfig.io`)).stdout; try {
} catch (error) {} ipv6 = await (await asyncExecShell(`curl -6s https://ifconfig.io`)).stdout;
for (const ssl of ssls) { } catch (error) {}
if (!dev) { for (const ssl of ssls) {
if ( if (!dev) {
certificates.includes(ssl.domain) || if (
certificates.includes(ssl.domain.replace('www.', '')) certificates.includes(ssl.domain) ||
) { certificates.includes(ssl.domain.replace('www.', ''))
// console.log(`Certificate for ${ssl.domain} already exists`); ) {
} else { // console.log(`Certificate for ${ssl.domain} already exists`);
// Checking DNS entry before generating certificate } else {
if (ipv4 || ipv6) { // Checking DNS entry before generating certificate
let domains4 = []; if (ipv4 || ipv6) {
let domains6 = []; let domains4 = [];
try { let domains6 = [];
domains4 = await resolver.resolve4(ssl.domain); try {
} catch (error) {} domains4 = await resolver.resolve4(ssl.domain);
try { } catch (error) {}
domains6 = await resolver.resolve6(ssl.domain); try {
} catch (error) {} domains6 = await resolver.resolve6(ssl.domain);
if (domains4.length > 0 || domains6.length > 0) { } catch (error) {}
if ( if (domains4.length > 0 || domains6.length > 0) {
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) || if (
(ipv6 && domains6.includes(ipv6.replace('\n', ''))) (ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
) { (ipv6 && domains6.includes(ipv6.replace('\n', '')))
console.log('Generating SSL for', ssl.domain); ) {
return await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify); console.log('Generating SSL for', ssl.domain);
return await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
}
} }
} }
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
}
} else {
if (
certificates.includes(ssl.domain) ||
certificates.includes(ssl.domain.replace('www.', ''))
) {
console.log(`Certificate for ${ssl.domain} already exists`);
} else {
// Checking DNS entry before generating certificate
if (ipv4 || ipv6) {
let domains4 = [];
let domains6 = [];
try {
domains4 = await resolver.resolve4(ssl.domain);
} catch (error) {}
try {
domains6 = await resolver.resolve6(ssl.domain);
} catch (error) {}
if (domains4.length > 0 || domains6.length > 0) {
if (
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
) {
console.log('Generating SSL for', ssl.domain);
return;
}
}
}
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
}
}
}
} else {
if (!dev) {
for (const ssl of ssls) {
if (
certificates.includes(ssl.domain) ||
certificates.includes(ssl.domain.replace('www.', ''))
) {
} else {
console.log('Generating SSL for', ssl.domain);
return await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
} }
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
} }
} else { } else {
if ( for (const ssl of ssls) {
certificates.includes(ssl.domain) || if (
certificates.includes(ssl.domain.replace('www.', '')) certificates.includes(ssl.domain) ||
) { certificates.includes(ssl.domain.replace('www.', ''))
console.log(`Certificate for ${ssl.domain} already exists`); ) {
} else { console.log(`Certificate for ${ssl.domain} already exists`);
// Checking DNS entry before generating certificate } else {
if (ipv4 || ipv6) { console.log('Generating SSL for', ssl.domain);
let domains4 = [];
let domains6 = [];
try {
domains4 = await resolver.resolve4(ssl.domain);
} catch (error) {}
try {
domains6 = await resolver.resolve6(ssl.domain);
} catch (error) {}
if (domains4.length > 0 || domains6.length > 0) {
if (
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
) {
console.log('Generating SSL for', ssl.domain);
return;
}
}
} }
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
} }
} }
} }

View File

@@ -308,7 +308,10 @@
"coolify_proxy_settings": "Coolify Proxy Settings", "coolify_proxy_settings": "Coolify Proxy Settings",
"credential_stat_explainer": "Credentials for <a class=\"text-white font-bold\" href=\"{{link}}\" target=\"_blank\">stats</a> page.", "credential_stat_explainer": "Credentials for <a class=\"text-white font-bold\" href=\"{{link}}\" target=\"_blank\">stats</a> page.",
"auto_update_enabled": "Auto update enabled?", "auto_update_enabled": "Auto update enabled?",
"auto_update_enabled_explainer": "Enable automatic updates for Coolify. It will be done automatically behind the scenes, if there is no build process running." "auto_update_enabled_explainer": "Enable automatic updates for Coolify. It will be done automatically behind the scenes, if there is no build process running.",
"generate_www_non_www_ssl": "It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-yellow-500'>both DNS entries</span> set in advance.<br><br>Service needs to be restarted.",
"is_dns_check_enabled": "DNS check enabled?",
"is_dns_check_enabled_explainer": "Enable DNS check during SSL certificate generation. <br>It will check if the DNS entries are set correctly, before trying to get a new cert from Let's Encrypt."
}, },
"team": { "team": {
"pending_invitations": "Pending invitations", "pending_invitations": "Pending invitations",

View File

@@ -64,13 +64,20 @@ export const post: RequestHandler = async (event) => {
}; };
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const { fqdn, isRegistrationEnabled, dualCerts, minPort, maxPort, isAutoUpdateEnabled } = const {
await event.request.json(); fqdn,
isRegistrationEnabled,
dualCerts,
minPort,
maxPort,
isAutoUpdateEnabled,
isDNSCheckEnabled
} = await event.request.json();
try { try {
const { id } = await db.listSettings(); const { id } = await db.listSettings();
await db.prisma.setting.update({ await db.prisma.setting.update({
where: { id }, where: { id },
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled } data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled }
}); });
if (fqdn) { if (fqdn) {
await db.prisma.setting.update({ where: { id }, data: { fqdn } }); await db.prisma.setting.update({ where: { id }, data: { fqdn } });

View File

@@ -43,6 +43,7 @@
let isRegistrationEnabled = settings.isRegistrationEnabled; let isRegistrationEnabled = settings.isRegistrationEnabled;
let dualCerts = settings.dualCerts; let dualCerts = settings.dualCerts;
let isAutoUpdateEnabled = settings.isAutoUpdateEnabled; let isAutoUpdateEnabled = settings.isAutoUpdateEnabled;
let isDNSCheckEnabled = settings.isDNSCheckEnabled;
let minPort = settings.minPort; let minPort = settings.minPort;
let maxPort = settings.maxPort; let maxPort = settings.maxPort;
@@ -78,7 +79,15 @@
if (name === 'isAutoUpdateEnabled') { if (name === 'isAutoUpdateEnabled') {
isAutoUpdateEnabled = !isAutoUpdateEnabled; isAutoUpdateEnabled = !isAutoUpdateEnabled;
} }
await post(`/settings.json`, { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled }); if (name === 'isDNSCheckEnabled') {
isDNSCheckEnabled = !isDNSCheckEnabled;
}
await post(`/settings.json`, {
isRegistrationEnabled,
dualCerts,
isAutoUpdateEnabled,
isDNSCheckEnabled
});
return toast.push(t.get('application.settings_saved')); return toast.push(t.get('application.settings_saved'));
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
@@ -176,13 +185,21 @@
/> />
</div> </div>
</div> </div>
<div class="grid grid-cols-2 items-center">
<Setting
bind:setting={isDNSCheckEnabled}
title={$t('setting.is_dns_check_enabled')}
description={$t('setting.is_dns_check_enabled_explainer')}
on:click={() => changeSettings('isDNSCheckEnabled')}
/>
</div>
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<Setting <Setting
dataTooltip={$t('setting.must_remove_domain_before_changing')} dataTooltip={$t('setting.must_remove_domain_before_changing')}
disabled={isFqdnSet} disabled={isFqdnSet}
bind:setting={dualCerts} bind:setting={dualCerts}
title={$t('application.ssl_www_and_non_www')} title={$t('application.ssl_www_and_non_www')}
description={$t('services.generate_www_non_www_ssl')} description={$t('setting.generate_www_non_www_ssl')}
on:click={() => !isFqdnSet && changeSettings('dualCerts')} on:click={() => !isFqdnSet && changeSettings('dualCerts')}
/> />
</div> </div>