Merge pull request #3010 from fatedier/dev

release v0.44.0
This commit is contained in:
fatedier 2022-07-11 00:10:43 +08:00 committed by GitHub
commit 8888610d83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 142 additions and 63 deletions

View File

@ -477,6 +477,21 @@ dashboard_pwd = admin
Then visit `http://[server_addr]:7500` to see the dashboard, with username and password both being `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) ![dashboard](/doc/pic/dashboard.png)
### Admin UI ### Admin UI

View File

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

View File

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

View File

@ -17,7 +17,6 @@ package proxy
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"io" "io"
"net" "net"
"strconv" "strconv"
@ -307,7 +306,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
Sid: natHoleSidMsg.Sid, Sid: natHoleSidMsg.Sid,
} }
raddr, _ := net.ResolveUDPAddr("udp", 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) clientConn, err := net.DialUDP("udp", nil, raddr)
if err != nil { if err != nil {
xl.Error("dial server udp addr error: %v", err) 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) { 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 { if err != nil {
return err return err
} }
@ -448,7 +447,7 @@ type UDPProxy struct {
} }
func (pxy *UDPProxy) Run() (err error) { 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 { if err != nil {
return return
} }
@ -570,7 +569,7 @@ type SUDPProxy struct {
} }
func (pxy *SUDPProxy) Run() (err error) { 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 { if err != nil {
return return
} }

View File

@ -212,7 +212,7 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
} }
raddr, err := net.ResolveUDPAddr("udp", 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 { if err != nil {
xl.Error("resolve server UDP addr error") xl.Error("resolve server UDP addr error")
return return

View File

@ -37,31 +37,34 @@ var (
cfgFile string cfgFile string
showVersion bool showVersion bool
bindAddr string bindAddr string
bindPort int bindPort int
bindUDPPort int bindUDPPort int
kcpBindPort int kcpBindPort int
proxyBindAddr string proxyBindAddr string
vhostHTTPPort int vhostHTTPPort int
vhostHTTPSPort int vhostHTTPSPort int
vhostHTTPTimeout int64 vhostHTTPTimeout int64
dashboardAddr string dashboardAddr string
dashboardPort int dashboardPort int
dashboardUser string dashboardUser string
dashboardPwd string dashboardPwd string
enablePrometheus bool enablePrometheus bool
assetsDir string assetsDir string
logFile string logFile string
logLevel string logLevel string
logMaxDays int64 logMaxDays int64
disableLogColor bool disableLogColor bool
token string token string
subDomainHost string subDomainHost string
tcpMux bool tcpMux bool
allowPorts string allowPorts string
maxPoolCount int64 maxPoolCount int64
maxPortsPerClient int64 maxPortsPerClient int64
tlsOnly bool tlsOnly bool
dashboardTLSMode bool
dashboardTLSCertFile string
dashboardTLSKeyFile string
) )
func init() { func init() {
@ -91,6 +94,9 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports") rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
rootCmd.PersistentFlags().Int64VarP(&maxPortsPerClient, "max_ports_per_client", "", 0, "max ports per client") 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(&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{ var rootCmd = &cobra.Command{
@ -167,6 +173,9 @@ func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
cfg.DashboardUser = dashboardUser cfg.DashboardUser = dashboardUser
cfg.DashboardPwd = dashboardPwd cfg.DashboardPwd = dashboardPwd
cfg.EnablePrometheus = enablePrometheus cfg.EnablePrometheus = enablePrometheus
cfg.DashboardTLSCertFile = dashboardTLSCertFile
cfg.DashboardTLSKeyFile = dashboardTLSKeyFile
cfg.DashboardTLSMode = dashboardTLSMode
cfg.LogFile = logFile cfg.LogFile = logFile
cfg.LogLevel = logLevel cfg.LogLevel = logLevel
cfg.LogMaxDays = logMaxDays cfg.LogMaxDays = logMaxDays

View File

@ -43,6 +43,11 @@ dashboard_port = 7500
dashboard_user = admin dashboard_user = admin
dashboard_pwd = 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 will export prometheus metrics on {dashboard_addr}:{dashboard_port} in /metrics api.
enable_prometheus = true enable_prometheus = true

View File

@ -16,7 +16,9 @@ package config
import ( import (
"fmt" "fmt"
"net"
"reflect" "reflect"
"strconv"
"strings" "strings"
"github.com/fatedier/frp/pkg/consts" "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 != "" { 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, "/") { if !strings.HasPrefix(cfg.HealthCheckURL, "/") {
s += "/" s += "/"
} }

View File

@ -74,6 +74,17 @@ type ServerCommonConf struct {
// value is 0, the dashboard will not be started. By default, this value is // value is 0, the dashboard will not be started. By default, this value is
// 0. // 0.
DashboardPort int `ini:"dashboard_port" json:"dashboard_port" validate:"gte=0,lte=65535"` 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 // DashboardUser specifies the username that the dashboard will use for
// login. // login.
DashboardUser string `ini:"dashboard_user" json:"dashboard_user"` DashboardUser string `ini:"dashboard_user" json:"dashboard_user"`
@ -297,6 +308,23 @@ func (cfg *ServerCommonConf) Complete() {
} }
func (cfg *ServerCommonConf) Validate() error { 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) return validator.New().Struct(cfg)
} }

View File

@ -23,6 +23,7 @@ import (
"net/http/httputil" "net/http/httputil"
"strings" "strings"
"github.com/fatedier/frp/pkg/transport"
frpNet "github.com/fatedier/frp/pkg/util/net" 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 == "" { if localAddr == "" {
return nil, fmt.Errorf("plugin_local_addr is required") return nil, fmt.Errorf("plugin_local_addr is required")
} }
@ -96,7 +91,16 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
Handler: rp, 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 { if err != nil {
return nil, fmt.Errorf("gen TLS config error: %v", err) return nil, fmt.Errorf("gen TLS config error: %v", err)
} }

View File

@ -23,6 +23,7 @@ import (
"net/http/httputil" "net/http/httputil"
"strings" "strings"
"github.com/fatedier/frp/pkg/transport"
frpNet "github.com/fatedier/frp/pkg/util/net" 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 == "" { if localAddr == "" {
return nil, fmt.Errorf("plugin_local_addr is required") return nil, fmt.Errorf("plugin_local_addr is required")
} }
@ -101,7 +96,16 @@ func NewHTTPS2HTTPSPlugin(params map[string]string) (Plugin, error) {
Handler: rp, 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 { if err != nil {
return nil, fmt.Errorf("gen TLS config error: %v", err) 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 { func (p *HTTPS2HTTPSPlugin) Name() string {
return PluginHTTPS2HTTP return PluginHTTPS2HTTPS
} }
func (p *HTTPS2HTTPSPlugin) Close() error { func (p *HTTPS2HTTPSPlugin) Close() error {

View File

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

View File

@ -15,6 +15,7 @@
package server package server
import ( import (
"crypto/tls"
"net" "net"
"net/http" "net/http"
"net/http/pprof" "net/http/pprof"
@ -76,14 +77,21 @@ func (svr *Service) RunDashboardServer(address string) (err error) {
ReadTimeout: httpServerReadTimeout, ReadTimeout: httpServerReadTimeout,
WriteTimeout: httpServerWriteTimeout, WriteTimeout: httpServerWriteTimeout,
} }
if address == "" || address == ":" {
address = ":http"
}
ln, err := net.Listen("tcp", address) ln, err := net.Listen("tcp", address)
if err != nil { if err != nil {
return err 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) go server.Serve(ln)
return return
} }