mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 14:46:42 +00:00
node,rpc: adds TLS support
This commit is contained in:
parent
891fcd8ce1
commit
d8ee45a0aa
10 changed files with 332 additions and 9 deletions
|
|
@ -17,6 +17,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
|
@ -108,7 +109,8 @@ func localConsole(ctx *cli.Context) error {
|
|||
// console to it.
|
||||
func remoteConsole(ctx *cli.Context) error {
|
||||
// Attach to a remotely running geth instance and start the JavaScript console
|
||||
client, err := dialRPC(ctx.Args().First())
|
||||
dialctx := rpc.MakeTLSConfigContext(utils.MakeClientTLSConfig(ctx))
|
||||
client, err := dialRPCContext(dialctx, ctx.Args().First())
|
||||
if err != nil {
|
||||
utils.Fatalf("Unable to attach to remote geth: %v", err)
|
||||
}
|
||||
|
|
@ -137,9 +139,14 @@ func remoteConsole(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
// dialRPC returns a RPC client which connects to the given endpoint.
|
||||
func dialRPC(endpoint string) (*rpc.Client, error) {
|
||||
return dialRPCContext(context.Background(), endpoint)
|
||||
}
|
||||
|
||||
// dialRPCContext returns a RPC client which connects to the given endpoint.
|
||||
// The check for empty endpoint implements the defaulting logic
|
||||
// for "geth attach" and "geth monitor" with no argument.
|
||||
func dialRPC(endpoint string) (*rpc.Client, error) {
|
||||
func dialRPCContext(ctx context.Context, endpoint string) (*rpc.Client, error) {
|
||||
if endpoint == "" {
|
||||
endpoint = node.DefaultIPCEndpoint(clientIdentifier)
|
||||
} else if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") {
|
||||
|
|
@ -147,7 +154,7 @@ func dialRPC(endpoint string) (*rpc.Client, error) {
|
|||
// these prefixes.
|
||||
endpoint = endpoint[4:]
|
||||
}
|
||||
return rpc.Dial(endpoint)
|
||||
return rpc.DialContext(ctx, endpoint)
|
||||
}
|
||||
|
||||
// ephemeralConsole starts a new geth node, attaches an ephemeral JavaScript
|
||||
|
|
|
|||
|
|
@ -117,6 +117,11 @@ func init() {
|
|||
utils.RPCListenAddrFlag,
|
||||
utils.RPCPortFlag,
|
||||
utils.RPCApiFlag,
|
||||
utils.TLSEnabledFlag,
|
||||
utils.TLSCertFileFlag,
|
||||
utils.TLSCertCAFlag,
|
||||
utils.TLSKeyFileFlag,
|
||||
utils.TLSNoVerifyFlag,
|
||||
utils.WSEnabledFlag,
|
||||
utils.WSListenAddrFlag,
|
||||
utils.WSPortFlag,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
|
|
@ -75,7 +76,7 @@ func monitor(ctx *cli.Context) error {
|
|||
)
|
||||
// Attach to an Ethereum node over IPC or RPC
|
||||
endpoint := ctx.String(monitorCommandAttachFlag.Name)
|
||||
if client, err = dialRPC(endpoint); err != nil {
|
||||
if client, err = dialRPCContext(context.Background(), endpoint); err != nil {
|
||||
utils.Fatalf("Unable to attach to geth node: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
|
|
|||
|
|
@ -99,6 +99,11 @@ var AppHelpFlagGroups = []flagGroup{
|
|||
utils.RPCListenAddrFlag,
|
||||
utils.RPCPortFlag,
|
||||
utils.RPCApiFlag,
|
||||
utils.TLSEnabledFlag,
|
||||
utils.TLSCertFileFlag,
|
||||
utils.TLSCertCAFlag,
|
||||
utils.TLSKeyFileFlag,
|
||||
utils.TLSNoVerifyFlag,
|
||||
utils.WSEnabledFlag,
|
||||
utils.WSListenAddrFlag,
|
||||
utils.WSPortFlag,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package utils
|
|||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
|
|
@ -274,6 +275,26 @@ var (
|
|||
Usage: "API's offered over the HTTP-RPC interface",
|
||||
Value: rpc.DefaultHTTPApis,
|
||||
}
|
||||
TLSEnabledFlag = cli.BoolFlag{
|
||||
Name: "tls",
|
||||
Usage: "Activate TLS support on HTTP-RPC server",
|
||||
}
|
||||
TLSCertFileFlag = cli.StringFlag{
|
||||
Name: "tlscert",
|
||||
Usage: "TLS certificate file (auto-generated if empty, and TLS support is enabled)",
|
||||
}
|
||||
TLSCertCAFlag = cli.BoolFlag{
|
||||
Name: "tlscertca",
|
||||
Usage: "Whether provided certificate is serves as its own CA",
|
||||
}
|
||||
TLSKeyFileFlag = cli.StringFlag{
|
||||
Name: "tlskey",
|
||||
Usage: "TLS key file (auto-generated if empty, and TLS support is enabled)",
|
||||
}
|
||||
TLSNoVerifyFlag = cli.BoolFlag{
|
||||
Name: "tlsnoverify",
|
||||
Usage: "Whether server's TSL certificate must be verified or not (on connection by client)",
|
||||
}
|
||||
IPCDisabledFlag = cli.BoolFlag{
|
||||
Name: "ipcdisable",
|
||||
Usage: "Disable the IPC-RPC server",
|
||||
|
|
@ -687,6 +708,7 @@ func MakeNode(ctx *cli.Context, name, gitCommit string) *node.Node {
|
|||
WSPort: ctx.GlobalInt(WSPortFlag.Name),
|
||||
WSOrigins: ctx.GlobalString(WSAllowedOriginsFlag.Name),
|
||||
WSModules: MakeRPCModules(ctx.GlobalString(WSApiFlag.Name)),
|
||||
TLSConfig: MakeServerTLSConfig(ctx),
|
||||
}
|
||||
if ctx.GlobalBool(DevModeFlag.Name) {
|
||||
if !ctx.GlobalIsSet(DataDirFlag.Name) {
|
||||
|
|
@ -703,6 +725,9 @@ func MakeNode(ctx *cli.Context, name, gitCommit string) *node.Node {
|
|||
}
|
||||
config.NetRestrict = list
|
||||
}
|
||||
if config.TLSConfig != nil {
|
||||
config.TLSEnabled = true
|
||||
}
|
||||
|
||||
stack, err := node.New(config)
|
||||
if err != nil {
|
||||
|
|
@ -958,3 +983,56 @@ func MakeConsolePreloads(ctx *cli.Context) []string {
|
|||
}
|
||||
return preloads
|
||||
}
|
||||
|
||||
// MakeServerTLSConfig parses incoming TLS-related options, and produces configuration
|
||||
// ready to be injected into server connection's transport
|
||||
func MakeServerTLSConfig(ctx *cli.Context) *tls.Config {
|
||||
if !ctx.GlobalBool(RPCEnabledFlag.Name) {
|
||||
return nil
|
||||
}
|
||||
if !ctx.GlobalBool(TLSEnabledFlag.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
config, err := rpc.MakeServerTLSConfig(MakeHTTPRpcHost(ctx), MakeTLSCertPath(ctx), MakeTLSKeyPath(ctx))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// MakeClientTLSConfig parses incoming TLS-related options, and produces configuration
|
||||
// ready to be injected into client connection's transport
|
||||
func MakeClientTLSConfig(ctx *cli.Context) *tls.Config {
|
||||
return rpc.MakeClientTLSConfig(
|
||||
MakeTLSCertPath(ctx), MakeTLSKeyPath(ctx),
|
||||
ctx.GlobalBool(TLSCertCAFlag.Name),
|
||||
ctx.GlobalBool(TLSNoVerifyFlag.Name))
|
||||
}
|
||||
|
||||
// MakeTLSCertPath returns TLS certificate file path from CLI options
|
||||
// (or default one, if nothing is provided in options)
|
||||
func MakeTLSCertPath(ctx *cli.Context) string {
|
||||
basePath := MakeDataDir(ctx)
|
||||
|
||||
certPath := ctx.GlobalString(TLSCertFileFlag.Name)
|
||||
if certPath == "" {
|
||||
certPath = filepath.Join(basePath, rpc.DefaultTLSCertFile)
|
||||
}
|
||||
|
||||
return certPath
|
||||
}
|
||||
|
||||
// MakeTLSKeyPath returns TLS key file path from CLI options
|
||||
// (or default one, if nothing is provided in options)
|
||||
func MakeTLSKeyPath(ctx *cli.Context) string {
|
||||
basePath := MakeDataDir(ctx)
|
||||
|
||||
keyPath := ctx.GlobalString(TLSKeyFileFlag.Name)
|
||||
if keyPath == "" {
|
||||
keyPath = filepath.Join(basePath, rpc.DefaultTLSKeyFile)
|
||||
}
|
||||
|
||||
return keyPath
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -157,6 +158,12 @@ type Config struct {
|
|||
// exposed.
|
||||
HTTPModules []string
|
||||
|
||||
// TLSEnabled whether TLSConfig was correctly populated, and server is TLS-enabled node.
|
||||
TLSEnabled bool
|
||||
|
||||
// TLSConfig is used to configure TLS capability of a server
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// WSHost is the host interface on which to start the websocket RPC server. If
|
||||
// this field is empty, no websocket API endpoint will be started.
|
||||
WSHost string
|
||||
|
|
|
|||
14
node/node.go
14
node/node.go
|
|
@ -17,6 +17,7 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
|
|
@ -407,8 +408,17 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors
|
|||
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n.config.TLSEnabled {
|
||||
listener = tls.NewListener(listener, n.config.TLSConfig)
|
||||
}
|
||||
|
||||
go rpc.NewHTTPServer(cors, handler).Serve(listener)
|
||||
if n.config.TLSEnabled {
|
||||
glog.V(logger.Info).Infof("HTTPS endpoint opened: https://%s", endpoint)
|
||||
} else {
|
||||
glog.V(logger.Info).Infof("HTTP endpoint opened: http://%s", endpoint)
|
||||
}
|
||||
|
||||
// All listeners booted successfully
|
||||
n.httpEndpoint = endpoint
|
||||
|
|
@ -424,8 +434,12 @@ func (n *Node) stopHTTP() {
|
|||
n.httpListener.Close()
|
||||
n.httpListener = nil
|
||||
|
||||
if n.config.TLSEnabled {
|
||||
glog.V(logger.Info).Infof("HTTPS endpoint closed: https://%s", n.httpEndpoint)
|
||||
} else {
|
||||
glog.V(logger.Info).Infof("HTTP endpoint closed: http://%s", n.httpEndpoint)
|
||||
}
|
||||
}
|
||||
if n.httpHandler != nil {
|
||||
n.httpHandler.Stop()
|
||||
n.httpHandler = nil
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) {
|
|||
}
|
||||
switch u.Scheme {
|
||||
case "http", "https":
|
||||
return DialHTTP(rawurl)
|
||||
return DialHTTP(ctx, rawurl)
|
||||
case "ws", "wss":
|
||||
return DialWebsocket(ctx, rawurl, "")
|
||||
case "":
|
||||
|
|
|
|||
11
rpc/http.go
11
rpc/http.go
|
|
@ -64,7 +64,7 @@ func (hc *httpConn) Close() error {
|
|||
}
|
||||
|
||||
// DialHTTP creates a new RPC clients that connection to an RPC server over HTTP.
|
||||
func DialHTTP(endpoint string) (*Client, error) {
|
||||
func DialHTTP(ctx context.Context, endpoint string) (*Client, error) {
|
||||
req, err := http.NewRequest("POST", endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -74,7 +74,14 @@ func DialHTTP(endpoint string) (*Client, error) {
|
|||
|
||||
initctx := context.Background()
|
||||
return newClient(initctx, func(context.Context) (net.Conn, error) {
|
||||
return &httpConn{client: new(http.Client), req: req, closed: make(chan struct{})}, nil
|
||||
// configure client capable of TLS handling
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: TLSConfigFromContext(ctx),
|
||||
},
|
||||
}
|
||||
|
||||
return &httpConn{client: client, req: req, closed: make(chan struct{})}, nil
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
199
rpc/tls.go
Normal file
199
rpc/tls.go
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
)
|
||||
|
||||
// TLSConfigKey is used to store a TLS configuration within the connection context
|
||||
type TLSConfigKey struct{}
|
||||
|
||||
const (
|
||||
DefaultTLSCertFile = "tlscert.pem"
|
||||
DefaultTLSKeyFile = "tlskey.pem"
|
||||
)
|
||||
|
||||
var (
|
||||
validFor = 365 * 24 * time.Hour
|
||||
rsaBits = 2048
|
||||
)
|
||||
|
||||
// TLSConfigFromContext fetches TLS configuration from context.
|
||||
// Fetched config is to be normally used to configure client connection's transport.
|
||||
func TLSConfigFromContext(ctx context.Context) *tls.Config {
|
||||
config, ok := ctx.Value(TLSConfigKey{}).(*tls.Config)
|
||||
if !ok {
|
||||
return &tls.Config{}
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// MakeTLSContext packs TLS configuration into context.
|
||||
func MakeTLSConfigContext(config *tls.Config) context.Context {
|
||||
return context.WithValue(context.Background(), TLSConfigKey{}, config)
|
||||
}
|
||||
|
||||
func MakeServerTLSConfig(host string, certPath string, keyPath string) (*tls.Config, error) {
|
||||
config := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
||||
PreferServerCipherSuites: true,
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
},
|
||||
}
|
||||
|
||||
// Make sure certificate and key files do exist
|
||||
if err := EnsureCerts(certPath, keyPath, host); err != nil {
|
||||
return config, err
|
||||
}
|
||||
|
||||
// Load certificate/key
|
||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
if err == nil {
|
||||
config.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// MakeClientTLSConfig is used to configure connection context. Particularly,
|
||||
// setting up TLS support for connection transport.
|
||||
//
|
||||
// For auto-generated certificates, isCA is always true, that's generated
|
||||
// certificate is used as its own parent when signing.
|
||||
func MakeClientTLSConfig(certPath string, keyPath string, isCA bool, certNoVerify bool) *tls.Config {
|
||||
config := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
||||
PreferServerCipherSuites: true,
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
},
|
||||
}
|
||||
|
||||
// Load certificate/key
|
||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
if err == nil {
|
||||
config.Certificates = []tls.Certificate{cert}
|
||||
|
||||
// Provide CA certs
|
||||
if isCA {
|
||||
if rootCert, err := ioutil.ReadFile(certPath); err == nil {
|
||||
certPool := x509.NewCertPool()
|
||||
certPool.AppendCertsFromPEM(rootCert)
|
||||
|
||||
config.RootCAs = certPool
|
||||
config.ClientCAs = certPool
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow to skip TLS certificate verification
|
||||
if certNoVerify {
|
||||
config.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// EnsureCerts check certificate/key files, and auto-generates both if necessary
|
||||
func EnsureCerts(certPath string, keyPath string, host string) error {
|
||||
var certFileExists, keyFileExists bool
|
||||
|
||||
if _, err := os.Stat(certPath); os.IsNotExist(err) {
|
||||
certFileExists = false
|
||||
}
|
||||
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
|
||||
keyFileExists = false
|
||||
}
|
||||
|
||||
// generate cert and private key (if they are not already available)
|
||||
if !certFileExists || !keyFileExists {
|
||||
if err := Generate(certPath, keyPath, host); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Generate(certPath string, keyPath string, host string) error {
|
||||
priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate private key: %s", err)
|
||||
}
|
||||
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(validFor)
|
||||
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate serial number: %s", err)
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Acme Co"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else {
|
||||
template.DNSNames = append(template.DNSNames, host)
|
||||
}
|
||||
|
||||
template.IsCA = true
|
||||
template.KeyUsage |= x509.KeyUsageCertSign
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create certificate: %s", err)
|
||||
}
|
||||
|
||||
certOut, err := os.Create(certPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open "+certPath+" for writing: %s", err)
|
||||
}
|
||||
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
certOut.Close()
|
||||
glog.V(logger.Info).Infof("TLS: %s written", certPath)
|
||||
|
||||
keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open "+keyPath+" for writing: %s", err)
|
||||
}
|
||||
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
|
||||
keyOut.Close()
|
||||
glog.V(logger.Info).Infof("TLS: %s written", keyPath)
|
||||
return nil
|
||||
}
|
||||
Loading…
Reference in a new issue