From a1698fdc8b75330b099bf6c919286d6e8b75b728 Mon Sep 17 00:00:00 2001 From: Siong Ong Date: Tue, 29 May 2018 13:07:31 +0800 Subject: [PATCH] rpc: support http basic authentication for RPC server. --- cmd/clef/main.go | 4 ++-- cmd/geth/main.go | 2 ++ cmd/geth/usage.go | 2 ++ cmd/utils/flags.go | 16 ++++++++++++++++ node/api.go | 6 +++++- node/config.go | 8 ++++++++ node/node.go | 6 +++--- rpc/endpoints.go | 4 ++-- rpc/http.go | 23 ++++++++++++++++++++++- 9 files changed, 62 insertions(+), 9 deletions(-) diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 348bcb22f6..5408262a9f 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -414,8 +414,8 @@ func signer(c *cli.Context) error { cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name)) // start http server - httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name)) - listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts) + httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name))) + listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, c.String(utils.RPCUserFlag.Name, c.String(utils.RPCPasswordFlag.Name)) if err != nil { utils.Fatalf("Could not start RPC api: %v", err) } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 09d9c493d1..b478b7ff20 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -123,6 +123,8 @@ var ( rpcFlags = []cli.Flag{ utils.RPCEnabledFlag, + utils.RPCUserFlag, + utils.RPCPasswordFlag, utils.RPCListenAddrFlag, utils.RPCPortFlag, utils.RPCApiFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index d934c6b021..f8797b23db 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -145,6 +145,8 @@ var AppHelpFlagGroups = []flagGroup{ Name: "API AND CONSOLE", Flags: []cli.Flag{ utils.RPCEnabledFlag, + utils.RPCUserFlag, + utils.RPCPasswordFlag, utils.RPCListenAddrFlag, utils.RPCPortFlag, utils.RPCApiFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index ef5f6a9f08..f62a0d5d7d 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -377,6 +377,16 @@ var ( Name: "rpc", 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{ Name: "rpcaddr", Usage: "HTTP-RPC server listening interface", @@ -684,6 +694,12 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(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) { cfg.HTTPCors = splitAndTrim(ctx.GlobalString(RPCCORSDomainFlag.Name)) } diff --git a/node/api.go b/node/api.go index da9da5bd72..c834363022 100644 --- a/node/api.go +++ b/node/api.go @@ -133,6 +133,10 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis port = &api.node.config.HTTPPort } + user := api.node.config.HTTPUser + + password := api.node.config.HTTPPassword + allowedOrigins := api.node.config.HTTPCors if cors != 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 true, nil diff --git a/node/config.go b/node/config.go index 486eddf925..6d1eafb5e5 100644 --- a/node/config.go +++ b/node/config.go @@ -95,6 +95,14 @@ type Config struct { // field is empty, no HTTP API endpoint will be started. 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 // default zero value is/ valid and will pick a port number randomly (useful // for ephemeral nodes). diff --git a/node/node.go b/node/node.go index c4368189f7..b15ef0211e 100644 --- a/node/node.go +++ b/node/node.go @@ -263,7 +263,7 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error { n.stopInProc() 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.stopInProc() return err @@ -331,12 +331,12 @@ func (n *Node) stopIPC() { } // 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 if endpoint == "" { 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 { return err } diff --git a/rpc/endpoints.go b/rpc/endpoints.go index 692c62d3a4..aa3473f27d 100644 --- a/rpc/endpoints.go +++ b/rpc/endpoints.go @@ -23,7 +23,7 @@ import ( ) // 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 whitelist := make(map[string]bool) 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 { return nil, nil, err } - go NewHTTPServer(cors, vhosts, handler).Serve(listener) + go NewHTTPServer(cors, vhosts, user, password, handler).Serve(listener) return listener, handler, err } diff --git a/rpc/http.go b/rpc/http.go index feaa7348c4..b850ce26f8 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -19,6 +19,7 @@ package rpc import ( "bytes" "context" + "crypto/subtle" "encoding/json" "errors" "fmt" @@ -31,6 +32,7 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/log" "github.com/rs/cors" ) @@ -161,10 +163,15 @@ func (t *httpReadWriteNopCloser) Close() error { // NewHTTPServer creates a new HTTP RPC server around an API provider. // // 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 handler := newCorsHandler(srv, cors) 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} } @@ -272,3 +279,17 @@ func newVHostHandler(vhosts []string, next http.Handler) http.Handler { } 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) + }) +}