Skip to content

Commit

Permalink
feat: add mysql ssl support
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Ionov committed Jan 26, 2024
1 parent 01317d0 commit 1108bd5
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 105 deletions.
37 changes: 19 additions & 18 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
volumes:
# mysql:
mysql:
psql:

services:
# mysql:
# image: mysql:8.0.29-debian
# container_name: mysql
# command: >
# --default-authentication-plugin=mysql_native_password
# restart: always
# environment:
# MYSQL_ROOT_PASSWORD: example
# MYSQL_DATABASE: world # https://dev.mysql.com/doc/world-x-setup/en/world-x-setup-installation.html
# ports:
# - 3306:3306
# volumes:
# - mysql:/var/lib/mysql
# - ./dev/mysql/mysql.conf:/etc/mysql/conf.d/mysql.conf
# # extract in dev/mysql folder before mounting
# - ./dev/mysql/world.sql:/docker-entrypoint-initdb.d/world.sql:ro
mysql:
image: mysql:8.0.29-debian
container_name: mysql
command: >
--default-authentication-plugin=mysql_native_password
restart: always
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: world # https://dev.mysql.com/doc/world-x-setup/en/world-x-setup-installation.html
ports:
- 3306:3306
volumes:
- mysql:/var/lib/mysql
- ./dev/mysql/mysql.cnf:/etc/mysql/conf.d/mysql.cnf
# extract in dev/mysql folder before mounting
- ./dev/mysql/world.sql:/docker-entrypoint-initdb.d/world.sql:ro
- ./dev/certs:/etc/ssl/mysql/certs
psql:
image: postgres:16-bookworm
container_name: psql
Expand All @@ -35,7 +36,7 @@ services:
volumes:
- psql:/var/lib/postgresql/data
- ./dev/psql/postgres.conf:/etc/postgresql/postgresql.conf
- ./dev/psql/pg_hba.conf:/var/lib/postgresql/data/pg_hba.conf
# - ./dev/psql/pg_hba.conf:/var/lib/postgresql/data/pg_hba.conf
# extract in dev/psql folder before mounting
- ./dev/psql/dvdrental.tar:/tmp/dvdrental.tar
- ./dev/psql/init.sh:/docker-entrypoint-initdb.d/init.sh
Expand Down
8 changes: 8 additions & 0 deletions dev/mysql/mysql.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/8.0/en/server-configuration-defaults.html

[mysqld]
ssl_ca=/etc/ssl/mysql/certs/ca.pem
ssl_cert=/etc/ssl/mysql/certs/server.pem
ssl_key=/etc/ssl/mysql/certs/server.key
# SHOW SESSION STATUS LIKE 'Ssl_cipher';
39 changes: 0 additions & 39 deletions dev/mysql/mysql.conf

This file was deleted.

55 changes: 49 additions & 6 deletions src-tauri/src/engine/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use deadpool_postgres::{
Config as PsqlConfig, ManagerConfig as PsqlManagerConfig, RecyclingMethod, SslMode,
};
use deadpool_sqlite::Config as SqliteConfig;
use mysql::{Opts, OptsBuilder, Pool as MysqlPool};
use mysql::{ClientIdentity, Opts, OptsBuilder, Pool as MysqlPool, SslOpts};
use openssl::ssl::{SslConnector, SslFiletype, SslMethod, SslVerifyMode};
use postgres::NoTls;
use postgres_openssl::MakeTlsConnector;
Expand All @@ -23,11 +23,54 @@ pub async fn init_conn(cfg: ConnectionConfig) -> Result<InitiatedConnection, Err
if cfg.mode == Mode::File {
return Err(anyhow::anyhow!("File mode is not supported for Mysql").into());
}
let builder = OptsBuilder::new();
let builder = builder
.from_hash_map(&cfg.credentials)?
.tcp_connect_timeout(Some(std::time::Duration::from_secs(15)))
.prefer_socket(cfg.mode == Mode::Socket);
let mut credentials = cfg.credentials.clone();
let ssl_keys = vec!["ssl_mode", "ca_cert", "client_p12", "client_p12_pass"];
let mut ssl_cfg = credentials.clone();
ssl_cfg.retain(|k, _| ssl_keys.contains(&k.as_str()));
for key in ssl_keys {
credentials.remove(key);
}
let ssl_mode = ssl_cfg.get("ssl_mode").cloned().unwrap_or("".to_string());
let ca_cert = ssl_cfg.get("ca_cert").cloned().unwrap_or("".to_string());
let client_p12 = ssl_cfg.get("client_p12").cloned().unwrap_or("".to_string());
let client_p12_pass = ssl_cfg
.get("client_p12_pass")
.cloned()
.unwrap_or("".to_string());

