rpc: support http basic authentication for RPC server.

This commit is contained in:
Siong Ong 2018-05-29 13:07:31 +08:00
parent ccc0debb63
commit a1698fdc8b
9 changed files with 62 additions and 9 deletions

View file

@ -414,8 +414,8 @@ func signer(c *cli.Context) error {
cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name)) cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name))
// start http server // start http server
httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name)) httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name)))
listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts) listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, c.String(utils.RPCUserFlag.Name, c.String(utils.RPCPasswordFlag.Name))
if err != nil { if err != nil {
utils.Fatalf("Could not start RPC api: %v", err) utils.Fatalf("Could not start RPC api: %v", err)
} }

View file

@ -123,6 +123,8 @@ var (
rpcFlags = []cli.Flag{ rpcFlags = []cli.Flag{
utils.RPCEnabledFlag, utils.RPCEnabledFlag,
utils.RPCUserFlag,
utils.RPCPasswordFlag,
utils.RPCListenAddrFlag, utils.RPCListenAddrFlag,
utils.RPCPortFlag, utils.RPCPortFlag,
utils.RPCApiFlag, utils.RPCApiFlag,

View file

@ -145,6 +145,8 @@ var AppHelpFlagGroups = []flagGroup{
Name: "API AND CONSOLE", Name: "API AND CONSOLE",
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.RPCEnabledFlag, utils.RPCEnabledFlag,
utils.RPCUserFlag,
utils.RPCPasswordFlag,
utils.RPCListenAddrFlag, utils.RPCListenAddrFlag,
utils.RPCPortFlag, utils.RPCPortFlag,
utils.RPCApiFlag, utils.RPCApiFlag,

View file

@ -377,6 +377,16 @@ var (
Name: "rpc", Name: "rpc",
Usage: "Enable the HTTP-RPC server", Usage: "Enable the HTTP-RPC server",
} }
RPCUserFlag = cli.StringFlag{
Name: "rpcuser",
Usage: "HTTP-RPC server user for basic authentication",
Value: "",
}
RPCPasswordFlag = cli.StringFlag{
Name: "rpcpassword",
Usage: "HTTP-RPC server password for basic authentication",
Value: "",
}
RPCListenAddrFlag = cli.StringFlag{ RPCListenAddrFlag = cli.StringFlag{
Name: "rpcaddr", Name: "rpcaddr",
Usage: "HTTP-RPC server listening interface", Usage: "HTTP-RPC server listening interface",
@ -684,6 +694,12 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) {
if ctx.GlobalIsSet(RPCPortFlag.Name) { if ctx.GlobalIsSet(RPCPortFlag.Name) {
cfg.HTTPPort = ctx.GlobalInt(RPCPortFlag.Name) cfg.HTTPPort = ctx.GlobalInt(RPCPortFlag.Name)
} }
if ctx.GlobalIsSet(RPCUserFlag.Name) {
cfg.HTTPUser = ctx.GlobalString(RPCUserFlag.Name)
}
if ctx.GlobalIsSet(RPCPasswordFlag.Name) {
cfg.HTTPPassword = ctx.GlobalString(RPCPasswordFlag.Name)
}
if ctx.GlobalIsSet(RPCCORSDomainFlag.Name) { if ctx.GlobalIsSet(RPCCORSDomainFlag.Name) {
cfg.HTTPCors = splitAndTrim(ctx.GlobalString(RPCCORSDomainFlag.Name)) cfg.HTTPCors = splitAndTrim(ctx.GlobalString(RPCCORSDomainFlag.Name))
} }

View file

@ -133,6 +133,10 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis
port = &api.node.config.HTTPPort port = &api.node.config.HTTPPort
} }
user := api.node.config.HTTPUser
password := api.node.config.HTTPPassword
allowedOrigins := api.node.config.HTTPCors allowedOrigins := api.node.config.HTTPCors
if cors != nil { if cors != nil {
allowedOrigins = nil allowedOrigins = nil
@ -157,7 +161,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis
} }
} }
if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts); err != nil { if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts, user, password); err != nil {
return false, err return false, err
} }
return true, nil return true, nil

View file

