From: Francis Dupont Date: Mon, 6 Dec 2021 14:38:49 +0000 (+0100) Subject: [#34] Checkpoint: more PgSQL/CQL and hook X-Git-Tag: Kea-2.1.2~156 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5529fd85011de1cfe92e3cfbf2176d123251fda0;p=thirdparty%2Fkea.git [#34] Checkpoint: more PgSQL/CQL and hook --- diff --git a/doc/devel/unit-tests.dox b/doc/devel/unit-tests.dox index e4bc7e2c35..e843c8c69f 100644 --- a/doc/devel/unit-tests.dox +++ b/doc/devel/unit-tests.dox @@ -64,6 +64,11 @@ The following environment variable can affect the unit tests: expected are by default silent. If set, these unit tests display real and expected logs. +- KEA_MYSQL_HAVE_SSL - Specifies the SSL/TLS support status of MySQL. + When not set the corresponding MySQL global variable is read and + the environment of the unit test process is updated so usually this + variable is manually set only in order to enforce a particular status. + - KEA_PIDFILE_DIR - Specifies the directory which should be used for PID files as used by dhcp::Daemon or its derivatives. If not specified, the default is prefix/var/run/kea, where prefix defaults to @@ -169,12 +174,15 @@ anything e.g. `DEBUG=true`. `unset DEBUG` to remove this behavior. @verbatim mysql> CREATE USER 'keatest'@'localhost' IDENTIFIED BY 'keatest'; mysql> CREATE USER 'keatest_readonly'@'localhost' IDENTIFIED BY 'keatest'; + mysql> CREATE USER 'keatest_secure'@'localhost' IDENTIFIED BY 'keatest'; + mysql> ALTER USER 'keatest_secure'@'localhost' REQUIRE X509; mysql>@endverbatim\n -# Grant the created users permissions to access the keatest database (again, the apostrophes around the user names and localhost are required): @verbatim mysql> GRANT ALL ON keatest.* TO 'keatest'@'localhost'; + mysql> GRANT ALL ON keatest.* TO 'keatest_secure'@'localhost'; mysql> GRANT SELECT ON keatest.* TO 'keatest_readonly'@'localhost'; mysql>@endverbatim\n -# If you get You do not have the SUPER privilege and binary logging is @@ -193,6 +201,49 @@ anything e.g. `DEBUG=true`. `unset DEBUG` to remove this behavior. section in the Kea Administrator Reference Manual). + @subsection mysqlUnitTestsILS MySQL Database with SSL/TLS + + Usually MySQL is compiled with SSL/TLS support using OpenSSL. + This is easy to verify using the: + +@verbatim +mysql> SHOW GLOBAL VARIABLES LIKE 'have_ssl'; +@endverbatim + + The variable is documented to have three possible values: + +- DISABLED: compiled with TLS support but it was not configured + +- YES: compiled with configured TLS support + +- NO: not compiled with TLS support + +The value of this MySQL global variable is reflected by the +KEA_MYSQL_HAVE_SSL environment variable. + +The keatest_secure user requires X509 so a client certificate. Of course +in production a stricter requirement should be used, in particular when +a client certificate should be bound to a particular user. + +MySQL unit tests reuse the asiolink library setup. This .my.cnf +configuration file works with MariaDB 10.6.4: + +@verbatim +[mysqld] +ssl_cert=/src/lib/asiolink/testutils/ca/kea-server.crt +ssl_keyt=/src/lib/asiolink/testutils/ca/kea-server.key +ssl_ca=/src/lib/asiolink/testutils/ca/kea-ca.crt + +[client-mariadb] +ssl_cert=/src/lib/asiolink/testutils/ca/kea-client.crt +ssl_keyt=/src/lib/asiolink/testutils/ca/kea-client.key +ssl_ca=/src/lib/asiolink/testutils/ca/kea-ca.crt +ssl-verify-server-cert +@endverbatim + +The last statement requires mutual authentication named two way in the +MariaDB documentation. + @subsection pgsqlUnitTestsPrerequisites PostgreSQL Database PostgreSQL set up differs from system to system. Please consult your diff --git a/doc/examples/kea4/all-keys.json b/doc/examples/kea4/all-keys.json index ae60b2ed50..ef624eb613 100644 --- a/doc/examples/kea4/all-keys.json +++ b/doc/examples/kea4/all-keys.json @@ -330,7 +330,7 @@ "key-file": "my key", // Cipher list (see the OpenSSL ciohers command manual). - "cipher-list": "!SSLv3" + "cipher-list": "AES" }, { // Name of the database to connect to. diff --git a/doc/examples/kea4/mysql-reservations.json b/doc/examples/kea4/mysql-reservations.json index c15e970661..31cabf7586 100644 --- a/doc/examples/kea4/mysql-reservations.json +++ b/doc/examples/kea4/mysql-reservations.json @@ -67,7 +67,7 @@ "trust-anchor": "my-ca", "cert-file": "my-cert", "key-file": "my-key", - "cipher-list": "!SSLv3" + "cipher-list": "AES" }, // Define a subnet with a single pool of dynamic addresses. Addresses from diff --git a/doc/examples/kea6/all-keys.json b/doc/examples/kea6/all-keys.json index 658a09e3cd..02ecb3f242 100644 --- a/doc/examples/kea6/all-keys.json +++ b/doc/examples/kea6/all-keys.json @@ -290,7 +290,7 @@ "key-file": "my key", // Cipher list (see the OpenSSL ciohers command manual). - "cipher-list": "!SSLv3" + "cipher-list": "AES" }, { // Name of the database to connect to. diff --git a/doc/examples/kea6/mysql-reservations.json b/doc/examples/kea6/mysql-reservations.json index a0a2c952d7..f8decf2711 100644 --- a/doc/examples/kea6/mysql-reservations.json +++ b/doc/examples/kea6/mysql-reservations.json @@ -55,7 +55,7 @@ "trust-anchor": "my-ca", "cert-file": "my-cert", "key-file": "my-key", - "cipher-list": "!SSLv3" + "cipher-list": "AES" }, // Define a subnet with a pool of dynamic addresses and a pool of dynamic diff --git a/src/lib/database/tests/database_connection_unittest.cc b/src/lib/database/tests/database_connection_unittest.cc index 7cbea22206..548cd018fb 100644 --- a/src/lib/database/tests/database_connection_unittest.cc +++ b/src/lib/database/tests/database_connection_unittest.cc @@ -548,7 +548,7 @@ TEST(DatabaseConnection, toElementDbAccessStringValid) { "\"trust-anchor\": \"my-ca\", \n" "\"cert-file\": \"my-cert.crt\", \n" "\"key-file\": \"my-key.key\", \n" - "\"cipher-list\": \"!SSLv3\" \n" + "\"cipher-list\": \"AES\" \n" "}\n" }; diff --git a/src/lib/database/testutils/Makefile.am b/src/lib/database/testutils/Makefile.am index 1a368e88d1..3cf82e04e1 100644 --- a/src/lib/database/testutils/Makefile.am +++ b/src/lib/database/testutils/Makefile.am @@ -1,6 +1,8 @@ SUBDIRS = . AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib +TEST_CA_DIR = $(abs_srcdir)/../../asiolink/testutils/ca +AM_CPPFLAGS += -DTEST_CA_DIR=\"$(TEST_CA_DIR)\" AM_CXXFLAGS = $(KEA_CXXFLAGS) diff --git a/src/lib/database/testutils/schema.cc b/src/lib/database/testutils/schema.cc index f3db8ba64b..951c8a861a 100644 --- a/src/lib/database/testutils/schema.cc +++ b/src/lib/database/testutils/schema.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -26,6 +26,7 @@ const char* VALID_HOST = "host=localhost"; const char* INVALID_HOST = "host=invalidhost"; const char* VALID_USER = "user=keatest"; const char* VALID_READONLY_USER = "user=keatest_readonly"; +const char* VALID_SECURE_USER = "user=keatest_secure"; const char* INVALID_USER = "user=invaliduser"; const char* VALID_PASSWORD = "password=keatest"; const char* INVALID_PASSWORD = "password=invalid"; @@ -34,58 +35,94 @@ const char* INVALID_TIMEOUT_1 = "connect-timeout=foo"; const char* INVALID_TIMEOUT_2 = "connect-timeout=-17"; const char* VALID_READONLY_DB = "readonly=true"; const char* INVALID_READONLY_DB = "readonly=5"; +const char* VALID_CERT = "cert-file=" TEST_CA_DIR "/kea-client.crt"; +const char* VALID_KEY = "key-file=" TEST_CA_DIR "/kea-client.key"; +const char* INVALID_KEY = "key-file=" TEST_CA_DIR "/kea-other.key"; +const char* VALID_CA = "trust-anchor=" TEST_CA_DIR "/kea-ca.crt"; +const char* VALID_CIPHER = "cipher-list=AES"; string connectionString(const char* type, const char* name, const char* host, - const char* user, const char* password, const char* timeout, - const char* readonly_db = NULL) { + const char* user, const char* password, + const char* timeout, const char* readonly_db, + const char* cert_file, const char* key_file, + const char* trust_anchor, const char* cipher) { const string space = " "; string result = ""; - if (type != NULL) { + if (type) { result += string(type); } - if (name != NULL) { + + if (name) { if (! result.empty()) { result += space; } result += string(name); } - if (host != NULL) { + if (host) { if (! result.empty()) { result += space; } result += string(host); } - if (user != NULL) { + if (user) { if (! result.empty()) { result += space; } result += string(user); } - if (password != NULL) { + if (password) { if (! result.empty()) { result += space; } result += string(password); } - if (timeout != NULL) { + if (timeout) { if (! result.empty()) { result += space; } result += string(timeout); } - if (readonly_db != NULL) { + if (readonly_db) { if (! result.empty()) { result += space; } result += string(readonly_db); } + if (cert_file) { + if (! result.empty()) { + result += space; + } + result += string(cert_file); + } + + if (key_file) { + if (! result.empty()) { + result += space; + } + result += string(key_file); + } + + if (trust_anchor) { + if (! result.empty()) { + result += space; + } + result += string(trust_anchor); + } + + if (cipher) { + if (! result.empty()) { + result += space; + } + result += string(cipher); + } + return (result); } diff --git a/src/lib/database/testutils/schema.h b/src/lib/database/testutils/schema.h index 1b34f38255..7dce7cbb72 100644 --- a/src/lib/database/testutils/schema.h +++ b/src/lib/database/testutils/schema.h @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -22,6 +22,7 @@ extern const char* VALID_HOST; extern const char* INVALID_HOST; extern const char* VALID_USER; extern const char* VALID_READONLY_USER; +extern const char* VALID_SECURE_USER; extern const char* INVALID_USER; extern const char* VALID_PASSWORD; extern const char* INVALID_PASSWORD; @@ -30,6 +31,11 @@ extern const char* INVALID_TIMEOUT_1; extern const char* INVALID_TIMEOUT_2; extern const char* VALID_READONLY_DB; extern const char* INVALID_READONLY_DB; +extern const char* VALID_CERT; +extern const char* VALID_KEY; +extern const char* INVALID_KEY; +extern const char* VALID_CA; +extern const char* VALID_CIPHER; /// @brief Given a combination of strings above, produce a connection string. /// @@ -40,11 +46,19 @@ extern const char* INVALID_READONLY_DB; /// @param password password used to authenticate during connection attempt /// @param timeout timeout used during connection attempt /// @param readonly_db specifies if database is read only +/// @param cert_file specifies the client certificate file +/// @param key_file specifies the private key file +/// @param trust_anchor specifies the trust anchor aka cert authority +/// @param cipher specifies the cipher list /// @return string containing all specified parameters -std::string connectionString(const char* type, const char* name = NULL, - const char* host = NULL, const char* user = NULL, - const char* password = NULL, const char* timeout = NULL, - const char* readonly_db = NULL); +std::string connectionString(const char* type, const char* name = 0, + const char* host = 0, const char* user = 0, + const char* password = 0, const char* timeout = 0, + const char* readonly_db = 0, + const char* cert_file = 0, + const char* key_file = 0, + const char* trust_anchor = 0, + const char* cipher = 0); /// @brief Determines if wiping only the data between tests is enabled /// diff --git a/src/lib/mysql/tests/mysql_connection_unittest.cc b/src/lib/mysql/tests/mysql_connection_unittest.cc index 3327627753..eb83ed2f13 100644 --- a/src/lib/mysql/tests/mysql_connection_unittest.cc +++ b/src/lib/mysql/tests/mysql_connection_unittest.cc @@ -658,4 +658,119 @@ TEST_F(MySqlSchemaTest, checkVersion) { EXPECT_EQ(MYSQL_SCHEMA_VERSION_MINOR, version.second); } +/// @brief Test fixture class for secure connection. +class MySqlSecureConnectionTest : public ::testing::Test { +public: + + /// @brief Check if SSL/TLS support is available and configured. + bool hasMySQLTls() { + std::string tls = getMySQLTlsEnv(); + if (tls.empty()) { + tls = getMySQLTlsEnv(); + } + return (tls == "YES"); + } +}; + +/// @brief Check that we can get the MySQL support status. +TEST_F(MySqlSecureConnectionTest, getMySQLTls) { + std::string env; + try { + env = getMySQLTlsEnv(); + std::cout << "getMySQLTlsEnv returns '" << env << "'\n"; + } catch (const isc::Exception& ex) { + std::cerr << "getMySQLTlsEnv fails with " << ex.what() << "\n"; + } + if (!env.empty()) { + return; + } + try { + std::cout << "getMySQLTlsServer returns '" << getMySQLTlsServer() << "'\n"; + } catch (const isc::Exception& ex) { + std::cerr << "getMySQLTlsServer fails with " << ex.what() << "\n"; + } +} + +/// @brief Check the SSL/TLS protected connection. +TEST_F(MySqlSecureConnectionTest, Tls) { + if (!hasMySQLTls()) { + std::cout << "SSL/TLS support is not available or configured: " + << "skipping this test\n"; + return; + } + std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME, + VALID_HOST, VALID_SECURE_USER, + VALID_PASSWORD, 0, 0, + VALID_CERT, VALID_KEY, VALID_CA, + VALID_CIPHER); + MySqlConnection conn(DatabaseConnection::parse(conn_str)); + ASSERT_NO_THROW(conn.openDatabase()); + EXPECT_TRUE(conn.getTls()); + std::string cipher = conn.getTlsCipher(); + EXPECT_FALSE(cipher.empty()); + std::cout << "TLS cipher is '" << cipher << "'\n"; +} + +/// @brief Check the SSL/TLS protected connection still requires the password. +TEST_F(MySqlSecureConnectionTest, TlsInvalidPassword) { + if (!hasMySQLTls()) { + std::cout << "SSL/TLS support is not available or configured: " + << "skipping this test\n"; + return; + } + std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME, + VALID_HOST, VALID_SECURE_USER, + INVALID_PASSWORD, 0, 0, + VALID_CERT, VALID_KEY, VALID_CA, + VALID_CIPHER); + MySqlConnection conn(DatabaseConnection::parse(conn_str)); + EXPECT_THROW(conn.openDatabase(), DbOpenError); +} + +/// @brief Check the SSL/TLS protected connection requires crypto parameters. +TEST_F(MySqlSecureConnectionTest, TlsNoCrypto) { + if (!hasMySQLTls()) { + std::cout << "SSL/TLS support is not available or configured: " + << "skipping this test\n"; + return; + } + std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME, + VALID_HOST, VALID_SECURE_USER, + VALID_PASSWORD); + MySqlConnection conn(DatabaseConnection::parse(conn_str)); + EXPECT_THROW(conn.openDatabase(), DbOpenError); +} + +/// @brief Check the SSL/TLS protected connection requires valid key. +TEST_F(MySqlSecureConnectionTest, TlsInvalidKey) { + if (!hasMySQLTls()) { + std::cout << "SSL/TLS support is not available or configured: " + << "skipping this test\n"; + return; + } + std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME, + VALID_HOST, VALID_SECURE_USER, + VALID_PASSWORD, 0, 0, + VALID_CERT, INVALID_KEY, VALID_CA, + VALID_CIPHER); + MySqlConnection conn(DatabaseConnection::parse(conn_str)); + EXPECT_THROW(conn.openDatabase(), DbOpenError); +} + +/// @brief Check the SSL/TLS protected connection requires a key. +TEST_F(MySqlSecureConnectionTest, TlsNoKey) { + if (!hasMySQLTls()) { + std::cout << "SSL/TLS support is not available or configured: " + << "skipping this test\n"; + return; + } + std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME, + VALID_HOST, VALID_SECURE_USER, + VALID_PASSWORD, 0, 0, + VALID_CERT, 0, VALID_CA, + VALID_CIPHER); + MySqlConnection conn(DatabaseConnection::parse(conn_str)); + EXPECT_THROW(conn.openDatabase(), DbOpenError); +} + } // namespace diff --git a/src/lib/mysql/testutils/mysql_schema.cc b/src/lib/mysql/testutils/mysql_schema.cc index ddded67fdc..08d725e1c1 100644 --- a/src/lib/mysql/testutils/mysql_schema.cc +++ b/src/lib/mysql/testutils/mysql_schema.cc @@ -87,6 +87,54 @@ void runMySQLScript(const std::string& path, const std::string& script_name, } } +string getMySQLTlsEnv() { + const string name("KEA_MYSQL_HAVE_SSL"); + const char* val = getenv(name.c_str()); + return (val ? string(val) : ""); +} + +string getMySQLTlsServer() { + DatabaseConnection::ParameterMap parameters = + DatabaseConnection::parse(validMySQLConnectionString()); + MySqlConnection conn(parameters); + MYSQL_RES* result(0); + try { + conn.openDatabase(); + string sql("SHOW GLOBAL VARIABLES LIKE 'have_ssl'"); + if (mysql_query(conn.mysql_, sql.c_str())) { + isc_throw(DbOperationError, + sql << ": " << mysql_error(conn.mysql_)); + } + result = mysql_use_result(conn.mysql_); + size_t count = mysql_num_fields(result); + if (count != 2) { + isc_throw(DbOperationError, + sql << " returned " << count << " rows, expecting 2"); + } + MYSQL_ROW row = mysql_fetch_row(result); + if (!row) { + isc_throw(DbOperationError, sql << " returned row is null"); + } + // first column is 'have_ssl', second is the status. + string name(row[0]); + if (name != "have_ssl") { + isc_throw(DbOperationError, + sql << " returned a wrong name '" << name + << "', expected 'have_ssl'"); + } + string value(row[1]); + const string env("KEA_MYSQL_HAVE_SSL"); + static_cast(setenv(env.c_str(), value.c_str(), 1)); + mysql_free_result(result); + return (value); + } catch (...) { + if (result) { + mysql_free_result(result); + } + throw; + } +} + } // namespace test } // namespace db } // namespace isc diff --git a/src/lib/mysql/testutils/mysql_schema.h b/src/lib/mysql/testutils/mysql_schema.h index f3e5d11341..50c14f765b 100644 --- a/src/lib/mysql/testutils/mysql_schema.h +++ b/src/lib/mysql/testutils/mysql_schema.h @@ -1,4 +1,4 @@ -// Copyright (C) 2015-2019 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2015-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -69,7 +69,6 @@ void destroyMySQLSchema(bool show_err = false, bool force = false); /// schema. void createMySQLSchema(bool show_err = false, bool force = false); - /// @brief Attempts to wipe data from the MySQL unit test database /// /// Runs the shell script @@ -99,8 +98,17 @@ bool wipeMySQLData(bool show_err = false); void runMySQLScript(const std::string& path, const std::string& script_name, bool show_err); -}; -}; -}; +/// @brief Get the SSL/TLS support status from the environment +/// +/// The environment variable is KEA_MYSQL_HAVE_SSL +std::string getMySQLTlsEnv(); + +/// @brief Get the SSL/TLS support status from the server +/// @note the returned value is set in the environment +std::string getMySQLTlsServer(); + +} +} +} #endif