let builder = match ssl_mode.as_str() {
"prefer" | "require" => {
let mut ssl_opts = if !client_p12.is_empty() && !client_p12_pass.is_empty() {
let identity = ClientIdentity::new(PathBuf::from(&client_p12))
.with_password(client_p12_pass);
let sslopts = SslOpts::default().with_client_identity(Some(identity));
sslopts
} else if !client_p12.is_empty() {
// TODO: this does not work without a password
// https://github.com/blackbeam/rust-mysql-simple/issues/369
let identity = ClientIdentity::new(PathBuf::from(&client_p12));
let sslopts = SslOpts::default().with_client_identity(Some(identity));
sslopts
} else {
SslOpts::default()
};
if !ca_cert.is_empty() {
ssl_opts = ssl_opts.with_root_cert_path(Some(PathBuf::from(&ca_cert)));
}
ssl_opts = ssl_opts.with_danger_accept_invalid_certs(true);

OptsBuilder::new()
.from_hash_map(&credentials)?
.tcp_connect_timeout(Some(std::time::Duration::from_secs(15)))
.prefer_socket(cfg.mode == Mode::Socket)
.ssl_opts(ssl_opts)
}
_ => OptsBuilder::new()
.from_hash_map(&credentials)?
.tcp_connect_timeout(Some(std::time::Duration::from_secs(15)))
.prefer_socket(cfg.mode == Mode::Socket),
};

