github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/checker/privilege_test.go (about)

     1  // Copyright 2021 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package checker
    15  
    16  import (
    17  	"testing"
    18  
    19  	tc "github.com/pingcap/check"
    20  	"github.com/pingcap/tidb/pkg/parser/mysql"
    21  	"github.com/pingcap/tidb/pkg/util/filter"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  func TestClient(t *testing.T) {
    26  	tc.TestingT(t)
    27  }
    28  
    29  var _ = tc.Suite(&testCheckSuite{})
    30  
    31  type testCheckSuite struct{}
    32  
    33  func TestVerifyDumpPrivileges(t *testing.T) {
    34  	cases := []struct {
    35  		grants            []string
    36  		checkTables       []filter.Table
    37  		dumpWholeInstance bool
    38  		dumpState         State
    39  		errStr            string
    40  	}{
    41  		{
    42  			grants:    nil, // non grants
    43  			dumpState: StateFailure,
    44  			errStr:    "there is no such grant defined for current user on host '%'",
    45  		},
    46  		{
    47  			grants:    []string{"invalid SQL statement"},
    48  			dumpState: StateFailure,
    49  			errStr:    "line 1 column 7 near \"invalid SQL statement\" ",
    50  		},
    51  		{
    52  			grants:    []string{"CREATE DATABASE db1"}, // non GRANT statement
    53  			dumpState: StateFailure,
    54  			errStr:    "CREATE DATABASE db1 is not grant statement",
    55  		},
    56  		{
    57  			grants:    []string{"GRANT RELOAD ON *.* TO 'user'@'%'"}, // lack SELECT privilege
    58  			dumpState: StateFailure,
    59  			checkTables: []filter.Table{
    60  				{Schema: "db1", Name: "tb1"},
    61  			},
    62  			errStr: "lack of Select privilege: {`db1`.`tb1`}; ",
    63  		},
    64  		{
    65  			grants:    []string{"GRANT RELOAD ON *.* TO 'user'@'%'"}, // lack SELECT privilege but no do-tables
    66  			dumpState: StateSuccess,
    67  		},
    68  		{
    69  			grants: []string{ // lack optional privilege
    70  				"GRANT RELOAD ON *.* TO 'user'@'%'",
    71  				"GRANT EXECUTE ON FUNCTION db1.anomaly_score TO 'user1'@'domain-or-ip-address1'",
    72  			},
    73  			dumpState: StateFailure,
    74  			checkTables: []filter.Table{
    75  				{Schema: "db1", Name: "anomaly_score"},
    76  			},
    77  			errStr: "lack of Select privilege: {`db1`.`anomaly_score`}; ",
    78  		},
    79  		{
    80  			grants: []string{ // have privileges
    81  				"GRANT RELOAD, SELECT ON *.* TO 'user'@'%'",
    82  			},
    83  			dumpState: StateSuccess,
    84  		},
    85  		{
    86  			grants: []string{ // have privileges
    87  				"GRANT ALL PRIVILEGES ON *.* TO 'user'@'%'",
    88  			},
    89  			dumpState: StateSuccess,
    90  		},
    91  		{
    92  			grants: []string{ // lower case
    93  				"GRANT all privileges ON *.* TO 'user'@'%'",
    94  			},
    95  			dumpState: StateSuccess,
    96  		},
    97  		{
    98  			grants: []string{ // IDENTIFIED BY PASSWORD
    99  				"GRANT ALL PRIVILEGES ON *.* TO 'user'@'%' IDENTIFIED BY PASSWORD 'secret'",
   100  			},
   101  			dumpState: StateSuccess,
   102  		},
   103  		{
   104  			grants: []string{ // IDENTIFIED BY PASSWORD
   105  				"GRANT ALL PRIVILEGES ON *.* TO 'user'@'%' IDENTIFIED BY PASSWORD 'password' WITH GRANT OPTION",
   106  			},
   107  			dumpState: StateSuccess,
   108  		},
   109  		{
   110  			grants: []string{ // Aurora have `LOAD FROM S3, SELECT INTO S3, INVOKE LAMBDA`
   111  				"GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, LOAD FROM S3, SELECT INTO S3, INVOKE LAMBDA, INVOKE SAGEMAKER, INVOKE COMPREHEND ON *.* TO 'root'@'%' WITH GRANT OPTION",
   112  			},
   113  			dumpState: StateSuccess,
   114  		},
   115  		{
   116  			grants: []string{ // Aurora have `LOAD FROM S3, SELECT INTO S3, INVOKE LAMBDA`
   117  				"GRANT INSERT, UPDATE, DELETE, CREATE, DROP, PROCESS, REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, LOAD FROM S3, SELECT INTO S3, INVOKE LAMBDA, INVOKE SAGEMAKER, INVOKE COMPREHEND ON *.* TO 'root'@'%' WITH GRANT OPTION",
   118  			},
   119  			checkTables: []filter.Table{
   120  				{Schema: "db1", Name: "tb1"},
   121  			},
   122  			dumpState: StateFailure,
   123  			errStr:    "lack of Select privilege: {`db1`.`tb1`}; lack of RELOAD global (*.*) privilege; ",
   124  		},
   125  		{
   126  			grants: []string{ // test `LOAD FROM S3, SELECT INTO S3` not at end
   127  				"GRANT INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, LOAD FROM S3, SELECT INTO S3, SELECT ON *.* TO 'root'@'%' WITH GRANT OPTION",
   128  			},
   129  			checkTables: []filter.Table{
   130  				{Schema: "db1", Name: "tb1"},
   131  			},
   132  			dumpState: StateSuccess,
   133  		},
   134  		{
   135  			grants: []string{ // ... and `LOAD FROM S3` at beginning, as well as not adjacent with `SELECT INTO S3`
   136  				"GRANT LOAD FROM S3, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, SELECT INTO S3, SELECT ON *.* TO 'root'@'%' WITH GRANT OPTION",
   137  			},
   138  			dumpState: StateSuccess,
   139  		},
   140  		{
   141  			grants: []string{ // lack db/table level privilege
   142  				"GRANT ALL PRIVILEGES ON `medz`.* TO `zhangsan`@`10.8.1.9` WITH GRANT OPTION",
   143  			},
   144  			dumpState: StateFailure,
   145  			checkTables: []filter.Table{
   146  				{Schema: "medz", Name: "medz"},
   147  			},
   148  			errStr: "lack of RELOAD global (*.*) privilege; ",
   149  		},
   150  		{
   151  			grants: []string{ // privilege on db/table level is not enough to execute SHOW MASTER STATUS
   152  				"GRANT ALL PRIVILEGES ON `medz`.* TO `zhangsan`@`10.8.1.9` WITH GRANT OPTION",
   153  			},
   154  			dumpState: StateFailure,
   155  			checkTables: []filter.Table{
   156  				{Schema: "medz", Name: "medz"},
   157  			},
   158  			errStr: "lack of RELOAD global (*.*) privilege; ",
   159  		},
   160  		{
   161  			grants: []string{ // privilege on column level is not enough to execute SHOW CREATE TABLE
   162  				"GRANT RELOAD ON *.* TO 'user'@'%'",
   163  				"GRANT SELECT (c) ON `lance`.`t` TO 'user'@'%'",
   164  			},
   165  			dumpState: StateFailure,
   166  			checkTables: []filter.Table{
   167  				{Schema: "lance", Name: "t"},
   168  			},
   169  			errStr: "lack of Select privilege: {`lance`.`t`}; ",
   170  		},
   171  		{
   172  			grants: []string{
   173  				"GRANT RELOAD ON *.* TO `u1`@`localhost`",
   174  				"GRANT SELECT ON `db1`.* TO `u1`@`localhost`",
   175  				"GRANT `r1`@`%`,`r2`@`%` TO `u1`@`localhost`",
   176  			},
   177  			dumpState: StateSuccess,
   178  			checkTables: []filter.Table{
   179  				{Schema: "db1", Name: "t"},
   180  			},
   181  		},
   182  		{
   183  			grants: []string{
   184  				"GRANT RELOAD ON *.* TO `u1`@`localhost`",
   185  				"GRANT SELECT ON `db1`.* TO `u1`@`localhost`",
   186  				"GRANT `r1`@`%`,`r2`@`%` TO `u1`@`localhost`",
   187  			},
   188  			dumpState:         StateFailure,
   189  			dumpWholeInstance: true,
   190  			errStr:            "lack of Select global (*.*) privilege; ",
   191  		},
   192  		{
   193  			grants: []string{
   194  				"GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, CREATE TABLESPACE, CREATE ROLE, DROP ROLE ON *.* TO `root`@`localhost` WITH GRANT OPTION",
   195  				"GRANT APPLICATION_PASSWORD_ADMIN,AUDIT_ADMIN,BACKUP_ADMIN,BINLOG_ADMIN,BINLOG_ENCRYPTION_ADMIN,CLONE_ADMIN,CONNECTION_ADMIN,ENCRYPTION_KEY_ADMIN,GROUP_REPLICATION_ADMIN,INNODB_REDO_LOG_ARCHIVE,PERSIST_RO_VARIABLES_ADMIN,REPLICATION_APPLIER,REPLICATION_SLAVE_ADMIN,RESOURCE_GROUP_ADMIN,RESOURCE_GROUP_USER,ROLE_ADMIN,SERVICE_CONNECTION_ADMIN,SESSION_VARIABLES_ADMIN,SET_USER_ID,SYSTEM_USER,SYSTEM_VARIABLES_ADMIN,TABLE_ENCRYPTION_ADMIN,XA_RECOVER_ADMIN ON *.* TO `root`@`localhost` WITH GRANT OPTION",
   196  				"GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION",
   197  			},
   198  			dumpState: StateSuccess,
   199  		},
   200  	}
   201  
   202  	for _, cs := range cases {
   203  		result := &Result{
   204  			State: StateFailure,
   205  		}
   206  		dumpRequiredPrivs := map[mysql.PrivilegeType]priv{
   207  			mysql.SelectPriv: {
   208  				needGlobal: false,
   209  				dbs:        genTableLevelPrivs(cs.checkTables),
   210  			},
   211  			mysql.ReloadPriv: {needGlobal: true},
   212  		}
   213  		if cs.dumpWholeInstance {
   214  			dumpRequiredPrivs[mysql.SelectPriv] = priv{needGlobal: true}
   215  		}
   216  		err := verifyPrivilegesWithResult(result, cs.grants, dumpRequiredPrivs)
   217  		if cs.dumpState == StateSuccess {
   218  			require.Nil(t, err, "grants: %v", cs.grants)
   219  		} else {
   220  			require.NotNil(t, err, "grants: %v", cs.grants)
   221  			require.Equal(t, cs.errStr, err.ShortErr, "grants: %v", cs.grants)
   222  		}
   223  	}
   224  }
   225  
   226  func TestVerifyReplicationPrivileges(t *testing.T) {
   227  	cases := []struct {
   228  		grants           []string
   229  		checkTables      []*filter.Table
   230  		replicationState State
   231  		errStr           string
   232  	}{
   233  		{
   234  			grants:           nil, // non grants
   235  			replicationState: StateFailure,
   236  			errStr:           "there is no such grant defined for current user on host '%'",
   237  		},
   238  		{
   239  			grants:           []string{"invalid SQL statement"},
   240  			replicationState: StateFailure,
   241  			errStr:           "line 1 column 7 near \"invalid SQL statement\" ",
   242  		},
   243  		{
   244  			grants:           []string{"CREATE DATABASE db1"}, // non GRANT statement
   245  			replicationState: StateFailure,
   246  			errStr:           "CREATE DATABASE db1 is not grant statement",
   247  		},
   248  		{
   249  			grants:           []string{"GRANT SELECT ON *.* TO 'user'@'%'"}, // lack necessary privilege
   250  			replicationState: StateFailure,
   251  			errStr:           "lack of REPLICATION CLIENT global (*.*) privilege; lack of REPLICATION SLAVE global (*.*) privilege; ",
   252  		},
   253  		{
   254  			grants:           []string{"GRANT REPLICATION SLAVE ON *.* TO 'user'@'%'"}, // lack REPLICATION CLIENT privilege
   255  			replicationState: StateFailure,
   256  			errStr:           "lack of REPLICATION CLIENT global (*.*) privilege; ",
   257  		},
   258  		{
   259  			grants:           []string{"GRANT REPLICATION CLIENT ON *.* TO 'user'@'%'"}, // lack REPLICATION SLAVE privilege
   260  			replicationState: StateFailure,
   261  			errStr:           "lack of REPLICATION SLAVE global (*.*) privilege; ",
   262  		},
   263  		{
   264  			grants: []string{ // have privileges
   265  				"GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'user'@'%'",
   266  			},
   267  			replicationState: StateSuccess,
   268  		},
   269  		{
   270  			grants: []string{ // have privileges
   271  				"GRANT ALL PRIVILEGES ON *.* TO 'user'@'%'",
   272  			},
   273  			replicationState: StateSuccess,
   274  		},
   275  		{
   276  			grants: []string{ // lower case
   277  				"GRANT all privileges ON *.* TO 'user'@'%'",
   278  			},
   279  			replicationState: StateSuccess,
   280  		},
   281  		{
   282  			grants: []string{ // IDENTIFIED BY PASSWORD
   283  				"GRANT ALL PRIVILEGES ON *.* TO 'user'@'%' IDENTIFIED BY PASSWORD 'secret'",
   284  			},
   285  			replicationState: StateSuccess,
   286  		},
   287  		{
   288  			grants: []string{ // IDENTIFIED BY PASSWORD
   289  				"GRANT ALL PRIVILEGES ON *.* TO 'user'@'%' IDENTIFIED BY PASSWORD 'password' WITH GRANT OPTION",
   290  			},
   291  			replicationState: StateSuccess,
   292  		},
   293  		{
   294  			grants: []string{ // Aurora have `LOAD FROM S3, SELECT INTO S3, INVOKE LAMBDA`
   295  				"GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, LOAD FROM S3, SELECT INTO S3, INVOKE LAMBDA, INVOKE SAGEMAKER, INVOKE COMPREHEND ON *.* TO 'root'@'%' WITH GRANT OPTION",
   296  			},
   297  			replicationState: StateSuccess,
   298  		},
   299  		{
   300  			grants: []string{ // Aurora have `LOAD FROM S3, SELECT INTO S3, INVOKE LAMBDA`
   301  				"GRANT INSERT, UPDATE, DELETE, CREATE, DROP, PROCESS, REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, LOAD FROM S3, SELECT INTO S3, INVOKE LAMBDA, INVOKE SAGEMAKER, INVOKE COMPREHEND ON *.* TO 'root'@'%' WITH GRANT OPTION",
   302  			},
   303  			replicationState: StateFailure,
   304  			errStr:           "lack of REPLICATION CLIENT global (*.*) privilege; lack of REPLICATION SLAVE global (*.*) privilege; ",
   305  		},
   306  		{
   307  			grants: []string{ // test `LOAD FROM S3, SELECT INTO S3` not at end
   308  				"GRANT INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, LOAD FROM S3, SELECT INTO S3, SELECT ON *.* TO 'root'@'%' WITH GRANT OPTION",
   309  			},
   310  			replicationState: StateSuccess,
   311  		},
   312  		{
   313  			grants: []string{ // ... and `LOAD FROM S3` at beginning, as well as not adjacent with `SELECT INTO S3`
   314  				"GRANT LOAD FROM S3, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, SELECT INTO S3, SELECT ON *.* TO 'root'@'%' WITH GRANT OPTION",
   315  			},
   316  			replicationState: StateSuccess,
   317  		},
   318  	}
   319  
   320  	for _, cs := range cases {
   321  		result := &Result{
   322  			State: StateFailure,
   323  		}
   324  		replRequiredPrivs := map[mysql.PrivilegeType]priv{
   325  			mysql.ReplicationSlavePriv:  {needGlobal: true},
   326  			mysql.ReplicationClientPriv: {needGlobal: true},
   327  		}
   328  		err := verifyPrivilegesWithResult(result, cs.grants, replRequiredPrivs)
   329  		if cs.replicationState == StateSuccess {
   330  			require.Nil(t, err, "grants: %v", cs.grants)
   331  		} else {
   332  			require.NotNil(t, err, "grants: %v", cs.grants)
   333  			require.Equal(t, cs.errStr, err.ShortErr, "grants: %v", cs.grants)
   334  		}
   335  	}
   336  }
   337  
   338  func TestVerifyPrivilegesWildcard(t *testing.T) {
   339  	cases := []struct {
   340  		grants           []string
   341  		checkTables      []filter.Table
   342  		replicationState State
   343  		errStr           string
   344  	}{
   345  		{
   346  			grants: []string{
   347  				"GRANT SELECT ON `demo\\_foobar`.* TO `dmuser`@`%`",
   348  			},
   349  			checkTables: []filter.Table{
   350  				{Schema: "demo_foobar", Name: "t1"},
   351  			},
   352  			replicationState: StateSuccess,
   353  		},
   354  		{
   355  			grants: []string{
   356  				"GRANT SELECT ON `demo\\_foobar`.* TO `dmuser`@`%`",
   357  			},
   358  			checkTables: []filter.Table{
   359  				{Schema: "demo2foobar", Name: "t1"},
   360  			},
   361  			replicationState: StateFailure,
   362  			errStr:           "lack of Select privilege: {`demo2foobar`.`t1`}; ",
   363  		},
   364  		{
   365  			grants: []string{
   366  				"GRANT SELECT ON `demo_`.* TO `dmuser`@`%`",
   367  			},
   368  			checkTables: []filter.Table{
   369  				{Schema: "demo1", Name: "t1"},
   370  				{Schema: "demo2", Name: "t1"},
   371  			},
   372  			replicationState: StateSuccess,
   373  		},
   374  		{
   375  			grants: []string{
   376  				"GRANT SELECT ON `demo%`.* TO `dmuser`@`%`",
   377  			},
   378  			checkTables: []filter.Table{
   379  				{Schema: "demo_some", Name: "t1"},
   380  				{Schema: "block_db", Name: "t1"},
   381  			},
   382  			replicationState: StateFailure,
   383  			errStr:           "lack of Select privilege: {`block_db`.`t1`}; ",
   384  		},
   385  		{
   386  			grants: []string{
   387  				"GRANT SELECT ON `demo_db`.`t1` TO `dmuser`@`%`",
   388  			},
   389  			checkTables: []filter.Table{
   390  				{Schema: "demo_db", Name: "t1"},
   391  				{Schema: "demo2db", Name: "t1"},
   392  			},
   393  			replicationState: StateFailure,
   394  			errStr:           "lack of Select privilege: {`demo2db`.`t1`}; ",
   395  		},
   396  	}
   397  
   398  	for i, cs := range cases {
   399  		t.Logf("case %d", i)
   400  		result := &Result{
   401  			State: StateFailure,
   402  		}
   403  		requiredPrivs := map[mysql.PrivilegeType]priv{
   404  			mysql.SelectPriv: {
   405  				dbs: genTableLevelPrivs(cs.checkTables),
   406  			},
   407  		}
   408  		err := verifyPrivilegesWithResult(result, cs.grants, requiredPrivs)
   409  		if cs.replicationState == StateSuccess {
   410  			require.Nil(t, err, "grants: %v", cs.grants)
   411  		} else {
   412  			require.NotNil(t, err, "grants: %v", cs.grants)
   413  			require.Equal(t, cs.errStr, err.ShortErr, "grants: %v", cs.grants)
   414  		}
   415  	}
   416  }
   417  
   418  func TestVerifyTargetPrivilege(t *testing.T) {
   419  	cases := []struct {
   420  		grants     []string
   421  		checkState State
   422  		errStr     string
   423  	}{
   424  		{
   425  			grants:     nil, // non grants
   426  			checkState: StateWarning,
   427  			errStr:     "there is no such grant defined for current user on host '%'",
   428  		},
   429  		{
   430  			grants:     []string{"invalid SQL statement"},
   431  			checkState: StateWarning,
   432  			errStr:     "line 1 column 7 near \"invalid SQL statement\" ",
   433  		},
   434  		{
   435  			grants:     []string{"CREATE DATABASE db1"}, // non GRANT statement
   436  			checkState: StateWarning,
   437  			errStr:     "CREATE DATABASE db1 is not grant statement",
   438  		},
   439  		{
   440  			grants: []string{
   441  				"GRANT ALL PRIVILEGES ON *.* TO 'user'@'%'",
   442  			},
   443  			checkState: StateSuccess,
   444  		},
   445  		{
   446  			grants: []string{
   447  				"GRANT SELECT, CREATE, INSERT, UPDATE, DELETE, ALTER, DROP ON *.* TO 'root'@'%'",
   448  			},
   449  			checkState: StateSuccess,
   450  		},
   451  		{
   452  			grants: []string{
   453  				"GRANT SELECT, INSERT, DELETE, ALTER, DROP ON *.* TO 'root'@'%'",
   454  			},
   455  			checkState: StateWarning,
   456  			errStr:     "lack of Create global (*.*) privilege; lack of Update global (*.*) privilege; ",
   457  		},
   458  	}
   459  	for _, cs := range cases {
   460  		result := &Result{
   461  			State: StateWarning,
   462  		}
   463  		replRequiredPrivs := map[mysql.PrivilegeType]priv{
   464  			mysql.CreatePriv: {needGlobal: true},
   465  			mysql.SelectPriv: {needGlobal: true},
   466  			mysql.InsertPriv: {needGlobal: true},
   467  			mysql.UpdatePriv: {needGlobal: true},
   468  			mysql.DeletePriv: {needGlobal: true},
   469  			mysql.AlterPriv:  {needGlobal: true},
   470  			mysql.DropPriv:   {needGlobal: true},
   471  		}
   472  		err := verifyPrivilegesWithResult(result, cs.grants, replRequiredPrivs)
   473  		if cs.checkState == StateSuccess {
   474  			require.Nil(t, err, "grants: %v", cs.grants)
   475  		} else {
   476  			require.NotNil(t, err, "grants: %v", cs.grants)
   477  			require.Equal(t, cs.errStr, err.ShortErr, "grants: %v", cs.grants)
   478  		}
   479  	}
   480  }