Browse Source

Merge pull request #40 from fatedier/fatedier/privilege_mode

support privilege mode
fatedier 8 years ago
parent
commit
1bad5c6561

+ 20 - 0
conf/frpc.ini

@@ -9,6 +9,8 @@ log_level = info
 log_max_days = 3
 # for authentication
 auth_token = 123
+# for privilege mode
+privilege_key = 12345678
 
 # ssh is the proxy name same as server's configuration
 [ssh]
@@ -32,3 +34,21 @@ use_gzip = true
 type = http
 local_ip = 127.0.0.1
 local_port = 8000
+
+[privilege_ssh]
+# if privilege_mode is enabled, this proxy will be created automatically
+privilege_mode = true
+type = tcp
+local_ip = 127.0.0.1
+local_port = 22
+use_encryption = true
+use_gzip = false
+remote_port = 6001
+
+[privilege_web]
+privilege_mode = true
+type = http
+local_ip = 127.0.0.1
+local_port = 80
+use_gzip = true
+custom_domains = web03.yourdomain.com

+ 3 - 0
conf/frps.ini

@@ -12,6 +12,9 @@ log_file = ./frps.log
 # debug, info, warn, error
 log_level = info
 log_max_days = 3
+# if you enable privilege mode, frpc can create a proxy without pre-configure in frps when privilege_key is correct
+privilege_mode = true
+privilege_key = 12345678
 
 # ssh is the proxy name, client will use this name and auth_token to connect to server
 [ssh]

+ 7 - 0
src/frp/cmd/frpc/control.go

@@ -144,8 +144,15 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
 		AuthKey:       authKey,
 		UseEncryption: cli.UseEncryption,
 		UseGzip:       cli.UseGzip,
+		PrivilegeMode: cli.PrivilegeMode,
+		ProxyType:     cli.Type,
 		Timestamp:     nowTime,
 	}
+	if cli.PrivilegeMode {
+		req.RemotePort = cli.RemotePort
+		req.CustomDomains = cli.CustomDomains
+	}
+
 	buf, _ := json.Marshal(req)
 	err = c.Write(string(buf) + "\n")
 	if err != nil {

+ 57 - 16
src/frp/cmd/frps/control.go

@@ -194,30 +194,68 @@ func msgSender(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}
 // if success, ret equals 0, otherwise greater than 0
 func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
 	ret = 1
-	// check if proxy name exist
-	s, ok := server.ProxyServers[req.ProxyName]
-	if !ok {
-		info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName)
-		log.Warn(info)
+	if req.PrivilegeMode && !server.PrivilegeMode {
+		info = fmt.Sprintf("ProxyName [%s], PrivilegeMode is disabled in frps", req.ProxyName)
+		log.Warn("info")
 		return
 	}
 
-	// check authKey
+	var (
+		s  *server.ProxyServer
+		ok bool
+	)
+	s, ok = server.ProxyServers[req.ProxyName]
+	if req.PrivilegeMode && req.Type == consts.NewCtlConn {
+		log.Debug("ProxyName [%s], doLogin and privilege mode is enabled", req.ProxyName)
+	} else {
+		if !ok {
+			info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName)
+			log.Warn(info)
+			return
+		}
+	}
+
+	// check authKey or privilegeKey
 	nowTime := time.Now().Unix()
-	authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp))
-	// authKey avaiable in 15 minutes
-	if nowTime-req.Timestamp > 15*60 {
-		info = fmt.Sprintf("ProxyName [%s], authorization timeout", req.ProxyName)
-		log.Warn(info)
-		return
-	} else if req.AuthKey != authKey {
-		info = fmt.Sprintf("ProxyName [%s], authorization failed", req.ProxyName)
-		log.Warn(info)
-		return
+	if req.PrivilegeMode {
+		privilegeKey := pcrypto.GetAuthKey(req.ProxyName + server.PrivilegeKey + fmt.Sprintf("%d", req.Timestamp))
+		// privilegeKey avaiable in 15 minutes
+		if nowTime-req.Timestamp > 15*60 {
+			info = fmt.Sprintf("ProxyName [%s], privilege mode authorization timeout", req.ProxyName)
+			log.Warn(info)
+			return
+		} else if req.AuthKey != privilegeKey {
+			log.Debug("%s  %s", req.AuthKey, privilegeKey)
+			info = fmt.Sprintf("ProxyName [%s], privilege mode authorization failed", req.ProxyName)
+			log.Warn(info)
+			return
+		}
+	} else {
+		authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp))
+		// authKey avaiable in 15 minutes
+		if nowTime-req.Timestamp > 15*60 {
+			info = fmt.Sprintf("ProxyName [%s], authorization timeout", req.ProxyName)
+			log.Warn(info)
+			return
+		} else if req.AuthKey != authKey {
+			info = fmt.Sprintf("ProxyName [%s], authorization failed", req.ProxyName)
+			log.Warn(info)
+			return
+		}
 	}
 
 	// control conn
 	if req.Type == consts.NewCtlConn {
+		if req.PrivilegeMode {
+			s = server.NewProxyServerFromCtlMsg(req)
+			err := server.CreateProxy(s)
+			if err != nil {
+				info = fmt.Sprintf("ProxyName [%s], %v", req.ProxyName, err)
+				log.Warn(info)
+				return
+			}
+		}
+
 		if s.Status == consts.Working {
 			info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName)
 			log.Warn(info)
@@ -248,6 +286,9 @@ func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
 			return
 		}
 		log.Info("ProxyName [%s], start proxy success", req.ProxyName)
