Skip to content
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
10 changes: 8 additions & 2 deletions Config/database.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class DATABASE_CONFIG {
'database' => '{{ MYSQL_DATABASE }}',
'prefix' => '',
'encoding' => 'utf8mb4 COLLATE utf8mb4_unicode_ci',
{% if MYSQL_USE_TLS %}
'ssl_ca' => '{{ MYSQL_TLS_CA }}',
{% endif %}
'settings' => [
'time_zone' => '"+00:00"',
{% if MYSQL_SETTINGS -%}
Expand All @@ -19,9 +22,12 @@ class DATABASE_CONFIG {
{% endfor %}
{%- endif %}
],
{% if MYSQL_FLAGS %}
{% if MYSQL_FLAGS or (MYSQL_USE_TLS and not MYSQL_TLS_VERIFY) %}
'flags' => [
{% for flag, value in MYSQL_FLAGS.items() %}
{% if MYSQL_USE_TLS and not MYSQL_TLS_VERIFY %}
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false,
{% endif %}
{% for flag, value in (MYSQL_FLAGS or {}).items() %}
'{{ flag }}' => '{{ value }}',
{% endfor %}
]
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ MISP requires MySQL or MariaDB database.
* `MYSQL_DATABASE` (required, string) - database name
* `MYSQL_SETTINGS` (optional, string) - database settings, which should be set for each db connection (JSON dict, or semicolon separated key value pairs)
* `MYSQL_FLAGS` (required, string) - PDO flags which should be set for each db connection (JSON dict, or semicolon separated key value pairs)
* `MYSQL_USE_TLS` (optional, bool, default `false`) - connect to the database over TLS; the server certificate and hostname are verified unless `MYSQL_TLS_VERIFY` is disabled
* `MYSQL_TLS_CA` (optional, string, default `/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem`) - path to the CA certificate (PEM) used to verify the database server certificate, for example a mounted file with your internal CA
* `MYSQL_TLS_VERIFY` (optional, bool, default `true`) - verify the database server certificate and hostname; set to `false` only if the server certificate cannot be verified (for example the self-signed certificate MariaDB generates automatically), the connection is then still encrypted, but not protected against man-in-the-middle attacks

### Redis

Expand Down
9 changes: 9 additions & 0 deletions bin/misp_create_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ def parse_mysql_settings(variable_name: str, value: str) -> dict:
"MYSQL_DATABASE": Option(required=True),
"MYSQL_SETTINGS": Option(required=False, parser=parse_mysql_settings),
"MYSQL_FLAGS": Option(required=False, parser=parse_mysql_settings),
"MYSQL_USE_TLS": Option(typ=bool, default=False),
"MYSQL_TLS_CA": Option(default="/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"),
"MYSQL_TLS_VERIFY": Option(typ=bool, default=True),
# Redis
"REDIS_HOST": Option(required=True),
"REDIS_PORT": Option(typ=int, default=6379, validation=check_uint),
Expand Down Expand Up @@ -594,6 +597,12 @@ def create():
if not variables[var]:
variables[var] = variables[var.replace("_INNER", "")]

if variables["MYSQL_USE_TLS"] and not os.access(variables["MYSQL_TLS_CA"], os.R_OK):
error(f"TLS for MySQL is enabled, but CA file '{variables['MYSQL_TLS_CA']}' from 'MYSQL_TLS_CA' does not exist or is not readable")

if variables["MYSQL_USE_TLS"] and not variables["MYSQL_TLS_VERIFY"]:
warning("TLS certificate verification for MySQL is disabled ('MYSQL_TLS_VERIFY' is false). The connection is encrypted, but not protected against man-in-the-middle attacks.")

if variables["OIDC_LOGIN"]:
for var in ("OIDC_PROVIDER", "OIDC_CLIENT_CRYPTO_PASS", "OIDC_CLIENT_ID", "OIDC_CLIENT_SECRET"):
if not variables[var]:
Expand Down
42 changes: 36 additions & 6 deletions bin/misp_create_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,49 @@
from pymysql.connections import Connection
from typing import Optional, TextIO

DEFAULT_TLS_CA = "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"

def connect(host: str, port: int, user: str, password: Optional[str]) -> pymysql.connections.Connection:
connection = pymysql.connect(host=host, port=port, user=user, password=password, connect_timeout=1, client_flag=CLIENT.MULTI_STATEMENTS)

def error(message: str):
print(f"ERROR: {message}", file=sys.stderr)
sys.exit(1)


def convert_bool(variable_name: str, value: str) -> bool:
value = value.lower()
if value in ("true", "1", "yes", "on"):
return True
if value in ("false", "0", "no", "off", ""):
return False

error(f"Environment variable '{variable_name}' must be boolean (`true`, `1`, `yes`, `false`, `0` or `no`), `{value}` given")


def tls_options() -> Optional[dict]:
if not convert_bool("MYSQL_USE_TLS", os.environ.get("MYSQL_USE_TLS", "false")):
return None
if convert_bool("MYSQL_TLS_VERIFY", os.environ.get("MYSQL_TLS_VERIFY", "true")):
# With a CA file, PyMySQL requires TLS and verifies both the certificate and the hostname
return {"ca": os.environ.get("MYSQL_TLS_CA") or DEFAULT_TLS_CA}
# Connection is still encrypted, but the server certificate is not verified
return {"verify_mode": False, "check_hostname": False}


def connect(host: str, port: int, user: str, password: Optional[str], ssl: Optional[dict] = None) -> pymysql.connections.Connection:
connection = pymysql.connect(host=host, port=port, user=user, password=password, connect_timeout=1, client_flag=CLIENT.MULTI_STATEMENTS, ssl=ssl)
connection.ping()
return connection


def wait_for_connection(host: str, port: int, user: str, password: Optional[str]) -> Connection:
logging.info(f"Connecting to MySQL server {host}:{port}")
def wait_for_connection(host: str, port: int, user: str, password: Optional[str], ssl: Optional[dict] = None) -> Connection:
tls_info = ""
if ssl:
tls_info = " using TLS" if "ca" in ssl else " using TLS without certificate verification"
logging.info(f"Connecting to MySQL server {host}:{port}{tls_info}")
last_exception = None
for i in range(1, 10):
try:
return connect(host, port, user, password)
return connect(host, port, user, password, ssl)
except Exception as e:
last_exception = e
logging.info("Waiting for database connection...")
Expand Down Expand Up @@ -60,7 +90,7 @@ def main():
port = os.environ.get("MYSQL_PORT")
port = 3306 if port is None else int(port)

connection = wait_for_connection(args.host, port, args.user, password)
connection = wait_for_connection(args.host, port, args.user, password, tls_options())

if is_schema_created(connection, args.database):
logging.info("Database schema is already created.")
Expand Down