From 23b22e5ca852cbf4cf8e3f63a002d88e4fba45c8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 17 Jul 2023 13:50:26 +0200 Subject: [PATCH] wip: reencrypt everything --- Dockerfile | 2 +- apps/api/prisma/seed.js | 199 +++++++++++++++++++++++++++++++++---- apps/api/src/lib/common.ts | 197 ++++++++++++++++++------------------ 3 files changed, 281 insertions(+), 117 deletions(-) diff --git a/Dockerfile b/Dockerfile index 80f56f06a..e45a29852 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ ARG DOCKER_COMPOSE_VERSION=2.6.1 # https://github.com/buildpacks/pack/releases ARG PACK_VERSION=0.27.0 -RUN apt update && apt -y install --no-install-recommends ca-certificates git git-lfs openssh-client curl jq cmake sqlite3 openssl psmisc python3 +RUN apt update && apt -y install --no-install-recommends ca-certificates git git-lfs openssh-client curl jq cmake sqlite3 openssl psmisc python3 vim RUN apt-get clean autoclean && apt-get autoremove --yes && rm -rf /var/lib/{apt,dpkg,cache,log}/ RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION} RUN npm install -g npm@${PNPM_VERSION} diff --git a/apps/api/prisma/seed.js b/apps/api/prisma/seed.js index 3e5dab103..659592c81 100644 --- a/apps/api/prisma/seed.js +++ b/apps/api/prisma/seed.js @@ -95,45 +95,201 @@ async function main() { } async function reEncryptSecrets() { const { execaCommand } = await import('execa'); + const date = new Date().getTime(); await execaCommand('env | grep COOLIFY > .env', { shell: true }); const secretOld = process.env['COOLIFY_SECRET_KEY']; let secretNew = process.env['COOLIFY_SECRET_KEY_BETTER']; if (!secretNew) { - console.log('no new secret found, generating new one'); + console.log('No COOLIFY_SECRET_KEY_BETTER found... Generating new one...'); const { stdout: newKey } = await execaCommand( 'openssl rand -base64 1024 | sha256sum | base64 | head -c 32', { shell: true } ); - await execaCommand('echo "COOLIFY_SECRET_KEY_BETTER=' + newKey + '" >> .env ', { shell: true }); - await execaCommand(`sed -i '/COOLIFY_SECRET_KEY=/d' .env`, { shell: true }); - await execaCommand(`echo "COOLIFY_SECRET_KEY=${newKey}" >> .env`, { shell: true }); secretNew = newKey; } if (secretOld !== secretNew) { - console.log('secrets are different, so re-encrypting'); + console.log( + 'Secrets (COOLIFY_SECRET_KEY & COOLIFY_SECRET_KEY_BETTER) are different, so re-encrypting everything...' + ); + await execaCommand(`sed -i '/COOLIFY_SECRET_KEY=/d' .env`, { shell: true }); + await execaCommand(`sed -i '/COOLIFY_SECRET_KEY_BETTER=/d' .env`, { shell: true }); + await execaCommand(`echo "COOLIFY_SECRET_KEY=${secretNew}" >> .env`, { shell: true }); + await execaCommand('echo "COOLIFY_SECRET_KEY_BETTER=' + secretNew + '" >> .env ', { + shell: trueps + }); + await execaCommand(`echo "COOLIFY_SECRET_KEY_OLD_${date}=${secretOld}" >> .env`, { + shell: true + }); + const transactions = []; const secrets = await prisma.secret.findMany(); if (secrets.length > 0) { for (const secret of secrets) { const value = decrypt(secret.value, secretOld); const newValue = encrypt(value, secretNew); - await prisma.secret.update({ - where: { id: secret.id }, - data: { value: newValue } - }); + transactions.push( + prisma.secret.update({ + where: { id: secret.id }, + data: { value: newValue } + }) + ); } } + const serviceSecrets = await prisma.serviceSecret.findMany(); + if (serviceSecrets.length > 0) { + for (const secret of serviceSecrets) { + const value = decrypt(secret.value, secretOld); + const newValue = encrypt(value, secretNew); + transactions.push( + prisma.serviceSecret.update({ + where: { id: secret.id }, + data: { value: newValue } + }) + ); + } + } + const gitlabApps = await prisma.gitlabApp.findMany(); + if (gitlabApps.length > 0) { + for (const gitlabApp of gitlabApps) { + const value = decrypt(gitlabApp.privateSshKey, secretOld); + const newValue = encrypt(value, secretNew); + const appSecret = decrypt(gitlabApp.appSecret, secretOld); + const newAppSecret = encrypt(appSecret, secretNew); + transactions.push( + prisma.gitlabApp.update({ + where: { id: gitlabApp.id }, + data: { privateSshKey: newValue, appSecret: newAppSecret } + }) + ); + } + } + const githubApps = await prisma.githubApp.findMany(); + if (githubApps.length > 0) { + for (const githubApp of githubApps) { + const clientSecret = decrypt(githubApp.clientSecret, secretOld); + const newClientSecret = encrypt(clientSecret, secretNew); + const webhookSecret = decrypt(githubApp.webhookSecret, secretOld); + const newWebhookSecret = encrypt(webhookSecret, secretNew); + const privateKey = decrypt(githubApp.privateKey, secretOld); + const newPrivateKey = encrypt(privateKey, secretNew); + + transactions.push( + prisma.githubApp.update({ + where: { id: githubApp.id }, + data: { + clientSecret: newClientSecret, + webhookSecret: newWebhookSecret, + privateKey: newPrivateKey + } + }) + ); + } + } + const databases = await prisma.database.findMany(); + if (databases.length > 0) { + for (const database of databases) { + const dbUserPassword = decrypt(database.dbUserPassword, secretOld); + const newDbUserPassword = encrypt(dbUserPassword, secretNew); + const rootUserPassword = decrypt(database.rootUserPassword, secretOld); + const newRootUserPassword = encrypt(rootUserPassword, secretNew); + transactions.push( + prisma.database.update({ + where: { id: database.id }, + data: { + dbUserPassword: newDbUserPassword, + rootUserPassword: newRootUserPassword + } + }) + ); + } + } + const databaseSecrets = await prisma.databaseSecret.findMany(); + if (databaseSecrets.length > 0) { + for (const databaseSecret of databaseSecrets) { + const value = decrypt(databaseSecret.value, secretOld); + const newValue = encrypt(value, secretNew); + transactions.push( + prisma.databaseSecret.update({ + where: { id: databaseSecret.id }, + data: { value: newValue } + }) + ); + } + } + const wordpresses = await prisma.wordpress.findMany(); + if (wordpresses.length > 0) { + for (const wordpress of wordpresses) { + const value = decrypt(wordpress.ftpHostKey, secretOld); + const newValue = encrypt(value, secretNew); + const ftpHostKeyPrivate = decrypt(wordpress.ftpHostKeyPrivate, secretOld); + const newFtpHostKeyPrivate = encrypt(ftpHostKeyPrivate, secretNew); + let newFtpPassword = undefined; + if (wordpress.ftpPassword != null) { + const ftpPassword = decrypt(wordpress.ftpPassword, secretOld); + newFtpPassword = encrypt(ftpPassword, secretNew); + } + + transactions.push( + prisma.wordpress.update({ + where: { id: wordpress.id }, + data: { + ftpHostKey: newValue, + ftpHostKeyPrivate: newFtpHostKeyPrivate, + ftpPassword: newFtpPassword + } + }) + ); + } + } + const sshKeys = await prisma.sshKey.findMany(); + if (sshKeys.length > 0) { + for (const key of sshKeys) { + const value = decrypt(key.privateKey, secretOld); + const newValue = encrypt(value, secretNew); + transactions.push( + prisma.sshKey.update({ + where: { id: key.id }, + data: { + privateKey: newValue + } + }) + ); + } + } + const dockerRegistries = await prisma.dockerRegistry.findMany(); + if (dockerRegistries.length > 0) { + for (const registry of dockerRegistries) { + const value = decrypt(registry.password, secretOld); + const newValue = encrypt(value, secretNew); + transactions.push( + prisma.dockerRegistry.update({ + where: { id: registry.id }, + data: { + password: newValue + } + }) + ); + } + } + const certificates = await prisma.certificate.findMany(); + if (certificates.length > 0) { + for (const certificate of certificates) { + const value = decrypt(certificate.key, secretOld); + const newValue = encrypt(value, secretNew); + transactions.push( + prisma.certificate.update({ + where: { id: certificate.id }, + data: { + key: newValue + } + }) + ); + } + } + await prisma.$transaction(transactions); } else { console.log('secrets are the same, so no need to re-encrypt'); } } -main() - .catch((e) => { - console.error(e); - process.exit(1); - }) - .finally(async () => { - await prisma.$disconnect(); - }); const encrypt = (text, secret) => { if (text && secret) { @@ -162,6 +318,15 @@ const decrypt = (hashString, secret) => { } } }; + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); reEncryptSecrets() .catch((e) => { console.error(e); diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index 8bf2891c1..b64b1bbe7 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -868,97 +868,97 @@ export function generatePassword({ type DatabaseConfiguration = | { - volume: string; - image: string; - command?: string; - ulimits: Record; - privatePort: number; - environmentVariables: { - MYSQL_DATABASE: string; - MYSQL_PASSWORD: string; - MYSQL_ROOT_USER: string; - MYSQL_USER: string; - MYSQL_ROOT_PASSWORD: string; - }; - } + volume: string; + image: string; + command?: string; + ulimits: Record; + privatePort: number; + environmentVariables: { + MYSQL_DATABASE: string; + MYSQL_PASSWORD: string; + MYSQL_ROOT_USER: string; + MYSQL_USER: string; + MYSQL_ROOT_PASSWORD: string; + }; + } | { - volume: string; - image: string; - command?: string; - ulimits: Record; - privatePort: number; - environmentVariables: { - MONGO_INITDB_ROOT_USERNAME?: string; - MONGO_INITDB_ROOT_PASSWORD?: string; - MONGODB_ROOT_USER?: string; - MONGODB_ROOT_PASSWORD?: string; - }; - } + volume: string; + image: string; + command?: string; + ulimits: Record; + privatePort: number; + environmentVariables: { + MONGO_INITDB_ROOT_USERNAME?: string; + MONGO_INITDB_ROOT_PASSWORD?: string; + MONGODB_ROOT_USER?: string; + MONGODB_ROOT_PASSWORD?: string; + }; + } | { - volume: string; - image: string; - command?: string; - ulimits: Record; - privatePort: number; - environmentVariables: { - MARIADB_ROOT_USER: string; - MARIADB_ROOT_PASSWORD: string; - MARIADB_USER: string; - MARIADB_PASSWORD: string; - MARIADB_DATABASE: string; - }; - } + volume: string; + image: string; + command?: string; + ulimits: Record; + privatePort: number; + environmentVariables: { + MARIADB_ROOT_USER: string; + MARIADB_ROOT_PASSWORD: string; + MARIADB_USER: string; + MARIADB_PASSWORD: string; + MARIADB_DATABASE: string; + }; + } | { - volume: string; - image: string; - command?: string; - ulimits: Record; - privatePort: number; - environmentVariables: { - POSTGRES_PASSWORD?: string; - POSTGRES_USER?: string; - POSTGRES_DB?: string; - POSTGRESQL_POSTGRES_PASSWORD?: string; - POSTGRESQL_USERNAME?: string; - POSTGRESQL_PASSWORD?: string; - POSTGRESQL_DATABASE?: string; - }; - } + volume: string; + image: string; + command?: string; + ulimits: Record; + privatePort: number; + environmentVariables: { + POSTGRES_PASSWORD?: string; + POSTGRES_USER?: string; + POSTGRES_DB?: string; + POSTGRESQL_POSTGRES_PASSWORD?: string; + POSTGRESQL_USERNAME?: string; + POSTGRESQL_PASSWORD?: string; + POSTGRESQL_DATABASE?: string; + }; + } | { - volume: string; - image: string; - command?: string; - ulimits: Record; - privatePort: number; - environmentVariables: { - REDIS_AOF_ENABLED: string; - REDIS_PASSWORD: string; - }; - } + volume: string; + image: string; + command?: string; + ulimits: Record; + privatePort: number; + environmentVariables: { + REDIS_AOF_ENABLED: string; + REDIS_PASSWORD: string; + }; + } | { - volume: string; - image: string; - command?: string; - ulimits: Record; - privatePort: number; - environmentVariables: { - COUCHDB_PASSWORD: string; - COUCHDB_USER: string; - }; - } + volume: string; + image: string; + command?: string; + ulimits: Record; + privatePort: number; + environmentVariables: { + COUCHDB_PASSWORD: string; + COUCHDB_USER: string; + }; + } | { - volume: string; - image: string; - command?: string; - ulimits: Record; - privatePort: number; - environmentVariables: { - EDGEDB_SERVER_PASSWORD: string; - EDGEDB_SERVER_USER: string; - EDGEDB_SERVER_DATABASE: string; - EDGEDB_SERVER_TLS_CERT_MODE: string; - }; - }; + volume: string; + image: string; + command?: string; + ulimits: Record; + privatePort: number; + environmentVariables: { + EDGEDB_SERVER_PASSWORD: string; + EDGEDB_SERVER_USER: string; + EDGEDB_SERVER_DATABASE: string; + EDGEDB_SERVER_TLS_CERT_MODE: string; + }; + }; export function generateDatabaseConfiguration(database: any): DatabaseConfiguration { const { id, dbUser, dbUserPassword, rootUser, rootUserPassword, defaultDatabase, version, type } = database; @@ -1057,9 +1057,8 @@ export function generateDatabaseConfiguration(database: any): DatabaseConfigurat }; if (isARM()) { configuration.volume = `${id}-${type}-data:/data`; - configuration.command = `/usr/local/bin/redis-server --appendonly ${ - appendOnly ? 'yes' : 'no' - } --requirepass ${dbUserPassword}`; + configuration.command = `/usr/local/bin/redis-server --appendonly ${appendOnly ? 'yes' : 'no' + } --requirepass ${dbUserPassword}`; } return configuration; } else if (type === 'couchdb') { @@ -1144,12 +1143,12 @@ export type ComposeFileService = { command?: string; ports?: string[]; build?: - | { - context: string; - dockerfile: string; - args?: Record; - } - | string; + | { + context: string; + dockerfile: string; + args?: Record; + } + | string; deploy?: { restart_policy?: { condition?: string; @@ -1220,7 +1219,7 @@ export const createDirectories = async ({ let workdirFound = false; try { workdirFound = !!(await fs.stat(workdir)); - } catch (error) {} + } catch (error) { } if (workdirFound) { await executeCommand({ command: `rm -fr ${workdir}` }); } @@ -1744,7 +1743,7 @@ export async function stopBuild(buildId, applicationId) { } } count++; - } catch (error) {} + } catch (error) { } }, 100); }); } @@ -1767,7 +1766,7 @@ export async function cleanupDockerStorage(dockerId) { // Cleanup images that are not used by any container try { await executeCommand({ dockerId, command: `docker image prune -af` }); - } catch (error) {} + } catch (error) { } // Prune coolify managed containers try { @@ -1775,12 +1774,12 @@ export async function cleanupDockerStorage(dockerId) { dockerId, command: `docker container prune -f --filter "label=coolify.managed=true"` }); - } catch (error) {} + } catch (error) { } // Cleanup build caches try { await executeCommand({ dockerId, command: `docker builder prune -af` }); - } catch (error) {} + } catch (error) { } } export function persistentVolumes(id, persistentStorage, config) {