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 <i>prefix</i>/var/run/kea, where <i>prefix</i> defaults to
@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 <i>keatest</i> database
(again, the apostrophes around the user names and <i>localhost</i>
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 <i>You do not have the SUPER privilege and binary logging is
section in the <a href="https://kea.readthedocs.io/">Kea Administrator
Reference Manual</a>).
+ @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=<kea-sources>/src/lib/asiolink/testutils/ca/kea-server.crt
+ssl_keyt=<kea-sources>/src/lib/asiolink/testutils/ca/kea-server.key
+ssl_ca=<kea-sources>/src/lib/asiolink/testutils/ca/kea-ca.crt
+
+[client-mariadb]
+ssl_cert=<kea-sources>/src/lib/asiolink/testutils/ca/kea-client.crt
+ssl_keyt=<kea-sources>/src/lib/asiolink/testutils/ca/kea-client.key
+ssl_ca=<kea-sources>/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
"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.
"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
"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.
"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
"\"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"
};
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)
-// 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
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";
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);
}
-// 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
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;
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.
///
/// @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
///
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
}
}
+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<void>(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
-// 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
/// 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
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