SQL稽核 | SQLE 如何開發一條自定義的規則

愛可生雲資料庫發表於2022-04-15

作者:Jason

就職於捷信消費金融有限公司,擔任 DBA 工作。先後從事過 Oracle 、Mongo 、MySQL 的 DBA ,以及大資料 ETL 的開發工作。對 NEWSQL 以及雲原生分散式資料庫具有濃厚的興趣愛好。

本文來源:原創投稿

*愛可生開源社群出品,原創內容未經授權不得隨意使用,轉載請聯絡小編並註明來源。


SQLE 是由上海愛可生資訊科技股份有限公司 開發並開源,支援SQL稽核、索引優化、事前稽核、事後稽核、支援標準化上線流程、原生支援 MySQL 稽核且資料庫型別可擴充套件的 SQL 稽核工具。

官方主頁 https://opensource.actionsky....

官方文件 Introduction · SQLE manual (actiontech.github.io)

(!!!事先宣告:二次開發純屬於個人技術研究,不得進行任何商業盈利行為)

大家好! 本次來分享的是如何開發一條自定義的review rule 的規則(基於MySQL 5.7的語法)。

在開發程式碼前,我們先從後臺API呼叫的角度梳理一下稽核SQL的具體流程:

我們可以先用 POSTMAN 從呼叫一遍程式碼稽核這個方法:

首先,先確定規則驗證的API介面:
http://10.25.15.83:10000/v1/t...

與API相對應的原始碼方法的入口是:
api.controller.v1.task.go --> func CreateAndAuditTask(c echo.Context)

由於系統做了JWT的登入驗證, 我們需要先模擬一下登入:

1)呼叫登入的API 10.25.15.83:10000/v1/login,獲得token:

預設的是 admin/admin ,登入成功後,我們可拿到 token

2)我們呼叫一下規則驗證的API介面: http://10.25.15.83:10000/v1/t...

我們準備了一下簡單的SQL語句:

create table test (id int not null, name varchar(20));

這個語句不符合審計的規則如下:
1)無主鍵
2)無列和表級別的comment
3)無innodb engine 指定

我們首先設定引數: 可以參考request的結構體。

type CreateAuditTaskReqV1 struct {
   InstanceName   string `json:"instance_name" form:"instance_name" example:"inst_1" valid:"required"`
   InstanceSchema string `json:"instance_schema" form:"instance_schema" example:"db1"`
   Sql            string `json:"sql" form:"sql" example:"alter table tb1 drop columns c1"`
}

引數1: InstanceName -》 DBA
引數2: instance_schema =》 testdb
引數3: sql =》 create table test (id int not null, name varchar(20));

我們還需要設定一下 token: 這個token 從登入的API返回值可以獲取到

下面我們嘗試呼叫一下審計介面:http://10.25.15.83:10000/v1/t...

我們來看一下返回給我們的response的含義 :

{
   "code": 0,
   "message": "ok", --表示api呼叫成功
   "data": {
       "task_id": 1,  --返回的稽核結果儲存在 task_is =1的任務中
       "instance_name": "DBA", --例項名
       "instance_schema": "testdb", --DB名
       "audit_level": "error", --ERROR 級別的錯誤
       "pass_rate": 0,  --通過率為0
       "status": "audited", --已經審計的狀態
       "sql_source": "form_data" --直接獲得SQL語句的方式
   }
}

我們要想知道具體的審計返回的結果需要查詢資料庫: ("task_id": 1, --返回的稽核結果儲存在 task_is =1的任務中)

mysql> select  audit_result from execute_sql_detail where task_id=1\G
*************************** 1. row ***************************
audit_result: [notice]列建議新增註釋
[error]表必須有主鍵
[notice]必須使用Innodb資料庫引擎
[notice]表建議新增註釋
1 row in set (0.00 sec)

從 response 的資訊來看 是完全符合我們之前的預期的結果。

下面我們要程式碼的角度追蹤一下這個審計的整個流程:
SQLE 採用的是類似資料庫driver外掛的開發模式,介面 driver.go 已經定義好了抽象的函式,具體的實現需要每一種具體的資料庫來完成
例如 mysql.go, oracle.go

下面是如何新增一條新的rule 到稽核平臺的具體步驟:

New Rule的規則是:
我們想要在新建的TABLE 表中必須含有指定的列, 像是cdate cdate,creator,edate,editor 這幾個審計欄位。

