mirror of
				https://github.com/bjdgyc/anylink.git
				synced 2025-11-04 19:16:22 +08:00 
			
		
		
		
	@@ -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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user