Add api feature
This commit is contained in:
parent
8a7223033b
commit
24b71f987c
9
conf/frps_full.ini
Normal file → Executable file
9
conf/frps_full.ini
Normal file → Executable file
@ -68,3 +68,12 @@ tcp_mux = true
|
|||||||
|
|
||||||
# custom 404 page for HTTP requests
|
# custom 404 page for HTTP requests
|
||||||
# custom_404_page = /path/to/404.html
|
# custom_404_page = /path/to/404.html
|
||||||
|
|
||||||
|
# enable or disable the frps API
|
||||||
|
api_enable = false
|
||||||
|
|
||||||
|
# API base request url.
|
||||||
|
# api_baseurl = https://api.example.com/
|
||||||
|
|
||||||
|
# API token used to verify the server.
|
||||||
|
# api_token = 12345667890
|
||||||
|
186
extend/api/api.go
Executable file
186
extend/api/api.go
Executable file
@ -0,0 +1,186 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
Host url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(host string) (s *Service, err error) {
|
||||||
|
u, err := url.Parse(host)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return &Service{*u}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckToken 校验客户端 token
|
||||||
|
func (s Service) CheckToken(user string, token string, timestamp int64, stk string) (ok bool, err error) {
|
||||||
|
values := url.Values{}
|
||||||
|
values.Set("action", "checktoken")
|
||||||
|
values.Set("user", user)
|
||||||
|
values.Set("token", token)
|
||||||
|
values.Set("timestamp", fmt.Sprintf("%d", timestamp))
|
||||||
|
values.Set("apitoken", stk)
|
||||||
|
s.Host.RawQuery = values.Encode()
|
||||||
|
defer func(u *url.URL) {
|
||||||
|
u.RawQuery = ""
|
||||||
|
}(&s.Host)
|
||||||
|
resp, err := http.Get(s.Host.String())
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return false, ErrHTTPStatus{
|
||||||
|
Status: resp.StatusCode,
|
||||||
|
Text: resp.Status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
response := ResponseCheckToken{}
|
||||||
|
if err = json.Unmarshal(body, &response); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !response.Success {
|
||||||
|
return false, ErrCheckTokenFail{response.Message}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckProxy 校验客户端代理
|
||||||
|
func (s Service) CheckProxy(user string, pMsg *msg.NewProxy, timestamp int64, stk string) (ok bool, err error) {
|
||||||
|
|
||||||
|
domains, err := json.Marshal(pMsg.CustomDomains)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
headers, err := json.Marshal(pMsg.Headers)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
locations, err := json.Marshal(pMsg.Locations)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
values := url.Values{}
|
||||||
|
|
||||||
|
// API Basic
|
||||||
|
values.Set("action", "checkproxy")
|
||||||
|
values.Set("user", user)
|
||||||
|
values.Set("timestamp", fmt.Sprintf("%d", timestamp))
|
||||||
|
values.Set("apitoken", stk)
|
||||||
|
|
||||||
|
// Proxies basic info
|
||||||
|
values.Set("proxy_name", pMsg.ProxyName)
|
||||||
|
values.Set("proxy_type", pMsg.ProxyType)
|
||||||
|
values.Set("use_encryption", BoolToString(pMsg.UseEncryption))
|
||||||
|
values.Set("use_compression", BoolToString(pMsg.UseCompression))
|
||||||
|
|
||||||
|
// Http Proxies
|
||||||
|
values.Set("domain", string(domains))
|
||||||
|
values.Set("subdomain", pMsg.SubDomain)
|
||||||
|
|
||||||
|
// Headers
|
||||||
|
values.Set("locations", string(locations))
|
||||||
|
values.Set("http_user", pMsg.HttpUser)
|
||||||
|
values.Set("http_pwd", pMsg.HttpPwd)
|
||||||
|
values.Set("host_header_rewrite", pMsg.HostHeaderRewrite)
|
||||||
|
values.Set("headers", string(headers))
|
||||||
|
|
||||||
|
// Tcp & Udp & Stcp
|
||||||
|
values.Set("remote_port", strconv.Itoa(pMsg.RemotePort))
|
||||||
|
|
||||||
|
// Stcp & Xtcp
|
||||||
|
values.Set("sk", pMsg.Sk)
|
||||||
|
|
||||||
|
// Load balance
|
||||||
|
values.Set("group", pMsg.Group)
|
||||||
|
values.Set("group_key", pMsg.GroupKey)
|
||||||
|
|
||||||
|
s.Host.RawQuery = values.Encode()
|
||||||
|
defer func(u *url.URL) {
|
||||||
|
u.RawQuery = ""
|
||||||
|
}(&s.Host)
|
||||||
|
resp, err := http.Get(s.Host.String())
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return false, ErrHTTPStatus{
|
||||||
|
Status: resp.StatusCode,
|
||||||
|
Text: resp.Status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
response := ResponseCheckProxy{}
|
||||||
|
if err = json.Unmarshal(body, &response); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !response.Success {
|
||||||
|
return false, ErrCheckProxyFail{response.Message}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BoolToString(val bool) (str string) {
|
||||||
|
if val {
|
||||||
|
return "true"
|
||||||
|
} else {
|
||||||
|
return "false"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrHTTPStatus struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
Text string `json:"massage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrHTTPStatus) Error() string {
|
||||||
|
return fmt.Sprintf("%s", e.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseCheckToken struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseCheckProxy struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrCheckTokenFail struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrCheckProxyFail struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrCheckTokenFail) Error() string {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrCheckProxyFail) Error() string {
|
||||||
|
return e.Message
|
||||||
|
}
|
24
models/config/server_common.go
Normal file → Executable file
24
models/config/server_common.go
Normal file → Executable file
@ -76,6 +76,12 @@ type ServerCommonConf struct {
|
|||||||
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
||||||
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||||
UserConnTimeout int64 `json:"user_conn_timeout"`
|
UserConnTimeout int64 `json:"user_conn_timeout"`
|
||||||
|
|
||||||
|
// API
|
||||||
|
EnableApi bool `json:"api_enable"`
|
||||||
|
ApiBaseUrl string `json:"api_baseurl"`
|
||||||
|
ApiToken string `json:"api_token"`
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultServerConf() *ServerCommonConf {
|
func GetDefaultServerConf() *ServerCommonConf {
|
||||||
@ -106,6 +112,9 @@ func GetDefaultServerConf() *ServerCommonConf {
|
|||||||
HeartBeatTimeout: 90,
|
HeartBeatTimeout: 90,
|
||||||
UserConnTimeout: 10,
|
UserConnTimeout: 10,
|
||||||
Custom404Page: "",
|
Custom404Page: "",
|
||||||
|
EnableApi: false,
|
||||||
|
ApiBaseUrl: "",
|
||||||
|
ApiToken: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,6 +317,21 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
|
|||||||
cfg.HeartBeatTimeout = v
|
cfg.HeartBeatTimeout = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "api_enable"); ok && tmpStr == "false" {
|
||||||
|
cfg.EnableApi = false
|
||||||
|
} else {
|
||||||
|
cfg.EnableApi = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "api_baseurl"); ok {
|
||||||
|
cfg.ApiBaseUrl = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "api_token"); ok {
|
||||||
|
cfg.ApiToken = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
23
server/control.go
Normal file → Executable file
23
server/control.go
Normal file → Executable file
@ -35,6 +35,8 @@ import (
|
|||||||
"github.com/fatedier/golib/control/shutdown"
|
"github.com/fatedier/golib/control/shutdown"
|
||||||
"github.com/fatedier/golib/crypto"
|
"github.com/fatedier/golib/crypto"
|
||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/extend/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ControlManager struct {
|
type ControlManager struct {
|
||||||
@ -416,6 +418,27 @@ func (ctl *Control) manager() {
|
|||||||
|
|
||||||
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
|
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
|
||||||
var pxyConf config.ProxyConf
|
var pxyConf config.ProxyConf
|
||||||
|
|
||||||
|
s, err := api.NewService(g.GlbServerCfg.ApiBaseUrl)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return remoteAddr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.GlbServerCfg.EnableApi {
|
||||||
|
|
||||||
|
nowTime := time.Now().Unix()
|
||||||
|
ok, err := s.CheckProxy(ctl.loginMsg.User, pxyMsg, nowTime, g.GlbServerCfg.ApiToken)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return remoteAddr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return remoteAddr, fmt.Errorf("invalid proxy configuration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load configures from NewProxy message and check.
|
// Load configures from NewProxy message and check.
|
||||||
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg)
|
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
31
server/service.go
Normal file → Executable file
31
server/service.go
Normal file → Executable file
@ -26,6 +26,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/frp/assets"
|
||||||
@ -45,6 +46,8 @@ import (
|
|||||||
|
|
||||||
"github.com/fatedier/golib/net/mux"
|
"github.com/fatedier/golib/net/mux"
|
||||||
fmux "github.com/hashicorp/yamux"
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/extend/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -367,7 +370,35 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
|
|||||||
err = fmt.Errorf("authorization failed")
|
err = fmt.Errorf("authorization failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if g.GlbServerCfg.EnableApi {
|
||||||
|
|
||||||
|
nowTime := time.Now().Unix()
|
||||||
|
|
||||||
|
s, err := api.NewService(g.GlbServerCfg.ApiBaseUrl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := regexp.MustCompile(`^[A-Za-z0-9]{1,32}$`)
|
||||||
|
mm := r.FindAllStringSubmatch(loginMsg.User, -1)
|
||||||
|
|
||||||
|
if len(mm) < 1 {
|
||||||
|
return fmt.Errorf("invalid username")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to API server and verify the user.
|
||||||
|
valid, err := s.CheckToken(loginMsg.User, loginMsg.PrivilegeKey, nowTime, g.GlbServerCfg.ApiToken)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
return fmt.Errorf("authorization failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If client's RunId is empty, it's a new client, we just create a new controller.
|
// If client's RunId is empty, it's a new client, we just create a new controller.
|
||||||
// Otherwise, we check if there is one controller has the same run id. If so, we release previous controller and start new one.
|
// Otherwise, we check if there is one controller has the same run id. If so, we release previous controller and start new one.
|
||||||
if loginMsg.RunId == "" {
|
if loginMsg.RunId == "" {
|
||||||
|
Loading…
Reference in New Issue
Block a user