Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added tls config #5530

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions backend/src/config/database.config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import mockedEnv from 'mocked-env';
Fixed Show fixed Hide fixed

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();
});
});
});
77 changes: 77 additions & 0 deletions backend/src/config/database.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,57 @@
* 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;
database: string;
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(),
Avi98 marked this conversation as resolved.
Show resolved Hide resolved
});

Check warning on line 52 in backend/src/config/database.config.ts

View check run for this annotation

Codecov / codecov/patch

backend/src/config/database.config.ts#L52

Added line #L52 was not covered by tests
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.
Expand All @@ -46,17 +79,61 @@
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,
Expand Down
40 changes: 32 additions & 8 deletions docs/content/references/config/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.* |

| 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_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 -->
Loading