diff --git a/README.md b/README.md index 5accb6e..aeb6276 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# [Janusec Application Gateway / JANUSEC应用网关](https://www.janusec.com/) [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Protect%20web%20applications%20from%20network%20attacks%20with%20open%20source%20Janusec%20Application%20Gateway&url=https://github.com/Janusec/janusec&via=janusec&hashtags=waf,web,application,firewall,gateway) +# Janusec Application Gateway / JANUSEC应用网关 [![Build Status](https://travis-ci.org/Janusec/janusec.svg?branch=master)](https://travis-ci.org/Janusec/janusec) @@ -61,11 +61,12 @@ JANUSEC应用网关的主要功能有: ## 产品网站 -https://doc.janusec.com/cn/ +https://janusec.github.io/cn/ + ## 需求 -* SQLite3,或PostgreSQL 10/11/12/13+ (开发环境,及生产环境主节点需要) +* SQLite3,或PostgreSQL 10/11/12/13/14+ (开发环境,及生产环境主节点需要) * Debian 9/10/11+, CentOS/RHEL 7/8+, 首选Debian 10/11+ * systemd * nftables @@ -73,9 +74,7 @@ https://doc.janusec.com/cn/ ## 部署快速指引 -详细文档可在这里获取: [快速入门](https://doc.janusec.com/cn/quick-start/) - -如希望快速体验,可尝试使用 [Docker镜像](https://doc.janusec.com/cn/appendix-docker/) +详细文档可在这里获取: [Janusec应用网关快速入门](https://janusec.github.io/cn/quick-start/) ## 开发快速指引 @@ -112,7 +111,7 @@ Janusec将自动加密数据库口令 只使用主节点时,任意应用域名均可用于访问管理入口。 如果使用了副本节点,应为主节点申请一个单独的域名。 -[Janusec应用网关配置](https://doc.janusec.com/cn/quick-start/) +[Janusec应用网关配置](https://janusec.github.io/cn/quick-start/) ## 发布 @@ -128,9 +127,10 @@ Janusec将自动加密数据库口令 Web化管理所需的文件在 `./static/janusec-admin/` 目录, 源码在 [Janusec-Admin Github](https://github.com/Janusec/janusec-admin) ,前端源码使用Angular 9. -## 许可证 +## 多许可证 -Janusec应用网关源文件使用GNU [AGPLv3](http://www.gnu.org/licenses/agpl-3.0.html)授权. +JANUSEC应用网关开源版本的源文件使用GNU [AGPLv3](http://www.gnu.org/licenses/agpl-3.0.html)授权. +专业增强特性版本闭源发布,增强特性包括:GSLB、Cookie合规(应用无需修改)等。 ## 增强特性 @@ -145,9 +145,8 @@ Janusec应用网关源文件使用GNU [AGPLv3](http://www.gnu.org/licenses/agpl- ## 支持 -* 产品网站 [https://doc.janusec.com/cn/](https://doc.janusec.com/cn/) -* 官方网站: [https://www.janusec.com/](https://www.janusec.com/) -* Email: `support#janusec.com` +* 产品网站 [https://janusec.github.io/cn/](https://janusec.github.io/cn/) +* Email: `support`**@**`janusec.com` * QQ群: 776900157 @@ -215,7 +214,7 @@ https://janusec.github.io/ ## Requirements -* SQLite3 or PostgreSQL 10/11/12/13+ (Required by Development and Primary Node of Deployment) +* SQLite3 or PostgreSQL 10/11/12/13/14+ (Required by Development and Primary Node of Deployment) * Debian 9/10/11+, CentOS/RHEL 7/8+, Debian 10/11+ is preferred * systemd * nftables @@ -223,9 +222,7 @@ https://janusec.github.io/ ## Quick Start for Deployment -Detailed documentation is available at: [Janusec Application Gateway Quick Start](https://janusec.github.io/documentation/quick-start/). - -You can also try it with [Docker Image](https://janusec.github.io/documentation/appendix-docker/) +Detailed documentation is available at: [Janusec Application Gateway Quick Start](https://janusec.github.io/documentation/quick-start/). ## Quick Start for Developer @@ -277,9 +274,11 @@ The release package is under `./dist` . Release directory is `./static/janusec-admin/` , and source code is available at [Janusec-Admin Github](https://github.com/Janusec/janusec-admin) with Angular 9. -## LICENSE +## Multiple LICENSES + +The open source files are made available under the terms of the GNU Affero General Public License ([GNU AGPLv3](http://www.gnu.org/licenses/agpl-3.0.html)). -Janusec Application Gateway source files are made available under the terms of the GNU Affero General Public License ([GNU AGPLv3](http://www.gnu.org/licenses/agpl-3.0.html)). +The professional enhanced version is released in closed source, and the enhanced features including GSLB, Cookie compliance (No need to modify applications), etc. ## Professional Plus Edition @@ -294,7 +293,6 @@ The download link is available at: [Quick Start](https://janusec.github.io/doc ## Support -* Product: [https://janusec.github.io/](https://janusec.github.io/) -* Official site : [https://www.janusec.com/](https://www.janusec.com/) -* Email: `support#janusec.com` +* Product: [https://janusec.github.io/](https://janusec.github.io/) +* Email: `support`**@**`janusec.com` * QQ Group: 776900157 diff --git a/backend/application.go b/backend/application.go index a75aee7..5488eda 100644 --- a/backend/application.go +++ b/backend/application.go @@ -74,6 +74,8 @@ func SelectBackendRoute(app *models.Application, r *http.Request, srcIP string) // get online destinations var onlineDests = []*models.Destination{} for _, dest := range dests { + dest.Mutex.Lock() + defer dest.Mutex.Unlock() if dest.Online { onlineDests = append(onlineDests, dest) } diff --git a/backend/destination.go b/backend/destination.go index c4d186c..e34482a 100644 --- a/backend/destination.go +++ b/backend/destination.go @@ -9,11 +9,18 @@ package backend import ( "encoding/json" + "janusec/data" "janusec/models" "janusec/utils" "net" "net/http" "time" + + "github.com/patrickmn/go-cache" +) + +var ( + offlineCache = cache.New(30*time.Second, 30*time.Second) ) // ContainsDestinationID ... @@ -31,13 +38,17 @@ func ContainsDestinationID(destinations []*models.Destination, destID int64) boo func CheckOfflineDestinations(nowTimeStamp int64) { for _, app := range Apps { for _, dest := range app.Destinations { + dest.Mutex.Lock() + defer dest.Mutex.Unlock() if dest.RouteType == models.ReverseProxyRoute && !dest.Online { - go func(dest *models.Destination) { - conn, err := net.DialTimeout("tcp", dest.Destination, time.Second) + go func(dest2 *models.Destination) { + conn, err := net.DialTimeout("tcp", dest2.Destination, time.Second) if err == nil { defer conn.Close() - dest.Online = true - dest.CheckTime = nowTimeStamp + dest2.Mutex.Lock() + defer dest2.Mutex.Unlock() + dest2.Online = true + dest2.CheckTime = nowTimeStamp } }(dest) } else if dest.RouteType == models.K8S_Ingress && !dest.Online { @@ -54,8 +65,6 @@ func CheckOfflineDestinations(nowTimeStamp int64) { if err != nil { utils.DebugPrintln("Unmarshal K8S API", err) } - dest.Mutex.Lock() - defer dest.Mutex.Unlock() dest.Pods = "" for _, podItem := range pods.Items { if podItem.Status.Phase == "Running" { @@ -71,3 +80,45 @@ func CheckOfflineDestinations(nowTimeStamp int64) { } } } + +// SetDestinationOffline added on Mar 23, 2024, v1.5.0 +func SetDestinationOffline(dest *models.Destination) { + targetDest := dest.Destination + if dest.RouteType == models.K8S_Ingress { + targetDest = dest.PodsAPI + } + if count, ok := offlineCache.Get(targetDest); !ok { + offlineCache.Set(targetDest, int64(1), cache.DefaultExpiration) + } else { + nowCount := count.(int64) + int64(1) + if nowCount > 5 { + // more than 5 requests timeout + dest.Online = false + app, err := GetApplicationByID(dest.AppID) + if err == nil { + sendOfflineNotification(app, targetDest) + } + } + offlineCache.Set(targetDest, nowCount, cache.DefaultExpiration) + } +} + +// sendOfflineNotification ... +func sendOfflineNotification(app *models.Application, dest string) { + var emails string + if data.IsPrimary { + emails = data.DAL.GetAppAdminAndOwnerEmails(app.Owner) + } else { + emails = data.NodeSetting.SMTP.AdminEmails + } + mailBody := "Backend server: " + dest + " (" + app.Name + ") was offline." + if len(mailBody) > 0 && len(emails) > 0 { + go utils.SendEmail(data.NodeSetting.SMTP.SMTPServer, + data.NodeSetting.SMTP.SMTPPort, + data.NodeSetting.SMTP.SMTPAccount, + data.NodeSetting.SMTP.SMTPPassword, + emails, + "[JANUSEC] Backend server offline notification", + mailBody) + } +} diff --git a/backend/k8s.go b/backend/k8s.go index b52f51d..b1b8a0c 100644 --- a/backend/k8s.go +++ b/backend/k8s.go @@ -27,7 +27,7 @@ func UpdatePods(dest *models.Destination, nowTimeStamp int64) { if err != nil { utils.DebugPrintln("Check K8S API GetResponse", err) dest.CheckTime = nowTimeStamp - dest.Online = false + SetDestinationOffline(dest) } pods := models.PODS{} err = json.Unmarshal(resp, &pods) @@ -96,7 +96,7 @@ func SelectPodFromVIPTarget(dest *models.VipTarget, srcIP string) string { if err != nil { utils.DebugPrintln("Check K8S API GetResponse", err) dest.CheckTime = nowTimeStamp - dest.Online = false + SetVipTargetOffline(dest) } pods := models.PODS{} err = json.Unmarshal(resp, &pods) diff --git a/backend/vip_app.go b/backend/vip_app.go index f8cb42b..95158c3 100644 --- a/backend/vip_app.go +++ b/backend/vip_app.go @@ -104,22 +104,14 @@ func UDPForwarding(vipApp *models.VipApp, udpListenConn *net.UDPConn) { //fmt.Println("UDPForwarding ReadMsgUDP", err) break } - vipTarget := SelectVipTarget(vipApp, clientAddr.String()) - if err != nil { - //fmt.Println("UDPForwarding ResolveUDPAddr", err) - break - } if vipTarget != nil { vipTarget.CheckTime = time.Now().Unix() targetAddr, _ := net.ResolveUDPAddr("udp", vipTarget.Destination) udpTargetConn, err := net.DialUDP("udp", nil, targetAddr) if err != nil { utils.DebugPrintln("UDPForwarding DialUDP could not connect to target", vipTarget.Destination, err) - vipTarget.Online = false - if data.NodeSetting.SMTP.SMTPEnabled { - sendVIPOfflineNotification(vipApp, vipTarget.Destination) - } + SetVipTargetOffline(vipTarget) break } if udpTargetConn == nil { @@ -138,10 +130,7 @@ func UDPForwarding(vipApp *models.VipApp, udpListenConn *net.UDPConn) { for { n, _, err := udpTargetConn.ReadFromUDP(dataBuf) if err != nil { - vipTarget.Online = false - if data.NodeSetting.SMTP.SMTPEnabled { - sendVIPOfflineNotification(vipApp, vipTarget.Destination) - } + SetVipTargetOffline(vipTarget) break } // Response to client @@ -190,10 +179,7 @@ func TCPForwarding(vipApp *models.VipApp, vipListener net.Listener) { vipTarget.CheckTime = time.Now().Unix() if err != nil { utils.DebugPrintln("TCPForwarding could not connect to target", targetDest, err) - vipTarget.Online = false - if data.NodeSetting.SMTP.SMTPEnabled { - sendVIPOfflineNotification(vipApp, targetDest) - } + SetVipTargetOffline(vipTarget) continue } vipTarget.Online = true @@ -385,23 +371,3 @@ func GetVipAppIndex(vipAppID int64) int { } return -1 } - -// sendVIPOfflineNotification ... -func sendVIPOfflineNotification(app *models.VipApp, dest string) { - var emails string - if data.IsPrimary { - emails = data.DAL.GetAppAdminAndOwnerEmails(app.Owner) - } else { - emails = data.NodeSetting.SMTP.AdminEmails - } - mailBody := "Backend virtual IP server: " + dest + " (" + app.Name + ") was offline." - if len(mailBody) > 0 && len(emails) > 0 { - go utils.SendEmail(data.NodeSetting.SMTP.SMTPServer, - data.NodeSetting.SMTP.SMTPPort, - data.NodeSetting.SMTP.SMTPAccount, - data.NodeSetting.SMTP.SMTPPassword, - emails, - "[JANUSEC] Backend server offline notification", - mailBody) - } -} diff --git a/backend/vip_target.go b/backend/vip_target.go index 227958d..35e6479 100644 --- a/backend/vip_target.go +++ b/backend/vip_target.go @@ -13,6 +13,8 @@ import ( "janusec/utils" "net" "time" + + "github.com/patrickmn/go-cache" ) // DeleteVipTargetsByAppID delete backend targets for port forwarding @@ -42,7 +44,7 @@ func CheckOfflineVipTargets(nowTimeStamp int64) { targetAddr, _ := net.ResolveUDPAddr("udp", vTarget.Destination) udpTargetConn, err := net.DialUDP("udp", nil, targetAddr) if err != nil { - vTarget.Online = false + SetVipTargetOffline(vTarget) return } // udpTargetConn will be closed in go thread @@ -51,7 +53,7 @@ func CheckOfflineVipTargets(nowTimeStamp int64) { data := make([]byte, 2048) _, _, err := udpConn.ReadFromUDP(data) if err != nil { - vipTarget.Online = false + SetVipTargetOffline(vipTarget) } else { vipTarget.Online = true } @@ -61,7 +63,7 @@ func CheckOfflineVipTargets(nowTimeStamp int64) { // send test data to target _, err = udpTargetConn.Write([]byte("Hi")) if err != nil { - vTarget.Online = false + SetVipTargetOffline(vTarget) return } } @@ -79,3 +81,44 @@ func ContainsTargetID(targets []*models.VipTarget, targetID int64) bool { } return false } + +func SetVipTargetOffline(dest *models.VipTarget) { + target := dest.Destination + if dest.RouteType == models.K8S_Ingress { + target = dest.PodsAPI + } + if count, ok := offlineCache.Get(target); !ok { + offlineCache.Set(target, int64(1), cache.DefaultExpiration) + } else { + nowCount := count.(int64) + int64(1) + if nowCount > 5 { + // more than 5 requests timeout + dest.Online = false + app, err := GetVipAppByID(dest.VipAppID) + if err == nil { + sendVIPOfflineNotification(app, target) + } + } + offlineCache.Set(target, nowCount, cache.DefaultExpiration) + } +} + +// sendVIPOfflineNotification ... +func sendVIPOfflineNotification(app *models.VipApp, dest string) { + var emails string + if data.IsPrimary { + emails = data.DAL.GetAppAdminAndOwnerEmails(app.Owner) + } else { + emails = data.NodeSetting.SMTP.AdminEmails + } + mailBody := "Backend virtual IP: " + dest + " (" + app.Name + ") was offline." + if len(mailBody) > 0 && len(emails) > 0 { + go utils.SendEmail(data.NodeSetting.SMTP.SMTPServer, + data.NodeSetting.SMTP.SMTPPort, + data.NodeSetting.SMTP.SMTPAccount, + data.NodeSetting.SMTP.SMTPPassword, + emails, + "[JANUSEC] Backend server offline notification", + mailBody) + } +} diff --git a/data/data.go b/data/data.go index 7e2948a..894d2a4 100644 --- a/data/data.go +++ b/data/data.go @@ -35,7 +35,7 @@ var ( // IsPrimary i.e. Is Primary Node IsPrimary bool // Version of JANUSEC - Version = "1.4.2" + Version = "1.5.0" ) // InitConfig init Data Access Layer diff --git a/data/setting.go b/data/setting.go index 4e44e7d..c5fa16f 100644 --- a/data/setting.go +++ b/data/setting.go @@ -157,7 +157,6 @@ func InitDefaultSettings() { DAL.LoadInstanceKey() DAL.LoadNodesKey() DAL.LoadAPIKey() - var err error // Init PrimarySetting if !DAL.ExistsSetting("authenticator_enabled") { @@ -260,6 +259,10 @@ func InitDefaultSettings() { if !DAL.ExistsSetting("dingtalk_appsecret") { DAL.SaveStringSetting("dingtalk_appsecret", "crrALdXUIj4T0zBekYh4u9sU_T1GZT") } + if !DAL.ExistsSetting("dingtalk_corpid") { + // added on Mar 23, 2024, v1.5.0 + DAL.SaveStringSetting("dingtalk_corpid", "xxxx") + } // AuthConfig feishu if !DAL.ExistsSetting("feishu_display_name") { DAL.SaveStringSetting("feishu_display_name", "Login with Feishu") @@ -347,9 +350,6 @@ func InitDefaultSettings() { // 0.9.13 + _ = DAL.SaveIntSetting("init_time", time.Now().Unix()) } - if err != nil { - utils.DebugPrintln("InitDefaultSettings error", err) - } } // LoadSettings ... @@ -495,11 +495,13 @@ func GetDingtalkConfig() *models.DingtalkConfig { callback := DAL.SelectStringSetting("dingtalk_callback") appID := DAL.SelectStringSetting("dingtalk_appid") appSecret := DAL.SelectStringSetting("dingtalk_appsecret") + corpID := DAL.SelectStringSetting("dingtalk_corpid") dingtalkConfig := &models.DingtalkConfig{ DisplayName: displayName, Callback: callback, AppID: appID, AppSecret: appSecret, + CorpID: corpID, } return dingtalkConfig } @@ -515,17 +517,11 @@ func UpdateDingtalkConfig(body []byte, clientIP string, authUser *models.AuthUse return nil, err } dingtalkConfig := rpcDingtalkConfigRequest.Object - /* - dingtalkConfig := param["object"].(map[string]interface{}) - displayName := dingtalkConfig["display_name"].(string) - callback := dingtalkConfig["callback"].(string) - appid := dingtalkConfig["appid"].(string) - appsecret := dingtalkConfig["appsecret"].(string) - */ DAL.SaveStringSetting("dingtalk_display_name", dingtalkConfig.DisplayName) DAL.SaveStringSetting("dingtalk_callback", dingtalkConfig.Callback) DAL.SaveStringSetting("dingtalk_appid", dingtalkConfig.AppID) DAL.SaveStringSetting("dingtalk_appsecret", dingtalkConfig.AppSecret) + DAL.SaveStringSetting("dingtalk_corpid", dingtalkConfig.CorpID) NodeSetting.AuthConfig.Dingtalk = dingtalkConfig go utils.OperationLog(clientIP, authUser.Username, "Update Dingtalk Config", dingtalkConfig.DisplayName) diff --git a/firewall/cc.go b/firewall/cc.go index 22451ab..9df922d 100644 --- a/firewall/cc.go +++ b/firewall/cc.go @@ -156,7 +156,7 @@ func InitCCPolicy() { } existCCPolicy := data.DAL.ExistsCCPolicy() if !existCCPolicy { - err = data.DAL.InsertCCPolicy(0, 100, 6, 900, models.Action_Block_100, true, false, false, true) + err = data.DAL.InsertCCPolicy(0, 100, 15, 900, models.Action_Block_100, true, false, false, true) if err != nil { utils.DebugPrintln("InitCCPolicy InsertCCPolicy", err) } diff --git a/gateway/auth_oauth.go b/gateway/auth_oauth.go index 0922a24..874791c 100644 --- a/gateway/auth_oauth.go +++ b/gateway/auth_oauth.go @@ -79,9 +79,16 @@ func GetOAuthInfo() (*OAuthInfo, error) { oauthInfo.EntranceURL = entranceURL return &oauthInfo, nil case "dingtalk": + /* API v1 entranceURL := fmt.Sprintf("https://oapi.dingtalk.com/connect/qrconnect?appid=%s&response_type=code&scope=snsapi_login&state=admin&redirect_uri=%s", data.NodeSetting.AuthConfig.Dingtalk.AppID, data.NodeSetting.AuthConfig.Dingtalk.Callback) + */ + // API v2, added on Mar 23, 2024, v1.5.0 + entranceURL := fmt.Sprintf(`https://login.dingtalk.com/oauth2/auth?redirect_uri=%s&response_type=code&corpId=%s&client_id=%s&scope=openid%%20corpid&state=admin&prompt=consent`, + data.NodeSetting.AuthConfig.Dingtalk.Callback, + data.NodeSetting.AuthConfig.Dingtalk.CorpID, + data.NodeSetting.AuthConfig.Dingtalk.AppID) oauthInfo.UseOAuth = true oauthInfo.DisplayName = data.NodeSetting.AuthConfig.Dingtalk.DisplayName oauthInfo.EntranceURL = entranceURL diff --git a/gateway/gateway.go b/gateway/gateway.go index 8f2bfb8..94c2e7a 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -392,21 +392,20 @@ func ReverseHandlerFunc(w http.ResponseWriter, r *http.Request) { ExpectContinueTimeout: 10 * time.Second, MaxIdleConns: 100, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + dest.Mutex.Lock() + defer dest.Mutex.Unlock() dest.CheckTime = nowTimeStamp conn, err := net.Dial("tcp", targetDest) if err != nil { - dest.Mutex.Lock() - defer dest.Mutex.Unlock() - dest.Online = false + backend.SetDestinationOffline(dest) timeout := time.Now().Unix() - nowTimeStamp utils.DebugPrintln("DialContext error", err, timeout, "seconds") - if data.NodeSetting.SMTP.SMTPEnabled { - sendOfflineNotification(app, targetDest) - } - errInfo := &models.InternalErrorInfo{ - Description: "Internal Server Offline", + if !dest.Online { + errInfo := &models.InternalErrorInfo{ + Description: "Internal Server Offline", + } + GenerateInternalErrorResponse(w, errInfo) } - GenerateInternalErrorResponse(w, errInfo) } return conn, err }, @@ -414,16 +413,15 @@ func ReverseHandlerFunc(w http.ResponseWriter, r *http.Request) { dest.CheckTime = nowTimeStamp conn, err := net.Dial("tcp", targetDest) if err != nil { - dest.Online = false + backend.SetDestinationOffline(dest) timeout := time.Now().Unix() - nowTimeStamp utils.DebugPrintln("DialTLS error", err, timeout, "seconds") - if data.NodeSetting.SMTP.SMTPEnabled { - sendOfflineNotification(app, targetDest) - } - errInfo := &models.InternalErrorInfo{ - Description: "Internal Server Offline", + if !dest.Online { + errInfo := &models.InternalErrorInfo{ + Description: "Internal Server Offline", + } + GenerateInternalErrorResponse(w, errInfo) } - GenerateInternalErrorResponse(w, errInfo) return nil, err } cfg := &tls.Config{ @@ -469,7 +467,7 @@ func ReverseHandlerFunc(w http.ResponseWriter, r *http.Request) { utils.DebugPrintln("Check Update NewRequest", err) } if err == nil { - // copy header. + // copy request headers for k := range r.Header { req.Header.Set(k, r.Header.Get(k)) } @@ -577,10 +575,28 @@ func getOAuthEntrance(state string) (entranceURL string, err error) { data.NodeSetting.AuthConfig.Wxwork.Callback, state) case "dingtalk": - entranceURL = fmt.Sprintf("https://oapi.dingtalk.com/connect/qrconnect?appid=%s&response_type=code&scope=snsapi_login&state=%s&redirect_uri=%s", + /* This is the api v1 + entranceURL = fmt.Sprintf("https://oapi.dingtalk.com/connect/qrconnect?appid=%s&response_type=code&scope=snsapi_login&state=%s&redirect_uri=%s", data.NodeSetting.AuthConfig.Dingtalk.AppID, state, data.NodeSetting.AuthConfig.Dingtalk.Callback) + */ + // API V2, added on Mar 23, 2024, v1.5.0 + // doc: https://open.dingtalk.com/document/orgapp/tutorial-obtaining-user-personal-information + // Entrance URL Format: https://login.dingtalk.com/oauth2/auth? + // redirect_uri=https://test.janusec.com/oauth/dingtalk + // &response_type=code + // &client_id=... + // &scope=openid + // &state=... + // &prompt=consent + // &corpId=... + entranceURL = fmt.Sprintf(`https://login.dingtalk.com/oauth2/auth?redirect_uri=%s&response_type=code&corpId=%s&client_id=%s&scope=openid%%20corpid&state=%s&prompt=consent`, + data.NodeSetting.AuthConfig.Dingtalk.Callback, + data.NodeSetting.AuthConfig.Dingtalk.CorpID, + data.NodeSetting.AuthConfig.Dingtalk.AppID, + state) + case "feishu": entranceURL = fmt.Sprintf("https://open.feishu.cn/open-apis/authen/v1/index?redirect_uri=%s&app_id=%s&state=%s", data.NodeSetting.AuthConfig.Feishu.Callback, @@ -714,26 +730,6 @@ func CheckExpiringCertificates() { } } -// sendOfflineNotification ... -func sendOfflineNotification(app *models.Application, dest string) { - var emails string - if data.IsPrimary { - emails = data.DAL.GetAppAdminAndOwnerEmails(app.Owner) - } else { - emails = data.NodeSetting.SMTP.AdminEmails - } - mailBody := "Backend server: " + dest + " (" + app.Name + ") was offline." - if len(mailBody) > 0 && len(emails) > 0 { - go utils.SendEmail(data.NodeSetting.SMTP.SMTPServer, - data.NodeSetting.SMTP.SMTPPort, - data.NodeSetting.SMTP.SMTPAccount, - data.NodeSetting.SMTP.SMTPPassword, - emails, - "[JANUSEC] Backend server offline notification", - mailBody) - } -} - // Test ... func Test(w http.ResponseWriter, r *http.Request) { if utils.Debug { diff --git a/gateway/webssh.go b/gateway/webssh.go index 5c1a4de..df355d4 100644 --- a/gateway/webssh.go +++ b/gateway/webssh.go @@ -118,9 +118,9 @@ func WebSSHHandlerFunc(w http.ResponseWriter, r *http.Request) { } defer wsConn.Close() // Read SSH Parameters - _, msg, err := wsConn.ReadMessage() - if err != nil { - utils.DebugPrintln("ReadMessage SSH Parameters Error:", err) + _, msg, err2 := wsConn.ReadMessage() + if err2 != nil { + utils.DebugPrintln("ReadMessage SSH Parameters Error:", err2) return } if !data.PrimarySetting.WebSSHEnabled { @@ -135,16 +135,16 @@ func WebSSHHandlerFunc(w http.ResponseWriter, r *http.Request) { if err != nil { utils.DebugPrintln("WebSSHHandlerFunc json.Unmarshal error", err) } - if err := wsConn.WriteMessage(websocket.TextMessage, []byte("Connecting "+host.IP+":"+host.Port+" ... Please wait a moment!\r\n")); err != nil { + if err = wsConn.WriteMessage(websocket.TextMessage, []byte("Connecting "+host.IP+":"+host.Port+" ... Please wait a moment!\r\n")); err != nil { return } errChan := make(chan error) go SSH(&sshInput, &sshOutput, &host, errChan) err = <-errChan if err != nil { - err = wsConn.WriteMessage(websocket.TextMessage, []byte(err.Error())) - if err != nil { - utils.DebugPrintln("WebSSHHandlerFunc wsConn.WriteMessage error", err) + err2 := wsConn.WriteMessage(websocket.TextMessage, []byte(err.Error())) + if err2 != nil { + utils.DebugPrintln("WebSSHHandlerFunc wsConn.WriteMessage error", err2) } return } @@ -154,23 +154,23 @@ func WebSSHHandlerFunc(w http.ResponseWriter, r *http.Request) { for { select { case <-errChan: - err = wsConn.WriteMessage(websocket.TextMessage, []byte(err.Error())) - if err != nil { - utils.DebugPrintln("WebSSHHandlerFunc wsConn.WriteMessage error", err) + err2 := wsConn.WriteMessage(websocket.TextMessage, []byte(err.Error())) + if err2 != nil { + utils.DebugPrintln("WebSSHHandlerFunc wsConn.WriteMessage error", err2) } return default: if wsConn == nil { return } - _, msg, err := wsConn.ReadMessage() - if err != nil { + _, msg, err2 := wsConn.ReadMessage() + if err2 != nil { return } //log.Printf("Received: %s %v\n", string(msg), msg) if sshInput != nil { go CmdLog(&logBuf, username, &host, &msg) - if _, err := sshInput.Write(msg); err != nil { + if _, err = sshInput.Write(msg); err != nil { return } } diff --git a/models/backend.go b/models/backend.go index 3fec098..3e7f42f 100644 --- a/models/backend.go +++ b/models/backend.go @@ -139,6 +139,7 @@ type Destination struct { BackendRoute string `json:"backend_route"` // Destination is backend IP:Port , or static directory + // If RoutyType is K8S, this field is not used Destination string `json:"destination"` // PodsAPI example: http://127.0.0.1:8080/api/v1/namespaces/default/pods diff --git a/models/data_config.go b/models/data_config.go index 8ebdbc3..25551e3 100644 --- a/models/data_config.go +++ b/models/data_config.go @@ -75,6 +75,9 @@ type DingtalkConfig struct { Callback string `json:"callback"` AppID string `json:"appid"` AppSecret string `json:"appsecret"` + + // CorpID for API v2, added on Mar 23, 2024, v1.5.0 + CorpID string `json:"corpid"` } type FeishuConfig struct { diff --git a/release_batch.sh b/release_batch.sh index db32850..0cc6bca 100755 --- a/release_batch.sh +++ b/release_batch.sh @@ -2,7 +2,7 @@ printf "Creating installation package\n" printf "Checklist:\n" printf "* Angular Admin Version Check. \n" printf "* Janusec Version Check. \n" -version="1.4.2" +version="1.5.0" printf "Version: ${version} \n" read -r -p "Are You Sure? [Y/n] " option diff --git a/static/janusec-admin/index.html b/static/janusec-admin/index.html index 6836e9f..7c9e473 100644 --- a/static/janusec-admin/index.html +++ b/static/janusec-admin/index.html @@ -12,6 +12,6 @@