فهرست منبع

Merge pull request #3010 from fatedier/dev

release v0.44.0
fatedier 2 سال پیش
والد
کامیت
8888610d83

+ 15 - 0
README.md

@@ -477,6 +477,21 @@ dashboard_pwd = admin
 
 Then visit `http://[server_addr]:7500` to see the dashboard, with username and password both being `admin`.
 
+Additionally, you can use HTTPS port by using your domains wildcard or normal SSL certificate:
+
+```ini
+[common]
+dashboard_port = 7500
+# dashboard's username and password are both optional
+dashboard_user = admin
+dashboard_pwd = admin
+dashboard_tls_mode = true
+dashboard_tls_cert_file = server.crt
+dashboard_tls_key_file = server.key
+```
+
+Then visit `https://[server_addr]:7500` to see the dashboard in secure HTTPS connection, with username and password both being `admin`.
+
 ![dashboard](/doc/pic/dashboard.png)
 
 ### Admin UI

+ 6 - 3
Release.md

@@ -1,5 +1,8 @@
 ### New
 
-* Added `route_by_http_user` in `http` and `tcpmux` proxy to support route to different clients by HTTP basic auth user.
-* `CONNECT` method can be forwarded in `http` type proxy.
-* Added `tcpmux_passthrough` in `tcpmux` proxy. If true, `CONNECT` request will be forwarded to frpc.
+* Use auto generated certificates if `plugin_key_path` and `plugin_crt_path` are empty for plugin `https2https` and `https2http`.
+* Server dashboard supports TLS configs.
+
+### Fix
+
+* xtcp error with IPv6 address.

+ 11 - 9
client/admin_api.go

@@ -18,9 +18,11 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
+	"net"
 	"net/http"
 	"os"
 	"sort"
+	"strconv"
 	"strings"
 
 	"github.com/fatedier/frp/client/proxy"
@@ -105,48 +107,48 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
 	switch cfg := status.Cfg.(type) {
 	case *config.TCPProxyConf:
 		if cfg.LocalPort != 0 {
-			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
+			psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
 		}
 		psr.Plugin = cfg.Plugin
 		if status.Err != "" {
-			psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
+			psr.RemoteAddr = net.JoinHostPort(serverAddr, strconv.Itoa(cfg.RemotePort))
 		} else {
 			psr.RemoteAddr = serverAddr + status.RemoteAddr
 		}
 	case *config.UDPProxyConf:
 		if cfg.LocalPort != 0 {
-			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
+			psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
 		}
 		if status.Err != "" {
-			psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
+			psr.RemoteAddr = net.JoinHostPort(serverAddr, strconv.Itoa(cfg.RemotePort))
 		} else {
 			psr.RemoteAddr = serverAddr + status.RemoteAddr
 		}
 	case *config.HTTPProxyConf:
 		if cfg.LocalPort != 0 {
-			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
+			psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
 		}
 		psr.Plugin = cfg.Plugin
 		psr.RemoteAddr = status.RemoteAddr
 	case *config.HTTPSProxyConf:
 		if cfg.LocalPort != 0 {
-			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
+			psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
 		}
 		psr.Plugin = cfg.Plugin
 		psr.RemoteAddr = status.RemoteAddr
 	case *config.STCPProxyConf:
 		if cfg.LocalPort != 0 {
-			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
+			psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
 		}
 		psr.Plugin = cfg.Plugin
 	case *config.XTCPProxyConf:
 		if cfg.LocalPort != 0 {
-			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
+			psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
 		}
 		psr.Plugin = cfg.Plugin
 	case *config.SUDPProxyConf:
 		if cfg.LocalPort != 0 {
-			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
+			psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
 		}
 		psr.Plugin = cfg.Plugin
 	}

+ 4 - 5
client/proxy/proxy.go

