diff --git a/.gitignore b/.gitignore index ed5b147..84eeb8c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ target/ node_modules/ drizzle/ .vscode/ +certs/ config.json .env .yarn diff --git a/.prettierignore b/.prettierignore index c34419c..4b7038e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,8 +4,10 @@ drizzle/ .vscode/ .github/ .yarn/ +docs/ +certs/ config.json config.example.json package.json yarn.lock -README.md \ No newline at end of file +README.md diff --git a/docker-compose.yml b/docker-compose.yml index 5fd02bd..b7cb6c4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,11 +8,18 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: + - ./certs/psql-server.crt:/var/lib/postgresql/server.crt:ro + - ./certs/psql-server.key:/var/lib/postgresql/server.key:ro - postgres_data:/var/lib/postgresql/data ports: - - "5432:5432" + - '5432:5432' + command: > + postgres + -c ssl=on + -c ssl_cert_file=/var/lib/postgresql/server.crt + -c ssl_key_file=/var/lib/postgresql/server.key healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] + test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER}'] interval: 10s timeout: 5s retries: 5 @@ -23,13 +30,23 @@ services: image: valkey/valkey:8-alpine container_name: valkey restart: always - command: ["valkey-server", "--requirepass", "${VALKEY_PASSWORD}"] ports: - - "6379:6379" + - '6379:6379' volumes: + - ./certs/cache-server.crt:/certs/server.crt:ro + - ./certs/cache-server.key:/certs/server.key:ro + - ./certs/cache-ca.crt:/certs/ca.crt:ro - valkey_data:/data + command: > + valkey-server + --requirepass ${VALKEY_PASSWORD} + --tls-port 6379 + --port 0 + --tls-cert-file /certs/server.crt + --tls-key-file /certs/server.key + --tls-ca-cert-file /certs/ca.crt healthcheck: - test: ["CMD", "valkey-cli", "-a", "${VALKEY_PASSWORD}", "ping"] + test: ['CMD', 'valkey-cli', '-a', '${VALKEY_PASSWORD}', 'ping'] interval: 10s timeout: 5s retries: 5 diff --git a/drizzle.config.ts b/drizzle.config.ts index 6fe8193..eb5083a 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,4 +1,5 @@ import fs from 'node:fs'; +import path from 'node:path'; import { defineConfig } from 'drizzle-kit'; const config = JSON.parse(fs.readFileSync('./config.json', 'utf8')); @@ -10,5 +11,20 @@ export default defineConfig({ dialect: 'postgresql', dbCredentials: { url: database.dbConnectionString, + ssl: (() => { + try { + return { + ca: fs.readFileSync(path.resolve('./certs/psql-ca.crt')), + key: fs.readFileSync(path.resolve('./certs/psql-client.key')), + cert: fs.readFileSync(path.resolve('./certs/psql-server.crt')), + }; + } catch (error) { + console.warn( + 'Failed to load certificates for database, using insecure connection:', + error, + ); + return undefined; + } + })(), }, }); diff --git a/generate-certs.sh b/generate-certs.sh new file mode 100755 index 0000000..e025695 --- /dev/null +++ b/generate-certs.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Get the Effective User ID +_uid="$(id -u)" + +# Create the certificates directory +mkdir -p certs + +# Generate PostgreSQL Certificates +openssl req -new -x509 -days 365 -nodes \ + -out certs/psql-server.crt \ + -keyout certs/psql-server.key \ + -subj "/CN=localhost" + +# Generate Valkey Certificates +openssl req -new -x509 -days 365 -nodes \ + -out certs/cache-server.crt \ + -keyout certs/cache-server.key \ + -subj "/CN=localhost" + +# Get CA Certificates +cp certs/psql-server.crt certs/psql-ca.crt +cp certs/cache-server.crt certs/cache-ca.crt + +# Setup Permissions +chmod 0600 certs/psql-server.key +chmod 0600 certs/cache-server.key + +# Assign Ownership +sudo chown 70:70 certs/psql-*.* +sudo chown 999:1000 certs/cache-*.* + +# Get Client Keys +sudo cp certs/psql-server.key certs/psql-client.key +sudo cp certs/cache-server.key certs/cache-client.key + +# Change Client Key Ownership +sudo chown $_uid:$_uid certs/psql-client.key +sudo chown $_uid:$_uid certs/cache-client.key + +# Change Client Key Permissions +sudo chmod +r certs/psql-client.key +sudo chmod +r certs/cache-client.key diff --git a/src/db/db.ts b/src/db/db.ts index 694b2b1..bc5005f 100644 --- a/src/db/db.ts +++ b/src/db/db.ts @@ -1,6 +1,8 @@ // ======================== // External Imports // ======================== +import fs from 'node:fs'; +import path from 'node:path'; import pkg from 'pg'; import { drizzle } from 'drizzle-orm/node-postgres'; import { Client } from 'discord.js'; @@ -98,7 +100,21 @@ export async function initializeDatabaseConnection(): Promise { // Create new connection pool dbPool = new Pool({ connectionString: config.database.dbConnectionString, - ssl: true, + ssl: (() => { + try { + return { + ca: fs.readFileSync(path.resolve('./certs/psql-ca.crt')), + key: fs.readFileSync(path.resolve('./certs/psql-client.key')), + cert: fs.readFileSync(path.resolve('./certs/psql-server.crt')), + }; + } catch (error) { + console.warn( + 'Failed to load certificates for database, using insecure connection:', + error, + ); + return undefined; + } + })(), connectionTimeoutMillis: 10000, }); diff --git a/src/db/redis.ts b/src/db/redis.ts index 7897caa..8348190 100644 --- a/src/db/redis.ts +++ b/src/db/redis.ts @@ -1,3 +1,5 @@ +import fs from 'node:fs'; +import path from 'node:path'; import Redis from 'ioredis'; import { Client } from 'discord.js'; @@ -91,6 +93,21 @@ async function initializeRedisConnection() { }, maxRetriesPerRequest: 3, enableOfflineQueue: true, + tls: (() => { + try { + return { + ca: fs.readFileSync(path.resolve('./certs/cache-ca.crt')), + key: fs.readFileSync(path.resolve('./certs/cache-client.key')), + cert: fs.readFileSync(path.resolve('./certs/cache-server.crt')), + }; + } catch (error) { + console.warn( + 'Failed to load certificates for cache, using insecure connection:', + error, + ); + return undefined; + } + })(), }); // ======================== diff --git a/src/util/helpers.ts b/src/util/helpers.ts index 6279d22..2154c19 100644 --- a/src/util/helpers.ts +++ b/src/util/helpers.ts @@ -1,5 +1,6 @@ import Canvas from '@napi-rs/canvas'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import { AttachmentBuilder,