Golang 編寫 MySQL UDF
一、MySQL UDF
這玩意全稱 “MySQL user-definable function”, 從名字就可以看出來叫 “用戶定義的方法”; 那麼 UDF 到底是幹啥的呢?
簡單一句話說就是說: 你可以自己寫點代碼處理數據, 然後把這段代碼編譯成動態鏈接庫 (so), 最後在 MySQL 中動態加載後用戶就可以用了.
二、解決方案
由於要檢查數據庫, 但是實際上審查並不會關注每個表甚至數據庫細節; 所以想到最簡單的方案就是在讀取和寫入時通過 UDF 定義一個 SM4 的加密算法把數據動態加密和解密, 關於其他細節這裏不做詳細說明, 本文主要闡述如何用 Go 搓一個簡單的 UDF 並使用.
三、UDF 方法
由於 UDF 官方支持是 C/C++, 所以在 Go 中需要使用 CGO; 一個 UDF 實現通常包含兩個 func:
func xxx_init(initid *C.UDF_INIT, args *C.UDF_ARGS, message *C.char) C.int {
// ... 邏輯實現
}
func xxx(initid *C.UDF_INIT, args *C.UDF_ARGS, result *C.char, length *C.ulong, is_null *C.char, error *C.char) *C.char {
// ...邏輯實現
}
其中 xxx_init 用於預檢查, xxx 作爲真正的邏輯實現; 當 xxx 方法被調用之前會先通過 xxx_init 方法做一次參數、內存分配等預處理.
注意: 從 MySQL 8.0.1 開始 xxx_init 的返回值從 my_bool 變更爲 int, 網上很多代碼寫 my_bool 的會導致無法通過編譯; 具體參考 https://bugs.mysql.com/bug.php?id=85131
四、Go 實現 UDF
知道了方法簽名以後, 就不多廢話直接上代碼實現:
package main
// #include <stdio.h>
// #include <sys/types.h>
// #include <sys/stat.h>
// #include <stdlib.h>
// #include <string.h>
// #include <mysql.h>
// #cgo CFLAGS: -D ENVIRONMENT=0 -I/usr/include/mysql -fno-omit-frame-pointer
import "C"
import (
"github.com/tjfoc/gmsm/sm4"
)
//export xsm4_enc_init
func xsm4_enc_init(initid *C.UDF_INIT, args *C.UDF_ARGS, message *C.char) C.int {
if args.arg_count != 1 {
msg := "xsm4_enc() 僅支持單個字符串參數\n"
C.strcpy(message, C.CString(msg))
return 1
}
return 0
}
//export xsm4_enc
func xsm4_enc(initid *C.UDF_INIT, args *C.UDF_ARGS, result *C.char, length *C.ulong, is_null *C.char, error *C.char) *C.char {
// 將 C 的指針轉換爲 Go 的類型
var str = C.GoString(*args.args)
var resp string
enc, err := sm4.Sm4Ecb([]byte("1234567890abcdef"), []byte(str), true)
if err != nil {
resp = err.Error()
} else {
resp = string(enc)
}
// 將結果轉換爲 C 的類型
var res = C.CString(resp)
// 設置輸出參數
*length = C.ulong(len(resp))
*is_null = 0
// 返回結果
return res
}
func main() {}
xsm4_enc_init 方法做一下檢查, 當前只支持單個字段參數, xsm4_enc 通過開源的 gmsm 庫對傳入的字段進行簡單的 SM4 加密並返回; 在真實環境中需要調用加密機來實現相關加密, 這裏只演示直接使用開源庫 + 固定密碼.
五、編譯並加載
將上面的代碼保存爲 xsm4_enc.go, 然後在安裝有 MySQL 頭文件的的服務器上使用以下命令編譯:
go build -o xsm4_enc.so -buildmode=c-shared xsm4_enc.go
如果沒問題將會生成一個 xsm4_enc.so 文件, 如果提示 C.xxx 類型沒找到等問題說明頭文件沒有加載, 自行檢查或修改 -I/usr/include/mysql 位置.
生成好 so 文件以後將其複製到 MySQL 的插件目錄 (插件目錄可通過 SHOW VARIABLES LIKE 'plugin_dir'; 查詢到):
cp xsm4_enc.so /usr/lib/mysql/plugin/
最後在 MySQL 中創建 UDF:
# 創建
CREATE FUNCTION xsm4_enc RETURNS STRING SONAME 'xsm4_enc.so';
# 刪除
DROP FUNCTION xsm4_enc;
六、UDF 使用
使用就簡單了, 在查詢的時候直接把你的 func 名稱寫上就行:
SELECT id, xsm4_enc(username), username FROM users;
同理也可以創建一個解密 UDF, 當然這些 UDF 最終配合視圖啥的做啥、怎麼用就不做過多贅述了.
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/M21o8Ouwr7iLkCPEQVeO9Q