From 5b8b145577c2f54cbc58e0e2a76c6cd7b7765b79 Mon Sep 17 00:00:00 2001 From: fatedier Date: Sun, 5 Jun 2022 17:15:28 +0800 Subject: [PATCH 1/4] Use auto generated certificates if plugin_key_path and plugin_crt_path are empty for plugin https2https and https2http. (#2968) --- pkg/plugin/client/https2http.go | 18 +++++++++++------- pkg/plugin/client/https2https.go | 20 ++++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/pkg/plugin/client/https2http.go b/pkg/plugin/client/https2http.go index 81806e3..5aec067 100644 --- a/pkg/plugin/client/https2http.go +++ b/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) } diff --git a/pkg/plugin/client/https2https.go b/pkg/plugin/client/https2https.go index 159ed39..cefa203 100644 --- a/pkg/plugin/client/https2https.go +++ b/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 { From c652b8ef070507344f2cdd7230af1c6c10f0ee4c Mon Sep 17 00:00:00 2001 From: fatedier Date: Tue, 14 Jun 2022 14:24:34 +0800 Subject: [PATCH 2/4] fix ipv6 address parsing (#2978) --- client/admin_api.go | 20 +++++++++++--------- client/proxy/proxy.go | 9 ++++----- client/visitor.go | 2 +- pkg/config/proxy.go | 4 +++- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/client/admin_api.go b/client/admin_api.go index 5d40e2a..e434fcb 100644 --- a/client/admin_api.go +++ b/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 } diff --git a/client/proxy/proxy.go b/client/proxy/proxy.go index df5e87d..340da95 100644 --- a/client/proxy/proxy.go +++ b/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 } diff --git a/client/visitor.go b/client/visitor.go index ea54829..ba1cd7c 100644 --- a/client/visitor.go +++ b/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 diff --git a/pkg/config/proxy.go b/pkg/config/proxy.go index eb73cb3..e168520 100644 --- a/pkg/config/proxy.go +++ b/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 += "/" } From 218b354f8218b3c204c4f27268eb749f5f31024f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?EMRE=20=C3=87EL=C4=B0K?= Date: Mon, 27 Jun 2022 05:08:02 +0300 Subject: [PATCH 3/4] Server Dashboard SSL Support (#2982) --- README.md | 15 +++++++++++ cmd/frps/root.go | 59 +++++++++++++++++++++++++------------------- conf/frps_full.ini | 5 ++++ pkg/config/server.go | 28 +++++++++++++++++++++ server/dashboard.go | 14 ++++++++--- 5 files changed, 93 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 9095819..92c1493 100644 --- a/README.md +++ b/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 diff --git a/cmd/frps/root.go b/cmd/frps/root.go index cdf9267..d31944a 100644 --- a/cmd/frps/root.go +++ b/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 diff --git a/conf/frps_full.ini b/conf/frps_full.ini index 954221b..45b222f 100644 --- a/conf/frps_full.ini +++ b/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 diff --git a/pkg/config/server.go b/pkg/config/server.go index 090708a..50eb305 100644 --- a/pkg/config/server.go +++ b/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) } diff --git a/server/dashboard.go b/server/dashboard.go index 8ae1ea8..68e99f8 100644 --- a/server/dashboard.go +++ b/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 } From fa7c05c617ca872ef339bdca42836f400529180b Mon Sep 17 00:00:00 2001 From: fatedier Date: Mon, 11 Jul 2022 00:06:57 +0800 Subject: [PATCH 4/4] release note for v0.44.0 --- Release.md | 9 ++++++--- pkg/util/version/version.go | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Release.md b/Release.md index 810f675..35c8b12 100644 --- a/Release.md +++ b/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. diff --git a/pkg/util/version/version.go b/pkg/util/version/version.go index bc98a73..b5eca31 100644 --- a/pkg/util/version/version.go +++ b/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