Merge branch '2.3' into develop
This commit is contained in:
commit
bc2d877898
@ -958,6 +958,9 @@ add_test_executable(mxs2441_galera_slaves.cpp mxs2441_galera_slaves mxs2441_gale
|
||||
# MXS-2414: Block host after repeated authentication failures
|
||||
add_test_executable(mxs2414_host_blocking.cpp mxs2414_host_blocking replication LABELS REPL_BACKEND)
|
||||
|
||||
# PAM authentication
|
||||
add_test_executable(pam_authentication.cpp pam_authentication pam_authentication LABELS REPL_BACKEND)
|
||||
|
||||
############################################
|
||||
# BEGIN: binlogrouter and avrorouter tests #
|
||||
############################################
|
||||
|
@ -5,8 +5,8 @@ log_warning=1
|
||||
[namedserverfilter]
|
||||
type=filter
|
||||
module=namedserverfilter
|
||||
match=SELECT
|
||||
server=server2
|
||||
match01=^SELECT
|
||||
target01=server2,server3
|
||||
|
||||
[MySQL-Monitor]
|
||||
type=monitor
|
||||
@ -19,9 +19,7 @@ monitor_interval=1000
|
||||
[RW-Split-Router]
|
||||
type=service
|
||||
router=readwritesplit
|
||||
|
||||
# Mixing the order of slaves will make server3 the first slave server
|
||||
servers=server1,server3,server2,server4
|
||||
servers=server1,server2,server3,server4
|
||||
user=maxskysql
|
||||
password=skysql
|
||||
filters=namedserverfilter
|
||||
|
@ -0,0 +1,70 @@
|
||||
[maxscale]
|
||||
threads=###threads###
|
||||
log_info=1
|
||||
|
||||
[MySQL-Monitor]
|
||||
type=monitor
|
||||
module=mysqlmon
|
||||
servers= server1, server2, server3, server4
|
||||
user=maxskysql
|
||||
password= skysql
|
||||
monitor_interval=1000
|
||||
failcount=1
|
||||
replication_user=repl
|
||||
replication_password=repl
|
||||
backend_connect_timeout=10
|
||||
backend_read_timeout=10
|
||||
backend_write_timeout=10
|
||||
|
||||
[RWSplit-Router]
|
||||
type=service
|
||||
router=readwritesplit
|
||||
servers=server1, server2, server3, server4
|
||||
user=maxskysql
|
||||
password=skysql
|
||||
|
||||
[RWSplit-Listener]
|
||||
type=listener
|
||||
service=RWSplit-Router
|
||||
protocol=MySQLClient
|
||||
port=4006
|
||||
authenticator=PAMAuth
|
||||
|
||||
[CLI]
|
||||
type=service
|
||||
router=cli
|
||||
|
||||
[CLI-Listener]
|
||||
type=listener
|
||||
service=CLI
|
||||
protocol=maxscaled
|
||||
socket=default
|
||||
|
||||
[server1]
|
||||
type=server
|
||||
address=###node_server_IP_1###
|
||||
port=###node_server_port_1###
|
||||
protocol=MySQLBackend
|
||||
authenticator=PAMBackendAuth
|
||||
|
||||
[server2]
|
||||
type=server
|
||||
address=###node_server_IP_2###
|
||||
port=###node_server_port_2###
|
||||
protocol=MySQLBackend
|
||||
authenticator=PAMBackendAuth
|
||||
|
||||
[server3]
|
||||
type=server
|
||||
address=###node_server_IP_3###
|
||||
port=###node_server_port_3###
|
||||
protocol=MySQLBackend
|
||||
authenticator=PAMBackendAuth
|
||||
|
||||
[server4]
|
||||
type=server
|
||||
address=###node_server_IP_4###
|
||||
port=###node_server_port_4###
|
||||
protocol=MySQLBackend
|
||||
authenticator=PAMBackendAuth
|
||||
|
@ -6,40 +6,100 @@
|
||||
* `match=SELECT` which should match any SELECT query.
|
||||
*/
|
||||
|
||||
|
||||
#include <iostream>
|
||||
#include "testconnections.h"
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using std::cout;
|
||||
using IdSet = std::set<int>;
|
||||
|
||||
int compare_server_id(TestConnections* test, char* node_id)
|
||||
{
|
||||
char str[1024];
|
||||
int rval = 0;
|
||||
if (find_field(test->maxscales->conn_rwsplit[0], "SELECT @@server_id", "@@server_id", str))
|
||||
{
|
||||
test->tprintf("Failed to query for @@server_id.\n");
|
||||
rval = 1;
|
||||
}
|
||||
else if (strcmp(node_id, str))
|
||||
{
|
||||
test->tprintf("@@server_id is %s instead of %s\n", str, node_id);
|
||||
rval = 1;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
bool check_server_id(MYSQL* conn, const IdSet& allowed_ids);
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
TestConnections* test = new TestConnections(argc, argv);
|
||||
test->repl->connect();
|
||||
char server_id[1024];
|
||||
TestConnections test(argc, argv);
|
||||
test.repl->connect();
|
||||
int server_count = test.repl->N;
|
||||
if (server_count < 4)
|
||||
{
|
||||
test.expect(false, "Too few servers.");
|
||||
return test.global_result;
|
||||
}
|
||||
|
||||
sprintf(server_id, "%d", test->repl->get_server_id(1));
|
||||
test->tprintf("Server ID of server2 is: %s\n", server_id);
|
||||
test->add_result(test->maxscales->connect_rwsplit(0), "Test failed to connect to MaxScale.\n");
|
||||
test->add_result(compare_server_id(test, server_id), "Test failed, server ID was not correct.\n");
|
||||
int rval = test->global_result;
|
||||
delete test;
|
||||
return rval;
|
||||
int server_ids[server_count];
|
||||
cout << "Server id:s are:";
|
||||
for (int i = 0; i < server_count; i++)
|
||||
{
|
||||
server_ids[i] = test.repl->get_server_id(i);
|
||||
cout << " " << server_ids[i];
|
||||
}
|
||||
cout << ".\n";
|
||||
|
||||
auto maxconn = test.maxscales->open_rwsplit_connection(0);
|
||||
test.try_query(maxconn, "SELECT 1;");
|
||||
if (test.ok())
|
||||
{
|
||||
const char wrong_server[] = "Query went to wrong server.";
|
||||
cout << "Testing with all servers on. Select-queries should go to servers " << server_ids[1]
|
||||
<< " and " << server_ids[2] << ".\n";
|
||||
IdSet allowed = {server_ids[1], server_ids[2]};
|
||||
// With all servers on, the query should go to either 2 or 3. Test several times.
|
||||
for (int i = 0; i < 5 && test.ok(); i++)
|
||||
{
|
||||
test.expect(check_server_id(maxconn, allowed), wrong_server);
|
||||
}
|
||||
|
||||
auto test_server_down = [&](int node_to_stop, int allowed_node) {
|
||||
test.repl->stop_node(node_to_stop);
|
||||
test.maxscales->wait_for_monitor(1);
|
||||
int stopped_id = server_ids[node_to_stop];
|
||||
int allowed_id = server_ids[allowed_node];
|
||||
cout << "Stopped server " << stopped_id << ".\n";
|
||||
cout << "Select-queries should go to server " << allowed_id << " only.\n";
|
||||
IdSet allowed_set = {allowed_id};
|
||||
// Test that queries only go to the correct server.
|
||||
for (int i = 0; i < 5 && test.ok(); i++)
|
||||
{
|
||||
test.expect(check_server_id(maxconn, allowed_set), "%s", wrong_server);
|
||||
}
|
||||
test.repl->start_node(node_to_stop, "");
|
||||
cout << "Restarted server " << stopped_id << ".\n";
|
||||
};
|
||||
|
||||
if (test.ok())
|
||||
{
|
||||
test_server_down(1, 2);
|
||||
}
|
||||
if (test.ok())
|
||||
{
|
||||
test_server_down(2, 1);
|
||||
}
|
||||
}
|
||||
mysql_close(maxconn);
|
||||
|
||||
test.repl->disconnect();
|
||||
return test.global_result;
|
||||
}
|
||||
|
||||
bool check_server_id(MYSQL* conn, const IdSet& allowed_ids)
|
||||
{
|
||||
bool id_ok = false;
|
||||
char str[100];
|
||||
if (find_field(conn, "SELECT @@server_id", "@@server_id", str))
|
||||
{
|
||||
cout << "Failed to query for @@server_id: " << mysql_error(conn) << ".\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
int queried_id = atoi(str);
|
||||
if (allowed_ids.count(queried_id))
|
||||
{
|
||||
cout << "Query went to server " << queried_id << ".\n";
|
||||
id_ok = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << "Queried unexpected server id " << queried_id << ".\n";
|
||||
}
|
||||
}
|
||||
return id_ok;
|
||||
}
|
||||
|
210
maxscale-system-test/pam_authentication.cpp
Normal file
210
maxscale-system-test/pam_authentication.cpp
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (c) 2016 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: 2022-01-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 "fail_switch_rejoin_common.cpp"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using std::string;
|
||||
using std::cout;
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
TestConnections test(argc, argv);
|
||||
test.repl->connect();
|
||||
delete_slave_binlogs(test);
|
||||
|
||||
const char pam_user[] = "dduck";
|
||||
const char pam_pw[] = "313";
|
||||
|
||||
const string add_user_cmd = (string)"useradd " + pam_user;
|
||||
const string add_pw_cmd = (string)"echo " + pam_user + ":" + pam_pw + " | chpasswd";
|
||||
const string read_shadow = "chmod o+r /etc/shadow";
|
||||
|
||||
const string remove_user_cmd = (string)"userdel --remove " + pam_user;
|
||||
const string read_shadow_off = "chmod o-r /etc/shadow";
|
||||
|
||||
test.repl->connect();
|
||||
// Prepare the backends for PAM authentication. Enable the plugin and create a user. Also, since
|
||||
// make /etc/shadow readable for all so that the server process can access it.
|
||||
for (int i = 0; i < test.repl->N; i++)
|
||||
{
|
||||
MYSQL* conn = test.repl->nodes[i];
|
||||
test.try_query(conn, "INSTALL SONAME 'auth_pam';");
|
||||
test.repl->ssh_node_f(i, true, "%s", add_user_cmd.c_str());
|
||||
test.repl->ssh_node_f(i, true, "%s", add_pw_cmd.c_str());
|
||||
test.repl->ssh_node_f(i, true, "%s", read_shadow.c_str());
|
||||
}
|
||||
|
||||
// Also create the user on the node running MaxScale, as the MaxScale PAM plugin compares against
|
||||
// local users.
|
||||
test.maxscales->ssh_node_f(0, true, "%s", add_user_cmd.c_str());
|
||||
test.maxscales->ssh_node_f(0, true, "%s", add_pw_cmd.c_str());
|
||||
test.maxscales->ssh_node_f(0, true, "%s", read_shadow.c_str());
|
||||
|
||||
if (test.ok())
|
||||
{
|
||||
cout << "PAM-plugin installed and users created on all servers. Starting MaxScale.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << "Test preparations failed.\n";
|
||||
}
|
||||
|
||||
|
||||
|
||||
auto expect_server_status = [&test](const string& server_name, const string& status) {
|
||||
auto set_to_string = [](const StringSet& str_set) -> string {
|
||||
string rval;
|
||||
string sep;
|
||||
for (const string& elem : str_set)
|
||||
{
|
||||
rval += elem + sep;
|
||||
sep = ", ";
|
||||
}
|
||||
return rval;
|
||||
};
|
||||
|
||||
auto status_set = test.maxscales->get_server_status(server_name.c_str());
|
||||
string status_str = set_to_string(status_set);
|
||||
bool found = (status_set.count(status) == 1);
|
||||
test.expect(found, "%s was not %s as was expected. Status: %s.",
|
||||
server_name.c_str(), status.c_str(), status_str.c_str());
|
||||
};
|
||||
|
||||
string server_names[] = {"server1", "server2", "server3", "server4"};
|
||||
string master = "Master";
|
||||
string slave = "Slave";
|
||||
|
||||
if (test.ok())
|
||||
{
|
||||
get_output(test);
|
||||
print_gtids(test);
|
||||
|
||||
expect_server_status(server_names[0], master);
|
||||
expect_server_status(server_names[1], slave);
|
||||
expect_server_status(server_names[2], slave);
|
||||
expect_server_status(server_names[3], slave);
|
||||
}
|
||||
|
||||
// Helper function for checking PAM-login.
|
||||
auto try_log_in = [&test](const string& user, const string& pass) {
|
||||
const char* host = test.maxscales->IP[0];
|
||||
int port = test.maxscales->ports[0][0];
|
||||
printf("Trying to log in to [%s]:%i as %s.\n", host, port, user.c_str());
|
||||
|
||||
MYSQL* maxconn = mysql_init(NULL);
|
||||
test.expect(maxconn, "mysql_init failed");
|
||||
if (maxconn)
|
||||
{
|
||||
// Need to set plugin directory so that dialog.so is found.
|
||||
const char plugin_path[] = "../connector-c/install/lib/mariadb/plugin";
|
||||
mysql_optionsv(maxconn, MYSQL_PLUGIN_DIR, plugin_path);
|
||||
mysql_real_connect(maxconn, host, user.c_str(), pass.c_str(), NULL, port, NULL, 0);
|
||||
auto err = mysql_error(maxconn);
|
||||
if (*err)
|
||||
{
|
||||
test.expect(false, "Could not log in: '%s'", err);
|
||||
}
|
||||
else
|
||||
{
|
||||
test.try_query(maxconn, "SELECT rand();");
|
||||
if (test.ok())
|
||||
{
|
||||
cout << "Logged in and queried successfully.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << "Query rejected: '" << mysql_error(maxconn) << "'\n";
|
||||
}
|
||||
}
|
||||
mysql_close(maxconn);
|
||||
}
|
||||
};
|
||||
|
||||
auto update_users = [&test]() {
|
||||
test.maxscales->execute_maxadmin_command(0, "reload dbusers RWSplit-Router");
|
||||
};
|
||||
|
||||
if (test.ok())
|
||||
{
|
||||
MYSQL* conn = test.repl->nodes[0];
|
||||
// Create the PAM user on the master, it will replicate. Use the standard password service for
|
||||
// authenticating.
|
||||
test.try_query(conn, "CREATE OR REPLACE USER '%s'@'%%' IDENTIFIED VIA pam USING 'passwd';", pam_user);
|
||||
test.try_query(conn, "GRANT SELECT ON *.* TO '%s'@'%%';", pam_user);
|
||||
test.try_query(conn, "FLUSH PRIVILEGES;");
|
||||
sleep(1);
|
||||
test.repl->sync_slaves();
|
||||
update_users();
|
||||
|
||||
// If ok so far, try logging in with PAM.
|
||||
if (test.ok())
|
||||
{
|
||||
cout << "Testing normal PAM user.\n";
|
||||
try_log_in(pam_user, pam_pw);
|
||||
}
|
||||
|
||||
// Remove the created user.
|
||||
test.try_query(conn, "DROP USER '%s'@'%%';", pam_user);
|
||||
}
|
||||
|
||||
if (test.ok())
|
||||
{
|
||||
const char dummy_user[] = "proxy-target";
|
||||
const char dummy_pw[] = "unused_pw";
|
||||
// Basic PAM authentication seems to be working. Now try with an anonymous user proxying to
|
||||
// the real user. The following does not actually do proper user mapping, as that requires further
|
||||
// setup on the backends. It does however demonstrate that MaxScale detects the anonymous user and
|
||||
// accepts the login of a non-existent user with PAM.
|
||||
MYSQL* conn = test.repl->nodes[0];
|
||||
// Add a user which will be proxied.
|
||||
test.try_query(conn, "CREATE OR REPLACE USER '%s'@'%%' IDENTIFIED BY '%s';", dummy_user, dummy_pw);
|
||||
|
||||
// Create the anonymous catch-all user and allow it to proxy as the "proxy-target", meaning it
|
||||
// gets the target's privileges. Granting the proxy privilege is a bit tricky since only the local
|
||||
// root user can give it.
|
||||
test.try_query(conn, "CREATE OR REPLACE USER ''@'%%' IDENTIFIED VIA pam USING 'passwd';");
|
||||
test.repl->ssh_node_f(0, true, "echo \"GRANT PROXY ON '%s'@'%%' TO ''@'%%'; FLUSH PRIVILEGES;\" | mysql --user=root",
|
||||
dummy_user);
|
||||
sleep(1);
|
||||
test.repl->sync_slaves();
|
||||
update_users();
|
||||
|
||||
if (test.ok())
|
||||
{
|
||||
// Again, try logging in with the same user.
|
||||
cout << "Testing anonymous proxy user.\n";
|
||||
try_log_in(pam_user, pam_pw);
|
||||
}
|
||||
|
||||
// Remove the created users.
|
||||
test.try_query(conn, "DROP USER '%s'@'%%';", dummy_user);
|
||||
test.try_query(conn, "DROP USER ''@'%%';");
|
||||
}
|
||||
|
||||
// Cleanup: remove the linux users on the backends and MaxScale node, unload pam plugin.
|
||||
for (int i = 0; i < test.repl->N; i++)
|
||||
{
|
||||
MYSQL* conn = test.repl->nodes[i];
|
||||
test.try_query(conn, "UNINSTALL SONAME 'auth_pam';");
|
||||
test.repl->ssh_node_f(i, true, "%s", remove_user_cmd.c_str());
|
||||
test.repl->ssh_node_f(i, true, "%s", read_shadow_off.c_str());
|
||||
}
|
||||
test.maxscales->ssh_node_f(0, true, "%s", remove_user_cmd.c_str());
|
||||
test.maxscales->ssh_node_f(0, true, "%s", read_shadow_off.c_str());
|
||||
|
||||
test.repl->disconnect();
|
||||
return test.global_result;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user