feat: ssl certificate sets custom ssl for applications

This commit is contained in:
Andras Bacsai 2022-09-23 15:21:19 +02:00
parent f9d94fa660
commit 4abe9c6fb2
10 changed files with 102 additions and 45 deletions

View File

@ -0,0 +1,23 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_ApplicationSettings" (
"id" TEXT NOT NULL PRIMARY KEY,
"applicationId" TEXT NOT NULL,
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
"debug" BOOLEAN NOT NULL DEFAULT false,
"previews" BOOLEAN NOT NULL DEFAULT false,
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
"isBot" BOOLEAN NOT NULL DEFAULT false,
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
"isDBBranching" BOOLEAN NOT NULL DEFAULT false,
"isCustomSSL" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isDBBranching", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isDBBranching", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings";
DROP TABLE "ApplicationSettings";
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@ -172,6 +172,7 @@ model ApplicationSettings {
isBot Boolean @default(false)
isPublicRepository Boolean @default(false)
isDBBranching Boolean @default(false)
isCustomSSL Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id])

View File

@ -60,40 +60,49 @@ async function copySSLCertificates() {
const destinations = await prisma.destinationDocker.findMany({ where: { isCoolifyProxyUsed: true, teams: { some: { id: { in: [...teamIds] } } } } })
for (const destination of destinations) {
if (destination.remoteEngine) {
const { id: dockerId, remoteIpAddress, remoteVerified } = destination
if (!remoteVerified) {
continue;
}
// TODO: copy certificates to remote engine
for (const certificate of certificates) {
const { id, key, cert } = certificate
const decryptedKey = decrypt(key)
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey)
await fs.writeFile(`/tmp/${id}-cert.pem`, cert)
await asyncExecShell(`scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/`)
await fs.rm(`/tmp/${id}-key.pem`)
await fs.rm(`/tmp/${id}-cert.pem`)
await executeSSHCmd({ dockerId, command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'` })
await executeSSHCmd({ dockerId, command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/ && rm /tmp/${id}-key.pem` })
await executeSSHCmd({ dockerId, command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/ && rm /tmp/${id}-cert.pem` })
try {
const { id, key, cert } = certificate
const decryptedKey = decrypt(key)
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey)
await fs.writeFile(`/tmp/${id}-cert.pem`, cert)
await asyncExecShell(`scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/`)
await fs.rm(`/tmp/${id}-key.pem`)
await fs.rm(`/tmp/${id}-cert.pem`)
await executeSSHCmd({ dockerId, command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'` })
await executeSSHCmd({ dockerId, command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/ && rm /tmp/${id}-key.pem` })
await executeSSHCmd({ dockerId, command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/ && rm /tmp/${id}-cert.pem` })
} catch (error) {
console.log('Error copying SSL certificates to remote engine', error)
}
}
} else {
for (const certificate of certificates) {
const { id, key, cert } = certificate
const decryptedKey = decrypt(key)
await asyncExecShell(`docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`)
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey)
await fs.writeFile(`/tmp/${id}-cert.pem`, cert)
await asyncExecShell(`docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/`)
await asyncExecShell(`docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/`)
await fs.rm(`/tmp/${id}-key.pem`)
await fs.rm(`/tmp/${id}-cert.pem`)
try {
const { id, key, cert } = certificate
const decryptedKey = decrypt(key)
await asyncExecShell(`docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`)
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey)
await fs.writeFile(`/tmp/${id}-cert.pem`, cert)
await asyncExecShell(`docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/`)
await asyncExecShell(`docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/`)
await fs.rm(`/tmp/${id}-key.pem`)
await fs.rm(`/tmp/${id}-cert.pem`)
} catch (error) {
console.log('Error copying SSL certificates to remote engine', error)
}
}
}
}
} catch (error) {
console.log('Error copying SSL certificates', error)
}
}
async function checkProxies() {

View File

@ -321,17 +321,12 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
export async function saveApplicationSettings(request: FastifyRequest<SaveApplicationSettings>, reply: FastifyReply) {
try {
const { id } = request.params
const { debug, previews, dualCerts, autodeploy, branch, projectId, isBot, isDBBranching } = request.body
// const isDouble = await checkDoubleBranch(branch, projectId);
// if (isDouble && autodeploy) {
// await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
// throw { status: 500, message: 'Cannot activate automatic deployments until only one application is defined for this repository / branch.' }
// }
const { debug, previews, dualCerts, autodeploy, branch, projectId, isBot, isDBBranching, isCustomSSL } = request.body
await prisma.application.update({
where: { id },
data: { fqdn: isBot ? null : undefined, settings: { update: { debug, previews, dualCerts, autodeploy, isBot, isDBBranching } } },
data: { fqdn: isBot ? null : undefined, settings: { update: { debug, previews, dualCerts, autodeploy, isBot, isDBBranching, isCustomSSL } } },
include: { destinationDocker: true }
});
});
return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message })

