]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#34] Checkpoint: more PgSQL/CQL and hook
authorFrancis Dupont <fdupont@isc.org>
Mon, 6 Dec 2021 14:38:49 +0000 (15:38 +0100)
committerFrancis Dupont <fdupont@isc.org>
Thu, 6 Jan 2022 12:12:40 +0000 (13:12 +0100)
12 files changed:
doc/devel/unit-tests.dox
doc/examples/kea4/all-keys.json
doc/examples/kea4/mysql-reservations.json
doc/examples/kea6/all-keys.json
doc/examples/kea6/mysql-reservations.json
src/lib/database/tests/database_connection_unittest.cc
src/lib/database/testutils/Makefile.am
src/lib/database/testutils/schema.cc
src/lib/database/testutils/schema.h
src/lib/mysql/tests/mysql_connection_unittest.cc
src/lib/mysql/testutils/mysql_schema.cc
src/lib/mysql/testutils/mysql_schema.h

index e4bc7e2c35c74cef9df72a9cd5c8071aed5fc386..e843c8c69fb0492fb13f7e630207e63f35acd13c 100644 (file)
@@ -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 <i>prefix</i>/var/run/kea, where <i>prefix</i> 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 <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
@@ -193,6 +201,49 @@ anything e.g. `DEBUG=true`. `unset DEBUG` to remove this behavior.
   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
index ae60b2ed5078535d21e91806195ac82de96cda8d..ef624eb6131c4aeb5deb844f2c56a879579ed7e7 100644 (file)
                 "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.
index c15e9706619b1520a98a8319b0e29c9014c8e9d7..31cabf7586f448eb1c3a6018a033e79e61f62b0d 100644 (file)
@@ -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
index 658a09e3cdc9c62d2888c706cc7379b2a55220bb..02ecb3f242c5b13374af50c0b09fab39c10a1ba5 100644 (file)
                 "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.
index a0a2c952d741d218aab292ee3d279de3055857cf..f8decf27118756723f98f0a7c68dee9b99f56cc4 100644 (file)
@@ -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
index 7cbea22206b2b238b67155ca9f16e3a22f24d5b0..548cd018fb969020ddc96fa3474dd27436f2941b 100644 (file)
@@ -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"
     };
 
index 1a368e88d17e6edcbae7c24361a90aa06cf11e06..3cf82e04e114e97b9b45e2d669fb43b10268e4f3 100644 (file)
@@ -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)
 
index f3db8ba64b9afaa730a083e425f7f0a5c5ff9905..951c8a861a9b77cd04c145396f804b8282e099ea 100644 (file)
@@ -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);
 }
 
index 1b34f3825546d996e5dabde7544941fb5c613ac5..7dce7cbb72514e56f4cc99af8cb16402bfe58d05 100644 (file)
@@ -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
 ///
index 332762775358d699ff40b8b44021ba144ea9c40a..eb83ed2f1312a4926a294e4837a6190189430246 100644 (file)
@@ -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
index ddded67fdcd25fac8b1be2124a53e84c0573263d..08d725e1c115a597a85ffe19a3575a7d5ab4e3aa 100644 (file)
@@ -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<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
index f3e5d113414e06ae1157cc1021ecc808a2e56b9c..50c14f765be86b04840e287a4ee2b6182cca9013 100644 (file)
@@ -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