/* * Copyright (c) 2018 MariaDB Corporation Ab * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file and at www.mariadb.com/bsl11. * * Change Date: 2019-07-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2 or later of the General * Public License. */ #include "testconnections.h" #include #include #include #include #include #include #include #include #include using namespace std; namespace { template void to_collection(string s, const string& delimiter, T* pT) { size_t pos; while ((pos = s.find(delimiter)) != std::string::npos) { pT->push_back(s.substr(0, pos)); s.erase(0, pos + delimiter.length()); } if (s.length() != 0) { pT->push_back(s); } } string& ltrim(std::string& s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); return s; } string& rtrim(std::string& s) { s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); return s; } string& trim(std::string& s) { return ltrim(rtrim(s)); } string extract_ip(string s) { // 's' looks something like: " inet 127.0.0.1/..."; s = s.substr(9); // => "127.0.0.1/..."; s = s.substr(0, s.find_first_of('/')); // => "127.0.0.1" return s; } void get_maxscale_ips(TestConnections& test, vector* pIps) { static const char COMMAND[] = "export PATH=$PATH:/sbin:/usr/sbin; ip addr|fgrep inet|fgrep -v ::"; int exit_code; string output(test.maxscales->ssh_node_output(0, COMMAND, false, &exit_code)); to_collection(output, "\n", pIps); transform(pIps->begin(), pIps->end(), pIps->begin(), extract_ip); // Remove 127.0.0.1 if it is present. auto i = find(pIps->begin(), pIps->end(), "127.0.0.1"); if (i != pIps->end()) { pIps->erase(i); } } } namespace { void drop_user(TestConnections& test, const string& user, const string& host) { string stmt("DROP USER IF EXISTS "); stmt += "'"; stmt += user; stmt += "'@'"; stmt += host; stmt += "'"; test.try_query(test.maxscales->conn_rwsplit[0], stmt.c_str()); } void create_user(TestConnections& test, const string& user, const string& password, const string& host) { string stmt("CREATE USER "); stmt += "'"; stmt += user; stmt += "'@'"; stmt += host; stmt += "'"; stmt += " IDENTIFIED BY "; stmt += "'"; stmt += password; stmt += "'"; test.try_query(test.maxscales->conn_rwsplit[0], stmt.c_str()); } void grant_access(TestConnections& test, const string& user, const string& host) { string stmt("GRANT SELECT, INSERT, UPDATE ON *.* TO "); stmt += "'"; stmt += user; stmt += "'@'"; stmt += host; stmt += "'"; test.try_query(test.maxscales->conn_rwsplit[0], stmt.c_str()); test.try_query(test.maxscales->conn_rwsplit[0], "FLUSH PRIVILEGES"); } void create_user_and_grants(TestConnections& test, const string& user, const string& password, const string& host) { test.tprintf("Creating user: %s@%s", user.c_str(), host.c_str()); drop_user(test, user, host); create_user(test, user, password, host); grant_access(test, user, host); } bool select_user(MYSQL* pMysql, string* pUser) { bool rv = false; if (mysql_query(pMysql, "SELECT USER()") == 0) { MYSQL_RES* pRes = mysql_store_result(pMysql); if (mysql_num_rows(pRes) == 1) { MYSQL_ROW row = mysql_fetch_row(pRes); *pUser = row[0]; rv = true; } mysql_free_result(pRes); while (mysql_next_result(pMysql) == 0) { MYSQL_RES* pRes = mysql_store_result(pMysql); mysql_free_result(pRes); } } return rv; } bool can_connect_to_maxscale(const char* zHost, int port, const char* zUser, const char* zPassword) { bool could_connect = false; MYSQL* pMysql = mysql_init(NULL); if (pMysql) { unsigned int timeout = 5; mysql_options(pMysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); mysql_options(pMysql, MYSQL_OPT_READ_TIMEOUT, &timeout); mysql_options(pMysql, MYSQL_OPT_WRITE_TIMEOUT, &timeout); if (mysql_real_connect(pMysql, zHost, zUser, zPassword, NULL, port, NULL, 0)) { string user; if (select_user(pMysql, &user)) { could_connect = true; } else { cout << "Could not 'SELECT USER()' as '" << zUser << "': " << mysql_error(pMysql) << endl; } } else { cout << "Could not connect as '" << zUser << "': " << mysql_error(pMysql) << endl; } mysql_close(pMysql); } return could_connect; } string get_local_ip(TestConnections& test) { int exit_code; string output(test.maxscales->ssh_node_output(0, "nslookup maxscale|fgrep Server:|sed s/Server://", false, &exit_code)); return trim(output); } void start_maxscale_with_local_address(TestConnections& test, const string& replace, const string& with) { string command("sed -i s/"); command += replace; command += "/"; command += with; command += "/ "; command += "/etc/maxscale.cnf"; test.maxscales->ssh_node(0, command.c_str(), true); test.start_maxscale(); } void test_connecting(TestConnections& test, const char* zUser, const char* zPassword, const char* zHost, bool should_be_able_to) { bool could_connect = can_connect_to_maxscale(test.maxscales->IP[0], test.maxscales->rwsplit_port[0], zUser, zPassword); if (!could_connect && should_be_able_to) { test.assert(false, "%s@%s should have been able to connect, but wasn't.", zUser, zHost); } else if (could_connect && !should_be_able_to) { test.assert(false, "%s@%s should NOT have been able to connect, but was.", zUser, zHost); } else { if (could_connect) { test.tprintf("%s@%s could connect, as expected.", zUser, zHost); } else { test.tprintf("%s@%s could NOT connect, as expected.", zUser, zHost); } } } void run_test(TestConnections& test, const vector& ips) { test.maxscales->connect(); string ip1 = ips[0]; // If we do not have a proper second IP-address, we'll use an arbitrary one. string ip2 = (ips.size() > 1) ? ips[1] : string("42.42.42.42"); string local_ip = get_local_ip(test); const char* zUser1 = "alice"; const char* zUser2 = "bob"; const char* zPassword1 = "alicepwd"; const char* zPassword2 = "bobpwd"; create_user_and_grants(test, zUser1, zPassword1, ip1); create_user_and_grants(test, zUser1, zPassword1, local_ip); create_user_and_grants(test, zUser2, zPassword2, ip2); create_user_and_grants(test, zUser2, zPassword2, local_ip); test.repl->sync_slaves(); test.tprintf("\n"); test.tprintf("Testing default; alice should be able to access, bob not."); test_connecting(test, zUser1, zPassword1, ip1.c_str(), true); test_connecting(test, zUser2, zPassword2, ip2.c_str(), false); test.maxscales->disconnect(); test.stop_maxscale(); test.tprintf("\n"); test.tprintf("Testing with local_address=%s; alice should be able to access, bob not.", ip1.c_str()); string local_address_ip1 = "local_address=" + ip1; start_maxscale_with_local_address(test, "###local_address###", local_address_ip1); test.maxscales->connect(); test_connecting(test, zUser1, zPassword1, ip1.c_str(), true); test_connecting(test, zUser2, zPassword2, ip2.c_str(), false); test.maxscales->disconnect(); test.stop_maxscale(); if (ips.size() > 1) { #ifdef USABLE_SECOND_IP_ADDRESS_ON_MAXSCALE_NODE_IS_AVAILABLE test.tprintf("\n"); test.tprintf("\nTesting with local_address=%s, bob should be able to access, alice not.", ip2.c_str()); string local_address_ip2 = "local_address=" + ip2; start_maxscale_with_local_address(test, local_address_ip1, local_address_ip2); test.connect_maxscale(); test_connecting(test, zUser1, zPassword1, ip1.c_str(), false); test_connecting(test, zUser2, zPassword2, ip2.c_str(), true); test.maxscales->disconnect(); test.stop_maxscale(); #else test.tprintf("\n"); test.tprintf("WARNING: Other IP-address (%s) not tested, as IP-address currently " "not usable on VM.", ip2.c_str()); #endif } else { test.tprintf("\n"); test.tprintf("WARNING: Only one IP-address found on MaxScale node, 'local_address' " "not properly tested."); } } } int main(int argc, char** argv) { TestConnections test(argc, argv); vector ips; get_maxscale_ips(test, &ips); if (ips.size() >= 1) { run_test(test, ips); } else { test.assert(false, "MaxScale node does not have at least one IP-address."); } return test.global_result; }