View File

@ -26,7 +26,7 @@ export interface SaveApplication extends OnlyId {
}
export interface SaveApplicationSettings extends OnlyId {
Querystring: { domain: string; };
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; isDBBranching: boolean };
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; isDBBranching: boolean, isCustomSSL: boolean };
}
export interface DeleteApplication extends OnlyId {
Querystring: { domain: string; };

View File

@ -45,6 +45,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
}
}
await prisma.certificate.create({ data: { cert, key: encrypt(key), team: { connect: { id: teamId } } } })
await prisma.applicationSettings.updateMany({ where: { application: { AND: [{ fqdn: { endsWith: cn } }, { fqdn: { startsWith: 'https' } }] } }, data: { isCustomSSL: true } })
return { message: 'Certificated uploaded' }
} catch ({ status, message }) {
return errorHandler({ status, message });

View File

@ -6,7 +6,7 @@ import { TraefikOtherConfiguration } from "./types";
import { OnlyId } from "../../../types";
function configureMiddleware(
{ id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName, type },
{ id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName, type, isCustomSSL },
traefik
) {
if (isHttps) {
@ -55,7 +55,7 @@ function configureMiddleware(
entrypoints: ['websecure'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/\`)`,
service: `${id}`,
tls: {
tls: isCustomSSL ? true : {
certresolver: 'letsencrypt'
},
middlewares: []
@ -66,7 +66,7 @@ function configureMiddleware(
entrypoints: ['websecure'],
rule: `Host(\`www.${nakedDomain}\`) && PathPrefix(\`/\`)`,
service: `${id}`,
tls: {
tls: isCustomSSL ? true : {
certresolver: 'letsencrypt'
},
middlewares: []
@ -99,7 +99,7 @@ function configureMiddleware(
entrypoints: ['websecure'],
rule: `Host(\`${domain}\`) && PathPrefix(\`/\`)`,
service: `${id}`,
tls: {
tls: isCustomSSL ? true : {
certresolver: 'letsencrypt'
},
middlewares: []
@ -179,7 +179,7 @@ function configureMiddleware(
export async function traefikConfiguration(request, reply) {
try {
const sslpath = '/etc/traefik/acme/custom';
const certificates = await prisma.certificate.findMany()
const certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } } } } })
let parsedCertificates = []
for (const certificate of certificates) {
parsedCertificates.push({
@ -236,7 +236,7 @@ export async function traefikConfiguration(request, reply) {
port,
destinationDocker,
destinationDockerId,
settings: { previews, dualCerts }
settings: { previews, dualCerts, isCustomSSL }
} = application;
if (destinationDockerId) {
const { network, id: dockerId } = destinationDocker;
@ -256,7 +256,8 @@ export async function traefikConfiguration(request, reply) {
isRunning,
isHttps,
isWWW,
isDualCerts: dualCerts
isDualCerts: dualCerts,
isCustomSSL
});
}
if (previews) {
@ -279,7 +280,8 @@ export async function traefikConfiguration(request, reply) {
nakedDomain,
isHttps,
isWWW,
isDualCerts: dualCerts
isDualCerts: dualCerts,
isCustomSSL
});
}
}
@ -547,7 +549,7 @@ export async function remoteTraefikConfiguration(request: FastifyRequest<OnlyId>
const { id } = request.params
try {
const sslpath = '/etc/traefik/acme/custom';
const certificates = await prisma.certificate.findMany({ where: { team: { destinationDocker: { some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true } } } } })
const certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true } } } } })
let parsedCertificates = []
for (const certificate of certificates) {
parsedCertificates.push({

View File

@ -132,7 +132,7 @@
<ellipse cx="12" cy="6" rx="8" ry="3" />
<path d="M4 6v6a8 3 0 0 0 16 0v-6" />
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
</svg>Peristent Volumes</a
</svg>Persistent Volumes</a
>
</li>
<li

View File

@ -61,12 +61,14 @@
let debug = application.settings.debug;
let previews = application.settings.previews;
let dualCerts = application.settings.dualCerts;
let isCustomSSL = application.settings.isCustomSSL;
let autodeploy = application.settings.autodeploy;
let isBot = application.settings.isBot;
let isDBBranching = application.settings.isDBBranching;
let baseDatabaseBranch: any = application?.connectedDatabase?.hostedDatabaseDBName || null;
let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
let isHttps = application.fqdn && application.fqdn.startsWith('https://');
let isNonWWWDomainOK = false;
let isWWWDomainOK = false;
@ -92,7 +94,7 @@
if (window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
application.fqdn = `http://${cuid()}.demo.coolify.io`;
await handleSubmit();
}
}
await getBaseBuildImages();
});
async function getBaseBuildImages() {
@ -141,6 +143,9 @@
if (name === 'autodeploy') {
autodeploy = !autodeploy;
}
if (name === 'isCustomSSL') {
isCustomSSL = !isCustomSSL;
}
if (name === 'isBot') {
if ($status.application.isRunning) return;
isBot = !isBot;
@ -159,6 +164,7 @@
isBot,
autodeploy,
isDBBranching,
isCustomSSL,
branch: application.branch,
projectId: application.projectId
});
@ -185,6 +191,9 @@
if (name === 'isDBBranching') {
isDBBranching = !isDBBranching;
}
if (name === 'isCustomSSL') {
isCustomSSL = !isCustomSSL;
}
return errorNotification(error);
} finally {
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
@ -214,6 +223,11 @@
message: 'Configuration saved.',
type: 'success'
});
if (application.fqdn && application.fqdn.startsWith('https')) {
isHttps = true;
} else {
isHttps = false;
}
} catch (error) {
//@ts-ignore
if (error?.message.startsWith($t('application.dns_not_set_partial_error'))) {
@ -548,6 +562,18 @@
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
/>
</div>
{#if isHttps}
<div class="grid grid-cols-2 items-center pb-4">
<Setting
id="isCustomSSL"
isCenter={false}
bind:setting={isCustomSSL}
title="Use Custom SSL Certificate"
description="Use Custom SSL Certificated added in the Settings/SSL Certificates section. <br><br>By default, the SSL certificate is generated automatically through Let's Encrypt"
on:click={() => changeSettings('isCustomSSL')}
/>
</div>
{/if}
{/if}
{#if application.buildPack === 'python'}
<div class="grid grid-cols-2 items-center">
@ -769,4 +795,4 @@
</div>
</div>
</form>
</div>
</div>

View File

@ -41,7 +41,7 @@
}
}
async function deleteCertificate(id: string) {
const sure = confirm('Are you sure you would like to delete this SSH key?');
const sure = confirm('Are you sure you would like to delete this SSL Certificate?');
if (sure) {
try {
if (!id) return;
@ -89,7 +89,7 @@
<div class="text-sm">No SSL Certificate found</div>
{/if}
</div>
{#if isModalActive}
<input type="checkbox" id="my-modal" class="modal-toggle" />
<div class="modal modal-bottom sm:modal-middle ">