mirror of https://github.com/bjdgyc/anylink.git
commit
1b066ef602
|
@ -23,7 +23,7 @@ AnyLink 是一个企业级远程办公 sslvpn 的软件,可以支持多人同
|
||||||
AnyLink 基于 [ietf-openconnect](https://tools.ietf.org/html/draft-mavrogiannopoulos-openconnect-02)
|
AnyLink 基于 [ietf-openconnect](https://tools.ietf.org/html/draft-mavrogiannopoulos-openconnect-02)
|
||||||
协议开发,并且借鉴了 [ocserv](http://ocserv.gitlab.io/www/index.html) 的开发思路,使其可以同时兼容 AnyConnect 客户端。
|
协议开发,并且借鉴了 [ocserv](http://ocserv.gitlab.io/www/index.html) 的开发思路,使其可以同时兼容 AnyConnect 客户端。
|
||||||
|
|
||||||
AnyLink 使用 TLS/DTLS 进行数据加密,因此需要 RSA 或 ECC 证书,可以通过 Let's Encrypt 和 TrustAsia 申请免费的 SSL 证书。
|
AnyLink 使用 TLS/DTLS 进行数据加密,因此需要 RSA 或 ECC 证书,可以使用私有自签证书,可以通过 Let's Encrypt 和 TrustAsia 申请免费的 SSL 证书。
|
||||||
|
|
||||||
AnyLink 服务端仅在 CentOS 7、CentOS 8、Ubuntu 18.04、Ubuntu 20.04 测试通过,如需要安装在其他系统,需要服务端支持 tun/tap
|
AnyLink 服务端仅在 CentOS 7、CentOS 8、Ubuntu 18.04、Ubuntu 20.04 测试通过,如需要安装在其他系统,需要服务端支持 tun/tap
|
||||||
功能、ip 设置命令、iptables命令。
|
功能、ip 设置命令、iptables命令。
|
||||||
|
@ -60,9 +60,9 @@ AnyLink 服务端仅在 CentOS 7、CentOS 8、Ubuntu 18.04、Ubuntu 20.04 测试
|
||||||
|
|
||||||
### 使用问题
|
### 使用问题
|
||||||
|
|
||||||
> 对于测试环境,可以使用 vpn.test.vqilu.cn 绑定host进行测试
|
> 对于测试环境,可以直接进行测试,需要客户端取消勾选【阻止不受信任的服务器(Block connections to untrusted servers)】
|
||||||
>
|
>
|
||||||
> 对于线上环境,必须申请安全的https证书(跟nginx使用的证书类型一致),不支持私有证书连接
|
> 对于线上环境,尽量申请安全的https证书(跟nginx使用的pem证书类型一致)
|
||||||
>
|
>
|
||||||
> 群共享文件有相关客户端软件下载,其他版本没有测试过,不保证使用正常
|
> 群共享文件有相关客户端软件下载,其他版本没有测试过,不保证使用正常
|
||||||
>
|
>
|
||||||
|
@ -133,6 +133,7 @@ sudo ./anylink
|
||||||
- [x] 流量压缩功能
|
- [x] 流量压缩功能
|
||||||
- [x] 出口 IP 自动放行
|
- [x] 出口 IP 自动放行
|
||||||
- [x] 支持多服务的配置区分
|
- [x] 支持多服务的配置区分
|
||||||
|
- [x] 支持私有自签证书
|
||||||
- [ ] 基于 ipvtap 设备的桥接访问模式
|
- [ ] 基于 ipvtap 设备的桥接访问模式
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
|
@ -214,6 +214,7 @@ type userAccountMailData struct {
|
||||||
PinCode string
|
PinCode string
|
||||||
OtpImg string
|
OtpImg string
|
||||||
OtpImgBase64 string
|
OtpImgBase64 string
|
||||||
|
DisableOtp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func userAccountMail(user *dbdata.User) error {
|
func userAccountMail(user *dbdata.User) error {
|
||||||
|
@ -265,6 +266,7 @@ func userAccountMail(user *dbdata.User) error {
|
||||||
PinCode: user.PinCode,
|
PinCode: user.PinCode,
|
||||||
OtpImg: fmt.Sprintf("https://%s/otp_qr?id=%d&jwt=%s", setting.LinkAddr, user.Id, tokenString),
|
OtpImg: fmt.Sprintf("https://%s/otp_qr?id=%d&jwt=%s", setting.LinkAddr, user.Id, tokenString),
|
||||||
OtpImgBase64: "data:image/png;base64," + otpData,
|
OtpImgBase64: "data:image/png;base64," + otpData,
|
||||||
|
DisableOtp: user.DisableOtp,
|
||||||
}
|
}
|
||||||
w := bytes.NewBufferString("")
|
w := bytes.NewBufferString("")
|
||||||
t, _ := template.New("auth_complete").Parse(htmlBody)
|
t, _ := template.New("auth_complete").Parse(htmlBody)
|
||||||
|
@ -273,12 +275,18 @@ func userAccountMail(user *dbdata.User) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// fmt.Println(w.String())
|
// fmt.Println(w.String())
|
||||||
imgData, _ := userOtpQr(user.Id, false)
|
|
||||||
attach := &mail.File{
|
var attach *mail.File
|
||||||
MimeType: "image/png",
|
if user.DisableOtp {
|
||||||
Name: "userOtpQr.png",
|
attach = nil
|
||||||
Data: []byte(imgData),
|
} else {
|
||||||
Inline: true,
|
imgData, _ := userOtpQr(user.Id, false)
|
||||||
|
attach = &mail.File{
|
||||||
|
MimeType: "image/png",
|
||||||
|
Name: "userOtpQr.png",
|
||||||
|
Data: []byte(imgData),
|
||||||
|
Inline: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SendMail(base.Cfg.Issuer, user.Email, w.String(), attach)
|
return SendMail(base.Cfg.Issuer, user.Email, w.String(), attach)
|
||||||
|
|
|
@ -111,12 +111,6 @@ func StartAdmin() {
|
||||||
selectedCipherSuites = append(selectedCipherSuites, s.ID)
|
selectedCipherSuites = append(selectedCipherSuites, s.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tlscert, _, err := dbdata.ParseCert(); err != nil {
|
|
||||||
base.Fatal("证书加载失败", err)
|
|
||||||
} else {
|
|
||||||
dbdata.LoadCertificate(tlscert)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置tls信息
|
// 设置tls信息
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
NextProtos: []string{"http/1.1"},
|
NextProtos: []string{"http/1.1"},
|
||||||
|
|
|
@ -174,6 +174,9 @@ func CheckErrNotFound(err error) bool {
|
||||||
return err == ErrNotFound
|
return err == ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// base64 图片
|
||||||
|
// 用户动态码(请妥善保存):<br/>
|
||||||
|
// <img src="{{.OtpImgBase64}}"/><br/>
|
||||||
const accountMail = `<p>您好:</p>
|
const accountMail = `<p>您好:</p>
|
||||||
<p> 您的{{.Issuer}}账号已经审核开通。</p>
|
<p> 您的{{.Issuer}}账号已经审核开通。</p>
|
||||||
<p>
|
<p>
|
||||||
|
@ -181,17 +184,18 @@ const accountMail = `<p>您好:</p>
|
||||||
用户组: <b>{{.Group}}</b> <br/>
|
用户组: <b>{{.Group}}</b> <br/>
|
||||||
用户名: <b>{{.Username}}</b> <br/>
|
用户名: <b>{{.Username}}</b> <br/>
|
||||||
用户PIN码: <b>{{.PinCode}}</b> <br/>
|
用户PIN码: <b>{{.PinCode}}</b> <br/>
|
||||||
|
{{if .DisableOtp}}
|
||||||
|
<!-- nothing -->
|
||||||
|
{{else}}
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
用户动态码(3天后失效):<br/>
|
用户动态码(3天后失效):<br/>
|
||||||
<img src="{{.OtpImg}}"/><br/>
|
<img src="{{.OtpImg}}"/><br/>
|
||||||
|
|
||||||
用户动态码(请妥善保存):<br/>
|
|
||||||
<img src="{{.OtpImgBase64}}"/><br/>
|
|
||||||
|
|
||||||
下面是兼容 gmail 的写法
|
|
||||||
-->
|
-->
|
||||||
用户动态码(请妥善保存):<br/>
|
用户动态码(请妥善保存):<br/>
|
||||||
<img src="cid:userOtpQr.png" alt="userOtpQr" /><br/>
|
<img src="cid:userOtpQr.png" alt="userOtpQr" /><br/>
|
||||||
|
|
||||||
|
{{end}}
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
使用说明:
|
使用说明:
|
||||||
|
|
|
@ -17,7 +17,10 @@ import (
|
||||||
"github.com/bjdgyc/anylink/sessdata"
|
"github.com/bjdgyc/anylink/sessdata"
|
||||||
)
|
)
|
||||||
|
|
||||||
var profileHash = ""
|
var (
|
||||||
|
profileHash = ""
|
||||||
|
certHash = ""
|
||||||
|
)
|
||||||
|
|
||||||
func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO 调试信息输出
|
// TODO 调试信息输出
|
||||||
|
@ -138,7 +141,7 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
||||||
other := &dbdata.SettingOther{}
|
other := &dbdata.SettingOther{}
|
||||||
_ = dbdata.SettingGet(other)
|
_ = dbdata.SettingGet(other)
|
||||||
rd := RequestData{SessionId: sess.Sid, SessionToken: sess.Sid + "@" + sess.Token,
|
rd := RequestData{SessionId: sess.Sid, SessionToken: sess.Sid + "@" + sess.Token,
|
||||||
Banner: other.Banner, ProfileName: base.Cfg.ProfileName, ProfileHash: profileHash}
|
Banner: other.Banner, ProfileName: base.Cfg.ProfileName, ProfileHash: profileHash, CertHash: certHash}
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
tplRequest(tpl_complete, w, rd)
|
tplRequest(tpl_complete, w, rd)
|
||||||
base.Info("login", cr.Auth.Username, userAgent)
|
base.Info("login", cr.Auth.Username, userAgent)
|
||||||
|
@ -178,6 +181,7 @@ type RequestData struct {
|
||||||
Banner string
|
Banner string
|
||||||
ProfileName string
|
ProfileName string
|
||||||
ProfileHash string
|
ProfileHash string
|
||||||
|
CertHash string
|
||||||
}
|
}
|
||||||
|
|
||||||
var auth_request = `<?xml version="1.0" encoding="UTF-8"?>
|
var auth_request = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
@ -223,7 +227,7 @@ var auth_complete = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
</capabilities>
|
</capabilities>
|
||||||
<config client="vpn" type="private">
|
<config client="vpn" type="private">
|
||||||
<vpn-base-config>
|
<vpn-base-config>
|
||||||
<server-cert-hash>240B97A685B2BFA66AD699B90AAC49EA66495D69</server-cert-hash>
|
<server-cert-hash>{{.CertHash}}</server-cert-hash>
|
||||||
</vpn-base-config>
|
</vpn-base-config>
|
||||||
<opaque is-for="vpn-client"></opaque>
|
<opaque is-for="vpn-client"></opaque>
|
||||||
<vpn-profile-manifest>
|
<vpn-profile-manifest>
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
|
@ -36,6 +39,19 @@ func startTls() {
|
||||||
// certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
// certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
tlscert, _, err := dbdata.ParseCert()
|
||||||
|
if err != nil {
|
||||||
|
base.Fatal("证书加载失败", err)
|
||||||
|
}
|
||||||
|
dbdata.LoadCertificate(tlscert)
|
||||||
|
|
||||||
|
// 计算证书hash值
|
||||||
|
s1 := sha1.New()
|
||||||
|
s1.Write(tlscert.Certificate[0])
|
||||||
|
h2s := hex.EncodeToString(s1.Sum(nil))
|
||||||
|
certHash = strings.ToUpper(h2s)
|
||||||
|
base.Info("certHash", certHash)
|
||||||
|
|
||||||
// 修复 CVE-2016-2183
|
// 修复 CVE-2016-2183
|
||||||
// https://segmentfault.com/a/1190000038486901
|
// https://segmentfault.com/a/1190000038486901
|
||||||
// nmap -sV --script ssl-enum-ciphers -p 443 www.example.com
|
// nmap -sV --script ssl-enum-ciphers -p 443 www.example.com
|
||||||
|
|
|
@ -201,9 +201,9 @@
|
||||||
<el-switch v-model="ruleForm.allow_lan"></el-switch>
|
<el-switch v-model="ruleForm.allow_lan"></el-switch>
|
||||||
<div class="msg-info">
|
<div class="msg-info">
|
||||||
注:本地网络 指的是:
|
注:本地网络 指的是:
|
||||||
运行 anyconnect 客户端的PC 所在的的网络,既本地路由网段。
|
运行 anyconnect 客户端的PC 所在的的网络,即本地路由网段。
|
||||||
开启后,PC本地路由网段的数据就不会走隧道链路转发数据了。
|
开启后,PC本地路由网段的数据就不会走隧道链路转发数据了。
|
||||||
同时 anyconnect 客户端需要勾选本地网络(Local Lan)的开关,功能才能生效。
|
同时 anyconnect 客户端需要勾选本地网络(Allow Local Lan)的开关,功能才能生效。
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue