feat: added tls config

Signed-off-by: Avinash <avinash.kumar.cs92@gmail.com>
This commit is contained in:
Avinash 2024-03-06 10:23:02 +05:30
parent 956dd28648
commit 448bdddab9
3 changed files with 192 additions and 8 deletions

View file

@ -0,0 +1,83 @@
import mockedEnv from 'mocked-env';
import dbConfig from './database.config';
describe('databaseConfig', () => {
const dbName = 'test_DB';
const dbPass = 'password';
const dbPort = '5432';
const dbHost = 'localhost';
const dbUser = 'user_DB';
const dbType = 'postgres';
const databaseConfig = {
HD_DATABASE_TYPE: dbType,
HD_DATABASE_NAME: dbName,
HD_DATABASE_PASS: dbPass,
HD_DATABASE_PORT: dbPort,
HD_DATABASE_HOST: dbHost,
HD_DATABASE_USER: dbUser,
};
describe('is correctly parsed', () => {
it('when default database config is passed', () => {
const restore = mockedEnv(databaseConfig, {
clear: true,
});
const config = dbConfig();
expect(config.database).toEqual(dbName);
expect(config.host).toEqual(dbHost);
expect(config.password).toEqual(dbPass);
expect(String(config.port)).toEqual(dbPort);
expect(config.type).toEqual(dbType);
expect(config.username).toEqual(dbUser);
restore();
});
it('when HD_DATABASE_SSL_ENABLED is true', () => {
const needsSSL = true;
const tlsCa = './test/private-api/fixtures/hedgedoc.pem';
const tlsCaContent = ['test-cert\n'];
const shouldRejectUnAuth = true;
const sslCiphers = 'TLS_AES_256...';
const maxSSLVersion = 'TLSv1.3';
const minSSLVersion = 'TLSv1';
const passphrase = 'XSX@W...';
const tlsConfig = {
HD_DATABASE_SSL_ENABLED: String(needsSSL),
HD_DATABASE_SSL_CA_PATH: tlsCa,
HD_DATABASE_SSL_CERT_PATH: tlsCa,
HD_DATABASE_SSL_KEY_PATH: tlsCa,
HD_DATABASE_SSL_REJECT_UNAUTHORIZED: String(shouldRejectUnAuth),
HD_DATABASE_SSL_CIPHERS: sslCiphers,
HD_DATABASE_SSL_MAX_VERSION: maxSSLVersion,
HD_DATABASE_SSL_MIN_VERSION: minSSLVersion,
HD_DATABASE_SSL_PASSPHRASE: passphrase,
};
const envs = {
...databaseConfig,
...tlsConfig,
};
const restore = mockedEnv(envs, { clear: true });
const config = dbConfig();
console.log({ config: config });
expect(config.ssl?.ca).toEqual(tlsCaContent);
expect(config.ssl?.ciphers).toBe(sslCiphers);
expect(config.ssl?.key).toBe(tlsCaContent);
expect(config.ssl?.maxVersion).toBe(maxSSLVersion);
expect(config.ssl?.minVersion).toBe(minSSLVersion);
expect(config.ssl?.cert).toBe(tlsCaContent);
expect(config.ssl?.passphrase).toBe(passphrase);
expect(config.ssl?.rejectUnauthorized).toBe(shouldRejectUnAuth);
// expect(config.ssl).toBeTruthy();
restore();
});
});
});

View file

