Skip to content

Commit 3e0cd37

Browse files
committed
feat(mysql): mysql权限检查库 #8871
1 parent a7d3e8f commit 3e0cd37

34 files changed

+163335
-35
lines changed

dbm-services/mysql/db-tools/mysql-monitor/go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
module dbm-services/mysql/db-tools/mysql-monitor
22

3-
go 1.21
3+
go 1.21.11
44

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

25+
require golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc
26+
2427
require (
2528
github.com/fsnotify/fsnotify v1.6.0 // indirect
2629
github.com/gabriel-vasile/mimetype v1.4.2 // indirect

dbm-services/mysql/db-tools/mysql-monitor/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
3838
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
3939
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
4040
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
41+
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
42+
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
4143
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
4244
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
4345
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@@ -243,6 +245,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
243245
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
244246
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
245247
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
248+
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=
249+
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
246250
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
247251
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
248252
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

dbm-services/mysql/db-tools/mysql-monitor/items-config.sql

Lines changed: 34 additions & 33 deletions
Large diffs are not rendered by default.

dbm-services/mysql/db-tools/mysql-monitor/items-config.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,4 +267,13 @@
267267
machine_type:
268268
- spider
269269
role:
270-
- spider_master
270+
- spider_master
271+
- name: priv-check
272+
enable: true
273+
schedule: 0 40 9 * * *
274+
machine_type:
275+
- spider
276+
- remote
277+
- backend
278+
- single
279+
role: []

dbm-services/mysql/db-tools/mysql-monitor/pkg/itemscollect/items_collect.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package itemscollect
1111

1212
import (
13+
"dbm-services/mysql/db-tools/mysql-monitor/pkg/itemscollect/privcheck"
1314
"dbm-services/mysql/db-tools/mysql-monitor/pkg/itemscollect/rotateproxyconnlog"
1415
"fmt"
1516
"log/slog"
@@ -95,4 +96,5 @@ func init() {
9596
_ = registerItemConstructor(timezonechange.RegisterMySQLTimezoneChange())
9697
_ = registerItemConstructor(rotateproxyconnlog.RegisterRotateProxyConnlog())
9798
_ = registerItemConstructor(spiderctlchecker.GetCtlPrimaryRegister())
99+
_ = registerItemConstructor(privcheck.Register())
98100
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package privcheck
2+
3+
import (
4+
"dbm-services/common/go-pubpkg/cmutil"
5+
"dbm-services/common/go-pubpkg/reportlog"
6+
"dbm-services/mysql/db-tools/mysql-monitor/pkg/config"
7+
"dbm-services/mysql/db-tools/mysql-monitor/pkg/internal/cst"
8+
"dbm-services/mysql/db-tools/mysql-monitor/pkg/itemscollect/privcheck/internal/checker"
9+
"dbm-services/mysql/db-tools/mysql-monitor/pkg/monitoriteminterface"
10+
"fmt"
11+
"log/slog"
12+
"os"
13+
"path/filepath"
14+
"time"
15+
16+
"github.com/jmoiron/sqlx"
17+
"github.com/pkg/errors"
18+
)
19+
20+
var name = "priv-check"
21+
22+
type Checker struct {
23+
db *sqlx.DB
24+
az *checker.Analyzer
25+
}
26+
27+
type reportType struct {
28+
BkBizId int `json:"bk_biz_id"`
29+
BkCloudId int `json:"bk_cloud_id"`
30+
ClusterDomain string `json:"cluster_domain"`
31+
MachineType string `json:"machine_type"`
32+
Ip string `json:"ip"`
33+
Port int `json:"port"`
34+
ReportTime time.Time `json:"report_time"`
35+
*checker.PrivErrorInfo
36+
}
37+
38+
func (c *Checker) Run() (msg string, err error) {
39+
privs, err := c.showAllPrivileges()
40+
if err != nil {
41+
slog.Error("show all privs", slog.String("err", err.Error()))
42+
return "", err
43+
}
44+
45+
for _, priv := range privs {
46+
c.az.AddPrivSQLString(priv)
47+
}
48+
49+
report := c.az.Check(true)
50+
51+
privCheckReportBaseDir := filepath.Join(cst.DBAReportBase, "mysql/privcheck")
52+
err = os.MkdirAll(privCheckReportBaseDir, os.ModePerm)
53+
if err != nil {
54+
slog.Error("create priv check report dir", slog.String("err", err.Error()))
55+
return "", err
56+
}
57+
58+
resultReport, err := reportlog.NewReporter(privCheckReportBaseDir, "report.log", nil)
59+
if err != nil {
60+
slog.Error("create priv check report", slog.String("err", err.Error()))
61+
return "", err
62+
}
63+
reportTs := cmutil.TimeToSecondPrecision(time.Now())
64+
65+
for _, r := range report {
66+
resultReport.Println(reportType{
67+
BkBizId: config.MonitorConfig.BkBizId,
68+
BkCloudId: *config.MonitorConfig.BkCloudID,
69+
ClusterDomain: config.MonitorConfig.ImmuteDomain,
70+
MachineType: config.MonitorConfig.MachineType,
71+
Ip: config.MonitorConfig.Ip,
72+
Port: config.MonitorConfig.Port,
73+
ReportTime: reportTs,
74+
PrivErrorInfo: r,
75+
})
76+
}
77+
78+
return "", nil
79+
}
80+
81+
func (c *Checker) showAllPrivileges() (privs []string, err error) {
82+
rows, err := c.db.Queryx(`SELECT user, host FROM mysql.user`)
83+
if err != nil {
84+
slog.Error("list user host", slog.String("err", err.Error()))
85+
return nil, errors.Wrap(err, "list user host")
86+
}
87+
defer func() {
88+
_ = rows.Close()
89+
}()
90+
91+
for rows.Next() {
92+
var user, host string
93+
err = rows.Scan(&user, &host)
94+
if err != nil {
95+
slog.Error("scan user host", slog.String("err", err.Error()))
96+
return nil, errors.Wrap(err, "scan user host")
97+
}
98+
99+
res, err := c.showPrivileges(user, host)
100+
if err != nil {
101+
slog.Error(
102+
"show one user grants",
103+
slog.String("user", user),
104+
slog.String("host", host),
105+
slog.String("err", err.Error()),
106+
)
107+
}
108+
109+
privs = append(privs, res...)
110+
}
111+
if err := rows.Err(); err != nil {
112+
slog.Error("iterate user host", slog.String("err", err.Error()))
113+
return nil, errors.Wrap(err, "iterate user host")
114+
}
115+
116+
return privs, nil
117+
}
118+
119+
func (c *Checker) showPrivileges(user, host string) (privs []string, err error) {
120+
var version float32
121+
err = c.db.QueryRowx(`SELECT SUBSTRING_INDEX(@@version, ".", 2)`).Scan(&version)
122+
if err != nil {
123+
slog.Error("get version", slog.String("err", err.Error()))
124+
return nil, errors.Wrap(err, "get version")
125+
}
126+
127+
if version > 5.5 {
128+
var createUserRes []string
129+
err = c.db.Select(
130+
&createUserRes,
131+
fmt.Sprintf(`SHOW CREATE USER '%s'@'%s'`, user, host),
132+
)
133+
if err != nil {
134+
slog.Error("get create user", slog.String("err", err.Error()))
135+
return nil, errors.Wrap(err, "get create user")
136+
}
137+
138+
privs = append(privs, createUserRes...)
139+
}
140+
141+
var grantsRes []string
142+
err = c.db.Select(
143+
&grantsRes,
144+
fmt.Sprintf(`SHOW GRANTS FOR '%s'@'%s'`, user, host),
145+
)
146+
if err != nil {
147+
slog.Error("get grants", slog.String("err", err.Error()))
148+
return nil, errors.Wrap(err, "get grants")
149+
}
150+
151+
privs = append(privs, grantsRes...)
152+
return privs, nil
153+
}
154+
155+
func (c *Checker) Name() string {
156+
return name
157+
}
158+
159+
func NewChecker(cc *monitoriteminterface.ConnectionCollect) monitoriteminterface.MonitorItemInterface {
160+
return &Checker{
161+
db: cc.MySqlDB,
162+
az: checker.NewAnalyzer(),
163+
}
164+
}
165+
166+
func Register() (string, monitoriteminterface.MonitorItemConstructorFuncType) {
167+
return name, NewChecker
168+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package checker
2+
3+
import (
4+
"dbm-services/mysql/db-tools/mysql-monitor/pkg/itemscollect/privcheck/internal/listener"
5+
"dbm-services/mysql/db-tools/mysql-monitor/pkg/itemscollect/privcheck/internal/parsing"
6+
7+
"github.com/antlr4-go/antlr/v4"
8+
)
9+
10+
func (c *Analyzer) AddPrivSQLString(sql string) {
11+
in := antlr.NewInputStream(sql)
12+
lexer := parsing.NewMySqlLexer(in)
13+
stream := antlr.NewCommonTokenStream(lexer, 0)
14+
p := parsing.NewMySqlParser(stream)
15+
tree := p.Root()
16+
l := listener.NewPrivListener(stream)
17+
antlr.ParseTreeWalkerDefault.Walk(l, tree)
18+
19+
c.addUserSummary(l)
20+
}
21+
22+
func (c *Analyzer) addUserSummary(l *listener.PrivListener) {
23+
if !l.IsGrantPriv && !l.IsCreateUser {
24+
return
25+
}
26+
27+
if _, ok := c.userPrivSummaries[l.Username]; !ok {
28+
c.userPrivSummaries[l.Username] = &userPrivSummary{
29+
Username: l.Username,
30+
}
31+
}
32+
33+
if l.IsCreateUser {
34+
return
35+
}
36+
37+
c.addHostSummary(l, c.userPrivSummaries[l.Username])
38+
}
39+
40+
func (c *Analyzer) addHostSummary(l *listener.PrivListener, userSummary *userPrivSummary) {
41+
if userSummary.HostPrivSummaries == nil {
42+
userSummary.HostPrivSummaries = make(map[string]*hostPrivSummary)
43+
}
44+
45+
if _, ok := userSummary.HostPrivSummaries[l.Host]; !ok {
46+
userSummary.HostPrivSummaries[l.Host] = &hostPrivSummary{
47+
Host: l.Host,
48+
Password: l.Password,
49+
}
50+
}
51+
52+
c.addDBSummary(l, userSummary.HostPrivSummaries[l.Host])
53+
}
54+
55+
func (c *Analyzer) addDBSummary(l *listener.PrivListener, hostSummary *hostPrivSummary) {
56+
if hostSummary.DBPrivSummaries == nil {
57+
hostSummary.DBPrivSummaries = make(map[string]*dbPrivSummary)
58+
}
59+
60+
if _, ok := hostSummary.DBPrivSummaries[l.DBName]; !ok {
61+
hostSummary.DBPrivSummaries[l.DBName] = &dbPrivSummary{
62+
DBName: l.DBName,
63+
WithGrantOption: l.WithGrantOption,
64+
}
65+
}
66+
67+
c.addTableSummary(l, hostSummary.DBPrivSummaries[l.DBName])
68+
}
69+
70+
func (c *Analyzer) addTableSummary(l *listener.PrivListener, dbSummary *dbPrivSummary) {
71+
if dbSummary.TablePrivSummaries == nil {
72+
dbSummary.TablePrivSummaries = make(map[string]*tablePrivSummary)
73+
}
74+
75+
if _, ok := dbSummary.TablePrivSummaries[l.TableName]; !ok {
76+
dbSummary.TablePrivSummaries[l.TableName] = &tablePrivSummary{
77+
TableName: l.TableName,
78+
Privileges: l.Privileges,
79+
}
80+
}
81+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package checker
2+
3+
const (
4+
PrivErrorHostConflict = "host_conflict"
5+
PrivErrorDBConflict = "db_conflict"
6+
PrivErrorPasswordNotMatch = "password_not_match"
7+
PrivErrorWithGrantOptionNotMatch = "with_grant_option_not_match"
8+
PrivErrorGrantToDifferentDB = "grant_to_different_db"
9+
PrivErrorGrantToDifferentTable = "grant_to_different_table"
10+
PrivErrorPrivilegesNotMatch = "privileges_not_match"
11+
)
12+
13+
type PrivErrorInfo struct {
14+
ErrorType string `json:"error_type"`
15+
Object1 string `json:"object1"`
16+
Object2 string `json:"object2"`
17+
Msg string `json:"msg"`
18+
}
19+
20+
func (c *Analyzer) Check(deep bool) (res []*PrivErrorInfo) {
21+
c.deep = deep
22+
for userName, userSummary := range c.userPrivSummaries {
23+
if !IsSystemUser(userName) {
24+
res = append(res, c.checkUser(userSummary)...)
25+
}
26+
}
27+
28+
return res
29+
}

0 commit comments

Comments
 (0)