SQL稽核 | 如何快速使用 SQLE 稽核各種型別的資料庫

愛可生雲資料庫 發表於 2022-05-20
資料庫 SQL

作者:孫健

孫健,愛可生研發工程師,負責 SQLE 相關開發;

本文來源:原創投稿

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


前言

近些年來,資料庫產業發展迅猛,各種新興資料庫如雨後春筍般出現,各個公司的技術棧也不再侷限於某一種資料庫。對於SQL質量管理平臺來說僅支援某一個型別的資料庫(例如MySQL),那麼是會有一定的侷限性,SQLE在設計之初考慮支援多種資料庫,因此產品設計時,將稽核流程(業務)的程式碼和具體SQL稽核上線的程式碼進行分離,SQL稽核上線通過外掛的形式實現。SQLE對外提供外掛開發所需的介面和庫,可以快速建立開啟一個稽核外掛,無需升級軟體,匯入稽核外掛即可獲對應資料庫型別的稽核上線能力,使用平臺所有功能。

外掛的開發參考文件:https://actiontech.github.io/...

外掛的使用參考文件:https://actiontech.github.io/...

本文將演示如何從零開始建立一個簡單可用的稽核外掛,作為案例。

目標

首先將建立一個 Postgres 資料庫稽核外掛,並新增兩條規則,“禁止使用 SELECT *”和“建立的表欄位過多”,並在開發過程中結合SQLE對Postgres資料庫進行SQL稽核上線工單的測試演示。以下過程中的演示程式碼可從此處https://github.com/actiontech...下載。

實操

提示:SQLE和外掛為GO語言開發,如果要進行外掛開發,需要對GO有一丟丟了解即可。

1. 建立外掛專案

首先使用go mod初始化一個go專案,然後

mkdir sqle-pg-plugin
cd sqle-pg-plugin
touch main.go
go mod init sqle-pg-plugin # 初始化go mod 
export GOPROXY=goproxy.cn,goproxy.io,direct # 設定 GoProxy,解決SQLE庫下載問題,通過IDEA開發的可以在IDEA軟體上設定;
go get github.com/actiontech/[email protected] # 此版本為該文章編輯時的最新版本。

2.編寫最小化外掛程式碼

在專案main.go檔案內編寫如下程式碼,即可最快的新增一個Postgres資料庫稽核外掛,此時外掛沒有稽核規則。

package main
 
import (
   adaptor "github.com/actiontech/sqle/sqle/pkg/driver"
)
 
func main() {
   plugin := adaptor.NewAdaptor(&adaptor.PostgresDialector{})
   plugin.Serve()
}

使用‘go build‘編譯後得到二進位制檔案 sqle-pg-plugin,按前言中的外掛的使用參考文件,我們部署到SQLE服務裡。可以正常新增資料來源,如下圖所示:

SQL稽核 | 如何快速使用 SQLE 稽核各種型別的資料庫

此時正常進行SQL稽核上線工單建立並上線,如下圖所示:

SQL稽核 | 如何快速使用 SQLE 稽核各種型別的資料庫

3.給外掛新增一條規則

在剛剛程式碼的基礎上,我們在main函式內新增如下程式碼來新增一條規則“禁止使用 SELECT *”,完整程式碼如下所示。

package main
 
import (
   "context"
   "strings"
 
   "github.com/actiontech/sqle/sqle/driver"
   adaptor "github.com/actiontech/sqle/sqle/pkg/driver"
 
)
 
func main() {
   plugin := adaptor.NewAdaptor(&adaptor.PostgresDialector{})
   rule1 := &driver.Rule{
      Name:     "pg_rule_1", // 規則ID,該值會與外掛型別一起作為這條規則在 SQLE 的唯一標識
      Desc:     "禁止使用 SELECT *",      // 規則描述
      Category: "DQL規範",           // 規則分類,用於分組,相同型別的規則會在 SQLE 的頁面上展示在一起
      Level:    driver.RuleLevelError,    // 規則等級,表示該規則的嚴重程度
   }
    //
   rule1Handler := func(ctx context.Context, rule *driver.Rule, sql string) (string, error) {
      if strings.Contains(sql, "select *") {
         return rule.Desc, nil
      }
      return "", nil
   }
   plugin.AddRule(rule1, rule1Handler)
   plugin.Serve()
}

我們按之前的方式編譯外掛二進位制檔案,並部署到SQLE server內,可以看到新增了一條規則,如下圖所示:

SQL稽核 | 如何快速使用 SQLE 稽核各種型別的資料庫

此時我們提交一個工單驗證一下,可以看到觸發了我們剛新增的規則

SQL稽核 | 如何快速使用 SQLE 稽核各種型別的資料庫

4.給外掛新增一條可配置的複雜規則

