diff --git a/go/vt/vtctl/workflow/framework_test.go b/go/vt/vtctl/workflow/framework_test.go index fb3c547964e..25a6369089d 100644 --- a/go/vt/vtctl/workflow/framework_test.go +++ b/go/vt/vtctl/workflow/framework_test.go @@ -578,6 +578,13 @@ func (tmc *testTMClient) UpdateVReplicationWorkflow(ctx context.Context, tablet }, nil } +func (tmc *testTMClient) ValidateVReplicationPermissions(ctx context.Context, tablet *topodatapb.Tablet, req *tabletmanagerdatapb.ValidateVReplicationPermissionsRequest) (*tabletmanagerdatapb.ValidateVReplicationPermissionsResponse, error) { + return &tabletmanagerdatapb.ValidateVReplicationPermissionsResponse{ + User: "vt_filtered", + Ok: true, + }, nil +} + func (tmc *testTMClient) setPrimaryPosition(tablet *topodatapb.Tablet, position string) { tmc.mu.Lock() defer tmc.mu.Unlock() diff --git a/go/vt/vtctl/workflow/utils.go b/go/vt/vtctl/workflow/utils.go index 98b4e70a299..89eb5d6dded 100644 --- a/go/vt/vtctl/workflow/utils.go +++ b/go/vt/vtctl/workflow/utils.go @@ -666,23 +666,21 @@ func areTabletsAvailableToStreamFrom(ctx context.Context, req *vtctldatapb.Workf allErrors.RecordError(fmt.Errorf("no tablet found to source data in keyspace %s, shard %s", keyspace, shard.ShardName())) return } - if req.GetEnableReverseReplication() { - // Ensure the tablet has the minimum privileges required on the sidecar database - // table in order to manage the reverse workflow as part of the traffic switch. - for _, tablet := range tablets { - wg.Add(1) - go func() { - defer wg.Done() - res, err := ts.ws.tmc.ValidateVReplicationPermissions(ctx, tablet.Tablet, nil) - if err != nil { - allErrors.RecordError(vterrors.Wrapf(err, "failed to validate required vreplication metadata permissions on tablet %s", topoproto.TabletAliasString(tablet.Alias))) - } - if !res.GetOk() { - allErrors.RecordError(fmt.Errorf("user %s does not have the required set of permissions (insert,update,delete) on the %s.vreplication table on tablet %s", - res.GetUser(), sidecar.GetIdentifier(), topoproto.TabletAliasString(tablet.Alias))) - } - }() - } + // Ensure the tablet has the minimum privileges required on the sidecar database + // table in order to manage the reverse workflow as part of the traffic switch. + for _, tablet := range tablets { + wg.Add(1) + go func() { + defer wg.Done() + res, err := ts.ws.tmc.ValidateVReplicationPermissions(ctx, tablet.Tablet, nil) + if err != nil { + allErrors.RecordError(vterrors.Wrapf(err, "failed to validate required vreplication metadata permissions on tablet %s", topoproto.TabletAliasString(tablet.Alias))) + } + if !res.GetOk() { + allErrors.RecordError(fmt.Errorf("user %s does not have the required set of permissions (insert,update,delete) on the %s.vreplication table on tablet %s", + res.GetUser(), sidecar.GetIdentifier(), topoproto.TabletAliasString(tablet.Alias))) + } + }() } }(cells, keyspace, shard) } diff --git a/go/vt/vttablet/tabletmanager/rpc_vreplication.go b/go/vt/vttablet/tabletmanager/rpc_vreplication.go index 73d1dbf049a..bd093b9c2b8 100644 --- a/go/vt/vttablet/tabletmanager/rpc_vreplication.go +++ b/go/vt/vttablet/tabletmanager/rpc_vreplication.go @@ -62,20 +62,22 @@ const ( // Check if workflow is still copying. sqlGetVReplicationCopyStatus = "select distinct vrepl_id from %s.copy_state where vrepl_id = %d" // Validate the minimum set of permissions needed to manage vreplication metadata. + // This is a simple check for a matching user rather than any specific user@host + // combination. sqlValidateVReplicationPermissions = ` select if(count(*)>0, 1, 0) as good from mysql.user as u left join mysql.db as d on (u.user = d.user) left join mysql.tables_priv as t on (u.user = t.user) where u.user = %a and ( - (u.insert_priv = 'y' and u.update_priv = 'y' and u.delete_priv = 'y') - or (d.db = %a and u.insert_priv = 'y' and u.update_priv = 'y' and u.delete_priv = 'y') - or (t.db = %a and t.table_name = 'vreplication' + (u.insert_priv = 'y' and u.update_priv = 'y' and u.delete_priv = 'y') /* user has global privs */ + or (d.db = %a and u.insert_priv = 'y' and u.update_priv = 'y' and u.delete_priv = 'y') /* user has db privs */ + or (t.db = %a and t.table_name = 'vreplication' /* user has table privs */ and find_in_set('insert', t.table_priv) and find_in_set('update', t.table_priv) and find_in_set('delete', t.table_priv) ) - ) + ) limit 1 ` ) @@ -549,7 +551,7 @@ func (tm *TabletManager) UpdateVReplicationWorkflows(ctx context.Context, req *t // ValidateVReplicationPermissions validates that the --db_filtered_user has // the minimum permissions required on the sidecardb vreplication table -// needed in order to manager vreplication metadata. +// needed in order to manage vreplication metadata. func (tm *TabletManager) ValidateVReplicationPermissions(ctx context.Context, req *tabletmanagerdatapb.ValidateVReplicationPermissionsRequest) (*tabletmanagerdatapb.ValidateVReplicationPermissionsResponse, error) { query, err := sqlparser.ParseAndBind(sqlValidateVReplicationPermissions, sqltypes.StringBindVariable(tm.DBConfigs.Filtered.User), @@ -573,8 +575,9 @@ func (tm *TabletManager) ValidateVReplicationPermissions(ctx context.Context, re query, qr) } val, err := qr.Rows[0][0].ToBool() - if err != nil { - return nil, err + if err != nil { // Should never happen + return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "unexpected result for query %s: expected boolean-like value, got: %q", + query, qr.Rows[0][0].ToString()) } return &tabletmanagerdatapb.ValidateVReplicationPermissionsResponse{ User: tm.DBConfigs.Filtered.User,