@ -4,11 +4,22 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { registerAs } from '@nestjs/config';
import * as fs from 'fs';
import * as Joi from 'joi';
import { DatabaseType } from './database-type.enum';
import { buildErrorMessage, parseOptionalNumber } from './utils';
interface SSLConfig {
ca: string;
key: string;
cert: string;
rejectUnauthorized: boolean;
ciphers: string;
maxVersion: string;
minVersion: string;
passphrase: string;
}
export interface DatabaseConfig {
username: string;
password: string;
@ -16,12 +27,34 @@ export interface DatabaseConfig {
host: string;
port: number;
type: DatabaseType;
ssl?: SSLConfig;
}
const getSecret = (path?: string) => {
if (path && fs.existsSync(path)) {
return fs.readFileSync(path, 'utf8');
}
};
const sqlTlsSchema = Joi.object({
rejectUnauthorized: Joi.boolean()
.default(true)
.label('HD_DATABASE_SSL_REJECT_UNAUTHORIZED')
.optional(),
ca: Joi.string().label('HD_DATABASE_SSL_CA_PATH'),
cert: Joi.string().label('HD_DATABASE_SSL_CERT_PATH').optional(),
key: Joi.string().label('HD_DATABASE_SSL_KEY_PATH').optional(),
ciphers: Joi.string().label('HD_DATABASE_SSL_CIPHERS').optional(),
maxVersion: Joi.string().label('HD_DATABASE_SSL_MAX_VERSION').optional(),
minVersion: Joi.string().label('HD_DATABASE_SSL_MIN_VERSION').optional(),
passphrase: Joi.string().label('HD_DATABASE_SSL_PASSPHRASE').optional(),
});
const databaseSchema = Joi.object({
type: Joi.string()
.valid(...Object.values(DatabaseType))
.label('HD_DATABASE_TYPE'),
needsSSL: Joi.boolean().label('HD_DATABASE_SSL'),
// This is the database name, except for SQLite,
// where it is the path to the database file.
@ -46,17 +79,61 @@ const databaseSchema = Joi.object({
then: Joi.number(),
otherwise: Joi.optional(),
}).label('HD_DATABASE_PORT'),
ssl: Joi.when('needsSSL', {
is: Joi.valid('1'),
then: Joi.required(),
otherwise: Joi.optional(),
}).when('type', {
is: Joi.valid(
DatabaseType.MARIADB,
DatabaseType.MYSQL,
DatabaseType.SQLITE,
),
then: sqlTlsSchema,
otherwise: Joi.optional(),
}),
});
const getTlsConfig = (dbType: DatabaseType, needsSSL: boolean) => {
if (!needsSSL) {
return;
}
const sqlTlsConfig = [DatabaseType.MARIADB, DatabaseType.MYSQL].includes(
dbType,
) && {
ssl: {
ca: getSecret(process.env.HD_DATABASE_SSL_CA_PATH),
key: getSecret(process.env.HD_DATABASE_SSL_KEY_PATH),
cert: getSecret(process.env.HD_DATABASE_SSL_CERT_PATH),
rejectUnauthorized:
process.env.HD_DATABASE_SSL_REJECT_UNAUTHORIZED === 'true',
ciphers: process.env.HD_DATABASE_SSL_CIPHERS,
maxVersion: process.env.HD_DATABASE_SSL_MAX_VERSION,
minVersion: process.env.HD_DATABASE_SSL_MIN_VERSION,
passphrase: process.env.HD_DATABASE_SSL_PASSPHRASE,
},
};
return sqlTlsConfig;
};
const dbType = process.env.HD_DATABASE_TYPE;
const needsSSL = process.env.HD_DATABASE_SSL_ENABLED === 'true';
const tlsConfig = getTlsConfig(dbType as DatabaseType, needsSSL);
export default registerAs('databaseConfig', () => {
const databaseConfig = databaseSchema.validate(
{
needsSSL,
type: process.env.HD_DATABASE_TYPE,
username: process.env.HD_DATABASE_USER,
password: process.env.HD_DATABASE_PASS,
database: process.env.HD_DATABASE_NAME,
host: process.env.HD_DATABASE_HOST,
port: parseOptionalNumber(process.env.HD_DATABASE_PORT),
...tlsConfig,
},
{
abortEarly: false,

View file

@ -7,12 +7,36 @@ We officially support and test these databases:
- MariaDB
<!-- markdownlint-disable proper-names -->
| environment variable | default | example | description |
|-----------------------|---------|---------------------|--------------------------------------------------------------------------------------------|
| -------------------- | ------- | ---------------- | ------------------------------------------------------------------------------------------ |
| `HD_DATABASE_TYPE` | - | `postgres` | The database type you want to use. This can be `postgres`, `mysql`, `mariadb` or `sqlite`. |
| `HD_DATABASE_NAME` | - | `hedgedoc` | The name of the database to use. When using SQLite, this is the path to the database file. |
| `HD_DATABASE_HOST` | - | `db.example.com` | The host, where the database runs. *Only if you're **not** using `sqlite`.* |
| `HD_DATABASE_PORT` | - | `5432` | The port, where the database runs. *Only if you're **not** using `sqlite`.* |
| `HD_DATABASE_USER` | - | `hedgedoc` | The user that logs in the database. *Only if you're **not** using `sqlite`.* |
| `HD_DATABASE_PASS` | - | `password` | The password to log into the database. *Only if you're **not** using `sqlite`.* |
| `HD_DATABASE_HOST` | - | `db.example.com` | The host, where the database runs. _Only if you're **not** using `sqlite`._ |
| `HD_DATABASE_PORT` | - | `5432` | The port, where the database runs. _Only if you're **not** using `sqlite`._ |
| `HD_DATABASE_USER` | - | `hedgedoc` | The user that logs in the database. _Only if you're **not** using `sqlite`._ |
| `HD_DATABASE_PASS` | - | `password` | The password to log into the database. _Only if you're **not** using `sqlite`._ |
| `HD_DATABASE_SSL` | '0' | `1` | Pass this value when SSL/TLS configuration is needed |
### SSL/TLS configuration for database
**Note:** `HD_DATABASE_SSL='1'` should be added in .env else following SSL/TLS config will not work
<!-- markdownlint-disable proper-names -->
| environment variable | default | example | description |
| ------------------------------------- | ------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `HD_DATABASE_SSL_CA_PATH` | - | ./mysql-ca.crt | The path of SSL/TLS certificate authority (CA) certificate needed for establishing secure database connections |
| `HD_DATABASE_SSL_CERT_PATH` | - | ./mysql-cert.crt | The path for client certificate to use in the SSL handshake. |
| `HD_DATABASE_SSL_REJECT_UNAUTHORIZED` | 'true' | 'false' | If true server certificate will be verified against the list of supplied CAs. |
| `HD_DATABASE_SSL_KEY_PATH` | - | ./key.pem | This is passed as the key option to [SSL key option](https://github.com/mysqljs/mysql?tab=readme-ov-file#ssl-options) |
| `HD_DATABASE_SSL_CIPHERS` | - | 'TLS_AES_256...' | The ciphers to use to use in the SSL handshake instead of the default ones for Node.js. |
| `HD_DATABASE_SSL_MAX_VERSION` | - | 'TLSv1.3' | This is passed as the maxVersion option for [SSL max_version](https://github.com/mysqljs/mysql?tab=readme-ov-file#ssl-options) |
| `HD_DATABASE_SSL_MIN_VERSION` | - | 'TLSv1.3' | This is passed as the minVersion option for [SSL max_version](https://github.com/mysqljs/mysql?tab=readme-ov-file#ssl-options) |
| `HD_DATABASE_SSL_PASSPHRASE` | - | 'XSX@W...' | Shared passphrase used for a single private key and/or a PFX.This is passed as the passphrase option for [SSL max_version](https://github.com/mysqljs/mysql?tab=readme-ov-file#ssl-options) |
This CA certificate serves as the anchor for verifying the validity of the certificate chain. Ensure that the provided CA certificate is trusted by the database server to establish secure connections.
Check full description for [mysql TLS](https://github.com/mysqljs/mysql?tab=readme-ov-file#ssl-options)
<!-- markdownlint-enable proper-names -->