all: add new feature privilege mode

This commit is contained in:
fatedier 2016-06-26 22:36:07 +08:00
parent b14441d5cd
commit f968f3eace
13 changed files with 252 additions and 53 deletions

View File

@ -9,6 +9,8 @@ log_level = info
log_max_days = 3 log_max_days = 3
# for authentication # for authentication
auth_token = 123 auth_token = 123
# for privilege mode
privilege_key = 12345678
# ssh is the proxy name same as server's configuration # ssh is the proxy name same as server's configuration
[ssh] [ssh]
@ -32,3 +34,21 @@ use_gzip = true
type = http type = http
local_ip = 127.0.0.1 local_ip = 127.0.0.1
local_port = 8000 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

View File

@ -12,6 +12,9 @@ log_file = ./frps.log
# debug, info, warn, error # debug, info, warn, error
log_level = info log_level = info
log_max_days = 3 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 is the proxy name, client will use this name and auth_token to connect to server
[ssh] [ssh]

View File

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

View File

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

View File

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

View File

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

View File

@ -17,6 +17,7 @@ package client
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"strings"
ini "github.com/vaughan0/go-ini" ini "github.com/vaughan0/go-ini"
) )
@ -29,6 +30,7 @@ var (
LogWay string = "console" LogWay string = "console"
LogLevel string = "info" LogLevel string = "info"
LogMaxDays int64 = 3 LogMaxDays int64 = 3
PrivilegeKey string = ""
HeartBeatInterval int64 = 20 HeartBeatInterval int64 = 20
HeartBeatTimeout int64 = 90 HeartBeatTimeout int64 = 90
) )
@ -75,12 +77,15 @@ func LoadConf(confFile string) (err error) {
LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64) LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
} }
tmpStr, ok = conf.Get("common", "privilege_key")
if ok {
PrivilegeKey = tmpStr
}
var authToken string var authToken string
tmpStr, ok = conf.Get("common", "auth_token") tmpStr, ok = conf.Get("common", "auth_token")
if ok { if ok {
authToken = tmpStr authToken = tmpStr
} else {
return fmt.Errorf("auth_token not found")
} }
// proxies // proxies
@ -90,9 +95,6 @@ func LoadConf(confFile string) (err error) {
// name // name
proxyClient.Name = name proxyClient.Name = name
// auth_token
proxyClient.AuthToken = authToken
// local_ip // local_ip
proxyClient.LocalIp, ok = section["local_ip"] proxyClient.LocalIp, ok = section["local_ip"]
if !ok { if !ok {
@ -101,46 +103,101 @@ func LoadConf(confFile string) (err error) {
} }
// local_port // local_port
portStr, ok := section["local_port"] tmpStr, ok = section["local_port"]
if ok { if ok {
proxyClient.LocalPort, err = strconv.ParseInt(portStr, 10, 64) proxyClient.LocalPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil { 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 { } 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 // type
proxyClient.Type = "tcp" proxyClient.Type = "tcp"
typeStr, ok := section["type"] tmpStr, ok = section["type"]
if ok { if ok {
if typeStr != "tcp" && typeStr != "http" && typeStr != "https" { if tmpStr != "tcp" && tmpStr != "http" && tmpStr != "https" {
return fmt.Errorf("Parse ini file error: proxy [%s] type error", proxyClient.Name) return fmt.Errorf("Parse conf error: proxy [%s] type error", proxyClient.Name)
} }
proxyClient.Type = typeStr proxyClient.Type = tmpStr
} }
// use_encryption // use_encryption
proxyClient.UseEncryption = false proxyClient.UseEncryption = false
useEncryptionStr, ok := section["use_encryption"] tmpStr, ok = section["use_encryption"]
if ok && useEncryptionStr == "true" { if ok && tmpStr == "true" {
proxyClient.UseEncryption = true proxyClient.UseEncryption = true
} }
// use_gzip // use_gzip
proxyClient.UseGzip = false proxyClient.UseGzip = false
useGzipStr, ok := section["use_gzip"] tmpStr, ok = section["use_gzip"]
if ok && useGzipStr == "true" { if ok && tmpStr == "true" {
proxyClient.UseGzip = 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 ProxyClients[proxyClient.Name] = proxyClient
} }
} }
if len(ProxyClients) == 0 { 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 return nil

View File

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

View File

@ -22,11 +22,17 @@ type GeneralRes struct {
// messages between control connections of frpc and frps // messages between control connections of frpc and frps
type ControlReq struct { type ControlReq struct {
Type int64 `json:"type"` Type int64 `json:"type"`
ProxyName string `json:"proxy_name,omitempty"` ProxyName string `json:"proxy_name"`
AuthKey string `json:"auth_key, omitempty"` AuthKey string `json:"auth_key"`
UseEncryption bool `json:"use_encryption, omitempty"` UseEncryption bool `json:"use_encryption"`
UseGzip bool `json:"use_gzip, omitempty"` UseGzip bool `json:"use_gzip"`
Timestamp int64 `json:"timestamp, omitempty"`
// 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 { type ControlRes struct {

View File

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

View File

@ -22,6 +22,7 @@ import (
ini "github.com/vaughan0/go-ini" ini "github.com/vaughan0/go-ini"
"frp/models/consts"
"frp/utils/log" "frp/utils/log"
"frp/utils/vhost" "frp/utils/vhost"
) )
@ -38,6 +39,8 @@ var (
LogWay string = "console" // console or file LogWay string = "console" // console or file
LogLevel string = "info" LogLevel string = "info"
LogMaxDays int64 = 3 LogMaxDays int64 = 3
PrivilegeMode bool = false
PrivilegeKey string = ""
HeartBeatTimeout int64 = 90 HeartBeatTimeout int64 = 90
UserConnTimeout int64 = 10 UserConnTimeout int64 = 10
@ -132,6 +135,22 @@ func loadCommonConf(confFile string) error {
LogMaxDays = v 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 return nil
} }
@ -189,6 +208,8 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e
for i, domain := range proxyServer.CustomDomains { for i, domain := range proxyServer.CustomDomains {
proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain)) 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" { } else if proxyServer.Type == "https" {
// for https // for https
@ -201,6 +222,8 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e
for i, domain := range proxyServer.CustomDomains { for i, domain := range proxyServer.CustomDomains {
proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain)) 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 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 { for name, oldProxyServer := range ProxyServers {
_, ok := loadProxyServers[name] _, ok := loadProxyServers[name]
if !ok { if !ok {
oldProxyServer.Close() if !oldProxyServer.PrivilegeMode {
delete(ProxyServers, name) oldProxyServer.Close()
log.Info("ProxyName [%s] deleted, close it", name) 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() ProxyServersMutex.Unlock()
return nil 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
}

View File

@ -52,6 +52,20 @@ func NewProxyServer() (p *ProxyServer) {
return p 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() { func (p *ProxyServer) Init() {
p.Lock() p.Lock()
p.Status = consts.Idle p.Status = consts.Idle
@ -161,11 +175,9 @@ func (p *ProxyServer) Close() {
p.Lock() p.Lock()
if p.Status != consts.Closed { if p.Status != consts.Closed {
p.Status = consts.Closed p.Status = consts.Closed
if len(p.listeners) != 0 { for _, l := range p.listeners {
for _, l := range p.listeners { if l != nil {
if l != nil { l.Close()
l.Close()
}
} }
} }
close(p.ctlMsgChan) close(p.ctlMsgChan)

View File

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