vitess.io/vitess@v0.16.2/go/test/endtoend/vtgate/grpc_server_acls/acls_test.go (about)

     1  /*
     2  Copyright 2023 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package grpc_server_acls
    18  
    19  import (
    20  	"context"
    21  	"flag"
    22  	"fmt"
    23  	"os"
    24  	"path"
    25  	"testing"
    26  
    27  	"vitess.io/vitess/go/vt/callerid"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/require"
    31  	"google.golang.org/grpc"
    32  
    33  	"vitess.io/vitess/go/test/endtoend/cluster"
    34  	"vitess.io/vitess/go/vt/grpcclient"
    35  	"vitess.io/vitess/go/vt/vtgate/grpcvtgateconn"
    36  	"vitess.io/vitess/go/vt/vtgate/vtgateconn"
    37  )
    38  
    39  var (
    40  	clusterInstance   *cluster.LocalProcessCluster
    41  	vtgateGrpcAddress string
    42  	hostname          = "localhost"
    43  	keyspaceName      = "ks"
    44  	cell              = "zone1"
    45  	sqlSchema         = `
    46  		create table test_table (
    47  			id bigint,
    48  			val varchar(128),
    49  			primary key(id)
    50  		) Engine=InnoDB;
    51  `
    52  	grpcServerAuthStaticJSON = `
    53  		[
    54  		  {
    55  			"Username": "some_other_user",
    56  			"Password": "test_password"
    57  		  },
    58  		  {
    59  			"Username": "another_unrelated_user",
    60  			"Password": "test_password"
    61  		  }
    62  		]
    63  `
    64  	tableACLJSON = `
    65  		{
    66  		  "table_groups": [
    67  			{
    68  			  "name": "default",
    69  			  "table_names_or_prefixes": ["%"],
    70  			  "readers": ["user_with_access"],
    71  			  "writers": ["user_with_access"],
    72  			  "admins": ["user_with_access"]
    73  			}
    74  		  ]
    75  		}
    76  `
    77  )
    78  
    79  func TestMain(m *testing.M) {
    80  
    81  	defer cluster.PanicHandler(nil)
    82  	flag.Parse()
    83  
    84  	exitcode := func() int {
    85  		clusterInstance = cluster.NewCluster(cell, hostname)
    86  		defer clusterInstance.Teardown()
    87  
    88  		// Start topo server
    89  		if err := clusterInstance.StartTopo(); err != nil {
    90  			return 1
    91  		}
    92  
    93  		// Directory for authn / authz config files
    94  		authDirectory := path.Join(clusterInstance.TmpDirectory, "auth")
    95  		if err := os.Mkdir(authDirectory, 0700); err != nil {
    96  			return 1
    97  		}
    98  
    99  		// Create grpc_server_auth_static.json file
   100  		grpcServerAuthStaticPath := path.Join(authDirectory, "grpc_server_auth_static.json")
   101  		if err := createFile(grpcServerAuthStaticPath, grpcServerAuthStaticJSON); err != nil {
   102  			return 1
   103  		}
   104  
   105  		// Create table_acl.json file
   106  		tableACLPath := path.Join(authDirectory, "table_acl.json")
   107  		if err := createFile(tableACLPath, tableACLJSON); err != nil {
   108  			return 1
   109  		}
   110  
   111  		// Configure vtgate to use static auth
   112  		clusterInstance.VtGateExtraArgs = []string{
   113  			"--grpc_auth_mode", "static",
   114  			"--grpc_auth_static_password_file", grpcServerAuthStaticPath,
   115  			"--grpc_use_effective_callerid",
   116  			"--grpc-use-static-authentication-callerid",
   117  		}
   118  
   119  		// Configure vttablet to use table ACL
   120  		clusterInstance.VtTabletExtraArgs = []string{
   121  			"--enforce-tableacl-config",
   122  			"--queryserver-config-strict-table-acl",
   123  			"--table-acl-config", tableACLPath,
   124  		}
   125  
   126  		// Start keyspace
   127  		keyspace := &cluster.Keyspace{
   128  			Name:      keyspaceName,
   129  			SchemaSQL: sqlSchema,
   130  		}
   131  		if err := clusterInstance.StartUnshardedKeyspace(*keyspace, 1, false); err != nil {
   132  			return 1
   133  		}
   134  
   135  		// Start vtgate
   136  		if err := clusterInstance.StartVtgate(); err != nil {
   137  			clusterInstance.VtgateProcess = cluster.VtgateProcess{}
   138  			return 1
   139  		}
   140  		vtgateGrpcAddress = fmt.Sprintf("%s:%d", clusterInstance.Hostname, clusterInstance.VtgateGrpcPort)
   141  
   142  		return m.Run()
   143  	}()
   144  	os.Exit(exitcode)
   145  }
   146  
   147  // TestEffectiveCallerIDWithAccess verifies that an authenticated gRPC static user with an effectiveCallerID that has ACL access can execute queries
   148  func TestEffectiveCallerIDWithAccess(t *testing.T) {
   149  	ctx, cancel := context.WithCancel(context.Background())
   150  	defer cancel()
   151  
   152  	vtgateConn, err := dialVTGate(ctx, t, "some_other_user", "test_password")
   153  	if err != nil {
   154  		t.Fatal(err)
   155  	}
   156  	defer vtgateConn.Close()
   157  
   158  	session := vtgateConn.Session(keyspaceName+"@primary", nil)
   159  	query := "SELECT id FROM test_table"
   160  	ctx = callerid.NewContext(ctx, callerid.NewEffectiveCallerID("user_with_access", "", ""), nil)
   161  	_, err = session.Execute(ctx, query, nil)
   162  	assert.NoError(t, err)
   163  }
   164  
   165  // TestEffectiveCallerIDWithNoAccess verifies that an authenticated gRPC static user without an effectiveCallerID that has ACL access cannot execute queries
   166  func TestEffectiveCallerIDWithNoAccess(t *testing.T) {
   167  	ctx, cancel := context.WithCancel(context.Background())
   168  	defer cancel()
   169  
   170  	vtgateConn, err := dialVTGate(ctx, t, "another_unrelated_user", "test_password")
   171  	if err != nil {
   172  		t.Fatal(err)
   173  	}
   174  	defer vtgateConn.Close()
   175  
   176  	session := vtgateConn.Session(keyspaceName+"@primary", nil)
   177  	query := "SELECT id FROM test_table"
   178  	ctx = callerid.NewContext(ctx, callerid.NewEffectiveCallerID("user_no_access", "", ""), nil)
   179  	_, err = session.Execute(ctx, query, nil)
   180  	require.Error(t, err)
   181  	assert.Contains(t, err.Error(), "Select command denied to user")
   182  	assert.Contains(t, err.Error(), "for table 'test_table' (ACL check error)")
   183  }
   184  
   185  func dialVTGate(ctx context.Context, t *testing.T, username string, password string) (*vtgateconn.VTGateConn, error) {
   186  	clientCreds := &grpcclient.StaticAuthClientCreds{Username: username, Password: password}
   187  	creds := grpc.WithPerRPCCredentials(clientCreds)
   188  	dialerFunc := grpcvtgateconn.DialWithOpts(ctx, creds)
   189  	dialerName := t.Name()
   190  	vtgateconn.RegisterDialer(dialerName, dialerFunc)
   191  	return vtgateconn.DialProtocol(ctx, dialerName, vtgateGrpcAddress)
   192  }
   193  
   194  func createFile(path string, contents string) error {
   195  	f, err := os.Create(path)
   196  	if err != nil {
   197  		return err
   198  	}
   199  	_, err = f.WriteString(contents)
   200  	if err != nil {
   201  		return err
   202  	}
   203  	return f.Close()
   204  }