@@ -17,7 +17,6 @@ package proxy
 import (
 	"bytes"
 	"context"
-	"fmt"
 	"io"
 	"net"
 	"strconv"
@@ -307,7 +306,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
 		Sid:       natHoleSidMsg.Sid,
 	}
 	raddr, _ := net.ResolveUDPAddr("udp",
-		fmt.Sprintf("%s:%d", pxy.clientCfg.ServerAddr, pxy.serverUDPPort))
+		net.JoinHostPort(pxy.clientCfg.ServerAddr, strconv.Itoa(pxy.serverUDPPort)))
 	clientConn, err := net.DialUDP("udp", nil, raddr)
 	if err != nil {
 		xl.Error("dial server udp addr error: %v", err)
@@ -415,7 +414,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
 }
 
 func (pxy *XTCPProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
-	daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
+	daddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(addr, strconv.Itoa(port)))
 	if err != nil {
 		return err
 	}
@@ -448,7 +447,7 @@ type UDPProxy struct {
 }
 
 func (pxy *UDPProxy) Run() (err error) {
-	pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIP, pxy.cfg.LocalPort))
+	pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
 	if err != nil {
 		return
 	}
@@ -570,7 +569,7 @@ type SUDPProxy struct {
 }
 
 func (pxy *SUDPProxy) Run() (err error) {
-	pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIP, pxy.cfg.LocalPort))
+	pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
 	if err != nil {
 		return
 	}

+ 1 - 1
client/visitor.go

