之前的文章 ”CryptDB原理概述“ 介紹了CryptDB的基本原理,接下來從代碼的角度介紹其實(shí)現(xiàn)原理食铐。本文首先關(guān)注mysql-proxy的lua腳本與CryptDB加密庫的交互過程裙戏。
前期準(zhǔn)備
在進(jìn)行源碼閱讀和調(diào)試之前公浪,首先需要進(jìn)行CryptDB的安裝姆打。 之前已經(jīng)對CryptDB在ubuntu 16.04上的安裝做過介紹沧卢。也可以使用我在github上共享的項目:https://github.com/yiwenshao/Practical-Cryptdb,里面對原始的安裝腳本做了小改嫩实,在ubuntu16.04只要執(zhí)行INSTALL.sh 就可以完成全部的安裝工作刽辙。
安裝完成以后,首先執(zhí)行如下的命令:
mkdir shadow
mysql-proxy --defaults-file=./mysql-proxy.cnf --proxy-lua-script=wrapper.lua
啟動mysql-proxy[1]甲献,然后就可以通過MySQL的客戶端連接mysql-proxy來完成數(shù)據(jù)庫操作宰缤。 所有SQL語句首先經(jīng)過mysql-proxy的加密處理, 然后轉(zhuǎn)發(fā)給MySQL服務(wù)器晃洒。 對于MySQL服務(wù)器的返回結(jié)果慨灭, 也是先轉(zhuǎn)發(fā)給mysql-proxy, 經(jīng)過解密處理以后返回給客戶端球及。
mysql-proxy通過lua腳本調(diào)用CryptDB的加密庫來完成SQL語句的加密操作氧骤,而上面建立的shadow目錄則是為了保存一個embedded模式的MySQL數(shù)據(jù)庫, 里面存儲了密鑰以及加密洋蔥的結(jié)構(gòu)的等相關(guān)信息吃引。
所以筹陵,接下來,我們先從lua腳本的入口出發(fā)镊尺,看一個SQL語句在加密過程中會經(jīng)過哪些模塊朦佩。
mysql-proxy的與CryptDB的交互
mysql-proxy提供了如下幾個函數(shù), 在客戶端執(zhí)行SQL的不同階段庐氮, 會調(diào)用這幾個函數(shù)语稠, 這些函數(shù)實(shí)現(xiàn)在 wrapper.lua 中:
- read_auth()
- disconnect_client()
- read_query(packet)
- read_query_result(inj)
在上面的四個函數(shù)的實(shí)現(xiàn)中,調(diào)用了CryptDB的加密庫中的幾個函數(shù)弄砍,這些函數(shù)實(shí)現(xiàn)在 mysqlproxy/ConnectWrapper.cc 中仙畦,分別是:
- connect
- disconnect
- rewrite
- next
所以,所有的CryptDB操作音婶,都是以上面的8個函數(shù)為基礎(chǔ)的慨畸。對于CryptDB加密庫而言,里面的connect以及disconnect 只在客戶端建立連接和客戶端離開的時候被調(diào)用桃熄,所有的加解密功能都是通過rewrite和next這兩個函數(shù)來完成先口,這兩個函數(shù)就是CryptDB所有功能的入口型奥。
回到mysql-proxy的lua腳本中的四個函數(shù)瞳收,可以分以下三個階段來介紹。
客戶端建立連接
在這個階段厢汹,lua腳本中的函數(shù)read_auth被調(diào)用螟深,其內(nèi)部調(diào)用CryptDB的函數(shù)connect. 在這個階段,需要為每個客戶端建立一個WrapperState結(jié)構(gòu)來保存相關(guān)的信息烫葬。不同的客戶端通過ip+port來標(biāo)識界弧,多個客戶端的信息則通過一個如下的map結(jié)構(gòu)來進(jìn)行保存:
std::map<std::string, WrapperState*> clients;
要使用加密庫凡蜻,還需要進(jìn)行適當(dāng)?shù)某跏蓟⑶叶鄠€client之間有共享狀態(tài)垢箕。所以划栓,如果當(dāng)前連接進(jìn)來的是第一個客戶端,則需要對這個共享狀態(tài)進(jìn)行初始化条获,其對應(yīng)的是一個變量:
SharedProxyState * shared_ps//多個client的共享狀態(tài)
客戶端離開
當(dāng)客戶端關(guān)閉的時候忠荞,lua腳本中的disconnect_client函數(shù)被調(diào)用。其內(nèi)部調(diào)用CryptDB的disconnect函數(shù)帅掘。這個階段會把map中保存的客戶端的信息刪除委煤。
客戶端的命令執(zhí)行流程
客戶端發(fā)送命令給mysql-proxy的時候,lua腳本中的read_query(packet)函數(shù)被調(diào)用修档,參數(shù)packet中包含了SQL命令碧绞。MySQL執(zhí)行結(jié)果返回的時候,lua腳本中的read_query_result(inj)函數(shù)被調(diào)用吱窝,參數(shù)inj包含了返回結(jié)果讥邻。
- 首先來看read_query函數(shù)。
當(dāng)read_query 函數(shù)獲取到客戶端發(fā)送的明文SQL 命令的時候癣诱,會調(diào)用lua腳本中的read_query_real函數(shù)计维,其內(nèi)部首先調(diào)用CryptDB庫中的rewrite函數(shù),完成SQL語句的改寫撕予。改寫后的SQL語句保存在之前介紹過的 clients 結(jié)構(gòu)中鲫惶。然后調(diào)用lua腳本中的next _handler 函數(shù),其內(nèi)部調(diào)用CryptDB庫中的next函數(shù)实抡。在next函數(shù)中欠母,首先執(zhí)行函數(shù)獲得一個參數(shù)result_type,分為三種情況吆寨,根據(jù)不同的結(jié)果選擇不同的執(zhí)行流程赏淌,包含了SQL命令執(zhí)行的所有情況,分別如下:
//mysqlproxy/ConnectWrapper.cc
switch (result_type) {
case AbstractQueryExecutor::ResultType::QUERY_COME_AGAIN: {
//返回sql語句給lua腳本啄清,執(zhí)行結(jié)果再次進(jìn)入該函數(shù)處理
}
case AbstractQueryExecutor::ResultType::QUERY_USE_RESULTS:{
//返回sql語句給lua腳本六水,執(zhí)行結(jié)果直接返回客戶端
}
case AbstractQueryExecutor::ResultType::RESULTS:{
//返回解密結(jié)果給lua腳本,并由mysql-proxy返回給客戶端
}
}
對于一般的SQL語句的執(zhí)行辣卒,分為兩種情況掷贾,第一種是直接進(jìn)入QUERY_USE_RESULTS分支,返回SQL語句給lua腳本荣茫,SQL執(zhí)行的結(jié)果直接返回給客戶端想帅。 第二種是進(jìn)入第一個分支QUERY_COME_AGAIN,返回SQL語句給lua腳本轉(zhuǎn)發(fā)到MySQL執(zhí)行啡莉,返回的結(jié)果再次進(jìn)入next函數(shù)港准,執(zhí)行并且進(jìn)入第三個分支旨剥,返回解密的結(jié)果給客戶端。
另外浅缸,對于有些語句轨帜,并不需要調(diào)用rewrite函數(shù)進(jìn)行加密,在lua腳本的階段直接過濾了衩椒,這些情況就更加簡單阵谚。
- 然后來看read _query_results階段。
當(dāng)上面介紹的加密SQL語句發(fā)送給MySQL執(zhí)行烟具,并返回執(zhí)行結(jié)果給mysql-proxy的時候梢什,會調(diào)用lua腳本中的read _query_results(inj)函數(shù)。如果在read_query階段進(jìn)入了第二個分支朝聋,那么lua腳本會設(shè)置一個全局變量skip為true嗡午,read_query_results的處理就被跳過,直接返回結(jié)果給客戶端冀痕。如果在read_query階段進(jìn)入了第一個分支荔睹,則會在這里再次調(diào)用next_handler函數(shù),從而進(jìn)入next函數(shù)言蛇,再次執(zhí)行并進(jìn)入switch分支的判斷流程僻他。
兩個執(zhí)行的例子
一些解密的細(xì)節(jié)以及類的介紹將在后續(xù)的文章中給出。在這里腊尚,給出兩個SQL語句執(zhí)行的例子吨拗,用于說明執(zhí)行過程中l(wèi)ua腳本以及CryptDB庫中的幾個函數(shù)的調(diào)用過程,以及幾個主要執(zhí)行分支的含義婿斥。
- show databases;
該命令的處理流程:
首先進(jìn)入read_query劝篷,內(nèi)部調(diào)用CryptDB的rewrite函數(shù)進(jìn)行加密,然后調(diào)用lua中的next_handler民宿,內(nèi)部調(diào)用CryptDB的next函數(shù)娇妓,根據(jù)上面介紹的,進(jìn)入switch的第二個分支活鹰,表示執(zhí)行命令的結(jié)果不需要處理哈恰,直接返回給客戶端。然后給lua腳本傳遞改寫以后的命令志群。
再次回到lua腳本的next_handler函數(shù)着绷,其處理了query results分支拆撼,將獲得的命令轉(zhuǎn)發(fā)給MySQL執(zhí)行奢人。執(zhí)行完成以后的結(jié)果返回給mysql-proxy的時候,會調(diào)用read_query_result函數(shù)。根據(jù)上面介紹的宾抓,由于在read_query階段進(jìn)入了第二個分支子漩,這里的處理會被跳過,也就是不做任何處理石洗,結(jié)果直接返回給客戶端幢泼。這樣,客戶端就得到了show databases的執(zhí)行結(jié)果讲衫。
- select * from student;
我們假設(shè)已經(jīng)有了一張表缕棵,對其進(jìn)行select操作,其對應(yīng)的執(zhí)行流程如下涉兽。
首先進(jìn)入read_query招驴,內(nèi)部調(diào)用CryptDB的rewrite函數(shù)進(jìn)行SQL語句的加密,返回以后調(diào)用lua中的next_handler函數(shù)枷畏,內(nèi)部調(diào)用CryptDB的next函數(shù)别厘,根據(jù)上面介紹的,進(jìn)入第一個分支:QUERY_COME_AGAIN拥诡。返回加密以后的SQL命令給lua腳本触趴。
在lua腳本中,next_handler處理了again分支渴肉,發(fā)送加密命令給MySQL冗懦,獲得select返回的加密結(jié)果以后,lua腳本中的read_query_results被調(diào)用仇祭。由于在read_query階段進(jìn)入了第一個分支披蕉,這里會繼續(xù)調(diào)用next_handler函數(shù),并進(jìn)入到next函數(shù)乌奇,執(zhí)行獲得result_type嚣艇,然后這次進(jìn)入到RESULTS分支。在lua腳本中华弓,處理了results分支食零,將解密后的結(jié)果返回給客戶端,這樣就完成了整個流程寂屏。
總結(jié)
本文介紹了SQL加密執(zhí)行過程中贰谣,mysql-proxy使用的lua腳本與CryptDB的加密庫的交互過程,其中主要的幾個函數(shù)是lua腳本中的read_query迁霎,read_query_results吱抚,以及CryptDB庫中的rewrite和next。代碼位于wrapper.lua以及mysqlproxy/ConnectWrapper.cc中考廉。
參考文獻(xiàn)
- 1. MySQL-Proxy相關(guān): https://downloads.mysql.com/docs/mysql-proxy-en.pdf
- CryptDB的原理介紹: https://yiwenshao.github.io/2017/05/01/Cryptdb原理概述/#more
- CryptDB的安裝腳本: https://github.com/yiwenshao/Practical-Cryptdb
- 作者的wiki說明: https://github.com/cryptdb-org/cryptdb-wiki/wiki/New-Pipeline
原始鏈接:yiwenshao.github.io/2018/02/26/CryptDB代碼分析1-lua與加密庫/
文章作者:Yiwen Shao
許可協(xié)議: Attribution-NonCommercial 4.0
轉(zhuǎn)載請保留以上信息, 謝謝!