@ -95,6 +95,14 @@ type Config struct {
// field is empty, no HTTP API endpoint will be started. // field is empty, no HTTP API endpoint will be started.
HTTPHost string `toml:",omitempty"` HTTPHost string `toml:",omitempty"`
// HTTPUser is the username on which the HTTP RPC server uses for basic authentication. If
// this field is empty, no basic authentication is used.
HTTPUser string `toml:",omitempty"`
// HTTPPassword is the password on which the HTTP RPC server uses for basic authentication. If
// this field is empty, no basic authentication is used.
HTTPPassword string `toml:",omitempty"`
// HTTPPort is the TCP port number on which to start the HTTP RPC server. The // HTTPPort is the TCP port number on which to start the HTTP RPC server. The
// default zero value is/ valid and will pick a port number randomly (useful // default zero value is/ valid and will pick a port number randomly (useful
// for ephemeral nodes). // for ephemeral nodes).

View file

@ -263,7 +263,7 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error {
n.stopInProc() n.stopInProc()
return err return err
} }
if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts); err != nil { if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPUser, n.config.HTTPUser); err != nil {
n.stopIPC() n.stopIPC()
n.stopInProc() n.stopInProc()
return err return err
@ -331,12 +331,12 @@ func (n *Node) stopIPC() {
} }
// startHTTP initializes and starts the HTTP RPC endpoint. // startHTTP initializes and starts the HTTP RPC endpoint.
func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string) error { func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string, user string, password string) error {
// Short circuit if the HTTP endpoint isn't being exposed // Short circuit if the HTTP endpoint isn't being exposed
if endpoint == "" { if endpoint == "" {
return nil return nil
} }
listener, handler, err := rpc.StartHTTPEndpoint(endpoint, apis, modules, cors, vhosts) listener, handler, err := rpc.StartHTTPEndpoint(endpoint, apis, modules, cors, vhosts, user, password)
if err != nil { if err != nil {
return err return err
} }

View file

@ -23,7 +23,7 @@ import (
) )
// StartHTTPEndpoint starts the HTTP RPC endpoint, configured with cors/vhosts/modules // StartHTTPEndpoint starts the HTTP RPC endpoint, configured with cors/vhosts/modules
func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []string, vhosts []string) (net.Listener, *Server, error) { func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []string, vhosts []string, user string, password string) (net.Listener, *Server, error) {
// Generate the whitelist based on the allowed modules // Generate the whitelist based on the allowed modules
whitelist := make(map[string]bool) whitelist := make(map[string]bool)
for _, module := range modules { for _, module := range modules {
@ -47,7 +47,7 @@ func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []str
if listener, err = net.Listen("tcp", endpoint); err != nil { if listener, err = net.Listen("tcp", endpoint); err != nil {
return nil, nil, err return nil, nil, err
} }
go NewHTTPServer(cors, vhosts, handler).Serve(listener) go NewHTTPServer(cors, vhosts, user, password, handler).Serve(listener)
return listener, handler, err return listener, handler, err
} }

View file

@ -19,6 +19,7 @@ package rpc
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/subtle"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -31,6 +32,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum/log"
"github.com/rs/cors" "github.com/rs/cors"
) )
@ -161,10 +163,15 @@ func (t *httpReadWriteNopCloser) Close() error {
// NewHTTPServer creates a new HTTP RPC server around an API provider. // NewHTTPServer creates a new HTTP RPC server around an API provider.
// //
// Deprecated: Server implements http.Handler // Deprecated: Server implements http.Handler
func NewHTTPServer(cors []string, vhosts []string, srv *Server) *http.Server { func NewHTTPServer(cors []string, vhosts []string, user string, password string, srv *Server) *http.Server {
// Wrap the CORS-handler within a host-handler // Wrap the CORS-handler within a host-handler
handler := newCorsHandler(srv, cors) handler := newCorsHandler(srv, cors)
handler = newVHostHandler(vhosts, handler) handler = newVHostHandler(vhosts, handler)
if user != "" && password != "" {
log.Info("HTTP endpoint is secured by basic authentication.")
handler = newBasicAuthHandler(user, password, handler)
}
return &http.Server{Handler: handler} return &http.Server{Handler: handler}
} }
@ -272,3 +279,17 @@ func newVHostHandler(vhosts []string, next http.Handler) http.Handler {
} }
return &virtualHostHandler{vhostMap, next} return &virtualHostHandler{vhostMap, next}
} }
func newBasicAuthHandler(username string, password string, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 {
w.Header().Set("WWW-Authenticate", `Basic realm="Please use the right user and password"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}