@@ -212,7 +212,7 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
 	}
 
 	raddr, err := net.ResolveUDPAddr("udp",
-		fmt.Sprintf("%s:%d", sv.ctl.clientCfg.ServerAddr, sv.ctl.serverUDPPort))
+		net.JoinHostPort(sv.ctl.clientCfg.ServerAddr, strconv.Itoa(sv.ctl.serverUDPPort)))
 	if err != nil {
 		xl.Error("resolve server UDP addr error")
 		return

+ 34 - 25
cmd/frps/root.go

@@ -37,31 +37,34 @@ var (
 	cfgFile     string
 	showVersion bool
 
-	bindAddr          string
-	bindPort          int
-	bindUDPPort       int
-	kcpBindPort       int
-	proxyBindAddr     string
-	vhostHTTPPort     int
-	vhostHTTPSPort    int
-	vhostHTTPTimeout  int64
-	dashboardAddr     string
-	dashboardPort     int
-	dashboardUser     string
-	dashboardPwd      string
-	enablePrometheus  bool
-	assetsDir         string
-	logFile           string
-	logLevel          string
-	logMaxDays        int64
-	disableLogColor   bool
-	token             string
-	subDomainHost     string
-	tcpMux            bool
-	allowPorts        string
-	maxPoolCount      int64
-	maxPortsPerClient int64
-	tlsOnly           bool
+	bindAddr             string
+	bindPort             int
+	bindUDPPort          int
+	kcpBindPort          int
+	proxyBindAddr        string
+	vhostHTTPPort        int
+	vhostHTTPSPort       int
+	vhostHTTPTimeout     int64
+	dashboardAddr        string
+	dashboardPort        int
+	dashboardUser        string
+	dashboardPwd         string
+	enablePrometheus     bool
+	assetsDir            string
+	logFile              string
+	logLevel             string
+	logMaxDays           int64
+	disableLogColor      bool
+	token                string
+	subDomainHost        string
+	tcpMux               bool
+	allowPorts           string
+	maxPoolCount         int64
+	maxPortsPerClient    int64
+	tlsOnly              bool
+	dashboardTLSMode     bool
+	dashboardTLSCertFile string
+	dashboardTLSKeyFile  string
 )
 
 func init() {
@@ -91,6 +94,9 @@ func init() {
 	rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
 	rootCmd.PersistentFlags().Int64VarP(&maxPortsPerClient, "max_ports_per_client", "", 0, "max ports per client")
 	rootCmd.PersistentFlags().BoolVarP(&tlsOnly, "tls_only", "", false, "frps tls only")
+	rootCmd.PersistentFlags().BoolVarP(&dashboardTLSMode, "dashboard_tls_mode", "", false, "dashboard tls mode")
+	rootCmd.PersistentFlags().StringVarP(&dashboardTLSCertFile, "dashboard_tls_cert_file", "", "", "dashboard tls cert file")
+	rootCmd.PersistentFlags().StringVarP(&dashboardTLSKeyFile, "dashboard_tls_key_file", "", "", "dashboard tls key file")
 }
 
 var rootCmd = &cobra.Command{
@@ -167,6 +173,9 @@ func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
 	cfg.DashboardUser = dashboardUser
 	cfg.DashboardPwd = dashboardPwd
 	cfg.EnablePrometheus = enablePrometheus
+	cfg.DashboardTLSCertFile = dashboardTLSCertFile
+	cfg.DashboardTLSKeyFile = dashboardTLSKeyFile
+	cfg.DashboardTLSMode = dashboardTLSMode
 	cfg.LogFile = logFile
 	cfg.LogLevel = logLevel
 	cfg.LogMaxDays = logMaxDays

+ 5 - 0
conf/frps_full.ini

@@ -43,6 +43,11 @@ dashboard_port = 7500
 dashboard_user = admin
 dashboard_pwd = admin
 
+# dashboard TLS mode
+dashboard_tls_mode = false
+# dashboard_tls_cert_file = server.crt
+# dashboard_tls_key_file = server.key
+
 # enable_prometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port} in /metrics api.
 enable_prometheus = true
 

+ 3 - 1
pkg/config/proxy.go

@@ -16,7 +16,9 @@ package config
 
 import (
 	"fmt"
+	"net"
 	"reflect"
+	"strconv"
 	"strings"
 
 	"github.com/fatedier/frp/pkg/consts"
@@ -372,7 +374,7 @@ func (cfg *BaseProxyConf) decorate(prefix string, name string, section *ini.Sect
 	}
 
 	if cfg.HealthCheckType == "http" && cfg.Plugin == "" && cfg.HealthCheckURL != "" {
-		s := fmt.Sprintf("http://%s:%d", cfg.LocalIP, cfg.LocalPort)
+		s := "http://" + net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
 		if !strings.HasPrefix(cfg.HealthCheckURL, "/") {
 			s += "/"
 		}

+ 28 - 0
pkg/config/server.go

@@ -74,6 +74,17 @@ type ServerCommonConf struct {
 	// value is 0, the dashboard will not be started. By default, this value is
 	// 0.
 	DashboardPort int `ini:"dashboard_port" json:"dashboard_port" validate:"gte=0,lte=65535"`
+	// DashboardTLSCertFile specifies the path of the cert file that the server will
+	// load. If "dashboard_tls_cert_file", "dashboard_tls_key_file" are valid, the server will use this
+	// supplied tls configuration.
+	DashboardTLSCertFile string `ini:"dashboard_tls_cert_file" json:"dashboard_tls_cert_file"`
+	// DashboardTLSKeyFile specifies the path of the secret key that the server will
+	// load. If "dashboard_tls_cert_file", "dashboard_tls_key_file" are valid, the server will use this
+	// supplied tls configuration.
+	DashboardTLSKeyFile string `ini:"dashboard_tls_key_file" json:"dashboard_tls_key_file"`
+	// DashboardTLSMode specifies the mode of the dashboard between HTTP or HTTPS modes. By
+	// default, this value is false, which is HTTP mode.
+	DashboardTLSMode bool `ini:"dashboard_tls_mode" json:"dashboard_tls_mode"`
 	// DashboardUser specifies the username that the dashboard will use for
 	// login.
 	DashboardUser string `ini:"dashboard_user" json:"dashboard_user"`
@@ -297,6 +308,23 @@ func (cfg *ServerCommonConf) Complete() {
 }
 
 func (cfg *ServerCommonConf) Validate() error {
+	if cfg.DashboardTLSMode == false {
+		if cfg.DashboardTLSCertFile != "" {
+			fmt.Println("WARNING! dashboard_tls_cert_file is invalid when dashboard_tls_mode is false")
+		}
+
+		if cfg.DashboardTLSKeyFile != "" {
+			fmt.Println("WARNING! dashboard_tls_key_file is invalid when dashboard_tls_mode is false")
+		}
+	} else {
+		if cfg.DashboardTLSCertFile == "" {
+			return fmt.Errorf("ERROR! dashboard_tls_cert_file must be specified when dashboard_tls_mode is true")
+		}
+
+		if cfg.DashboardTLSKeyFile == "" {
+			return fmt.Errorf("ERROR! dashboard_tls_cert_file must be specified when dashboard_tls_mode is true")
+		}
+	}
 	return validator.New().Struct(cfg)
 }
 

+ 11 - 7
pkg/plugin/client/https2http.go

@@ -23,6 +23,7 @@ import (
 	"net/http/httputil"
 	"strings"
 
+	"github.com/fatedier/frp/pkg/transport"
 	frpNet "github.com/fatedier/frp/pkg/util/net"
 )
 
@@ -58,12 +59,6 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
 		}
 	}
 
-	if crtPath == "" {
-		return nil, fmt.Errorf("plugin_crt_path is required")
-	}
-	if keyPath == "" {
-		return nil, fmt.Errorf("plugin_key_path is required")
-	}
 	if localAddr == "" {
 		return nil, fmt.Errorf("plugin_local_addr is required")
 	}
@@ -96,7 +91,16 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
 		Handler: rp,
 	}
 
-	tlsConfig, err := p.genTLSConfig()
+	var (
+		tlsConfig *tls.Config
+		err       error
+	)
+	if crtPath != "" || keyPath != "" {
+		tlsConfig, err = p.genTLSConfig()
+	} else {
+		tlsConfig, err = transport.NewServerTLSConfig("", "", "")
+		tlsConfig.InsecureSkipVerify = true
+	}
 	if err != nil {
 		return nil, fmt.Errorf("gen TLS config error: %v", err)
 	}

+ 12 - 8
pkg/plugin/client/https2https.go

@@ -23,6 +23,7 @@ import (
 	"net/http/httputil"
 	"strings"
 
+	"github.com/fatedier/frp/pkg/transport"
 	frpNet "github.com/fatedier/frp/pkg/util/net"
 )
 
@@ -58,12 +59,6 @@ func NewHTTPS2HTTPSPlugin(params map[string]string) (Plugin, error) {
 		}
 	}
 
-	if crtPath == "" {
-		return nil, fmt.Errorf("plugin_crt_path is required")
-	}
-	if keyPath == "" {
-		return nil, fmt.Errorf("plugin_key_path is required")
-	}
 	if localAddr == "" {
 		return nil, fmt.Errorf("plugin_local_addr is required")
 	}
@@ -101,7 +96,16 @@ func NewHTTPS2HTTPSPlugin(params map[string]string) (Plugin, error) {
 		Handler: rp,
 	}
 
-	tlsConfig, err := p.genTLSConfig()
+	var (
+		tlsConfig *tls.Config
+		err       error
+	)
+	if crtPath != "" || keyPath != "" {
+		tlsConfig, err = p.genTLSConfig()
+	} else {
+		tlsConfig, err = transport.NewServerTLSConfig("", "", "")
+		tlsConfig.InsecureSkipVerify = true
+	}
 	if err != nil {
 		return nil, fmt.Errorf("gen TLS config error: %v", err)
 	}
@@ -127,7 +131,7 @@ func (p *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, e
 }
 
 func (p *HTTPS2HTTPSPlugin) Name() string {
-	return PluginHTTPS2HTTP
+	return PluginHTTPS2HTTPS
 }
 
 func (p *HTTPS2HTTPSPlugin) Close() error {

+ 1 - 1
pkg/util/version/version.go

@@ -19,7 +19,7 @@ import (
 	"strings"
 )
 
-var version string = "0.43.0"
+var version string = "0.44.0"
 
 func Full() string {
 	return version

+ 11 - 3
server/dashboard.go

@@ -15,6 +15,7 @@
 package server
 
 import (
+	"crypto/tls"
 	"net"
 	"net/http"
 	"net/http/pprof"
@@ -76,14 +77,21 @@ func (svr *Service) RunDashboardServer(address string) (err error) {
 		ReadTimeout:  httpServerReadTimeout,
 		WriteTimeout: httpServerWriteTimeout,
 	}
-	if address == "" || address == ":" {
-		address = ":http"
-	}
 	ln, err := net.Listen("tcp", address)
 	if err != nil {
 		return err
 	}
 
+	if svr.cfg.DashboardTLSMode {
+		cert, err := tls.LoadX509KeyPair(svr.cfg.DashboardTLSCertFile, svr.cfg.DashboardTLSKeyFile)
+		if err != nil {
+			return err
+		}
+		tlsCfg := &tls.Config{
+			Certificates: []tls.Certificate{cert},
+		}
+		ln = tls.NewListener(ln, tlsCfg)
+	}
 	go server.Serve(ln)
 	return
 }