From e24aa2d900208975c8202ef6cd67d34f431b4ed8 Mon Sep 17 00:00:00 2001 From: lanrenwo Date: Mon, 13 Jun 2022 18:31:32 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=AD=96=E7=95=A5=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/admin/api_policy.go | 98 ++++++++ server/admin/server.go | 4 + server/dbdata/db.go | 2 +- server/dbdata/group.go | 2 +- server/dbdata/policy.go | 101 +++++++++ server/dbdata/tables.go | 14 ++ server/dbdata/user_test.go | 8 + server/handler/link_tunnel.go | 17 ++ web/src/layout/LayoutAside.vue | 1 + web/src/pages/user/Policy.vue | 402 +++++++++++++++++++++++++++++++++ web/src/plugins/router.js | 1 + 11 files changed, 648 insertions(+), 2 deletions(-) create mode 100644 server/admin/api_policy.go create mode 100644 server/dbdata/policy.go create mode 100644 web/src/pages/user/Policy.vue diff --git a/server/admin/api_policy.go b/server/admin/api_policy.go new file mode 100644 index 0000000..a4934c9 --- /dev/null +++ b/server/admin/api_policy.go @@ -0,0 +1,98 @@ +package admin + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "strconv" + + "github.com/bjdgyc/anylink/dbdata" +) + +func PolicyList(w http.ResponseWriter, r *http.Request) { + _ = r.ParseForm() + pageS := r.FormValue("page") + page, _ := strconv.Atoi(pageS) + if page < 1 { + page = 1 + } + + var pageSize = dbdata.PageSize + + count := dbdata.CountAll(&dbdata.Policy{}) + + var datas []dbdata.Policy + err := dbdata.Find(&datas, pageSize, page) + if err != nil { + RespError(w, RespInternalErr, err) + return + } + + data := map[string]interface{}{ + "count": count, + "page_size": pageSize, + "datas": datas, + } + + RespSucess(w, data) +} + +func PolicyDetail(w http.ResponseWriter, r *http.Request) { + _ = r.ParseForm() + idS := r.FormValue("id") + id, _ := strconv.Atoi(idS) + if id < 1 { + RespError(w, RespParamErr, "Id错误") + return + } + + var data dbdata.Policy + err := dbdata.One("Id", id, &data) + if err != nil { + RespError(w, RespInternalErr, err) + return + } + + RespSucess(w, data) +} + +func PolicySet(w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + RespError(w, RespInternalErr, err) + return + } + defer r.Body.Close() + v := &dbdata.Policy{} + err = json.Unmarshal(body, v) + if err != nil { + RespError(w, RespInternalErr, err) + return + } + + err = dbdata.SetPolicy(v) + if err != nil { + RespError(w, RespInternalErr, err) + return + } + + RespSucess(w, nil) +} + +func PolicyDel(w http.ResponseWriter, r *http.Request) { + _ = r.ParseForm() + idS := r.FormValue("id") + id, _ := strconv.Atoi(idS) + if id < 1 { + RespError(w, RespParamErr, "Id错误") + return + } + + data := dbdata.Policy{Id: id} + err := dbdata.Del(&data) + if err != nil { + RespError(w, RespInternalErr, err) + return + } + RespSucess(w, nil) +} diff --git a/server/admin/server.go b/server/admin/server.go index 98f3b20..75b6ac9 100644 --- a/server/admin/server.go +++ b/server/admin/server.go @@ -52,6 +52,10 @@ func StartAdmin() { r.HandleFunc("/user/ip_map/detail", UserIpMapDetail) r.HandleFunc("/user/ip_map/set", UserIpMapSet) r.HandleFunc("/user/ip_map/del", UserIpMapDel) + r.HandleFunc("/user/policy/list", PolicyList) + r.HandleFunc("/user/policy/detail", PolicyDetail) + r.HandleFunc("/user/policy/set", PolicySet) + r.HandleFunc("/user/policy/del", PolicyDel) r.HandleFunc("/group/list", GroupList) r.HandleFunc("/group/names", GroupNames) diff --git a/server/dbdata/db.go b/server/dbdata/db.go index b6f9263..994651b 100644 --- a/server/dbdata/db.go +++ b/server/dbdata/db.go @@ -25,7 +25,7 @@ func initDb() { } // 初始化数据库 - err = xdb.Sync2(&User{}, &Setting{}, &Group{}, &IpMap{}, &AccessAudit{}) + err = xdb.Sync2(&User{}, &Setting{}, &Group{}, &IpMap{}, &AccessAudit{}, &Policy{}) if err != nil { base.Fatal(err) } diff --git a/server/dbdata/group.go b/server/dbdata/group.go index 52524d1..112805d 100644 --- a/server/dbdata/group.go +++ b/server/dbdata/group.go @@ -161,7 +161,7 @@ func SetGroup(g *Group) error { } else { _, ok := authRegistry[authType] if !ok { - return errors.New("未知的认证方式: " + fmt.Sprintf("%s", g.Auth["type"])) + return errors.New("未知的认证方式: " + authType) } auth := makeInstance(authType).(IUserAuth) err = auth.checkData(g.Auth) diff --git a/server/dbdata/policy.go b/server/dbdata/policy.go new file mode 100644 index 0000000..a6e6ad5 --- /dev/null +++ b/server/dbdata/policy.go @@ -0,0 +1,101 @@ +package dbdata + +import ( + "errors" + "net" + "strings" + "time" +) + +func GetPolicy(Username string) *Policy { + policyData := &Policy{} + err := One("Username", Username, policyData) + if err != nil { + return policyData + } + return policyData +} + +func SetPolicy(p *Policy) error { + var err error + if p.Username == "" { + return errors.New("用户名错误") + } + + // 包含路由 + routeInclude := []ValData{} + for _, v := range p.RouteInclude { + if v.Val != "" { + if v.Val == All { + routeInclude = append(routeInclude, v) + continue + } + + ipMask, _, err := parseIpNet(v.Val) + if err != nil { + return errors.New("RouteInclude 错误" + err.Error()) + } + + v.IpMask = ipMask + routeInclude = append(routeInclude, v) + } + } + p.RouteInclude = routeInclude + // 包含路由 + routeExclude := []ValData{} + for _, v := range p.RouteExclude { + if v.Val != "" { + ipMask, _, err := parseIpNet(v.Val) + if err != nil { + return errors.New("RouteExclude 错误" + err.Error()) + } + v.IpMask = ipMask + routeExclude = append(routeExclude, v) + } + } + p.RouteExclude = routeExclude + + // DNS 判断 + clientDns := []ValData{} + for _, v := range p.ClientDns { + if v.Val != "" { + ip := net.ParseIP(v.Val) + if ip.String() != v.Val { + return errors.New("DNS IP 错误") + } + clientDns = append(clientDns, v) + } + } + if len(routeInclude) == 0 || (len(routeInclude) == 1 && routeInclude[0].Val == "all") { + if len(clientDns) == 0 { + return errors.New("默认路由,必须设置一个DNS") + } + } + p.ClientDns = clientDns + + // 域名拆分隧道,不能同时填写 + p.DsIncludeDomains = strings.TrimSpace(p.DsIncludeDomains) + p.DsExcludeDomains = strings.TrimSpace(p.DsExcludeDomains) + if p.DsIncludeDomains != "" && p.DsExcludeDomains != "" { + return errors.New("包含/排除域名不能同时填写") + } + // 校验包含域名的格式 + err = CheckDomainNames(p.DsIncludeDomains) + if err != nil { + return errors.New("包含域名有误:" + err.Error()) + } + // 校验排除域名的格式 + err = CheckDomainNames(p.DsExcludeDomains) + if err != nil { + return errors.New("排除域名有误:" + err.Error()) + } + + p.UpdatedAt = time.Now() + if p.Id > 0 { + err = Set(p) + } else { + err = Add(p) + } + + return err +} diff --git a/server/dbdata/tables.go b/server/dbdata/tables.go index 9dd2b5d..56b2dbe 100644 --- a/server/dbdata/tables.go +++ b/server/dbdata/tables.go @@ -68,3 +68,17 @@ type AccessAudit struct { DstPort uint16 `json:"dst_port" xorm:"not null"` CreatedAt time.Time `json:"created_at" xorm:"DateTime"` } + +type Policy struct { + Id int `json:"id" xorm:"pk autoincr not null"` + Username string `json:"username" xorm:"varchar(60) not null unique"` + AllowLan bool `json:"allow_lan" xorm:"Bool"` + ClientDns []ValData `json:"client_dns" xorm:"Text"` + RouteInclude []ValData `json:"route_include" xorm:"Text"` + RouteExclude []ValData `json:"route_exclude" xorm:"Text"` + DsExcludeDomains string `json:"ds_exclude_domains" xorm:"Text"` + DsIncludeDomains string `json:"ds_include_domains" xorm:"Text"` + Status int8 `json:"status" xorm:"Int"` // 1正常 0 禁用 + CreatedAt time.Time `json:"created_at" xorm:"DateTime created"` + UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"` +} diff --git a/server/dbdata/user_test.go b/server/dbdata/user_test.go index 545ca45..8e46dcc 100644 --- a/server/dbdata/user_test.go +++ b/server/dbdata/user_test.go @@ -58,4 +58,12 @@ func TestCheckUser(t *testing.T) { ast.Equal("aaa Radius服务器连接异常, 请检测服务器和端口", err.Error()) } + // 添加用户策略 + dns2 := []ValData{{Val: "8.8.8.8"}} + route2 := []ValData{{Val: "192.168.2.1/24"}} + p1 := Policy{Username: "aaa", Status: 1, ClientDns: dns2, RouteInclude: route2} + err = SetPolicy(&p1) + ast.Nil(err) + err = CheckUser("aaa", u.PinCode, group) + ast.Nil(err) } diff --git a/server/handler/link_tunnel.go b/server/handler/link_tunnel.go index 52aac12..42bf87f 100644 --- a/server/handler/link_tunnel.go +++ b/server/handler/link_tunnel.go @@ -100,6 +100,9 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) { //HttpSetHeader(w, "X-CSTP-Default-Domain", cSess.LocalIp) HttpSetHeader(w, "X-CSTP-Base-MTU", cstpBaseMtu) + // 设置用户策略 + SetUserPolicy(sess.Username, cSess.Group) + // 允许本地LAN访问vpn网络,必须放在路由的第一个 if cSess.Group.AllowLan { HttpSetHeader(w, "X-CSTP-Split-Exclude", "0.0.0.0/255.255.255.255") @@ -209,3 +212,17 @@ func SetPostAuthXml(g *dbdata.Group, w http.ResponseWriter) error { HttpSetHeader(w, "X-CSTP-Post-Auth-XML", result.String()) return nil } + +// 设置用户策略, 覆盖Group的属性值 +func SetUserPolicy(username string, g *dbdata.Group) { + userPolicy := dbdata.GetPolicy(username) + if userPolicy.Id != 0 && userPolicy.Status == 1 { + base.Debug(username + " use UserPolicy") + g.AllowLan = userPolicy.AllowLan + g.ClientDns = userPolicy.ClientDns + g.RouteInclude = userPolicy.RouteInclude + g.RouteExclude = userPolicy.RouteExclude + g.DsExcludeDomains = userPolicy.DsExcludeDomains + g.DsIncludeDomains = userPolicy.DsIncludeDomains + } +} diff --git a/web/src/layout/LayoutAside.vue b/web/src/layout/LayoutAside.vue index fb2973c..fbb82dd 100644 --- a/web/src/layout/LayoutAside.vue +++ b/web/src/layout/LayoutAside.vue @@ -42,6 +42,7 @@ 用户列表 + 用户策略 在线用户 IP映射 diff --git a/web/src/pages/user/Policy.vue b/web/src/pages/user/Policy.vue new file mode 100644 index 0000000..e009fad --- /dev/null +++ b/web/src/pages/user/Policy.vue @@ -0,0 +1,402 @@ + + + + + diff --git a/web/src/plugins/router.js b/web/src/plugins/router.js index f574b08..07b42e5 100644 --- a/web/src/plugins/router.js +++ b/web/src/plugins/router.js @@ -20,6 +20,7 @@ const routes = [ {path: 'set/audit', component: () => import('@/pages/set/Audit')}, {path: 'user/list', component: () => import('@/pages/user/List')}, + {path: 'user/policy', component: () => import('@/pages/user/Policy')}, {path: 'user/online', component: () => import('@/pages/user/Online')}, {path: 'user/ip_map', component: () => import('@/pages/user/IpMap')}, From 68076f58eb5ca51d57a371a270be0af71c6d57b4 Mon Sep 17 00:00:00 2001 From: lanrenwo Date: Mon, 13 Jun 2022 18:35:24 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9SetPolicy=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/dbdata/policy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/dbdata/policy.go b/server/dbdata/policy.go index a6e6ad5..9777804 100644 --- a/server/dbdata/policy.go +++ b/server/dbdata/policy.go @@ -41,7 +41,7 @@ func SetPolicy(p *Policy) error { } } p.RouteInclude = routeInclude - // 包含路由 + // 排除路由 routeExclude := []ValData{} for _, v := range p.RouteExclude { if v.Val != "" { From 6a997bfd467aae907353c32b535e956d80612a0e Mon Sep 17 00:00:00 2001 From: lanrenwo Date: Tue, 14 Jun 2022 09:23:38 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=8F=90=E4=BA=A4policy=5Ftest.go=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/dbdata/policy_test.go | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 server/dbdata/policy_test.go diff --git a/server/dbdata/policy_test.go b/server/dbdata/policy_test.go new file mode 100644 index 0000000..e2dd409 --- /dev/null +++ b/server/dbdata/policy_test.go @@ -0,0 +1,45 @@ +package dbdata + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetPolicy(t *testing.T) { + ast := assert.New(t) + + preIpData() + defer closeIpdata() + + // 添加 Policy + p1 := Policy{Username: "a1", ClientDns: []ValData{{Val: "114.114.114.114"}}, DsExcludeDomains: "baidu.com,163.com"} + err := SetPolicy(&p1) + ast.Nil(err) + + p2 := Policy{Username: "a2", ClientDns: []ValData{{Val: "114.114.114.114"}}, DsExcludeDomains: "com.cn,qq.com"} + err = SetPolicy(&p2) + ast.Nil(err) + + route := []ValData{{Val: "192.168.1.1/24"}} + p3 := Policy{Username: "a3", ClientDns: []ValData{{Val: "114.114.114.114"}}, RouteInclude: route, DsExcludeDomains: "com.cn,qq.com"} + err = SetPolicy(&p3) + ast.Nil(err) + // 判断 IpMask + ast.Equal(p3.RouteInclude[0].IpMask, "192.168.1.1/255.255.255.0") + + route2 := []ValData{{Val: "192.168.2.1/24"}} + p4 := Policy{Username: "a4", ClientDns: []ValData{{Val: "114.114.114.114"}}, RouteExclude: route2, DsIncludeDomains: "com.cn,qq.com"} + err = SetPolicy(&p4) + ast.Nil(err) + // 判断 IpMask + ast.Equal(p4.RouteExclude[0].IpMask, "192.168.2.1/255.255.255.0") + + // 判断所有数据 + var userPolicy *Policy + pAll := []string{"a1", "a2", "a3", "a4"} + for _, v := range pAll { + userPolicy = GetPolicy(v) + ast.NotEqual(userPolicy.Id, 0, "user policy id is zero") + } +}