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