From 3f1cbe2f437c9aad8b972b40283d2dc69a2bd6b9 Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Fri, 31 Jul 2015 17:34:56 -0400 Subject: [PATCH 1/2] corehttp: add net.Listener to ServeOption ServeOptions take the node and muxer, they should get the listener too as sometimes they need to operate on the listener address. License: MIT Signed-off-by: Juan Batiz-Benet --- cmd/ipfs/daemon.go | 5 +++-- core/corehttp/commands.go | 3 ++- core/corehttp/corehttp.go | 8 ++++---- core/corehttp/gateway.go | 5 +++-- core/corehttp/gateway_test.go | 20 ++++++++++++++++---- core/corehttp/ipns_hostname.go | 3 ++- core/corehttp/logs.go | 3 ++- core/corehttp/prometheus.go | 3 ++- core/corehttp/redirect.go | 3 ++- 9 files changed, 36 insertions(+), 17 deletions(-) diff --git a/cmd/ipfs/daemon.go b/cmd/ipfs/daemon.go index d14770ebf8c..0b23d079db7 100644 --- a/cmd/ipfs/daemon.go +++ b/cmd/ipfs/daemon.go @@ -3,6 +3,7 @@ package main import ( _ "expvar" "fmt" + "net" "net/http" _ "net/http/pprof" "os" @@ -126,8 +127,8 @@ future version, along with this notice. Please move to setting the HTTP Headers. // mostly useful to hook up things that register in the default muxer, // and don't provide a convenient http.Handler entry point, such as // expvar and http/pprof. -func defaultMux(path string) func(node *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { - return func(node *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { +func defaultMux(path string) corehttp.ServeOption { + return func(node *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { mux.Handle(path, http.DefaultServeMux) return mux, nil } diff --git a/core/corehttp/commands.go b/core/corehttp/commands.go index 0a65fcc3a72..1793c553970 100644 --- a/core/corehttp/commands.go +++ b/core/corehttp/commands.go @@ -1,6 +1,7 @@ package corehttp import ( + "net" "net/http" "os" "strings" @@ -58,7 +59,7 @@ func addHeadersFromConfig(c *cmdsHttp.ServerConfig, nc *config.Config) { } func CommandsOption(cctx commands.Context) ServeOption { - return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { + return func(n *core.IpfsNode, l net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { cfg := &cmdsHttp.ServerConfig{ CORSOpts: &cors.Options{ diff --git a/core/corehttp/corehttp.go b/core/corehttp/corehttp.go index 042f056ad24..dc221f3cdf4 100644 --- a/core/corehttp/corehttp.go +++ b/core/corehttp/corehttp.go @@ -23,16 +23,16 @@ var log = eventlog.Logger("core/server") // It returns the mux to expose to future options, which may be a new mux if it // is interested in mediating requests to future options, or the same mux // initially passed in if not. -type ServeOption func(*core.IpfsNode, *http.ServeMux) (*http.ServeMux, error) +type ServeOption func(*core.IpfsNode, net.Listener, *http.ServeMux) (*http.ServeMux, error) // makeHandler turns a list of ServeOptions into a http.Handler that implements // all of the given options, in order. -func makeHandler(n *core.IpfsNode, options ...ServeOption) (http.Handler, error) { +func makeHandler(n *core.IpfsNode, l net.Listener, options ...ServeOption) (http.Handler, error) { topMux := http.NewServeMux() mux := topMux for _, option := range options { var err error - mux, err = option(n, mux) + mux, err = option(n, l, mux) if err != nil { return nil, err } @@ -65,7 +65,7 @@ func ListenAndServe(n *core.IpfsNode, listeningMultiAddr string, options ...Serv } func Serve(node *core.IpfsNode, lis net.Listener, options ...ServeOption) error { - handler, err := makeHandler(node, options...) + handler, err := makeHandler(node, lis, options...) if err != nil { return err } diff --git a/core/corehttp/gateway.go b/core/corehttp/gateway.go index f70a1d11fbf..584a894376a 100644 --- a/core/corehttp/gateway.go +++ b/core/corehttp/gateway.go @@ -2,6 +2,7 @@ package corehttp import ( "fmt" + "net" "net/http" "sync" @@ -27,7 +28,7 @@ func NewGateway(conf GatewayConfig) *Gateway { } func (g *Gateway) ServeOption() ServeOption { - return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { // pass user's HTTP headers g.Config.Headers = n.Repo.Config().Gateway.HTTPHeaders @@ -50,7 +51,7 @@ func GatewayOption(writable bool) ServeOption { } func VersionOption() ServeOption { - return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Client Version: %s\n", id.ClientVersion) fmt.Fprintf(w, "Protocol Version: %s\n", id.IpfsVersion) diff --git a/core/corehttp/gateway_test.go b/core/corehttp/gateway_test.go index 01d4295b719..7ad3584da57 100644 --- a/core/corehttp/gateway_test.go +++ b/core/corehttp/gateway_test.go @@ -55,6 +55,14 @@ func newNodeWithMockNamesys(t *testing.T, ns mockNamesys) *core.IpfsNode { return n } +type delegatedHandler struct { + http.Handler +} + +func (dh *delegatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + dh.Handler.ServeHTTP(w, r) +} + func TestGatewayGet(t *testing.T) { t.Skip("not sure whats going on here") ns := mockNamesys{} @@ -65,7 +73,14 @@ func TestGatewayGet(t *testing.T) { } ns["example.com"] = path.FromString("/ipfs/" + k) - h, err := makeHandler(n, + // need this variable here since we need to construct handler with + // listener, and server with handler. yay cycles. + dh := &delegatedHandler{} + ts := httptest.NewServer(dh) + defer ts.Close() + + dh.Handler, err = makeHandler(n, + ts.Listener, IPNSHostnameOption(), GatewayOption(false), ) @@ -73,9 +88,6 @@ func TestGatewayGet(t *testing.T) { t.Fatal(err) } - ts := httptest.NewServer(h) - defer ts.Close() - t.Log(ts.URL) for _, test := range []struct { host string diff --git a/core/corehttp/ipns_hostname.go b/core/corehttp/ipns_hostname.go index 6f31e526884..10edb0aced3 100644 --- a/core/corehttp/ipns_hostname.go +++ b/core/corehttp/ipns_hostname.go @@ -1,6 +1,7 @@ package corehttp import ( + "net" "net/http" "strings" @@ -13,7 +14,7 @@ import ( // an IPNS name. // The rewritten request points at the resolved name on the gateway handler. func IPNSHostnameOption() ServeOption { - return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { childMux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithCancel(n.Context()) diff --git a/core/corehttp/logs.go b/core/corehttp/logs.go index 7624644cf56..59b87b9bcb7 100644 --- a/core/corehttp/logs.go +++ b/core/corehttp/logs.go @@ -2,6 +2,7 @@ package corehttp import ( "io" + "net" "net/http" core "github.com/ipfs/go-ipfs/core" @@ -36,7 +37,7 @@ func (w *writeErrNotifier) Write(b []byte) (int, error) { } func LogOption() ServeOption { - return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { mux.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) wnf, errs := newWriteErrNotifier(w) diff --git a/core/corehttp/prometheus.go b/core/corehttp/prometheus.go index d6e8ef4d099..0642c04b512 100644 --- a/core/corehttp/prometheus.go +++ b/core/corehttp/prometheus.go @@ -1,6 +1,7 @@ package corehttp import ( + "net" "net/http" prom "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus" @@ -9,7 +10,7 @@ import ( ) func PrometheusOption(path string) ServeOption { - return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { mux.Handle(path, prom.Handler()) return mux, nil } diff --git a/core/corehttp/redirect.go b/core/corehttp/redirect.go index 67d6c07739b..ec70ffaf911 100644 --- a/core/corehttp/redirect.go +++ b/core/corehttp/redirect.go @@ -1,6 +1,7 @@ package corehttp import ( + "net" "net/http" core "github.com/ipfs/go-ipfs/core" @@ -8,7 +9,7 @@ import ( func RedirectOption(path string, redirect string) ServeOption { handler := &redirectHandler{redirect} - return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { mux.Handle("/"+path+"/", handler) return mux, nil } From 3ee83a7c5eae50496a9a349330bc70e4d635393c Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Fri, 31 Jul 2015 17:36:02 -0400 Subject: [PATCH 2/2] fix cors: defaults should take the port of the listener need to do it this way to avoid VERY confusing situations where the user would change the API port (to another port, or maybe even to :0). this way things dont break on the user, and by default, users only need to change the API address and things should still "just work" License: MIT Signed-off-by: Juan Batiz-Benet --- commands/http/handler.go | 36 +++++++++++------------------ commands/http/handler_test.go | 12 ++++++++++ core/corehttp/commands.go | 43 +++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 23 deletions(-) diff --git a/commands/http/handler.go b/commands/http/handler.go index 53f63517f09..03ef3909917 100644 --- a/commands/http/handler.go +++ b/commands/http/handler.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "net/url" "strconv" "strings" @@ -55,13 +56,6 @@ const ( ACACredentials = "Access-Control-Allow-Credentials" ) -var localhostOrigins = []string{ - "http://127.0.0.1", - "https://127.0.0.1", - "http://localhost", - "https://localhost", -} - var mimeTypes = map[string]string{ cmds.JSON: "application/json", cmds.XML: "application/xml", @@ -91,21 +85,7 @@ func skipAPIHeader(h string) bool { func NewHandler(ctx cmds.Context, root *cmds.Command, cfg *ServerConfig) *Handler { if cfg == nil { - cfg = &ServerConfig{} - } - - if cfg.CORSOpts == nil { - cfg.CORSOpts = new(cors.Options) - } - - // by default, use GET, PUT, POST - if cfg.CORSOpts.AllowedMethods == nil { - cfg.CORSOpts.AllowedMethods = []string{"GET", "POST", "PUT"} - } - - // by default, only let 127.0.0.1 through. - if cfg.CORSOpts.AllowedOrigins == nil { - cfg.CORSOpts.AllowedOrigins = localhostOrigins + panic("must provide a valid ServerConfig") } // Wrap the internal handler with CORS handling-middleware. @@ -375,6 +355,16 @@ func allowReferer(r *http.Request, cfg *ServerConfig) bool { return true } + u, err := url.Parse(referer) + if err != nil { + // bad referer. but there _is_ something, so bail. + log.Debug("failed to parse referer: ", referer) + // debug because referer comes straight from the client. dont want to + // let people DOS by putting a huge referer that gets stored in log files. + return false + } + origin := u.Scheme + "://" + u.Host + // check CORS ACAOs and pretend Referer works like an origin. // this is valid for many (most?) sane uses of the API in // other applications, and will have the desired effect. @@ -384,7 +374,7 @@ func allowReferer(r *http.Request, cfg *ServerConfig) bool { } // referer is allowed explicitly - if o == referer { + if o == origin { return true } } diff --git a/commands/http/handler_test.go b/commands/http/handler_test.go index 4539d16415e..b61a4145763 100644 --- a/commands/http/handler_test.go +++ b/commands/http/handler_test.go @@ -31,6 +31,7 @@ func originCfg(origins []string) *ServerConfig { return &ServerConfig{ CORSOpts: &cors.Options{ AllowedOrigins: origins, + AllowedMethods: []string{"GET", "PUT", "POST"}, }, } } @@ -46,6 +47,13 @@ type testCase struct { ResHeaders map[string]string } +var defaultOrigins = []string{ + "http://localhost", + "http://127.0.0.1", + "https://localhost", + "https://127.0.0.1", +} + func getTestServer(t *testing.T, origins []string) *httptest.Server { cmdsCtx, err := coremock.MockCmdsCtx() if err != nil { @@ -59,6 +67,10 @@ func getTestServer(t *testing.T, origins []string) *httptest.Server { }, } + if len(origins) == 0 { + origins = defaultOrigins + } + handler := NewHandler(cmdsCtx, cmdRoot, originCfg(origins)) return httptest.NewServer(handler) } diff --git a/core/corehttp/commands.go b/core/corehttp/commands.go index 1793c553970..d96818e2a34 100644 --- a/core/corehttp/commands.go +++ b/core/corehttp/commands.go @@ -4,6 +4,7 @@ import ( "net" "net/http" "os" + "strconv" "strings" cors "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors" @@ -29,6 +30,13 @@ or ipfs daemon --api-http-header 'Access-Control-Allow-Origin: *' ` +var defaultLocalhostOrigins = []string{ + "http://127.0.0.1:", + "https://127.0.0.1:", + "http://localhost:", + "https://localhost:", +} + func addCORSFromEnv(c *cmdsHttp.ServerConfig) { origin := os.Getenv(originEnvKey) if origin != "" { @@ -58,6 +66,39 @@ func addHeadersFromConfig(c *cmdsHttp.ServerConfig, nc *config.Config) { c.Headers = nc.API.HTTPHeaders } +func addCORSDefaults(c *cmdsHttp.ServerConfig) { + // by default use localhost origins + if len(c.CORSOpts.AllowedOrigins) == 0 { + c.CORSOpts.AllowedOrigins = defaultLocalhostOrigins + } + + // by default, use GET, PUT, POST + if len(c.CORSOpts.AllowedMethods) == 0 { + c.CORSOpts.AllowedMethods = []string{"GET", "POST", "PUT"} + } +} + +func patchCORSVars(c *cmdsHttp.ServerConfig, addr net.Addr) { + + // we have to grab the port from an addr, which may be an ip6 addr. + // TODO: this should take multiaddrs and derive port from there. + port := "" + if tcpaddr, ok := addr.(*net.TCPAddr); ok { + port = strconv.Itoa(tcpaddr.Port) + } else if udpaddr, ok := addr.(*net.UDPAddr); ok { + port = strconv.Itoa(udpaddr.Port) + } + + // we're listening on tcp/udp with ports. ("udp!?" you say? yeah... it happens...) + for i, o := range c.CORSOpts.AllowedOrigins { + // TODO: allow replacing . tricky, ip4 and ip6 and hostnames... + if port != "" { + o = strings.Replace(o, "", port, -1) + } + c.CORSOpts.AllowedOrigins[i] = o + } +} + func CommandsOption(cctx commands.Context) ServeOption { return func(n *core.IpfsNode, l net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { @@ -69,6 +110,8 @@ func CommandsOption(cctx commands.Context) ServeOption { addHeadersFromConfig(cfg, n.Repo.Config()) addCORSFromEnv(cfg) + addCORSDefaults(cfg) + patchCORSVars(cfg, l.Addr()) cmdHandler := cmdsHttp.NewHandler(cctx, corecommands.Root, cfg) mux.Handle(cmdsHttp.ApiPath+"/", cmdHandler)