diff --git a/cmd/derper/derper.go b/cmd/derper/derper.go index 3c6fda68c..445b9c0a2 100644 --- a/cmd/derper/derper.go +++ b/cmd/derper/derper.go @@ -58,6 +58,7 @@ var ( dev = flag.Bool("dev", false, "run in localhost development mode (overrides -a)") versionFlag = flag.Bool("version", false, "print version and exit") addr = flag.String("a", ":443", "server HTTP/HTTPS listen address, in form \":port\", \"ip:port\", or for IPv6 \"[ip]:port\". If the IP is omitted, it defaults to all interfaces. Serves HTTPS if the port is 443 and/or -certmode is manual, otherwise HTTP.") + meshAddr = flag.String("mesh-addr", "", "optional, Specify a separate http/https listen address for mesh connections, if unset, mesh will use address specified in 'a'") httpPort = flag.Int("http-port", 80, "The port on which to serve HTTP. Set to -1 to disable. The listener is bound to the same IP (if any) as specified in the -a flag.") stunPort = flag.Int("stun-port", 3478, "The UDP port on which to serve STUN. The listener is bound to the same IP (if any) as specified in the -a flag.") configPath = flag.String("c", "", "config file path") @@ -203,6 +204,13 @@ func main() { s.SetVerifyClientURL(*verifyClientURL) s.SetVerifyClientURLFailOpen(*verifyFailOpen) s.SetTCPWriteTimeout(*tcpWriteTimeout) + if *meshAddr != "" { + _, port, err := net.SplitHostPort(*meshAddr) + if err != nil { + log.Fatalf("failed to parse mesh port addr %s: %v", *meshAddr, err) + } + s.SetMeshPort(port) + } var meshKey string if *dev { @@ -345,9 +353,23 @@ func main() { ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, } + var meshSrv *http.Server + if *meshAddr != "" { + // Same as httpsrv above, different port. + meshSrv = &http.Server{ + Addr: *meshAddr, + Handler: mux, + ErrorLog: quietLogger, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + } + } go func() { <-ctx.Done() httpsrv.Shutdown(ctx) + if meshSrv != nil { + meshSrv.Shutdown(ctx) + } }() if serveTLS { @@ -359,7 +381,7 @@ func main() { } httpsrv.TLSConfig = certManager.TLSConfig() getCert := httpsrv.TLSConfig.GetCertificate - httpsrv.TLSConfig.GetCertificate = func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) { + certFunc := func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) { cert, err := getCert(hi) if err != nil { return nil, err @@ -367,9 +389,10 @@ func main() { cert.Certificate = append(cert.Certificate, s.MetaCert()) return cert, nil } + httpsrv.TLSConfig.GetCertificate = certFunc // Disable TLS 1.0 and 1.1, which are obsolete and have security issues. httpsrv.TLSConfig.MinVersion = tls.VersionTLS12 - httpsrv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.TLS != nil { label := "unknown" switch r.TLS.Version { @@ -389,6 +412,17 @@ func main() { mux.ServeHTTP(w, r) }) + httpsrv.Handler = handlerFunc + + if meshSrv != nil { + go func() { + meshSrv.TLSConfig = certManager.TLSConfig() + meshSrv.TLSConfig.GetCertificate = certFunc + meshSrv.TLSConfig.MinVersion = tls.VersionTLS12 + meshSrv.Handler = handlerFunc + err = rateLimitedListenAndServeTLS(meshSrv, &lc) + }() + } if *httpPort > -1 { go func() { port80mux := http.NewServeMux() @@ -420,6 +454,17 @@ func main() { } err = rateLimitedListenAndServeTLS(httpsrv, &lc) } else { + if meshSrv != nil { + go func() { + log.Printf("derper: starting mesh on %s", *meshAddr) + var lnm net.Listener + lnm, err = lc.Listen(context.Background(), "tcp", *meshAddr) + if err != nil { + log.Fatal(err) + } + err = meshSrv.Serve(lnm) + }() + } log.Printf("derper: serving on %s", *addr) var ln net.Listener ln, err = lc.Listen(context.Background(), "tcp", httpsrv.Addr) diff --git a/derp/derp_server.go b/derp/derp_server.go index c330572d2..7811cbd93 100644 --- a/derp/derp_server.go +++ b/derp/derp_server.go @@ -23,6 +23,7 @@ import ( "math" "math/big" "math/rand/v2" + "net" "net/http" "net/netip" "os" @@ -133,6 +134,7 @@ type Server struct { logf logger.Logf memSys0 uint64 // runtime.MemStats.Sys at start (or early-ish) meshKey string + meshPort string limitedLogf logger.Logf metaCert []byte // the encoded x509 cert to send after LetsEncrypt cert+intermediate dupPolicy dupPolicy @@ -503,6 +505,10 @@ func (s *Server) SetTCPWriteTimeout(d time.Duration) { s.tcpWriteTimeout = d } +func (s *Server) SetMeshPort(port string) { + s.meshPort = port +} + // HasMeshKey reports whether the server is configured with a mesh key. func (s *Server) HasMeshKey() bool { return s.meshKey != "" } @@ -900,6 +906,8 @@ func (s *Server) accept(ctx context.Context, nc Conn, brw *bufio.ReadWriter, rem return fmt.Errorf("receive client key: %v", err) } + _, localPort, _ := net.SplitHostPort(nc.LocalAddr().String()) + remoteIPPort, _ := netip.ParseAddrPort(remoteAddr) if err := s.verifyClient(ctx, clientKey, clientInfo, remoteIPPort.Addr()); err != nil { return fmt.Errorf("client %v rejected: %v", clientKey, err) @@ -926,7 +934,7 @@ func (s *Server) accept(ctx context.Context, nc Conn, brw *bufio.ReadWriter, rem discoSendQueue: make(chan pkt, s.perClientSendQueueDepth), sendPongCh: make(chan [8]byte, 1), peerGone: make(chan peerGoneMsg), - canMesh: s.isMeshPeer(clientInfo), + canMesh: s.hasMeshPSK(clientInfo) && s.usingMeshPort(localPort), isNotIdealConn: IdealNodeContextKey.Value(ctx) != "", peerGoneLim: rate.NewLimiter(rate.Every(time.Second), 3), } @@ -1331,16 +1339,26 @@ func (c *sclient) requestMeshUpdate() { } } -// isMeshPeer reports whether the client is a trusted mesh peer -// node in the DERP region. -func (s *Server) isMeshPeer(info *clientInfo) bool { +// hasMeshPSK reports whether the client provides +// a matching pre-shared key and can be trusted. +func (s *Server) hasMeshPSK(info *clientInfo) bool { return info != nil && info.MeshKey != "" && info.MeshKey == s.meshKey } +// usingMeshPort reports if the given port is +// authorized for mesh connections. +func (s *Server) usingMeshPort(port string) bool { + if s.meshPort == "" { + return true + } + + return s.meshPort == port +} + // verifyClient checks whether the client is allowed to connect to the derper, // depending on how & whether the server's been configured to verify. func (s *Server) verifyClient(ctx context.Context, clientKey key.NodePublic, info *clientInfo, clientIP netip.Addr) error { - if s.isMeshPeer(info) { + if s.hasMeshPSK(info) { // Trusted mesh peer. No need to verify further. In fact, verifying // further wouldn't work: it's not part of the tailnet so tailscaled and // likely the admission control URL wouldn't know about it.