github.com/onflow/flow-go@v0.33.17/engine/access/secure_grpcr_test.go (about)

     1  package access
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"os"
     7  	"testing"
     8  	"time"
     9  
    10  	accessproto "github.com/onflow/flow/protobuf/go/flow/access"
    11  	"github.com/rs/zerolog"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/mock"
    14  	"github.com/stretchr/testify/suite"
    15  	"google.golang.org/grpc"
    16  	"google.golang.org/grpc/credentials"
    17  	"google.golang.org/grpc/credentials/insecure"
    18  
    19  	"github.com/onflow/flow-go/crypto"
    20  	accessmock "github.com/onflow/flow-go/engine/access/mock"
    21  	"github.com/onflow/flow-go/engine/access/rpc"
    22  	"github.com/onflow/flow-go/engine/access/rpc/backend"
    23  	statestreambackend "github.com/onflow/flow-go/engine/access/state_stream/backend"
    24  	"github.com/onflow/flow-go/model/flow"
    25  	"github.com/onflow/flow-go/module/grpcserver"
    26  	"github.com/onflow/flow-go/module/irrecoverable"
    27  	"github.com/onflow/flow-go/module/metrics"
    28  	module "github.com/onflow/flow-go/module/mock"
    29  	"github.com/onflow/flow-go/network"
    30  	protocol "github.com/onflow/flow-go/state/protocol/mock"
    31  	storagemock "github.com/onflow/flow-go/storage/mock"
    32  	"github.com/onflow/flow-go/utils/grpcutils"
    33  	"github.com/onflow/flow-go/utils/unittest"
    34  )
    35  
    36  // SecureGRPCTestSuite tests that Access node provides a secure GRPC server
    37  type SecureGRPCTestSuite struct {
    38  	suite.Suite
    39  	state      *protocol.State
    40  	snapshot   *protocol.Snapshot
    41  	epochQuery *protocol.EpochQuery
    42  	log        zerolog.Logger
    43  	net        *network.EngineRegistry
    44  	request    *module.Requester
    45  	collClient *accessmock.AccessAPIClient
    46  	execClient *accessmock.ExecutionAPIClient
    47  	me         *module.Local
    48  	chainID    flow.ChainID
    49  	metrics    *metrics.NoopCollector
    50  	rpcEng     *rpc.Engine
    51  	publicKey  crypto.PublicKey
    52  
    53  	// storage
    54  	blocks       *storagemock.Blocks
    55  	headers      *storagemock.Headers
    56  	collections  *storagemock.Collections
    57  	transactions *storagemock.Transactions
    58  	receipts     *storagemock.ExecutionReceipts
    59  
    60  	ctx    irrecoverable.SignalerContext
    61  	cancel context.CancelFunc
    62  
    63  	// grpc servers
    64  	secureGrpcServer   *grpcserver.GrpcServer
    65  	unsecureGrpcServer *grpcserver.GrpcServer
    66  }
    67  
    68  func (suite *SecureGRPCTestSuite) SetupTest() {
    69  	suite.log = zerolog.New(os.Stdout)
    70  	suite.net = new(network.EngineRegistry)
    71  	suite.state = new(protocol.State)
    72  	suite.snapshot = new(protocol.Snapshot)
    73  
    74  	rootHeader := unittest.BlockHeaderFixture()
    75  	params := new(protocol.Params)
    76  	params.On("SporkID").Return(unittest.IdentifierFixture(), nil)
    77  	params.On("ProtocolVersion").Return(uint(unittest.Uint64InRange(10, 30)), nil)
    78  	params.On("SporkRootBlockHeight").Return(rootHeader.Height, nil)
    79  	params.On("SealedRoot").Return(rootHeader, nil)
    80  
    81  	suite.epochQuery = new(protocol.EpochQuery)
    82  	suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe()
    83  	suite.state.On("Final").Return(suite.snapshot, nil).Maybe()
    84  	suite.state.On("Params").Return(params, nil).Maybe()
    85  	suite.snapshot.On("Epochs").Return(suite.epochQuery).Maybe()
    86  	suite.blocks = new(storagemock.Blocks)
    87  	suite.headers = new(storagemock.Headers)
    88  	suite.transactions = new(storagemock.Transactions)
    89  	suite.collections = new(storagemock.Collections)
    90  	suite.receipts = new(storagemock.ExecutionReceipts)
    91  
    92  	suite.collClient = new(accessmock.AccessAPIClient)
    93  	suite.execClient = new(accessmock.ExecutionAPIClient)
    94  
    95  	suite.request = new(module.Requester)
    96  	suite.request.On("EntityByID", mock.Anything, mock.Anything)
    97  
    98  	suite.me = new(module.Local)
    99  
   100  	accessIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleAccess))
   101  	suite.me.
   102  		On("NodeID").
   103  		Return(accessIdentity.NodeID)
   104  
   105  	suite.chainID = flow.Testnet
   106  	suite.metrics = metrics.NewNoopCollector()
   107  
   108  	config := rpc.Config{
   109  		UnsecureGRPCListenAddr: unittest.DefaultAddress,
   110  		SecureGRPCListenAddr:   unittest.DefaultAddress,
   111  		HTTPListenAddr:         unittest.DefaultAddress,
   112  	}
   113  
   114  	// generate a server certificate that will be served by the GRPC server
   115  	networkingKey := unittest.NetworkingPrivKeyFixture()
   116  	x509Certificate, err := grpcutils.X509Certificate(networkingKey)
   117  	assert.NoError(suite.T(), err)
   118  	tlsConfig := grpcutils.DefaultServerTLSConfig(x509Certificate)
   119  	// set the transport credentials for the server to use
   120  	config.TransportCredentials = credentials.NewTLS(tlsConfig)
   121  	// save the public key to use later in tests later
   122  	suite.publicKey = networkingKey.PublicKey()
   123  
   124  	suite.secureGrpcServer = grpcserver.NewGrpcServerBuilder(suite.log,
   125  		config.SecureGRPCListenAddr,
   126  		grpcutils.DefaultMaxMsgSize,
   127  		false,
   128  		nil,
   129  		nil,
   130  		grpcserver.WithTransportCredentials(config.TransportCredentials)).Build()
   131  
   132  	suite.unsecureGrpcServer = grpcserver.NewGrpcServerBuilder(suite.log,
   133  		config.UnsecureGRPCListenAddr,
   134  		grpcutils.DefaultMaxMsgSize,
   135  		false,
   136  		nil,
   137  		nil).Build()
   138  
   139  	block := unittest.BlockHeaderFixture()
   140  	suite.snapshot.On("Head").Return(block, nil)
   141  
   142  	bnd, err := backend.New(backend.Params{
   143  		State:                suite.state,
   144  		CollectionRPC:        suite.collClient,
   145  		Blocks:               suite.blocks,
   146  		Headers:              suite.headers,
   147  		Collections:          suite.collections,
   148  		Transactions:         suite.transactions,
   149  		ChainID:              suite.chainID,
   150  		AccessMetrics:        suite.metrics,
   151  		MaxHeightRange:       0,
   152  		Log:                  suite.log,
   153  		SnapshotHistoryLimit: 0,
   154  		Communicator:         backend.NewNodeCommunicator(false),
   155  	})
   156  	suite.Require().NoError(err)
   157  
   158  	stateStreamConfig := statestreambackend.Config{}
   159  	rpcEngBuilder, err := rpc.NewBuilder(
   160  		suite.log,
   161  		suite.state,
   162  		config,
   163  		suite.chainID,
   164  		suite.metrics,
   165  		false,
   166  		suite.me,
   167  		bnd,
   168  		bnd,
   169  		suite.secureGrpcServer,
   170  		suite.unsecureGrpcServer,
   171  		nil,
   172  		stateStreamConfig,
   173  	)
   174  	assert.NoError(suite.T(), err)
   175  	suite.rpcEng, err = rpcEngBuilder.WithLegacy().Build()
   176  	assert.NoError(suite.T(), err)
   177  	suite.ctx, suite.cancel = irrecoverable.NewMockSignalerContextWithCancel(suite.T(), context.Background())
   178  
   179  	suite.rpcEng.Start(suite.ctx)
   180  
   181  	suite.secureGrpcServer.Start(suite.ctx)
   182  	suite.unsecureGrpcServer.Start(suite.ctx)
   183  
   184  	// wait for the servers to startup
   185  	unittest.AssertClosesBefore(suite.T(), suite.secureGrpcServer.Ready(), 2*time.Second)
   186  	unittest.AssertClosesBefore(suite.T(), suite.unsecureGrpcServer.Ready(), 2*time.Second)
   187  
   188  	// wait for the engine to startup
   189  	unittest.AssertClosesBefore(suite.T(), suite.rpcEng.Ready(), 2*time.Second)
   190  }
   191  
   192  func (suite *SecureGRPCTestSuite) TearDownTest() {
   193  	if suite.cancel != nil {
   194  		suite.cancel()
   195  		unittest.AssertClosesBefore(suite.T(), suite.secureGrpcServer.Done(), 2*time.Second)
   196  		unittest.AssertClosesBefore(suite.T(), suite.unsecureGrpcServer.Done(), 2*time.Second)
   197  		unittest.AssertClosesBefore(suite.T(), suite.rpcEng.Done(), 2*time.Second)
   198  	}
   199  }
   200  
   201  func TestSecureGRPC(t *testing.T) {
   202  	suite.Run(t, new(SecureGRPCTestSuite))
   203  }
   204  
   205  func (suite *SecureGRPCTestSuite) TestAPICallUsingSecureGRPC() {
   206  
   207  	req := &accessproto.PingRequest{}
   208  	ctx := context.Background()
   209  
   210  	// expect 2 upstream calls
   211  	suite.execClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Twice()
   212  	suite.collClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Twice()
   213  
   214  	suite.Run("happy path - grpc client can connect successfully with the correct public key", func() {
   215  		client, closer := suite.secureGRPCClient(suite.publicKey)
   216  		defer closer.Close()
   217  		resp, err := client.Ping(ctx, req)
   218  		assert.NoError(suite.T(), err)
   219  		assert.NotNil(suite.T(), resp)
   220  	})
   221  
   222  	suite.Run("happy path - connection fails with an incorrect public key", func() {
   223  		newKey := unittest.NetworkingPrivKeyFixture()
   224  		client, closer := suite.secureGRPCClient(newKey.PublicKey())
   225  		defer closer.Close()
   226  		_, err := client.Ping(ctx, req)
   227  		assert.Error(suite.T(), err)
   228  	})
   229  
   230  	suite.Run("happy path - connection fails, unsecure client can not get info from secure server connection", func() {
   231  		conn, err := grpc.Dial(
   232  			suite.secureGrpcServer.GRPCAddress().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
   233  		assert.NoError(suite.T(), err)
   234  
   235  		client := accessproto.NewAccessAPIClient(conn)
   236  		closer := io.Closer(conn)
   237  		defer closer.Close()
   238  
   239  		_, err = client.Ping(ctx, req)
   240  		assert.Error(suite.T(), err)
   241  	})
   242  }
   243  
   244  // secureGRPCClient creates a secure GRPC client using the given public key
   245  func (suite *SecureGRPCTestSuite) secureGRPCClient(publicKey crypto.PublicKey) (accessproto.AccessAPIClient, io.Closer) {
   246  	tlsConfig, err := grpcutils.DefaultClientTLSConfig(publicKey)
   247  	assert.NoError(suite.T(), err)
   248  
   249  	conn, err := grpc.Dial(
   250  		suite.secureGrpcServer.GRPCAddress().String(),
   251  		grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
   252  	assert.NoError(suite.T(), err)
   253  
   254  	client := accessproto.NewAccessAPIClient(conn)
   255  	closer := io.Closer(conn)
   256  	return client, closer
   257  }