+		if req.PrivilegeMode {
+			log.Info("ProxyName [%s], created by PrivilegeMode", req.ProxyName)
+		}
 	} else if req.Type == consts.NewWorkConn {
 		// work conn
 		if s.Status != consts.Working {

+ 3 - 0
src/frp/cmd/frps/main.go

@@ -172,5 +172,8 @@ func main() {
 	}
 
 	log.Info("Start frps success")
+	if server.PrivilegeMode == true {
+		log.Info("PrivilegeMode is enabled, you should pay more attention to security issues")
+	}
 	ProcessControlConn(l)
 }

+ 8 - 4
src/frp/models/client/client.go

@@ -31,6 +31,9 @@ type ProxyClient struct {
 	config.BaseConf
 	LocalIp   string
 	LocalPort int64
+
+	RemotePort    int64
+	CustomDomains []string
 }
 
 func (p *ProxyClient) GetLocalConn() (c *conn.Conn, err error) {
@@ -57,10 +60,11 @@ func (p *ProxyClient) GetRemoteConn(addr string, port int64) (c *conn.Conn, err
 	nowTime := time.Now().Unix()
 	authKey := pcrypto.GetAuthKey(p.Name + p.AuthToken + fmt.Sprintf("%d", nowTime))
 	req := &msg.ControlReq{
-		Type:      consts.NewWorkConn,
-		ProxyName: p.Name,
-		AuthKey:   authKey,
-		Timestamp: nowTime,
+		Type:          consts.NewWorkConn,
+		ProxyName:     p.Name,
+		AuthKey:       authKey,
+		PrivilegeMode: p.PrivilegeMode,
+		Timestamp:     nowTime,
 	}
 
 	buf, _ := json.Marshal(req)

+ 75 - 18
src/frp/models/client/config.go

@@ -17,6 +17,7 @@ package client
 import (
 	"fmt"
 	"strconv"
+	"strings"
 
 	ini "github.com/vaughan0/go-ini"
 )
@@ -29,6 +30,7 @@ var (
 	LogWay            string = "console"
 	LogLevel          string = "info"
 	LogMaxDays        int64  = 3
+	PrivilegeKey      string = ""
 	HeartBeatInterval int64  = 20
 	HeartBeatTimeout  int64  = 90
 )
@@ -75,12 +77,15 @@ func LoadConf(confFile string) (err error) {
 		LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
 	}
 
+	tmpStr, ok = conf.Get("common", "privilege_key")
+	if ok {
+		PrivilegeKey = tmpStr
+	}
+
 	var authToken string
 	tmpStr, ok = conf.Get("common", "auth_token")
 	if ok {
 		authToken = tmpStr
-	} else {
-		return fmt.Errorf("auth_token not found")
 	}
 
 	// proxies
@@ -90,9 +95,6 @@ func LoadConf(confFile string) (err error) {
 			// name
 			proxyClient.Name = name
 
-			// auth_token
-			proxyClient.AuthToken = authToken
-
 			// local_ip
 			proxyClient.LocalIp, ok = section["local_ip"]
 			if !ok {
@@ -101,46 +103,101 @@ func LoadConf(confFile string) (err error) {
 			}
 
 			// local_port
-			portStr, ok := section["local_port"]
+			tmpStr, ok = section["local_port"]
 			if ok {
-				proxyClient.LocalPort, err = strconv.ParseInt(portStr, 10, 64)
+				proxyClient.LocalPort, err = strconv.ParseInt(tmpStr, 10, 64)
 				if err != nil {
-					return fmt.Errorf("Parse ini file error: proxy [%s] local_port error", proxyClient.Name)
+					return fmt.Errorf("Parse conf error: proxy [%s] local_port error", proxyClient.Name)
 				}
 			} else {
-				return fmt.Errorf("Parse ini file error: proxy [%s] local_port not found", proxyClient.Name)
+				return fmt.Errorf("Parse conf error: proxy [%s] local_port not found", proxyClient.Name)
 			}
 
 			// type
 			proxyClient.Type = "tcp"
-			typeStr, ok := section["type"]
+			tmpStr, ok = section["type"]
 			if ok {
-				if typeStr != "tcp" && typeStr != "http" && typeStr != "https" {
-					return fmt.Errorf("Parse ini file error: proxy [%s] type error", proxyClient.Name)
+				if tmpStr != "tcp" && tmpStr != "http" && tmpStr != "https" {
+					return fmt.Errorf("Parse conf error: proxy [%s] type error", proxyClient.Name)
 				}
-				proxyClient.Type = typeStr
+				proxyClient.Type = tmpStr
 			}
 
 			// use_encryption
 			proxyClient.UseEncryption = false
-			useEncryptionStr, ok := section["use_encryption"]
-			if ok && useEncryptionStr == "true" {
+			tmpStr, ok = section["use_encryption"]
+			if ok && tmpStr == "true" {
 				proxyClient.UseEncryption = true
 			}
 
 			// use_gzip
 			proxyClient.UseGzip = false
-			useGzipStr, ok := section["use_gzip"]
-			if ok && useGzipStr == "true" {
+			tmpStr, ok = section["use_gzip"]
+			if ok && tmpStr == "true" {
 				proxyClient.UseGzip = true
 			}
 
+			// privilege_mode
+			proxyClient.PrivilegeMode = false
+			tmpStr, ok = section["privilege_mode"]
+			if ok && tmpStr == "true" {
+				proxyClient.PrivilegeMode = true
+			}
+
+			// configures used in privilege mode
+			if proxyClient.PrivilegeMode == true {
+				// auth_token
+				proxyClient.AuthToken = PrivilegeKey
+
+				if proxyClient.Type == "tcp" {
+					// remote_port
+					tmpStr, ok = section["remote_port"]
+					if ok {
+						proxyClient.RemotePort, err = strconv.ParseInt(tmpStr, 10, 64)
+						if err != nil {
+							return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", proxyClient.Name)
+						}
+					} else {
+						return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", proxyClient.Name)
+					}
+				} else if proxyClient.Type == "http" {
+					domainStr, ok := section["custom_domains"]
+					if ok {
+						proxyClient.CustomDomains = strings.Split(domainStr, ",")
+						if len(proxyClient.CustomDomains) == 0 {
+							return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyClient.Name)
+						}
+						for i, domain := range proxyClient.CustomDomains {
+							proxyClient.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
+						}
+					} else {
+						return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyClient.Name)
+					}
+				} else if proxyClient.Type == "https" {
+					domainStr, ok := section["custom_domains"]
+					if ok {
+						proxyClient.CustomDomains = strings.Split(domainStr, ",")
+						if len(proxyClient.CustomDomains) == 0 {
+							return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals https", proxyClient.Name)
+						}
+						for i, domain := range proxyClient.CustomDomains {
+							proxyClient.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
+						}
+					} else {
+						return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyClient.Name)
+					}
+				}
+			} else /* proxyClient.PrivilegeMode == false */ {
+				// authToken
+				proxyClient.AuthToken = authToken
+			}
+
 			ProxyClients[proxyClient.Name] = proxyClient
 		}
 	}
 
 	if len(ProxyClients) == 0 {
-		return fmt.Errorf("Parse ini file error: no proxy config found")
+		return fmt.Errorf("Parse conf error: no proxy config found")
 	}
 
 	return nil

+ 1 - 0
src/frp/models/config/config.go

@@ -20,4 +20,5 @@ type BaseConf struct {
 	Type          string
 	UseEncryption bool
 	UseGzip       bool
+	PrivilegeMode bool
 }

+ 11 - 5
src/frp/models/msg/msg.go

@@ -22,11 +22,17 @@ type GeneralRes struct {
 // messages between control connections of frpc and frps
 type ControlReq struct {
 	Type          int64  `json:"type"`
-	ProxyName     string `json:"proxy_name,omitempty"`
-	AuthKey       string `json:"auth_key, omitempty"`
-	UseEncryption bool   `json:"use_encryption, omitempty"`
-	UseGzip       bool   `json:"use_gzip, omitempty"`
-	Timestamp     int64  `json:"timestamp, omitempty"`
+	ProxyName     string `json:"proxy_name"`
+	AuthKey       string `json:"auth_key"`
+	UseEncryption bool   `json:"use_encryption"`
+	UseGzip       bool   `json:"use_gzip"`
+
+	// configures used if privilege_mode is enabled
+	PrivilegeMode bool     `json:"privilege_mode"`
+	ProxyType     string   `json:"proxy_type"`
+	RemotePort    int64    `json:"remote_port"`
+	CustomDomains []string `json:"custom_domains, omitempty"`
+	Timestamp     int64    `json:"timestamp"`
 }
 
 type ControlRes struct {

+ 0 - 1
src/frp/models/msg/process.go

@@ -141,7 +141,6 @@ func pipeDecrypt(r net.Conn, w net.Conn, conf config.BaseConf) (err error) {
 		}
 		// gzip
 		if conf.UseGzip {
-			log.Warn("%x", res)
 			res, err = laes.Decompression(res)
 			if err != nil {
 				log.Warn("ProxyName [%s], decompression error, %v", conf.Name, err)

+ 49 - 3
src/frp/models/server/config.go

@@ -22,6 +22,7 @@ import (
 
 	ini "github.com/vaughan0/go-ini"
 
+	"frp/models/consts"
 	"frp/utils/log"
 	"frp/utils/vhost"
 )
@@ -38,6 +39,8 @@ var (
 	LogWay           string = "console" // console or file
 	LogLevel         string = "info"
 	LogMaxDays       int64  = 3
+	PrivilegeMode    bool   = false
+	PrivilegeKey     string = ""
 	HeartBeatTimeout int64  = 90
 	UserConnTimeout  int64  = 10
 
@@ -132,6 +135,22 @@ func loadCommonConf(confFile string) error {
 			LogMaxDays = v
 		}
 	}
+
+	tmpStr, ok = conf.Get("common", "privilege_mode")
+	if ok {
+		if tmpStr == "true" {
+			PrivilegeMode = true
+		}
+	}
+
+	if PrivilegeMode == true {
+		tmpStr, ok = conf.Get("common", "privilege_key")
+		if ok {
+			PrivilegeKey = tmpStr
+		} else {
+			return fmt.Errorf("Parse conf error: privilege_key must be set if privilege_mode is enabled")
+		}
+	}
 	return nil
 }
 
@@ -189,6 +208,8 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e
 					for i, domain := range proxyServer.CustomDomains {
 						proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
 					}
+				} else {
+					return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyServer.Name)
 				}
 			} else if proxyServer.Type == "https" {
 				// for https
@@ -201,6 +222,8 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e
 					for i, domain := range proxyServer.CustomDomains {
 						proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
 					}
+				} else {
+					return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals https", proxyServer.Name)
 				}
 			}
 			proxyServers[proxyServer.Name] = proxyServer
@@ -234,14 +257,37 @@ func ReloadConf(confFile string) (err error) {
 		}
 	}
 
+	// proxies created by PrivilegeMode won't be deleted
 	for name, oldProxyServer := range ProxyServers {
 		_, ok := loadProxyServers[name]
 		if !ok {
-			oldProxyServer.Close()
-			delete(ProxyServers, name)
-			log.Info("ProxyName [%s] deleted, close it", name)
+			if !oldProxyServer.PrivilegeMode {
+				oldProxyServer.Close()
+				delete(ProxyServers, name)
+				log.Info("ProxyName [%s] deleted, close it", name)
+			} else {
+				log.Info("ProxyName [%s] created by PrivilegeMode, won't be closed", name)
+			}
 		}
 	}
 	ProxyServersMutex.Unlock()
 	return nil
 }