let opts = Opts::from(builder);
let cloned = opts.clone();
match MysqlPool::new(opts.clone()) {
Expand Down
8 changes: 4 additions & 4 deletions src-tauri/src/engine/types/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ impl ConnectionConfig {
"tcp_connect_timeout_ms",
"stmt_cache_size",
"secure_auth",
// TODO: check not official config keys, might be needed to remove before creating the config
"ssl_mode", // disable, prefer, require, if with ca_cert ssl verify mode is peer
// later before connecting those keys are omitted and everything else is passed to cfg builder
"ssl_mode",
"ca_cert",
"client_cert",
"client_key",
"client_p12",
"client_p12_pass",
];
let _ = credentials.retain(|k, _| allowed_keys.contains(&k.as_str()));
let schema = credentials
Expand Down
110 changes: 74 additions & 36 deletions src/components/Screens/Home/Connections/AddConnectionForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ const CredentialsSchema = z.union([
ca_cert: zstr.optional().or(z.literal('')),
client_cert: zstr.optional().or(z.literal('')),
client_key: zstr.optional().or(z.literal('')),
client_p12: zstr.optional().or(z.literal('')),
client_p12_pass: zstr.optional().or(z.literal('')),
}),
z.object({
socket: zstr,
Expand Down Expand Up @@ -82,6 +84,8 @@ const defaultValues = {
ca_cert: '',
client_cert: '',
client_key: '',
client_p12: '',
client_p12_pass: '',
},
};

Expand Down Expand Up @@ -128,6 +132,7 @@ const AddConnectionForm = () => {
initialValues: defaultValues,
extend: validator({ schema }),
});
form; // ts-server - stfu

return (
<div class="p-3 w-full flex justify-center items-around pt-20 rounded-tl-lg bg-base-200">
Expand Down Expand Up @@ -301,44 +306,77 @@ const AddConnectionForm = () => {
}}
/>
</div>
<div class="col-span-12">
<div class="block">
<Label label={t('add_connection_form.labels.client_cert')} for="credentials.client_cert" />
<Show when={data('dialect') === Dialect.Postgresql}>
<div class="col-span-12">
<div class="block">
<Label label={t('add_connection_form.labels.client_cert')} for="credentials.client_cert" />
</div>
<FilePicker
name="credentials.client_cert"
onClear={() => {
setFields('credentials.client_cert', '');
}}
onChange={async () => {
const path = (await open({
multiple: false,
title: t('add_connection_form.select_file'),
})) as string;
if (!path) return;
setFields('credentials.client_cert', path);
}}
/>
</div>
<FilePicker
name="credentials.client_cert"
onClear={() => {
setFields('credentials.client_cert', '');
}}
onChange={async () => {
const path = (await open({
multiple: false,
title: t('add_connection_form.select_file'),
})) as string;
if (!path) return;
setFields('credentials.client_cert', path);
}}
/>
</div>
<div class="col-span-12">
<div class="block">
<Label label={t('add_connection_form.labels.client_key')} for="credentials.client_key" />
</Show>
<Show when={data('dialect') === Dialect.Postgresql}>
<div class="col-span-12">
<div class="block">
<Label label={t('add_connection_form.labels.client_key')} for="credentials.client_key" />
</div>
<FilePicker
name="credentials.client_key"
onClear={() => {
setFields('credentials.client_key', '');
}}
onChange={async () => {
const path = (await open({
multiple: false,
title: t('add_connection_form.select_file'),
})) as string;
if (!path) return;
setFields('credentials.client_key', path);
}}
/>
</div>
<FilePicker
name="credentials.client_key"
onClear={() => {
setFields('credentials.client_key', '');
}}
onChange={async () => {
const path = (await open({
multiple: false,
title: t('add_connection_form.select_file'),
})) as string;
if (!path) return;
setFields('credentials.client_key', path);
}}
/>
</div>
</Show>
<Show when={data('dialect') === Dialect.Mysql}>
<div class="col-span-12">
<div class="block">
<Label label={t('add_connection_form.labels.client_p12')} for="credentials.client_cert" />
</div>
<FilePicker
name="credentials.client_p12"
onClear={() => {
setFields('credentials.client_p12', '');
}}
onChange={async () => {
const path = (await open({
multiple: false,
title: t('add_connection_form.select_file'),
})) as string;
if (!path) return;
setFields('credentials.client_p12', path);
}}
/>
</div>
</Show>
<Show when={data('dialect') === Dialect.Mysql}>
<div class="col-span-12">
<TextInput
label={t('add_connection_form.labels.client_p12_pass')}
name="credentials.client_p12_pass"
/>
</div>
</Show>
</Show>
<div class="col-span-8">
<TextInput label={t('add_connection_form.labels.name')} errors={errors('name')} name="name" />
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const PORTS_MAP: Record<DialectType, number> = {
[Dialect.Sqlite]: 0,
} as const;

export const dialects = [Dialect.Mysql, Dialect.Postgresql, Dialect.Sqlite] as const;
export const dialects = [Dialect.Postgresql, Dialect.Mysql, Dialect.Sqlite] as const;

export const Mode = {
Host: 'Host',
Expand Down
4 changes: 3 additions & 1 deletion src/utils/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"ca_cert": "CA certificate (optional)",
"client_cert": "Client certificate (optional)",
"client_key": "Client key (optional)",
"show_ssl_certs": "Add certificates"
"show_ssl_certs": "Add certificates",
"client_p12": "P12 file (optional)",
"client_p12_pass": "P12 password (optional)"
},
"select_file": "Select file"
},
Expand Down

0 comments on commit 1108bd5

Please sign in to comment.