首先找到對應的資料庫裡面rule的表是 rule:

mysql> select * from rules \G
*************************** 28. row ***************************
  name: ddl_check_object_name_using_keyword
db_type: mysql
  desc: 資料庫物件命名禁止使用保留字
 level: error
  type: 命名規範
params: NULL
*************************** 29. row ***************************
  name: ddl_check_pk_name
db_type: mysql
  desc: 建議主鍵命名為"PK_表名"
 level: notice
  type: 命名規範
params: NULL

....

我們來手動插入一條新rule的記錄:

mysql> INSERT INTO sqle.rules
   -> (name, db_type, `desc`, `level`, `type`, params)
   -> VALUES('ddl_check_audit_column', 'mysql', '建表語句需要包含4個審計列(cdate,creator,edate,editor)', 'notice', '命名規範', NULL);
Query OK, 1 row affected (0.00 sec)


mysql> commit;
Query OK, 0 rows affected (0.00 sec)

我們可以從頁面看到這條新加的規則已經出現在了列表裡面:

我們把這條新的規則追加到我們自定義的規則模板中:

最後一步,提交規則的模板資訊。

下面我們從頁面上測試一下我們新加的規則:(當然了,對應的規則要新增對應的後臺程式碼,這個後面會有介紹~)

我們新建一個稽核任務:

輸入建表語句:

create table test (id int not null, name varchar(20));

點選稽核按鈕

我們可以看到我們自定義的稽核規則已經生效:

最後我們來看看後臺程式碼是如何開發實現這個規則的:

後臺對應的這條規則的邏輯程式碼是:(入門級別的簡單程式碼)這段程式碼加入到 sqle/driver/mysql/rule/rule.go中
用Map的結構體來判斷,必要的列的名字是否存在review 語句的表中

//可以在linux下進行命令列的程式碼單元測試
// dlv test github.com/actiontech/sqle/sqle/driver/mysql -- -test.run ^TestCheckAuditColumn$
func checkAuditColumn(ctx *session.Context, rule driver.Rule, res *driver.AuditResult, node ast.Node) error {
   var auditCols = [4]string{"cdate","edate","creator","editor"}
   var set map[string]struct{}
   set = make(map[string]struct{})
   for _, value := range auditCols{
      set[value] = struct{}{}
   }
    var cnt int = 0
   switch stmt := node.(type) {
   case *ast.CreateTableStmt:
      for _,value := range stmt.Cols {
         fmt.Println(value.Name.Name)
         if _, ok := set[value.Name.Name.String()];ok {
            cnt++
         } 

      }
   if cnt != 4{
      addResult(res, rule, rule.Name)
   }  


   }


   return nil
}

同時我們新增rule的mapping 規則: sqle/driver/mysql/rule/rule.go中

{
   Rule: driver.Rule{
      Name:     DDLCheckAuditColumn,
      Desc:     "建表語句必須包含審計列(cdate,creator,edate,editor)",
      Level:    driver.RuleLevelError,
      Category: RuleTypeUsageSuggestion,
   },
   Message:      "建表語句必須包含審計列(cdate,creator,edate,editor)",
   AllowOffline: true,
   Func:         checkAuditColumn,
},

新增常量定義: sqle/driver/mysql/rule/rule.go中

DDLCheckAuditColumn                         = "ddl_check_audit_column"

我們還可以在 sqle/driver/mysql/audit_offline_test.go 進行unit testing:

func TestCheckAuditColumn(t *testing.T) {
   fmt.Println("start..............")
   runSingleRuleInspectCase(rulepkg.RuleHandlerMap[rulepkg.DDLCheckAuditColumn].Rule, t,
      "create table test (id int not null, name varchar(20));  ",
      DefaultMysqlInspectOffline(),
      `create table test (id int not null,
                                name varchar(20),
                                cdate datetime,
                                edate datetime,
                                creator1 varchar(20),
                                editor1 varchar(20));`,
      newTestResult().addResult(rulepkg.DDLCheckAuditColumn),
   )
}


最後測試通過後,我們即可提交程式碼,釋出新的程式來從頁面上驗證我們自己定義的規則了。

總體來說,愛可生的程式碼是十分規範的,便於二次程式碼開發和維護。如果你是golang的開發愛好者,亦可作為學習專案的經典案例。

相關文章