vitess.io/vitess@v0.16.2/go/vt/vtadmin/api_authz_test.go (about)

     1  // Code generated by testutil/authztestgen. DO NOT EDIT.
     2  
     3  /*
     4  Copyright 2022 The Vitess Authors.
     5  
     6  Licensed under the Apache License, Version 2.0 (the "License");
     7  you may not use this file except in compliance with the License.
     8  You may obtain a copy of the License at
     9  
    10      http://www.apache.org/licenses/LICENSE-2.0
    11  
    12  Unless required by applicable law or agreed to in writing, software
    13  distributed under the License is distributed on an "AS IS" BASIS,
    14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  See the License for the specific language governing permissions and
    16  limitations under the License.
    17  */
    18  
    19  package vtadmin_test
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  
    30  	"vitess.io/vitess/go/vt/topo/topoproto"
    31  	"vitess.io/vitess/go/vt/vtadmin"
    32  	"vitess.io/vitess/go/vt/vtadmin/cluster"
    33  	"vitess.io/vitess/go/vt/vtadmin/rbac"
    34  	"vitess.io/vitess/go/vt/vtadmin/testutil"
    35  	"vitess.io/vitess/go/vt/vtadmin/vtctldclient/fakevtctldclient"
    36  
    37  	logutilpb "vitess.io/vitess/go/vt/proto/logutil"
    38  	mysqlctlpb "vitess.io/vitess/go/vt/proto/mysqlctl"
    39  	tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata"
    40  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    41  	vschemapb "vitess.io/vitess/go/vt/proto/vschema"
    42  	vtadminpb "vitess.io/vitess/go/vt/proto/vtadmin"
    43  	vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata"
    44  )
    45  
    46  func TestCreateKeyspace(t *testing.T) {
    47  	t.Parallel()
    48  
    49  	opts := vtadmin.Options{
    50  		RBAC: &rbac.Config{
    51  			Rules: []*struct {
    52  				Resource string
    53  				Actions  []string
    54  				Subjects []string
    55  				Clusters []string
    56  			}{
    57  				{
    58  					Resource: "Keyspace",
    59  					Actions:  []string{"create"},
    60  					Subjects: []string{"user:allowed"},
    61  					Clusters: []string{"*"},
    62  				},
    63  			},
    64  		},
    65  	}
    66  	err := opts.RBAC.Reify()
    67  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
    68  
    69  	api := vtadmin.NewAPI(testClusters(t), opts)
    70  	t.Cleanup(func() {
    71  		if err := api.Close(); err != nil {
    72  			t.Logf("api did not close cleanly: %s", err.Error())
    73  		}
    74  	})
    75  
    76  	t.Run("unauthorized actor", func(t *testing.T) {
    77  		t.Parallel()
    78  
    79  		actor := &rbac.Actor{Name: "other"}
    80  		ctx := context.Background()
    81  		if actor != nil {
    82  			ctx = rbac.NewContext(ctx, actor)
    83  		}
    84  
    85  		resp, err := api.CreateKeyspace(ctx, &vtadminpb.CreateKeyspaceRequest{
    86  			ClusterId: "test",
    87  			Options: &vtctldatapb.CreateKeyspaceRequest{
    88  				Name: "test",
    89  			},
    90  		})
    91  		assert.Error(t, err, "actor %+v should not be permitted to CreateKeyspace", actor)
    92  		assert.Nil(t, resp, "actor %+v should not be permitted to CreateKeyspace", actor)
    93  	})
    94  
    95  	t.Run("authorized actor", func(t *testing.T) {
    96  		t.Parallel()
    97  
    98  		actor := &rbac.Actor{Name: "allowed"}
    99  		ctx := context.Background()
   100  		if actor != nil {
   101  			ctx = rbac.NewContext(ctx, actor)
   102  		}
   103  
   104  		resp, err := api.CreateKeyspace(ctx, &vtadminpb.CreateKeyspaceRequest{
   105  			ClusterId: "test",
   106  			Options: &vtctldatapb.CreateKeyspaceRequest{
   107  				Name: "test",
   108  			},
   109  		})
   110  		require.NoError(t, err)
   111  		assert.NotNil(t, resp, "actor %+v should be permitted to CreateKeyspace", actor)
   112  	})
   113  }
   114  
   115  func TestCreateShard(t *testing.T) {
   116  	t.Parallel()
   117  
   118  	opts := vtadmin.Options{
   119  		RBAC: &rbac.Config{
   120  			Rules: []*struct {
   121  				Resource string
   122  				Actions  []string
   123  				Subjects []string
   124  				Clusters []string
   125  			}{
   126  				{
   127  					Resource: "Shard",
   128  					Actions:  []string{"create"},
   129  					Subjects: []string{"user:allowed"},
   130  					Clusters: []string{"*"},
   131  				},
   132  			},
   133  		},
   134  	}
   135  	err := opts.RBAC.Reify()
   136  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
   137  
   138  	api := vtadmin.NewAPI(testClusters(t), opts)
   139  	t.Cleanup(func() {
   140  		if err := api.Close(); err != nil {
   141  			t.Logf("api did not close cleanly: %s", err.Error())
   142  		}
   143  	})
   144  
   145  	t.Run("unauthorized actor", func(t *testing.T) {
   146  		t.Parallel()
   147  
   148  		actor := &rbac.Actor{Name: "other"}
   149  		ctx := context.Background()
   150  		if actor != nil {
   151  			ctx = rbac.NewContext(ctx, actor)
   152  		}
   153  
   154  		resp, err := api.CreateShard(ctx, &vtadminpb.CreateShardRequest{
   155  			ClusterId: "test",
   156  			Options: &vtctldatapb.CreateShardRequest{
   157  				Keyspace:  "test",
   158  				ShardName: "-",
   159  			},
   160  		})
   161  		assert.Error(t, err, "actor %+v should not be permitted to CreateShard", actor)
   162  		assert.Nil(t, resp, "actor %+v should not be permitted to CreateShard", actor)
   163  	})
   164  
   165  	t.Run("authorized actor", func(t *testing.T) {
   166  		t.Parallel()
   167  
   168  		actor := &rbac.Actor{Name: "allowed"}
   169  		ctx := context.Background()
   170  		if actor != nil {
   171  			ctx = rbac.NewContext(ctx, actor)
   172  		}
   173  
   174  		resp, err := api.CreateShard(ctx, &vtadminpb.CreateShardRequest{
   175  			ClusterId: "test",
   176  			Options: &vtctldatapb.CreateShardRequest{
   177  				Keyspace:  "test",
   178  				ShardName: "-",
   179  			},
   180  		})
   181  		require.NoError(t, err)
   182  		assert.NotNil(t, resp, "actor %+v should be permitted to CreateShard", actor)
   183  	})
   184  }
   185  
   186  func TestDeleteKeyspace(t *testing.T) {
   187  	t.Parallel()
   188  
   189  	opts := vtadmin.Options{
   190  		RBAC: &rbac.Config{
   191  			Rules: []*struct {
   192  				Resource string
   193  				Actions  []string
   194  				Subjects []string
   195  				Clusters []string
   196  			}{
   197  				{
   198  					Resource: "Keyspace",
   199  					Actions:  []string{"delete"},
   200  					Subjects: []string{"user:allowed"},
   201  					Clusters: []string{"*"},
   202  				},
   203  			},
   204  		},
   205  	}
   206  	err := opts.RBAC.Reify()
   207  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
   208  
   209  	api := vtadmin.NewAPI(testClusters(t), opts)
   210  	t.Cleanup(func() {
   211  		if err := api.Close(); err != nil {
   212  			t.Logf("api did not close cleanly: %s", err.Error())
   213  		}
   214  	})
   215  
   216  	t.Run("unauthorized actor", func(t *testing.T) {
   217  		t.Parallel()
   218  
   219  		actor := &rbac.Actor{Name: "other"}
   220  		ctx := context.Background()
   221  		if actor != nil {
   222  			ctx = rbac.NewContext(ctx, actor)
   223  		}
   224  
   225  		resp, err := api.DeleteKeyspace(ctx, &vtadminpb.DeleteKeyspaceRequest{
   226  			ClusterId: "test",
   227  			Options: &vtctldatapb.DeleteKeyspaceRequest{
   228  				Keyspace: "test",
   229  			},
   230  		})
   231  		assert.Error(t, err, "actor %+v should not be permitted to DeleteKeyspace", actor)
   232  		assert.Nil(t, resp, "actor %+v should not be permitted to DeleteKeyspace", actor)
   233  	})
   234  
   235  	t.Run("authorized actor", func(t *testing.T) {
   236  		t.Parallel()
   237  
   238  		actor := &rbac.Actor{Name: "allowed"}
   239  		ctx := context.Background()
   240  		if actor != nil {
   241  			ctx = rbac.NewContext(ctx, actor)
   242  		}
   243  
   244  		resp, err := api.DeleteKeyspace(ctx, &vtadminpb.DeleteKeyspaceRequest{
   245  			ClusterId: "test",
   246  			Options: &vtctldatapb.DeleteKeyspaceRequest{
   247  				Keyspace: "test",
   248  			},
   249  		})
   250  		require.NoError(t, err)
   251  		assert.NotNil(t, resp, "actor %+v should be permitted to DeleteKeyspace", actor)
   252  	})
   253  }
   254  
   255  func TestDeleteShards(t *testing.T) {
   256  	t.Parallel()
   257  
   258  	opts := vtadmin.Options{
   259  		RBAC: &rbac.Config{
   260  			Rules: []*struct {
   261  				Resource string
   262  				Actions  []string
   263  				Subjects []string
   264  				Clusters []string
   265  			}{
   266  				{
   267  					Resource: "Shard",
   268  					Actions:  []string{"delete"},
   269  					Subjects: []string{"user:allowed"},
   270  					Clusters: []string{"*"},
   271  				},
   272  			},
   273  		},
   274  	}
   275  	err := opts.RBAC.Reify()
   276  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
   277  
   278  	api := vtadmin.NewAPI(testClusters(t), opts)
   279  	t.Cleanup(func() {
   280  		if err := api.Close(); err != nil {
   281  			t.Logf("api did not close cleanly: %s", err.Error())
   282  		}
   283  	})
   284  
   285  	t.Run("unauthorized actor", func(t *testing.T) {
   286  		t.Parallel()
   287  
   288  		actor := &rbac.Actor{Name: "other"}
   289  		ctx := context.Background()
   290  		if actor != nil {
   291  			ctx = rbac.NewContext(ctx, actor)
   292  		}
   293  
   294  		resp, err := api.DeleteShards(ctx, &vtadminpb.DeleteShardsRequest{
   295  			ClusterId: "test",
   296  			Options: &vtctldatapb.DeleteShardsRequest{
   297  				Shards: []*vtctldatapb.Shard{
   298  					{
   299  						Keyspace: "test",
   300  						Name:     "-",
   301  					},
   302  				},
   303  			},
   304  		})
   305  		assert.Error(t, err, "actor %+v should not be permitted to DeleteShards", actor)
   306  		assert.Nil(t, resp, "actor %+v should not be permitted to DeleteShards", actor)
   307  	})
   308  
   309  	t.Run("authorized actor", func(t *testing.T) {
   310  		t.Parallel()
   311  
   312  		actor := &rbac.Actor{Name: "allowed"}
   313  		ctx := context.Background()
   314  		if actor != nil {
   315  			ctx = rbac.NewContext(ctx, actor)
   316  		}
   317  
   318  		resp, err := api.DeleteShards(ctx, &vtadminpb.DeleteShardsRequest{
   319  			ClusterId: "test",
   320  			Options: &vtctldatapb.DeleteShardsRequest{
   321  				Shards: []*vtctldatapb.Shard{
   322  					{
   323  						Keyspace: "test",
   324  						Name:     "-",
   325  					},
   326  				},
   327  			},
   328  		})
   329  		require.NoError(t, err)
   330  		assert.NotNil(t, resp, "actor %+v should be permitted to DeleteShards", actor)
   331  	})
   332  }
   333  
   334  func TestDeleteTablet(t *testing.T) {
   335  	t.Parallel()
   336  
   337  	opts := vtadmin.Options{
   338  		RBAC: &rbac.Config{
   339  			Rules: []*struct {
   340  				Resource string
   341  				Actions  []string
   342  				Subjects []string
   343  				Clusters []string
   344  			}{
   345  				{
   346  					Resource: "Tablet",
   347  					Actions:  []string{"delete"},
   348  					Subjects: []string{"user:allowed"},
   349  					Clusters: []string{"*"},
   350  				},
   351  			},
   352  		},
   353  	}
   354  	err := opts.RBAC.Reify()
   355  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
   356  
   357  	api := vtadmin.NewAPI(testClusters(t), opts)
   358  	t.Cleanup(func() {
   359  		if err := api.Close(); err != nil {
   360  			t.Logf("api did not close cleanly: %s", err.Error())
   361  		}
   362  	})
   363  
   364  	t.Run("unauthorized actor", func(t *testing.T) {
   365  		t.Parallel()
   366  
   367  		actor := &rbac.Actor{Name: "other"}
   368  		ctx := context.Background()
   369  		if actor != nil {
   370  			ctx = rbac.NewContext(ctx, actor)
   371  		}
   372  
   373  		resp, err := api.DeleteTablet(ctx, &vtadminpb.DeleteTabletRequest{
   374  			ClusterIds: []string{"test"},
   375  			Alias: &topodatapb.TabletAlias{
   376  				Cell: "zone1",
   377  				Uid:  100,
   378  			},
   379  		})
   380  		assert.Error(t, err, "actor %+v should not be permitted to DeleteTablet", actor)
   381  		assert.Nil(t, resp, "actor %+v should not be permitted to DeleteTablet", actor)
   382  	})
   383  
   384  	t.Run("authorized actor", func(t *testing.T) {
   385  		t.Parallel()
   386  
   387  		actor := &rbac.Actor{Name: "allowed"}
   388  		ctx := context.Background()
   389  		if actor != nil {
   390  			ctx = rbac.NewContext(ctx, actor)
   391  		}
   392  
   393  		resp, err := api.DeleteTablet(ctx, &vtadminpb.DeleteTabletRequest{
   394  			ClusterIds: []string{"test"},
   395  			Alias: &topodatapb.TabletAlias{
   396  				Cell: "zone1",
   397  				Uid:  100,
   398  			},
   399  		})
   400  		require.NoError(t, err)
   401  		assert.NotNil(t, resp, "actor %+v should be permitted to DeleteTablet", actor)
   402  	})
   403  }
   404  
   405  func TestEmergencyFailoverShard(t *testing.T) {
   406  	t.Parallel()
   407  
   408  	opts := vtadmin.Options{
   409  		RBAC: &rbac.Config{
   410  			Rules: []*struct {
   411  				Resource string
   412  				Actions  []string
   413  				Subjects []string
   414  				Clusters []string
   415  			}{
   416  				{
   417  					Resource: "Shard",
   418  					Actions:  []string{"emergency_failover_shard"},
   419  					Subjects: []string{"user:allowed"},
   420  					Clusters: []string{"*"},
   421  				},
   422  			},
   423  		},
   424  	}
   425  	err := opts.RBAC.Reify()
   426  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
   427  
   428  	api := vtadmin.NewAPI(testClusters(t), opts)
   429  	t.Cleanup(func() {
   430  		if err := api.Close(); err != nil {
   431  			t.Logf("api did not close cleanly: %s", err.Error())
   432  		}
   433  	})
   434  
   435  	t.Run("unauthorized actor", func(t *testing.T) {
   436  		t.Parallel()
   437  
   438  		actor := &rbac.Actor{Name: "other"}
   439  		ctx := context.Background()
   440  		if actor != nil {
   441  			ctx = rbac.NewContext(ctx, actor)
   442  		}
   443  
   444  		resp, err := api.EmergencyFailoverShard(ctx, &vtadminpb.EmergencyFailoverShardRequest{
   445  			ClusterId: "test",
   446  			Options: &vtctldatapb.EmergencyReparentShardRequest{
   447  				Keyspace: "test",
   448  				Shard:    "-",
   449  			},
   450  		})
   451  		require.NoError(t, err)
   452  		assert.Nil(t, resp, "actor %+v should not be permitted to EmergencyFailoverShard", actor)
   453  	})
   454  
   455  	t.Run("authorized actor", func(t *testing.T) {
   456  		t.Parallel()
   457  
   458  		actor := &rbac.Actor{Name: "allowed"}
   459  		ctx := context.Background()
   460  		if actor != nil {
   461  			ctx = rbac.NewContext(ctx, actor)
   462  		}
   463  
   464  		resp, err := api.EmergencyFailoverShard(ctx, &vtadminpb.EmergencyFailoverShardRequest{
   465  			ClusterId: "test",
   466  			Options: &vtctldatapb.EmergencyReparentShardRequest{
   467  				Keyspace: "test",
   468  				Shard:    "-",
   469  			},
   470  		})
   471  		require.NoError(t, err)
   472  		assert.NotNil(t, resp, "actor %+v should be permitted to EmergencyFailoverShard", actor)
   473  	})
   474  }
   475  
   476  func TestFindSchema(t *testing.T) {
   477  	t.Parallel()
   478  
   479  	opts := vtadmin.Options{
   480  		RBAC: &rbac.Config{
   481  			Rules: []*struct {
   482  				Resource string
   483  				Actions  []string
   484  				Subjects []string
   485  				Clusters []string
   486  			}{
   487  				{
   488  					Resource: "Schema",
   489  					Actions:  []string{"get"},
   490  					Subjects: []string{"user:allowed-all"},
   491  					Clusters: []string{"*"},
   492  				},
   493  				{
   494  					Resource: "Schema",
   495  					Actions:  []string{"get"},
   496  					Subjects: []string{"user:allowed-other"},
   497  					Clusters: []string{"other"},
   498  				},
   499  			},
   500  		},
   501  	}
   502  	err := opts.RBAC.Reify()
   503  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
   504  
   505  	t.Run("unauthorized actor", func(t *testing.T) {
   506  		t.Parallel()
   507  
   508  		api := vtadmin.NewAPI(testClusters(t), opts)
   509  		t.Cleanup(func() {
   510  			if err := api.Close(); err != nil {
   511  				t.Logf("api did not close cleanly: %s", err.Error())
   512  			}
   513  		})
   514  
   515  		actor := &rbac.Actor{Name: "unauthorized"}
   516  		ctx := context.Background()
   517  		if actor != nil {
   518  			ctx = rbac.NewContext(ctx, actor)
   519  		}
   520  
   521  		resp, err := api.FindSchema(ctx, &vtadminpb.FindSchemaRequest{
   522  			Table: "t1",
   523  		})
   524  		assert.Error(t, err)
   525  		assert.Nil(t, resp, "actor %+v should not be permitted to FindSchema", actor)
   526  	})
   527  
   528  	t.Run("partial access", func(t *testing.T) {
   529  		t.Parallel()
   530  
   531  		api := vtadmin.NewAPI(testClusters(t), opts)
   532  		t.Cleanup(func() {
   533  			if err := api.Close(); err != nil {
   534  				t.Logf("api did not close cleanly: %s", err.Error())
   535  			}
   536  		})
   537  
   538  		actor := &rbac.Actor{Name: "allowed-other"}
   539  		ctx := context.Background()
   540  		if actor != nil {
   541  			ctx = rbac.NewContext(ctx, actor)
   542  		}
   543  
   544  		resp, _ := api.FindSchema(ctx, &vtadminpb.FindSchemaRequest{
   545  			Table: "t1",
   546  		})
   547  		assert.NotEmpty(t, resp, "actor %+v should be permitted to FindSchema", actor)
   548  	})
   549  
   550  	t.Run("full access", func(t *testing.T) {
   551  		t.Parallel()
   552  
   553  		api := vtadmin.NewAPI(testClusters(t), opts)
   554  		t.Cleanup(func() {
   555  			if err := api.Close(); err != nil {
   556  				t.Logf("api did not close cleanly: %s", err.Error())
   557  			}
   558  		})
   559  
   560  		actor := &rbac.Actor{Name: "allowed-all"}
   561  		ctx := context.Background()
   562  		if actor != nil {
   563  			ctx = rbac.NewContext(ctx, actor)
   564  		}
   565  
   566  		resp, err := api.FindSchema(ctx, &vtadminpb.FindSchemaRequest{
   567  			Table: "t1",
   568  		})
   569  		assert.Error(t, err)
   570  		assert.Contains(t, err.Error(), "multiple schemas found")
   571  		assert.Nil(t, resp)
   572  	})
   573  }
   574  
   575  func TestGetBackups(t *testing.T) {
   576  	t.Parallel()
   577  
   578  	opts := vtadmin.Options{
   579  		RBAC: &rbac.Config{
   580  			Rules: []*struct {
   581  				Resource string
   582  				Actions  []string
   583  				Subjects []string
   584  				Clusters []string
   585  			}{
   586  				{
   587  					Resource: "Backup",
   588  					Actions:  []string{"get"},
   589  					Subjects: []string{"user:allowed-all"},
   590  					Clusters: []string{"*"},
   591  				},
   592  				{
   593  					Resource: "Backup",
   594  					Actions:  []string{"get"},
   595  					Subjects: []string{"user:allowed-other"},
   596  					Clusters: []string{"other"},
   597  				},
   598  			},
   599  		},
   600  	}
   601  	err := opts.RBAC.Reify()
   602  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
   603  
   604  	api := vtadmin.NewAPI(testClusters(t), opts)
   605  	t.Cleanup(func() {
   606  		if err := api.Close(); err != nil {
   607  			t.Logf("api did not close cleanly: %s", err.Error())
   608  		}
   609  	})
   610  
   611  	t.Run("unauthorized actor", func(t *testing.T) {
   612  		t.Parallel()
   613  
   614  		actor := &rbac.Actor{Name: "unauthorized"}
   615  		ctx := context.Background()
   616  		if actor != nil {
   617  			ctx = rbac.NewContext(ctx, actor)
   618  		}
   619  
   620  		resp, err := api.GetBackups(ctx, &vtadminpb.GetBackupsRequest{})
   621  		assert.NoError(t, err)
   622  		assert.Empty(t, resp.Backups, "actor %+v should not be permitted to GetBackups", actor)
   623  	})
   624  
   625  	t.Run("partial access", func(t *testing.T) {
   626  		t.Parallel()
   627  
   628  		actor := &rbac.Actor{Name: "allowed-other"}
   629  		ctx := context.Background()
   630  		if actor != nil {
   631  			ctx = rbac.NewContext(ctx, actor)
   632  		}
   633  
   634  		resp, _ := api.GetBackups(ctx, &vtadminpb.GetBackupsRequest{})
   635  		assert.NotEmpty(t, resp.Backups, "actor %+v should be permitted to GetBackups", actor)
   636  		assert.Len(t, resp.Backups, 3, "'other' actor should be able to see the 3 backups in cluster 'other'")
   637  	})
   638  
   639  	t.Run("full access", func(t *testing.T) {
   640  		t.Parallel()
   641  
   642  		actor := &rbac.Actor{Name: "allowed-all"}
   643  		ctx := context.Background()
   644  		if actor != nil {
   645  			ctx = rbac.NewContext(ctx, actor)
   646  		}
   647  
   648  		resp, _ := api.GetBackups(ctx, &vtadminpb.GetBackupsRequest{})
   649  		assert.NotEmpty(t, resp.Backups, "actor %+v should be permitted to GetBackups", actor)
   650  		assert.Len(t, resp.Backups, 4, "'all' actor should be able to see backups in all clusters")
   651  	})
   652  }
   653  
   654  func TestGetCellInfos(t *testing.T) {
   655  	t.Parallel()
   656  
   657  	opts := vtadmin.Options{
   658  		RBAC: &rbac.Config{
   659  			Rules: []*struct {
   660  				Resource string
   661  				Actions  []string
   662  				Subjects []string
   663  				Clusters []string
   664  			}{
   665  				{
   666  					Resource: "CellInfo",
   667  					Actions:  []string{"get"},
   668  					Subjects: []string{"user:allowed-all"},
   669  					Clusters: []string{"*"},
   670  				},
   671  				{
   672  					Resource: "CellInfo",
   673  					Actions:  []string{"get"},
   674  					Subjects: []string{"user:allowed-other"},
   675  					Clusters: []string{"other"},
   676  				},
   677  			},
   678  		},
   679  	}
   680  	err := opts.RBAC.Reify()
   681  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
   682  
   683  	api := vtadmin.NewAPI(testClusters(t), opts)
   684  	t.Cleanup(func() {
   685  		if err := api.Close(); err != nil {
   686  			t.Logf("api did not close cleanly: %s", err.Error())
   687  		}
   688  	})
   689  
   690  	t.Run("unauthorized actor", func(t *testing.T) {
   691  		t.Parallel()
   692  
   693  		actor := &rbac.Actor{Name: "unauthorized"}
   694  		ctx := context.Background()
   695  		if actor != nil {
   696  			ctx = rbac.NewContext(ctx, actor)
   697  		}
   698  
   699  		resp, err := api.GetCellInfos(ctx, &vtadminpb.GetCellInfosRequest{
   700  			NamesOnly: true,
   701  		})
   702  		assert.NoError(t, err)
   703  		assert.Empty(t, resp.CellInfos, "actor %+v should not be permitted to GetCellInfos", actor)
   704  	})
   705  
   706  	t.Run("partial access", func(t *testing.T) {
   707  		t.Parallel()
   708  
   709  		actor := &rbac.Actor{Name: "allowed-other"}
   710  		ctx := context.Background()
   711  		if actor != nil {
   712  			ctx = rbac.NewContext(ctx, actor)
   713  		}
   714  
   715  		resp, _ := api.GetCellInfos(ctx, &vtadminpb.GetCellInfosRequest{
   716  			NamesOnly: true,
   717  		})
   718  		assert.NotEmpty(t, resp.CellInfos, "actor %+v should be permitted to GetCellInfos", actor)
   719  		assert.ElementsMatch(t, resp.CellInfos, []*vtadminpb.ClusterCellInfo{{Cluster: &vtadminpb.Cluster{Id: "other", Name: "other"}, Name: "other1"}})
   720  	})
   721  
   722  	t.Run("full access", func(t *testing.T) {
   723  		t.Parallel()
   724  
   725  		actor := &rbac.Actor{Name: "allowed-all"}
   726  		ctx := context.Background()
   727  		if actor != nil {
   728  			ctx = rbac.NewContext(ctx, actor)
   729  		}
   730  
   731  		resp, _ := api.GetCellInfos(ctx, &vtadminpb.GetCellInfosRequest{
   732  			NamesOnly: true,
   733  		})
   734  		assert.NotEmpty(t, resp.CellInfos, "actor %+v should be permitted to GetCellInfos", actor)
   735  		assert.ElementsMatch(t, resp.CellInfos, []*vtadminpb.ClusterCellInfo{{Cluster: &vtadminpb.Cluster{Id: "test", Name: "test"}, Name: "zone1"}, {Cluster: &vtadminpb.Cluster{Id: "other", Name: "other"}, Name: "other1"}})
   736  	})
   737  }
   738  
   739  func TestGetCellsAliases(t *testing.T) {
   740  	t.Parallel()
   741  
   742  	opts := vtadmin.Options{
   743  		RBAC: &rbac.Config{
   744  			Rules: []*struct {
   745  				Resource string
   746  				Actions  []string
   747  				Subjects []string
   748  				Clusters []string
   749  			}{
   750  				{
   751  					Resource: "CellsAlias",
   752  					Actions:  []string{"get"},
   753  					Subjects: []string{"user:allowed-all"},
   754  					Clusters: []string{"*"},
   755  				},
   756  				{
   757  					Resource: "CellsAlias",
   758  					Actions:  []string{"get"},
   759  					Subjects: []string{"user:allowed-other"},
   760  					Clusters: []string{"other"},
   761  				},
   762  			},
   763  		},
   764  	}
   765  	err := opts.RBAC.Reify()
   766  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
   767  
   768  	api := vtadmin.NewAPI(testClusters(t), opts)
   769  	t.Cleanup(func() {
   770  		if err := api.Close(); err != nil {
   771  			t.Logf("api did not close cleanly: %s", err.Error())
   772  		}
   773  	})
   774  
   775  	t.Run("unauthorized actor", func(t *testing.T) {
   776  		t.Parallel()
   777  
   778  		actor := &rbac.Actor{Name: "unauthorized"}
   779  		ctx := context.Background()
   780  		if actor != nil {
   781  			ctx = rbac.NewContext(ctx, actor)
   782  		}
   783  
   784  		resp, err := api.GetCellsAliases(ctx, &vtadminpb.GetCellsAliasesRequest{})
   785  		assert.NoError(t, err)
   786  		assert.Empty(t, resp.Aliases, "actor %+v should not be permitted to GetCellsAliases", actor)
   787  	})
   788  
   789  	t.Run("partial access", func(t *testing.T) {
   790  		t.Parallel()
   791  
   792  		actor := &rbac.Actor{Name: "allowed-other"}
   793  		ctx := context.Background()
   794  		if actor != nil {
   795  			ctx = rbac.NewContext(ctx, actor)
   796  		}
   797  
   798  		resp, _ := api.GetCellsAliases(ctx, &vtadminpb.GetCellsAliasesRequest{})
   799  		assert.NotEmpty(t, resp.Aliases, "actor %+v should be permitted to GetCellsAliases", actor)
   800  		assert.ElementsMatch(t, resp.Aliases, []*vtadminpb.ClusterCellsAliases{{Cluster: &vtadminpb.Cluster{Id: "other", Name: "other"}, Aliases: map[string]*topodatapb.CellsAlias{"other": {Cells: []string{"other1"}}}}})
   801  	})
   802  
   803  	t.Run("full access", func(t *testing.T) {
   804  		t.Parallel()
   805  
   806  		actor := &rbac.Actor{Name: "allowed-all"}
   807  		ctx := context.Background()
   808  		if actor != nil {
   809  			ctx = rbac.NewContext(ctx, actor)
   810  		}
   811  
   812  		resp, _ := api.GetCellsAliases(ctx, &vtadminpb.GetCellsAliasesRequest{})
   813  		assert.NotEmpty(t, resp.Aliases, "actor %+v should be permitted to GetCellsAliases", actor)
   814  		assert.ElementsMatch(t, resp.Aliases, []*vtadminpb.ClusterCellsAliases{{Cluster: &vtadminpb.Cluster{Id: "test", Name: "test"}, Aliases: map[string]*topodatapb.CellsAlias{"zone": {Cells: []string{"zone1"}}}}, {Cluster: &vtadminpb.Cluster{Id: "other", Name: "other"}, Aliases: map[string]*topodatapb.CellsAlias{"other": {Cells: []string{"other1"}}}}})
   815  	})
   816  }
   817  
   818  func TestGetClusters(t *testing.T) {
   819  	t.Parallel()
   820  
   821  	opts := vtadmin.Options{
   822  		RBAC: &rbac.Config{
   823  			Rules: []*struct {
   824  				Resource string
   825  				Actions  []string
   826  				Subjects []string
   827  				Clusters []string
   828  			}{
   829  				{
   830  					Resource: "Cluster",
   831  					Actions:  []string{"get"},
   832  					Subjects: []string{"user:allowed"},
   833  					Clusters: []string{"*"},
   834  				},
   835  			},
   836  		},
   837  	}
   838  	err := opts.RBAC.Reify()
   839  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
   840  
   841  	api := vtadmin.NewAPI(testClusters(t), opts)
   842  	t.Cleanup(func() {
   843  		if err := api.Close(); err != nil {
   844  			t.Logf("api did not close cleanly: %s", err.Error())
   845  		}
   846  	})
   847  
   848  	t.Run("unauthenticated", func(t *testing.T) {
   849  		t.Parallel()
   850  
   851  		var actor *rbac.Actor
   852  		ctx := context.Background()
   853  		if actor != nil {
   854  			ctx = rbac.NewContext(ctx, actor)
   855  		}
   856  
   857  		resp, _ := api.GetClusters(ctx, &vtadminpb.GetClustersRequest{})
   858  		assert.Empty(t, resp.Clusters, "actor %+v should not be permitted to GetClusters", actor)
   859  	})
   860  
   861  	t.Run("unauthorized actor", func(t *testing.T) {
   862  		t.Parallel()
   863  
   864  		actor := &rbac.Actor{Name: "other"}
   865  		ctx := context.Background()
   866  		if actor != nil {
   867  			ctx = rbac.NewContext(ctx, actor)
   868  		}
   869  
   870  		resp, _ := api.GetClusters(ctx, &vtadminpb.GetClustersRequest{})
   871  		assert.Empty(t, resp.Clusters, "actor %+v should not be permitted to GetClusters", actor)
   872  	})
   873  
   874  	t.Run("authorized actor", func(t *testing.T) {
   875  		t.Parallel()
   876  
   877  		actor := &rbac.Actor{Name: "allowed"}
   878  		ctx := context.Background()
   879  		if actor != nil {
   880  			ctx = rbac.NewContext(ctx, actor)
   881  		}
   882  
   883  		resp, err := api.GetClusters(ctx, &vtadminpb.GetClustersRequest{})
   884  		require.NoError(t, err)
   885  		assert.NotEmpty(t, resp.Clusters, "actor %+v should be permitted to GetClusters", actor)
   886  	})
   887  }
   888  
   889  func TestGetGates(t *testing.T) {
   890  	t.Parallel()
   891  
   892  	opts := vtadmin.Options{
   893  		RBAC: &rbac.Config{
   894  			Rules: []*struct {
   895  				Resource string
   896  				Actions  []string
   897  				Subjects []string
   898  				Clusters []string
   899  			}{
   900  				{
   901  					Resource: "VTGate",
   902  					Actions:  []string{"get"},
   903  					Subjects: []string{"user:allowed-all"},
   904  					Clusters: []string{"*"},
   905  				},
   906  				{
   907  					Resource: "VTGate",
   908  					Actions:  []string{"get"},
   909  					Subjects: []string{"user:allowed-other"},
   910  					Clusters: []string{"other"},
   911  				},
   912  			},
   913  		},
   914  	}
   915  	err := opts.RBAC.Reify()
   916  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
   917  
   918  	t.Run("unauthorized actor", func(t *testing.T) {
   919  		t.Parallel()
   920  
   921  		api := vtadmin.NewAPI(testClusters(t), opts)
   922  		t.Cleanup(func() {
   923  			if err := api.Close(); err != nil {
   924  				t.Logf("api did not close cleanly: %s", err.Error())
   925  			}
   926  		})
   927  
   928  		actor := &rbac.Actor{Name: "unauthorized"}
   929  		ctx := context.Background()
   930  		if actor != nil {
   931  			ctx = rbac.NewContext(ctx, actor)
   932  		}
   933  
   934  		resp, err := api.GetGates(ctx, &vtadminpb.GetGatesRequest{})
   935  		assert.NoError(t, err)
   936  		assert.Empty(t, resp.Gates, "actor %+v should not be permitted to GetGates", actor)
   937  	})
   938  
   939  	t.Run("partial access", func(t *testing.T) {
   940  		t.Parallel()
   941  
   942  		api := vtadmin.NewAPI(testClusters(t), opts)
   943  		t.Cleanup(func() {
   944  			if err := api.Close(); err != nil {
   945  				t.Logf("api did not close cleanly: %s", err.Error())
   946  			}
   947  		})
   948  
   949  		actor := &rbac.Actor{Name: "allowed-other"}
   950  		ctx := context.Background()
   951  		if actor != nil {
   952  			ctx = rbac.NewContext(ctx, actor)
   953  		}
   954  
   955  		resp, _ := api.GetGates(ctx, &vtadminpb.GetGatesRequest{})
   956  		assert.NotEmpty(t, resp.Gates, "actor %+v should be permitted to GetGates", actor)
   957  		// testutil.BuildCluster creates exactly one gate per cluster
   958  		assert.Len(t, resp.Gates, 1, "actor %+v should only be able to see VTGate from cluster 'other'", actor)
   959  	})
   960  
   961  	t.Run("full access", func(t *testing.T) {
   962  		t.Parallel()
   963  
   964  		api := vtadmin.NewAPI(testClusters(t), opts)
   965  		t.Cleanup(func() {
   966  			if err := api.Close(); err != nil {
   967  				t.Logf("api did not close cleanly: %s", err.Error())
   968  			}
   969  		})
   970  
   971  		actor := &rbac.Actor{Name: "allowed-all"}
   972  		ctx := context.Background()
   973  		if actor != nil {
   974  			ctx = rbac.NewContext(ctx, actor)
   975  		}
   976  
   977  		resp, _ := api.GetGates(ctx, &vtadminpb.GetGatesRequest{})
   978  		assert.NotEmpty(t, resp.Gates, "actor %+v should be permitted to GetGates", actor)
   979  		// testutil.BuildCluster creates exactly one gate per cluster
   980  		assert.Len(t, resp.Gates, 2, "actor %+v should be able to see VTGate from all clusters", actor)
   981  	})
   982  }
   983  
   984  func TestGetKeyspace(t *testing.T) {
   985  	t.Parallel()
   986  
   987  	opts := vtadmin.Options{
   988  		RBAC: &rbac.Config{
   989  			Rules: []*struct {
   990  				Resource string
   991  				Actions  []string
   992  				Subjects []string
   993  				Clusters []string
   994  			}{
   995  				{
   996  					Resource: "Keyspace",
   997  					Actions:  []string{"get"},
   998  					Subjects: []string{"user:allowed"},
   999  					Clusters: []string{"*"},
  1000  				},
  1001  			},
  1002  		},
  1003  	}
  1004  	err := opts.RBAC.Reify()
  1005  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  1006  
  1007  	api := vtadmin.NewAPI(testClusters(t), opts)
  1008  	t.Cleanup(func() {
  1009  		if err := api.Close(); err != nil {
  1010  			t.Logf("api did not close cleanly: %s", err.Error())
  1011  		}
  1012  	})
  1013  
  1014  	t.Run("unauthorized actor", func(t *testing.T) {
  1015  		t.Parallel()
  1016  
  1017  		actor := &rbac.Actor{Name: "other"}
  1018  		ctx := context.Background()
  1019  		if actor != nil {
  1020  			ctx = rbac.NewContext(ctx, actor)
  1021  		}
  1022  
  1023  		resp, err := api.GetKeyspace(ctx, &vtadminpb.GetKeyspaceRequest{
  1024  			ClusterId: "test",
  1025  			Keyspace:  "test",
  1026  		})
  1027  		require.NoError(t, err)
  1028  		assert.Nil(t, resp, "actor %+v should not be permitted to GetKeyspace", actor)
  1029  	})
  1030  
  1031  	t.Run("authorized actor", func(t *testing.T) {
  1032  		t.Parallel()
  1033  
  1034  		actor := &rbac.Actor{Name: "allowed"}
  1035  		ctx := context.Background()
  1036  		if actor != nil {
  1037  			ctx = rbac.NewContext(ctx, actor)
  1038  		}
  1039  
  1040  		resp, err := api.GetKeyspace(ctx, &vtadminpb.GetKeyspaceRequest{
  1041  			ClusterId: "test",
  1042  			Keyspace:  "test",
  1043  		})
  1044  		require.NoError(t, err)
  1045  		assert.NotNil(t, resp, "actor %+v should be permitted to GetKeyspace", actor)
  1046  	})
  1047  }
  1048  
  1049  func TestGetKeyspaces(t *testing.T) {
  1050  	t.Parallel()
  1051  
  1052  	opts := vtadmin.Options{
  1053  		RBAC: &rbac.Config{
  1054  			Rules: []*struct {
  1055  				Resource string
  1056  				Actions  []string
  1057  				Subjects []string
  1058  				Clusters []string
  1059  			}{
  1060  				{
  1061  					Resource: "Keyspace",
  1062  					Actions:  []string{"get"},
  1063  					Subjects: []string{"user:allowed-all"},
  1064  					Clusters: []string{"*"},
  1065  				},
  1066  				{
  1067  					Resource: "Keyspace",
  1068  					Actions:  []string{"get"},
  1069  					Subjects: []string{"user:allowed-other"},
  1070  					Clusters: []string{"other"},
  1071  				},
  1072  			},
  1073  		},
  1074  	}
  1075  	err := opts.RBAC.Reify()
  1076  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  1077  
  1078  	api := vtadmin.NewAPI(testClusters(t), opts)
  1079  	t.Cleanup(func() {
  1080  		if err := api.Close(); err != nil {
  1081  			t.Logf("api did not close cleanly: %s", err.Error())
  1082  		}
  1083  	})
  1084  
  1085  	t.Run("unauthorized actor", func(t *testing.T) {
  1086  		t.Parallel()
  1087  
  1088  		actor := &rbac.Actor{Name: "unauthorized"}
  1089  		ctx := context.Background()
  1090  		if actor != nil {
  1091  			ctx = rbac.NewContext(ctx, actor)
  1092  		}
  1093  
  1094  		resp, err := api.GetKeyspaces(ctx, &vtadminpb.GetKeyspacesRequest{})
  1095  		assert.NoError(t, err)
  1096  		assert.Empty(t, resp.Keyspaces, "actor %+v should not be permitted to GetKeyspaces", actor)
  1097  	})
  1098  
  1099  	t.Run("partial access", func(t *testing.T) {
  1100  		t.Parallel()
  1101  
  1102  		actor := &rbac.Actor{Name: "allowed-other"}
  1103  		ctx := context.Background()
  1104  		if actor != nil {
  1105  			ctx = rbac.NewContext(ctx, actor)
  1106  		}
  1107  
  1108  		resp, _ := api.GetKeyspaces(ctx, &vtadminpb.GetKeyspacesRequest{})
  1109  		assert.NotEmpty(t, resp.Keyspaces, "actor %+v should be permitted to GetKeyspaces", actor)
  1110  		ksMap := map[string][]string{}
  1111  		for _, ks := range resp.Keyspaces {
  1112  			if _, ok := ksMap[ks.Cluster.Id]; !ok {
  1113  				ksMap[ks.Cluster.Id] = []string{}
  1114  			}
  1115  			ksMap[ks.Cluster.Id] = append(ksMap[ks.Cluster.Id], ks.Keyspace.Name)
  1116  		}
  1117  		assert.Equal(t, ksMap, map[string][]string{"other": {"otherks"}}, "actor %+v should be permitted to GetKeyspaces", actor)
  1118  	})
  1119  
  1120  	t.Run("full access", func(t *testing.T) {
  1121  		t.Parallel()
  1122  
  1123  		actor := &rbac.Actor{Name: "allowed-all"}
  1124  		ctx := context.Background()
  1125  		if actor != nil {
  1126  			ctx = rbac.NewContext(ctx, actor)
  1127  		}
  1128  
  1129  		resp, _ := api.GetKeyspaces(ctx, &vtadminpb.GetKeyspacesRequest{})
  1130  		assert.NotEmpty(t, resp.Keyspaces, "actor %+v should be permitted to GetKeyspaces", actor)
  1131  		ksMap := map[string][]string{}
  1132  		for _, ks := range resp.Keyspaces {
  1133  			if _, ok := ksMap[ks.Cluster.Id]; !ok {
  1134  				ksMap[ks.Cluster.Id] = []string{}
  1135  			}
  1136  			ksMap[ks.Cluster.Id] = append(ksMap[ks.Cluster.Id], ks.Keyspace.Name)
  1137  		}
  1138  		assert.Equal(t, ksMap, map[string][]string{"test": {"test"}, "other": {"otherks"}}, "actor %+v should be permitted to GetKeyspaces", actor)
  1139  	})
  1140  }
  1141  
  1142  func TestGetSchema(t *testing.T) {
  1143  	t.Parallel()
  1144  
  1145  	opts := vtadmin.Options{
  1146  		RBAC: &rbac.Config{
  1147  			Rules: []*struct {
  1148  				Resource string
  1149  				Actions  []string
  1150  				Subjects []string
  1151  				Clusters []string
  1152  			}{
  1153  				{
  1154  					Resource: "Schema",
  1155  					Actions:  []string{"get"},
  1156  					Subjects: []string{"user:allowed"},
  1157  					Clusters: []string{"*"},
  1158  				},
  1159  			},
  1160  		},
  1161  	}
  1162  	err := opts.RBAC.Reify()
  1163  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  1164  
  1165  	t.Run("unauthorized actor", func(t *testing.T) {
  1166  		t.Parallel()
  1167  
  1168  		api := vtadmin.NewAPI(testClusters(t), opts)
  1169  		t.Cleanup(func() {
  1170  			if err := api.Close(); err != nil {
  1171  				t.Logf("api did not close cleanly: %s", err.Error())
  1172  			}
  1173  		})
  1174  
  1175  		actor := &rbac.Actor{Name: "other"}
  1176  		ctx := context.Background()
  1177  		if actor != nil {
  1178  			ctx = rbac.NewContext(ctx, actor)
  1179  		}
  1180  
  1181  		resp, err := api.GetSchema(ctx, &vtadminpb.GetSchemaRequest{
  1182  			ClusterId: "test",
  1183  			Keyspace:  "test",
  1184  			Table:     "t1",
  1185  		})
  1186  		require.NoError(t, err)
  1187  		assert.Nil(t, resp, "actor %+v should not be permitted to GetSchema", actor)
  1188  	})
  1189  
  1190  	t.Run("authorized actor", func(t *testing.T) {
  1191  		t.Parallel()
  1192  
  1193  		api := vtadmin.NewAPI(testClusters(t), opts)
  1194  		t.Cleanup(func() {
  1195  			if err := api.Close(); err != nil {
  1196  				t.Logf("api did not close cleanly: %s", err.Error())
  1197  			}
  1198  		})
  1199  
  1200  		actor := &rbac.Actor{Name: "allowed"}
  1201  		ctx := context.Background()
  1202  		if actor != nil {
  1203  			ctx = rbac.NewContext(ctx, actor)
  1204  		}
  1205  
  1206  		resp, err := api.GetSchema(ctx, &vtadminpb.GetSchemaRequest{
  1207  			ClusterId: "test",
  1208  			Keyspace:  "test",
  1209  			Table:     "t1",
  1210  		})
  1211  		require.NoError(t, err)
  1212  		assert.NotNil(t, resp, "actor %+v should be permitted to GetSchema", actor)
  1213  	})
  1214  }
  1215  
  1216  func TestGetSchemas(t *testing.T) {
  1217  	t.Parallel()
  1218  
  1219  	opts := vtadmin.Options{
  1220  		RBAC: &rbac.Config{
  1221  			Rules: []*struct {
  1222  				Resource string
  1223  				Actions  []string
  1224  				Subjects []string
  1225  				Clusters []string
  1226  			}{
  1227  				{
  1228  					Resource: "Schema",
  1229  					Actions:  []string{"get"},
  1230  					Subjects: []string{"user:allowed-all"},
  1231  					Clusters: []string{"*"},
  1232  				},
  1233  				{
  1234  					Resource: "Schema",
  1235  					Actions:  []string{"get"},
  1236  					Subjects: []string{"user:allowed-other"},
  1237  					Clusters: []string{"other"},
  1238  				},
  1239  			},
  1240  		},
  1241  	}
  1242  	err := opts.RBAC.Reify()
  1243  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  1244  
  1245  	t.Run("unauthorized actor", func(t *testing.T) {
  1246  		t.Parallel()
  1247  
  1248  		api := vtadmin.NewAPI(testClusters(t), opts)
  1249  		t.Cleanup(func() {
  1250  			if err := api.Close(); err != nil {
  1251  				t.Logf("api did not close cleanly: %s", err.Error())
  1252  			}
  1253  		})
  1254  
  1255  		actor := &rbac.Actor{Name: "unauthorized"}
  1256  		ctx := context.Background()
  1257  		if actor != nil {
  1258  			ctx = rbac.NewContext(ctx, actor)
  1259  		}
  1260  
  1261  		resp, err := api.GetSchemas(ctx, &vtadminpb.GetSchemasRequest{})
  1262  		assert.NoError(t, err)
  1263  		assert.Empty(t, resp.Schemas, "actor %+v should not be permitted to GetSchemas", actor)
  1264  	})
  1265  
  1266  	t.Run("partial access", func(t *testing.T) {
  1267  		t.Parallel()
  1268  
  1269  		api := vtadmin.NewAPI(testClusters(t), opts)
  1270  		t.Cleanup(func() {
  1271  			if err := api.Close(); err != nil {
  1272  				t.Logf("api did not close cleanly: %s", err.Error())
  1273  			}
  1274  		})
  1275  
  1276  		actor := &rbac.Actor{Name: "allowed-other"}
  1277  		ctx := context.Background()
  1278  		if actor != nil {
  1279  			ctx = rbac.NewContext(ctx, actor)
  1280  		}
  1281  
  1282  		resp, _ := api.GetSchemas(ctx, &vtadminpb.GetSchemasRequest{})
  1283  		assert.NotEmpty(t, resp.Schemas, "actor %+v should be permitted to GetSchemas", actor)
  1284  		schemaMap := map[string][]string{}
  1285  		for _, schema := range resp.Schemas {
  1286  			if _, ok := schemaMap[schema.Cluster.Id]; !ok {
  1287  				schemaMap[schema.Cluster.Id] = []string{}
  1288  			}
  1289  			schemaMap[schema.Cluster.Id] = append(schemaMap[schema.Cluster.Id], schema.Keyspace)
  1290  		}
  1291  		assert.Equal(t, schemaMap, map[string][]string{"other": {"otherks"}}, "actor %+v should be permitted to GetSchemas", actor)
  1292  	})
  1293  
  1294  	t.Run("full access", func(t *testing.T) {
  1295  		t.Parallel()
  1296  
  1297  		api := vtadmin.NewAPI(testClusters(t), opts)
  1298  		t.Cleanup(func() {
  1299  			if err := api.Close(); err != nil {
  1300  				t.Logf("api did not close cleanly: %s", err.Error())
  1301  			}
  1302  		})
  1303  
  1304  		actor := &rbac.Actor{Name: "allowed-all"}
  1305  		ctx := context.Background()
  1306  		if actor != nil {
  1307  			ctx = rbac.NewContext(ctx, actor)
  1308  		}
  1309  
  1310  		resp, _ := api.GetSchemas(ctx, &vtadminpb.GetSchemasRequest{})
  1311  		assert.NotEmpty(t, resp.Schemas, "actor %+v should be permitted to GetSchemas", actor)
  1312  		schemaMap := map[string][]string{}
  1313  		for _, schema := range resp.Schemas {
  1314  			if _, ok := schemaMap[schema.Cluster.Id]; !ok {
  1315  				schemaMap[schema.Cluster.Id] = []string{}
  1316  			}
  1317  			schemaMap[schema.Cluster.Id] = append(schemaMap[schema.Cluster.Id], schema.Keyspace)
  1318  		}
  1319  		assert.Equal(t, schemaMap, map[string][]string{"test": {"test"}, "other": {"otherks"}}, "actor %+v should be permitted to GetSchemas", actor)
  1320  	})
  1321  }
  1322  
  1323  func TestGetShardReplicationPositions(t *testing.T) {
  1324  	t.Parallel()
  1325  
  1326  	opts := vtadmin.Options{
  1327  		RBAC: &rbac.Config{
  1328  			Rules: []*struct {
  1329  				Resource string
  1330  				Actions  []string
  1331  				Subjects []string
  1332  				Clusters []string
  1333  			}{
  1334  				{
  1335  					Resource: "ShardReplicationPosition",
  1336  					Actions:  []string{"get"},
  1337  					Subjects: []string{"user:allowed-all"},
  1338  					Clusters: []string{"*"},
  1339  				},
  1340  				{
  1341  					Resource: "ShardReplicationPosition",
  1342  					Actions:  []string{"get"},
  1343  					Subjects: []string{"user:allowed-other"},
  1344  					Clusters: []string{"other"},
  1345  				},
  1346  			},
  1347  		},
  1348  	}
  1349  	err := opts.RBAC.Reify()
  1350  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  1351  
  1352  	api := vtadmin.NewAPI(testClusters(t), opts)
  1353  	t.Cleanup(func() {
  1354  		if err := api.Close(); err != nil {
  1355  			t.Logf("api did not close cleanly: %s", err.Error())
  1356  		}
  1357  	})
  1358  
  1359  	t.Run("unauthorized actor", func(t *testing.T) {
  1360  		t.Parallel()
  1361  
  1362  		actor := &rbac.Actor{Name: "unauthorized"}
  1363  		ctx := context.Background()
  1364  		if actor != nil {
  1365  			ctx = rbac.NewContext(ctx, actor)
  1366  		}
  1367  
  1368  		resp, err := api.GetShardReplicationPositions(ctx, &vtadminpb.GetShardReplicationPositionsRequest{})
  1369  		assert.NoError(t, err)
  1370  		assert.Empty(t, resp.ReplicationPositions, "actor %+v should not be permitted to GetShardReplicationPositions", actor)
  1371  	})
  1372  
  1373  	t.Run("partial access", func(t *testing.T) {
  1374  		t.Parallel()
  1375  
  1376  		actor := &rbac.Actor{Name: "allowed-other"}
  1377  		ctx := context.Background()
  1378  		if actor != nil {
  1379  			ctx = rbac.NewContext(ctx, actor)
  1380  		}
  1381  
  1382  		resp, _ := api.GetShardReplicationPositions(ctx, &vtadminpb.GetShardReplicationPositionsRequest{})
  1383  		assert.NotEmpty(t, resp.ReplicationPositions, "actor %+v should be permitted to GetShardReplicationPositions", actor)
  1384  		posMap := map[string][]string{}
  1385  		for _, pos := range resp.ReplicationPositions {
  1386  			if _, ok := posMap[pos.Cluster.Id]; !ok {
  1387  				posMap[pos.Cluster.Id] = []string{}
  1388  			}
  1389  			posMap[pos.Cluster.Id] = append(posMap[pos.Cluster.Id], fmt.Sprintf("%s/%s", pos.Keyspace, pos.Shard))
  1390  		}
  1391  		assert.Equal(t, posMap, map[string][]string{"other": {"otherks/-"}}, "actor %+v should be permitted to GetShardReplicationPositions", actor)
  1392  	})
  1393  
  1394  	t.Run("full access", func(t *testing.T) {
  1395  		t.Parallel()
  1396  
  1397  		actor := &rbac.Actor{Name: "allowed-all"}
  1398  		ctx := context.Background()
  1399  		if actor != nil {
  1400  			ctx = rbac.NewContext(ctx, actor)
  1401  		}
  1402  
  1403  		resp, _ := api.GetShardReplicationPositions(ctx, &vtadminpb.GetShardReplicationPositionsRequest{})
  1404  		assert.NotEmpty(t, resp.ReplicationPositions, "actor %+v should be permitted to GetShardReplicationPositions", actor)
  1405  		posMap := map[string][]string{}
  1406  		for _, pos := range resp.ReplicationPositions {
  1407  			if _, ok := posMap[pos.Cluster.Id]; !ok {
  1408  				posMap[pos.Cluster.Id] = []string{}
  1409  			}
  1410  			posMap[pos.Cluster.Id] = append(posMap[pos.Cluster.Id], fmt.Sprintf("%s/%s", pos.Keyspace, pos.Shard))
  1411  		}
  1412  		assert.Equal(t, posMap, map[string][]string{"test": {"test/-"}, "other": {"otherks/-"}}, "actor %+v should be permitted to GetShardReplicationPositions", actor)
  1413  	})
  1414  }
  1415  
  1416  func TestGetSrvVSchema(t *testing.T) {
  1417  	t.Parallel()
  1418  
  1419  	opts := vtadmin.Options{
  1420  		RBAC: &rbac.Config{
  1421  			Rules: []*struct {
  1422  				Resource string
  1423  				Actions  []string
  1424  				Subjects []string
  1425  				Clusters []string
  1426  			}{
  1427  				{
  1428  					Resource: "SrvVSchema",
  1429  					Actions:  []string{"get"},
  1430  					Subjects: []string{"user:allowed"},
  1431  					Clusters: []string{"*"},
  1432  				},
  1433  			},
  1434  		},
  1435  	}
  1436  	err := opts.RBAC.Reify()
  1437  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  1438  
  1439  	api := vtadmin.NewAPI(testClusters(t), opts)
  1440  	t.Cleanup(func() {
  1441  		if err := api.Close(); err != nil {
  1442  			t.Logf("api did not close cleanly: %s", err.Error())
  1443  		}
  1444  	})
  1445  
  1446  	t.Run("unauthorized actor", func(t *testing.T) {
  1447  		t.Parallel()
  1448  
  1449  		actor := &rbac.Actor{Name: "other"}
  1450  		ctx := context.Background()
  1451  		if actor != nil {
  1452  			ctx = rbac.NewContext(ctx, actor)
  1453  		}
  1454  
  1455  		resp, err := api.GetSrvVSchema(ctx, &vtadminpb.GetSrvVSchemaRequest{
  1456  			ClusterId: "test",
  1457  			Cell:      "zone1",
  1458  		})
  1459  		require.NoError(t, err)
  1460  		assert.Nil(t, resp, "actor %+v should not be permitted to GetSrvVSchema", actor)
  1461  	})
  1462  
  1463  	t.Run("authorized actor", func(t *testing.T) {
  1464  		t.Parallel()
  1465  
  1466  		actor := &rbac.Actor{Name: "allowed"}
  1467  		ctx := context.Background()
  1468  		if actor != nil {
  1469  			ctx = rbac.NewContext(ctx, actor)
  1470  		}
  1471  
  1472  		resp, err := api.GetSrvVSchema(ctx, &vtadminpb.GetSrvVSchemaRequest{
  1473  			ClusterId: "test",
  1474  			Cell:      "zone1",
  1475  		})
  1476  		require.NoError(t, err)
  1477  		assert.NotNil(t, resp, "actor %+v should be permitted to GetSrvVSchema", actor)
  1478  	})
  1479  }
  1480  
  1481  func TestGetSrvVSchemas(t *testing.T) {
  1482  	t.Parallel()
  1483  
  1484  	opts := vtadmin.Options{
  1485  		RBAC: &rbac.Config{
  1486  			Rules: []*struct {
  1487  				Resource string
  1488  				Actions  []string
  1489  				Subjects []string
  1490  				Clusters []string
  1491  			}{
  1492  				{
  1493  					Resource: "SrvVSchema",
  1494  					Actions:  []string{"get"},
  1495  					Subjects: []string{"user:allowed-all"},
  1496  					Clusters: []string{"*"},
  1497  				},
  1498  				{
  1499  					Resource: "SrvVSchema",
  1500  					Actions:  []string{"get"},
  1501  					Subjects: []string{"user:allowed-other"},
  1502  					Clusters: []string{"other"},
  1503  				},
  1504  			},
  1505  		},
  1506  	}
  1507  	err := opts.RBAC.Reify()
  1508  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  1509  
  1510  	api := vtadmin.NewAPI(testClusters(t), opts)
  1511  	t.Cleanup(func() {
  1512  		if err := api.Close(); err != nil {
  1513  			t.Logf("api did not close cleanly: %s", err.Error())
  1514  		}
  1515  	})
  1516  
  1517  	t.Run("unauthorized actor", func(t *testing.T) {
  1518  		t.Parallel()
  1519  
  1520  		actor := &rbac.Actor{Name: "unauthorized"}
  1521  		ctx := context.Background()
  1522  		if actor != nil {
  1523  			ctx = rbac.NewContext(ctx, actor)
  1524  		}
  1525  
  1526  		resp, err := api.GetSrvVSchemas(ctx, &vtadminpb.GetSrvVSchemasRequest{})
  1527  		require.NoError(t, err)
  1528  		assert.Nil(t, resp.SrvVSchemas, "actor %+v should not be permitted to GetSrvVSchemas", actor)
  1529  	})
  1530  
  1531  	t.Run("partial access", func(t *testing.T) {
  1532  		t.Parallel()
  1533  
  1534  		actor := &rbac.Actor{Name: "allowed-other"}
  1535  		ctx := context.Background()
  1536  		if actor != nil {
  1537  			ctx = rbac.NewContext(ctx, actor)
  1538  		}
  1539  
  1540  		resp, _ := api.GetSrvVSchemas(ctx, &vtadminpb.GetSrvVSchemasRequest{})
  1541  		assert.NotEmpty(t, resp.SrvVSchemas, "actor %+v should be permitted to GetSrvVSchemas", actor)
  1542  		clusterCells := map[string][]string{}
  1543  		for _, svs := range resp.SrvVSchemas {
  1544  			if _, ok := clusterCells[svs.Cluster.Id]; !ok {
  1545  				clusterCells[svs.Cluster.Id] = []string{}
  1546  			}
  1547  			clusterCells[svs.Cluster.Id] = append(clusterCells[svs.Cluster.Id], svs.Cell)
  1548  		}
  1549  		assert.Equal(t, clusterCells, map[string][]string{"other": {"other1"}}, "actor %+v should be permitted to GetSrvVSchemas", actor)
  1550  	})
  1551  
  1552  	t.Run("full access", func(t *testing.T) {
  1553  		t.Parallel()
  1554  
  1555  		actor := &rbac.Actor{Name: "allowed-all"}
  1556  		ctx := context.Background()
  1557  		if actor != nil {
  1558  			ctx = rbac.NewContext(ctx, actor)
  1559  		}
  1560  
  1561  		resp, _ := api.GetSrvVSchemas(ctx, &vtadminpb.GetSrvVSchemasRequest{})
  1562  		assert.NotEmpty(t, resp.SrvVSchemas, "actor %+v should be permitted to GetSrvVSchemas", actor)
  1563  		clusterCells := map[string][]string{}
  1564  		for _, svs := range resp.SrvVSchemas {
  1565  			if _, ok := clusterCells[svs.Cluster.Id]; !ok {
  1566  				clusterCells[svs.Cluster.Id] = []string{}
  1567  			}
  1568  			clusterCells[svs.Cluster.Id] = append(clusterCells[svs.Cluster.Id], svs.Cell)
  1569  		}
  1570  		assert.Equal(t, clusterCells, map[string][]string{"test": {"zone1"}, "other": {"other1"}}, "actor %+v should be permitted to GetSrvVSchemas", actor)
  1571  	})
  1572  }
  1573  
  1574  func TestGetTablet(t *testing.T) {
  1575  	t.Parallel()
  1576  
  1577  	opts := vtadmin.Options{
  1578  		RBAC: &rbac.Config{
  1579  			Rules: []*struct {
  1580  				Resource string
  1581  				Actions  []string
  1582  				Subjects []string
  1583  				Clusters []string
  1584  			}{
  1585  				{
  1586  					Resource: "Tablet",
  1587  					Actions:  []string{"get"},
  1588  					Subjects: []string{"user:allowed"},
  1589  					Clusters: []string{"*"},
  1590  				},
  1591  			},
  1592  		},
  1593  	}
  1594  	err := opts.RBAC.Reify()
  1595  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  1596  
  1597  	api := vtadmin.NewAPI(testClusters(t), opts)
  1598  	t.Cleanup(func() {
  1599  		if err := api.Close(); err != nil {
  1600  			t.Logf("api did not close cleanly: %s", err.Error())
  1601  		}
  1602  	})
  1603  
  1604  	t.Run("unauthorized actor", func(t *testing.T) {
  1605  		t.Parallel()
  1606  
  1607  		actor := &rbac.Actor{Name: "other"}
  1608  		ctx := context.Background()
  1609  		if actor != nil {
  1610  			ctx = rbac.NewContext(ctx, actor)
  1611  		}
  1612  
  1613  		resp, err := api.GetTablet(ctx, &vtadminpb.GetTabletRequest{
  1614  			Alias: &topodatapb.TabletAlias{
  1615  				Cell: "zone1",
  1616  				Uid:  100,
  1617  			},
  1618  		})
  1619  		assert.Error(t, err, "actor %+v should not be permitted to GetTablet", actor)
  1620  		assert.Nil(t, resp, "actor %+v should not be permitted to GetTablet", actor)
  1621  	})
  1622  
  1623  	t.Run("authorized actor", func(t *testing.T) {
  1624  		t.Parallel()
  1625  
  1626  		actor := &rbac.Actor{Name: "allowed"}
  1627  		ctx := context.Background()
  1628  		if actor != nil {
  1629  			ctx = rbac.NewContext(ctx, actor)
  1630  		}
  1631  
  1632  		resp, err := api.GetTablet(ctx, &vtadminpb.GetTabletRequest{
  1633  			Alias: &topodatapb.TabletAlias{
  1634  				Cell: "zone1",
  1635  				Uid:  100,
  1636  			},
  1637  		})
  1638  		require.NoError(t, err)
  1639  		assert.NotNil(t, resp, "actor %+v should be permitted to GetTablet", actor)
  1640  	})
  1641  }
  1642  
  1643  func TestGetTablets(t *testing.T) {
  1644  	t.Parallel()
  1645  
  1646  	opts := vtadmin.Options{
  1647  		RBAC: &rbac.Config{
  1648  			Rules: []*struct {
  1649  				Resource string
  1650  				Actions  []string
  1651  				Subjects []string
  1652  				Clusters []string
  1653  			}{
  1654  				{
  1655  					Resource: "Tablet",
  1656  					Actions:  []string{"get"},
  1657  					Subjects: []string{"user:allowed-all"},
  1658  					Clusters: []string{"*"},
  1659  				},
  1660  				{
  1661  					Resource: "Tablet",
  1662  					Actions:  []string{"get"},
  1663  					Subjects: []string{"user:allowed-other"},
  1664  					Clusters: []string{"other"},
  1665  				},
  1666  			},
  1667  		},
  1668  	}
  1669  	err := opts.RBAC.Reify()
  1670  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  1671  
  1672  	api := vtadmin.NewAPI(testClusters(t), opts)
  1673  	t.Cleanup(func() {
  1674  		if err := api.Close(); err != nil {
  1675  			t.Logf("api did not close cleanly: %s", err.Error())
  1676  		}
  1677  	})
  1678  
  1679  	t.Run("unauthorized actor", func(t *testing.T) {
  1680  		t.Parallel()
  1681  
  1682  		actor := &rbac.Actor{Name: "unauthorized"}
  1683  		ctx := context.Background()
  1684  		if actor != nil {
  1685  			ctx = rbac.NewContext(ctx, actor)
  1686  		}
  1687  
  1688  		resp, err := api.GetTablets(ctx, &vtadminpb.GetTabletsRequest{})
  1689  		require.NoError(t, err)
  1690  		assert.Nil(t, resp.Tablets, "actor %+v should not be permitted to GetTablets", actor)
  1691  	})
  1692  
  1693  	t.Run("partial access", func(t *testing.T) {
  1694  		t.Parallel()
  1695  
  1696  		actor := &rbac.Actor{Name: "allowed-other"}
  1697  		ctx := context.Background()
  1698  		if actor != nil {
  1699  			ctx = rbac.NewContext(ctx, actor)
  1700  		}
  1701  
  1702  		resp, _ := api.GetTablets(ctx, &vtadminpb.GetTabletsRequest{})
  1703  		assert.NotEmpty(t, resp.Tablets, "actor %+v should be permitted to GetTablets", actor)
  1704  		clusterAliases := map[string][]string{}
  1705  		for _, tablet := range resp.Tablets {
  1706  			if _, ok := clusterAliases[tablet.Cluster.Id]; !ok {
  1707  				clusterAliases[tablet.Cluster.Id] = []string{}
  1708  			}
  1709  			clusterAliases[tablet.Cluster.Id] = append(clusterAliases[tablet.Cluster.Id], topoproto.TabletAliasString(tablet.Tablet.Alias))
  1710  		}
  1711  		assert.Equal(t, clusterAliases, map[string][]string{"other": {"other1-0000000100"}}, "actor %+v should be permitted to GetTablets", actor)
  1712  	})
  1713  
  1714  	t.Run("full access", func(t *testing.T) {
  1715  		t.Parallel()
  1716  
  1717  		actor := &rbac.Actor{Name: "allowed-all"}
  1718  		ctx := context.Background()
  1719  		if actor != nil {
  1720  			ctx = rbac.NewContext(ctx, actor)
  1721  		}
  1722  
  1723  		resp, _ := api.GetTablets(ctx, &vtadminpb.GetTabletsRequest{})
  1724  		assert.NotEmpty(t, resp.Tablets, "actor %+v should be permitted to GetTablets", actor)
  1725  		clusterAliases := map[string][]string{}
  1726  		for _, tablet := range resp.Tablets {
  1727  			if _, ok := clusterAliases[tablet.Cluster.Id]; !ok {
  1728  				clusterAliases[tablet.Cluster.Id] = []string{}
  1729  			}
  1730  			clusterAliases[tablet.Cluster.Id] = append(clusterAliases[tablet.Cluster.Id], topoproto.TabletAliasString(tablet.Tablet.Alias))
  1731  		}
  1732  		assert.Equal(t, clusterAliases, map[string][]string{"test": {"zone1-0000000100"}, "other": {"other1-0000000100"}}, "actor %+v should be permitted to GetTablets", actor)
  1733  	})
  1734  }
  1735  
  1736  func TestGetVSchema(t *testing.T) {
  1737  	t.Parallel()
  1738  
  1739  	opts := vtadmin.Options{
  1740  		RBAC: &rbac.Config{
  1741  			Rules: []*struct {
  1742  				Resource string
  1743  				Actions  []string
  1744  				Subjects []string
  1745  				Clusters []string
  1746  			}{
  1747  				{
  1748  					Resource: "VSchema",
  1749  					Actions:  []string{"get"},
  1750  					Subjects: []string{"user:allowed"},
  1751  					Clusters: []string{"*"},
  1752  				},
  1753  			},
  1754  		},
  1755  	}
  1756  	err := opts.RBAC.Reify()
  1757  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  1758  
  1759  	api := vtadmin.NewAPI(testClusters(t), opts)
  1760  	t.Cleanup(func() {
  1761  		if err := api.Close(); err != nil {
  1762  			t.Logf("api did not close cleanly: %s", err.Error())
  1763  		}
  1764  	})
  1765  
  1766  	t.Run("unauthorized actor", func(t *testing.T) {
  1767  		t.Parallel()
  1768  
  1769  		actor := &rbac.Actor{Name: "other"}
  1770  		ctx := context.Background()
  1771  		if actor != nil {
  1772  			ctx = rbac.NewContext(ctx, actor)
  1773  		}
  1774  
  1775  		resp, err := api.GetVSchema(ctx, &vtadminpb.GetVSchemaRequest{
  1776  			ClusterId: "test",
  1777  			Keyspace:  "test",
  1778  		})
  1779  		require.NoError(t, err)
  1780  		assert.Nil(t, resp, "actor %+v should not be permitted to GetVSchema", actor)
  1781  	})
  1782  
  1783  	t.Run("authorized actor", func(t *testing.T) {
  1784  		t.Parallel()
  1785  
  1786  		actor := &rbac.Actor{Name: "allowed"}
  1787  		ctx := context.Background()
  1788  		if actor != nil {
  1789  			ctx = rbac.NewContext(ctx, actor)
  1790  		}
  1791  
  1792  		resp, err := api.GetVSchema(ctx, &vtadminpb.GetVSchemaRequest{
  1793  			ClusterId: "test",
  1794  			Keyspace:  "test",
  1795  		})
  1796  		require.NoError(t, err)
  1797  		assert.NotNil(t, resp, "actor %+v should be permitted to GetVSchema", actor)
  1798  	})
  1799  }
  1800  
  1801  func TestGetVSchemas(t *testing.T) {
  1802  	t.Parallel()
  1803  
  1804  	opts := vtadmin.Options{
  1805  		RBAC: &rbac.Config{
  1806  			Rules: []*struct {
  1807  				Resource string
  1808  				Actions  []string
  1809  				Subjects []string
  1810  				Clusters []string
  1811  			}{
  1812  				{
  1813  					Resource: "VSchema",
  1814  					Actions:  []string{"get"},
  1815  					Subjects: []string{"user:allowed-all"},
  1816  					Clusters: []string{"*"},
  1817  				},
  1818  				{
  1819  					Resource: "VSchema",
  1820  					Actions:  []string{"get"},
  1821  					Subjects: []string{"user:allowed-other"},
  1822  					Clusters: []string{"other"},
  1823  				},
  1824  			},
  1825  		},
  1826  	}
  1827  	err := opts.RBAC.Reify()
  1828  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  1829  
  1830  	api := vtadmin.NewAPI(testClusters(t), opts)
  1831  	t.Cleanup(func() {
  1832  		if err := api.Close(); err != nil {
  1833  			t.Logf("api did not close cleanly: %s", err.Error())
  1834  		}
  1835  	})
  1836  
  1837  	t.Run("unauthorized actor", func(t *testing.T) {
  1838  		t.Parallel()
  1839  
  1840  		actor := &rbac.Actor{Name: "unauthorized"}
  1841  		ctx := context.Background()
  1842  		if actor != nil {
  1843  			ctx = rbac.NewContext(ctx, actor)
  1844  		}
  1845  
  1846  		resp, err := api.GetVSchemas(ctx, &vtadminpb.GetVSchemasRequest{})
  1847  		require.NoError(t, err)
  1848  		assert.Nil(t, resp.VSchemas, "actor %+v should not be permitted to GetVSchemas", actor)
  1849  	})
  1850  
  1851  	t.Run("partial access", func(t *testing.T) {
  1852  		t.Parallel()
  1853  
  1854  		actor := &rbac.Actor{Name: "allowed-other"}
  1855  		ctx := context.Background()
  1856  		if actor != nil {
  1857  			ctx = rbac.NewContext(ctx, actor)
  1858  		}
  1859  
  1860  		resp, _ := api.GetVSchemas(ctx, &vtadminpb.GetVSchemasRequest{})
  1861  		assert.NotEmpty(t, resp.VSchemas, "actor %+v should be permitted to GetVSchemas", actor)
  1862  		clusterKeyspaces := map[string][]string{}
  1863  		for _, vs := range resp.VSchemas {
  1864  			if _, ok := clusterKeyspaces[vs.Cluster.Id]; !ok {
  1865  				clusterKeyspaces[vs.Cluster.Id] = []string{}
  1866  			}
  1867  			clusterKeyspaces[vs.Cluster.Id] = append(clusterKeyspaces[vs.Cluster.Id], vs.Name)
  1868  		}
  1869  		assert.Equal(t, clusterKeyspaces, map[string][]string{"other": {"otherks"}}, "actor %+v should be permitted to GetVSchemas", actor)
  1870  	})
  1871  
  1872  	t.Run("full access", func(t *testing.T) {
  1873  		t.Parallel()
  1874  
  1875  		actor := &rbac.Actor{Name: "allowed-all"}
  1876  		ctx := context.Background()
  1877  		if actor != nil {
  1878  			ctx = rbac.NewContext(ctx, actor)
  1879  		}
  1880  
  1881  		resp, _ := api.GetVSchemas(ctx, &vtadminpb.GetVSchemasRequest{})
  1882  		assert.NotEmpty(t, resp.VSchemas, "actor %+v should be permitted to GetVSchemas", actor)
  1883  		clusterKeyspaces := map[string][]string{}
  1884  		for _, vs := range resp.VSchemas {
  1885  			if _, ok := clusterKeyspaces[vs.Cluster.Id]; !ok {
  1886  				clusterKeyspaces[vs.Cluster.Id] = []string{}
  1887  			}
  1888  			clusterKeyspaces[vs.Cluster.Id] = append(clusterKeyspaces[vs.Cluster.Id], vs.Name)
  1889  		}
  1890  		assert.Equal(t, clusterKeyspaces, map[string][]string{"test": {"test"}, "other": {"otherks"}}, "actor %+v should be permitted to GetVSchemas", actor)
  1891  	})
  1892  }
  1893  
  1894  func TestGetVtctlds(t *testing.T) {
  1895  	t.Parallel()
  1896  
  1897  	opts := vtadmin.Options{
  1898  		RBAC: &rbac.Config{
  1899  			Rules: []*struct {
  1900  				Resource string
  1901  				Actions  []string
  1902  				Subjects []string
  1903  				Clusters []string
  1904  			}{
  1905  				{
  1906  					Resource: "Vtctld",
  1907  					Actions:  []string{"get"},
  1908  					Subjects: []string{"user:allowed-all"},
  1909  					Clusters: []string{"*"},
  1910  				},
  1911  				{
  1912  					Resource: "Vtctld",
  1913  					Actions:  []string{"get"},
  1914  					Subjects: []string{"user:allowed-other"},
  1915  					Clusters: []string{"other"},
  1916  				},
  1917  			},
  1918  		},
  1919  	}
  1920  	err := opts.RBAC.Reify()
  1921  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  1922  
  1923  	t.Run("unauthorized actor", func(t *testing.T) {
  1924  		t.Parallel()
  1925  
  1926  		api := vtadmin.NewAPI(testClusters(t), opts)
  1927  		t.Cleanup(func() {
  1928  			if err := api.Close(); err != nil {
  1929  				t.Logf("api did not close cleanly: %s", err.Error())
  1930  			}
  1931  		})
  1932  
  1933  		actor := &rbac.Actor{Name: "unauthorized"}
  1934  		ctx := context.Background()
  1935  		if actor != nil {
  1936  			ctx = rbac.NewContext(ctx, actor)
  1937  		}
  1938  
  1939  		resp, err := api.GetVtctlds(ctx, &vtadminpb.GetVtctldsRequest{})
  1940  		assert.NoError(t, err)
  1941  		assert.Empty(t, resp.Vtctlds, "actor %+v should not be permitted to GetVtctlds", actor)
  1942  	})
  1943  
  1944  	t.Run("partial access", func(t *testing.T) {
  1945  		t.Parallel()
  1946  
  1947  		api := vtadmin.NewAPI(testClusters(t), opts)
  1948  		t.Cleanup(func() {
  1949  			if err := api.Close(); err != nil {
  1950  				t.Logf("api did not close cleanly: %s", err.Error())
  1951  			}
  1952  		})
  1953  
  1954  		actor := &rbac.Actor{Name: "allowed-other"}
  1955  		ctx := context.Background()
  1956  		if actor != nil {
  1957  			ctx = rbac.NewContext(ctx, actor)
  1958  		}
  1959  
  1960  		resp, _ := api.GetVtctlds(ctx, &vtadminpb.GetVtctldsRequest{})
  1961  		assert.NotEmpty(t, resp.Vtctlds, "actor %+v should be permitted to GetVtctlds", actor)
  1962  		// testutil.BuildCluster creates exactly one gate per cluster
  1963  		assert.Len(t, resp.Vtctlds, 1, "actor %+v should only be able to see Vtctld from cluster 'other'", actor)
  1964  	})
  1965  
  1966  	t.Run("full access", func(t *testing.T) {
  1967  		t.Parallel()
  1968  
  1969  		api := vtadmin.NewAPI(testClusters(t), opts)
  1970  		t.Cleanup(func() {
  1971  			if err := api.Close(); err != nil {
  1972  				t.Logf("api did not close cleanly: %s", err.Error())
  1973  			}
  1974  		})
  1975  
  1976  		actor := &rbac.Actor{Name: "allowed-all"}
  1977  		ctx := context.Background()
  1978  		if actor != nil {
  1979  			ctx = rbac.NewContext(ctx, actor)
  1980  		}
  1981  
  1982  		resp, _ := api.GetVtctlds(ctx, &vtadminpb.GetVtctldsRequest{})
  1983  		assert.NotEmpty(t, resp.Vtctlds, "actor %+v should be permitted to GetVtctlds", actor)
  1984  		// testutil.BuildCluster creates exactly one gate per cluster
  1985  		assert.Len(t, resp.Vtctlds, 2, "actor %+v should be able to see Vtctld from all clusters", actor)
  1986  	})
  1987  }
  1988  
  1989  func TestGetWorkflow(t *testing.T) {
  1990  	t.Parallel()
  1991  
  1992  	opts := vtadmin.Options{
  1993  		RBAC: &rbac.Config{
  1994  			Rules: []*struct {
  1995  				Resource string
  1996  				Actions  []string
  1997  				Subjects []string
  1998  				Clusters []string
  1999  			}{
  2000  				{
  2001  					Resource: "Workflow",
  2002  					Actions:  []string{"get"},
  2003  					Subjects: []string{"user:allowed"},
  2004  					Clusters: []string{"*"},
  2005  				},
  2006  			},
  2007  		},
  2008  	}
  2009  	err := opts.RBAC.Reify()
  2010  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  2011  
  2012  	api := vtadmin.NewAPI(testClusters(t), opts)
  2013  	t.Cleanup(func() {
  2014  		if err := api.Close(); err != nil {
  2015  			t.Logf("api did not close cleanly: %s", err.Error())
  2016  		}
  2017  	})
  2018  
  2019  	t.Run("unauthorized actor", func(t *testing.T) {
  2020  		t.Parallel()
  2021  
  2022  		actor := &rbac.Actor{Name: "other"}
  2023  		ctx := context.Background()
  2024  		if actor != nil {
  2025  			ctx = rbac.NewContext(ctx, actor)
  2026  		}
  2027  
  2028  		resp, err := api.GetWorkflow(ctx, &vtadminpb.GetWorkflowRequest{
  2029  			ClusterId: "test",
  2030  			Keyspace:  "test",
  2031  			Name:      "testworkflow",
  2032  		})
  2033  		require.NoError(t, err)
  2034  		assert.Nil(t, resp, "actor %+v should not be permitted to GetWorkflow", actor)
  2035  	})
  2036  
  2037  	t.Run("authorized actor", func(t *testing.T) {
  2038  		t.Parallel()
  2039  
  2040  		actor := &rbac.Actor{Name: "allowed"}
  2041  		ctx := context.Background()
  2042  		if actor != nil {
  2043  			ctx = rbac.NewContext(ctx, actor)
  2044  		}
  2045  
  2046  		resp, err := api.GetWorkflow(ctx, &vtadminpb.GetWorkflowRequest{
  2047  			ClusterId: "test",
  2048  			Keyspace:  "test",
  2049  			Name:      "testworkflow",
  2050  		})
  2051  		require.NoError(t, err)
  2052  		assert.NotNil(t, resp, "actor %+v should be permitted to GetWorkflow", actor)
  2053  	})
  2054  }
  2055  
  2056  func TestGetWorkflows(t *testing.T) {
  2057  	t.Parallel()
  2058  
  2059  	opts := vtadmin.Options{
  2060  		RBAC: &rbac.Config{
  2061  			Rules: []*struct {
  2062  				Resource string
  2063  				Actions  []string
  2064  				Subjects []string
  2065  				Clusters []string
  2066  			}{
  2067  				{
  2068  					Resource: "Workflow",
  2069  					Actions:  []string{"get"},
  2070  					Subjects: []string{"user:allowed-all"},
  2071  					Clusters: []string{"*"},
  2072  				},
  2073  				{
  2074  					Resource: "Workflow",
  2075  					Actions:  []string{"get"},
  2076  					Subjects: []string{"user:allowed-other"},
  2077  					Clusters: []string{"other"},
  2078  				},
  2079  			},
  2080  		},
  2081  	}
  2082  	err := opts.RBAC.Reify()
  2083  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  2084  
  2085  	api := vtadmin.NewAPI(testClusters(t), opts)
  2086  	t.Cleanup(func() {
  2087  		if err := api.Close(); err != nil {
  2088  			t.Logf("api did not close cleanly: %s", err.Error())
  2089  		}
  2090  	})
  2091  
  2092  	t.Run("unauthorized actor", func(t *testing.T) {
  2093  		t.Parallel()
  2094  
  2095  		actor := &rbac.Actor{Name: "unauthorized"}
  2096  		ctx := context.Background()
  2097  		if actor != nil {
  2098  			ctx = rbac.NewContext(ctx, actor)
  2099  		}
  2100  
  2101  		resp, err := api.GetWorkflows(ctx, &vtadminpb.GetWorkflowsRequest{})
  2102  		require.NoError(t, err)
  2103  		assert.Empty(t, resp.WorkflowsByCluster, "actor %+v should not be permitted to GetWorkflows", actor)
  2104  	})
  2105  
  2106  	t.Run("partial access", func(t *testing.T) {
  2107  		t.Parallel()
  2108  
  2109  		actor := &rbac.Actor{Name: "allowed-other"}
  2110  		ctx := context.Background()
  2111  		if actor != nil {
  2112  			ctx = rbac.NewContext(ctx, actor)
  2113  		}
  2114  
  2115  		resp, _ := api.GetWorkflows(ctx, &vtadminpb.GetWorkflowsRequest{})
  2116  		assert.NotEmpty(t, resp.WorkflowsByCluster, "actor %+v should be permitted to GetWorkflows", actor)
  2117  		assert.Equal(t, resp.WorkflowsByCluster, map[string]*vtadminpb.ClusterWorkflows{"other": {Workflows: []*vtadminpb.Workflow{{Cluster: &vtadminpb.Cluster{Id: "other", Name: "other"}, Keyspace: "otherks", Workflow: &vtctldatapb.Workflow{Name: "otherks_workflow"}}}}}, "actor %+v should be permitted to GetWorkflows", actor)
  2118  	})
  2119  
  2120  	t.Run("full access", func(t *testing.T) {
  2121  		t.Parallel()
  2122  
  2123  		actor := &rbac.Actor{Name: "allowed-all"}
  2124  		ctx := context.Background()
  2125  		if actor != nil {
  2126  			ctx = rbac.NewContext(ctx, actor)
  2127  		}
  2128  
  2129  		resp, _ := api.GetWorkflows(ctx, &vtadminpb.GetWorkflowsRequest{})
  2130  		assert.NotEmpty(t, resp.WorkflowsByCluster, "actor %+v should be permitted to GetWorkflows", actor)
  2131  		assert.Equal(t, resp.WorkflowsByCluster, map[string]*vtadminpb.ClusterWorkflows{"test": {Workflows: []*vtadminpb.Workflow{{Cluster: &vtadminpb.Cluster{Id: "test", Name: "test"}, Keyspace: "test", Workflow: &vtctldatapb.Workflow{Name: "testworkflow"}}}}, "other": {Workflows: []*vtadminpb.Workflow{{Cluster: &vtadminpb.Cluster{Id: "other", Name: "other"}, Keyspace: "otherks", Workflow: &vtctldatapb.Workflow{Name: "otherks_workflow"}}}}}, "actor %+v should be permitted to GetWorkflows", actor)
  2132  	})
  2133  }
  2134  
  2135  func TestPingTablet(t *testing.T) {
  2136  	t.Parallel()
  2137  
  2138  	opts := vtadmin.Options{
  2139  		RBAC: &rbac.Config{
  2140  			Rules: []*struct {
  2141  				Resource string
  2142  				Actions  []string
  2143  				Subjects []string
  2144  				Clusters []string
  2145  			}{
  2146  				{
  2147  					Resource: "Tablet",
  2148  					Actions:  []string{"ping"},
  2149  					Subjects: []string{"user:allowed"},
  2150  					Clusters: []string{"*"},
  2151  				},
  2152  			},
  2153  		},
  2154  	}
  2155  	err := opts.RBAC.Reify()
  2156  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  2157  
  2158  	api := vtadmin.NewAPI(testClusters(t), opts)
  2159  	t.Cleanup(func() {
  2160  		if err := api.Close(); err != nil {
  2161  			t.Logf("api did not close cleanly: %s", err.Error())
  2162  		}
  2163  	})
  2164  
  2165  	t.Run("unauthorized actor", func(t *testing.T) {
  2166  		t.Parallel()
  2167  
  2168  		actor := &rbac.Actor{Name: "other"}
  2169  		ctx := context.Background()
  2170  		if actor != nil {
  2171  			ctx = rbac.NewContext(ctx, actor)
  2172  		}
  2173  
  2174  		resp, err := api.PingTablet(ctx, &vtadminpb.PingTabletRequest{
  2175  			Alias: &topodatapb.TabletAlias{
  2176  				Cell: "zone1",
  2177  				Uid:  100,
  2178  			},
  2179  		})
  2180  		assert.Error(t, err, "actor %+v should not be permitted to PingTablet", actor)
  2181  		assert.Nil(t, resp, "actor %+v should not be permitted to PingTablet", actor)
  2182  	})
  2183  
  2184  	t.Run("authorized actor", func(t *testing.T) {
  2185  		t.Parallel()
  2186  
  2187  		actor := &rbac.Actor{Name: "allowed"}
  2188  		ctx := context.Background()
  2189  		if actor != nil {
  2190  			ctx = rbac.NewContext(ctx, actor)
  2191  		}
  2192  
  2193  		resp, err := api.PingTablet(ctx, &vtadminpb.PingTabletRequest{
  2194  			Alias: &topodatapb.TabletAlias{
  2195  				Cell: "zone1",
  2196  				Uid:  100,
  2197  			},
  2198  		})
  2199  		require.NoError(t, err)
  2200  		assert.NotNil(t, resp, "actor %+v should be permitted to PingTablet", actor)
  2201  	})
  2202  }
  2203  
  2204  func TestPlannedFailoverShard(t *testing.T) {
  2205  	t.Parallel()
  2206  
  2207  	opts := vtadmin.Options{
  2208  		RBAC: &rbac.Config{
  2209  			Rules: []*struct {
  2210  				Resource string
  2211  				Actions  []string
  2212  				Subjects []string
  2213  				Clusters []string
  2214  			}{
  2215  				{
  2216  					Resource: "Shard",
  2217  					Actions:  []string{"planned_failover_shard"},
  2218  					Subjects: []string{"user:allowed"},
  2219  					Clusters: []string{"*"},
  2220  				},
  2221  			},
  2222  		},
  2223  	}
  2224  	err := opts.RBAC.Reify()
  2225  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  2226  
  2227  	api := vtadmin.NewAPI(testClusters(t), opts)
  2228  	t.Cleanup(func() {
  2229  		if err := api.Close(); err != nil {
  2230  			t.Logf("api did not close cleanly: %s", err.Error())
  2231  		}
  2232  	})
  2233  
  2234  	t.Run("unauthorized actor", func(t *testing.T) {
  2235  		t.Parallel()
  2236  
  2237  		actor := &rbac.Actor{Name: "other"}
  2238  		ctx := context.Background()
  2239  		if actor != nil {
  2240  			ctx = rbac.NewContext(ctx, actor)
  2241  		}
  2242  
  2243  		resp, err := api.PlannedFailoverShard(ctx, &vtadminpb.PlannedFailoverShardRequest{
  2244  			ClusterId: "test",
  2245  			Options: &vtctldatapb.PlannedReparentShardRequest{
  2246  				Keyspace: "test",
  2247  				Shard:    "-",
  2248  			},
  2249  		})
  2250  		require.NoError(t, err)
  2251  		assert.Nil(t, resp, "actor %+v should not be permitted to PlannedFailoverShard", actor)
  2252  	})
  2253  
  2254  	t.Run("authorized actor", func(t *testing.T) {
  2255  		t.Parallel()
  2256  
  2257  		actor := &rbac.Actor{Name: "allowed"}
  2258  		ctx := context.Background()
  2259  		if actor != nil {
  2260  			ctx = rbac.NewContext(ctx, actor)
  2261  		}
  2262  
  2263  		resp, err := api.PlannedFailoverShard(ctx, &vtadminpb.PlannedFailoverShardRequest{
  2264  			ClusterId: "test",
  2265  			Options: &vtctldatapb.PlannedReparentShardRequest{
  2266  				Keyspace: "test",
  2267  				Shard:    "-",
  2268  			},
  2269  		})
  2270  		require.NoError(t, err)
  2271  		assert.NotNil(t, resp, "actor %+v should be permitted to PlannedFailoverShard", actor)
  2272  	})
  2273  }
  2274  
  2275  func TestRefreshState(t *testing.T) {
  2276  	t.Parallel()
  2277  
  2278  	opts := vtadmin.Options{
  2279  		RBAC: &rbac.Config{
  2280  			Rules: []*struct {
  2281  				Resource string
  2282  				Actions  []string
  2283  				Subjects []string
  2284  				Clusters []string
  2285  			}{
  2286  				{
  2287  					Resource: "Tablet",
  2288  					Actions:  []string{"put"},
  2289  					Subjects: []string{"user:allowed"},
  2290  					Clusters: []string{"*"},
  2291  				},
  2292  			},
  2293  		},
  2294  	}
  2295  	err := opts.RBAC.Reify()
  2296  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  2297  
  2298  	api := vtadmin.NewAPI(testClusters(t), opts)
  2299  	t.Cleanup(func() {
  2300  		if err := api.Close(); err != nil {
  2301  			t.Logf("api did not close cleanly: %s", err.Error())
  2302  		}
  2303  	})
  2304  
  2305  	t.Run("unauthorized actor", func(t *testing.T) {
  2306  		t.Parallel()
  2307  
  2308  		actor := &rbac.Actor{Name: "other"}
  2309  		ctx := context.Background()
  2310  		if actor != nil {
  2311  			ctx = rbac.NewContext(ctx, actor)
  2312  		}
  2313  
  2314  		resp, err := api.RefreshState(ctx, &vtadminpb.RefreshStateRequest{
  2315  			Alias: &topodatapb.TabletAlias{
  2316  				Cell: "zone1",
  2317  				Uid:  100,
  2318  			},
  2319  		})
  2320  		assert.Error(t, err, "actor %+v should not be permitted to RefreshState", actor)
  2321  		assert.Nil(t, resp, "actor %+v should not be permitted to RefreshState", actor)
  2322  	})
  2323  
  2324  	t.Run("authorized actor", func(t *testing.T) {
  2325  		t.Parallel()
  2326  
  2327  		actor := &rbac.Actor{Name: "allowed"}
  2328  		ctx := context.Background()
  2329  		if actor != nil {
  2330  			ctx = rbac.NewContext(ctx, actor)
  2331  		}
  2332  
  2333  		resp, err := api.RefreshState(ctx, &vtadminpb.RefreshStateRequest{
  2334  			Alias: &topodatapb.TabletAlias{
  2335  				Cell: "zone1",
  2336  				Uid:  100,
  2337  			},
  2338  		})
  2339  		require.NoError(t, err)
  2340  		assert.NotNil(t, resp, "actor %+v should be permitted to RefreshState", actor)
  2341  	})
  2342  }
  2343  
  2344  func TestRefreshTabletReplicationSource(t *testing.T) {
  2345  	t.Parallel()
  2346  
  2347  	opts := vtadmin.Options{
  2348  		RBAC: &rbac.Config{
  2349  			Rules: []*struct {
  2350  				Resource string
  2351  				Actions  []string
  2352  				Subjects []string
  2353  				Clusters []string
  2354  			}{
  2355  				{
  2356  					Resource: "Tablet",
  2357  					Actions:  []string{"refresh_tablet_replication_source"},
  2358  					Subjects: []string{"user:allowed"},
  2359  					Clusters: []string{"*"},
  2360  				},
  2361  			},
  2362  		},
  2363  	}
  2364  	err := opts.RBAC.Reify()
  2365  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  2366  
  2367  	api := vtadmin.NewAPI(testClusters(t), opts)
  2368  	t.Cleanup(func() {
  2369  		if err := api.Close(); err != nil {
  2370  			t.Logf("api did not close cleanly: %s", err.Error())
  2371  		}
  2372  	})
  2373  
  2374  	t.Run("unauthorized actor", func(t *testing.T) {
  2375  		t.Parallel()
  2376  
  2377  		actor := &rbac.Actor{Name: "other"}
  2378  		ctx := context.Background()
  2379  		if actor != nil {
  2380  			ctx = rbac.NewContext(ctx, actor)
  2381  		}
  2382  
  2383  		resp, err := api.RefreshTabletReplicationSource(ctx, &vtadminpb.RefreshTabletReplicationSourceRequest{
  2384  			Alias: &topodatapb.TabletAlias{
  2385  				Cell: "zone1",
  2386  				Uid:  100,
  2387  			},
  2388  		})
  2389  		assert.Error(t, err, "actor %+v should not be permitted to RefreshTabletReplicationSource", actor)
  2390  		assert.Nil(t, resp, "actor %+v should not be permitted to RefreshTabletReplicationSource", actor)
  2391  	})
  2392  
  2393  	t.Run("authorized actor", func(t *testing.T) {
  2394  		t.Parallel()
  2395  
  2396  		actor := &rbac.Actor{Name: "allowed"}
  2397  		ctx := context.Background()
  2398  		if actor != nil {
  2399  			ctx = rbac.NewContext(ctx, actor)
  2400  		}
  2401  
  2402  		resp, err := api.RefreshTabletReplicationSource(ctx, &vtadminpb.RefreshTabletReplicationSourceRequest{
  2403  			Alias: &topodatapb.TabletAlias{
  2404  				Cell: "zone1",
  2405  				Uid:  100,
  2406  			},
  2407  		})
  2408  		require.NoError(t, err)
  2409  		assert.NotNil(t, resp, "actor %+v should be permitted to RefreshTabletReplicationSource", actor)
  2410  	})
  2411  }
  2412  
  2413  func TestReloadSchemas(t *testing.T) {
  2414  	t.Parallel()
  2415  
  2416  	opts := vtadmin.Options{
  2417  		RBAC: &rbac.Config{
  2418  			Rules: []*struct {
  2419  				Resource string
  2420  				Actions  []string
  2421  				Subjects []string
  2422  				Clusters []string
  2423  			}{
  2424  				{
  2425  					Resource: "Schema",
  2426  					Actions:  []string{"reload"},
  2427  					Subjects: []string{"user:allowed-all"},
  2428  					Clusters: []string{"*"},
  2429  				},
  2430  				{
  2431  					Resource: "Schema",
  2432  					Actions:  []string{"reload"},
  2433  					Subjects: []string{"user:allowed-other"},
  2434  					Clusters: []string{"other"},
  2435  				},
  2436  			},
  2437  		},
  2438  	}
  2439  	err := opts.RBAC.Reify()
  2440  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  2441  
  2442  	api := vtadmin.NewAPI(testClusters(t), opts)
  2443  	t.Cleanup(func() {
  2444  		if err := api.Close(); err != nil {
  2445  			t.Logf("api did not close cleanly: %s", err.Error())
  2446  		}
  2447  	})
  2448  
  2449  	t.Run("unauthorized actor", func(t *testing.T) {
  2450  		t.Parallel()
  2451  
  2452  		actor := &rbac.Actor{Name: "unauthorized"}
  2453  		ctx := context.Background()
  2454  		if actor != nil {
  2455  			ctx = rbac.NewContext(ctx, actor)
  2456  		}
  2457  
  2458  		resp, err := api.ReloadSchemas(ctx, &vtadminpb.ReloadSchemasRequest{
  2459  			Keyspaces: []string{
  2460  				"test",
  2461  				"otherks",
  2462  			},
  2463  		})
  2464  		require.NoError(t, err)
  2465  		assert.Empty(t, resp.KeyspaceResults, "actor %+v should not be permitted to ReloadSchemas", actor)
  2466  	})
  2467  
  2468  	t.Run("partial access", func(t *testing.T) {
  2469  		t.Parallel()
  2470  
  2471  		actor := &rbac.Actor{Name: "allowed-other"}
  2472  		ctx := context.Background()
  2473  		if actor != nil {
  2474  			ctx = rbac.NewContext(ctx, actor)
  2475  		}
  2476  
  2477  		resp, _ := api.ReloadSchemas(ctx, &vtadminpb.ReloadSchemasRequest{
  2478  			Keyspaces: []string{
  2479  				"test",
  2480  				"otherks",
  2481  			},
  2482  		})
  2483  		assert.NotEmpty(t, resp.KeyspaceResults, "actor %+v should be permitted to ReloadSchemas", actor)
  2484  	})
  2485  
  2486  	t.Run("full access", func(t *testing.T) {
  2487  		t.Parallel()
  2488  
  2489  		actor := &rbac.Actor{Name: "allowed-all"}
  2490  		ctx := context.Background()
  2491  		if actor != nil {
  2492  			ctx = rbac.NewContext(ctx, actor)
  2493  		}
  2494  
  2495  		resp, _ := api.ReloadSchemas(ctx, &vtadminpb.ReloadSchemasRequest{
  2496  			Keyspaces: []string{
  2497  				"test",
  2498  				"otherks",
  2499  			},
  2500  		})
  2501  		assert.NotEmpty(t, resp.KeyspaceResults, "actor %+v should be permitted to ReloadSchemas", actor)
  2502  	})
  2503  }
  2504  
  2505  func TestRunHealthCheck(t *testing.T) {
  2506  	t.Parallel()
  2507  
  2508  	opts := vtadmin.Options{
  2509  		RBAC: &rbac.Config{
  2510  			Rules: []*struct {
  2511  				Resource string
  2512  				Actions  []string
  2513  				Subjects []string
  2514  				Clusters []string
  2515  			}{
  2516  				{
  2517  					Resource: "Tablet",
  2518  					Actions:  []string{"get"},
  2519  					Subjects: []string{"user:allowed"},
  2520  					Clusters: []string{"*"},
  2521  				},
  2522  			},
  2523  		},
  2524  	}
  2525  	err := opts.RBAC.Reify()
  2526  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  2527  
  2528  	api := vtadmin.NewAPI(testClusters(t), opts)
  2529  	t.Cleanup(func() {
  2530  		if err := api.Close(); err != nil {
  2531  			t.Logf("api did not close cleanly: %s", err.Error())
  2532  		}
  2533  	})
  2534  
  2535  	t.Run("unauthorized actor", func(t *testing.T) {
  2536  		t.Parallel()
  2537  
  2538  		actor := &rbac.Actor{Name: "other"}
  2539  		ctx := context.Background()
  2540  		if actor != nil {
  2541  			ctx = rbac.NewContext(ctx, actor)
  2542  		}
  2543  
  2544  		resp, err := api.RunHealthCheck(ctx, &vtadminpb.RunHealthCheckRequest{
  2545  			Alias: &topodatapb.TabletAlias{
  2546  				Cell: "zone1",
  2547  				Uid:  100,
  2548  			},
  2549  		})
  2550  		assert.Error(t, err, "actor %+v should not be permitted to RunHealthCheck", actor)
  2551  		assert.Nil(t, resp, "actor %+v should not be permitted to RunHealthCheck", actor)
  2552  	})
  2553  
  2554  	t.Run("authorized actor", func(t *testing.T) {
  2555  		t.Parallel()
  2556  
  2557  		actor := &rbac.Actor{Name: "allowed"}
  2558  		ctx := context.Background()
  2559  		if actor != nil {
  2560  			ctx = rbac.NewContext(ctx, actor)
  2561  		}
  2562  
  2563  		resp, err := api.RunHealthCheck(ctx, &vtadminpb.RunHealthCheckRequest{
  2564  			Alias: &topodatapb.TabletAlias{
  2565  				Cell: "zone1",
  2566  				Uid:  100,
  2567  			},
  2568  		})
  2569  		require.NoError(t, err)
  2570  		assert.NotNil(t, resp, "actor %+v should be permitted to RunHealthCheck", actor)
  2571  	})
  2572  }
  2573  
  2574  func TestSetReadOnly(t *testing.T) {
  2575  	t.Parallel()
  2576  
  2577  	opts := vtadmin.Options{
  2578  		RBAC: &rbac.Config{
  2579  			Rules: []*struct {
  2580  				Resource string
  2581  				Actions  []string
  2582  				Subjects []string
  2583  				Clusters []string
  2584  			}{
  2585  				{
  2586  					Resource: "Tablet",
  2587  					Actions:  []string{"manage_tablet_writability"},
  2588  					Subjects: []string{"user:allowed"},
  2589  					Clusters: []string{"*"},
  2590  				},
  2591  			},
  2592  		},
  2593  	}
  2594  	err := opts.RBAC.Reify()
  2595  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  2596  
  2597  	api := vtadmin.NewAPI(testClusters(t), opts)
  2598  	t.Cleanup(func() {
  2599  		if err := api.Close(); err != nil {
  2600  			t.Logf("api did not close cleanly: %s", err.Error())
  2601  		}
  2602  	})
  2603  
  2604  	t.Run("unauthorized actor", func(t *testing.T) {
  2605  		t.Parallel()
  2606  
  2607  		actor := &rbac.Actor{Name: "other"}
  2608  		ctx := context.Background()
  2609  		if actor != nil {
  2610  			ctx = rbac.NewContext(ctx, actor)
  2611  		}
  2612  
  2613  		resp, err := api.SetReadOnly(ctx, &vtadminpb.SetReadOnlyRequest{
  2614  			Alias: &topodatapb.TabletAlias{
  2615  				Cell: "zone1",
  2616  				Uid:  100,
  2617  			},
  2618  		})
  2619  		assert.Error(t, err, "actor %+v should not be permitted to SetReadOnly", actor)
  2620  		assert.Nil(t, resp, "actor %+v should not be permitted to SetReadOnly", actor)
  2621  	})
  2622  
  2623  	t.Run("authorized actor", func(t *testing.T) {
  2624  		t.Parallel()
  2625  
  2626  		actor := &rbac.Actor{Name: "allowed"}
  2627  		ctx := context.Background()
  2628  		if actor != nil {
  2629  			ctx = rbac.NewContext(ctx, actor)
  2630  		}
  2631  
  2632  		resp, err := api.SetReadOnly(ctx, &vtadminpb.SetReadOnlyRequest{
  2633  			Alias: &topodatapb.TabletAlias{
  2634  				Cell: "zone1",
  2635  				Uid:  100,
  2636  			},
  2637  		})
  2638  		require.NoError(t, err)
  2639  		assert.NotNil(t, resp, "actor %+v should be permitted to SetReadOnly", actor)
  2640  	})
  2641  }
  2642  
  2643  func TestSetReadWrite(t *testing.T) {
  2644  	t.Parallel()
  2645  
  2646  	opts := vtadmin.Options{
  2647  		RBAC: &rbac.Config{
  2648  			Rules: []*struct {
  2649  				Resource string
  2650  				Actions  []string
  2651  				Subjects []string
  2652  				Clusters []string
  2653  			}{
  2654  				{
  2655  					Resource: "Tablet",
  2656  					Actions:  []string{"manage_tablet_writability"},
  2657  					Subjects: []string{"user:allowed"},
  2658  					Clusters: []string{"*"},
  2659  				},
  2660  			},
  2661  		},
  2662  	}
  2663  	err := opts.RBAC.Reify()
  2664  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  2665  
  2666  	api := vtadmin.NewAPI(testClusters(t), opts)
  2667  	t.Cleanup(func() {
  2668  		if err := api.Close(); err != nil {
  2669  			t.Logf("api did not close cleanly: %s", err.Error())
  2670  		}
  2671  	})
  2672  
  2673  	t.Run("unauthorized actor", func(t *testing.T) {
  2674  		t.Parallel()
  2675  
  2676  		actor := &rbac.Actor{Name: "other"}
  2677  		ctx := context.Background()
  2678  		if actor != nil {
  2679  			ctx = rbac.NewContext(ctx, actor)
  2680  		}
  2681  
  2682  		resp, err := api.SetReadWrite(ctx, &vtadminpb.SetReadWriteRequest{
  2683  			Alias: &topodatapb.TabletAlias{
  2684  				Cell: "zone1",
  2685  				Uid:  100,
  2686  			},
  2687  		})
  2688  		assert.Error(t, err, "actor %+v should not be permitted to SetReadWrite", actor)
  2689  		assert.Nil(t, resp, "actor %+v should not be permitted to SetReadWrite", actor)
  2690  	})
  2691  
  2692  	t.Run("authorized actor", func(t *testing.T) {
  2693  		t.Parallel()
  2694  
  2695  		actor := &rbac.Actor{Name: "allowed"}
  2696  		ctx := context.Background()
  2697  		if actor != nil {
  2698  			ctx = rbac.NewContext(ctx, actor)
  2699  		}
  2700  
  2701  		resp, err := api.SetReadWrite(ctx, &vtadminpb.SetReadWriteRequest{
  2702  			Alias: &topodatapb.TabletAlias{
  2703  				Cell: "zone1",
  2704  				Uid:  100,
  2705  			},
  2706  		})
  2707  		require.NoError(t, err)
  2708  		assert.NotNil(t, resp, "actor %+v should be permitted to SetReadWrite", actor)
  2709  	})
  2710  }
  2711  
  2712  func TestStartReplication(t *testing.T) {
  2713  	t.Parallel()
  2714  
  2715  	opts := vtadmin.Options{
  2716  		RBAC: &rbac.Config{
  2717  			Rules: []*struct {
  2718  				Resource string
  2719  				Actions  []string
  2720  				Subjects []string
  2721  				Clusters []string
  2722  			}{
  2723  				{
  2724  					Resource: "Tablet",
  2725  					Actions:  []string{"manage_tablet_replication"},
  2726  					Subjects: []string{"user:allowed"},
  2727  					Clusters: []string{"*"},
  2728  				},
  2729  			},
  2730  		},
  2731  	}
  2732  	err := opts.RBAC.Reify()
  2733  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  2734  
  2735  	api := vtadmin.NewAPI(testClusters(t), opts)
  2736  	t.Cleanup(func() {
  2737  		if err := api.Close(); err != nil {
  2738  			t.Logf("api did not close cleanly: %s", err.Error())
  2739  		}
  2740  	})
  2741  
  2742  	t.Run("unauthorized actor", func(t *testing.T) {
  2743  		t.Parallel()
  2744  
  2745  		actor := &rbac.Actor{Name: "other"}
  2746  		ctx := context.Background()
  2747  		if actor != nil {
  2748  			ctx = rbac.NewContext(ctx, actor)
  2749  		}
  2750  
  2751  		resp, err := api.StartReplication(ctx, &vtadminpb.StartReplicationRequest{
  2752  			Alias: &topodatapb.TabletAlias{
  2753  				Cell: "zone1",
  2754  				Uid:  100,
  2755  			},
  2756  		})
  2757  		assert.Error(t, err, "actor %+v should not be permitted to StartReplication", actor)
  2758  		assert.Nil(t, resp, "actor %+v should not be permitted to StartReplication", actor)
  2759  	})
  2760  
  2761  	t.Run("authorized actor", func(t *testing.T) {
  2762  		t.Parallel()
  2763  
  2764  		actor := &rbac.Actor{Name: "allowed"}
  2765  		ctx := context.Background()
  2766  		if actor != nil {
  2767  			ctx = rbac.NewContext(ctx, actor)
  2768  		}
  2769  
  2770  		resp, err := api.StartReplication(ctx, &vtadminpb.StartReplicationRequest{
  2771  			Alias: &topodatapb.TabletAlias{
  2772  				Cell: "zone1",
  2773  				Uid:  100,
  2774  			},
  2775  		})
  2776  		require.NoError(t, err)
  2777  		assert.NotNil(t, resp, "actor %+v should be permitted to StartReplication", actor)
  2778  	})
  2779  }
  2780  
  2781  func TestStopReplication(t *testing.T) {
  2782  	t.Parallel()
  2783  
  2784  	opts := vtadmin.Options{
  2785  		RBAC: &rbac.Config{
  2786  			Rules: []*struct {
  2787  				Resource string
  2788  				Actions  []string
  2789  				Subjects []string
  2790  				Clusters []string
  2791  			}{
  2792  				{
  2793  					Resource: "Tablet",
  2794  					Actions:  []string{"manage_tablet_replication"},
  2795  					Subjects: []string{"user:allowed"},
  2796  					Clusters: []string{"*"},
  2797  				},
  2798  			},
  2799  		},
  2800  	}
  2801  	err := opts.RBAC.Reify()
  2802  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  2803  
  2804  	api := vtadmin.NewAPI(testClusters(t), opts)
  2805  	t.Cleanup(func() {
  2806  		if err := api.Close(); err != nil {
  2807  			t.Logf("api did not close cleanly: %s", err.Error())
  2808  		}
  2809  	})
  2810  
  2811  	t.Run("unauthorized actor", func(t *testing.T) {
  2812  		t.Parallel()
  2813  
  2814  		actor := &rbac.Actor{Name: "other"}
  2815  		ctx := context.Background()
  2816  		if actor != nil {
  2817  			ctx = rbac.NewContext(ctx, actor)
  2818  		}
  2819  
  2820  		resp, err := api.StopReplication(ctx, &vtadminpb.StopReplicationRequest{
  2821  			Alias: &topodatapb.TabletAlias{
  2822  				Cell: "zone1",
  2823  				Uid:  100,
  2824  			},
  2825  		})
  2826  		assert.Error(t, err, "actor %+v should not be permitted to StopReplication", actor)
  2827  		assert.Nil(t, resp, "actor %+v should not be permitted to StopReplication", actor)
  2828  	})
  2829  
  2830  	t.Run("authorized actor", func(t *testing.T) {
  2831  		t.Parallel()
  2832  
  2833  		actor := &rbac.Actor{Name: "allowed"}
  2834  		ctx := context.Background()
  2835  		if actor != nil {
  2836  			ctx = rbac.NewContext(ctx, actor)
  2837  		}
  2838  
  2839  		resp, err := api.StopReplication(ctx, &vtadminpb.StopReplicationRequest{
  2840  			Alias: &topodatapb.TabletAlias{
  2841  				Cell: "zone1",
  2842  				Uid:  100,
  2843  			},
  2844  		})
  2845  		require.NoError(t, err)
  2846  		assert.NotNil(t, resp, "actor %+v should be permitted to StopReplication", actor)
  2847  	})
  2848  }
  2849  
  2850  func TestTabletExternallyPromoted(t *testing.T) {
  2851  	t.Parallel()
  2852  
  2853  	opts := vtadmin.Options{
  2854  		RBAC: &rbac.Config{
  2855  			Rules: []*struct {
  2856  				Resource string
  2857  				Actions  []string
  2858  				Subjects []string
  2859  				Clusters []string
  2860  			}{
  2861  				{
  2862  					Resource: "Shard",
  2863  					Actions:  []string{"tablet_externally_promoted"},
  2864  					Subjects: []string{"user:allowed"},
  2865  					Clusters: []string{"*"},
  2866  				},
  2867  			},
  2868  		},
  2869  	}
  2870  	err := opts.RBAC.Reify()
  2871  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  2872  
  2873  	api := vtadmin.NewAPI(testClusters(t), opts)
  2874  	t.Cleanup(func() {
  2875  		if err := api.Close(); err != nil {
  2876  			t.Logf("api did not close cleanly: %s", err.Error())
  2877  		}
  2878  	})
  2879  
  2880  	t.Run("unauthorized actor", func(t *testing.T) {
  2881  		t.Parallel()
  2882  
  2883  		actor := &rbac.Actor{Name: "other"}
  2884  		ctx := context.Background()
  2885  		if actor != nil {
  2886  			ctx = rbac.NewContext(ctx, actor)
  2887  		}
  2888  
  2889  		resp, err := api.TabletExternallyPromoted(ctx, &vtadminpb.TabletExternallyPromotedRequest{
  2890  			Alias: &topodatapb.TabletAlias{
  2891  				Cell: "zone1",
  2892  				Uid:  100,
  2893  			},
  2894  		})
  2895  		assert.Error(t, err, "actor %+v should not be permitted to TabletExternallyPromoted", actor)
  2896  		assert.Nil(t, resp, "actor %+v should not be permitted to TabletExternallyPromoted", actor)
  2897  	})
  2898  
  2899  	t.Run("authorized actor", func(t *testing.T) {
  2900  		t.Parallel()
  2901  
  2902  		actor := &rbac.Actor{Name: "allowed"}
  2903  		ctx := context.Background()
  2904  		if actor != nil {
  2905  			ctx = rbac.NewContext(ctx, actor)
  2906  		}
  2907  
  2908  		resp, err := api.TabletExternallyPromoted(ctx, &vtadminpb.TabletExternallyPromotedRequest{
  2909  			Alias: &topodatapb.TabletAlias{
  2910  				Cell: "zone1",
  2911  				Uid:  100,
  2912  			},
  2913  		})
  2914  		require.NoError(t, err)
  2915  		assert.NotNil(t, resp, "actor %+v should be permitted to TabletExternallyPromoted", actor)
  2916  	})
  2917  }
  2918  
  2919  func TestVTExplain(t *testing.T) {
  2920  	t.Parallel()
  2921  
  2922  	opts := vtadmin.Options{
  2923  		RBAC: &rbac.Config{
  2924  			Rules: []*struct {
  2925  				Resource string
  2926  				Actions  []string
  2927  				Subjects []string
  2928  				Clusters []string
  2929  			}{
  2930  				{
  2931  					Resource: "VTExplain",
  2932  					Actions:  []string{"get"},
  2933  					Subjects: []string{"user:allowed"},
  2934  					Clusters: []string{"*"},
  2935  				},
  2936  			},
  2937  		},
  2938  	}
  2939  	err := opts.RBAC.Reify()
  2940  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  2941  
  2942  	api := vtadmin.NewAPI(testClusters(t), opts)
  2943  	t.Cleanup(func() {
  2944  		if err := api.Close(); err != nil {
  2945  			t.Logf("api did not close cleanly: %s", err.Error())
  2946  		}
  2947  	})
  2948  
  2949  	t.Run("unauthorized actor", func(t *testing.T) {
  2950  		t.Parallel()
  2951  
  2952  		actor := &rbac.Actor{Name: "other"}
  2953  		ctx := context.Background()
  2954  		if actor != nil {
  2955  			ctx = rbac.NewContext(ctx, actor)
  2956  		}
  2957  
  2958  		resp, err := api.VTExplain(ctx, &vtadminpb.VTExplainRequest{
  2959  			Cluster:  "test",
  2960  			Keyspace: "test",
  2961  			Sql:      "select id from t1;"})
  2962  		require.NoError(t, err)
  2963  		assert.Nil(t, resp, "actor %+v should not be permitted to VTExplain", actor)
  2964  	})
  2965  
  2966  	t.Run("authorized actor", func(t *testing.T) {
  2967  		t.Parallel()
  2968  
  2969  		actor := &rbac.Actor{Name: "allowed"}
  2970  		ctx := context.Background()
  2971  		if actor != nil {
  2972  			ctx = rbac.NewContext(ctx, actor)
  2973  		}
  2974  
  2975  		resp, err := api.VTExplain(ctx, &vtadminpb.VTExplainRequest{
  2976  			Cluster:  "test",
  2977  			Keyspace: "test",
  2978  			Sql:      "select id from t1;"})
  2979  		require.NoError(t, err)
  2980  		assert.NotNil(t, resp, "actor %+v should be permitted to VTExplain", actor)
  2981  	})
  2982  }
  2983  
  2984  func TestValidateKeyspace(t *testing.T) {
  2985  	t.Parallel()
  2986  
  2987  	opts := vtadmin.Options{
  2988  		RBAC: &rbac.Config{
  2989  			Rules: []*struct {
  2990  				Resource string
  2991  				Actions  []string
  2992  				Subjects []string
  2993  				Clusters []string
  2994  			}{
  2995  				{
  2996  					Resource: "Keyspace",
  2997  					Actions:  []string{"put"},
  2998  					Subjects: []string{"user:allowed"},
  2999  					Clusters: []string{"*"},
  3000  				},
  3001  			},
  3002  		},
  3003  	}
  3004  	err := opts.RBAC.Reify()
  3005  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  3006  
  3007  	api := vtadmin.NewAPI(testClusters(t), opts)
  3008  	t.Cleanup(func() {
  3009  		if err := api.Close(); err != nil {
  3010  			t.Logf("api did not close cleanly: %s", err.Error())
  3011  		}
  3012  	})
  3013  
  3014  	t.Run("unauthorized actor", func(t *testing.T) {
  3015  		t.Parallel()
  3016  
  3017  		actor := &rbac.Actor{Name: "other"}
  3018  		ctx := context.Background()
  3019  		if actor != nil {
  3020  			ctx = rbac.NewContext(ctx, actor)
  3021  		}
  3022  
  3023  		resp, err := api.ValidateKeyspace(ctx, &vtadminpb.ValidateKeyspaceRequest{
  3024  			ClusterId: "test",
  3025  			Keyspace:  "test",
  3026  		})
  3027  		require.NoError(t, err)
  3028  		assert.Nil(t, resp, "actor %+v should not be permitted to ValidateKeyspace", actor)
  3029  	})
  3030  
  3031  	t.Run("authorized actor", func(t *testing.T) {
  3032  		t.Parallel()
  3033  
  3034  		actor := &rbac.Actor{Name: "allowed"}
  3035  		ctx := context.Background()
  3036  		if actor != nil {
  3037  			ctx = rbac.NewContext(ctx, actor)
  3038  		}
  3039  
  3040  		resp, err := api.ValidateKeyspace(ctx, &vtadminpb.ValidateKeyspaceRequest{
  3041  			ClusterId: "test",
  3042  			Keyspace:  "test",
  3043  		})
  3044  		require.NoError(t, err)
  3045  		assert.NotNil(t, resp, "actor %+v should be permitted to ValidateKeyspace", actor)
  3046  	})
  3047  }
  3048  
  3049  func TestValidateSchemaKeyspace(t *testing.T) {
  3050  	t.Parallel()
  3051  
  3052  	opts := vtadmin.Options{
  3053  		RBAC: &rbac.Config{
  3054  			Rules: []*struct {
  3055  				Resource string
  3056  				Actions  []string
  3057  				Subjects []string
  3058  				Clusters []string
  3059  			}{
  3060  				{
  3061  					Resource: "Keyspace",
  3062  					Actions:  []string{"put"},
  3063  					Subjects: []string{"user:allowed"},
  3064  					Clusters: []string{"*"},
  3065  				},
  3066  			},
  3067  		},
  3068  	}
  3069  	err := opts.RBAC.Reify()
  3070  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  3071  
  3072  	api := vtadmin.NewAPI(testClusters(t), opts)
  3073  	t.Cleanup(func() {
  3074  		if err := api.Close(); err != nil {
  3075  			t.Logf("api did not close cleanly: %s", err.Error())
  3076  		}
  3077  	})
  3078  
  3079  	t.Run("unauthorized actor", func(t *testing.T) {
  3080  		t.Parallel()
  3081  
  3082  		actor := &rbac.Actor{Name: "other"}
  3083  		ctx := context.Background()
  3084  		if actor != nil {
  3085  			ctx = rbac.NewContext(ctx, actor)
  3086  		}
  3087  
  3088  		resp, err := api.ValidateSchemaKeyspace(ctx, &vtadminpb.ValidateSchemaKeyspaceRequest{
  3089  			ClusterId: "test",
  3090  			Keyspace:  "test",
  3091  		})
  3092  		require.NoError(t, err)
  3093  		assert.Nil(t, resp, "actor %+v should not be permitted to ValidateSchemaKeyspace", actor)
  3094  	})
  3095  
  3096  	t.Run("authorized actor", func(t *testing.T) {
  3097  		t.Parallel()
  3098  
  3099  		actor := &rbac.Actor{Name: "allowed"}
  3100  		ctx := context.Background()
  3101  		if actor != nil {
  3102  			ctx = rbac.NewContext(ctx, actor)
  3103  		}
  3104  
  3105  		resp, err := api.ValidateSchemaKeyspace(ctx, &vtadminpb.ValidateSchemaKeyspaceRequest{
  3106  			ClusterId: "test",
  3107  			Keyspace:  "test",
  3108  		})
  3109  		require.NoError(t, err)
  3110  		assert.NotNil(t, resp, "actor %+v should be permitted to ValidateSchemaKeyspace", actor)
  3111  	})
  3112  }
  3113  
  3114  func TestValidateVersionKeyspace(t *testing.T) {
  3115  	t.Parallel()
  3116  
  3117  	opts := vtadmin.Options{
  3118  		RBAC: &rbac.Config{
  3119  			Rules: []*struct {
  3120  				Resource string
  3121  				Actions  []string
  3122  				Subjects []string
  3123  				Clusters []string
  3124  			}{
  3125  				{
  3126  					Resource: "Keyspace",
  3127  					Actions:  []string{"put"},
  3128  					Subjects: []string{"user:allowed"},
  3129  					Clusters: []string{"*"},
  3130  				},
  3131  			},
  3132  		},
  3133  	}
  3134  	err := opts.RBAC.Reify()
  3135  	require.NoError(t, err, "failed to reify authorization rules: %+v", opts.RBAC.Rules)
  3136  
  3137  	api := vtadmin.NewAPI(testClusters(t), opts)
  3138  	t.Cleanup(func() {
  3139  		if err := api.Close(); err != nil {
  3140  			t.Logf("api did not close cleanly: %s", err.Error())
  3141  		}
  3142  	})
  3143  
  3144  	t.Run("unauthorized actor", func(t *testing.T) {
  3145  		t.Parallel()
  3146  
  3147  		actor := &rbac.Actor{Name: "other"}
  3148  		ctx := context.Background()
  3149  		if actor != nil {
  3150  			ctx = rbac.NewContext(ctx, actor)
  3151  		}
  3152  
  3153  		resp, err := api.ValidateVersionKeyspace(ctx, &vtadminpb.ValidateVersionKeyspaceRequest{
  3154  			ClusterId: "test",
  3155  			Keyspace:  "test",
  3156  		})
  3157  		require.NoError(t, err)
  3158  		assert.Nil(t, resp, "actor %+v should not be permitted to ValidateVersionKeyspace", actor)
  3159  	})
  3160  
  3161  	t.Run("authorized actor", func(t *testing.T) {
  3162  		t.Parallel()
  3163  
  3164  		actor := &rbac.Actor{Name: "allowed"}
  3165  		ctx := context.Background()
  3166  		if actor != nil {
  3167  			ctx = rbac.NewContext(ctx, actor)
  3168  		}
  3169  
  3170  		resp, err := api.ValidateVersionKeyspace(ctx, &vtadminpb.ValidateVersionKeyspaceRequest{
  3171  			ClusterId: "test",
  3172  			Keyspace:  "test",
  3173  		})
  3174  		require.NoError(t, err)
  3175  		assert.NotNil(t, resp, "actor %+v should be permitted to ValidateVersionKeyspace", actor)
  3176  	})
  3177  }
  3178  
  3179  func testClusters(t testing.TB) []*cluster.Cluster {
  3180  	configs := []testutil.TestClusterConfig{
  3181  		{
  3182  			Cluster: &vtadminpb.Cluster{
  3183  				Id:   "test",
  3184  				Name: "test",
  3185  			},
  3186  			VtctldClient: &fakevtctldclient.VtctldClient{
  3187  				DeleteShardsResults: map[string]error{
  3188  					"test/-": nil,
  3189  				},
  3190  				DeleteTabletsResults: map[string]error{
  3191  					"zone1-0000000100": nil,
  3192  				},
  3193  				EmergencyReparentShardResults: map[string]struct {
  3194  					Response *vtctldatapb.EmergencyReparentShardResponse
  3195  					Error    error
  3196  				}{
  3197  					"test/-": {
  3198  						Response: &vtctldatapb.EmergencyReparentShardResponse{},
  3199  					},
  3200  				},
  3201  				FindAllShardsInKeyspaceResults: map[string]struct {
  3202  					Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  3203  					Error    error
  3204  				}{
  3205  					"test": {
  3206  						Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  3207  							Shards: map[string]*vtctldatapb.Shard{
  3208  								"-": {
  3209  									Keyspace: "test",
  3210  									Name:     "-",
  3211  									Shard: &topodatapb.Shard{
  3212  										KeyRange:         &topodatapb.KeyRange{},
  3213  										IsPrimaryServing: true,
  3214  									},
  3215  								},
  3216  							},
  3217  						},
  3218  					},
  3219  				},
  3220  				GetBackupsResults: map[string]struct {
  3221  					Response *vtctldatapb.GetBackupsResponse
  3222  					Error    error
  3223  				}{
  3224  					"test/-": {
  3225  						Response: &vtctldatapb.GetBackupsResponse{
  3226  							Backups: []*mysqlctlpb.BackupInfo{
  3227  								{},
  3228  							},
  3229  						},
  3230  					},
  3231  				},
  3232  				GetCellInfoNamesResults: &struct {
  3233  					Response *vtctldatapb.GetCellInfoNamesResponse
  3234  					Error    error
  3235  				}{
  3236  					Response: &vtctldatapb.GetCellInfoNamesResponse{
  3237  						Names: []string{"zone1"},
  3238  					},
  3239  				},
  3240  				GetCellsAliasesResults: &struct {
  3241  					Response *vtctldatapb.GetCellsAliasesResponse
  3242  					Error    error
  3243  				}{
  3244  					Response: &vtctldatapb.GetCellsAliasesResponse{
  3245  						Aliases: map[string]*topodatapb.CellsAlias{
  3246  							"zone": {
  3247  								Cells: []string{"zone1"}},
  3248  						},
  3249  					},
  3250  				},
  3251  				GetKeyspaceResults: map[string]struct {
  3252  					Response *vtctldatapb.GetKeyspaceResponse
  3253  					Error    error
  3254  				}{
  3255  					"test": {
  3256  						Response: &vtctldatapb.GetKeyspaceResponse{
  3257  							Keyspace: &vtctldatapb.Keyspace{
  3258  								Name:     "test",
  3259  								Keyspace: &topodatapb.Keyspace{},
  3260  							},
  3261  						},
  3262  					},
  3263  				},
  3264  				GetKeyspacesResults: &struct {
  3265  					Keyspaces []*vtctldatapb.Keyspace
  3266  					Error     error
  3267  				}{
  3268  					Keyspaces: []*vtctldatapb.Keyspace{
  3269  						{
  3270  							Name:     "test",
  3271  							Keyspace: &topodatapb.Keyspace{},
  3272  						},
  3273  					},
  3274  				},
  3275  				GetSchemaResults: map[string]struct {
  3276  					Response *vtctldatapb.GetSchemaResponse
  3277  					Error    error
  3278  				}{
  3279  					"zone1-0000000100": {
  3280  						Response: &vtctldatapb.GetSchemaResponse{
  3281  							Schema: &tabletmanagerdatapb.SchemaDefinition{
  3282  								TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  3283  									{Name: "t1", Schema: "create table t1 (id int(11) not null primary key);"},
  3284  									{Name: "t2"},
  3285  								},
  3286  							},
  3287  						},
  3288  					},
  3289  				},
  3290  				GetSrvVSchemaResults: map[string]struct {
  3291  					Response *vtctldatapb.GetSrvVSchemaResponse
  3292  					Error    error
  3293  				}{
  3294  					"zone1": {
  3295  						Response: &vtctldatapb.GetSrvVSchemaResponse{
  3296  							SrvVSchema: &vschemapb.SrvVSchema{
  3297  								Keyspaces: map[string]*vschemapb.Keyspace{
  3298  									"test": {
  3299  										Sharded: true,
  3300  										Vindexes: map[string]*vschemapb.Vindex{
  3301  											"id": {
  3302  												Type: "hash",
  3303  											},
  3304  										},
  3305  										Tables: map[string]*vschemapb.Table{
  3306  											"t1": {
  3307  												ColumnVindexes: []*vschemapb.ColumnVindex{
  3308  													{
  3309  														Name:   "id",
  3310  														Column: "id",
  3311  													},
  3312  												},
  3313  											},
  3314  										},
  3315  									},
  3316  								},
  3317  							},
  3318  						},
  3319  					},
  3320  				},
  3321  				GetVSchemaResults: map[string]struct {
  3322  					Response *vtctldatapb.GetVSchemaResponse
  3323  					Error    error
  3324  				}{
  3325  					"test": {
  3326  						Response: &vtctldatapb.GetVSchemaResponse{
  3327  							VSchema: &vschemapb.Keyspace{},
  3328  						},
  3329  					},
  3330  				},
  3331  				GetWorkflowsResults: map[string]struct {
  3332  					Response *vtctldatapb.GetWorkflowsResponse
  3333  					Error    error
  3334  				}{
  3335  					"test": {
  3336  						Response: &vtctldatapb.GetWorkflowsResponse{
  3337  							Workflows: []*vtctldatapb.Workflow{
  3338  								{
  3339  									Name: "testworkflow",
  3340  								},
  3341  							},
  3342  						}},
  3343  				},
  3344  				PingTabletResults: map[string]error{
  3345  					"zone1-0000000100": nil,
  3346  				},
  3347  				PlannedReparentShardResults: map[string]struct {
  3348  					Response *vtctldatapb.PlannedReparentShardResponse
  3349  					Error    error
  3350  				}{
  3351  					"test/-": {
  3352  						Response: &vtctldatapb.PlannedReparentShardResponse{},
  3353  					},
  3354  				},
  3355  				RefreshStateResults: map[string]error{
  3356  					"zone1-0000000100": nil,
  3357  				},
  3358  				ReloadSchemaKeyspaceResults: map[string]struct {
  3359  					Response *vtctldatapb.ReloadSchemaKeyspaceResponse
  3360  					Error    error
  3361  				}{
  3362  					"test": {
  3363  						Response: &vtctldatapb.ReloadSchemaKeyspaceResponse{
  3364  							Events: []*logutilpb.Event{{}, {}, {}}},
  3365  					},
  3366  				},
  3367  				ReparentTabletResults: map[string]struct {
  3368  					Response *vtctldatapb.ReparentTabletResponse
  3369  					Error    error
  3370  				}{
  3371  					"zone1-0000000100": {
  3372  						Response: &vtctldatapb.ReparentTabletResponse{},
  3373  					},
  3374  				},
  3375  				RunHealthCheckResults: map[string]error{
  3376  					"zone1-0000000100": nil,
  3377  				},
  3378  				SetWritableResults: map[string]error{
  3379  					"zone1-0000000100": nil,
  3380  				},
  3381  				ShardReplicationPositionsResults: map[string]struct {
  3382  					Response *vtctldatapb.ShardReplicationPositionsResponse
  3383  					Error    error
  3384  				}{
  3385  					"test/-": {
  3386  						Response: &vtctldatapb.ShardReplicationPositionsResponse{},
  3387  					},
  3388  				},
  3389  				StartReplicationResults: map[string]error{
  3390  					"zone1-0000000100": nil,
  3391  				},
  3392  				StopReplicationResults: map[string]error{
  3393  					"zone1-0000000100": nil,
  3394  				},
  3395  				TabletExternallyReparentedResults: map[string]struct {
  3396  					Response *vtctldatapb.TabletExternallyReparentedResponse
  3397  					Error    error
  3398  				}{
  3399  					"zone1-0000000100": {
  3400  						Response: &vtctldatapb.TabletExternallyReparentedResponse{},
  3401  					},
  3402  				},
  3403  				ValidateKeyspaceResults: map[string]struct {
  3404  					Response *vtctldatapb.ValidateKeyspaceResponse
  3405  					Error    error
  3406  				}{
  3407  					"test": {
  3408  						Response: &vtctldatapb.ValidateKeyspaceResponse{},
  3409  					},
  3410  				},
  3411  				ValidateSchemaKeyspaceResults: map[string]struct {
  3412  					Response *vtctldatapb.ValidateSchemaKeyspaceResponse
  3413  					Error    error
  3414  				}{
  3415  					"test": {
  3416  						Response: &vtctldatapb.ValidateSchemaKeyspaceResponse{},
  3417  					},
  3418  				},
  3419  				ValidateVersionKeyspaceResults: map[string]struct {
  3420  					Response *vtctldatapb.ValidateVersionKeyspaceResponse
  3421  					Error    error
  3422  				}{
  3423  					"test": {
  3424  						Response: &vtctldatapb.ValidateVersionKeyspaceResponse{},
  3425  					},
  3426  				},
  3427  			},
  3428  			Tablets: []*vtadminpb.Tablet{
  3429  				{
  3430  					Cluster: &vtadminpb.Cluster{Id: "test", Name: "test"},
  3431  					Tablet: &topodatapb.Tablet{
  3432  						Alias: &topodatapb.TabletAlias{
  3433  							Cell: "zone1",
  3434  							Uid:  100,
  3435  						},
  3436  						Keyspace: "test",
  3437  						Type:     topodatapb.TabletType_REPLICA,
  3438  					},
  3439  					State: vtadminpb.Tablet_SERVING,
  3440  				},
  3441  			},
  3442  			Config: &cluster.Config{
  3443  				TopoReadPoolConfig: &cluster.RPCPoolConfig{
  3444  					Size:        100,
  3445  					WaitTimeout: time.Millisecond * 50,
  3446  				},
  3447  			},
  3448  		}, {
  3449  			Cluster: &vtadminpb.Cluster{
  3450  				Id:   "other",
  3451  				Name: "other",
  3452  			},
  3453  			VtctldClient: &fakevtctldclient.VtctldClient{
  3454  				FindAllShardsInKeyspaceResults: map[string]struct {
  3455  					Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  3456  					Error    error
  3457  				}{
  3458  					"otherks": {
  3459  						Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  3460  							Shards: map[string]*vtctldatapb.Shard{
  3461  								"-": {
  3462  									Keyspace: "otherks",
  3463  									Name:     "-",
  3464  									Shard:    &topodatapb.Shard{},
  3465  								},
  3466  							},
  3467  						},
  3468  					},
  3469  				},
  3470  				GetBackupsResults: map[string]struct {
  3471  					Response *vtctldatapb.GetBackupsResponse
  3472  					Error    error
  3473  				}{
  3474  					"otherks/-": {
  3475  						Response: &vtctldatapb.GetBackupsResponse{
  3476  							Backups: []*mysqlctlpb.BackupInfo{
  3477  								{}, {}, {},
  3478  							},
  3479  						},
  3480  					},
  3481  				},
  3482  				GetCellInfoNamesResults: &struct {
  3483  					Response *vtctldatapb.GetCellInfoNamesResponse
  3484  					Error    error
  3485  				}{
  3486  					Response: &vtctldatapb.GetCellInfoNamesResponse{
  3487  						Names: []string{"other1"},
  3488  					},
  3489  				},
  3490  				GetCellsAliasesResults: &struct {
  3491  					Response *vtctldatapb.GetCellsAliasesResponse
  3492  					Error    error
  3493  				}{
  3494  					Response: &vtctldatapb.GetCellsAliasesResponse{
  3495  						Aliases: map[string]*topodatapb.CellsAlias{
  3496  							"other": {
  3497  								Cells: []string{"other1"}},
  3498  						},
  3499  					},
  3500  				},
  3501  				GetKeyspacesResults: &struct {
  3502  					Keyspaces []*vtctldatapb.Keyspace
  3503  					Error     error
  3504  				}{
  3505  					Keyspaces: []*vtctldatapb.Keyspace{
  3506  						{
  3507  							Name:     "otherks",
  3508  							Keyspace: &topodatapb.Keyspace{},
  3509  						},
  3510  					},
  3511  				},
  3512  				GetSchemaResults: map[string]struct {
  3513  					Response *vtctldatapb.GetSchemaResponse
  3514  					Error    error
  3515  				}{
  3516  					"other1-0000000100": {
  3517  						Response: &vtctldatapb.GetSchemaResponse{
  3518  							Schema: &tabletmanagerdatapb.SchemaDefinition{
  3519  								TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  3520  									{Name: "t1"},
  3521  								},
  3522  							},
  3523  						},
  3524  					},
  3525  				},
  3526  				GetSrvVSchemaResults: map[string]struct {
  3527  					Response *vtctldatapb.GetSrvVSchemaResponse
  3528  					Error    error
  3529  				}{
  3530  					"other1": {
  3531  						Response: &vtctldatapb.GetSrvVSchemaResponse{
  3532  							SrvVSchema: &vschemapb.SrvVSchema{},
  3533  						},
  3534  					},
  3535  				},
  3536  				GetVSchemaResults: map[string]struct {
  3537  					Response *vtctldatapb.GetVSchemaResponse
  3538  					Error    error
  3539  				}{
  3540  					"otherks": {
  3541  						Response: &vtctldatapb.GetVSchemaResponse{
  3542  							VSchema: &vschemapb.Keyspace{},
  3543  						},
  3544  					},
  3545  				},
  3546  				GetWorkflowsResults: map[string]struct {
  3547  					Response *vtctldatapb.GetWorkflowsResponse
  3548  					Error    error
  3549  				}{
  3550  					"otherks": {
  3551  						Response: &vtctldatapb.GetWorkflowsResponse{
  3552  							Workflows: []*vtctldatapb.Workflow{
  3553  								{
  3554  									Name: "otherks_workflow",
  3555  								},
  3556  							},
  3557  						}},
  3558  				},
  3559  				ShardReplicationPositionsResults: map[string]struct {
  3560  					Response *vtctldatapb.ShardReplicationPositionsResponse
  3561  					Error    error
  3562  				}{
  3563  					"otherks/-": {
  3564  						Response: &vtctldatapb.ShardReplicationPositionsResponse{},
  3565  					},
  3566  				},
  3567  				ReloadSchemaKeyspaceResults: map[string]struct {
  3568  					Response *vtctldatapb.ReloadSchemaKeyspaceResponse
  3569  					Error    error
  3570  				}{
  3571  					"otherks": {
  3572  						Response: &vtctldatapb.ReloadSchemaKeyspaceResponse{
  3573  							Events: []*logutilpb.Event{{}}},
  3574  					},
  3575  				},
  3576  			},
  3577  			Tablets: []*vtadminpb.Tablet{
  3578  				{
  3579  					Cluster: &vtadminpb.Cluster{Id: "other", Name: "other"},
  3580  					Tablet: &topodatapb.Tablet{
  3581  						Alias: &topodatapb.TabletAlias{
  3582  							Cell: "other1",
  3583  							Uid:  100,
  3584  						},
  3585  						Keyspace: "otherks",
  3586  						Type:     topodatapb.TabletType_UNKNOWN,
  3587  					},
  3588  					State: vtadminpb.Tablet_SERVING,
  3589  				},
  3590  			},
  3591  			Config: &cluster.Config{
  3592  				TopoReadPoolConfig: &cluster.RPCPoolConfig{
  3593  					Size:        100,
  3594  					WaitTimeout: time.Millisecond * 50,
  3595  				},
  3596  			},
  3597  		},
  3598  	}
  3599  
  3600  	return testutil.BuildClusters(t, configs...)
  3601  }