mirror of https://github.com/bjdgyc/anylink.git
commit
3deda4d77f
11
README.md
11
README.md
|
@ -60,11 +60,17 @@ AnyLink 服务端仅在 CentOS 7、CentOS 8、Ubuntu 18.04、Ubuntu 20.04 测试
|
||||||
|
|
||||||
### 自行编译安装
|
### 自行编译安装
|
||||||
|
|
||||||
> 需要提前安装好 golang >= 1.19 和 nodejs >= 14.x 和 yarn >= v1.22.x
|
> 需要提前安装好 golang >= 1.19 和 nodejs >= 16.x 和 yarn >= v1.22.x
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/bjdgyc/anylink.git
|
git clone https://github.com/bjdgyc/anylink.git
|
||||||
|
|
||||||
|
# 编译参考软件版本
|
||||||
|
# go 1.20.12
|
||||||
|
# node v16.20.2
|
||||||
|
# yarn 1.22.19
|
||||||
|
|
||||||
|
|
||||||
cd anylink
|
cd anylink
|
||||||
sh build.sh
|
sh build.sh
|
||||||
|
|
||||||
|
@ -284,7 +290,7 @@ ipv4_end = "10.1.2.200"
|
||||||
-c=/etc/server.toml --ip_lease=1209600 # IP地址租约时长
|
-c=/etc/server.toml --ip_lease=1209600 # IP地址租约时长
|
||||||
```
|
```
|
||||||
|
|
||||||
7. 构建镜像
|
7. 构建镜像 (非必需)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#获取仓库源码
|
#获取仓库源码
|
||||||
|
@ -301,6 +307,7 @@ ipv4_end = "10.1.2.200"
|
||||||
## Discussion
|
## Discussion
|
||||||
|
|
||||||
添加QQ群(1): 567510628
|
添加QQ群(1): 567510628
|
||||||
|
|
||||||
添加QQ群(2): 739072205
|
添加QQ群(2): 739072205
|
||||||
|
|
||||||
群共享文件有相关软件下载
|
群共享文件有相关软件下载
|
||||||
|
|
|
@ -36,7 +36,7 @@ cp -rf $cpath/web/ui .
|
||||||
#国内可替换源加快速度
|
#国内可替换源加快速度
|
||||||
export GOPROXY=https://goproxy.io
|
export GOPROXY=https://goproxy.io
|
||||||
go mod tidy
|
go mod tidy
|
||||||
go build -v -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)"
|
go build -v -o anylink -ldflags "-s -w -X main.CommitId=$(git rev-parse HEAD)"
|
||||||
RETVAL $?
|
RETVAL $?
|
||||||
|
|
||||||
cd $cpath
|
cd $cpath
|
||||||
|
@ -52,6 +52,7 @@ cp -r server/conf $deploy
|
||||||
|
|
||||||
cp -r systemd $deploy
|
cp -r systemd $deploy
|
||||||
cp -r LICENSE $deploy
|
cp -r LICENSE $deploy
|
||||||
|
cp -r home $deploy
|
||||||
|
|
||||||
tar zcvf ${deploy}.tar.gz $deploy
|
tar zcvf ${deploy}.tar.gz $deploy
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,13 @@ echo $ver
|
||||||
#docker login -u bjdgyc
|
#docker login -u bjdgyc
|
||||||
|
|
||||||
#docker build -t bjdgyc/anylink .
|
#docker build -t bjdgyc/anylink .
|
||||||
docker build -t bjdgyc/anylink -f docker/Dockerfile .
|
|
||||||
|
docker build -t bjdgyc/anylink --build-arg GitCommitId=$(git rev-parse HEAD) -f docker/Dockerfile .
|
||||||
|
|
||||||
docker tag bjdgyc/anylink:latest bjdgyc/anylink:$ver
|
docker tag bjdgyc/anylink:latest bjdgyc/anylink:$ver
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
||||||
docker push bjdgyc/anylink:$ver
|
docker push bjdgyc/anylink:$ver
|
||||||
docker push bjdgyc/anylink:latest
|
docker push bjdgyc/anylink:latest
|
||||||
|
|
||||||
|
|
|
@ -12,34 +12,36 @@
|
||||||
>
|
>
|
||||||
> 需要展示主页的同学,可以在QQ群 直接联系我添加。
|
> 需要展示主页的同学,可以在QQ群 直接联系我添加。
|
||||||
|
|
||||||
| 昵称 | 主页 |
|
| 昵称 | 主页 / 留言 |
|
||||||
|---------| ---------------------------- |
|
|-----------|------------------------------|
|
||||||
| 代码 oo8 | |
|
| 代码 oo8 | |
|
||||||
| 甘磊 | https://github.com/ganlei333 |
|
| 甘磊 | https://github.com/ganlei333 |
|
||||||
| Oo@ | https://github.com/chooop |
|
| Oo@ | https://github.com/chooop |
|
||||||
| 虚极静笃 | |
|
| 虚极静笃 | |
|
||||||
| 请喝可乐 | |
|
| 请喝可乐 | |
|
||||||
| 加油加油 | |
|
| 加油加油 | |
|
||||||
| 李建 | |
|
| 李建 | |
|
||||||
| lanbin | |
|
| lanbin | |
|
||||||
| 乐在东途 | |
|
| 乐在东途 | |
|
||||||
| 孤鸿 | |
|
| 孤鸿 | |
|
||||||
| 刘国华 | |
|
| 刘国华 | |
|
||||||
| 改名好无聊 | |
|
| 改名好无聊 | |
|
||||||
| 全能互联网专家 | |
|
| 全能互联网专家 | |
|
||||||
| JCM | |
|
| JCM | |
|
||||||
| Eh... | |
|
| Eh... | |
|
||||||
| 沉 | |
|
| 沉 | |
|
||||||
| 刘国华 | |
|
| 刘国华 | |
|
||||||
| 忧郁的豚骨拉面 | |
|
| 忧郁的豚骨拉面 | |
|
||||||
| 张小旋当爹地 | |
|
| 张小旋当爹地 | |
|
||||||
| 对方正在输入 | |
|
| 对方正在输入 | |
|
||||||
| Ronny | |
|
| Ronny | |
|
||||||
| 奔跑的少年 | |
|
| 奔跑的少年 | |
|
||||||
| ZBW | |
|
| ZBW | |
|
||||||
| 悲鸣 | |
|
| 悲鸣 | |
|
||||||
| 谢谢 | |
|
| 谢谢 | |
|
||||||
| 云思科技 | |
|
| 云思科技 | |
|
||||||
|
| 哆啦A伟(张佳伟) | |
|
||||||
|
| nobody | 开源不易,感谢分享 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,34 @@ stream {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> nginx实现 共用443端口 示例
|
||||||
|
|
||||||
|
```conf
|
||||||
|
stream {
|
||||||
|
map $ssl_preread_server_name $name {
|
||||||
|
vpn.xx.com myvpn;
|
||||||
|
default defaultpage;
|
||||||
|
}
|
||||||
|
|
||||||
|
# upstream pool
|
||||||
|
upstream myvpn {
|
||||||
|
server 127.0.0.1:8443;
|
||||||
|
}
|
||||||
|
upstream defaultpage {
|
||||||
|
server 127.0.0.1:8080;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 so_keepalive=on;
|
||||||
|
ssl_preread on;
|
||||||
|
#接收端也需要设置 proxy_protocol
|
||||||
|
#proxy_protocol on;
|
||||||
|
proxy_pass $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
### 性能问题
|
### 性能问题
|
||||||
```
|
```
|
||||||
内网环境测试数据
|
内网环境测试数据
|
||||||
|
|
|
@ -1,40 +1,49 @@
|
||||||
|
#node:16-bullseye
|
||||||
|
#golang:1.20-bullseye
|
||||||
|
#debian:bullseye-slim
|
||||||
|
#sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
|
||||||
|
|
||||||
|
|
||||||
# web
|
# web
|
||||||
FROM node:16.17.1-alpine3.15 as builder_node
|
FROM node:16-alpine3.18 as builder_node
|
||||||
WORKDIR /web
|
WORKDIR /web
|
||||||
COPY ./web /web
|
COPY ./web /web
|
||||||
RUN yarn install \
|
RUN yarn install \
|
||||||
&& yarn run build \
|
&& yarn run build \
|
||||||
&& ls /web/ui
|
&& ls /web/ui
|
||||||
|
|
||||||
|
|
||||||
# server
|
# server
|
||||||
FROM golang:1.19-alpine as builder_golang
|
FROM golang:1.20-alpine3.18 as builder_golang
|
||||||
#TODO 本地打包时使用镜像
|
#TODO 本地打包时使用镜像
|
||||||
ENV GOPROXY=https://goproxy.io
|
ENV GOPROXY=https://goproxy.cn
|
||||||
ENV GOOS=linux
|
ENV GOOS=linux
|
||||||
|
ARG GitCommitId="gitCommitId"
|
||||||
|
|
||||||
WORKDIR /anylink
|
WORKDIR /anylink
|
||||||
COPY . /anylink
|
COPY server /anylink
|
||||||
COPY --from=builder_node /web/ui /anylink/server/ui
|
COPY --from=builder_node /web/ui /anylink/ui
|
||||||
|
|
||||||
#TODO 本地打包时使用镜像
|
#TODO 本地打包时使用镜像
|
||||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
||||||
RUN apk add --no-cache git gcc musl-dev
|
RUN apk add gcc musl-dev
|
||||||
RUN cd /anylink/server;go mod tidy;go build -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)" \
|
RUN cd /anylink;go mod tidy;go build -o anylink -ldflags "-s -w -X main.CommitId=${GitCommitId}" \
|
||||||
&& /anylink/server/anylink tool -v
|
&& /anylink/anylink tool -v
|
||||||
|
|
||||||
|
|
||||||
# anylink
|
# anylink
|
||||||
FROM alpine
|
FROM alpine:3.18
|
||||||
LABEL maintainer="github.com/bjdgyc"
|
LABEL maintainer="github.com/bjdgyc"
|
||||||
|
|
||||||
#ENV IPV4_CIDR="192.168.10.0/24"
|
ENV ANYLINK_IN_CONTAINER=true
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder_golang /anylink/server/anylink /app/
|
COPY --from=builder_golang /anylink/anylink /app/
|
||||||
COPY docker/docker_entrypoint.sh /app/
|
COPY docker/docker_entrypoint.sh /app/
|
||||||
|
COPY ./server/bridge-init.sh /app/
|
||||||
#COPY ./server/bridge-init.sh /app/
|
|
||||||
COPY ./server/conf /app/conf
|
COPY ./server/conf /app/conf
|
||||||
COPY ./LICENSE /app/LICENSE
|
COPY ./LICENSE /app/LICENSE
|
||||||
|
COPY ./home /app/home
|
||||||
|
|
||||||
#TODO 本地打包时使用镜像
|
#TODO 本地打包时使用镜像
|
||||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>AnyLink - 企业级远程办公 SSL VPN</title>
|
||||||
|
<style>
|
||||||
|
/* CSS样式表 */
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 20px auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
margin-bottom: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设置页脚固定在底部,并且占满横向宽度 */
|
||||||
|
footer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.cta-button {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #fff;
|
||||||
|
padding: 10px 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>欢迎使用 AnyLink</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h2>什么是 AnyLink?</h2>
|
||||||
|
<p>AnyLink 是一款面向企业级的远程办公 SSL VPN 软件,支持多人同时在线使用。它提供安全、便捷的访问内部网络资源的方式,使远程工作者能够有效协作。</p>
|
||||||
|
|
||||||
|
<h2>核心功能</h2>
|
||||||
|
<ul>
|
||||||
|
<li>安全远程访问:AnyLink 使用 SSL/TLS 加密技术,确保远程用户与企业网络之间的连接安全可靠。</li>
|
||||||
|
<li>多用户支持:多个用户可以同时连接 VPN,实现不同地点团队的无缝协作。</li>
|
||||||
|
<li>灵活网络访问:AnyLink 能够安全地让远程工作者访问内部资源,如文件、应用程序和数据库。</li>
|
||||||
|
<li>集中化管理:该 VPN 解决方案提供集中化管理控制台,便于用户管理、访问控制和监控。</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>开始使用 AnyLink</h2>
|
||||||
|
<p>体验 AnyLink 为您的企业远程办公需求所带来的便利和安全。</p>
|
||||||
|
|
||||||
|
<h2>下载客户端</h2>
|
||||||
|
<a href="/files/anyconnect-win-4.10.05111.msi" class="cta-button">Windows 客户端</a>
|
||||||
|
<a href="/files/anyconnect-macos-4.10.05111.dmg" class="cta-button">Mac 客户端</a>
|
||||||
|
|
||||||
|
<a href="https://apps.apple.com/cn/app/cisco-secure-client/id1135064690" class="cta-button">iOS 客户端</a>
|
||||||
|
<a href="/files/CiscoSecureClientAnyConnect_v5.0.00247.apk" class="cta-button">Android 客户端</a>
|
||||||
|
|
||||||
|
<a href="/files/freeotp.apk" class="cta-button">Android FreeOTP客户端</a>
|
||||||
|
<a href="https://apps.apple.com/cn/app/freeotp-authenticator/id872559395" class="cta-button">iOS FreeOTP客户端</a>
|
||||||
|
<h2>使用手册</h2>
|
||||||
|
<a href="/files/anylink_doc.pdf" class="cta-button">使用手册(Windows)</a>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
© 2023 AnyLink. 保留所有权利。
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -67,6 +67,14 @@ func Login(w http.ResponseWriter, r *http.Request) {
|
||||||
data["admin_user"] = adminUser
|
data["admin_user"] = adminUser
|
||||||
data["expires_at"] = expiresAt
|
data["expires_at"] = expiresAt
|
||||||
|
|
||||||
|
ck := &http.Cookie{
|
||||||
|
Name: "jwt",
|
||||||
|
Value: tokenString,
|
||||||
|
Path: "/",
|
||||||
|
HttpOnly: true,
|
||||||
|
}
|
||||||
|
http.SetCookie(w, ck)
|
||||||
|
|
||||||
RespSucess(w, data)
|
RespSucess(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,13 +84,15 @@ func authMiddleware(next http.Handler) http.Handler {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
|
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "*")
|
w.Header().Set("Access-Control-Allow-Headers", "*")
|
||||||
if r.Method == http.MethodOptions {
|
if r.Method == http.MethodOptions {
|
||||||
|
// 正式环境不支持 OPTIONS
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
route := mux.CurrentRoute(r)
|
route := mux.CurrentRoute(r)
|
||||||
name := route.GetName()
|
name := route.GetName()
|
||||||
// fmt.Println("bb", r.URL.Path, name)
|
// fmt.Println("bb", r.URL.Path, name)
|
||||||
if utils.InArrStr([]string{"login", "index", "static", "debug"}, name) {
|
if utils.InArrStr([]string{"login", "index", "static"}, name) {
|
||||||
// 不进行鉴权
|
// 不进行鉴权
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
|
@ -93,6 +103,12 @@ func authMiddleware(next http.Handler) http.Handler {
|
||||||
if jwtToken == "" {
|
if jwtToken == "" {
|
||||||
jwtToken = r.FormValue("jwt")
|
jwtToken = r.FormValue("jwt")
|
||||||
}
|
}
|
||||||
|
if jwtToken == "" {
|
||||||
|
cc, err := r.Cookie("jwt")
|
||||||
|
if err == nil {
|
||||||
|
jwtToken = cc.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
data, err := GetJwtData(jwtToken)
|
data, err := GetJwtData(jwtToken)
|
||||||
if err != nil || base.Cfg.AdminUser != fmt.Sprint(data["admin_user"]) {
|
if err != nil || base.Cfg.AdminUser != fmt.Sprint(data["admin_user"]) {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/arl/statsviz"
|
"github.com/arl/statsviz"
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
"github.com/bjdgyc/anylink/dbdata"
|
"github.com/bjdgyc/anylink/dbdata"
|
||||||
|
"github.com/bjdgyc/anylink/pkg/utils"
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
@ -20,6 +21,13 @@ var UiData embed.FS
|
||||||
func StartAdmin() {
|
func StartAdmin() {
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
|
// 所有路由添加安全头
|
||||||
|
r.Use(func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
utils.SetSecureHeader(w)
|
||||||
|
next.ServeHTTP(w, req)
|
||||||
|
})
|
||||||
|
})
|
||||||
r.Use(authMiddleware)
|
r.Use(authMiddleware)
|
||||||
r.Use(handlers.CompressHandler)
|
r.Use(handlers.CompressHandler)
|
||||||
|
|
||||||
|
|
|
@ -3,5 +3,5 @@ package base
|
||||||
const (
|
const (
|
||||||
APP_NAME = "AnyLink"
|
APP_NAME = "AnyLink"
|
||||||
// app版本号
|
// app版本号
|
||||||
APP_VER = "0.9.4"
|
APP_VER = "0.10.1"
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
procModulesPath = "/proc/modules"
|
||||||
|
inContainerKey = "ANYLINK_IN_CONTAINER"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
inContainer = false
|
||||||
|
modMap = map[string]struct{}{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func initMod() {
|
||||||
|
container := os.Getenv(inContainerKey)
|
||||||
|
if container == "true" {
|
||||||
|
inContainer = true
|
||||||
|
}
|
||||||
|
log.Println("inContainer", inContainer)
|
||||||
|
|
||||||
|
file, err := os.Open(procModulesPath)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("[ERROR] Problem with open file: %s", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
scanner.Split(bufio.ScanLines)
|
||||||
|
for scanner.Scan() {
|
||||||
|
splited := strings.Split(scanner.Text(), " ")
|
||||||
|
if len(splited[0]) > 0 {
|
||||||
|
modMap[splited[0]] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckModOrLoad(mod string) {
|
||||||
|
log.Println("CheckModOrLoad", mod)
|
||||||
|
|
||||||
|
if _, ok := modMap[mod]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if inContainer {
|
||||||
|
err := fmt.Errorf("Linux modules %s is not loaded, please run `modprobe %s`", mod, mod)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdstr := fmt.Sprintln("modprobe", mod)
|
||||||
|
|
||||||
|
cmd := exec.Command("sh", "-c", cmdstr)
|
||||||
|
b, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(string(b))
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ func Start() {
|
||||||
execute()
|
execute()
|
||||||
initCfg()
|
initCfg()
|
||||||
initLog()
|
initLog()
|
||||||
|
initMod()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test() {
|
func Test() {
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
客户端软件需放置在files目录内,
|
客户端软件需放置在files目录内,
|
||||||
如需要帮助请加QQ群:567510628
|
如需要帮助请加QQ群:567510628 、739072205
|
|
@ -8,6 +8,7 @@
|
||||||
<RestrictPreferenceCaching>false</RestrictPreferenceCaching>
|
<RestrictPreferenceCaching>false</RestrictPreferenceCaching>
|
||||||
<RestrictTunnelProtocols>IPSec</RestrictTunnelProtocols>
|
<RestrictTunnelProtocols>IPSec</RestrictTunnelProtocols>
|
||||||
<BypassDownloader>true</BypassDownloader>
|
<BypassDownloader>true</BypassDownloader>
|
||||||
|
<AutoUpdate UserControllable="false">false</AutoUpdate>
|
||||||
<WindowsVPNEstablishment>AllowRemoteUsers</WindowsVPNEstablishment>
|
<WindowsVPNEstablishment>AllowRemoteUsers</WindowsVPNEstablishment>
|
||||||
<LinuxVPNEstablishment>AllowRemoteUsers</LinuxVPNEstablishment>
|
<LinuxVPNEstablishment>AllowRemoteUsers</LinuxVPNEstablishment>
|
||||||
<CertEnrollmentPin>pinAllowed</CertEnrollmentPin>
|
<CertEnrollmentPin>pinAllowed</CertEnrollmentPin>
|
||||||
|
@ -20,15 +21,19 @@
|
||||||
</ExtendedKeyUsage>
|
</ExtendedKeyUsage>
|
||||||
</CertificateMatch>
|
</CertificateMatch>
|
||||||
|
|
||||||
<BackupServerList>
|
|
||||||
<HostAddress>localhost</HostAddress>
|
|
||||||
</BackupServerList>
|
|
||||||
</ClientInitialization>
|
</ClientInitialization>
|
||||||
|
|
||||||
<ServerList>
|
<ServerList>
|
||||||
|
|
||||||
<HostEntry>
|
<HostEntry>
|
||||||
<HostName>VPN Server</HostName>
|
<HostName>VPN</HostName>
|
||||||
<HostAddress>localhost</HostAddress>
|
<HostAddress>localhost</HostAddress>
|
||||||
</HostEntry>
|
</HostEntry>
|
||||||
|
|
||||||
|
<HostEntry>
|
||||||
|
<HostName>VPN2</HostName>
|
||||||
|
<HostAddress>localhost2</HostAddress>
|
||||||
|
</HostEntry>
|
||||||
|
|
||||||
</ServerList>
|
</ServerList>
|
||||||
</AnyConnectProfile>
|
</AnyConnectProfile>
|
|
@ -41,6 +41,6 @@ iptables_nat = true
|
||||||
|
|
||||||
|
|
||||||
#客户端显示详细错误信息(线上环境慎开启)
|
#客户端显示详细错误信息(线上环境慎开启)
|
||||||
display_error = false
|
display_error = true
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,13 @@ package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
|
@ -20,10 +23,13 @@ func startDtls() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
certificate, err := selfsign.GenerateSelfSigned()
|
// rsa 兼容 open connect
|
||||||
|
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
certificate, err := selfsign.SelfSign(priv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logf := logging.NewDefaultLoggerFactory()
|
logf := logging.NewDefaultLoggerFactory()
|
||||||
logf.Writer = base.GetBaseLw()
|
logf.Writer = base.GetBaseLw()
|
||||||
// logf.DefaultLogLevel = logging.LogLevelTrace
|
// logf.DefaultLogLevel = logging.LogLevelTrace
|
||||||
|
@ -34,12 +40,17 @@ func startDtls() {
|
||||||
|
|
||||||
config := &dtls.Config{
|
config := &dtls.Config{
|
||||||
Certificates: []tls.Certificate{certificate},
|
Certificates: []tls.Certificate{certificate},
|
||||||
InsecureSkipVerify: true,
|
|
||||||
ExtendedMasterSecret: dtls.DisableExtendedMasterSecret,
|
ExtendedMasterSecret: dtls.DisableExtendedMasterSecret,
|
||||||
CipherSuites: []dtls.CipherSuiteID{dtls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
|
CipherSuites: func() []dtls.CipherSuiteID {
|
||||||
LoggerFactory: logf,
|
var cs = []dtls.CipherSuiteID{}
|
||||||
MTU: BufferSize,
|
for _, vv := range dtlsCipherSuites {
|
||||||
SessionStore: sessStore,
|
cs = append(cs, vv)
|
||||||
|
}
|
||||||
|
return cs
|
||||||
|
}(),
|
||||||
|
LoggerFactory: logf,
|
||||||
|
MTU: BufferSize,
|
||||||
|
SessionStore: sessStore,
|
||||||
ConnectContextMaker: func() (context.Context, func()) {
|
ConnectContextMaker: func() (context.Context, func()) {
|
||||||
return context.WithTimeout(context.Background(), 5*time.Second)
|
return context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
},
|
},
|
||||||
|
@ -98,3 +109,23 @@ func (ms *sessionStore) Get(key []byte) (dtls.Session, error) {
|
||||||
func (ms *sessionStore) Del(key []byte) error {
|
func (ms *sessionStore) Del(key []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 客户端和服务端映射 X-DTLS12-CipherSuite
|
||||||
|
var dtlsCipherSuites = map[string]dtls.CipherSuiteID{
|
||||||
|
// "ECDHE-ECDSA-AES256-GCM-SHA384": dtls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
// "ECDHE-ECDSA-AES128-GCM-SHA256": dtls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
"ECDHE-RSA-AES256-GCM-SHA384": dtls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
"ECDHE-RSA-AES128-GCM-SHA256": dtls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDtls12Ciphersuite(ciphersuite string) string {
|
||||||
|
csArr := strings.Split(ciphersuite, ":")
|
||||||
|
|
||||||
|
for _, v := range csArr {
|
||||||
|
if _, ok := dtlsCipherSuites[v]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 返回默认值
|
||||||
|
return "ECDHE-RSA-AES128-GCM-SHA256"
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -49,7 +50,7 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// fmt.Printf("%+v \n", cr)
|
// fmt.Printf("%+v \n", cr)
|
||||||
setCommonHeader(w)
|
// setCommonHeader(w)
|
||||||
if cr.Type == "logout" {
|
if cr.Type == "logout" {
|
||||||
// 退出删除session信息
|
// 退出删除session信息
|
||||||
if cr.SessionToken != "" {
|
if cr.SessionToken != "" {
|
||||||
|
@ -154,10 +155,12 @@ func tplRequest(typ int, w io.Writer, data RequestData) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(data.Banner, "\n") {
|
if data.Banner != "" {
|
||||||
// 替换xml文件的换行符
|
buf := new(bytes.Buffer)
|
||||||
data.Banner = strings.ReplaceAll(data.Banner, "\n", "
")
|
xml.EscapeText(buf, []byte(data.Banner))
|
||||||
|
data.Banner = buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
t, _ := template.New("auth_complete").Parse(auth_complete)
|
t, _ := template.New("auth_complete").Parse(auth_complete)
|
||||||
_ = t.Execute(w, data)
|
_ = t.Execute(w, data)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package handler
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,28 +41,6 @@ type macAddressList struct {
|
||||||
MacAddress string `xml:"mac-address"`
|
MacAddress string `xml:"mac-address"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func setCommonHeader(w http.ResponseWriter) {
|
|
||||||
// Content-Length Date 默认已经存在
|
|
||||||
w.Header().Set("Server", "AnyLinkOpenSource")
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
||||||
w.Header().Set("Cache-Control", "no-store,no-cache")
|
|
||||||
w.Header().Set("Pragma", "no-cache")
|
|
||||||
w.Header().Set("Transfer-Encoding", "chunked")
|
|
||||||
w.Header().Set("Connection", "keep-alive")
|
|
||||||
w.Header().Set("X-Frame-Options", "deny")
|
|
||||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
||||||
w.Header().Set("Content-Security-Policy", "default-src 'none'")
|
|
||||||
w.Header().Set("X-Permitted-Cross-Domain-Policies", "none")
|
|
||||||
w.Header().Set("Referrer-Policy", "no-referrer")
|
|
||||||
w.Header().Set("Clear-Site-Data", "cache,cookies,storage")
|
|
||||||
w.Header().Set("Cross-Origin-Embedder-Policy", "require-corp")
|
|
||||||
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
|
|
||||||
w.Header().Set("Cross-Origin-Resource-Policy", "same-origin")
|
|
||||||
w.Header().Set("X-XSS-Protection", "0")
|
|
||||||
w.Header().Set("X-Aggregate-Auth", "1")
|
|
||||||
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
|
||||||
}
|
|
||||||
|
|
||||||
func execCmd(cmdStrs []string) error {
|
func execCmd(cmdStrs []string) error {
|
||||||
for _, cmdStr := range cmdStrs {
|
for _, cmdStr := range cmdStrs {
|
||||||
cmd := exec.Command("sh", "-c", cmdStr)
|
cmd := exec.Command("sh", "-c", cmdStr)
|
||||||
|
|
|
@ -13,7 +13,7 @@ func LinkHome(w http.ResponseWriter, r *http.Request) {
|
||||||
// fmt.Println(r.RemoteAddr)
|
// fmt.Println(r.RemoteAddr)
|
||||||
// hu, _ := httputil.DumpRequest(r, true)
|
// hu, _ := httputil.DumpRequest(r, true)
|
||||||
// fmt.Println("DumpHome: ", string(hu))
|
// fmt.Println("DumpHome: ", string(hu))
|
||||||
w.Header().Set("Server", "AnyLinkOpenSource")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
connection := strings.ToLower(r.Header.Get("Connection"))
|
connection := strings.ToLower(r.Header.Get("Connection"))
|
||||||
userAgent := strings.ToLower(r.UserAgent())
|
userAgent := strings.ToLower(r.UserAgent())
|
||||||
if connection == "close" && (strings.Contains(userAgent, "anyconnect") || strings.Contains(userAgent, "openconnect")) {
|
if connection == "close" && (strings.Contains(userAgent, "anyconnect") || strings.Contains(userAgent, "openconnect")) {
|
||||||
|
|
|
@ -22,22 +22,29 @@ func checkTun() {
|
||||||
defer ifce.Close()
|
defer ifce.Close()
|
||||||
|
|
||||||
// 测试ip命令
|
// 测试ip命令
|
||||||
cmdstr := fmt.Sprintf("ip link set dev %s up mtu %s multicast off", ifce.Name(), "1399")
|
base.CheckModOrLoad("tun")
|
||||||
err = execCmd([]string{cmdstr})
|
|
||||||
|
cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %s multicast off", ifce.Name(), "1399")
|
||||||
|
err = execCmd([]string{cmdstr1})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatal("testTun err: ", err)
|
base.Fatal("testTun err: ", err)
|
||||||
}
|
}
|
||||||
//开启服务器转发
|
// 开启服务器转发
|
||||||
if err := execCmd([]string{"sysctl -w net.ipv4.ip_forward=1"}); err != nil {
|
if err := execCmd([]string{"sysctl -w net.ipv4.ip_forward=1"}); err != nil {
|
||||||
base.Error(err)
|
base.Fatal(err)
|
||||||
}
|
}
|
||||||
if base.Cfg.IptablesNat {
|
if base.Cfg.IptablesNat {
|
||||||
//添加NAT转发规则
|
// 添加NAT转发规则
|
||||||
ipt, err := iptables.New()
|
ipt, err := iptables.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error(err)
|
base.Fatal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 修复 rockyos nat 不生效
|
||||||
|
base.CheckModOrLoad("iptable_filter")
|
||||||
|
base.CheckModOrLoad("iptable_nat")
|
||||||
|
|
||||||
natRule := []string{"-s", base.Cfg.Ipv4CIDR, "-o", base.Cfg.Ipv4Master, "-j", "MASQUERADE"}
|
natRule := []string{"-s", base.Cfg.Ipv4CIDR, "-o", base.Cfg.Ipv4Master, "-j", "MASQUERADE"}
|
||||||
forwardRule := []string{"-j", "ACCEPT"}
|
forwardRule := []string{"-j", "ACCEPT"}
|
||||||
if natExists, _ := ipt.Exists("nat", "POSTROUTING", natRule...); !natExists {
|
if natExists, _ := ipt.Exists("nat", "POSTROUTING", natRule...); !natExists {
|
||||||
|
@ -65,7 +72,10 @@ func LinkTun(cSess *sessdata.ConnSession) error {
|
||||||
// log.Printf("Interface Name: %s\n", ifce.Name())
|
// log.Printf("Interface Name: %s\n", ifce.Name())
|
||||||
cSess.SetIfName(ifce.Name())
|
cSess.SetIfName(ifce.Name())
|
||||||
|
|
||||||
cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast off", ifce.Name(), cSess.Mtu)
|
// 通过 ip link show 查看 alias 信息
|
||||||
|
|
||||||
|
cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast off alias %s.%s", ifce.Name(), cSess.Mtu,
|
||||||
|
cSess.Group.Name, cSess.Username)
|
||||||
cmdstr2 := fmt.Sprintf("ip addr add dev %s local %s peer %s/32",
|
cmdstr2 := fmt.Sprintf("ip addr add dev %s local %s peer %s/32",
|
||||||
ifce.Name(), base.Cfg.Ipv4Gateway, cSess.IpAddr)
|
ifce.Name(), base.Cfg.Ipv4Gateway, cSess.IpAddr)
|
||||||
err = execCmd([]string{cmdstr1, cmdstr2})
|
err = execCmd([]string{cmdstr1, cmdstr2})
|
||||||
|
|
|
@ -92,6 +92,10 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
base.Debug(cSess.IpAddr, cSess.MacHw, sess.Username, mobile)
|
base.Debug(cSess.IpAddr, cSess.MacHw, sess.Username, mobile)
|
||||||
|
|
||||||
|
// 检测密码套件
|
||||||
|
dtlsCiphersuite := checkDtls12Ciphersuite(r.Header.Get("X-Dtls12-Ciphersuite"))
|
||||||
|
base.Trace("dtlsCiphersuite", dtlsCiphersuite)
|
||||||
|
|
||||||
// 压缩
|
// 压缩
|
||||||
if cmpName, ok := cSess.SetPickCmp("cstp", r.Header.Get("X-Cstp-Accept-Encoding")); ok {
|
if cmpName, ok := cSess.SetPickCmp("cstp", r.Header.Get("X-Cstp-Accept-Encoding")); ok {
|
||||||
HttpSetHeader(w, "X-CSTP-Content-Encoding", cmpName)
|
HttpSetHeader(w, "X-CSTP-Content-Encoding", cmpName)
|
||||||
|
@ -164,7 +168,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
||||||
HttpSetHeader(w, "X-DTLS-Port", dtlsPort)
|
HttpSetHeader(w, "X-DTLS-Port", dtlsPort)
|
||||||
HttpSetHeader(w, "X-DTLS-DPD", fmt.Sprintf("%d", cstpDpd))
|
HttpSetHeader(w, "X-DTLS-DPD", fmt.Sprintf("%d", cstpDpd))
|
||||||
HttpSetHeader(w, "X-DTLS-Keepalive", fmt.Sprintf("%d", cstpKeepalive))
|
HttpSetHeader(w, "X-DTLS-Keepalive", fmt.Sprintf("%d", cstpKeepalive))
|
||||||
HttpSetHeader(w, "X-DTLS12-CipherSuite", "ECDHE-ECDSA-AES128-GCM-SHA256")
|
HttpSetHeader(w, "X-DTLS12-CipherSuite", dtlsCiphersuite)
|
||||||
|
|
||||||
HttpSetHeader(w, "X-CSTP-License", "accept")
|
HttpSetHeader(w, "X-CSTP-License", "accept")
|
||||||
HttpSetHeader(w, "X-CSTP-Routing-Filtering-Ignore", "false")
|
HttpSetHeader(w, "X-CSTP-Routing-Filtering-Ignore", "false")
|
||||||
|
@ -234,7 +238,11 @@ func SetPostAuthXml(g *dbdata.Group, w http.ResponseWriter) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
HttpSetHeader(w, "X-CSTP-Post-Auth-XML", result.String())
|
xmlAuth := ""
|
||||||
|
for _, v := range strings.Split(result.String(), "\n") {
|
||||||
|
xmlAuth += strings.TrimSpace(v)
|
||||||
|
}
|
||||||
|
HttpSetHeader(w, "X-CSTP-Post-Auth-XML", xmlAuth)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,13 +33,14 @@ func checkMacvtap() {
|
||||||
|
|
||||||
ifName := "anylinkMacvtap"
|
ifName := "anylinkMacvtap"
|
||||||
// 加载 macvtap
|
// 加载 macvtap
|
||||||
cmdstr0 := fmt.Sprintln("modprobe -i macvtap")
|
base.CheckModOrLoad("macvtap")
|
||||||
|
|
||||||
// 开启主网卡混杂模式
|
// 开启主网卡混杂模式
|
||||||
cmdstr1 := fmt.Sprintf("ip link set dev %s promisc on", base.Cfg.Ipv4Master)
|
cmdstr1 := fmt.Sprintf("ip link set dev %s promisc on", base.Cfg.Ipv4Master)
|
||||||
// 测试 macvtap 功能
|
// 测试 macvtap 功能
|
||||||
cmdstr2 := fmt.Sprintf("ip link add link %s name %s type macvtap mode bridge", base.Cfg.Ipv4Master, ifName)
|
cmdstr2 := fmt.Sprintf("ip link add link %s name %s type macvtap mode bridge", base.Cfg.Ipv4Master, ifName)
|
||||||
cmdstr3 := fmt.Sprintf("ip link del %s", ifName)
|
cmdstr3 := fmt.Sprintf("ip link del %s", ifName)
|
||||||
err := execCmd([]string{cmdstr0, cmdstr1, cmdstr2, cmdstr3})
|
err := execCmd([]string{cmdstr1, cmdstr2, cmdstr3})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatal(err)
|
base.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -54,7 +55,8 @@ func LinkMacvtap(cSess *sessdata.ConnSession) error {
|
||||||
cSess.SetIfName(ifName)
|
cSess.SetIfName(ifName)
|
||||||
|
|
||||||
cmdstr1 := fmt.Sprintf("ip link add link %s name %s type macvtap mode bridge", base.Cfg.Ipv4Master, ifName)
|
cmdstr1 := fmt.Sprintf("ip link add link %s name %s type macvtap mode bridge", base.Cfg.Ipv4Master, ifName)
|
||||||
cmdstr2 := fmt.Sprintf("ip link set dev %s up mtu %d address %s", ifName, cSess.Mtu, cSess.MacHw)
|
cmdstr2 := fmt.Sprintf("ip link set dev %s up mtu %d address %s alias %s.%s", ifName, cSess.Mtu, cSess.MacHw,
|
||||||
|
cSess.Group.Name, cSess.Username)
|
||||||
err := execCmd([]string{cmdstr1, cmdstr2})
|
err := execCmd([]string{cmdstr1, cmdstr2})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error(err)
|
base.Error(err)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package handler
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"runtime/debug"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
|
@ -101,11 +102,17 @@ func logAuditBatch() {
|
||||||
|
|
||||||
// 解析IP包的数据
|
// 解析IP包的数据
|
||||||
func logAudit(userName string, pl *sessdata.Payload) {
|
func logAudit(userName string, pl *sessdata.Payload) {
|
||||||
defer putPayload(pl)
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
base.Error("logAudit is panic: ", err, "\n", string(debug.Stack()), "\n", pl.Data)
|
||||||
|
}
|
||||||
|
putPayload(pl)
|
||||||
|
}()
|
||||||
|
|
||||||
if !(pl.LType == sessdata.LTypeIPData && pl.PType == 0x00) {
|
if !(pl.LType == sessdata.LTypeIPData && pl.PType == 0x00) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ipProto := waterutil.IPv4Protocol(pl.Data)
|
ipProto := waterutil.IPv4Protocol(pl.Data)
|
||||||
// 访问协议
|
// 访问协议
|
||||||
var accessProto uint8
|
var accessProto uint8
|
||||||
|
@ -118,11 +125,15 @@ func logAudit(userName string, pl *sessdata.Payload) {
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// IP报文只包含头部信息时, 则打印LOG,并退出
|
||||||
|
ipPl := waterutil.IPv4Payload(pl.Data)
|
||||||
|
if len(ipPl) < 4 {
|
||||||
|
base.Error("ipPl len < 4", ipPl, pl.Data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ipPort := (uint16(ipPl[2]) << 8) | uint16(ipPl[3])
|
||||||
ipSrc := waterutil.IPv4Source(pl.Data)
|
ipSrc := waterutil.IPv4Source(pl.Data)
|
||||||
ipDst := waterutil.IPv4Destination(pl.Data)
|
ipDst := waterutil.IPv4Destination(pl.Data)
|
||||||
ipPort := waterutil.IPv4DestinationPort(pl.Data)
|
|
||||||
|
|
||||||
b := getByte51()
|
b := getByte51()
|
||||||
key := *b
|
key := *b
|
||||||
copy(key[:16], ipSrc)
|
copy(key[:16], ipSrc)
|
||||||
|
@ -178,7 +189,6 @@ func logAudit(userName string, pl *sessdata.Payload) {
|
||||||
AccessProto: accessProto,
|
AccessProto: accessProto,
|
||||||
Info: info,
|
Info: info,
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case logBatch.LogChan <- audit:
|
case logBatch.LogChan <- audit:
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -29,7 +29,7 @@ func onTCP(payload []byte) (uint8, string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func sniNewParser(b []byte) (uint8, string) {
|
func sniNewParser(b []byte) (uint8, string) {
|
||||||
if len(b) < 2 || b[0] != 0x16 || b[1] != 0x03 {
|
if len(b) < 6 || b[0] != 0x16 || b[1] != 0x03 {
|
||||||
return acc_proto_tcp, ""
|
return acc_proto_tcp, ""
|
||||||
}
|
}
|
||||||
rest := b[5:]
|
rest := b[5:]
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
"github.com/bjdgyc/anylink/dbdata"
|
"github.com/bjdgyc/anylink/dbdata"
|
||||||
|
"github.com/bjdgyc/anylink/pkg/utils"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/pires/go-proxyproto"
|
"github.com/pires/go-proxyproto"
|
||||||
)
|
)
|
||||||
|
@ -53,7 +54,6 @@ func startTls() {
|
||||||
base.Trace("GetCertificate", chi.ServerName)
|
base.Trace("GetCertificate", chi.ServerName)
|
||||||
return dbdata.GetCertificateBySNI(chi.ServerName)
|
return dbdata.GetCertificateBySNI(chi.ServerName)
|
||||||
},
|
},
|
||||||
// InsecureSkipVerify: true,
|
|
||||||
}
|
}
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
|
@ -86,6 +86,14 @@ func startTls() {
|
||||||
|
|
||||||
func initRoute() http.Handler {
|
func initRoute() http.Handler {
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
|
// 所有路由添加安全头
|
||||||
|
r.Use(func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
utils.SetSecureHeader(w)
|
||||||
|
next.ServeHTTP(w, req)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
r.HandleFunc("/", LinkHome).Methods(http.MethodGet)
|
r.HandleFunc("/", LinkHome).Methods(http.MethodGet)
|
||||||
r.HandleFunc("/", LinkAuth).Methods(http.MethodPost)
|
r.HandleFunc("/", LinkAuth).Methods(http.MethodPost)
|
||||||
r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect)
|
r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect)
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// SetSecureHeader 设置安全的header头
|
||||||
|
// https://blog.csdn.net/liwan09/article/details/130248003
|
||||||
|
// https://zhuanlan.zhihu.com/p/335165168
|
||||||
|
func SetSecureHeader(w http.ResponseWriter) {
|
||||||
|
// Content-Length Date 默认已经存在
|
||||||
|
w.Header().Set("Server", "AnyLinkOpenSource")
|
||||||
|
// w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
// w.Header().Set("Transfer-Encoding", "chunked")
|
||||||
|
w.Header().Set("X-Aggregate-Auth", "1")
|
||||||
|
|
||||||
|
w.Header().Set("Cache-Control", "no-store,no-cache")
|
||||||
|
w.Header().Set("Pragma", "no-cache")
|
||||||
|
w.Header().Set("Connection", "keep-alive")
|
||||||
|
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
||||||
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
w.Header().Set("X-Download-Options", "noopen")
|
||||||
|
w.Header().Set("Content-Security-Policy", "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:; frame-ancestors 'self'; base-uri 'self'; block-all-mixed-content")
|
||||||
|
w.Header().Set("X-Permitted-Cross-Domain-Policies", "none")
|
||||||
|
w.Header().Set("Referrer-Policy", "same-origin")
|
||||||
|
w.Header().Set("Cross-Origin-Embedder-Policy", "require-corp")
|
||||||
|
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
|
||||||
|
w.Header().Set("Cross-Origin-Resource-Policy", "same-origin")
|
||||||
|
w.Header().Set("X-XSS-Protection", "1;mode=block")
|
||||||
|
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
||||||
|
|
||||||
|
// w.Header().Set("Clear-Site-Data", "cache,cookies,storage")
|
||||||
|
|
||||||
|
}
|
|
@ -11,12 +11,14 @@ Restart=on-failure
|
||||||
RestartSec=5s
|
RestartSec=5s
|
||||||
ExecStart=/usr/local/anylink-deploy/anylink --conf=/usr/local/anylink-deploy/conf/server.toml
|
ExecStart=/usr/local/anylink-deploy/anylink --conf=/usr/local/anylink-deploy/conf/server.toml
|
||||||
|
|
||||||
|
# systemctl --version
|
||||||
|
|
||||||
# systemd older than v236
|
# systemd older than v236
|
||||||
# ExecStart=/bin/bash -c 'exec /usr/local/anylink-deploy/anylink --conf=/usr/local/anylink-deploy/conf/server.toml >> /usr/local/anylink-deploy/log/anylink.log 2>&1'
|
# ExecStart=/bin/bash -c 'exec /usr/local/anylink-deploy/anylink --conf=/usr/local/anylink-deploy/conf/server.toml >> /usr/local/anylink-deploy/log/anylink.log 2>&1'
|
||||||
|
|
||||||
|
# systemd new than v236
|
||||||
StandardOutput=file:/usr/local/anylink-deploy/log/anylink.log
|
# StandardOutput=file:/usr/local/anylink-deploy/log/anylink.log
|
||||||
StandardError=file:/usr/local/anylink-deploy/log/anylink.log
|
# StandardError=file:/usr/local/anylink-deploy/log/anylink.log
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
|
@ -281,48 +281,60 @@
|
||||||
<el-tab-pane label="路由设置" name="route">
|
<el-tab-pane label="路由设置" name="route">
|
||||||
<el-form-item label="包含路由" prop="route_include">
|
<el-form-item label="包含路由" prop="route_include">
|
||||||
<el-row class="msg-info">
|
<el-row class="msg-info">
|
||||||
<el-col :span="20">输入CIDR格式如: 192.168.1.0/24</el-col>
|
<el-col :span="18">输入CIDR格式如: 192.168.1.0/24</el-col>
|
||||||
<el-col :span="4">
|
<el-col :span="2">
|
||||||
<el-button size="mini" type="success" icon="el-icon-plus" circle
|
<el-button size="mini" type="success" icon="el-icon-plus" circle
|
||||||
@click.prevent="addDomain(ruleForm.route_include)"></el-button>
|
@click.prevent="addDomain(ruleForm.route_include)"></el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
<el-col :span="4">
|
||||||
<el-row v-for="(item,index) in ruleForm.route_include"
|
<el-button size="mini" type="info" icon="el-icon-edit" circle
|
||||||
:key="index" style="margin-bottom: 5px" :gutter="10">
|
@click.prevent="openIpListDialog('route_include')"></el-button>
|
||||||
<el-col :span="10">
|
|
||||||
<el-input v-model="item.val"></el-input>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-input v-model="item.note" placeholder="备注"></el-input>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="2">
|
|
||||||
<el-button size="mini" type="danger" icon="el-icon-minus" circle
|
|
||||||
@click.prevent="removeDomain(ruleForm.route_include,index)"></el-button>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
<templete v-if="activeTab == 'route'">
|
||||||
|
<el-row v-for="(item,index) in ruleForm.route_include"
|
||||||
|
:key="index" style="margin-bottom: 5px" :gutter="10">
|
||||||
|
<el-col :span="10">
|
||||||
|
<el-input v-model="item.val"></el-input>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-input v-model="item.note" placeholder="备注"></el-input>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="2">
|
||||||
|
<el-button size="mini" type="danger" icon="el-icon-minus" circle
|
||||||
|
@click.prevent="removeDomain(ruleForm.route_include,index)"></el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</templete>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="排除路由" prop="route_exclude">
|
<el-form-item label="排除路由" prop="route_exclude">
|
||||||
<el-row class="msg-info">
|
<el-row class="msg-info">
|
||||||
<el-col :span="20">输入CIDR格式如: 192.168.2.0/24</el-col>
|
<el-col :span="18">输入CIDR格式如: 192.168.2.0/24</el-col>
|
||||||
<el-col :span="4">
|
<el-col :span="2">
|
||||||
<el-button size="mini" type="success" icon="el-icon-plus" circle
|
<el-button size="mini" type="success" icon="el-icon-plus" circle
|
||||||
@click.prevent="addDomain(ruleForm.route_exclude)"></el-button>
|
@click.prevent="addDomain(ruleForm.route_exclude)"></el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
<el-col :span="4">
|
||||||
<el-row v-for="(item,index) in ruleForm.route_exclude"
|
<el-button size="mini" type="info" icon="el-icon-edit" circle
|
||||||
:key="index" style="margin-bottom: 5px" :gutter="10">
|
@click.prevent="openIpListDialog('route_exclude')"></el-button>
|
||||||
<el-col :span="10">
|
|
||||||
<el-input v-model="item.val"></el-input>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-input v-model="item.note" placeholder="备注"></el-input>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="2">
|
|
||||||
<el-button size="mini" type="danger" icon="el-icon-minus" circle
|
|
||||||
@click.prevent="removeDomain(ruleForm.route_exclude,index)"></el-button>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
<templete v-if="activeTab == 'route'">
|
||||||
|
<el-row v-for="(item,index) in ruleForm.route_exclude"
|
||||||
|
:key="index" style="margin-bottom: 5px" :gutter="10">
|
||||||
|
<el-col :span="10">
|
||||||
|
<el-input v-model="item.val"></el-input>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-input v-model="item.note" placeholder="备注"></el-input>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="2">
|
||||||
|
<el-button size="mini" type="danger" icon="el-icon-minus" circle
|
||||||
|
@click.prevent="removeDomain(ruleForm.route_exclude,index)"></el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</templete>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="权限控制" name="link_acl">
|
<el-tab-pane label="权限控制" name="link_acl">
|
||||||
|
@ -365,6 +377,7 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="排除域名" prop="ds_exclude_domains">
|
<el-form-item label="排除域名" prop="ds_exclude_domains">
|
||||||
<el-input type="textarea" :rows="5" v-model="ruleForm.ds_exclude_domains" placeholder="输入域名用,号分隔,默认匹配所有子域名, 如baidu.com,163.com"></el-input>
|
<el-input type="textarea" :rows="5" v-model="ruleForm.ds_exclude_domains" placeholder="输入域名用,号分隔,默认匹配所有子域名, 如baidu.com,163.com"></el-input>
|
||||||
|
<div class="msg-info">注:域名拆分隧道,仅支持AnyConnect的桌面客户端,不支持移动端.</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
|
@ -398,6 +411,25 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
<!--编辑模式弹窗-->
|
||||||
|
<el-dialog
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
title="编辑模式"
|
||||||
|
:visible.sync="ipListDialog"
|
||||||
|
width="650px"
|
||||||
|
custom-class="valgin-dialog"
|
||||||
|
center>
|
||||||
|
<el-form ref="ipEditForm" label-width="80px">
|
||||||
|
<el-form-item label="路由表" prop="ip_list">
|
||||||
|
<el-input type="textarea" :rows="10" v-model="ipEditForm.ip_list" placeholder="每行一条路由,例:192.168.1.0/24,备注 或 192.168.1.0/24"></el-input>
|
||||||
|
<div class="msg-info">当前共 {{ ipEditForm.ip_list.trim() === '' ? 0 : ipEditForm.ip_list.trim().split("\n").length }} 条(注:AnyConnect客户端最多支持{{ this.maxRouteRows }}条路由)</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="ipEdit()" :loading="ipEditLoading">更新</el-button>
|
||||||
|
<el-button @click="ipListDialog = false">取 消</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -424,6 +456,7 @@ export default {
|
||||||
activeTab : "general",
|
activeTab : "general",
|
||||||
readMore: {},
|
readMore: {},
|
||||||
readMinRows : 5,
|
readMinRows : 5,
|
||||||
|
maxRouteRows : 2500,
|
||||||
defAuth : {
|
defAuth : {
|
||||||
type:'local',
|
type:'local',
|
||||||
radius:{addr:"", secret:""},
|
radius:{addr:"", secret:""},
|
||||||
|
@ -450,11 +483,17 @@ export default {
|
||||||
auth : {},
|
auth : {},
|
||||||
},
|
},
|
||||||
authLoginDialog : false,
|
authLoginDialog : false,
|
||||||
|
ipListDialog : false,
|
||||||
authLoginLoading : false,
|
authLoginLoading : false,
|
||||||
authLoginForm : {
|
authLoginForm : {
|
||||||
name : "",
|
name : "",
|
||||||
pwd : "",
|
pwd : "",
|
||||||
},
|
},
|
||||||
|
ipEditForm: {
|
||||||
|
ip_list: "",
|
||||||
|
type : "",
|
||||||
|
},
|
||||||
|
ipEditLoading : false,
|
||||||
authLoginRules: {
|
authLoginRules: {
|
||||||
name: [
|
name: [
|
||||||
{required: true, message: '请输入账号', trigger: 'blur'},
|
{required: true, message: '请输入账号', trigger: 'blur'},
|
||||||
|
@ -644,6 +683,70 @@ export default {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
openIpListDialog(type) {
|
||||||
|
this.ipListDialog = true;
|
||||||
|
this.ipEditForm.type = type;
|
||||||
|
this.ipEditForm.ip_list = this.ruleForm[type].map(item => item.val + (item.note ? "," + item.note : "")).join("\n");
|
||||||
|
},
|
||||||
|
ipEdit() {
|
||||||
|
this.ipEditLoading = true;
|
||||||
|
let ipList = [];
|
||||||
|
if (this.ipEditForm.ip_list.trim() !== "") {
|
||||||
|
ipList = this.ipEditForm.ip_list.trim().split("\n");
|
||||||
|
}
|
||||||
|
let arr = [];
|
||||||
|
for (let i = 0; i < ipList.length; i++) {
|
||||||
|
let item = ipList[i];
|
||||||
|
if (item.trim() === "") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let ip = item.split(",");
|
||||||
|
if (ip.length > 2) {
|
||||||
|
ip[1] = ip.slice(1).join(",");
|
||||||
|
}
|
||||||
|
let note = ip[1] ? ip[1] : "";
|
||||||
|
const pushToArr = () => {
|
||||||
|
arr.push({val: ip[0], note: note});
|
||||||
|
};
|
||||||
|
if (this.ipEditForm.type == "route_include" && ip[0] == "all") {
|
||||||
|
pushToArr();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let valid = this.isValidCIDR(ip[0]);
|
||||||
|
if (!valid.valid) {
|
||||||
|
this.$message.error("错误:CIDR格式错误,建议 " + ip[0] + " 改为 " + valid.suggestion);
|
||||||
|
this.ipEditLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pushToArr();
|
||||||
|
}
|
||||||
|
this.ruleForm[this.ipEditForm.type] = arr;
|
||||||
|
this.ipEditLoading = false;
|
||||||
|
this.ipListDialog = false;
|
||||||
|
},
|
||||||
|
isValidCIDR(input) {
|
||||||
|
const cidrRegex = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)\/([12]?\d|3[0-2])$/;
|
||||||
|
if (!cidrRegex.test(input)) {
|
||||||
|
return { valid: false, suggestion: null };
|
||||||
|
}
|
||||||
|
const [ip, mask] = input.split('/');
|
||||||
|
const maskNum = parseInt(mask);
|
||||||
|
const ipParts = ip.split('.').map(part => parseInt(part));
|
||||||
|
const binaryIP = ipParts.map(part => part.toString(2).padStart(8, '0')).join('');
|
||||||
|
for (let i = maskNum; i < 32; i++) {
|
||||||
|
if (binaryIP[i] === '1') {
|
||||||
|
const binaryNetworkPart = binaryIP.substring(0, maskNum).padEnd(32, '0');
|
||||||
|
const networkIPParts = [];
|
||||||
|
for (let j = 0; j < 4; j++) {
|
||||||
|
const octet = binaryNetworkPart.substring(j * 8, (j + 1) * 8);
|
||||||
|
networkIPParts.push(parseInt(octet, 2));
|
||||||
|
}
|
||||||
|
const suggestedIP = networkIPParts.join('.');
|
||||||
|
return { valid: false, suggestion: `${suggestedIP}/${mask}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { valid: true, suggestion: null };
|
||||||
|
},
|
||||||
resetForm(formName) {
|
resetForm(formName) {
|
||||||
this.$refs[formName].resetFields();
|
this.$refs[formName].resetFields();
|
||||||
},
|
},
|
||||||
|
|
|
@ -248,11 +248,14 @@
|
||||||
<el-form-item label="自定义首页" prop="homeindex">
|
<el-form-item label="自定义首页" prop="homeindex">
|
||||||
<el-input
|
<el-input
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:rows="5"
|
:rows="10"
|
||||||
placeholder="请输入内容"
|
placeholder="请输入内容"
|
||||||
v-model="dataOther.homeindex"
|
v-model="dataOther.homeindex"
|
||||||
>
|
>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
<el-tooltip content="自定义内容可以参考 home 目录下的文件" placement="top">
|
||||||
|
<i class="el-icon-question"></i>
|
||||||
|
</el-tooltip>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="账户开通邮件" prop="account_mail">
|
<el-form-item label="账户开通邮件" prop="account_mail">
|
||||||
|
|
Loading…
Reference in New Issue