Questo documento e' inutile! C'era gia' ottima documentazione su: Wikipedia (IT), O'Reilly, MySQL, documentazione ufficiale (strutture interne), [NdE i link su MySQL non sono piu' validi: MySQL Proxy non e' piu' distribuito/supportato e non e' mai stato ufficialmente in GA] ... Ma visto che avevo scritto altri documenti su MySQL ho deciso di scriverlo lo stesso per completezza!
MySQL Proxy e' un tool
che opera come proxy di un server MySQL. Nella configurazione
di default non fa nulla di piu' di un proxy verso localhost:3306
ma e' molto semplice programmarlo
per introdurre funzionalita' di logging, load balancing, SQL injection, ...
La programmazione si effettua con
LUA:
un semplice e
potente
linguaggio embedded interpretato.
Nel seguito sono riportate alcune informazioni su MySQL Proxy organizzate in paragrafi specifici: Introduzione Installazione Configurazione/Lancio Programmazione (esempi: login, tempi di risposta, sincrono, ...), Architettura, Amministrazione, Esempi (completi).
Meglio un esempio di tante parole:
mysql-proxy --proxy-backend-addresses=collsql.xenialab.it:3306 --proxy-lua-script=/usr/local/proxy/my_trace.luaCon il comando precedente si attiva MySQL Proxy in ascolto sulla porta 4040 che ridirige tutto il traffico SQL sul DB MySQL di backend specificato. Basta ora creare questo semplice script per disporre di un trace su MySQL:
# my_trace.lua function read_query( packet ) print( os.date('%Y-%m-%d %H:%M:%S ') .. string.sub(packet, 2) ) endViene cosi' tracciato ogni statement SQL richiesto dai client. Poiche' lo script viene valutato a runtime e' possibile modificare in ogni momento il trace senza interrompere il servizio.
MySQL Proxy e' molto utile... insomma: continuate a leggere!
MySQL Proxy e' ancora in release Alpha, non sono quindi
forniti pacchetti autoinstallanti.
Sono comunque stati generati da MySQL AB gli
eseguibili per la maggioranza delle piattaforme ed e' quindi sufficiente
effettuare il download dal sito ufficiale MySQL
e scaricare il contenuto sul proprio server per
avere un ambiente funzionante.
Questo documento e' stato preparato utilizzando MySQL Proxy 0.7.0 su un Linux/GNU CentOS 5.4,
ma gli elementi contenuti sono validi, mutatis mutandis, anche per tutte le altre
releases di MySQL Proxy e di sistema Unix ospite.
Non e' necessaria alcuna configurazione. Tutti i parametri possono essere passati da linea di comando.
# ./mysql-proxy --proxy-backend-addresses=collsql.xenialab.it:3306 # ./mysql-proxy --help-all Usage: /usr/local/mysql-proxy/libexec/mysql-proxy [OPTION...] - MySQL App Shell Help Options: -?, --help Show help options --help-all Show all help options ... admin-module --admin-address=<host:port> listening address:port of the admin-server (default: :4041) --admin-password=<string> password to allow to log in (default: secret [N.d.A.]) --admin-lua-script=<filename> script to execute by the admin plugin (default: not set [N.d.A.]) ... proxy-module --proxy-address=<host:port> listening address:port of the proxy-server (default: :4040) --proxy-read-only-backend-addresses=<host:port> address:port of the remote slave-server (default: not set) --proxy-backend-addresses=<host:port> address:port of the remote backend-servers (default: 127.0.0.1:3306) --proxy-lua-script=<file> filename of the lua script (default: not set) ... Application Options: -V, --version Show version --defaults-file=<file> configuration file --daemon Start in daemon-mode ...
I parametri riportati nell'esempio sono quelli maggiormente utilizzati con i relativi valori di default... Le versioni piu' recenti di MySQL Proxy consentono il logging su syslog e l'utilizzo di un file di configurazione.
MySQL-Proxy puo' intercettare tutti i tipi di pacchetti scambiati tra client e DB Server e svolgere le azioni desiderate che possono essere sia di logging, tracing che di modifica dei dati scambiati. Il diagramma seguente (mi perdonino i puristi UML...) riporta i diversi scambi di messaggi tra client e MySQL Server e le funzioni che e' possibile utilizzare con LUA per intercettarli.
connect_server() read_auth_result() read_handshake() read_auth() read_query() read_query_result()I dati sui messaggi scambiati e gli oggetti coinvolti sono disponibili nella struttura proxy e possono essere semplicemente acceduti con il LUA. Ad esempio l'attributo proxy.connection.client.src.address contiene l'IP del client che si connette al DB. La sintassi del LUA? Leggetela nella documentazione LUA ufficiale! L'elenco completo degli oggetti e degli attributi? Leggetelo sulla documentazione MySQL ufficiale!
Ecco un esempio che riporta tutte le operazioni di login:
function read_auth_result( auth ) print( "date="..os.date("%Y-%m-%d %H:%M:%S") .. ";" .. "command=LOGIN" .. ";" .. "source=" .. proxy.connection.client.src.name .. ";" .. "destination=" ..proxy.connection.server.dst.name ) endMa in realta' e' possibile tracciare molto di piu' iniziando dalle fasi iniziali dello scambio di messaggi con il client (quindi anche gli eventuali accessi diretti alla porta 3306). Ecco un esempio che cattura in modo completo lo scambio di dati in fase di login:
function connect_server() print( "date="..os.date("%Y-%m-%d %H:%M:%S") .. ";" .. "command=CONNECT" .. ";" .. "source="..proxy.connection.client.src.name .. ";" ) end function read_handshake() print( "date="..os.date("%Y-%m-%d %H:%M:%S") .. ";" .. "command=HANDSHAKE" .. ";" .. "source="..proxy.connection.client.src.name .. ";" .. "destination="..proxy.connection.server.dst.name .. ";" .. "version="..proxy.connection.server.mysqld_version .. ";" .. "thread="..proxy.connection.server.thread_id ..";" ) end function read_auth() print( "date="..os.date("%Y-%m-%d %H:%M:%S") .. ";" .. "command=AUTH" .. ";" .. "source="..proxy.connection.client.src.name .. ";" .. "destination="..proxy.connection.server.dst.name .. ";" .. "username="..proxy.connection.client.username .. ";" .. "default_db="..proxy.connection.client.default_db .. ";" ) end function read_auth_result( auth ) local state = auth.packet:byte() print( "date="..os.date("%Y-%m-%d %H:%M:%S") .. ";" .. "command=LOGIN" .. ";" .. "source="..proxy.connection.client.src.name .. ";" .. "destination="..proxy.connection.server.dst.name ) if state == proxy.MYSQLD_PACKET_OK then print( "status=OK" ) elseif state == proxy.MYSQLD_PACKET_ERR then print( "status=KO" ) else print( "status=UNKNOWN" ) end endE' possibile catturare informazioni su ogni comando lanciato sulla base dati... ad esempio per raccogliere i tempi di risposta. Per analizzare le risposte di una query con read_query_result() e' necessario aver effettuato l'injection in read_query().
function read_query( packet ) if packet:byte() == proxy.COM_QUERY then proxy.queries:append(1, packet ) return proxy.PROXY_SEND_QUERY end end function read_query_result(inj) print(" query: " .. inj.query) print(" query-time: " .. (inj.query_time / 1000) .. "ms\n") print(" response-time: " .. (inj.response_time / 1000) .. 'ms\n') endE' anche possibile trattare i dati restituiti dalle query e modificarli... Poiche' questo introduce un ritardo per default i dati vengono restituiti direttamente al client senza renderli disponibili al Proxy. Per trattare il resultset con gli script LUA del proxy e' necessario indicarlo in modo esplicito con proxy.queries:append(1, packet, { resultset_is_needed = true } ). Poiche' l'esempio di costruzione di un resultset e' un poco piu' complicato l'abbiamo tenuto al fondo!
Il tracing degli statement deve essere immediato per il debugging. La funzione LUA print() utilizzata negli esempi precedenti opera con il buffering, quindi non e' adatta. Ma il LUA e' molto potente (e molto vicino al linguaggio C in questo caso) e consente di controllare l'IO:
print("Starting MyTrace script " .. os.date('%Y-%m-%d %H:%M:%S')) local fh = io.open("/var/log/MyProxy.log", "a+") function read_query( packet ) fh:write( os.date('%Y-%m-%d %H:%M:%S') .. string.format("%6d ", proxy.connection.server["thread_id"]) .. "CMD[".. string.format("%d",string.byte(packet)).."]: " .. string.sub(packet, 2).. '\n') fh:flush() if packet:byte() == proxy.COM_QUERY then proxy.queries:append(1, packet ) return proxy.PROXY_SEND_QUERY end end function read_query_result(inj) fh:write(" query: " .. inj.query) fh:write(" query-time: " .. (inj.query_time / 1000) .. "ms\n") fh:write(" response-time: " .. (inj.response_time / 1000) .. 'ms\n') fh:flush() end
In realta' in molti casi basta un semplice io.stdout:flush()... ma altrimenti come lo giustificavo l'esempio?
MySQL e' costituito da un unico processo in ascolto su una porta (4040 di default)
che ribalta ogni richiesta sul DB MySQL di backend configurato (localhost:3306 di default).
Se e' definito uno script MySQL Proxy esegue i relativi comandi ad ogni evento configurato.
Lo script puo' effettuare I/O (eg. per il logging/tracing/debugging), puo' modificare
i messaggi sia in ingresso (alterando una query), sia in uscita (mascherando i risultati)
e puo' indirizzare le richiste verso DB MySQL di backend differenti.
Il livello di controllo possibile con MySQL Proxy e' quindi molto elevato.
Il ritardo introdotto da MySQL Proxy e' molto limitato
(soprattutto se non vengono analizzati i result set)
ed e' quindi utilizzabile
senza particolari problemi per le prestazioni.
Volevo disegnare uno schema di esempio... ma l'immagine seguente e' troppo bella per rifarla! (provenienza sito ufficiale MySQL):
MySQL Proxy puo' essere installato sullo stesso sistema su cui e' presente il DB MySQL oppure su un altro sistema. E' importante notare che le connessioni al database risulteranno provenire dal server che ospita il Proxy e non dal client iniziale. Questo e' molto importante poiche' su MySQL e' possibile distinguere gli utenti e le loro abilitazioni a seconda del server di provenienza. Vi e' quindi un impatto sia sulla definizione delle GRANT che nel tracciabilita' degli IP degli utenti. Per rintracciare i client che si connettono alla base dati e' necessario utilizzare l'interfaccia di amministrazione del proxy oppure... sfruttare l'SQL injection per rendere visibile l'IP del client come commento iniziale alla query!
function read_query( packet ) if string.byte(packet) == proxy.COM_QUERY then local query = "/* " .. proxy.connection.client.src.address .. " */ " .. string.sub(packet, 2) proxy.queries:append(1, string.char(proxy.COM_QUERY) .. query ) return proxy.PROXY_SEND_QUERY end end
Non tutte le connessioni verso una base dati MySQL debbono necessariamente provenire dal Proxy:
e' possibile realizzare sofisticate architettura con differenti livelli di sicurezza a seconda
della provenienza delle connessioni.
Nella figura a lato e' mostrata un'implementazione che consente di soddisfare i
requisiti della attuale legge italiana.
La legge in vigore e' il Codice in materia di protezione dei dati personali (d.lg. 30 giugno 2003, n. 196)
e, in particolare, gli artt. 31 ss. e 154, comma 1, lett. c) e h),
nonche' il disciplinare tecnico in materia di misure minime di sicurezza di cui all'allegato B del medesimo Codice.
Tale legge inquadra in modo completo la materia ma, dal punto di vista pratico, sono molto importanti i provvedimenti del Garante.
Il provvedimento del Garante del 27 novembre 2008 (Rif.
Misure e accorgimenti prescritti ai titolari dei trattamenti... )
relativo a "Misure e accorgimenti prescritti ai titolari dei trattamenti effettuati con strumenti elettronici
relativamente alle attribuzioni delle funzioni di amministratore di sistema" e' stato pubblicato sulla G.U. n. 300
del 24 dicembre 2008. Tale provvedimento prevede al comma 4.5
"Devono essere adottati sistemi idonei alla registrazione degli accessi logici
(autenticazione informatica) ai sistemi di elaborazione e agli archivi elettronici da parte degli amministratori di sistema."
Sono inoltre definiti i dettagli sulla ritenzione dei log, sulla loro archiviazione, i controlli e la loro frequenza, ...
Il provvedimento e' stato ulteriormente modificato con provvedimento del 25 Giugno 2009
(Rif.
Modifiche del provvedimento del 27 novembre 2008...)
che, al comma c), ne proroga l'adozione alla data definitiva del 15 Dicembre 2009.
Insomma dal 15 Dicembre 2009 vanno registrati tutti gli accessi amministrativi ai sistemi ed
alle basi dati!
Su MySQL e' disponibile il parametro init_connect che consente di lanciare statement custom al momento della connect. Ma il parametro init_connect non si applica agli utenti DBA, quindi non e' possibile utilizzarlo per tracciare le utenze amministrative. Per tale ragione viene utilizzato il proxy per il logging delle sessioni amministrative. L'altra soluzione, tecnicamente possibile poco pratica nella maggioranza dei casi, sarebbe stata quella di tracciare ogni statement SQL e poi filtrali...
Un altro importante utilizzo di MySQL Proxy e' il load balancing. Se vengono definiti piu' MySQL backend la politica di default e' il round robin. Ma con il LUA e' possibile programmare qualsiasi altro tipo di associazione si voglia. Ad esempio MySQL Proxy puo' essere utilizzato per separare il carico applicativo riconoscendo il tipo di statement richiamati ed indirizzandoli verso una complessa architettura replicata. In questo caso non e' necessaria alcuna modifica applicativa e si puo' ottenere una scalabilita' orizzontale elevatissima sfruttando la replicazione di MySQL:
Generalmente MySQL Proxy non richiede alcuna amministrazione. E' tuttavia disponibile un plugin di amministrazione che presenta un'interfaccia "SQL" (porta 4041 di default). I contenuti possono essere programmati con un apposito script richiamato dal parametro admin-lua-script. Ad esempio per ottenere informazioni sui server di backend MySQL ed il numero di connessioni presenti:
# mysql --host=127.0.0.1 --port=4041 --user=root --password=secret ... mysql> show; +-----------------------+--------------------+ | object | value | +-----------------------+--------------------+ | Proxy Version (hex) | 1794 | | | | | Backend # | 2 | | Backend 1 | 10.123.123.69:3306 | | Backend 1 connections | 7 | | Backend 2 | 10.123.123.70:3306 | | Backend 2 connections | 6 | +-----------------------+--------------------+E' necessario un script come il seguente:
function read_query(packet) if packet:byte() ~= proxy.COM_QUERY then proxy.response = { type = proxy.MYSQLD_PACKET_ERR, errmsg = "Command supported: SHOW" } return proxy.PROXY_SEND_RESULT end if string.sub(packet, 2):lower() == 'show' then return show_info() else return show_help() end end function show_help() proxy.response.type = proxy.MYSQLD_PACKET_OK proxy.response.resultset = { fields = { { type = proxy.MYSQL_TYPE_STRING, name = "command", }, { type = proxy.MYSQL_TYPE_STRING, name = "description", }, }, rows = { {'SHOW', 'Show all useful information.'}, } } return proxy.PROXY_SEND_RESULT end function show_info() proxy.response.type = proxy.MYSQLD_PACKET_OK proxy.response.resultset = { fields = { { type = proxy.MYSQL_TYPE_STRING, name = "object", }, { type = proxy.MYSQL_TYPE_STRING, name = "value", }, }, rows = { {'Proxy Version (hex)', proxy.PROXY_VERSION}, {'', ''}, {'Backend #', #proxy.global.backends}, } } local a = proxy.response.resultset["rows"] for i = 1, #proxy.global.backends do local b = proxy.global.backends[i] a[#a + 1] = { "Backend " .. i, b.dst.name } a[#a + 1] = { "Backend " .. i .. " connections", b.connected_clients } end return proxy.PROXY_SEND_RESULT end
Nelle versioni precedenti (<0.7) l'interfaccia di amministrazione era preprogrammata nel sorgente C e riconosceva tre specifiche query:
mysql> select * from help; +---------------------------------+--------------------------------------------+ | command | description | +---------------------------------+--------------------------------------------+ | select * from proxy_connections | show information about proxy connections | | select * from proxy_config | show information about proxy configuration | | select * from help | show the available commands | +---------------------------------+--------------------------------------------+
In ambiente Unix MySQL proxy gira come demone. E' quindi necessario creare uno script di startup da configurare al boot del sistema o agganciato ad un servizio cluster. Nel capitolo seguente e' riportato un esempio completo.
Ecco un esempio di uno script completo che traccia tutte le attivita' di connessione al DB, riporta le query piu' lunghe, effettua una SQL injection, ... e consente, con una semplice modifica, di tracciare ogni query eseguita sulla base dati:
--[[ $%BEGINLICENSE%$ logging.lua v.1.0.5 - Apr 31th 2010 Enables MySQL login full tracing and long queries logging SQL DoS attack logging (GRANT/REVOKE/DROP commands are logged) Full SQL tracing easy setup Log format has been specifically customized for SPLUNK data collection Client IP addresses are injected into the queries stdio is flushed to avoid buffering Copyright (C) 2009,2010 mail@meo.bogliolo.name This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA $%ENDLICENSE%$ --]] function connect_server() print( "date="..os.date("%Y-%m-%d %H:%M:%S") .. ";" .. "command=CONNECT" .. ";" .. "sourceip="..proxy.connection.client.src.name .. ";" ) end function read_handshake() print( "date="..os.date("%Y-%m-%d %H:%M:%S") .. ";" .. "command=HANDSHAKE" .. ";" .. "sourceip="..proxy.connection.client.src.name .. ";" .. "destination="..proxy.connection.server.dst.name .. ";" .. "version="..proxy.connection.server.mysqld_version .. ";" .. "thread="..proxy.connection.server.thread_id ..";" ) end function read_auth() print( "date="..os.date("%Y-%m-%d %H:%M:%S") .. ";" .. "command=AUTH" .. ";" .. "sourceip="..proxy.connection.client.src.name .. ";" .. "destination="..proxy.connection.server.dst.name .. ";" .. "thread="..proxy.connection.server.thread_id ..";" .. "username="..proxy.connection.client.username .. ";" .. "default_db="..proxy.connection.client.default_db .. ";" ) end function read_auth_result( auth ) local state = auth.packet:byte() if state == proxy.MYSQLD_PACKET_OK then print( "date="..os.date("%Y-%m-%d %H:%M:%S") .. ";" .. "command=LOGIN" .. ";" .. "sourceip="..proxy.connection.client.src.name .. ";" .. "destination="..proxy.connection.server.dst.name .. ";" .. "status=OK" .. ";" .. "thread="..proxy.connection.server.thread_id ..";" .. "username="..proxy.connection.client.username .. ";" .. "default_db="..proxy.connection.client.default_db .. ";" ) elseif state == proxy.MYSQLD_PACKET_ERR then print( "date="..os.date("%Y-%m-%d %H:%M:%S") .. ";" .. "command=LOGIN" .. ";" .. "sourceip="..proxy.connection.client.src.name .. ";" .. "destination="..proxy.connection.server.dst.name .. ";" .. "status=KO" .. ";" .. "thread="..proxy.connection.server.thread_id ..";" .. "username="..proxy.connection.client.username .. ";" .. "default_db="..proxy.connection.client.default_db .. ";".. "notes=" .. string.format("%q", auth.packet:sub(10)) .. ";" ) else print( "date="..os.date("%Y-%m-%d %H:%M:%S") .. ";" .. "command=LOGIN" .. ";" .. "sourceip="..proxy.connection.client.src.name .. ";" .. "destination="..proxy.connection.server.dst.name .. ";" .. "status=UNKNOWN" .. ";" .. "thread="..proxy.connection.server.thread_id ..";" .. "username="..proxy.connection.client.username .. ";" .. "default_db="..proxy.connection.client.default_db .. ";" .. "notes=" .. string.format("%q", auth.packet) .. ";" ) end io.stdout:flush() end function read_query( packet ) if packet:byte() == proxy.COM_QUIT then print( "date="..os.date("%Y-%m-%d %H:%M:%S") .. ";" .. "command=LOGOUT" .. ";" .. "sourceip="..proxy.connection.client.src.name .. ";" .. "destination="..proxy.connection.server.dst.name .. ";" .. "thread="..proxy.connection.server.thread_id ..";" .. "username="..proxy.connection.client.username .. ";" .. "default_db="..proxy.connection.client.default_db .. ";" ) io.stdout:flush() end if string.byte(packet) == proxy.COM_QUERY then local query = "/* " .. proxy.connection.client.src.address .. " */ " .. string.sub(packet, 2) -- DEBUG: comment the condition to enable full SQL tracing (if...or end) if string.find(string.upper(query), "GRANT") or string.find(string.upper(query), "REVOKE") or string.find(string.upper(query), "DROP") then print( "date="..os.date("%Y-%m-%d %H:%M:%S") .. ";" .. "command=QUERY" .. ";" .. "sourceip="..proxy.connection.client.src.name .. ";" .. "destination="..proxy.connection.server.dst.name .. ";" .. "thread="..proxy.connection.server.thread_id ..";" .. "username="..proxy.connection.client.username .. ";" .. "default_db="..proxy.connection.client.default_db .. ";" .. "query=" .. query .. ";" ) io.stdout:flush() end proxy.queries:append(1, string.char(proxy.COM_QUERY) .. query ) return proxy.PROXY_SEND_QUERY end end function read_query_result(inj) if inj.response_time > 5*1000*1000 then print( "date="..os.date("%Y-%m-%d %H:%M:%S") .. ";" .. "command=QUERY" .. ";" .. "sourceip="..proxy.connection.client.src.name .. ";" .. "destination="..proxy.connection.server.dst.name .. ";" .. "thread="..proxy.connection.server.thread_id ..";" .. "username="..proxy.connection.client.username .. ";" .. "default_db="..proxy.connection.client.default_db .. ";" .. "query=" .. string.sub(inj.query, 2) .. ";" .. "query-time=" .. (inj.query_time / 1000) .. ";" .. "response-time=" .. (inj.response_time / 1000) .. ";" ) io.stdout:flush() end end
In ambiente Unix MySQL proxy gira come demone. Ecco l'esempio di uno script di startup da configurare al boot del sistema o agganciato ad un servizio cluster:
#!/bin/sh # mysql_proxy v.1.0.1 startup script # # Usually in /etc/init.d, accepts start/stop/status commands install_dir=/usr/local/mysql-proxy back_end=collsql.xenialab.it:3306 script_file=$install_dir/share/my_script.lua exec_file=$install_dir/sbin/mysql-proxy pid_file=$install_dir/proxy.PID log_file=$install_dir/proxy.out mode=$1 # start or stop or status case "$mode" in 'start') # Start daemon echo $echo_n "Starting MySQL Proxy" cd $install_dir $exec_file --pid-file=$pid_file --proxy-backend-addresses=$back_end --proxy-lua-script=$script_file >> $log_file &2>&1 ;; 'stop') # Stop daemon. if test -s "$pid_file" then echo $echo_n "Shutting down MySQL Proxy" mysqlp_pid=`cat $pid_file` kill $mysqlp_pid rm "$pid_file" else echo "MySQL Proxy PID file could not be found!" fi ;; 'status') # Check MySQL processes if test -s "$pid_file" then echo "MySQL Proxy: is working " else echo "MySQL Proxy: is NOT working " fi exit 0 ;; *) # usage echo "Usage: $0 [start|stop|status]" exit 1 ;; esac
Testo: MySQL Proxy
Data: 1 Aprile 2008
Versione: 1.0.6 - 31 Aprile 2010
Autore: mail@meo.bogliolo.name