+
+func CreateProxy(s *ProxyServer) error {
+	ProxyServersMutex.Lock()
+	defer ProxyServersMutex.Unlock()
+	oldServer, ok := ProxyServers[s.Name]
+	if ok {
+		if oldServer.Status == consts.Working {
+			return fmt.Errorf("this proxy is already working now")
+		}
+		oldServer.Close()
+		if oldServer.PrivilegeMode {
+			delete(ProxyServers, s.Name)
+		}
+	}
+	s.Init()
+	ProxyServers[s.Name] = s
+	return nil
+}

+ 17 - 5
src/frp/models/server/server.go

@@ -52,6 +52,20 @@ func NewProxyServer() (p *ProxyServer) {
 	return p
 }
 
+func NewProxyServerFromCtlMsg(req *msg.ControlReq) (p *ProxyServer) {
+	p = &ProxyServer{}
+	p.Name = req.ProxyName
+	p.Type = req.ProxyType
+	p.UseEncryption = req.UseEncryption
+	p.UseGzip = req.UseGzip
+	p.PrivilegeMode = req.PrivilegeMode
+	p.BindAddr = BindAddr
+	p.ListenPort = req.RemotePort
+	p.CustomDomains = req.CustomDomains
+	p.AuthToken = PrivilegeKey
+	return
+}
+
 func (p *ProxyServer) Init() {
 	p.Lock()
 	p.Status = consts.Idle
@@ -161,11 +175,9 @@ func (p *ProxyServer) Close() {
 	p.Lock()
 	if p.Status != consts.Closed {
 		p.Status = consts.Closed
-		if len(p.listeners) != 0 {
-			for _, l := range p.listeners {
-				if l != nil {
-					l.Close()
-				}
+		for _, l := range p.listeners {
+			if l != nil {
+				l.Close()
 			}
 		}
 		close(p.ctlMsgChan)

+ 1 - 1
src/frp/utils/version/version.go

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