Oberflächlich betrachtet ist der MySQL-Proxy [1] nicht mehr als ein Vermittler zwischen Datenbank-Client und -Server. Ohne spezifische Einstellungen gibt er Anfragen und Antworten zwischen Client und Server ohne weitere Veränderungen weiter. Mit dem nötigen Know-how eingesetzt, ist MySQL-Proxy ein mächtiges Werkzeug, dass eingehenden Datenbankabfragen und ihre Ergebnisse überwachen, analysieren und verändern kann. Dieser Artikel stellt seine Möglichkeiten und Einsatzgebiete auf und gibt eine Einführung, wie man MySQL-Proxy in eigenen Projekten einsetzen kann.
Die Kommunikation zwischen Client und Server bietet typischerweise drei Eingriffspunkte für einen Proxy: beim Verbindungsaufbau (Transparent Proxy, Loadbalancing), nach dem Empfangen der Client-Abfrage (Query-Modification, Code Injection) vor der Rückgabe des Query-Results an den Client (Result-Modification). An allen Punkten ist eine Vorgangsprotokollierung möglich, um das Systemverhalten zu analysieren. Abbildung 1 zeigt schematisch die möglichen Ansatzpunkte und verdeutlicht die damit verbunden Möglichkeiten.
Nach erfolgreicher Installation als Binary oder aus dem Quellcode ist der MySQL-Proxy einsatzbereit und kann mit dem Befehl »mysql-proxy
«
in Betrieb genommen werden. Mehr ist im ersten Schritt nicht notwendig, um einen Proxy auf seinem Standardport zu starten. Allerdings erwartet der Proxy einen lokal mit Standardeinstellungen laufenden MySQL-Server. Ein Verbindungsaufbau unter Angabe des Ports nutzt somit bereits den Proxy als Vermittler.
mysql --host=127.0.0.1 --port=4040 ↩ --user=Username --password
Zwar bietet der Proxy hier nicht als ein Port-Forwarder, jedoch basieren alle weiteren Funktionen auf diesem simplen Prinzip. Natürlich muss das Proxy-Zielsystem nicht auf dem gleichen Server oder Standardport laufen. Der Befehl »mysql-proxy --help-all
«
gibt Auskunft über die zur Verfügung stehenden Parameter.
Erweitert man die Konfiguration um einen zusätzlichen Backend-Server, hat man den einfachen Proxy-Server im Handumdrehen um Loadbalancer- und Failover-Funktionalität erweitert. Der Kommandozeilenbefehl könnte dann wie folgt aussehen.
mysql-proxy ↩ --proxy-backend-addresses=192.168.192.1:3306 ↩ --proxy-backend-addresses=192.168.192.2:3306 &
Alle eingehenden Anfragen verteilt der Proxy dann im Round-Robin-Prinzip an die verfügbaren Backend-Server. Verbindungsfehler, die zum Beispiel beim Ausfall eines Backend-Servers auftreten, meldet er auf der Standardausgabe. Bei ausschließlicher Verwendung als Loadbalancer- beziehungsweise Failover-Proxy sollte man das Profiling mit dem Parameter »--proxy-skip-profiling
«
ausschalten, um den Overhead zu minimieren.
Über die reine Vermittlungstätigkeit hinaus, bietet MySQL-Proxy eine außergewöhnlich flexible Schnittstelle, um den Datenfluss zwischen Client und Server(n) zu überwachen, zu protokollieren und vor allem zu verändern. Mit Hilfe der integrierten Lua-Engine bietet der MySQL-Proxy nahezu beliebige Möglichkeiten, den Datenfluss zu manipulieren.
Lua ist eine Skriptsprache, die meist zur Steuerung von C-Programmen dient, um diese ohne Veränderung des Source-Codes zu erweitern und zu skripten. Lua-Code wird zur Ausführung in eine Art Bytecode übersetzt und ist somit quasi plattformunabhängig. Ein Beschreibung von Lua und deren Aufbau würde den Rahmen des Artikels sprengen, exemplarisch werden jedoch auf einige Skripte näher beschrieben. Eine Einführung in Lua gibt der Artikel [2] in einem früheren Linux-Magazin-Sonderheft.
Wie erwähnt kann Lua an verschiedenen Punkten der Client-Server-Kommunikation ansetzten. Zur Veränderung beziehungsweise Erweiterung einer Abfrage bedient sich Lua der »read_query()
«
-Funktion. Der Lua-Code in Listing 1 verdeutlicht das Funktionsprinzip.
Listing 1
inform_user.lua
-- inform_user.lua function read_query(packet) if string.byte(packet) == proxy.COM_QUERY then print("The client committed the following query: " .. string.sub(packet, 2)) end end
Der Start des Proxys mit Angabe des Lua-Skripts aktiviert die entsprechende Funktion und führt zu folgender Ausgabe auf der Console:
The client committed the following query: ↩ select @@version_comment limit 1 The client committed the following query: ↩ SHOW TABLES FROM test
Dieses einfache Beispiel zeigt bereits deutlich, welche Möglichkeiten der MySQL-Proxy und sein Skript-Interface hier bieten.
Interessant wird eine solcher Proxy-Einsatz natürlich richtig bei der Modifikation des Datenverkehrs. Das Lua-Script in Listing 2 korrigiert einzelne Fehler in der Query-Syntax und verhindert die Ausführung einiger Befehle.
Listing 2
inspect_query.lua
-- inspect_query.lua function read_query( packet ) if string.byte(packet) == proxy.COM_QUERY then local query = string.sub(packet, 2) print ("received " .. query) -- matches "SELECT" as first word of the query if string.match(string.upper(query), '^%s*SELECT') then proxy.response = { type = proxy.MYSQLD_PACKET_ERR, errmsg = "SELECT ON DATABASE NOT ALLOWED", errno = 1305, sqlstate = "HY000" } return proxy.PROXY_SEND_QUERY end local replacing = false -- matches "INSET" as first word of the query if string.match(string.upper(query), '^%s*INSET') then query = string.gsub(query,'^%s*%w+', 'INSERT') replacing = true -- matches "UDATE" as first word of the query elseif string.match(string.upper(query), '^%s*UDATE') then query = string.gsub(query,'^%s*%w+', 'UPDATE') replacing = true end if (replacing) then print("replaced with " .. query ) proxy.queries:append(1, string.char(proxy.COM_QUERY) .. query ) return proxy.PROXY_SEND_QUERY end -- matches "SELECT" as first word of the query if string.match(string.upper(query), '^%s*SELECT') then proxy.response = { type = proxy.MYSQLD_PACKET_ERR, errmsg = "SELECT ON DATABASE NOT ALLOWED", errno = 1205, sqlstate = "HY000" } end end end
Nach dem Start des MySQL-Proxys mit dem Parameter »--proxy-lua-script=inspect_query.lua
«
führt der Proxy folgende Veränderungen durch: Ein Select der Daten wird durch die erste Abfrage im Lua-Script unterdrückt und führt zu einem Client-Fehler mit angegebener Warnung. So lassen sich einfache Select-Abfragen komplett unterdrücken. In den beiden nächsten Schritten korrigiert das Skript falsch geschriebene Insert- oder Update-Befehle, was im weiteren zum korrekten Ergebnis führt.
Mit Hilfe von Lua sind beliebige Kombinationen und Einbeziehung von Fremddaten möglich. So könnten auch Abfragen auf einzelne Namen innerhalb des Proxies unterdrückt oder auf Wunsch verändert.