diff --git a/beacon-chain/main.go b/beacon-chain/main.go index 30585b54c07..869663b7ec5 100644 --- a/beacon-chain/main.go +++ b/beacon-chain/main.go @@ -79,6 +79,7 @@ var appFlags = []cli.Flag{ cmd.TracingEndpointFlag, cmd.TraceSampleFractionFlag, cmd.MonitoringHostFlag, + cmd.InsecureGRPCFlag, flags.MonitoringPortFlag, cmd.DisableMonitoringFlag, cmd.ClearDB, diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index 297caac00c0..4c53aefc3f9 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -562,20 +562,24 @@ func (b *BeaconNode) registerRPCService() error { port := b.cliCtx.String(flags.RPCPort.Name) cert := b.cliCtx.String(flags.CertFlag.Name) key := b.cliCtx.String(flags.KeyFlag.Name) + datadir := b.cliCtx.String(cmd.DataDirFlag.Name) slasherCert := b.cliCtx.String(flags.SlasherCertFlag.Name) slasherProvider := b.cliCtx.String(flags.SlasherProviderFlag.Name) mockEth1DataVotes := b.cliCtx.Bool(flags.InteropMockEth1DataVotesFlag.Name) enableDebugRPCEndpoints := b.cliCtx.Bool(flags.EnableDebugRPCEndpoints.Name) + insecureGRPC := b.cliCtx.Bool(cmd.InsecureGRPCFlag.Name) p2pService := b.fetchP2P() rpcService := rpc.NewService(b.ctx, &rpc.Config{ Host: host, Port: port, CertFlag: cert, KeyFlag: key, + DataDir: datadir, BeaconDB: b.db, Broadcaster: p2pService, PeersFetcher: p2pService, PeerManager: p2pService, + InsecureGRPC: insecureGRPC, HeadFetcher: chainService, ForkFetcher: chainService, FinalizationFetcher: chainService, diff --git a/beacon-chain/rpc/BUILD.bazel b/beacon-chain/rpc/BUILD.bazel index 15f0129ba87..3c6891c61a1 100644 --- a/beacon-chain/rpc/BUILD.bazel +++ b/beacon-chain/rpc/BUILD.bazel @@ -3,7 +3,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_test") go_library( name = "go_default_library", - srcs = ["service.go"], + srcs = [ + "service.go", + "tls.go", + ], importpath = "github.com/prysmaticlabs/prysm/beacon-chain/rpc", visibility = ["//beacon-chain:__subpackages__"], deps = [ @@ -30,11 +33,14 @@ go_library( "//proto/slashing:go_default_library", "//shared/featureconfig:go_default_library", "//shared/params:go_default_library", + "//shared/rand:go_default_library", + "//shared/roughtime:go_default_library", "//shared/traceutil:go_default_library", "@com_github_grpc_ecosystem_go_grpc_middleware//:go_default_library", "@com_github_grpc_ecosystem_go_grpc_middleware//recovery:go_default_library", "@com_github_grpc_ecosystem_go_grpc_middleware//tracing/opentracing:go_default_library", "@com_github_grpc_ecosystem_go_grpc_prometheus//:go_default_library", + "@com_github_pkg_errors//:go_default_library", "@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@io_opencensus_go//plugin/ocgrpc:go_default_library", diff --git a/beacon-chain/rpc/service.go b/beacon-chain/rpc/service.go index 8b9df15c392..d54cb301608 100644 --- a/beacon-chain/rpc/service.go +++ b/beacon-chain/rpc/service.go @@ -52,49 +52,51 @@ func init() { // Service defining an RPC server for a beacon node. type Service struct { - ctx context.Context - cancel context.CancelFunc - beaconDB db.HeadAccessDatabase - headFetcher blockchain.HeadFetcher - forkFetcher blockchain.ForkFetcher - finalizationFetcher blockchain.FinalizationFetcher - participationFetcher blockchain.ParticipationFetcher - genesisTimeFetcher blockchain.TimeFetcher - genesisFetcher blockchain.GenesisFetcher - attestationReceiver blockchain.AttestationReceiver - blockReceiver blockchain.BlockReceiver - powChainService powchain.Chain - chainStartFetcher powchain.ChainStartFetcher + insecureGRPC bool mockEth1Votes bool enableDebugRPCEndpoints bool - attestationsPool attestations.Pool - exitPool *voluntaryexits.Pool slashingsPool *slashings.Pool - syncService sync.Checker - host string - port string - listener net.Listener - withCert string - withKey string + cancel context.CancelFunc + exitPool *voluntaryexits.Pool + slasherConn *grpc.ClientConn + stateGen *stategen.State grpcServer *grpc.Server canonicalStateChan chan *pbp2p.BeaconState + connectedRPCClients map[net.Addr]bool incomingAttestation chan *ethpb.Attestation - credentialError error - p2p p2p.Broadcaster + pendingDepositFetcher depositcache.PendingDepositsFetcher peersFetcher p2p.PeersProvider - peerManager p2p.PeerManager + p2p p2p.Broadcaster + credentialError error depositFetcher depositcache.DepositFetcher - pendingDepositFetcher depositcache.PendingDepositsFetcher stateNotifier statefeed.Notifier blockNotifier blockfeed.Notifier + datadir string + withKey string + withCert string + ctx context.Context operationNotifier opfeed.Notifier - slasherConn *grpc.ClientConn + port string + host string + syncService sync.Checker + listener net.Listener slasherProvider string + attestationsPool attestations.Pool slasherCert string slasherCredentialError error + chainStartFetcher powchain.ChainStartFetcher + powChainService powchain.Chain + blockReceiver blockchain.BlockReceiver + attestationReceiver blockchain.AttestationReceiver + genesisFetcher blockchain.GenesisFetcher + genesisTimeFetcher blockchain.TimeFetcher + participationFetcher blockchain.ParticipationFetcher + finalizationFetcher blockchain.FinalizationFetcher + forkFetcher blockchain.ForkFetcher + headFetcher blockchain.HeadFetcher + beaconDB db.HeadAccessDatabase slasherClient slashpb.SlasherClient - stateGen *stategen.State - connectedRPCClients map[net.Addr]bool + peerManager p2p.PeerManager } // Config options for the beacon node RPC server. @@ -103,6 +105,7 @@ type Config struct { Port string CertFlag string KeyFlag string + DataDir string BeaconDB db.HeadAccessDatabase HeadFetcher blockchain.HeadFetcher ForkFetcher blockchain.ForkFetcher @@ -115,6 +118,7 @@ type Config struct { GenesisTimeFetcher blockchain.TimeFetcher GenesisFetcher blockchain.GenesisFetcher EnableDebugRPCEndpoints bool + InsecureGRPC bool MockEth1Votes bool AttestationsPool attestations.Pool ExitPool *voluntaryexits.Pool @@ -147,9 +151,11 @@ func NewService(ctx context.Context, cfg *Config) *Service { participationFetcher: cfg.ParticipationFetcher, genesisTimeFetcher: cfg.GenesisTimeFetcher, genesisFetcher: cfg.GenesisFetcher, + datadir: cfg.DataDir, attestationReceiver: cfg.AttestationReceiver, blockReceiver: cfg.BlockReceiver, p2p: cfg.Broadcaster, + insecureGRPC: cfg.InsecureGRPC, peersFetcher: cfg.PeersFetcher, peerManager: cfg.PeerManager, powChainService: cfg.POWChainService, @@ -208,6 +214,7 @@ func (s *Service) Start() { )), } grpc_prometheus.EnableHandlingTimeHistogram() + if s.withCert != "" && s.withKey != "" { creds, err := credentials.NewServerTLSFromFile(s.withCert, s.withKey) if err != nil { @@ -216,10 +223,26 @@ func (s *Service) Start() { } opts = append(opts, grpc.Creds(creds)) } else { - log.Warn("You are using an insecure gRPC server. If you are running your beacon node and " + - "validator on the same machines, you can ignore this message. If you want to know " + - "how to enable secure connections, see: https://docs.prylabs.network/docs/prysm-usage/secure-grpc") + if s.insecureGRPC { + log.Warn("You are using an insecure gRPC server. If you are running your beacon node and " + + "validator on the same machines, you can ignore this message. If you want to know " + + "how to enable secure connections, see: https://docs.prylabs.network/docs/prysm-usage/secure-grpc") + } else { + // Generate self-signed certs by default. + certPath, certKeyPath, err := generateSelfSignedCerts(s.datadir) + if err != nil { + log.Fatalf("Could not generate self-signed, secure gRPC certificates: %v", err) + } + creds, err := credentials.NewServerTLSFromFile(certPath, certKeyPath) + if err != nil { + log.Errorf("Could not load TLS keys: %s", err) + s.credentialError = err + } + opts = append(opts, grpc.Creds(creds)) + log.Info("Establishing secure gRPC server using self-signed certificates") + } } + s.grpcServer = grpc.NewServer(opts...) validatorServer := &validator.Server{ diff --git a/beacon-chain/rpc/tls.go b/beacon-chain/rpc/tls.go new file mode 100644 index 00000000000..9ee19734c8c --- /dev/null +++ b/beacon-chain/rpc/tls.go @@ -0,0 +1,93 @@ +package rpc + +import ( + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "net" + "os" + "path" + "time" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/shared/rand" + "github.com/prysmaticlabs/prysm/shared/roughtime" +) + +const ( + rsaBits = 2048 + validFor = 365 * 24 * time.Hour +) + +var ( + selfSignedCertName = "beacon.pem" + selfSignedCertKeyName = "key.pem" +) + +// Generates self-signed certificates at a datadir path. This function +// returns the paths of the cert.pem and key.pem files that +// were generated as a result. +func generateSelfSignedCerts(datadir string) (string, string, error) { + priv, err := rsa.GenerateKey(rand.NewGenerator(), rsaBits) + if err != nil { + return "", "", errors.Wrap(err, "nailed to generate private key") + } + + notBefore := roughtime.Now() + notAfter := notBefore.Add(validFor) + + serialNumber := big.NewInt(int64(rand.NewGenerator().Int() % 128)) + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Prysm"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + template.IPAddresses = append(template.IPAddresses, net.ParseIP("127.0.0.1")) + + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + + derBytes, err := x509.CreateCertificate(rand.NewGenerator(), &template, &template, &priv.PublicKey, priv) + if err != nil { + return "", "", errors.Wrap(err, "failed to create x509 certificate") + } + + certPath := path.Join(datadir, selfSignedCertName) + certKeyPath := path.Join(datadir, selfSignedCertKeyName) + certOut, err := os.Create(certPath) + if err != nil { + return "", "", errors.Wrapf(err, "failed to open %s for writing", certPath) + } + if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return "", "", errors.Wrapf(err, "failed to write data to %s", certPath) + } + if err := certOut.Close(); err != nil { + return "", "", errors.Wrapf(err, "error closing write buffer: %s", certPath) + } + log.WithField("certPath", certPath).Info("Wrote self-signed certificate file") + + keyOut, err := os.OpenFile(certKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return "", "", errors.Wrapf(err, "failed to open %s for writing", certKeyPath) + } + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return "", "", errors.Wrap(err, "unable to marshal private key") + } + if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { + return "", "", errors.Wrapf(err, "failed to write data to %s", certKeyPath) + } + if err := keyOut.Close(); err != nil { + return "", "", errors.Wrapf(err, "error closing write buffer: %s", certKeyPath) + } + log.WithField("certKeyPath", certKeyPath).Info("Wrote self-signed certificate key file") + return certPath, certKeyPath, nil +} diff --git a/beacon-chain/usage.go b/beacon-chain/usage.go index 67dfde5ca8e..59d41df8ef5 100644 --- a/beacon-chain/usage.go +++ b/beacon-chain/usage.go @@ -67,6 +67,7 @@ var appHelpFlagGroups = []flagGroup{ cmd.ConfigFileFlag, cmd.ChainConfigFileFlag, cmd.GrpcMaxCallRecvMsgSizeFlag, + cmd.InsecureGRPCFlag, }, }, { diff --git a/shared/cmd/flags.go b/shared/cmd/flags.go index 40128546ed6..93f152b02b7 100644 --- a/shared/cmd/flags.go +++ b/shared/cmd/flags.go @@ -205,7 +205,13 @@ var ( // GrpcMaxCallRecvMsgSizeFlag defines the max call message size for GRPC GrpcMaxCallRecvMsgSizeFlag = &cli.IntFlag{ Name: "grpc-max-msg-size", - Usage: "Integer to define max recieve message call size (default: 4194304 (for 4MB))", + Usage: "Integer to define max receive message call size (default: 4194304 (for 4MB))", Value: 1 << 22, } + // InsecureGRPCFlag defines using an insecure gRPC connection (not recommended). + InsecureGRPCFlag = &cli.BoolFlag{ + Name: "insecure-grpc-flag", + Usage: "Utilize an insecure grpc connection (not recommended)", + Value: false, + } ) diff --git a/validator/client/polling/service.go b/validator/client/polling/service.go index 38de2e80753..1933b1d5c2e 100644 --- a/validator/client/polling/service.go +++ b/validator/client/polling/service.go @@ -2,6 +2,7 @@ package polling import ( "context" + "crypto/tls" "strings" "github.com/dgraph-io/ristretto" @@ -33,21 +34,22 @@ var log = logrus.WithField("prefix", "validator") // ValidatorService represents a service to manage the validator client // routine. type ValidatorService struct { - ctx context.Context + insecureGRPC bool + emitAccountMetrics bool + logValidatorBalances bool cancel context.CancelFunc - validator Validator - graffiti []byte + grpcRetries uint + maxCallRecvMsgSize int conn *grpc.ClientConn - endpoint string - withCert string + protector slashingprotection.Protector dataDir string + withCert string + endpoint string keyManager keymanager.KeyManager - logValidatorBalances bool - emitAccountMetrics bool - maxCallRecvMsgSize int - grpcRetries uint + validator Validator + ctx context.Context + graffiti []byte grpcHeaders []string - protector slashingprotection.Protector } // Config for the validator service. @@ -59,6 +61,7 @@ type Config struct { KeyManager keymanager.KeyManager LogValidatorBalances bool EmitAccountMetrics bool + InsecureGRPC bool GrpcMaxCallRecvMsgSizeFlag int GrpcRetriesFlag uint GrpcHeadersFlag string @@ -76,6 +79,7 @@ func NewValidatorService(ctx context.Context, cfg *Config) (*ValidatorService, e withCert: cfg.CertFlag, dataDir: cfg.DataDir, graffiti: []byte(cfg.GraffitiFlag), + insecureGRPC: cfg.InsecureGRPC, keyManager: cfg.KeyManager, logValidatorBalances: cfg.LogValidatorBalances, emitAccountMetrics: cfg.EmitAccountMetrics, @@ -99,6 +103,7 @@ func (v *ValidatorService) Start() { v.withCert, v.grpcHeaders, v.grpcRetries, + v.insecureGRPC, streamInterceptor, ) if dialOpts == nil { @@ -202,6 +207,7 @@ func ConstructDialOptions( withCert string, grpcHeaders []string, grpcRetries uint, + insecureGRPC bool, extraOpts ...grpc.DialOption, ) []grpc.DialOption { var transportSecurity grpc.DialOption @@ -213,10 +219,17 @@ func ConstructDialOptions( } transportSecurity = grpc.WithTransportCredentials(creds) } else { - transportSecurity = grpc.WithInsecure() - log.Warn("You are using an insecure gRPC connection. If you are running your beacon node and " + - "validator on the same machines, you can ignore this message. If you want to know " + - "how to enable secure connections, see: https://docs.prylabs.network/docs/prysm-usage/secure-grpc") + if insecureGRPC { + transportSecurity = grpc.WithInsecure() + log.Warn("You are using an insecure gRPC connection. If you are running your beacon node and " + + "validator on the same machines, you can ignore this message. If you want to know " + + "how to enable secure connections, see: https://docs.prylabs.network/docs/prysm-usage/secure-grpc") + } else { + transportSecurity = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ + InsecureSkipVerify: true, + })) + log.Info("Establishing secure gRPC connection") + } } if maxCallRecvMsgSize == 0 { diff --git a/validator/client/streaming/service.go b/validator/client/streaming/service.go index d3a8f22b15b..3c409156dc1 100644 --- a/validator/client/streaming/service.go +++ b/validator/client/streaming/service.go @@ -2,6 +2,7 @@ package streaming import ( "context" + "crypto/tls" "strings" "github.com/dgraph-io/ristretto" @@ -33,36 +34,38 @@ var log = logrus.WithField("prefix", "validator") // ValidatorService represents a service to manage the validator client // routine. type ValidatorService struct { - ctx context.Context + insecureGRPC bool + emitAccountMetrics bool + logValidatorBalances bool cancel context.CancelFunc - validator Validator - graffiti []byte + grpcRetries uint + maxCallRecvMsgSize int conn *grpc.ClientConn - endpoint string - withCert string + protector slashingprotection.Protector dataDir string + withCert string + endpoint string keyManager keymanager.KeyManager - logValidatorBalances bool - emitAccountMetrics bool - maxCallRecvMsgSize int - grpcRetries uint + validator Validator + ctx context.Context + graffiti []byte grpcHeaders []string - protector slashingprotection.Protector } // Config for the validator service. type Config struct { - Endpoint string - DataDir string - CertFlag string - GraffitiFlag string - KeyManager keymanager.KeyManager - LogValidatorBalances bool + InsecureGRPC bool EmitAccountMetrics bool - GrpcMaxCallRecvMsgSizeFlag int + LogValidatorBalances bool GrpcRetriesFlag uint - GrpcHeadersFlag string + GrpcMaxCallRecvMsgSizeFlag int Protector slashingprotection.Protector + KeyManager keymanager.KeyManager + GrpcHeadersFlag string + GraffitiFlag string + CertFlag string + DataDir string + Endpoint string } // NewValidatorService creates a new validator service for the service @@ -75,6 +78,7 @@ func NewValidatorService(ctx context.Context, cfg *Config) (*ValidatorService, e endpoint: cfg.Endpoint, withCert: cfg.CertFlag, dataDir: cfg.DataDir, + insecureGRPC: cfg.InsecureGRPC, graffiti: []byte(cfg.GraffitiFlag), keyManager: cfg.KeyManager, logValidatorBalances: cfg.LogValidatorBalances, @@ -95,7 +99,13 @@ func (v *ValidatorService) Start() { grpc_retry.StreamClientInterceptor(), )) dialOpts := ConstructDialOptions( - v.maxCallRecvMsgSize, v.withCert, v.grpcHeaders, v.grpcRetries, streamInterceptor) + v.maxCallRecvMsgSize, + v.withCert, + v.grpcHeaders, + v.grpcRetries, + v.insecureGRPC, + streamInterceptor, + ) if dialOpts == nil { return } @@ -194,6 +204,7 @@ func ConstructDialOptions( withCert string, grpcHeaders []string, grpcRetries uint, + insecureGRPC bool, extraOpts ...grpc.DialOption, ) []grpc.DialOption { var transportSecurity grpc.DialOption @@ -205,8 +216,17 @@ func ConstructDialOptions( } transportSecurity = grpc.WithTransportCredentials(creds) } else { - transportSecurity = grpc.WithInsecure() - log.Warn("You are using an insecure gRPC connection! Please provide a certificate and key to use a secure connection.") + if insecureGRPC { + transportSecurity = grpc.WithInsecure() + log.Warn("You are using an insecure gRPC connection. If you are running your beacon node and " + + "validator on the same machines, you can ignore this message. If you want to know " + + "how to enable secure connections, see: https://docs.prylabs.network/docs/prysm-usage/secure-grpc") + } else { + transportSecurity = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ + InsecureSkipVerify: true, + })) + log.Info("Establishing secure gRPC connection") + } } if maxCallRecvMsgSize == 0 { diff --git a/validator/main.go b/validator/main.go index 4017c9a1390..7644545195c 100644 --- a/validator/main.go +++ b/validator/main.go @@ -81,6 +81,7 @@ var appFlags = []cli.Flag{ cmd.ConfigFileFlag, cmd.ChainConfigFileFlag, cmd.GrpcMaxCallRecvMsgSizeFlag, + cmd.InsecureGRPCFlag, debug.PProfFlag, debug.PProfAddrFlag, debug.PProfPortFlag, @@ -189,6 +190,7 @@ contract in order to activate the validator client`, cliCtx.String(flags.CertFlag.Name), strings.Split(cliCtx.String(flags.GrpcHeadersFlag.Name), ","), cliCtx.Uint(flags.GrpcRetriesFlag.Name), + false, /* insecure grpc */ grpc.WithBlock()) endpoint := cliCtx.String(flags.BeaconRPCProviderFlag.Name) conn, err := grpc.DialContext(ctx, endpoint, dialOpts...) diff --git a/validator/node/node.go b/validator/node/node.go index 09eb1790504..e3237948206 100644 --- a/validator/node/node.go +++ b/validator/node/node.go @@ -199,6 +199,8 @@ func (s *ValidatorClient) registerClientService(keyManager keymanager.KeyManager graffiti := s.cliCtx.String(flags.GraffitiFlag.Name) maxCallRecvMsgSize := s.cliCtx.Int(cmd.GrpcMaxCallRecvMsgSizeFlag.Name) grpcRetries := s.cliCtx.Uint(flags.GrpcRetriesFlag.Name) + insecureGRPC := s.cliCtx.Bool(cmd.InsecureGRPCFlag.Name) + var sp *slashing_protection.Service var protector slashing_protection.Protector if err := s.services.FetchService(&sp); err == nil { @@ -213,6 +215,7 @@ func (s *ValidatorClient) registerClientService(keyManager keymanager.KeyManager EmitAccountMetrics: emitAccountMetrics, CertFlag: cert, GraffitiFlag: graffiti, + InsecureGRPC: insecureGRPC, GrpcMaxCallRecvMsgSizeFlag: maxCallRecvMsgSize, GrpcRetriesFlag: grpcRetries, GrpcHeadersFlag: s.cliCtx.String(flags.GrpcHeadersFlag.Name), @@ -232,6 +235,7 @@ func (s *ValidatorClient) registerClientService(keyManager keymanager.KeyManager EmitAccountMetrics: emitAccountMetrics, CertFlag: cert, GraffitiFlag: graffiti, + InsecureGRPC: insecureGRPC, GrpcMaxCallRecvMsgSizeFlag: maxCallRecvMsgSize, GrpcRetriesFlag: grpcRetries, GrpcHeadersFlag: s.cliCtx.String(flags.GrpcHeadersFlag.Name), diff --git a/validator/usage.go b/validator/usage.go index 7ce2ae8691c..2809eb3c78c 100644 --- a/validator/usage.go +++ b/validator/usage.go @@ -61,6 +61,7 @@ var appHelpFlagGroups = []flagGroup{ cmd.ConfigFileFlag, cmd.ChainConfigFileFlag, cmd.GrpcMaxCallRecvMsgSizeFlag, + cmd.InsecureGRPCFlag, }, }, {