基於上面的程式碼,我們再新增一條規則“建立的表欄位過多”,具備如下特性:

  • 上面新增的規則基於字串匹配進行的,準確性不高,無法匹配到不同的書寫格式,比如大小寫,換行等。因此我們在這裡會基於SQL解析器開發一條規則,測試使用的解析庫 https://github.com/pganalyze/...
  • 為了增加規則的適用性,我們準備給規則加一個動態配置給使用者提供可選項。

程式碼如下:

package main
 
import (
   "context"
   "fmt"
   "strings"
 
   "github.com/actiontech/sqle/sqle/driver"
   adaptor "github.com/actiontech/sqle/sqle/pkg/driver"
   "github.com/actiontech/sqle/sqle/pkg/params"
   parser "github.com/pganalyze/pg_query_go/v2"
)
 
func main() {
   plugin := adaptor.NewAdaptor(&adaptor.PostgresDialector{})
 
   rule1 := &driver.Rule{
      Name:     "pg_rule_1",           // 規則ID,該值會與外掛型別一起作為這條規則在 SQLE 的唯一標識
      Desc:     "避免查詢所有的列",            // 規則描述
      Category: "DQL規範",               // 規則分類,用於分組,相同型別的規則會在 SQLE 的頁面上展示在一起
      Level:    driver.RuleLevelError, // 規則等級,表示該規則的嚴重程度
   }
   rule1Handler := func(ctx context.Context, rule *driver.Rule, sql string) (string, error) {
      if strings.Contains(sql, "select *") {
         return rule.Desc, nil
      }
      return "", nil
   }
 
   // 定義第二條規則
   rule2 := &driver.Rule{
      Name:     "pg_rule_2",
      Desc:     "表欄位不建議過多",
      Level:    driver.RuleLevelWarn,
      Category: "DDL規範",
      Params: []*params.Param{ // 自定義引數列表
         &params.Param{
            Key:   "max_column_count",  // 自定義引數的ID
            Value: "50",                // 自定義引數的預設值
            Desc:  "最大欄位個數",            // 自定義引數在頁面上的描述
            Type:  params.ParamTypeInt, // 自定義引數的值型別
         },
      },
   }
 
   // 這時處理函式的引數是 interface{} 型別,需要將其斷言成 AST 語法樹。
   rule2Handler := func(ctx context.Context, rule *driver.Rule, ast interface{}) (string, error) {
      node, ok := ast.(*parser.RawStmt)
      if !ok {
         return "", nil
      }
      switch stmt := node.GetStmt().GetNode().(type) {
      case *parser.Node_CreateStmt:
         columnCounter := 0
         for _, elt := range stmt.CreateStmt.TableElts {
            switch elt.GetNode().(type) {
            case *parser.Node_ColumnDef:
               columnCounter++
            }
         }
         // 讀取 SQLE 傳遞過來的該引數配置的值
         count := rule.Params.GetParam("max_column_count").Int()
         if count > 0 && columnCounter > count {
            return fmt.Sprintf("表欄位不建議超過%d個,目前有%d個", count, columnCounter), nil
         }
      }
      return "", nil
   }
 
   plugin.AddRule(rule1, rule1Handler)
   plugin.AddRuleWithSQLParser(rule2, rule2Handler)
 
   // 需要將 SQL 解析的方法註冊到外掛中。
   plugin.Serve(adaptor.WithSQLParser(func(sql string) (ast interface{}, err error) {
      // parser.Parse 使用 PostgreSQL 的解析器,將 sql 解析成 AST 語法樹。
      result, err := parser.Parse(sql)
      if err != nil {
         return nil, fmt.Errorf("parse sql error")
      }
      if len(result.Stmts) != 1 {
         return nil, fmt.Errorf("unexpected statement count: %d", len(result.Stmts))
      }
      // 將 SQL 的語法樹返回。
      return result.Stmts[0], nil
   }))
 
   plugin.Serve()
}

開啟SQLE規則介面,可以看到該規則已經新增到SQLE了,如圖:

SQL稽核 | 如何快速使用 SQLE 稽核各種型別的資料庫

我們將規則模板內該規則的值調小點然後進行測試一下

SQL稽核 | 如何快速使用 SQLE 稽核各種型別的資料庫

首先我們提交一條超過5個欄位的建表語句,此時SQLE會觸發該規則並給出預期的提示資訊,如下圖所示:

SQL稽核 | 如何快速使用 SQLE 稽核各種型別的資料庫

然後我們提交一條不超過5個欄位的建表語句,此時SQLE不會觸發該規則,如下圖所示:

SQL稽核 | 如何快速使用 SQLE 稽核各種型別的資料庫

總結

通過上面的演示,大概介紹了SQLE資料庫稽核外掛的簡單開發測試過程。大家可根據類似步驟開發出一套符合自己公司需求的規則集,結合SQLE平臺來滿足日常使用。我們也提供了一些常見資料庫的稽核外掛,大家也可以在此基礎上進行開發,參考文件:https://actiontech.github.io/...