From ecb7df0e8b5df7a2448090cf2d8275066653f53c Mon Sep 17 00:00:00 2001 From: ralimi Date: Mon, 14 Aug 2017 07:30:42 -0700 Subject: [PATCH] Support SSL for mysql connections (#1965) * Fix install location for config files when building to alternate directory. With the previous code, we ended up with a directory structure like the following: $ find /etc/zm/conf.d/ /etc/zm/conf.d/ /etc/zm/conf.d/01-system-paths.conf /etc/zm/conf.d/conf.d /etc/zm/conf.d/conf.d/README /etc/zm/conf.d/conf.d/02-multiserver.conf * Omitted README file that should have appeared in /etc/zm/conf.d * Fix location for configs when building to alternate directory. * Fix works, but this should go on a branch instead. * Fix works, but this should go on a branch instead. * Fix location for configs when building to alternate directory. With the previous code, we ended up with a directory structure like the following: $ find /etc/zm/conf.d/ /etc/zm/conf.d/ /etc/zm/conf.d/01-system-paths.conf /etc/zm/conf.d/conf.d /etc/zm/conf.d/conf.d/README /etc/zm/conf.d/conf.d/02-multiserver.conf * Remove double quotes. This is a list of paths. * Allow SSL database connection to be secured with SSL. * Fix incorrect variable name * Fix PHP syntax errors * SSL connection parameters must also be passed in API. * Revert fixes to build files; they should not be in this branch. --- INSTALL | 3 +++ cmakecacheimport.sh | 6 ++++++ scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in | 11 ++++++++++- scripts/ZoneMinder/lib/ZoneMinder/Database.pm | 13 ++++++++++++- scripts/ZoneMinder/lib/ZoneMinder/Logger.pm | 11 ++++++++++- src/zm_config.cpp | 6 ++++++ src/zm_config.h.in | 3 +++ src/zm_db.cpp | 2 ++ src/zm_logger.cpp | 2 ++ web/api/app/Config/database.php.default | 3 +++ web/includes/database.php | 7 ++++++- zm.conf.in | 9 +++++++++ 12 files changed, 72 insertions(+), 4 deletions(-) diff --git a/INSTALL b/INSTALL index 3b50f5b62..666105c40 100644 --- a/INSTALL +++ b/INSTALL @@ -45,6 +45,9 @@ Possible configuration options: ZM_DB_NAME Name of ZoneMinder database, default: zm ZM_DB_USER Name of ZoneMinder database user, default: zmuser ZM_DB_PASS Password of ZoneMinder database user, default: zmpass + ZM_DB_SSL_CA_CERT Path to SSL CA certificate, default: empty; SSL not enabled + ZM_DB_SSL_CLIENT_KEY Path to SSL client key, default: empty; SSL not enabled + ZM_DB_SSL_CLIENT_CERT Path to SSL client certificate, default: empty; SSL not enabled ZM_WEB_USER The user apache or the local web server runs on. Leave empty for automatic detection. If that fails, you can use this variable to force ZM_WEB_GROUP The group apache or the local web server runs on, Leave empty to be the same as the web user ZM_DIR_EVENTS Location where events are recorded to, default: ZM_CONTENTDIR/events diff --git a/cmakecacheimport.sh b/cmakecacheimport.sh index 79253f6ad..7bd44f311 100755 --- a/cmakecacheimport.sh +++ b/cmakecacheimport.sh @@ -55,6 +55,9 @@ echo "Database host : $ZM_DB_HOST" echo "Database name : $ZM_DB_NAME" echo "Database user : $ZM_DB_USER" echo "Database password : Not shown" +echo "Database SSL CA Cert : $ZM_DB_SSL_CA_CERT" +echo "Database SSL Client Key : $ZM_DB_SSL_CLIENT_KEY" +echo "Database SSL Client Cert : $ZM_DB_SSL_CLIENT_CERT" CMPATH="CACHE PATH \"Imported by cmakecacheimport.sh\" FORCE" @@ -72,6 +75,9 @@ echo "set(ZM_DB_HOST \"$ZM_DB_HOST\" $CMSTRING)">>zm_conf.cmake echo "set(ZM_DB_NAME \"$ZM_DB_NAME\" $CMSTRING)">>zm_conf.cmake echo "set(ZM_DB_USER \"$ZM_DB_USER\" $CMSTRING)">>zm_conf.cmake echo "set(ZM_DB_PASS \"$ZM_DB_PASS\" $CMSTRING)">>zm_conf.cmake +echo "set(ZM_DB_SSL_CA_CERT \"$ZM_DB_SSL_CA_CERT\" $CMSTRING)">>zm_conf.cmake +echo "set(ZM_DB_SSL_CLIENT_KEY \"$ZM_DB_SSL_CLIENT_KEY\" $CMSTRING)">>zm_conf.cmake +echo "set(ZM_DB_SSL_CLIENT_CERT \"$ZM_DB_SSL_CLIENT_CERT\" $CMSTRING)">>zm_conf.cmake echo "" echo "Wrote zm_conf.cmake" diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in index 60cdce658..d2b444ef4 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in @@ -101,8 +101,17 @@ BEGIN { } else { $socket = ";host=".$Config{ZM_DB_HOST}; } + my $sslOptions = ""; + if ( $Config{ZM_DB_SSL_CA_CERT} ) { + $sslOptions = ';'.join(';', + "mysql_ssl=1", + "mysql_ssl_ca_file=".$Config{ZM_DB_SSL_CA_CERT}, + "mysql_ssl_client_key=".$Config{ZM_DB_SSL_CLIENT_KEY}, + "mysql_ssl_client_cert=".$Config{ZM_DB_SSL_CLIENT_CERT} + ); + } my $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .$socket + .$socket.$sslOptions , $Config{ZM_DB_USER} , $Config{ZM_DB_PASS} ) or croak( "Can't connect to db" ); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index 19374543e..cf0f488e2 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -90,8 +90,19 @@ sub zmDbConnect { } else { $socket = ";host=".$Config{ZM_DB_HOST}; } + + my $sslOptions = ""; + if ( $Config{ZM_DB_SSL_CA_CERT} ) { + $sslOptions = ';'.join(';', + "mysql_ssl=1", + "mysql_ssl_ca_file=".$Config{ZM_DB_SSL_CA_CERT}, + "mysql_ssl_client_key=".$Config{ZM_DB_SSL_CLIENT_KEY}, + "mysql_ssl_client_cert=".$Config{ZM_DB_SSL_CLIENT_CERT} + ); + } + $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .$socket . ($options?';'.join(';', map { $_.'='.$$options{$_} } keys %{$options} ) : '' ) + .$socket . $sslOptions . ($options?';'.join(';', map { $_.'='.$$options{$_} } keys %{$options} ) : '' ) , $Config{ZM_DB_USER} , $Config{ZM_DB_PASS} ); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index 123033105..27a9fc1a8 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -434,8 +434,17 @@ sub databaseLevel { } else { $socket = ";host=".$Config{ZM_DB_HOST}; } + my $sslOptions = ""; + if ( $Config{ZM_DB_SSL_CA_CERT} ) { + $sslOptions = ';'.join(';', + "mysql_ssl=1", + "mysql_ssl_ca_file=".$Config{ZM_DB_SSL_CA_CERT}, + "mysql_ssl_client_key=".$Config{ZM_DB_SSL_CLIENT_KEY}, + "mysql_ssl_client_cert=".$Config{ZM_DB_SSL_CLIENT_CERT} + ); + } $this->{dbh} = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .$socket + .$socket.$sslOptions , $Config{ZM_DB_USER} , $Config{ZM_DB_PASS} ); diff --git a/src/zm_config.cpp b/src/zm_config.cpp index 601a4a950..fdbcffe52 100644 --- a/src/zm_config.cpp +++ b/src/zm_config.cpp @@ -150,6 +150,12 @@ void process_configfile( char* configFile) { staticConfig.DB_USER = std::string(val_ptr); else if ( strcasecmp( name_ptr, "ZM_DB_PASS" ) == 0 ) staticConfig.DB_PASS = std::string(val_ptr); + else if ( strcasecmp( name_ptr, "ZM_DB_SSL_CA_CERT" ) == 0 ) + staticConfig.DB_SSL_CA_CERT = std::string(val_ptr); + else if ( strcasecmp( name_ptr, "ZM_DB_SSL_CLIENT_KEY" ) == 0 ) + staticConfig.DB_SSL_CLIENT_KEY = std::string(val_ptr); + else if ( strcasecmp( name_ptr, "ZM_DB_SSL_CLIENT_CERT" ) == 0 ) + staticConfig.DB_SSL_CLIENT_CERT = std::string(val_ptr); else if ( strcasecmp( name_ptr, "ZM_PATH_WEB" ) == 0 ) staticConfig.PATH_WEB = std::string(val_ptr); else if ( strcasecmp( name_ptr, "ZM_SERVER_HOST" ) == 0 ) diff --git a/src/zm_config.h.in b/src/zm_config.h.in index 461e4bb5e..3ecc70d7c 100644 --- a/src/zm_config.h.in +++ b/src/zm_config.h.in @@ -67,6 +67,9 @@ struct StaticConfig std::string DB_NAME; std::string DB_USER; std::string DB_PASS; + std::string DB_SSL_CA_CERT; + std::string DB_SSL_CLIENT_KEY; + std::string DB_SSL_CLIENT_CERT; std::string PATH_WEB; std::string SERVER_NAME; unsigned int SERVER_ID; diff --git a/src/zm_db.cpp b/src/zm_db.cpp index 8eed90569..568f9cd72 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -37,6 +37,8 @@ void zmDbConnect() my_bool reconnect = 1; if ( mysql_options( &dbconn, MYSQL_OPT_RECONNECT, &reconnect ) ) Fatal( "Can't set database auto reconnect option: %s", mysql_error( &dbconn ) ); + if ( !staticConfig.DB_SSL_CA_CERT.empty() ) + mysql_ssl_set( &dbconn, staticConfig.DB_SSL_CLIENT_KEY.c_str(), staticConfig.DB_SSL_CLIENT_CERT.c_str(), staticConfig.DB_SSL_CA_CERT.c_str(), NULL, NULL ); std::string::size_type colonIndex = staticConfig.DB_HOST.find( ":" ); if ( colonIndex == std::string::npos ) { diff --git a/src/zm_logger.cpp b/src/zm_logger.cpp index ee3a5a8b5..bc0d59c23 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -341,6 +341,8 @@ Logger::Level Logger::databaseLevel( Logger::Level databaseLevel ) { my_bool reconnect = 1; if ( mysql_options( &mDbConnection, MYSQL_OPT_RECONNECT, &reconnect ) ) Fatal( "Can't set database auto reconnect option: %s", mysql_error( &mDbConnection ) ); + if ( !staticConfig.DB_SSL_CA_CERT.empty() ) + mysql_ssl_set( &mDbConnection, staticConfig.DB_SSL_CLIENT_KEY.c_str(), staticConfig.DB_SSL_CLIENT_CERT.c_str(), staticConfig.DB_SSL_CA_CERT.c_str(), NULL, NULL ); std::string::size_type colonIndex = staticConfig.DB_HOST.find( ":" ); if ( colonIndex == std::string::npos ) { if ( !mysql_real_connect( &mDbConnection, staticConfig.DB_HOST.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, 0, NULL, 0 ) ) { diff --git a/web/api/app/Config/database.php.default b/web/api/app/Config/database.php.default index 55f2bc958..c06953ec7 100644 --- a/web/api/app/Config/database.php.default +++ b/web/api/app/Config/database.php.default @@ -70,6 +70,9 @@ class DATABASE_CONFIG { 'login' => ZM_DB_USER, 'password' => ZM_DB_PASS, 'database' => ZM_DB_NAME, + 'ssl_ca' => ZM_DB_SSL_CA_CERT, + 'ssl_key' => ZM_DB_SSL_CLIENT_KEY, + 'ssl_cert' => ZM_DB_SSL_CLIENT_CERT, 'prefix' => '', 'encoding' => 'utf8', ); diff --git a/web/includes/database.php b/web/includes/database.php index dea9e4b8c..bc1242029 100644 --- a/web/includes/database.php +++ b/web/includes/database.php @@ -42,7 +42,12 @@ function dbConnect() { } try { - $dbConn = new PDO( ZM_DB_TYPE . $socket . ';dbname='.ZM_DB_NAME, ZM_DB_USER, ZM_DB_PASS ); + $dbOptions = array( + PDO::MYSQL_ATTR_SSL_CA => ZM_DB_SSL_CA_CERT, + PDO::MYSQL_ATTR_SSL_KEY => ZM_DB_SSL_CLIENT_KEY, + PDO::MYSQL_ATTR_SSL_CERT => ZM_DB_SSL_CLIENT_CERT, + ); + $dbConn = new PDO( ZM_DB_TYPE . $socket . ';dbname='.ZM_DB_NAME, ZM_DB_USER, ZM_DB_PASS, $dbOptions ); $dbConn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbConn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch(PDOException $ex ) { diff --git a/zm.conf.in b/zm.conf.in index bde068104..312c9aeae 100644 --- a/zm.conf.in +++ b/zm.conf.in @@ -49,6 +49,15 @@ ZM_DB_USER=@ZM_DB_USER@ # ZoneMinder database password ZM_DB_PASS=@ZM_DB_PASS@ +# SSL CA certificate for ZoneMinder database +ZM_DB_SSL_CA_CERT=@ZM_DB_SSL_CA_CERT@ + +# SSL client key for ZoneMinder database +ZM_DB_SSL_CLIENT_KEY=@ZM_DB_SSL_CLIENT_KEY@ + +# SSL client cert for ZoneMinder database +ZM_DB_SSL_CLIENT_CERT=@ZM_DB_SSL_CLIENT_CERT@ + # Do NOT set ZM_SERVER_HOST if you are not using Multi-Server # You have been warned #