From 48a6ab503e7f583fc7a495192c7f67319b694193 Mon Sep 17 00:00:00 2001 From: Esa Korhonen Date: Wed, 13 Feb 2019 14:44:00 +0200 Subject: [PATCH] MXS-2292 PAM authenticator detects anonymous users with defined hosts This allows anonymous user mapping from well-defined hosts. --- .../authenticator/PAM/PAMAuth/pam_auth.cc | 3 +- .../authenticator/PAM/PAMAuth/pam_auth.hh | 1 + .../PAM/PAMAuth/pam_client_session.cc | 53 +++++-- .../PAM/PAMAuth/pam_client_session.hh | 4 +- .../authenticator/PAM/PAMAuth/pam_instance.cc | 133 ++++++++++-------- .../authenticator/PAM/PAMAuth/pam_instance.hh | 9 +- 6 files changed, 124 insertions(+), 79 deletions(-) diff --git a/server/modules/authenticator/PAM/PAMAuth/pam_auth.cc b/server/modules/authenticator/PAM/PAMAuth/pam_auth.cc index 8479fc96c..b9fd4c1ff 100644 --- a/server/modules/authenticator/PAM/PAMAuth/pam_auth.cc +++ b/server/modules/authenticator/PAM/PAMAuth/pam_auth.cc @@ -26,7 +26,8 @@ const string FIELD_HOST = "host"; const string FIELD_DB = "db"; const string FIELD_ANYDB = "anydb"; const string FIELD_AUTHSTR = "authentication_string"; -const int NUM_FIELDS = 5; +const string FIELD_PROXY = "proxy_grant"; +const int NUM_FIELDS = 6; /** * Initialize PAM authenticator diff --git a/server/modules/authenticator/PAM/PAMAuth/pam_auth.hh b/server/modules/authenticator/PAM/PAMAuth/pam_auth.hh index 99184b783..13b975a39 100644 --- a/server/modules/authenticator/PAM/PAMAuth/pam_auth.hh +++ b/server/modules/authenticator/PAM/PAMAuth/pam_auth.hh @@ -32,4 +32,5 @@ extern const string FIELD_HOST; extern const string FIELD_DB; extern const string FIELD_ANYDB; extern const string FIELD_AUTHSTR; +extern const string FIELD_PROXY; extern const int NUM_FIELDS; diff --git a/server/modules/authenticator/PAM/PAMAuth/pam_client_session.cc b/server/modules/authenticator/PAM/PAMAuth/pam_client_session.cc index 837666ecd..28a37d787 100644 --- a/server/modules/authenticator/PAM/PAMAuth/pam_client_session.cc +++ b/server/modules/authenticator/PAM/PAMAuth/pam_client_session.cc @@ -257,37 +257,62 @@ PamClientSession* PamClientSession::create(const PamInstance& inst) * @param session MySQL session * @param services_out Output for services */ -void PamClientSession::get_pam_user_services(const DCB* dcb, - const MYSQL_session* session, +void PamClientSession::get_pam_user_services(const DCB* dcb, const MYSQL_session* session, StringVector* services_out) { - string services_query = string("SELECT authentication_string FROM ") + m_instance.m_tablename - + " WHERE " + FIELD_USER + " = '" + session->user + "' AND '" + dcb->remote - + "' LIKE " + FIELD_HOST + " AND (" + FIELD_ANYDB + " = '1' OR '" + session->db - + "' = '' OR '" + session->db + "' LIKE " + FIELD_DB - + ") ORDER BY authentication_string;"; + string services_query = string("SELECT authentication_string FROM ") + m_instance.m_tablename + " WHERE " + + FIELD_USER + " = '" + session->user + "'" + + " AND '" + dcb->remote + "' LIKE " + FIELD_HOST + + " AND (" + FIELD_ANYDB + " = '1' OR '" + session->db + "' = '' OR '" + + session->db + "' LIKE " + FIELD_DB + ")" + + " AND " + FIELD_PROXY + " = '0' ORDER BY authentication_string;"; MXS_DEBUG("PAM services search sql: '%s'.", services_query.c_str()); + char* err; if (sqlite3_exec(m_dbhandle, services_query.c_str(), user_services_cb, services_out, &err) != SQLITE_OK) { MXS_ERROR("Failed to execute query: '%s'", err); sqlite3_free(err); } - MXS_DEBUG("User '%s' matched %lu rows in %s db.", - session->user, - services_out->size(), - m_instance.m_tablename.c_str()); - if (services_out->empty()) + auto word_entry = [](size_t num) -> const char* { + return (num == 1) ? "entry" : "entries"; + }; + + if (!services_out->empty()) { - // No service found for user with correct username & password. Check if anonymous user exists. + auto num_services = services_out->size(); + MXS_INFO("Found %lu valid PAM user %s for '%s'@'%s'.", + num_services, word_entry(num_services), session->user, dcb->remote); + } + else + { + // No service found for user with correct username & host. + // Check if a matching anonymous user exists. const string anon_query = string("SELECT authentication_string FROM ") + m_instance.m_tablename - + " WHERE " + FIELD_USER + " = '' AND " + FIELD_HOST + " = '%';"; + + " WHERE " + FIELD_USER + " = ''" + + " AND '" + dcb->remote + "' LIKE " + FIELD_HOST + + + " AND " + FIELD_PROXY + " = '1' ORDER BY authentication_string;"; + MXS_DEBUG("PAM proxy user services search sql: '%s'.", anon_query.c_str()); + if (sqlite3_exec(m_dbhandle, anon_query.c_str(), user_services_cb, services_out, &err) != SQLITE_OK) { MXS_ERROR("Failed to execute query: '%s'", err); sqlite3_free(err); } + else + { + auto num_services = services_out->size(); + if (num_services == 0) + { + MXS_INFO("Found no PAM user entries for '%s'@'%s'.", session->user, dcb->remote); + } + else + { + MXS_INFO("Found %lu matching anonymous PAM user %s for '%s'@'%s'.", + num_services, word_entry(num_services), session->user, dcb->remote); + } + } } } diff --git a/server/modules/authenticator/PAM/PAMAuth/pam_client_session.hh b/server/modules/authenticator/PAM/PAMAuth/pam_client_session.hh index 438352215..26875ce4c 100644 --- a/server/modules/authenticator/PAM/PAMAuth/pam_client_session.hh +++ b/server/modules/authenticator/PAM/PAMAuth/pam_client_session.hh @@ -33,9 +33,7 @@ public: bool extract(DCB* dcb, GWBUF* read_buffer); private: PamClientSession(sqlite3* dbhandle, const PamInstance& instance); - void get_pam_user_services(const DCB* dcb, - const MYSQL_session* session, - StringVector* services_out); + void get_pam_user_services(const DCB* dcb, const MYSQL_session* session, StringVector* services_out); maxscale::Buffer create_auth_change_packet() const; pam_auth_state m_state; /**< Authentication state*/ diff --git a/server/modules/authenticator/PAM/PAMAuth/pam_instance.cc b/server/modules/authenticator/PAM/PAMAuth/pam_instance.cc index 5dc2e8c77..700f846c1 100644 --- a/server/modules/authenticator/PAM/PAMAuth/pam_instance.cc +++ b/server/modules/authenticator/PAM/PAMAuth/pam_instance.cc @@ -36,16 +36,23 @@ PamInstance* PamInstance::create(char** options) const string pam_db_name = DEFAULT_PAM_DATABASE_NAME; /** The table name where we store the users */ const string pam_table_name = DEFAULT_PAM_TABLE_NAME; + /** Deletion statement for the in-memory table */ + const string drop_sql = string("DROP TABLE IF EXISTS ") + pam_table_name + ";"; /** CREATE TABLE statement for the in-memory table */ - const string create_sql = string("CREATE TABLE IF NOT EXISTS ") + pam_table_name - + " (" + FIELD_USER + " varchar(255), " + FIELD_HOST + " varchar(255), " - + FIELD_DB + " varchar(255), " + FIELD_ANYDB + " boolean, " - + FIELD_AUTHSTR + " text);"; + const string create_sql = string("CREATE TABLE ") + pam_table_name + + " (" + FIELD_USER + " varchar(255), " + + FIELD_HOST + " varchar(255), " + + FIELD_DB + " varchar(255), " + + FIELD_ANYDB + " boolean, " + + FIELD_AUTHSTR + " text, " + + FIELD_PROXY + " boolean);"; + if (sqlite3_threadsafe() == 0) { MXS_WARNING("SQLite3 was compiled with thread safety off. May cause " "corruption of in-memory database."); } + bool error = false; /* This handle may be used from multiple threads, set full mutex. */ sqlite3* dbhandle = NULL; @@ -58,9 +65,15 @@ PamInstance* PamInstance::create(char** options) } char* err; + if (!error && sqlite3_exec(dbhandle, drop_sql.c_str(), NULL, NULL, &err) != SQLITE_OK) + { + MXS_ERROR("Failed to drop table: '%s'", err); + sqlite3_free(err); + error = true; + } if (!error && sqlite3_exec(dbhandle, create_sql.c_str(), NULL, NULL, &err) != SQLITE_OK) { - MXS_ERROR("Failed to create database: '%s'", err); + MXS_ERROR("Failed to create table: '%s'", err); sqlite3_free(err); error = true; } @@ -96,12 +109,10 @@ PamInstance::PamInstance(sqlite3* dbhandle, const string& dbname, const string& * @param db Database * @param anydb Global access to databases * @param pam_service The PAM service used + * @param proxy Is the user anonymous with a proxy grant */ -void PamInstance::add_pam_user(const char* user, - const char* host, - const char* db, - bool anydb, - const char* pam_service) +void PamInstance::add_pam_user(const char* user, const char* host, const char* db, bool anydb, + const char* pam_service, bool proxy) { /** * The insert query template which adds users to the pam_users table. @@ -110,7 +121,7 @@ void PamInstance::add_pam_user(const char* user, * no quotes around them. The quotes for strings are added in this function. */ const string insert_sql_template = - "INSERT INTO " + m_tablename + " VALUES ('%s', '%s', %s, '%s', %s)"; + "INSERT INTO " + m_tablename + " VALUES ('%s', '%s', %s, '%s', %s, '%s')"; /** Used for NULL value creation in the INSERT query */ const char NULL_TOKEN[] = "NULL"; @@ -141,11 +152,10 @@ void PamInstance::add_pam_user(const char* user, char insert_sql[len + 1]; sprintf(insert_sql, insert_sql_template.c_str(), - user, - host, - db_str.c_str(), - anydb ? "1" : "0", - service_str.c_str()); + user, host, + db_str.c_str(), anydb ? "1" : "0", + service_str.c_str(), + proxy ? "1" : "0"); char* err; if (sqlite3_exec(m_dbhandle, insert_sql, NULL, NULL, &err) != SQLITE_OK) @@ -153,6 +163,18 @@ void PamInstance::add_pam_user(const char* user, MXS_ERROR("Failed to insert user: %s", err); sqlite3_free(err); } + else + { + if (proxy) + { + MXS_INFO("Added anonymous PAM user ''@'%s' with proxy grants using service %s.", + host, service_str.c_str()); + } + else + { + MXS_INFO("Added normal PAM user '%s'@'%s' using service %s.", user, host, service_str.c_str()); + } + } } /** @@ -209,8 +231,7 @@ int PamInstance::load_users(SERVICE* service) if (mysql_query(mysql, PAM_USERS_QUERY)) { MXS_ERROR("Failed to query server '%s' for PAM users: '%s'.", - servers->server->name, - mysql_error(mysql)); + servers->server->name, mysql_error(mysql)); } else { @@ -219,23 +240,20 @@ int PamInstance::load_users(SERVICE* service) if (res) { mxb_assert(mysql_num_fields(res) == PAM_USERS_QUERY_NUM_FIELDS); - MXS_NOTICE("Loaded %llu users for service %s.", - mysql_num_rows(res), - service->name); MYSQL_ROW row; while ((row = mysql_fetch_row(res))) { - add_pam_user(row[0], - row[1], - row[2], - row[3] && strcasecmp(row[3], "Y") == 0, - row[4]); + add_pam_user(row[0], row[1], // user, host + row[2], row[3] && strcasecmp(row[3], "Y") == 0, // db, anydb + row[4], // pam service + false); // not a proxy } mysql_free_result(res); - if (query_anon_proxy_user(servers->server, mysql)) - { - rval = MXS_AUTH_LOADUSERS_OK; - } + } + + if (fetch_anon_proxy_users(servers->server, mysql)) + { + rval = MXS_AUTH_LOADUSERS_OK; } } mysql_close(mysql); @@ -306,63 +324,68 @@ json_t* PamInstance::diagnostic_json() return rval; } -bool PamInstance::query_anon_proxy_user(SERVER* server, MYSQL* conn) +bool PamInstance::fetch_anon_proxy_users(SERVER* server, MYSQL* conn) { bool success = true; - bool anon_user_found = false; - string anon_pam_service; - const char ANON_USER_QUERY[] = "SELECT authentication_string FROM mysql.user WHERE " - "(plugin = 'pam' AND user = '' AND host = '%');"; - const char ANON_GRANT_QUERY[] = "SHOW GRANTS FOR ''@'%';"; + const char ANON_USER_QUERY[] = "SELECT host,authentication_string FROM mysql.user WHERE " + "(plugin = 'pam' AND user = '');"; + const char GRANT_PROXY[] = "GRANT PROXY ON"; // Query for the anonymous user which is used with group mappings if (mysql_query(conn, ANON_USER_QUERY)) { - MXS_ERROR("Failed to query server '%s' for the anonymous PAM user: '%s'.", - server->name, - mysql_error(conn)); + MXS_ERROR("Failed to query server '%s' for anonymous PAM users: '%s'.", + server->name, mysql_error(conn)); success = false; } else { + // Temporary storage of host,authentication_string for anonymous pam users. + std::vector> anon_users_info; MYSQL_RES* res = mysql_store_result(conn); if (res) { - MYSQL_ROW row = mysql_fetch_row(res); - if (row) + MYSQL_ROW row; + while ((row = mysql_fetch_row(res))) { - anon_user_found = true; - if (row[0]) - { - anon_pam_service = row[0]; - } + string host = row[0] ? row[0] : ""; + string auth_str = row[1] ? row[1] : ""; + anon_users_info.push_back(std::make_pair(host, auth_str)); } mysql_free_result(res); } - if (anon_user_found) + if (!anon_users_info.empty()) { - // Check that the anon user has a proxy grant - if (mysql_query(conn, ANON_GRANT_QUERY)) + MXS_INFO("Found %lu anonymous PAM user(s). Checking them for proxy grants.", + anon_users_info.size()); + } + + for (const auto& elem : anon_users_info) + { + string query = "SHOW GRANTS FOR ''@'" + elem.first + "';"; + // Check that the anon user has a proxy grant. + if (mysql_query(conn, query.c_str())) { - MXS_ERROR("Failed to query server '%s' for the grants of the anonymous PAM user: '%s'.", - server->name, - mysql_error(conn)); + MXS_ERROR("Failed to query server '%s' for grants of anonymous PAM user ''@'%s': '%s'.", + server->name, elem.first.c_str(), mysql_error(conn)); success = false; } else { if ((res = mysql_store_result(conn))) { + // The user may have multiple proxy grants, but is only added once. MYSQL_ROW row; while ((row = mysql_fetch_row(res))) { if (row[0] && strncmp(row[0], GRANT_PROXY, sizeof(GRANT_PROXY) - 1) == 0) { - MXS_NOTICE("Anonymous PAM user with proxy grant found. User account mapping " - "enabled."); - add_pam_user("", "%", NULL, false, anon_pam_service.c_str()); + add_pam_user("", elem.first.c_str(), // user, host + NULL, false, // Unused + elem.second.c_str(), true); // service, proxy + break; } } mysql_free_result(res); diff --git a/server/modules/authenticator/PAM/PAMAuth/pam_instance.hh b/server/modules/authenticator/PAM/PAMAuth/pam_instance.hh index 164f4f4a3..2f232162c 100644 --- a/server/modules/authenticator/PAM/PAMAuth/pam_instance.hh +++ b/server/modules/authenticator/PAM/PAMAuth/pam_instance.hh @@ -33,13 +33,10 @@ public: const std::string m_tablename; /**< The table where users are stored */ private: PamInstance(sqlite3* dbhandle, const std::string& m_dbname, const std::string& tablename); - void add_pam_user(const char* user, - const char* host, - const char* db, - bool anydb, - const char* pam_service); + void add_pam_user(const char* user, const char* host, const char* db, bool anydb, + const char* pam_service, bool proxy); void delete_old_users(); - bool query_anon_proxy_user(SERVER* server, MYSQL* conn); + bool fetch_anon_proxy_users(SERVER* server, MYSQL* conn); sqlite3* const m_dbhandle; /**< SQLite3 database handle */ };