升级go version 1.16,ui文件嵌入go二进制内

This commit is contained in:
bjd 2020-12-28 16:10:47 +08:00
parent d8b55de5b5
commit b515406635
21 changed files with 225 additions and 107 deletions

View File

@ -16,7 +16,7 @@ jobs:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.14
go-version: ^1.16
id: go
- name: Check out code into the Go module directory

View File

@ -21,11 +21,13 @@ AnyLink 服务端仅在CentOS7测试通过如需要安装在其他系统
## Screenshot
![online](https://raw.githubusercontent.com/bjdgyc/anylink/master/screenshot/online.jpg)
![online](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/online.jpg)
## Installation
```
> 升级 go version >= 1.16
```shell
rootPath=`pwd`
git clone https://github.com/bjdgyc/anylink.git
@ -36,12 +38,13 @@ npm install
npm run build
cd $rootPath/anylink
cp -r $rootPath/anylink-web/ui .
go build -o anylink -ldflags "-X main.COMMIT_ID=`git rev-parse HEAD`"
#整理部署文件
mkdir $rootPath/anylink-deploy
cd $rootPath/anylink-deploy
cp -r $rootPath/anylink-web/ui .
cp -r $rootPath/anylink/anylink .
cp -r $rootPath/anylink/conf .
cp -r $rootPath/anylink/downfiles .
@ -71,7 +74,15 @@ sudo ./anylink -conf="conf/server.toml"
默认配置文件内有详细的注释,根据注释填写配置即可。
- [conf/server.toml](https://github.com/bjdgyc/anylink/blob/master/conf/server.toml)
```shell
# 生成后台密码
./anylink -passwd 123456
# 生成jwt密钥
./anylink -secret
```
[conf/server.toml](https://github.com/bjdgyc/anylink/blob/master/conf/server.toml)
## Setting
@ -82,43 +93,48 @@ sudo ./anylink -conf="conf/server.toml"
### tun设置
1. 开启服务器转发
```
# flie: /etc/sysctl.conf
net.ipv4.ip_forward = 1
#执行如下命令
sysctl -w net.ipv4.ip_forward=1
```
```shell
# flie: /etc/sysctl.conf
net.ipv4.ip_forward = 1
#执行如下命令
sysctl -w net.ipv4.ip_forward=1
```
2. 设置nat转发规则
```
# eth0为服务器内网网卡
iptables -t nat -A POSTROUTING -s 192.168.10.0/255.255.255.0 -o eth0 -j MASQUERADE
```
```shell
# eth0为服务器内网网卡
iptables -t nat -A POSTROUTING -s 192.168.10.0/255.255.255.0 -o eth0 -j MASQUERADE
```
3. 使用AnyConnect客户端连接即可
### tap设置
1. 创建桥接网卡
```
注意 server.toml 的ip参数需要与 bridge.sh 的配置参数一致
```
```
注意 server.toml 的ip参数需要与 bridge.sh 的配置参数一致
```
2. 修改 bridge.sh 内的参数
```
# file: ./bridge.sh
eth="eth0"
eth_ip="192.168.1.4"
eth_netmask="255.255.255.0"
eth_broadcast="192.168.1.255"
eth_gateway="192.168.1.1"
```
```
# file: ./bridge.sh
eth="eth0"
eth_ip="192.168.1.4"
eth_netmask="255.255.255.0"
eth_broadcast="192.168.1.255"
eth_gateway="192.168.1.1"
```
3. 执行 bridge.sh 文件
```
sh bridge.sh
```
```
sh bridge.sh
```
## Soft
@ -126,11 +142,11 @@ sudo ./anylink -conf="conf/server.toml"
## Other Screenshot
![system.jpg](https://raw.githubusercontent.com/bjdgyc/anylink/master/screenshot/system.jpg)
![setting.jpg](https://raw.githubusercontent.com/bjdgyc/anylink/master/screenshot/setting.jpg)
![users.jpg](https://raw.githubusercontent.com/bjdgyc/anylink/master/screenshot/users.jpg)
![ip_map.jpg](https://raw.githubusercontent.com/bjdgyc/anylink/master/screenshot/ip_map.jpg)
![group.jpg](https://raw.githubusercontent.com/bjdgyc/anylink/master/screenshot/group.jpg)
![system.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/system.jpg)
![setting.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/setting.jpg)
![users.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/users.jpg)
![ip_map.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/ip_map.jpg)
![group.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/group.jpg)
## License

View File

@ -5,9 +5,8 @@ import (
"net/http"
"time"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/gorilla/mux"
)

View File

@ -2,6 +2,7 @@
package admin
import (
"embed"
"net/http"
"net/http/pprof"
@ -9,11 +10,20 @@ import (
"github.com/gorilla/mux"
)
var UiPath embed.FS
// 开启服务
func StartAdmin() {
r := mux.NewRouter()
r.Use(authMiddleware)
r.Handle("/", http.RedirectHandler("/ui/", http.StatusFound)).
Name("static")
r.PathPrefix("/ui/").Handler(http.FileServer(
http.FS(UiPath),
)).Name("static")
r.HandleFunc("/base/login", Login).Name("login")
r.HandleFunc("/set/home", SetHome)
r.HandleFunc("/set/system", SetSystem)
@ -49,8 +59,6 @@ func StartAdmin() {
r.HandleFunc("/debug/pprof", location("/debug/pprof/"))
r.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
r.PathPrefix("/").Handler(http.FileServer(http.Dir(base.Cfg.UiPath))).Name("static")
base.Info("Listen admin", base.Cfg.AdminAddr)
err := http.ListenAndServe(base.Cfg.AdminAddr, r)
if err != nil {

View File

@ -3,6 +3,7 @@ package base
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
@ -38,8 +39,7 @@ type ServerConfig struct {
DbFile string `toml:"db_file" info:"数据库地址"`
CertFile string `toml:"cert_file" info:"证书文件"`
CertKey string `toml:"cert_key" info:"证书密钥"`
UiPath string `toml:"ui_path" info:"ui文件路径"`
FilesPath string `toml:"files_path" info:"外部下载文件路径"`
DownFilesPath string `json:"down_files_path" info:"外部下载文件路径"`
LogLevel string `toml:"log_level" info:"日志等级"`
Issuer string `toml:"issuer" info:"系统名称"`
AdminUser string `toml:"admin_user" info:"管理用户名"`
@ -82,8 +82,12 @@ func initServerCfg() {
Cfg.DbFile = getAbsPath(base, Cfg.DbFile)
Cfg.CertFile = getAbsPath(base, Cfg.CertFile)
Cfg.CertKey = getAbsPath(base, Cfg.CertKey)
Cfg.UiPath = getAbsPath(base, Cfg.UiPath)
Cfg.FilesPath = getAbsPath(base, Cfg.FilesPath)
Cfg.DownFilesPath = getAbsPath(base, Cfg.DownFilesPath)
if len(Cfg.JwtSecret) < 20 {
fmt.Println("请设置 jwt_secret 长度20位以上")
os.Exit(0)
}
fmt.Printf("ServerCfg: %+v \n", Cfg)
}

View File

@ -3,8 +3,11 @@ package base
import (
"flag"
"fmt"
"math/rand"
"os"
"runtime"
"strings"
"time"
"github.com/bjdgyc/anylink/pkg/utils"
)
@ -16,13 +19,16 @@ var (
serverFile string
// pass明文
passwd string
// 生成密钥
secret bool
// 显示版本信息
rev bool
)
func initFlag() {
flag.StringVar(&serverFile, "conf", "./conf/server.toml", "server config file path")
flag.StringVar(&passwd, "passwd", "", "the password plaintext")
flag.StringVar(&passwd, "passwd", "", "convert the password plaintext")
flag.BoolVar(&secret, "secret", false, "generate a random jwt secret")
flag.BoolVar(&rev, "rev", false, "display version info")
flag.Parse()
@ -32,6 +38,14 @@ func initFlag() {
os.Exit(0)
}
if secret {
rand.Seed(time.Now().UnixNano())
s, _ := utils.RandSecret(40, 60)
s = strings.Trim(s, "=")
fmt.Printf("Secret:%s\n", s)
os.Exit(0)
}
if rev {
fmt.Printf("%s v%s build on %s [%s, %s] commit_id(%s) \n",
APP_NAME, APP_VER, runtime.Version(), runtime.GOOS, runtime.GOARCH, CommitId)

View File

@ -18,7 +18,7 @@ const (
var (
baseLog *log.Logger
baseLevel int
level map[int]string
levels map[int]string
)
func initLog() {
@ -27,7 +27,7 @@ func initLog() {
}
func logLevel2Int(l string) int {
level = map[int]string{
levels = map[int]string{
_Debug: "Debug",
_Info: "Info",
_Warn: "Warn",
@ -35,7 +35,7 @@ func logLevel2Int(l string) int {
_Fatal: "Fatal",
}
lvl := _Info
for k, v := range level {
for k, v := range levels {
if strings.ToLower(l) == strings.ToLower(v) {
lvl = k
}
@ -44,7 +44,7 @@ func logLevel2Int(l string) int {
}
func output(l int, s ...interface{}) {
lvl := fmt.Sprintf("[%s] ", level[l])
lvl := fmt.Sprintf("[%s] ", levels[l])
baseLog.Output(3, lvl+fmt.Sprintln(s...))
}

View File

@ -8,8 +8,7 @@ db_file = "./data.db"
#证书文件
cert_file = "./vpn_cert.pem"
cert_key = "./vpn_cert.key"
ui_path = "../ui"
files_path = "../downfiles"
down_files_path = "../down_files"
log_level = "info"
@ -19,7 +18,7 @@ issuer = "XX公司VPN"
admin_user = "admin"
#pass 123456
admin_pass = "$2a$10$UQ7C.EoPifDeJh6d8.31TeSPQU7hM/NOM2nixmBucJpAuXDQNqNke"
jwt_secret = "7IrsKW3JuDJ68TPPrdsfweDFYJrO1Xg7JcdsfasMv3P3"
jwt_secret = ""
#vpn服务对外地址

View File

@ -69,6 +69,9 @@ func SetGroup(g *Group) error {
clientDns = append(clientDns, v)
}
}
if len(clientDns) == 0 {
return errors.New("DNS错误")
}
g.ClientDns = clientDns
routeInclude := []ValData{}

View File

@ -2,6 +2,8 @@ package dbdata
import (
"errors"
"fmt"
"sync"
"time"
"github.com/bjdgyc/anylink/pkg/utils"
@ -23,44 +25,6 @@ type User struct {
UpdatedAt time.Time `json:"updated_at"`
}
// 验证用户登陆信息
func CheckUser(name, pwd, group string) error {
// return nil
pl := len(pwd)
if name == "" || pl < 6 {
return errors.New("密码错误")
}
v := &User{}
err := One("Username", name, v)
if err != nil || v.Status != 1 {
return errors.New("用户名错误")
}
pass := pwd[:pl-6]
// if !utils.PasswordVerify(pass, v.Password) {
if pass != v.PinCode {
return errors.New("密码错误")
}
otp := pwd[pl-6:]
totp := gotp.NewDefaultTOTP(v.OtpSecret)
unix := time.Now().Unix()
verify := totp.Verify(otp, int(unix))
if !verify {
return errors.New("动态码错误")
}
// 判断用户组信息
if !utils.InArrStr(v.Groups, group) {
return errors.New("用户组错误")
}
groupData := &Group{}
err = One("Name", group, groupData)
if err != nil || groupData.Status != 1 {
return errors.New("用户组错误")
}
return nil
}
func SetUser(v *User) error {
var err error
if v.Username == "" || len(v.Groups) == 0 {
@ -96,3 +60,84 @@ func SetUser(v *User) error {
return err
}
// 验证用户登陆信息
func CheckUser(name, pwd, group string) error {
// return nil
pl := len(pwd)
if name == "" || pl < 6 {
return fmt.Errorf("%s %s", name, "密码错误")
}
v := &User{}
err := One("Username", name, v)
if err != nil || v.Status != 1 {
return fmt.Errorf("%s %s", name, "用户名错误")
}
// 判断用户组信息
if !utils.InArrStr(v.Groups, group) {
return fmt.Errorf("%s %s", name, "用户组错误")
}
groupData := &Group{}
err = One("Name", group, groupData)
if err != nil || groupData.Status != 1 {
return fmt.Errorf("%s %s", name, "用户组错误")
}
// 判断otp信息
otp := pwd[pl-6:]
if !checkOtp(name, otp) {
return fmt.Errorf("%s %s", name, "动态码错误")
}
totp := gotp.NewDefaultTOTP(v.OtpSecret)
unix := time.Now().Unix()
verify := totp.Verify(otp, int(unix))
if !verify {
return fmt.Errorf("%s %s", name, "动态码错误")
}
pinCode := pwd[:pl-6]
if pinCode != v.PinCode {
return fmt.Errorf("%s %s", name, "密码错误")
}
return nil
}
var (
userOtpMux = sync.Mutex{}
userOtp = map[string]time.Time{}
)
func init() {
go func() {
expire := time.Second * 60
for range time.Tick(time.Second * 10) {
tnow := time.Now()
userOtpMux.Lock()
for k, v := range userOtp {
if tnow.After(v.Add(expire)) {
delete(userOtp, k)
}
}
userOtpMux.Unlock()
}
}()
}
// 令牌只能使用一次
func checkOtp(username, otp string) bool {
key := fmt.Sprintf("%s:%s", username, otp)
userOtpMux.Lock()
defer userOtpMux.Unlock()
if _, ok := userOtp[key]; ok {
// 已经存在
return false
}
userOtp[key] = time.Now()
return true
}

View File

@ -2,3 +2,4 @@
*
!.gitignore
!index.html

10
down_files/index.html Normal file
View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>

1
go.mod
View File

@ -22,4 +22,5 @@ require (
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
)

2
go.sum
View File

@ -87,6 +87,8 @@ golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -2,6 +2,7 @@ package handler
import (
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"net/http"
@ -14,6 +15,17 @@ import (
)
func LinkAuth(w http.ResponseWriter, r *http.Request) {
// 判断anyconnect客户端
userAgent := strings.ToLower(r.UserAgent())
x_Aggregate_Auth := r.Header.Get("X-Aggregate-Auth")
x_Transcend_Version := r.Header.Get("X-Transcend-Version")
if !(strings.Contains(userAgent, "anyconnect") &&
x_Aggregate_Auth == "1" && x_Transcend_Version == "1") {
w.WriteHeader(http.StatusForbidden)
fmt.Fprintf(w, "error request")
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)

View File

@ -20,7 +20,7 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
err error
n int
dataLen uint16
dead = time.Duration(cSess.CstpDpd*2) * time.Second
dead = time.Duration(cSess.CstpDpd+5) * time.Second
)
go cstpWrite(conn, cSess)

View File

@ -8,7 +8,6 @@ import (
"os"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/sessdata"
)
@ -51,7 +50,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
masterSecret := r.Header.Get("X-DTLS-Master-Secret")
localIp := r.Header.Get("X-Cstp-Local-Address-Ip4")
mobile := r.Header.Get("X-Cstp-License")
platform := r.Header.Get("X-AnyConnect-Identifier-Platform")
cSess.SetMtu(cstpMtu)
cSess.MasterSecret = masterSecret
cSess.RemoteAddr = r.RemoteAddr
@ -67,12 +66,6 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
}
cSess.CstpDpd = cstpDpd
// iPhone手机需要最少一个dns
if platform == "apple-ios" && len(cSess.Group.ClientDns) == 0 {
dnsVal := dbdata.ValData{Val: "114.114.114.114"}
cSess.Group.ClientDns = append(cSess.Group.ClientDns, dnsVal)
}
base.Debug(cSess.IpAddr, cSess.MacHw, sess.Username, mobile)
// 返回客户端数据

View File

@ -6,6 +6,7 @@ import (
"log"
"net"
"net/http"
"os"
"time"
"github.com/bjdgyc/anylink/base"
@ -18,6 +19,7 @@ func startTls() {
certFile := base.Cfg.CertFile
keyFile := base.Cfg.CertKey
logger := log.New(os.Stdout, "[SERVER]", log.Lshortfile|log.Ldate)
// 设置tls信息
tlsConfig := &tls.Config{
NextProtos: []string{"http/1.1"},
@ -27,6 +29,7 @@ func startTls() {
Addr: addr,
Handler: initRoute(),
TLSConfig: tlsConfig,
ErrorLog: logger,
}
var ln net.Listener
@ -50,13 +53,13 @@ func startTls() {
func initRoute() http.Handler {
r := mux.NewRouter()
// r.HandleFunc("/", checkLinkClient(LinkHome)).Methods(http.MethodGet)
r.HandleFunc("/", checkLinkClient(LinkAuth)).Methods(http.MethodPost)
r.HandleFunc("/", LinkHome).Methods(http.MethodGet)
r.HandleFunc("/", LinkAuth).Methods(http.MethodPost)
r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect)
r.HandleFunc("/otp_qr", LinkOtpQr).Methods(http.MethodGet)
r.PathPrefix("/files/").Handler(
http.StripPrefix("/files/",
http.FileServer(http.Dir(base.Cfg.FilesPath)),
r.PathPrefix("/down_files/").Handler(
http.StripPrefix("/down_files/",
http.FileServer(http.Dir(base.Cfg.DownFilesPath)),
),
)
r.NotFoundHandler = http.HandlerFunc(notFound)

View File

@ -2,21 +2,29 @@
package main
import (
"embed"
"os"
"os/signal"
"syscall"
"github.com/bjdgyc/anylink/admin"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/handler"
)
//go:embed ui/*
var UiPath embed.FS
// 程序版本
var COMMIT_ID string
func main() {
base.CommitId = COMMIT_ID
admin.UiPath = UiPath
base.Start()
handler.Start()
signalWatch()
}

View File

@ -25,8 +25,8 @@ const (
delmiter = "$"
)
func saltSecret() (string, error) {
rb := make([]byte, randInt(10, 100))
func RandSecret(min int, max int) (string, error) {
rb := make([]byte, randInt(min, max))
_, err := rand.Read(rb)
if err != nil {
return "", err

View File

@ -145,8 +145,8 @@ func (s *Session) NewConn() *ConnSession {
macHw, err := net.ParseMAC(macAddr)
if err != nil {
sum := md5.Sum([]byte(s.UniqueIdGlobal))
macHw = sum[8:13] // 5个byte
macHw = append([]byte{0x00}, macHw...)
macHw = sum[0:5] // 5个byte
macHw = append([]byte{0x02}, macHw...)
macAddr = macHw.String()
}
ip := AcquireIp(username, macAddr)