From: Dave Miller Date: Sun, 17 Mar 2024 04:10:20 +0000 (-0400) Subject: Bug 1885709: Allow connecting to MySQL via SSL (#109) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=93cde83c6ff9cd50c5304bb54f8650b35c588b64;p=thirdparty%2Fbugzilla.git Bug 1885709: Allow connecting to MySQL via SSL (#109) * Bug 1885709: Allow connecting to MySQL via SSL --- diff --git a/Bugzilla/DB/Mysql.pm b/Bugzilla/DB/Mysql.pm index 9cc781bc1..484c1355e 100644 --- a/Bugzilla/DB/Mysql.pm +++ b/Bugzilla/DB/Mysql.pm @@ -55,6 +55,19 @@ sub BUILDARGS { my %attrs = (mysql_enable_utf8 => 1); + # MySQL SSL options + my ($ssl_ca_file, $ssl_ca_path, $ssl_cert, $ssl_key, $ssl_pubkey) = + @$params{qw(db_mysql_ssl_ca_file db_mysql_ssl_ca_path + db_mysql_ssl_client_cert db_mysql_ssl_client_key db_mysql_ssl_get_pubkey)}; + if ($ssl_ca_file || $ssl_ca_path || $ssl_cert || $ssl_key || $ssl_pubkey) { + $attrs{'mysql_ssl'} = 1; + $attrs{'mysql_ssl_ca_file'} = $ssl_ca_file if $ssl_ca_file; + $attrs{'mysql_ssl_ca_path'} = $ssl_ca_path if $ssl_ca_path; + $attrs{'mysql_ssl_client_cert'} = $ssl_cert if $ssl_cert; + $attrs{'mysql_ssl_client_key'} = $ssl_key if $ssl_key; + $attrs{'mysql_get_server_pubkey'} = $ssl_pubkey if $ssl_pubkey; + } + return {dsn => $dsn, user => $user, pass => $pass, attrs => \%attrs}; } diff --git a/Bugzilla/DaemonControl.pm b/Bugzilla/DaemonControl.pm index 8c352941e..7cf79ffb1 100644 --- a/Bugzilla/DaemonControl.pm +++ b/Bugzilla/DaemonControl.pm @@ -15,7 +15,7 @@ use Bugzilla::Constants qw(bz_locations); use Cwd qw(realpath); use English qw(-no_match_vars $PROGRAM_NAME); use File::Spec::Functions qw(catfile catdir); -use Future::Utils qw(repeat try_repeat); +use Future::Utils qw(repeat try_repeat_until_success); use Future; use IO::Async::Loop; use IO::Async::Process; @@ -222,6 +222,7 @@ sub assert_connect { } sub assert_database { + my $assert_dbierrstr = ""; my $loop = IO::Async::Loop->new; my $lc = Bugzilla::Install::Localconfig::read_localconfig(); @@ -230,23 +231,38 @@ sub assert_database { } my $dsn = "dbi:mysql:database=$lc->{db_name};host=$lc->{db_host}"; - my $repeat = repeat { + my $repeat = try_repeat_until_success { $loop->delay_future(after => 0.25)->then(sub { - my $dbh - = DBI->connect($dsn, $lc->{db_user}, $lc->{db_pass}, - {RaiseError => 0, PrintError => 0}, - ); + my $attrs = {RaiseError => 1, PrintError => 1}; + my ($ssl_ca_file, $ssl_ca_path, $ssl_cert, $ssl_key, $ssl_pubkey) = + @$lc{qw(db_mysql_ssl_ca_file db_mysql_ssl_ca_path + db_mysql_ssl_client_cert db_mysql_ssl_client_key db_mysql_ssl_get_pubkey)}; + if ($ssl_ca_file || $ssl_ca_path || $ssl_cert || $ssl_key || $ssl_pubkey) { + $attrs->{'mysql_ssl'} = 1; + $attrs->{'mysql_ssl_ca_file'} = $ssl_ca_file if $ssl_ca_file; + $attrs->{'mysql_ssl_ca_path'} = $ssl_ca_path if $ssl_ca_path; + $attrs->{'mysql_ssl_client_cert'} = $ssl_cert if $ssl_cert; + $attrs->{'mysql_ssl_client_key'} = $ssl_key if $ssl_key; + $attrs->{'mysql_get_server_pubkey'} = $ssl_pubkey if $ssl_pubkey; + } + my $dbh; + eval { + $dbh + = DBI->connect($dsn, $lc->{db_user}, $lc->{db_pass}, $attrs); + }; + if ($!) { $assert_dbierrstr = $@; die $@; } + $assert_dbierrstr = DBI->errstr() || ''; + die "$assert_dbierrstr" if $assert_dbierrstr; Future->wrap($dbh); }); - } - until => sub { defined shift->get }; + }; my $timeout = $loop->timeout_future(after => 20)->else_fail('assert_database timeout'); my $any_f = Future->wait_any($repeat, $timeout); return $any_f->transform( done => sub {return}, - fail => sub {"unable to connect to $dsn as $lc->{db_user}"}, + fail => sub {"unable to connect to $dsn as $lc->{db_user}: $assert_dbierrstr"}, ); } diff --git a/Bugzilla/Install/Localconfig.pm b/Bugzilla/Install/Localconfig.pm index c4c249709..4a19b2c36 100644 --- a/Bugzilla/Install/Localconfig.pm +++ b/Bugzilla/Install/Localconfig.pm @@ -68,6 +68,11 @@ use constant LOCALCONFIG_VARS => ( {name => 'db_port', default => 0,}, {name => 'db_sock', default => '',}, {name => 'db_check', default => 1,}, + {name => 'db_mysql_ssl_ca_file', default => '',}, + {name => 'db_mysql_ssl_ca_path', default => '',}, + {name => 'db_mysql_ssl_client_cert', default => '',}, + {name => 'db_mysql_ssl_client_key', default => '',}, + {name => 'db_mysql_ssl_get_pubkey', default => 0,}, {name => 'index_html', default => 0,}, {name => 'cvsbin', default => sub { bin_loc('cvs') },}, {name => 'interdiffbin', default => sub { bin_loc('interdiff') },}, diff --git a/conf/checksetup_answers.txt b/conf/checksetup_answers.txt index b83f6d0ba..2c8e7e6b7 100644 --- a/conf/checksetup_answers.txt +++ b/conf/checksetup_answers.txt @@ -27,3 +27,4 @@ $answer{'defaultpriority'} = '--'; $answer{'defaultseverity'} = 'normal'; $answer{'skin'} = 'Mozilla'; $answer{'docs_urlbase'} = 'https://bmo.readthedocs.io/en/latest/'; +$answer{'db_mysql_ssl_get_pubkey'} = 1; diff --git a/template/en/default/setup/strings.txt.pl b/template/en/default/setup/strings.txt.pl index 2b47653bf..851be09df 100644 --- a/template/en/default/setup/strings.txt.pl +++ b/template/en/default/setup/strings.txt.pl @@ -178,6 +178,26 @@ blank, then MySQL's compiled-in default will be used. You probably want that. END localconfig_db_user => "Who we connect to the database as.", + localconfig_db_mysql_ssl_ca_file => <<'END', +Path to a PEM file with a list of trusted SSL CA certificates. +The file must be readable by web server user. +END + localconfig_db_mysql_ssl_ca_path => <<'END', +Path to a directory containing trusted SSL CA certificates in PEM format. +Directory and files inside must be readable by the web server user. +END + localconfig_db_mysql_ssl_client_cert => <<'END', +Full path to the client SSL certificate in PEM format we will present to the DB server. +The file must be readable by web server user. +END + localconfig_db_mysql_ssl_client_key => <<'END', +Full path to the private key corresponding to the client SSL certificate. +The file must not be password-protected and must be readable by web server user. +END + localconfig_db_mysql_ssl_get_pubkey => <<'END', +Whether to have Bugzilla automatically fetch the public key from the server at connection time. +This is less secure than specifying the ca_file above. +END localconfig_diffpath => <<'END', For the "Difference Between Two Patches" feature to work, we need to know what directory the "diff" bin is in. (You only need to set this if you