Skip to content

Commit

Permalink
feat(mysql): mysql权限检查库 #8871
Browse files Browse the repository at this point in the history
  • Loading branch information
xfwduke committed Jan 2, 2025
1 parent a7d3e8f commit bb6a84b
Show file tree
Hide file tree
Showing 32 changed files with 163,334 additions and 34 deletions.
3 changes: 3 additions & 0 deletions dbm-services/mysql/db-tools/mysql-monitor/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module dbm-services/mysql/db-tools/mysql-monitor
go 1.21

require (
github.com/antlr4-go/antlr/v4 v4.13.0
github.com/dlclark/regexp2 v1.10.0
github.com/go-playground/validator/v10 v10.15.4
github.com/go-sql-driver/mysql v1.6.0
Expand All @@ -21,6 +22,8 @@ require (
gopkg.in/yaml.v2 v2.4.0
)

require golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc

require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
Expand Down
4 changes: 4 additions & 0 deletions dbm-services/mysql/db-tools/mysql-monitor/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
Expand Down Expand Up @@ -243,6 +245,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
Expand Down
67 changes: 34 additions & 33 deletions dbm-services/mysql/db-tools/mysql-monitor/items-config.sql

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion dbm-services/mysql/db-tools/mysql-monitor/items-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,13 @@
machine_type:
- spider
role:
- spider_master
- spider_master
- name: priv-check
enable: true
schedule: 0 40 9 * * *
machine_type:
- spider
- remote
- backend
- single
role: []
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
package itemscollect

import (
"dbm-services/mysql/db-tools/mysql-monitor/pkg/itemscollect/privcheck"
"dbm-services/mysql/db-tools/mysql-monitor/pkg/itemscollect/rotateproxyconnlog"
"fmt"
"log/slog"
Expand Down Expand Up @@ -95,4 +96,5 @@ func init() {
_ = registerItemConstructor(timezonechange.RegisterMySQLTimezoneChange())
_ = registerItemConstructor(rotateproxyconnlog.RegisterRotateProxyConnlog())
_ = registerItemConstructor(spiderctlchecker.GetCtlPrimaryRegister())
_ = registerItemConstructor(privcheck.Register())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package privcheck

import (
"dbm-services/common/go-pubpkg/cmutil"
"dbm-services/common/go-pubpkg/reportlog"
"dbm-services/mysql/db-tools/mysql-monitor/pkg/config"
"dbm-services/mysql/db-tools/mysql-monitor/pkg/internal/cst"
"dbm-services/mysql/db-tools/mysql-monitor/pkg/itemscollect/privcheck/internal/checker"
"dbm-services/mysql/db-tools/mysql-monitor/pkg/monitoriteminterface"
"fmt"
"log/slog"
"os"
"path/filepath"
"time"

"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)

var name = "priv-check"

type Checker struct {
db *sqlx.DB
az *checker.Analyzer
}

type reportType struct {
BkBizId int `json:"bk_biz_id"`
BkCloudId int `json:"bk_cloud_id"`
ClusterDomain string `json:"cluster_domain"`
MachineType string `json:"machine_type"`
Ip string `json:"ip"`
Port int `json:"port"`
ReportTime time.Time `json:"report_time"`
*checker.PrivErrorInfo
}

func (c *Checker) Run() (msg string, err error) {
privs, err := c.showAllPrivileges()
if err != nil {
slog.Error("show all privs", slog.String("err", err.Error()))
return "", err
}

for _, priv := range privs {
c.az.AddPrivSQLString(priv)
}

report := c.az.Check(true)

privCheckReportBaseDir := filepath.Join(cst.DBAReportBase, "mysql/privcheck")
err = os.MkdirAll(privCheckReportBaseDir, os.ModePerm)
if err != nil {
slog.Error("create priv check report dir", slog.String("err", err.Error()))
return "", err
}

resultReport, err := reportlog.NewReporter(privCheckReportBaseDir, "report.log", nil)
if err != nil {
slog.Error("create priv check report", slog.String("err", err.Error()))
return "", err
}
reportTs := cmutil.TimeToSecondPrecision(time.Now())

for _, r := range report {
resultReport.Println(reportType{
BkBizId: config.MonitorConfig.BkBizId,
BkCloudId: *config.MonitorConfig.BkCloudID,
ClusterDomain: config.MonitorConfig.ImmuteDomain,
MachineType: config.MonitorConfig.MachineType,
Ip: config.MonitorConfig.Ip,
Port: config.MonitorConfig.Port,
ReportTime: reportTs,
PrivErrorInfo: r,
})
}

return "", nil
}

func (c *Checker) showAllPrivileges() (privs []string, err error) {
rows, err := c.db.Queryx(`SELECT user, host FROM mysql.user`)
if err != nil {
slog.Error("list user host", slog.String("err", err.Error()))
return nil, errors.Wrap(err, "list user host")
}
defer func() {
_ = rows.Close()
}()

for rows.Next() {
var user, host string
err = rows.Scan(&user, &host)
if err != nil {
slog.Error("scan user host", slog.String("err", err.Error()))
return nil, errors.Wrap(err, "scan user host")
}

res, err := c.showPrivileges(user, host)
if err != nil {
slog.Error(
"show one user grants",
slog.String("user", user),
slog.String("host", host),
slog.String("err", err.Error()),
)
}

privs = append(privs, res...)
}
if err := rows.Err(); err != nil {
slog.Error("iterate user host", slog.String("err", err.Error()))
return nil, errors.Wrap(err, "iterate user host")
}

return privs, nil
}

func (c *Checker) showPrivileges(user, host string) (privs []string, err error) {
var version float32
err = c.db.QueryRowx(`SELECT SUBSTRING_INDEX(@@version, ".", 2)`).Scan(&version)
if err != nil {
slog.Error("get version", slog.String("err", err.Error()))
return nil, errors.Wrap(err, "get version")
}

if version > 5.5 {
var createUserRes []string
err = c.db.Select(
&createUserRes,
fmt.Sprintf(`SHOW CREATE USER '%s'@'%s'`, user, host),
)
if err != nil {
slog.Error("get create user", slog.String("err", err.Error()))
return nil, errors.Wrap(err, "get create user")
}

privs = append(privs, createUserRes...)
}

var grantsRes []string
err = c.db.Select(
&grantsRes,
fmt.Sprintf(`SHOW GRANTS FOR '%s'@'%s'`, user, host),
)
if err != nil {
slog.Error("get grants", slog.String("err", err.Error()))
return nil, errors.Wrap(err, "get grants")
}

privs = append(privs, grantsRes...)
return privs, nil
}

func (c *Checker) Name() string {
return name
}

func NewChecker(cc *monitoriteminterface.ConnectionCollect) monitoriteminterface.MonitorItemInterface {
return &Checker{
db: cc.MySqlDB,
az: checker.NewAnalyzer(),
}
}

func Register() (string, monitoriteminterface.MonitorItemConstructorFuncType) {
return name, NewChecker
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package checker

import (
"dbm-services/mysql/db-tools/mysql-monitor/pkg/itemscollect/privcheck/internal/listener"
"dbm-services/mysql/db-tools/mysql-monitor/pkg/itemscollect/privcheck/internal/parsing"

"github.com/antlr4-go/antlr/v4"
)

func (c *Analyzer) AddPrivSQLString(sql string) {
in := antlr.NewInputStream(sql)
lexer := parsing.NewMySqlLexer(in)
stream := antlr.NewCommonTokenStream(lexer, 0)
p := parsing.NewMySqlParser(stream)
tree := p.Root()
l := listener.NewPrivListener(stream)
antlr.ParseTreeWalkerDefault.Walk(l, tree)

c.addUserSummary(l)
}

func (c *Analyzer) addUserSummary(l *listener.PrivListener) {
if !l.IsGrantPriv && !l.IsCreateUser {
return
}

if _, ok := c.userPrivSummaries[l.Username]; !ok {
c.userPrivSummaries[l.Username] = &userPrivSummary{
Username: l.Username,
}
}

if l.IsCreateUser {
return
}

c.addHostSummary(l, c.userPrivSummaries[l.Username])
}

func (c *Analyzer) addHostSummary(l *listener.PrivListener, userSummary *userPrivSummary) {
if userSummary.HostPrivSummaries == nil {
userSummary.HostPrivSummaries = make(map[string]*hostPrivSummary)
}

if _, ok := userSummary.HostPrivSummaries[l.Host]; !ok {
userSummary.HostPrivSummaries[l.Host] = &hostPrivSummary{
Host: l.Host,
Password: l.Password,
}
}

c.addDBSummary(l, userSummary.HostPrivSummaries[l.Host])
}

func (c *Analyzer) addDBSummary(l *listener.PrivListener, hostSummary *hostPrivSummary) {
if hostSummary.DBPrivSummaries == nil {
hostSummary.DBPrivSummaries = make(map[string]*dbPrivSummary)
}

if _, ok := hostSummary.DBPrivSummaries[l.DBName]; !ok {
hostSummary.DBPrivSummaries[l.DBName] = &dbPrivSummary{
DBName: l.DBName,
WithGrantOption: l.WithGrantOption,
}
}

c.addTableSummary(l, hostSummary.DBPrivSummaries[l.DBName])
}

func (c *Analyzer) addTableSummary(l *listener.PrivListener, dbSummary *dbPrivSummary) {
if dbSummary.TablePrivSummaries == nil {
dbSummary.TablePrivSummaries = make(map[string]*tablePrivSummary)
}

if _, ok := dbSummary.TablePrivSummaries[l.TableName]; !ok {
dbSummary.TablePrivSummaries[l.TableName] = &tablePrivSummary{
TableName: l.TableName,
Privileges: l.Privileges,
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package checker

const (
PrivErrorHostConflict = "host_conflict"
PrivErrorDBConflict = "db_conflict"
PrivErrorPasswordNotMatch = "password_not_match"
PrivErrorWithGrantOptionNotMatch = "with_grant_option_not_match"
PrivErrorGrantToDifferentDB = "grant_to_different_db"
PrivErrorGrantToDifferentTable = "grant_to_different_table"
PrivErrorPrivilegesNotMatch = "privileges_not_match"
)

type PrivErrorInfo struct {
ErrorType string `json:"error_type"`
Object1 string `json:"object1"`
Object2 string `json:"object2"`
Msg string `json:"msg"`
}

func (c *Analyzer) Check(deep bool) (res []*PrivErrorInfo) {
c.deep = deep
for userName, userSummary := range c.userPrivSummaries {
if !IsSystemUser(userName) {
res = append(res, c.checkUser(userSummary)...)
}
}

return res
}
Loading

0 comments on commit bb6a84b

Please sign in to comment.