├── robots.txt ├── static ├── robots.txt ├── janusec-admin │ ├── assets │ │ └── images │ │ │ ├── logo.png │ │ │ ├── favicon.ico │ │ │ └── gateway.png │ ├── media │ │ └── material-symbols-outlined-UCGWUF4X.woff2 │ ├── chunk-LTNHPXB4.js │ ├── index.html │ └── styles-OGTDWD3C.css └── welcome │ └── index.html ├── gateway1.png ├── waf-demo1.png ├── waf-demo2.png ├── Data-Security.png ├── scripts ├── check_pid.sh ├── janusec.service ├── config.json.primary_bak ├── config.json.replica_bak ├── keepalived.conf └── install.sh ├── .travis.yml ├── .gitignore ├── LICENSE-Community.md ├── gateway ├── gateway_admin.go ├── auth_saml.go ├── waf_block.go ├── gateway_502.go ├── gateway_health.go ├── rpc_update.go ├── shield.go ├── auth_ldap.go ├── waf_captcha.go ├── auth_code.go ├── auth_oauth.go └── webssh.go ├── config.json.bak ├── usermgmt ├── oauth.go ├── oauth_cas2.go ├── authenticator.go ├── oauth_wxwork.go ├── oauth_feishu.go └── oauth_lark.go ├── data ├── db_env.go ├── rpc.go ├── backend_totp.go ├── data_config.go ├── firewall_vuln.go ├── backend_dns_domain.go ├── backend_node.go ├── backend_vip_app.go ├── firewall_ip_policy.go ├── backend_dns_record.go ├── backend_cookie_ref.go ├── gateway_discovery.go ├── backend_vip_target.go ├── backend_domain.go ├── backend_certificate.go ├── firewall_check_item.go ├── backend_cookie.go ├── firewall_cc.go ├── backend_destination.go ├── data.go ├── firewall_group_policy.go └── backend_setting.go ├── release.sh ├── firewall ├── init.go ├── rpc_log.go ├── rpc_vuln.go ├── rpc_cc.go ├── rpc_group_policy.go ├── routine.go ├── vuln.go ├── nftables.go └── ip_policy.go ├── backend ├── rpc_domain.go ├── rpc_certificate.go ├── rpc_application.go ├── dns_record.go ├── dns_domain.go ├── vip_target.go ├── k8s.go ├── domain.go └── destination.go ├── models ├── node.go ├── dns.go ├── api_gateway.go ├── cookie.go ├── api_firewall.go ├── api_backend.go ├── user.go ├── rpc_node.go └── data_config.go ├── .github └── ISSUE_TEMPLATE.md ├── logstash └── janusec.conf.j2 ├── release_batch.sh ├── go.mod └── utils └── generate_cert.go /robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /gateway1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Janusec/janusec/HEAD/gateway1.png -------------------------------------------------------------------------------- /waf-demo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Janusec/janusec/HEAD/waf-demo1.png -------------------------------------------------------------------------------- /waf-demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Janusec/janusec/HEAD/waf-demo2.png -------------------------------------------------------------------------------- /Data-Security.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Janusec/janusec/HEAD/Data-Security.png -------------------------------------------------------------------------------- /scripts/check_pid.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -z "`pidof janusec`" ]; then 3 | exit 1 4 | fi 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | sudo: false 4 | 5 | go: 6 | - '1.15' 7 | 8 | script: 9 | - go build 10 | -------------------------------------------------------------------------------- /static/janusec-admin/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Janusec/janusec/HEAD/static/janusec-admin/assets/images/logo.png -------------------------------------------------------------------------------- /static/janusec-admin/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Janusec/janusec/HEAD/static/janusec-admin/assets/images/favicon.ico -------------------------------------------------------------------------------- /static/janusec-admin/assets/images/gateway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Janusec/janusec/HEAD/static/janusec-admin/assets/images/gateway.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /dist 3 | /janusec 4 | /luci 5 | /config.json 6 | *.zip 7 | *.tar.gz 8 | /static/cdncache 9 | /certs 10 | /*.sqlite3 11 | /log -------------------------------------------------------------------------------- /static/janusec-admin/media/material-symbols-outlined-UCGWUF4X.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Janusec/janusec/HEAD/static/janusec-admin/media/material-symbols-outlined-UCGWUF4X.woff2 -------------------------------------------------------------------------------- /LICENSE-Community.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | Janusec Application Gateway Community Edition 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)). 4 | -------------------------------------------------------------------------------- /scripts/janusec.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Janusec Application Gateway 3 | Documentation=http://www.janusec.com/ 4 | After=network.target 5 | After=postgresql.service 6 | 7 | [Service] 8 | Type=simple 9 | ExecStart=/bin/bash -c '/usr/local/janusec/janusec >> /usr/local/janusec/log/error.log 2>&1' 10 | Restart=always 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | 15 | -------------------------------------------------------------------------------- /gateway/gateway_admin.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:36:26 5 | * @Last Modified: U2, 2018-07-14 16:36:26 6 | */ 7 | 8 | package gateway 9 | 10 | import ( 11 | "net/http" 12 | ) 13 | 14 | // AdminHandlerFunc is for /janusec-admin 15 | func AdminHandlerFunc(w http.ResponseWriter, r *http.Request) { 16 | staticHandler := http.FileServer(http.Dir("static")) 17 | staticHandler.ServeHTTP(w, r) 18 | } 19 | -------------------------------------------------------------------------------- /scripts/config.json.primary_bak: -------------------------------------------------------------------------------- 1 | { 2 | "node_role": "primary", 3 | "primary_node": { 4 | "admin": { 5 | "listen": true, 6 | "listen_http": ":9080", 7 | "listen_https": ":9443", 8 | "portal": "https://your_domain.com:9443/janusec-admin/" 9 | }, 10 | "database": { 11 | "host": "127.0.0.1", 12 | "port": "5432", 13 | "user": "janusec", 14 | "password": "123456", 15 | "dbname": "janusec" 16 | } 17 | }, 18 | "replica_node": { 19 | "node_key": "", 20 | "sync_addr": "http://gateway.primary_node.com:9080/janusec-admin/api" 21 | } 22 | } -------------------------------------------------------------------------------- /config.json.bak: -------------------------------------------------------------------------------- 1 | { 2 | "node_role": "primary", 3 | "primary_node": { 4 | "admin": { 5 | "listen": true, 6 | "listen_http": ":9080", 7 | "listen_https": ":9443", 8 | "portal": "https://your_domain.com:9443/janusec-admin/" 9 | }, 10 | "database_type": "sqlite", 11 | "database": { 12 | "host": "127.0.0.1", 13 | "port": "5432", 14 | "user": "janusec", 15 | "password": "123456", 16 | "dbname": "janusec" 17 | } 18 | }, 19 | "replica_node": { 20 | "node_key": "", 21 | "sync_addr": "http://gateway.primary_node.com:9080/janusec-admin/api" 22 | } 23 | } -------------------------------------------------------------------------------- /scripts/config.json.replica_bak: -------------------------------------------------------------------------------- 1 | { 2 | "node_role": "replica", 3 | "primary_node": { 4 | "admin": { 5 | "listen": true, 6 | "listen_http": ":9080", 7 | "listen_https": ":9443", 8 | "portal": "https://your_domain.com:9443/janusec-admin/" 9 | }, 10 | "database": { 11 | "host": "127.0.0.1", 12 | "port": "5432", 13 | "user": "", 14 | "password": "", 15 | "dbname": "janusec" 16 | } 17 | }, 18 | "replica_node": { 19 | "node_key": "node_key_generated_in_node_management", 20 | "sync_addr": "http://gateway.primary_node.com:9080/janusec-admin/api" 21 | } 22 | } -------------------------------------------------------------------------------- /usermgmt/oauth.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-03-22 10:29:15 5 | * @Last Modified: U2, 2020-03-22 10:29:15 6 | */ 7 | 8 | package usermgmt 9 | 10 | import ( 11 | "net" 12 | "net/http" 13 | 14 | "janusec/data" 15 | "janusec/models" 16 | "janusec/utils" 17 | ) 18 | 19 | func GetOAuthConfig() (*models.OAuthConfig, error) { 20 | return data.NodeSetting.AuthConfig, nil 21 | } 22 | 23 | // RecordAuthLog ... 24 | func RecordAuthLog(r *http.Request, username string, provider string, callback string) { 25 | // Get REMOTE_ADDR IP Address 26 | clientIP, _, _ := net.SplitHostPort(r.RemoteAddr) 27 | go utils.AuthLog(clientIP, username, provider, callback) 28 | } 29 | -------------------------------------------------------------------------------- /scripts/keepalived.conf: -------------------------------------------------------------------------------- 1 | global_defs { 2 | router_id janusec 3 | script_user root 4 | enable_script_security 5 | } 6 | 7 | vrrp_script check_pid { 8 | script "/usr/local/janusec/check_pid.sh" 9 | interval 5 10 | } 11 | 12 | vrrp_instance VI_01 { 13 | state BACKUP 14 | interface eth0 15 | virtual_router_id 101 16 | 17 | # priority may be modified 18 | priority 100 19 | 20 | nopreempt 21 | advert_int 1 22 | 23 | virtual_ipaddress { 24 | # The virtual ip should be modified 25 | 192.168.100.111 26 | } 27 | 28 | track_script { 29 | check_pid 30 | } 31 | 32 | authentication { 33 | auth_type PASS 34 | auth_pass janusec 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gateway/auth_saml.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-05-31 20:01:54 5 | * @Last Modified: U2, 2020-05-31 20:01:54 6 | */ 7 | 8 | package gateway 9 | 10 | import ( 11 | "fmt" 12 | "net/http" 13 | ) 14 | 15 | // SAMLLogin SAML2.0 Login 16 | func SAMLLogin(w http.ResponseWriter, r *http.Request) { 17 | fmt.Println("SAMLLogin ToDo") 18 | /* 19 | cert, err := backend.GetCertificateByDomain(r.URL.Host) 20 | samlSP, _ := samlsp.New(samlsp.Options{ 21 | URL: *rootURL, 22 | Key: cert.PrivateKey.(*rsa.PrivateKey), 23 | Certificate: cert.Leaf, 24 | IDPMetadataURL: idpMetadataURL, 25 | }) 26 | samlSP.HandleStartAuthFlow(w, r) 27 | */ 28 | } 29 | -------------------------------------------------------------------------------- /data/db_env.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:25:23 5 | * @Last Modified: U2, 2018-07-14 16:25:23 6 | */ 7 | 8 | package data 9 | 10 | /* 11 | const ( 12 | sqlSetIDSeqStartWith = `SELECT setval($1,$2,false)` 13 | ) 14 | 15 | // SetIDSeqStartWith modify the id sequence 16 | func (dal *MyDAL) SetIDSeqStartWith(tableName string, seq int64) error { 17 | tableIDSeq := tableName + `_id_seq` 18 | _, err := dal.db.Exec(sqlSetIDSeqStartWith, tableIDSeq, seq) 19 | if err != nil { 20 | tableIDSeq := `"` + tableName + `_id_SEQ"` 21 | _, err = dal.db.Exec(sqlSetIDSeqStartWith, tableIDSeq, seq) 22 | if err != nil { 23 | utils.DebugPrintln("SetIDSeqStartWith", err) 24 | } 25 | } 26 | return err 27 | } 28 | */ 29 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | printf "Creating installation package\n" 2 | printf "Checklist:\n" 3 | printf "* Angular Admin Version Check. \n" 4 | printf "* Janusec Version Check. \n" 5 | version=`./janusec --version` 6 | printf "Version: ${version} \n" 7 | dist_dir="./dist/janusec-${version}/" 8 | mkdir -p ${dist_dir} 9 | \cp -f ./janusec ${dist_dir} 10 | \cp -f ./3rdpartylicenses.txt ${dist_dir} 11 | rm -rf ${dist_dir}static 12 | mkdir ${dist_dir}static 13 | \cp -r ./static/janusec-admin ${dist_dir}static/ 14 | \cp -r ./static/welcome ${dist_dir}static/ 15 | \cp -f ./robots.txt ${dist_dir}static/ 16 | \cp -f ./scripts/* ${dist_dir} 17 | cd ./dist/ 18 | tar zcf ./janusec-${version}.tar.gz ./janusec-${version} 19 | rm -rf ./janusec-${version} 20 | \cp -f ./janusec-${version}.tar.gz ./janusec-latest.tar.gz 21 | cd .. 22 | printf "Done!\n" 23 | -------------------------------------------------------------------------------- /firewall/init.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:35:13 5 | * @Last Modified: U2, 2018-07-14 16:35:13 6 | */ 7 | 8 | package firewall 9 | 10 | import ( 11 | "janusec/models" 12 | "janusec/utils" 13 | ) 14 | 15 | // InitFirewall ... 16 | func InitFirewall() { 17 | utils.DebugPrintln("InitFirewall") 18 | InitCCPolicy() 19 | ccPolicies.Range(func(key, value interface{}) bool { 20 | appID := key.(int64) 21 | ccPolicy := value.(*models.CCPolicy) 22 | if ccPolicy.IsEnabled { 23 | go CCAttackTick(appID) 24 | } 25 | return true 26 | }) 27 | InitVulnType() 28 | InitGroupPolicy() 29 | InitIPPolicies() 30 | LoadCheckItems() 31 | InitHitLog() 32 | InitNFTables() 33 | go RoutineCleanLogTick() 34 | go RoutineCleanCacheTick() 35 | } 36 | -------------------------------------------------------------------------------- /firewall/rpc_log.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:35:48 5 | * @Last Modified: U2, 2018-07-14 16:35:48 6 | */ 7 | 8 | package firewall 9 | 10 | import ( 11 | "janusec/data" 12 | "janusec/models" 13 | "janusec/utils" 14 | ) 15 | 16 | // RPCGroupHitLog ... 17 | func RPCGroupHitLog(regexHitLog *models.GroupHitLog) { 18 | rpcRequest := &models.RPCRequest{ 19 | Action: "log_group_hit", Object: regexHitLog} 20 | _, err := data.GetRPCResponse(rpcRequest) 21 | if err != nil { 22 | utils.DebugPrintln("RPCRegexHitLog", err) 23 | } 24 | } 25 | 26 | // RPCCCLog ... 27 | func RPCCCLog(ccLog *models.CCLog) { 28 | rpcRequest := &models.RPCRequest{ 29 | Action: "log_cc", Object: ccLog} 30 | _, err := data.GetRPCResponse(rpcRequest) 31 | if err != nil { 32 | utils.DebugPrintln("RPCCCLog", err) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /backend/rpc_domain.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:23:23 5 | * @Last Modified: U2, 2018-07-14 16:23:23 6 | */ 7 | 8 | package backend 9 | 10 | import ( 11 | "encoding/json" 12 | 13 | "janusec/data" 14 | "janusec/models" 15 | "janusec/utils" 16 | ) 17 | 18 | // RPCSelectDomains ... 19 | func RPCSelectDomains() (dbDomains []*models.DBDomain) { 20 | rpcRequest := &models.RPCRequest{ 21 | Action: "get_domains", Object: nil} 22 | resp, err := data.GetRPCResponse(rpcRequest) 23 | if err != nil { 24 | utils.DebugPrintln("RPCSelectDomains GetResponse", err) 25 | return nil 26 | } 27 | rpcDBDomains := &models.RPCDBDomains{} 28 | err = json.Unmarshal(resp, rpcDBDomains) 29 | if err != nil { 30 | utils.DebugPrintln("RPCSelectDomains Unmarshal", err) 31 | return nil 32 | } 33 | dbDomains = rpcDBDomains.Object 34 | return dbDomains 35 | } 36 | -------------------------------------------------------------------------------- /firewall/rpc_vuln.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:35:55 5 | * @Last Modified: U2, 2018-07-14 16:35:55 6 | */ 7 | 8 | package firewall 9 | 10 | import ( 11 | "encoding/json" 12 | 13 | "janusec/data" 14 | "janusec/models" 15 | "janusec/utils" 16 | ) 17 | 18 | // RPCSelectVulntypes ... 19 | func RPCSelectVulntypes() (vulnTypes []*models.VulnType) { 20 | rpcRequest := &models.RPCRequest{ 21 | Action: "get_vuln_types", Object: nil} 22 | resp, err := data.GetRPCResponse(rpcRequest) 23 | if err != nil { 24 | utils.DebugPrintln("RPCSelectVulntypes GetResponse", err) 25 | return nil 26 | } 27 | rpcVulnTypes := &models.RPCVulntypes{} 28 | if err := json.Unmarshal(resp, rpcVulnTypes); err != nil { 29 | utils.DebugPrintln("RPCSelectVulntypes Unmarshal", err) 30 | return nil 31 | } 32 | vulnTypes = rpcVulnTypes.Object 33 | return vulnTypes 34 | } 35 | -------------------------------------------------------------------------------- /models/node.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:39:09 5 | * @Last Modified: U2, 2018-07-14 16:39:09 6 | */ 7 | 8 | package models 9 | 10 | type Node struct { 11 | ID int64 `json:"id,string"` 12 | Version string `json:"version"` 13 | LastIP string `json:"last_ip"` 14 | LastRequestTime int64 `json:"last_req_time"` 15 | 16 | // PublicIP used for dns load balance, added v1.4.1 17 | PublicIP string `json:"public_ip"` 18 | } 19 | 20 | /* 21 | type DBNode struct { 22 | ID int64 `json:"id,string"` 23 | Version string `json:"version"` 24 | LastIP string `json:"last_ip"` 25 | LastRequestTime int64 `json:"last_req_time"` 26 | } 27 | */ 28 | 29 | type AuthTime struct { 30 | CurTime int64 `json:"cur_time"` 31 | } 32 | 33 | type NodesKey struct { 34 | HexEncryptedKey string `json:"nodes_key"` 35 | } 36 | -------------------------------------------------------------------------------- /firewall/rpc_cc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:35:35 5 | * @Last Modified: U2, 2018-07-14 16:35:35 6 | */ 7 | 8 | package firewall 9 | 10 | import ( 11 | "encoding/json" 12 | 13 | "janusec/data" 14 | "janusec/models" 15 | "janusec/utils" 16 | ) 17 | 18 | // RPCSelectCCPolicies ... 19 | func RPCSelectCCPolicies() (ccPolicies []*models.CCPolicy) { 20 | rpcRequest := &models.RPCRequest{ 21 | Action: "get_cc_policies", Object: nil} 22 | resp, err := data.GetRPCResponse(rpcRequest) 23 | if err != nil { 24 | utils.DebugPrintln("RPCSelectCCPolicies GetResponse", err) 25 | return nil 26 | } 27 | rpcCCPolicies := &models.RPCCCPolicies{} 28 | if err := json.Unmarshal(resp, rpcCCPolicies); err != nil { 29 | utils.DebugPrintln("RPCSelectCCPolicies Unmarshal", err) 30 | return nil 31 | } 32 | ccPolicies = rpcCCPolicies.Object 33 | return ccPolicies 34 | } 35 | -------------------------------------------------------------------------------- /static/janusec-admin/chunk-LTNHPXB4.js: -------------------------------------------------------------------------------- 1 | import{$c as d,Yc as a,Zc as b,_c as c,ad as e,bd as f,cd as g,dd as h,ed as i,fd as j,gd as k,hd as l,id as m,jd as n,kd as o,ld as p,md as q,nd as r,od as s,pd as t,qd as u,rd as v,sd as w,td as x}from"./chunk-YOVRP3JA.js";export{l as AnimationDriver,k as NoopAnimationDriver,u as \u0275Animation,q as \u0275AnimationEngine,w as \u0275AnimationRenderer,x as \u0275AnimationRendererFactory,m as \u0275AnimationStyleNormalizer,v as \u0275BaseAnimationRenderer,f as \u0275ENTER_CLASSNAME,g as \u0275LEAVE_CLASSNAME,n as \u0275NoopAnimationStyleNormalizer,p as \u0275TransitionAnimationPlayer,s as \u0275WebAnimationsDriver,r as \u0275WebAnimationsPlayer,o as \u0275WebAnimationsStyleNormalizer,j as \u0275allowPreviousPlayerStylesMerge,i as \u0275camelCaseToDashCase,d as \u0275containsElement,t as \u0275createEngine,a as \u0275getParentElement,e as \u0275invokeQuery,h as \u0275normalizeKeyframes,b as \u0275validateStyleProperty,c as \u0275validateWebAnimatableStyleProperty}; 2 | -------------------------------------------------------------------------------- /firewall/rpc_group_policy.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:35:42 5 | * @Last Modified: U2, 2018-07-14 16:35:42 6 | */ 7 | 8 | package firewall 9 | 10 | import ( 11 | "encoding/json" 12 | 13 | "janusec/data" 14 | "janusec/models" 15 | "janusec/utils" 16 | ) 17 | 18 | // RPCSelectGroupPolicies ... 19 | func RPCSelectGroupPolicies() (groupPolicies []*models.GroupPolicy) { 20 | rpcRequest := &models.RPCRequest{ 21 | Action: "get_group_policies", Object: nil} 22 | resp, err := data.GetRPCResponse(rpcRequest) 23 | if err != nil { 24 | utils.DebugPrintln("RPCSelectGroupPolicies GetResponse", err) 25 | return nil 26 | } 27 | rpcGroupPolicies := &models.RPCGroupPolicies{} 28 | if err := json.Unmarshal(resp, rpcGroupPolicies); err != nil { 29 | utils.DebugPrintln("RPCSelectGroupPolicies Unmarshal", err) 30 | return nil 31 | } 32 | groupPolicies = rpcGroupPolicies.Object 33 | return groupPolicies 34 | } 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please answer these questions before submitting your issue. Thanks! 2 | 3 | 4 | ### What version of Go are you using (`go version`)? ( at least Go 1.9 is required) 5 | 6 | 7 | ### What version of PostgreSQL are you using (`psql --version`)? ( at least PostgreSQL 9.3 is required) 8 | 9 | 10 | ### Did you correctly configure the IP, Port, username, password, dbname of your PostgreSQL in ./config.json (Development) or /usr/local/janusec/config.json (Production), and verified OK by `psql -h 127.0.0.1 -U db_user -W janusec` ? 11 | 12 | Janusec will not create the dbname for you. 13 | 14 | ### Does this issue reproduce with the latest release? 15 | 16 | 17 | ### What operating system and processor architecture are you using (`go env`)? 18 | 19 | 20 | ### What did you do? 21 | 22 | If possible, provide a test case for reproducing the error. 23 | 24 | 25 | ### What did you expect to see? 26 | 27 | 28 | ### What did you see instead? 29 | 30 | 31 | ### Other information 32 | 33 | -------------------------------------------------------------------------------- /static/janusec-admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Janusec Application Gateway 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /models/dns.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2023-06-04 15:12 5 | */ 6 | 7 | package models 8 | 9 | import ( 10 | "github.com/miekg/dns" 11 | ) 12 | 13 | type DNSDomain struct { 14 | ID int64 `json:"id,string"` 15 | 16 | // Domain name, such as example.com 17 | Name string `json:"name"` 18 | 19 | // 20 | DNSRecords []*DNSRecord `json:"-"` 21 | } 22 | 23 | type DNSRecord struct { 24 | ID int64 `json:"id,string"` 25 | 26 | DNSDomainID int64 `json:"dns_domain_id,string"` 27 | 28 | // Rrtype is github.com/miekg/dns.Type 29 | // Supported types: A, AAAA, CNAME, MX, TXT, SRV, NS, HTTPS, CAA 30 | Rrtype dns.Type `json:"rrtype"` 31 | 32 | // Record name 33 | Name string `json:"name"` 34 | 35 | Value string `json:"value"` 36 | 37 | // TTL seconds, default 3600 38 | TTL uint32 `json:"ttl"` 39 | 40 | // Auto Dispatched within available gateway nodes, public network only 41 | // Skip the Value, for Load Balance 42 | Auto bool `json:"auto"` 43 | 44 | // Marked as internal record 45 | Internal bool `json:"internal"` 46 | } 47 | -------------------------------------------------------------------------------- /gateway/waf_block.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:38:30 5 | * @Last Modified: U2, 2018-07-14 16:38:30 6 | */ 7 | 8 | package gateway 9 | 10 | import ( 11 | "bytes" 12 | "html/template" 13 | "net/http" 14 | 15 | "janusec/data" 16 | "janusec/models" 17 | "janusec/utils" 18 | ) 19 | 20 | // GenerateBlockPage ... 21 | func GenerateBlockPage(w http.ResponseWriter, hitInfo *models.HitInfo) { 22 | if data.TmplWAF == nil { 23 | data.TmplWAF, _ = template.New("tmplWAF").Parse(data.NodeSetting.BlockHTML) 24 | } 25 | w.WriteHeader(403) 26 | err := data.TmplWAF.Execute(w, hitInfo) 27 | if err != nil { 28 | utils.DebugPrintln("GenerateBlockPage tmpl.Execute error", err) 29 | } 30 | } 31 | 32 | // GenerateBlockContent ... 33 | func GenerateBlockContent(hitInfo *models.HitInfo) []byte { 34 | if data.TmplWAF == nil { 35 | data.TmplWAF, _ = template.New("tmplWAF").Parse(data.NodeSetting.BlockHTML) 36 | } 37 | buf := &bytes.Buffer{} 38 | err := data.TmplWAF.Execute(buf, hitInfo) 39 | if err != nil { 40 | utils.DebugPrintln("GenerateBlockContent tmpl.Execute error", err) 41 | } 42 | return buf.Bytes() 43 | } 44 | -------------------------------------------------------------------------------- /backend/rpc_certificate.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:23:18 5 | * @Last Modified: U2, 2018-07-14 16:23:18 6 | */ 7 | 8 | package backend 9 | 10 | import ( 11 | "crypto/tls" 12 | "encoding/json" 13 | 14 | "janusec/data" 15 | "janusec/models" 16 | "janusec/utils" 17 | ) 18 | 19 | // RPCSelectCertificates ... 20 | func RPCSelectCertificates() []*models.CertItem { 21 | certs := []*models.CertItem{} 22 | rpcRequest := &models.RPCRequest{ 23 | Action: "get_certs", Object: nil} 24 | resp, err := data.GetRPCResponse(rpcRequest) 25 | if err != nil { 26 | utils.DebugPrintln("RPCSelectCertificates GetResponse", err) 27 | return certs 28 | } 29 | rpcCertItems := &models.RPCCertItems{} 30 | if err = json.Unmarshal(resp, rpcCertItems); err != nil { 31 | utils.DebugPrintln("RPCSelectCertificates Unmarshal", err) 32 | return certs 33 | } 34 | certItems := rpcCertItems.Object 35 | for _, certItem := range certItems { 36 | certItem.TlsCert, err = tls.X509KeyPair([]byte(certItem.CertContent), []byte(certItem.PrivKeyContent)) 37 | if err != nil { 38 | utils.DebugPrintln("RPCSelectCertificates X509KeyPair", err) 39 | } 40 | certs = append(certs, certItem) 41 | } 42 | return certs 43 | } 44 | -------------------------------------------------------------------------------- /models/api_gateway.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2023-04-02 21:32:15 5 | */ 6 | 7 | package models 8 | 9 | type APIStatByAppIDRequest struct { 10 | Action string `json:"action"` 11 | AppID int64 `json:"app_id,string"` 12 | Host string `json:"host"` 13 | } 14 | 15 | type APIWxworkConfigRequest struct { 16 | Action string `json:"action"` 17 | Object *WxworkConfig `json:"object"` 18 | } 19 | 20 | type APIDingtalkConfigRequest struct { 21 | Action string `json:"action"` 22 | Object *DingtalkConfig `json:"object"` 23 | } 24 | 25 | type APIFeishuConfigRequest struct { 26 | Action string `json:"action"` 27 | Object *FeishuConfig `json:"object"` 28 | } 29 | 30 | type APILarkConfigRequest struct { 31 | Action string `json:"action"` 32 | Object *LarkConfig `json:"object"` 33 | } 34 | 35 | type APILDAPConfigRequest struct { 36 | Action string `json:"action"` 37 | Object *LDAPConfig `json:"object"` 38 | } 39 | 40 | type APICAS2ConfigRequest struct { 41 | Action string `json:"action"` 42 | Object *CAS2Config `json:"object"` 43 | } 44 | 45 | type APIDiscoveryRuleRequest struct { 46 | Action string `json:"action"` 47 | Object *DiscoveryRule `json:"object"` 48 | } 49 | -------------------------------------------------------------------------------- /logstash/janusec.conf.j2: -------------------------------------------------------------------------------- 1 | input { 2 | file { 3 | path => "/usr/local/janusec/log/*.log" 4 | start_position => "beginning" 5 | sincedb_path => "/dev/null" 6 | } 7 | 8 | file { 9 | path => "/usr/local/janusec/log/*.log" 10 | start_position => "beginning" 11 | sincedb_path => "/dev/null" 12 | } 13 | 14 | } 15 | 16 | filter { 17 | grok { 18 | match => { "message" => "%{SYSLOGPROG:date} %{TIME:time} \[%{GREEDYDATA:IP}\] %{WORD:method} \[%{GREEDYDATA:path}\] UA:%{GREEDYDATA:navegador}" } 19 | 20 | } 21 | date { 22 | match => [ "timestamp" , "dd/MMM/yyyy:HH:mm:ss Z" ] 23 | } 24 | geoip { 25 | source => ["IP"] 26 | target => "geoip" 27 | fields => ["continent_code", "longitude", "city_name", "region_code", "country_name", "location", "ip", "latitude"] 28 | add_field => [ "[geoip][coordinates]", "%{[geoip][longitude]}" ] 29 | add_field => [ "[geoip][coordinates]", "%{[geoip][latitude]}" ] 30 | } 31 | mutate { 32 | convert => [ "[geoip][coordinates]", "float" ] 33 | } 34 | } 35 | 36 | output { 37 | elasticsearch { 38 | hosts => ["ELASTIC_SRV:9200"] 39 | user => "elastic" 40 | password => "changeme" 41 | manage_template => false 42 | index => "janusec-%{+YYYY.MM.dd}" 43 | } 44 | stdout { codec => rubydebug } 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /gateway/gateway_502.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-10-18 10:58:30 5 | * @Last Modified: U2, 2020-10-18 10:58:30 6 | */ 7 | 8 | package gateway 9 | 10 | import ( 11 | "html/template" 12 | "net/http" 13 | 14 | "janusec/models" 15 | "janusec/utils" 16 | ) 17 | 18 | var tmpl502 *template.Template 19 | 20 | // GenerateInternalErrorResponse ... 21 | func GenerateInternalErrorResponse(w http.ResponseWriter, errInfo *models.InternalErrorInfo) { 22 | if tmpl502 == nil { 23 | tmpl502, _ = template.New("InternalError").Parse(internalErrorHTML) 24 | } 25 | 26 | err := tmpl502.Execute(w, errInfo) 27 | if err != nil { 28 | utils.DebugPrintln("GenerateInternalErrorResponse tmpl.Execute error", err) 29 | } 30 | } 31 | 32 | const internalErrorHTML = ` 33 | 34 | 35 | Internal Error 36 | 37 | 50 | 51 |
52 |

Internal Server Offline

53 |
54 | {{ .Description }}. Detected by Janusec Application Gateway 55 |
56 | 57 | 58 | ` 59 | -------------------------------------------------------------------------------- /backend/rpc_application.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:23:12 5 | * @Last Modified: U2, 2018-07-14 16:23:12 6 | */ 7 | 8 | package backend 9 | 10 | import ( 11 | "encoding/json" 12 | 13 | "janusec/data" 14 | "janusec/models" 15 | "janusec/utils" 16 | ) 17 | 18 | // RPCSelectApplications ... 19 | func RPCSelectApplications() []*models.Application { 20 | rpcRequest := &models.RPCRequest{Action: "get_apps", Object: nil} 21 | resp, err := data.GetRPCResponse(rpcRequest) 22 | if err != nil { 23 | utils.DebugPrintln("RPCSelectApplications GetResponse", err) 24 | return nil 25 | } 26 | rpcApps := &models.RPCApplications{} 27 | err = json.Unmarshal(resp, rpcApps) 28 | if err != nil { 29 | utils.DebugPrintln("RPCSelectApplications Unmarshal", err) 30 | return nil 31 | } 32 | applications := rpcApps.Object 33 | return applications 34 | } 35 | 36 | // RPCSelectVipApplications VIP for port forwarding 37 | func RPCSelectVipApplications() []*models.VipApp { 38 | rpcRequest := &models.RPCRequest{Action: "get_vip_apps", Object: nil} 39 | resp, err := data.GetRPCResponse(rpcRequest) 40 | if err != nil { 41 | utils.DebugPrintln("RPCSelectVipApplications GetResponse", err) 42 | return nil 43 | } 44 | rpcVipApps := &models.RPCVipApps{} 45 | err = json.Unmarshal(resp, rpcVipApps) 46 | if err != nil { 47 | utils.DebugPrintln("RPCSelectVipApplications Unmarshal", err) 48 | return nil 49 | } 50 | vipApps := rpcVipApps.Object 51 | return vipApps 52 | } 53 | -------------------------------------------------------------------------------- /gateway/gateway_health.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-10-20 20:31:07 5 | * @Last Modified: U2, 2020-10-20 20:31:07 6 | */ 7 | 8 | package gateway 9 | 10 | import ( 11 | "janusec/data" 12 | "janusec/models" 13 | "janusec/utils" 14 | "time" 15 | 16 | "github.com/shirou/gopsutil/cpu" 17 | "github.com/shirou/gopsutil/disk" 18 | "github.com/shirou/gopsutil/load" 19 | "github.com/shirou/gopsutil/mem" 20 | ) 21 | 22 | var ( 23 | startTime = time.Now().Unix() 24 | concurrency = int64(0) 25 | ) 26 | 27 | // GetGatewayHealth show CPU MEM Storage 28 | func GetGatewayHealth() (models.GateHealth, error) { 29 | cpuLoad, _ := load.Avg() 30 | cpuPercent, _ := cpu.Percent(1*time.Second, false) 31 | if len(cpuPercent) == 0 { 32 | cpuPercent = []float64{0.0} 33 | utils.DebugPrintln("GetGatewayHealth cpu.Percent []") 34 | } 35 | memStat, _ := mem.VirtualMemory() 36 | diskStat, _ := disk.Usage("/") 37 | timeZone, offset := time.Now().Zone() 38 | gateHealth := models.GateHealth{ 39 | StartTime: startTime, 40 | CurrentTime: time.Now().Unix(), 41 | Version: data.Version, 42 | CPUPercent: cpuPercent[0], 43 | CPULoad1: cpuLoad.Load1, 44 | CPULoad5: cpuLoad.Load5, 45 | CPULoad15: cpuLoad.Load15, 46 | MemUsed: memStat.Used, 47 | MemTotal: memStat.Total, 48 | DiskUsed: diskStat.Used, 49 | DiskTotal: diskStat.Total, 50 | TimeZone: timeZone, 51 | TimeOffset: offset / 3600.0, 52 | ConCurrency: concurrency, 53 | } 54 | return gateHealth, nil 55 | } 56 | -------------------------------------------------------------------------------- /models/cookie.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2023-05-28 12:45:10 5 | */ 6 | 7 | package models 8 | 9 | type CookieType int64 10 | 11 | const ( 12 | Cookie_Necessary CookieType = 1 13 | Cookie_Functional CookieType = 1 << 1 14 | Cookie_Analytics CookieType = 1 << 2 15 | Cookie_Marketing CookieType = 1 << 3 16 | Cookie_Unclassified CookieType = 1 << 9 // 512 17 | ) 18 | 19 | // Cookie used by applications 20 | type Cookie struct { 21 | ID int64 `json:"id,string"` 22 | AppID int64 `json:"app_id,string"` 23 | Name string `json:"name"` 24 | Domain string `json:"domain"` 25 | Path string `json:"path"` 26 | Duration string `json:"duration"` 27 | Vendor string `json:"vendor"` 28 | Type CookieType `json:"type"` 29 | Description string `json:"description"` 30 | AccessTime int64 `json:"access_time"` 31 | Source string `json:"source"` 32 | } 33 | 34 | // CookieRef used for classification automatically 35 | type CookieRef struct { 36 | ID int64 `json:"id,string"` 37 | Name string `json:"name"` 38 | Vendor string `json:"vendor"` 39 | Type CookieType `json:"type"` 40 | Description string `json:"description"` 41 | Operation CookieOperation `json:"operation"` 42 | } 43 | 44 | type CookieOperation int64 45 | 46 | const ( 47 | CookieOperation_EqualsString CookieOperation = 1 48 | CookieOperation_BeginWithString CookieOperation = 1 << 1 49 | CookieOperation_RegexMatch CookieOperation = 1 << 2 50 | ) 51 | -------------------------------------------------------------------------------- /static/welcome/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Welcome to JANUSEC 6 | 7 | 46 | 47 | 48 |
49 | 50 |
51 |

Oops! The website you requested has not been configured yet.

52 |
53 | 54 | 55 |
56 |

Tips:

57 | 63 |
64 | 65 | 68 | 69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /release_batch.sh: -------------------------------------------------------------------------------- 1 | printf "Creating installation package\n" 2 | printf "Checklist:\n" 3 | printf "* Angular Admin Version Check. \n" 4 | printf "* Janusec Version Check. \n" 5 | version="1.6.0" 6 | printf "Version: ${version} \n" 7 | 8 | read -r -p "Are You Sure? [Y/n] " option 9 | 10 | case $option in 11 | [yY]) printf "Continue \n" 12 | ;; 13 | [nN]) printf "Bye! \n" 14 | exit 0 15 | ;; 16 | *) printf "Invalid option. \n" 17 | exit 1 18 | ;; 19 | esac 20 | 21 | function buildFor() { 22 | filename_prefix="janusec-${version}-$1" 23 | temp_dir="./dist/${filename_prefix}/" 24 | mkdir -p ${temp_dir} 25 | \cp -f ./janusec ${temp_dir} 26 | \cp -f ./3rdpartylicenses.txt ${temp_dir} 27 | rm -rf ${temp_dir}static 28 | mkdir ${temp_dir}static 29 | \cp -r ./static/janusec-admin ${temp_dir}static/ 30 | \cp -r ./static/welcome ${temp_dir}static/ 31 | \cp -f ./robots.txt ${temp_dir}static/ 32 | \cp -f ./scripts/* ${temp_dir} 33 | cd ./dist/ 34 | tar -zcf ./${filename_prefix}.tar.gz ./${filename_prefix} 35 | rm -rf ./${filename_prefix} 36 | cd .. 37 | } 38 | 39 | printf "Building amd64 ... \n" 40 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build janusec.go 41 | buildFor amd64 42 | printf "amd64 done!\n" 43 | 44 | printf "Building arm64 ... \n" 45 | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build janusec.go 46 | buildFor arm64 47 | printf "arm64 done!\n" 48 | 49 | #printf "Building mips64 ... \n" 50 | #CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build janusec.go 51 | #buildFor mips64 52 | #printf "mips64 done!\n" 53 | 54 | #printf "Building mips64le ... \n" 55 | #CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build janusec.go 56 | #buildFor mips64le 57 | #printf "mips64le done!\n" 58 | 59 | printf "Done!\n" 60 | -------------------------------------------------------------------------------- /data/rpc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:31:27 5 | * @Last Modified: U2, 2018-07-14 16:31:27 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "bytes" 12 | "encoding/hex" 13 | "encoding/json" 14 | "io" 15 | "net/http" 16 | "time" 17 | 18 | "janusec/models" 19 | "janusec/utils" 20 | ) 21 | 22 | // GenAuthKey 23 | // Using NodesKey for authentication between replica nodes and primary node 24 | // Using DataDiscoveryKey for data dicovery report 25 | func GenAuthKey(key []byte) string { 26 | authTime := models.AuthTime{CurTime: time.Now().Unix()} 27 | nodeAuthBytes, err := json.Marshal(authTime) 28 | if err != nil { 29 | utils.DebugPrintln("GenAuthKey", err) 30 | } 31 | encryptedAuthBytes := EncryptWithKey(nodeAuthBytes, key) 32 | return hex.EncodeToString(encryptedAuthBytes) 33 | } 34 | 35 | // GetRPCResponse ... 36 | func GetRPCResponse(rpcReq *models.RPCRequest) (respBytes []byte, err error) { 37 | rpcReq.NodeVersion = Version 38 | rpcReq.AuthKey = GenAuthKey(NodesKey) 39 | rpcReq.PublicIP = GetPublicIP() 40 | bytesData, err := json.Marshal(rpcReq) 41 | if err != nil { 42 | utils.DebugPrintln("GetRPCResponse Marshal", err) 43 | } 44 | reader := bytes.NewReader(bytesData) 45 | request, err := http.NewRequest("POST", CFG.ReplicaNode.SyncAddr, reader) 46 | request.Header.Set("Content-Type", "application/json;charset=UTF-8") 47 | client := http.Client{} 48 | resp, err := client.Do(request) 49 | if err != nil { 50 | utils.DebugPrintln("GetRPCResponse Do", err) 51 | } 52 | if err != nil { 53 | utils.DebugPrintln("GetRPCResponse Do", err) 54 | return nil, err 55 | } 56 | defer resp.Body.Close() 57 | respBytes, err = io.ReadAll(resp.Body) 58 | return respBytes, err 59 | 60 | } 61 | -------------------------------------------------------------------------------- /data/backend_totp.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-05-17 22:40:35 5 | * @Last Modified: U2, 2020-05-17 22:40:35 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "janusec/models" 12 | "janusec/utils" 13 | ) 14 | 15 | // CreateTableIfNotExistsTOTP init table 16 | // 0.9.12+fix: change uid to totp_uid 17 | func (dal *MyDAL) CreateTableIfNotExistsTOTP() error { 18 | const sqlCreateTableIfNotExistsTOTP = `CREATE TABLE IF NOT EXISTS "totp"("id" bigserial PRIMARY KEY, "totp_uid" VARCHAR(128) NOT NULL, "totp_key" VARCHAR(128) NOT NULL, "totp_verified" boolean)` 19 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsTOTP) 20 | return err 21 | } 22 | 23 | // GetTOTPItemByUID return object 24 | func (dal *MyDAL) GetTOTPItemByUID(uid string) (*models.TOTP, error) { 25 | var totpItem = &models.TOTP{} 26 | const sqlGetTOTP = `SELECT "id","totp_uid","totp_key","totp_verified" from "totp" where "totp_uid"=$1` 27 | err := dal.db.QueryRow(sqlGetTOTP, uid).Scan(&totpItem.ID, &totpItem.UID, &totpItem.TOTPKey, &totpItem.TOTPVerified) 28 | return totpItem, err 29 | } 30 | 31 | // InsertTOTPItem INSERT new totp item 32 | func (dal *MyDAL) InsertTOTPItem(uid string, totpKey string, totpVerified bool) (id int64, err error) { 33 | snowID := utils.GenSnowflakeID() 34 | const sqlInsertTOTP = `INSERT INTO "totp"("id","totp_uid","totp_key","totp_verified") VALUES($1,$2,$3,$4) RETURNING "id"` 35 | err = dal.db.QueryRow(sqlInsertTOTP, snowID, uid, totpKey, totpVerified).Scan(&id) 36 | return id, err 37 | } 38 | 39 | // UpdateTOTPVerified set verified 40 | func (dal *MyDAL) UpdateTOTPVerified(totpVerified bool, id int64) error { 41 | const sqlUpdateTOTP = `UPDATE "totp" SET "totp_verified"=$1 WHERE "id"=$2` 42 | _, err := dal.db.Exec(sqlUpdateTOTP, totpVerified, id) 43 | return err 44 | } 45 | -------------------------------------------------------------------------------- /data/data_config.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:25:04 5 | * @Last Modified: U2, 2018-07-14 16:25:04 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "encoding/hex" 12 | "encoding/json" 13 | "os" 14 | "strings" 15 | 16 | "janusec/models" 17 | "janusec/utils" 18 | //"fmt" 19 | ) 20 | 21 | // NewConfig ... 22 | func NewConfig(filename string) (*models.Config, error) { 23 | config := &models.Config{} 24 | configBytes, err := os.ReadFile(filename) 25 | if err != nil { 26 | return nil, err 27 | } 28 | err = json.Unmarshal(configBytes, config) 29 | if err != nil { 30 | utils.DebugPrintln("NewConfig json.Unmarshal", err) 31 | } 32 | if strings.ToLower(config.NodeRole) == "primary" { 33 | dbPassword := config.PrimaryNode.Database.Password 34 | if len(dbPassword) <= 32 { 35 | // Encrypt password 36 | encryptedPasswordBytes := AES256Encrypt([]byte(dbPassword), true) 37 | encryptedPassword := hex.EncodeToString(encryptedPasswordBytes) 38 | encryptedConfig := models.EncryptedConfig(*config) 39 | encryptedConfig.PrimaryNode.Database.Password = encryptedPassword 40 | encryptedConfigBytes, _ := json.MarshalIndent(encryptedConfig, "", "\t") 41 | _ = os.WriteFile(filename, encryptedConfigBytes, 0600) 42 | } else { 43 | // Decrypt password 44 | encryptedPassword, err := hex.DecodeString(dbPassword) 45 | if err != nil { 46 | return nil, err 47 | } 48 | passwordBytes, _ := AES256Decrypt(encryptedPassword, true) 49 | config.PrimaryNode.Database.Password = string(passwordBytes) 50 | } 51 | } 52 | //fmt.Println("NewConfig config.Database.Password=",config.Database.Password) 53 | // Init default listen port 54 | if len(config.ListenHTTP) == 0 { 55 | config.ListenHTTP = ":80" 56 | } 57 | if len(config.ListenHTTPS) == 0 { 58 | config.ListenHTTPS = ":443" 59 | } 60 | return config, nil 61 | } 62 | -------------------------------------------------------------------------------- /gateway/rpc_update.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:21:19 5 | * @Last Modified: U2, 2018-07-14 16:21:19 6 | */ 7 | 8 | package gateway 9 | 10 | import ( 11 | "encoding/hex" 12 | "time" 13 | 14 | "janusec/backend" 15 | "janusec/data" 16 | "janusec/firewall" 17 | "janusec/utils" 18 | ) 19 | 20 | var ( 21 | // syncTicker used for check update from primary node 22 | syncTicker *time.Ticker 23 | ) 24 | 25 | // SyncTimeTick let replica nodes get/sync NodeSettings from Primary Node 26 | func SyncTimeTick() { 27 | utils.DebugPrintln("SyncTimeTick init:", data.NodeSetting.SyncInterval) 28 | syncTicker = time.NewTicker(data.NodeSetting.SyncInterval) 29 | for range syncTicker.C { 30 | //fmt.Println("SyncTimeTick:", time.Now()) 31 | backendLastModified := data.NodeSetting.BackendLastModified 32 | firewallLastModified := data.NodeSetting.FirewallLastModified 33 | discoveryLastModified := data.NodeSetting.DiscoveryLastModified 34 | lastSyncInterval := data.NodeSetting.SyncInterval 35 | data.NodeSetting = data.RPCGetNodeSetting() 36 | // Check update 37 | if backendLastModified < data.NodeSetting.BackendLastModified { 38 | go backend.LoadAppConfiguration() 39 | } 40 | if firewallLastModified < data.NodeSetting.FirewallLastModified { 41 | go firewall.InitFirewall() 42 | } 43 | if discoveryLastModified < data.NodeSetting.DiscoveryLastModified { 44 | go firewall.LoadDiscoveryRules() 45 | } 46 | if lastSyncInterval != data.NodeSetting.SyncInterval { 47 | syncTicker.Stop() 48 | syncTicker = time.NewTicker(data.NodeSetting.SyncInterval) 49 | utils.DebugPrintln("SyncTimeTick change sync interval to:", data.NodeSetting.SyncInterval) 50 | } 51 | // update DataDiscoveryKey 52 | if len(data.NodeSetting.DataDiscoveryKey) > 0 { 53 | data.DataDiscoveryKey, _ = hex.DecodeString(data.NodeSetting.DataDiscoveryKey) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /models/api_firewall.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:39:15 5 | * @Last Modified: U2, 2018-07-14 16:39:15 6 | */ 7 | 8 | package models 9 | 10 | type APIGroupPolicyRequest struct { 11 | Action string `json:"action"` 12 | Object *GroupPolicy `json:"object"` 13 | } 14 | 15 | type APICCPolicyRequest struct { 16 | Action string `json:"action"` 17 | Object *CCPolicy `json:"object"` 18 | } 19 | 20 | type APIIPPolicyRequest struct { 21 | Action string `json:"action"` 22 | Object *IPPolicy `json:"object"` 23 | } 24 | 25 | // APIRegexMatchRequest for Regex Match Test 26 | type APIRegexMatchRequest struct { 27 | Action string `json:"action"` 28 | Object *RegexMatch `json:"object"` 29 | } 30 | 31 | type APIStatCountRequest struct { 32 | Action string `json:"action"` 33 | AppID int64 `json:"app_id,string"` 34 | StartTime int64 `json:"start_time"` 35 | EndTime int64 `json:"end_time"` 36 | Count int64 `json:"count"` 37 | } 38 | 39 | type HitLogsRequest struct { 40 | AppID int64 `json:"app_id,string"` 41 | StartTime int64 `json:"start_time"` 42 | EndTime int64 `json:"end_time"` 43 | RequestCount int64 `json:"request_count"` 44 | Offset int64 `json:"offset"` 45 | } 46 | 47 | type APIHitLogsRequest struct { 48 | Action string `json:"action"` 49 | AppID int64 `json:"app_id,string"` 50 | StartTime int64 `json:"start_time"` 51 | EndTime int64 `json:"end_time"` 52 | RequestCount int64 `json:"request_count"` 53 | Offset int64 `json:"offset"` 54 | } 55 | 56 | /* 57 | type WeekStat struct { 58 | AppID int64 `json:"app_id,string"` 59 | VulnID int64 `json:"vuln_id"` 60 | StartTime int64 `json:"start_time"` 61 | } 62 | */ 63 | 64 | type APIWeekStatRequest struct { 65 | Action string `json:"action"` 66 | AppID int64 `json:"app_id,string"` 67 | VulnID int64 `json:"vuln_id"` 68 | StartTime int64 `json:"start_time"` 69 | } 70 | -------------------------------------------------------------------------------- /data/firewall_vuln.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:31:14 5 | * @Last Modified: U2, 2018-07-14 16:31:14 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "janusec/models" 12 | "janusec/utils" 13 | ) 14 | 15 | const ( 16 | sqlCreateTableIfNotExistsVulnType = `CREATE TABLE IF NOT EXISTS "vulntypes"("id" bigint primary key,"name" VARCHAR(128))` 17 | sqlExistsVulnType = `SELECT COALESCE((SELECT 1 FROM "vulntypes" LIMIT 1),0)` 18 | sqlInsertVulnType = `INSERT INTO "vulntypes"("id","name") VALUES($1,$2)` 19 | sqlSelectVulnTypes = `SELECT "id","name" FROM "vulntypes"` 20 | ) 21 | 22 | // CreateTableIfNotExistsVulnType ... 23 | func (dal *MyDAL) CreateTableIfNotExistsVulnType() error { 24 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsVulnType) 25 | return err 26 | } 27 | 28 | // ExistsVulnType ... 29 | func (dal *MyDAL) ExistsVulnType() bool { 30 | var exist int 31 | err := dal.db.QueryRow(sqlExistsVulnType).Scan(&exist) 32 | if err != nil { 33 | utils.DebugPrintln("ExistsVulnType", err) 34 | } 35 | return exist != 0 36 | } 37 | 38 | // SelectVulnTypes ... 39 | func (dal *MyDAL) SelectVulnTypes() ([]*models.VulnType, error) { 40 | vulnTypes := []*models.VulnType{} 41 | rows, err := dal.db.Query(sqlSelectVulnTypes) 42 | if err != nil { 43 | utils.DebugPrintln("SelectVulnTypes", err) 44 | } 45 | defer rows.Close() 46 | for rows.Next() { 47 | vulnType := &models.VulnType{} 48 | err = rows.Scan(&vulnType.ID, &vulnType.Name) 49 | if err != nil { 50 | 51 | utils.DebugPrintln("SelectVulnTypes rows.Scan", err) 52 | return vulnTypes, err 53 | } 54 | vulnTypes = append(vulnTypes, vulnType) 55 | } 56 | return vulnTypes, err 57 | } 58 | 59 | // InsertVulnType ... 60 | func (dal *MyDAL) InsertVulnType(id int64, name string) (err error) { 61 | _, err = dal.db.Exec(sqlInsertVulnType, id, name) 62 | if err != nil { 63 | utils.DebugPrintln("InsertVulnType", err) 64 | } 65 | return err 66 | } 67 | -------------------------------------------------------------------------------- /models/api_backend.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2023-04-01 10:48:41 5 | */ 6 | 7 | package models 8 | 9 | // APIRequest used for gateway administration 10 | type APIRequest struct { 11 | AuthKey string `json:"auth_key"` 12 | Action string `json:"action"` 13 | ObjectID int64 `json:"id,string"` 14 | Object any `json:"object"` 15 | } 16 | 17 | type APIApplicationRequest struct { 18 | Action string `json:"action"` 19 | ObjectID int64 `json:"id,string"` 20 | Object *Application `json:"object"` 21 | } 22 | 23 | type APIVipAppRequest struct { 24 | Action string `json:"action"` 25 | ObjectID int64 `json:"id,string"` 26 | Object *VipApp `json:"object"` 27 | } 28 | 29 | type APICertRequest struct { 30 | Action string `json:"action"` 31 | ObjectID int64 `json:"id,string"` 32 | Object *CertItem `json:"object"` 33 | } 34 | 35 | type APIAppUserRequest struct { 36 | Action string `json:"action"` 37 | ObjectID int64 `json:"id,string"` 38 | Object *FrontAppUser `json:"object"` 39 | } 40 | 41 | type APICookieRequest struct { 42 | Action string `json:"action"` 43 | ObjectID int64 `json:"id,string"` 44 | Object *Cookie `json:"object"` 45 | } 46 | 47 | // APIDelCookiesRequest used for deletion cookies in batch 48 | // post {"action": "del_cookies", object: [1, 3, 5, 8]} 49 | type APIDelCookiesRequest struct { 50 | Action string `json:"action"` 51 | IDList []string `json:"ids"` 52 | } 53 | 54 | type APICookieRefRequest struct { 55 | Action string `json:"action"` 56 | ObjectID int64 `json:"id,string"` 57 | Object *CookieRef `json:"object"` 58 | } 59 | 60 | type APIDNSDomainRequest struct { 61 | Action string `json:"action"` 62 | ObjectID int64 `json:"id,string"` 63 | Object *DNSDomain `json:"object"` 64 | } 65 | 66 | type APIDNSRecordRequest struct { 67 | Action string `json:"action"` 68 | ObjectID int64 `json:"id,string"` 69 | Object *DNSRecord `json:"object"` 70 | } 71 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module janusec 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/andybalholm/brotli v1.1.1 9 | github.com/bwmarrin/snowflake v0.3.0 10 | github.com/dchest/captcha v1.1.0 11 | github.com/glebarez/go-sqlite v1.22.0 12 | github.com/go-ldap/ldap/v3 v3.4.10 13 | github.com/google/nftables v0.3.0 14 | github.com/gorilla/sessions v1.4.0 15 | github.com/gorilla/websocket v1.5.3 16 | github.com/lib/pq v1.10.9 17 | github.com/miekg/dns v1.1.63 18 | github.com/patrickmn/go-cache v2.1.0+incompatible 19 | github.com/shirou/gopsutil v3.21.11+incompatible 20 | github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e 21 | github.com/yookoala/gofast v0.8.0 22 | golang.org/x/crypto v0.36.0 23 | golang.org/x/net v0.37.0 24 | ) 25 | 26 | require ( 27 | github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect 28 | github.com/dustin/go-humanize v1.0.1 // indirect 29 | github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect 30 | github.com/go-ole/go-ole v1.3.0 // indirect 31 | github.com/google/go-cmp v0.7.0 // indirect 32 | github.com/google/uuid v1.6.0 // indirect 33 | github.com/gorilla/securecookie v1.1.2 // indirect 34 | github.com/josharian/native v1.1.0 // indirect 35 | github.com/mattn/go-isatty v0.0.20 // indirect 36 | github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect 37 | github.com/mdlayher/socket v0.5.1 // indirect 38 | github.com/ncruces/go-strftime v0.1.9 // indirect 39 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 40 | github.com/stretchr/testify v1.8.1 // indirect 41 | github.com/tklauser/go-sysconf v0.3.15 // indirect 42 | github.com/tklauser/numcpus v0.10.0 // indirect 43 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 44 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect 45 | golang.org/x/mod v0.24.0 // indirect 46 | golang.org/x/sync v0.12.0 // indirect 47 | golang.org/x/sys v0.31.0 // indirect 48 | golang.org/x/text v0.23.0 // indirect 49 | golang.org/x/tools v0.31.0 // indirect 50 | modernc.org/libc v1.61.13 // indirect 51 | modernc.org/mathutil v1.7.1 // indirect 52 | modernc.org/memory v1.8.2 // indirect 53 | modernc.org/sqlite v1.36.0 // indirect 54 | ) 55 | -------------------------------------------------------------------------------- /data/backend_dns_domain.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2023-06-04 18:54 5 | */ 6 | 7 | package data 8 | 9 | import ( 10 | "janusec/models" 11 | "janusec/utils" 12 | ) 13 | 14 | // CreateTableIfNotExistsDNSDomains ... 15 | func (dal *MyDAL) CreateTableIfNotExistsDNSDomains() error { 16 | const sqlCreateTableIfNotExistsDNSDomains = `CREATE TABLE IF NOT EXISTS "dns_domains"("id" BIGINT PRIMARY KEY, "name" VARCHAR(256) NOT NULL)` 17 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsDNSDomains) 18 | return err 19 | } 20 | 21 | // SelectDNSDomains ... 22 | func (dal *MyDAL) SelectDNSDomains() []*models.DNSDomain { 23 | const sqlSelectDNSDomains = `SELECT "id","name" FROM "dns_domains"` 24 | rows, err := dal.db.Query(sqlSelectDNSDomains) 25 | if err != nil { 26 | utils.DebugPrintln("SelectDNSDomains", err) 27 | } 28 | defer rows.Close() 29 | dnsDomains := []*models.DNSDomain{} 30 | for rows.Next() { 31 | dnsDomain := &models.DNSDomain{} 32 | _ = rows.Scan(&dnsDomain.ID, &dnsDomain.Name) 33 | dnsDomains = append(dnsDomains, dnsDomain) 34 | } 35 | return dnsDomains 36 | } 37 | 38 | func (dal *MyDAL) InsertDNSDomain(dnsDomain *models.DNSDomain) error { 39 | const sqlInsertDNSDomain = `INSERT INTO "dns_domains"("id","name") VALUES($1,$2)` 40 | _, err := dal.db.Exec(sqlInsertDNSDomain, dnsDomain.ID, dnsDomain.Name) 41 | return err 42 | } 43 | 44 | func (dal *MyDAL) UpdateDNSDomain(dnsDomain *models.DNSDomain) error { 45 | const sqlUpdateDNSDomain = `UPDATE "dns_domains" SET "name"=$1 WHERE "id"=$2` 46 | _, err := dal.db.Exec(sqlUpdateDNSDomain, dnsDomain.Name, dnsDomain.ID) 47 | return err 48 | } 49 | 50 | func (dal *MyDAL) DeleteDNSDomainByID(id int64) error { 51 | const sqlDelDNSDomain = `DELETE FROM "dns_domains" WHERE "id"=$1` 52 | _, err := dal.db.Exec(sqlDelDNSDomain, id) 53 | return err 54 | } 55 | 56 | /* 57 | func (dal *MyDAL) SelectDNSDomainByID(id int64) (*models.DNSDomain, error) { 58 | dnsDomain := models.DNSDomain{ 59 | ID: id, 60 | } 61 | const sqlSelectDNSDomains = `SELECT "name" FROM "dns_domains" WHERE "id"=$1` 62 | err := dal.db.QueryRow(sqlSelectDNSDomains, id).Scan(&dnsDomain.Name) 63 | return &dnsDomain, err 64 | } 65 | */ 66 | -------------------------------------------------------------------------------- /firewall/routine.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:35:30 5 | * @Last Modified: U2, 2018-07-14 16:35:30 6 | */ 7 | 8 | package firewall 9 | 10 | import ( 11 | "os" 12 | "syscall" 13 | "time" 14 | 15 | "janusec/data" 16 | "janusec/utils" 17 | ) 18 | 19 | // RoutineCleanLogTick Clear Expired Logs 20 | func RoutineCleanLogTick() { 21 | if data.IsPrimary { 22 | routineTicker := time.NewTicker(time.Duration(5*60) * time.Second) 23 | for range routineTicker.C { 24 | globalSettings := data.GetGlobalSettings2() 25 | timeStamp := time.Now().Unix() 26 | wafLogExpiredTime := timeStamp - (globalSettings.WAFLogDays * 86400) 27 | err := data.DAL.DeleteHitLogsBeforeTime(wafLogExpiredTime) 28 | if err != nil { 29 | utils.DebugPrintln("DeleteHitLogsBeforeTime error", err) 30 | } 31 | ccLogExpiredTime := timeStamp - (globalSettings.CCLogDays * 86400) 32 | err = data.DAL.DeleteCCLogsBeforeTime(ccLogExpiredTime) 33 | if err != nil { 34 | utils.DebugPrintln("DeleteCCLogsBeforeTime error", err) 35 | } 36 | } 37 | } 38 | } 39 | 40 | // RoutineCleanCacheTick Clean expired cdn files 41 | func RoutineCleanCacheTick() { 42 | routineTicker := time.NewTicker(time.Duration(7200) * time.Second) 43 | for range routineTicker.C { 44 | ClearExpiredFiles("./static/cdncache/", time.Now()) 45 | } 46 | } 47 | 48 | // ClearExpiredFiles clear expired static cdn files 49 | func ClearExpiredFiles(path string, now time.Time) { 50 | fs, err := os.ReadDir(path) 51 | if err != nil { 52 | utils.DebugPrintln("ClearExpiredFiles", err) 53 | } 54 | for _, file := range fs { 55 | if file.IsDir() { 56 | ClearExpiredFiles(path+file.Name()+"/", now) 57 | } else { 58 | targetFile := path + file.Name() 59 | if fi, err := os.Stat(targetFile); err == nil { 60 | fiStat := fi.Sys().(*syscall.Stat_t) 61 | // Use ctime fiStat.Ctim.Sec to mark the last check time 62 | pastSeconds := now.Unix() - int64(fiStat.Ctim.Sec) 63 | if pastSeconds >= 86400*7 { 64 | err = os.Remove(targetFile) 65 | if err != nil { 66 | utils.DebugPrintln("ClearExpiredFiles Remove", targetFile, err) 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /static/janusec-admin/styles-OGTDWD3C.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Material Symbols Outlined;font-style:normal;font-weight:100 700;font-display:block;src:url("./media/material-symbols-outlined-UCGWUF4X.woff2") format("woff2")}.material-symbols-outlined{font-family:Material Symbols Outlined;font-weight:400;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility;font-feature-settings:"liga"}body{font-family:Arial Narrow,Arial,sans-serif,Helvetica,sans-serif,PingFang SC,"Source Han Serif SC",Microsoft YaHei}.container{display:flex;flex-direction:column;margin:1px 0 0 1px}.container2{padding:10px}.inner-container{display:flex;flex-direction:column;background-color:#f0f0f0;margin-top:5px;margin-bottom:5px}.header-custom{background-color:#f5f5f5;padding-left:10px}.line-container-custom{display:flex;align-items:stretch;background-color:#f5f5f5;margin-top:2px;padding:5px;gap:20px}.line-container-custom input{height:16px;flex:1}.btn-add{padding:5px 10px}.btn-with-icon{display:flex;align-items:center;gap:2px;padding:2px 10px;border:1px solid #808080;height:28px}.notes{background-color:#f0f0f0;padding:5px;margin-top:5px;margin-left:3px;margin-right:0;list-style-type:none}.search-box{background-color:#fff;border:solid 1px #D0D0D0;border-radius:15px;padding:6px;vertical-align:middle}.middle{display:flex;align-items:center}.flex-row{display:flex;margin-bottom:5px}.flex-90{flex:90}.flex-50{flex:50}.flex-40{flex:40}.flex-20{flex:20}.flex-10{flex:10;justify-content:center;align-items:center}.alert{background-color:#cecaa6;margin:1px}.notes{background-color:#f0f0f0;padding:10px;margin-top:5px;margin-left:3px;margin-right:0;list-style-type:none}.pointer:hover{cursor:pointer}.icon-green{color:green}.icon-red{color:red}.btn-close{height:16px;margin:0;padding:0;float:right}.icon-close{font-size:12px;height:12px}.dialog-title{padding:0 10px;margin-top:0;margin-left:5px}.btn-action{margin-left:15px}.option-custom{background-color:#d0d0d0!important;border:1px solid #f5f5f5}.tooltip-custom{background-color:#00f!important;color:#fff!important;font-size:16px!important;padding:8px!important;border-radius:8px!important}.code-wrap{font-size:14px;background-color:#f4f4f4;padding:10px;line-height:20px;word-break:break-all} 2 | -------------------------------------------------------------------------------- /data/backend_node.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:24:48 5 | * @Last Modified: U2, 2018-07-14 16:24:48 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "janusec/models" 12 | "janusec/utils" 13 | ) 14 | 15 | const ( 16 | sqlSelectAllNodes = `SELECT "id","version","last_ip","last_req_time" FROM "nodes"` 17 | sqlCreateTableIfNotExistsNodes = `CREATE TABLE IF NOT EXISTS "nodes"("id" bigserial PRIMARY KEY,"version" VARCHAR(128) NOT NULL,"last_ip" VARCHAR(128) NOT NULL,"last_req_time" bigint)` 18 | sqlInsertNode = `INSERT INTO "nodes"("id","version","last_ip","last_req_time") VALUES($1,$2,$3,$4) RETURNING "id"` 19 | sqlUpdateNodeLastInfo = `UPDATE "nodes" SET "version"=$1,"last_ip"=$2,"last_req_time"=$3 WHERE "id"=$4` 20 | sqlDeleteNodeByID = `DELETE FROM "nodes" WHERE "id"=$1` 21 | ) 22 | 23 | // DeleteNodeByID ... 24 | func (dal *MyDAL) DeleteNodeByID(id int64) error { 25 | _, err := dal.db.Exec(sqlDeleteNodeByID, id) 26 | return err 27 | } 28 | 29 | // SelectAllNodes ... 30 | func (dal *MyDAL) SelectAllNodes() []*models.Node { 31 | rows, err := dal.db.Query(sqlSelectAllNodes) 32 | if err != nil { 33 | utils.DebugPrintln("SelectAllNodes", err) 34 | } 35 | defer rows.Close() 36 | nodes := []*models.Node{} 37 | for rows.Next() { 38 | node := &models.Node{} 39 | _ = rows.Scan(&node.ID, &node.Version, &node.LastIP, &node.LastRequestTime) 40 | nodes = append(nodes, node) 41 | } 42 | return nodes 43 | } 44 | 45 | // CreateTableIfNotExistsNodes ... 46 | func (dal *MyDAL) CreateTableIfNotExistsNodes() error { 47 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsNodes) 48 | return err 49 | } 50 | 51 | // InsertNode ... 52 | func (dal *MyDAL) InsertNode(version string, lastIP string, lastReqTime int64) (newID int64) { 53 | id := utils.GenSnowflakeID() 54 | err := dal.db.QueryRow(sqlInsertNode, id, version, lastIP, lastReqTime).Scan(&newID) 55 | if err != nil { 56 | utils.DebugPrintln("InsertNode", err) 57 | } 58 | return newID 59 | } 60 | 61 | // UpdateNodeLastInfo ... 62 | func (dal *MyDAL) UpdateNodeLastInfo(version string, lastIP string, lastReqTime int64, id int64) error { 63 | stmt, _ := dal.db.Prepare(sqlUpdateNodeLastInfo) 64 | defer stmt.Close() 65 | _, err := stmt.Exec(version, lastIP, lastReqTime, id) 66 | if err != nil { 67 | utils.DebugPrintln("UpdateNodeLastInfo", err) 68 | } 69 | return err 70 | } 71 | -------------------------------------------------------------------------------- /utils/generate_cert.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:20:56 5 | * @Last Modified: U2, 2018-07-14 16:20:56 6 | */ 7 | 8 | package utils 9 | 10 | import ( 11 | "crypto/rand" 12 | "crypto/rsa" 13 | "crypto/x509" 14 | "crypto/x509/pkix" 15 | "encoding/json" 16 | "encoding/pem" 17 | "janusec/models" 18 | "math/big" 19 | "strings" 20 | "time" 21 | ) 22 | 23 | // SelfSignedCertificate in Application Management 24 | type SelfSignedCertificate struct { 25 | CertContent string `json:"cert_content"` 26 | PrivKeyContent string `json:"priv_key_content"` 27 | } 28 | 29 | // GenerateRSACertificate in Application Management 30 | func GenerateRSACertificate(body []byte) (selfSignedCert *SelfSignedCertificate, err error) { 31 | var rpcCertRequest models.APICertRequest 32 | if err := json.Unmarshal(body, &rpcCertRequest); err != nil { 33 | DebugPrintln("GenerateRSACertificate", err) 34 | return nil, err 35 | } 36 | certItem := rpcCertRequest.Object 37 | org := strings.ToUpper(certItem.CommonName) 38 | dotIndex := strings.Index(org, ".") 39 | if dotIndex > 0 { 40 | org = org[dotIndex+1:] 41 | } 42 | priv, err := rsa.GenerateKey(rand.Reader, 2048) 43 | if err != nil { 44 | return nil, err 45 | } 46 | notBefore := time.Now() 47 | notAfter := notBefore.Add(3653 * 24 * time.Hour) 48 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 49 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 50 | if err != nil { 51 | return nil, err 52 | } 53 | template := x509.Certificate{ 54 | SerialNumber: serialNumber, 55 | Subject: pkix.Name{ 56 | Organization: []string{org}, 57 | }, 58 | NotBefore: notBefore, 59 | NotAfter: notAfter, 60 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 61 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 62 | BasicConstraintsValid: true, 63 | DNSNames: []string{certItem.CommonName}, 64 | } 65 | derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) 66 | //fmt.Println("derBytes=", derBytes) 67 | if err != nil { 68 | return nil, err 69 | } 70 | pubCertBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) 71 | privKeyBytes := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 72 | selfSignedCert = &SelfSignedCertificate{CertContent: string(pubCertBytes), PrivKeyContent: string(privKeyBytes)} 73 | return selfSignedCert, nil 74 | } 75 | -------------------------------------------------------------------------------- /models/user.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2023-04-02 19:54:41 5 | */ 6 | 7 | package models 8 | 9 | import "database/sql" 10 | 11 | // AuthUser used for Authentication in Memory 12 | type AuthUser struct { 13 | UserID int64 `json:"user_id,string"` 14 | Username string `json:"username"` 15 | Logged bool `json:"logged"` 16 | IsSuperAdmin bool `json:"is_super_admin"` 17 | IsCertAdmin bool `json:"is_cert_admin"` 18 | IsAppAdmin bool `json:"is_app_admin"` 19 | NeedModifyPWD bool `json:"need_modify_pwd"` 20 | // v1.2.2 21 | TOTPKey string `json:"totp_key"` 22 | TOTPVerified bool `json:"totp_verified"` 23 | } 24 | 25 | // AppUser used for DB Storage 26 | type AppUser struct { 27 | ID int64 `json:"id,string"` 28 | Username string `json:"username"` 29 | HashPwd string `json:"-"` 30 | Salt string `json:"-"` 31 | Email string `json:"email"` 32 | IsSuperAdmin bool `json:"is_super_admin"` 33 | IsCertAdmin bool `json:"is_cert_admin"` 34 | IsAppAdmin bool `json:"is_app_admin"` 35 | NeedModifyPWD bool `json:"need_modify_pwd"` 36 | } 37 | 38 | // FrontAppUser used for updating app user, receive from front end 39 | type FrontAppUser struct { 40 | ID int64 `json:"id,string"` 41 | Username string `json:"username"` 42 | Password string `json:"password"` 43 | Email string `json:"email"` 44 | IsSuperAdmin bool `json:"is_super_admin"` 45 | IsCertAdmin bool `json:"is_cert_admin"` 46 | IsAppAdmin bool `json:"is_app_admin"` 47 | NeedModifyPWD bool `json:"need_modify_pwd"` 48 | } 49 | 50 | // QueryAppUser not include password and salt 51 | type QueryAppUser struct { 52 | ID int64 53 | Username string 54 | Email sql.NullString 55 | IsSuperAdmin bool 56 | IsCertAdmin bool 57 | IsAppAdmin bool 58 | NeedModifyPWD bool 59 | } 60 | 61 | // TOTP Authenticator 62 | type TOTP struct { 63 | ID int64 `json:"id,string"` 64 | UID string `json:"uid"` 65 | TOTPKey string `json:"totp_key"` 66 | TOTPVerified bool `json:"totp_verified"` 67 | } 68 | 69 | type LoginUser struct { 70 | Username string `json:"username"` 71 | Password string `json:"passwd"` 72 | TOTPKey string `json:"totp_key"` 73 | } 74 | 75 | type APILoginUserRequest struct { 76 | Action string `json:"action"` 77 | Object *LoginUser `json:"object"` 78 | } 79 | 80 | type APITOTPVerifyRequest struct { 81 | Action string `json:"action"` 82 | UID string `json:"uid"` 83 | Code string `json:"code"` 84 | } 85 | -------------------------------------------------------------------------------- /firewall/vuln.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:36:02 5 | * @Last Modified: U2, 2018-07-14 16:36:02 6 | */ 7 | 8 | package firewall 9 | 10 | import ( 11 | "os" 12 | "sync" 13 | 14 | "janusec/data" 15 | "janusec/models" 16 | "janusec/utils" 17 | ) 18 | 19 | var ( 20 | vulnTypes = []*models.VulnType{} 21 | 22 | // VulnMap map[int64]string 23 | VulnMap = sync.Map{} 24 | ) 25 | 26 | // InitVulnType ... 27 | func InitVulnType() { 28 | if data.IsPrimary { 29 | err := data.DAL.CreateTableIfNotExistsVulnType() 30 | if err != nil { 31 | utils.DebugPrintln("InitVulnType CreateTableIfNotExistsVulnType error", err) 32 | os.Exit(1) 33 | } 34 | existVuln := data.DAL.ExistsVulnType() 35 | if !existVuln { 36 | err := data.DAL.InsertVulnType(001, "None") 37 | if err != nil { 38 | utils.DebugPrintln("InsertVulnType error", err) 39 | } else { 40 | _ = data.DAL.InsertVulnType(100, "Sensitive Data Leakage") 41 | _ = data.DAL.InsertVulnType(200, "SQL Injection") 42 | _ = data.DAL.InsertVulnType(210, "Command Injection") 43 | _ = data.DAL.InsertVulnType(220, "Code Injection") 44 | _ = data.DAL.InsertVulnType(230, "LDAP Injection") 45 | _ = data.DAL.InsertVulnType(240, "XPATH Injection") 46 | _ = data.DAL.InsertVulnType(300, "Cross-site Scripting(XSS)") 47 | _ = data.DAL.InsertVulnType(400, "Path Traversal") 48 | _ = data.DAL.InsertVulnType(410, "Remote File Inclusion(RFI)") 49 | _ = data.DAL.InsertVulnType(420, "Local File Inclusion(LFI)") 50 | _ = data.DAL.InsertVulnType(500, "Web Shell") 51 | _ = data.DAL.InsertVulnType(510, "Upload") 52 | _ = data.DAL.InsertVulnType(600, "Crawler/Scanner") 53 | _ = data.DAL.InsertVulnType(700, "Server-Side Request Forgery(SSRF)") 54 | _ = data.DAL.InsertVulnType(710, "Client-Side Request Forgery(CSRF)") 55 | _ = data.DAL.InsertVulnType(800, "Logic Vulnerability") 56 | _ = data.DAL.InsertVulnType(900, "Open Source Vulnerability") 57 | _ = data.DAL.InsertVulnType(920, "Broken Authentication") 58 | _ = data.DAL.InsertVulnType(930, "Broken Access Control") 59 | _ = data.DAL.InsertVulnType(940, "Misconfiguration") 60 | _ = data.DAL.InsertVulnType(950, "Insecure Deserialization") 61 | _ = data.DAL.InsertVulnType(960, "XML External Entity(XXE)") 62 | _ = data.DAL.InsertVulnType(999, "Other") 63 | } 64 | } 65 | vulnTypes, _ = data.DAL.SelectVulnTypes() 66 | } else { 67 | vulnTypes = RPCSelectVulntypes() 68 | } 69 | for _, vulnType := range vulnTypes { 70 | VulnMap.Store(vulnType.ID, vulnType.Name) 71 | } 72 | } 73 | 74 | // GetVulnTypes ... 75 | func GetVulnTypes() ([]*models.VulnType, error) { 76 | return vulnTypes, nil 77 | } 78 | -------------------------------------------------------------------------------- /gateway/shield.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2021-05-02 15:27:30 5 | * @Last Modified: U2, 2021-05-02 15:27:30 6 | */ 7 | 8 | package gateway 9 | 10 | import ( 11 | "html/template" 12 | "janusec/data" 13 | "janusec/models" 14 | "janusec/utils" 15 | "net/http" 16 | "regexp" 17 | "time" 18 | 19 | "github.com/gorilla/sessions" 20 | "github.com/patrickmn/go-cache" 21 | ) 22 | 23 | var ( 24 | shieldCache = cache.New(5*time.Second, 5*time.Second) 25 | ) 26 | 27 | func IsSearchEngine(ua string) bool { 28 | matched, err := regexp.MatchString(data.NodeSetting.SearchEnginesPattern, ua) 29 | if err != nil { 30 | utils.DebugPrintln("ReverseHandlerFunc Search Engines MatchString", err) 31 | } 32 | return matched 33 | } 34 | 35 | func IsCrawler(r *http.Request, srcIP string) bool { 36 | count, found := shieldCache.Get(srcIP) 37 | if found { 38 | nowCount := count.(int64) + int64(1) 39 | if nowCount > 30 { 40 | // Found crawler 41 | return true 42 | } 43 | shieldCache.Set(srcIP, nowCount, cache.DefaultExpiration) 44 | } else { 45 | shieldCache.Set(srcIP, int64(1), cache.DefaultExpiration) 46 | } 47 | return false 48 | } 49 | 50 | // SecondShieldAuthorization give authorization 51 | func SecondShieldAuthorization(w http.ResponseWriter, r *http.Request) { 52 | session, _ := store.Get(r, "janusec-token") 53 | // First, check time interval 54 | timestampI := session.Values["timestamp"] 55 | if timestampI == nil { 56 | // show 5-second shield 57 | GenerateShieldPage(w, r, r.URL.Path) 58 | return 59 | } 60 | timestamp := timestampI.(int64) 61 | now := time.Now().Unix() 62 | if now-timestamp < 5 { 63 | GenerateShieldPage(w, r, r.URL.Path) 64 | return 65 | } 66 | session.Values["shldtoken"] = now 67 | // 5-second shield session will be invalid when user close the browser. 68 | session.Options = &sessions.Options{Path: "/", HttpOnly: true} 69 | err := session.Save(r, w) 70 | if err != nil { 71 | utils.DebugPrintln("session save error", err) 72 | } 73 | callback := r.FormValue("callback") 74 | http.Redirect(w, r, callback, http.StatusTemporaryRedirect) 75 | } 76 | 77 | // GenerateShieldPage for first access if 5-second shield enabled 78 | func GenerateShieldPage(w http.ResponseWriter, r *http.Request, urlPath string) { 79 | if data.TmplShield == nil { 80 | data.TmplShield, _ = template.New("tmplShield").Parse(data.NodeSetting.ShieldHTML) 81 | } 82 | session, _ := store.Get(r, "janusec-token") 83 | session.Values["timestamp"] = time.Now().Unix() 84 | // 5-second shield session will be invalid when user close the browser. 85 | session.Options = &sessions.Options{Path: "/", HttpOnly: true} 86 | err := session.Save(r, w) 87 | if err != nil { 88 | utils.DebugPrintln("session save error", err) 89 | } 90 | w.WriteHeader(200) 91 | err = data.TmplShield.Execute(w, models.ShieldInfo{Callback: urlPath}) 92 | if err != nil { 93 | utils.DebugPrintln("GenerateShieldPage tmpl.Execute error", err) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /data/backend_vip_app.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-11-01 09:14:07 5 | * @Last Modified: U2, 2020-11-01 09:14:07 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "janusec/models" 12 | "janusec/utils" 13 | ) 14 | 15 | // CreateTableIfNotExistsVipApplications ... 16 | func (dal *MyDAL) CreateTableIfNotExistsVipApplications() error { 17 | const sqlCreateTableIfNotExistsVipApplications = `CREATE TABLE IF NOT EXISTS "vip_apps"("id" bigserial PRIMARY KEY, "name" VARCHAR(128) NOT NULL, "listen_port" bigint, "is_tcp" boolean default true, "owner" VARCHAR(128) NOT NULL DEFAULT '', "description" VARCHAR(256) NOT NULL DEFAULT '')` 18 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsVipApplications) 19 | return err 20 | } 21 | 22 | // SelectVipApplications ... 23 | func (dal *MyDAL) SelectVipApplications() []*models.VipApp { 24 | const sqlSelectVipApplications = `SELECT "id","name","listen_port","is_tcp","owner","description" FROM "vip_apps"` 25 | rows, err := dal.db.Query(sqlSelectVipApplications) 26 | if err != nil { 27 | utils.DebugPrintln("SelectVipApplications", err) 28 | } 29 | defer rows.Close() 30 | var vipApps = []*models.VipApp{} 31 | for rows.Next() { 32 | vipApp := &models.VipApp{} 33 | err = rows.Scan( 34 | &vipApp.ID, 35 | &vipApp.Name, 36 | &vipApp.ListenPort, 37 | &vipApp.IsTCP, 38 | &vipApp.Owner, 39 | &vipApp.Description, 40 | ) 41 | if err != nil { 42 | utils.DebugPrintln("SelectVipApplications rows.Scan", err) 43 | } 44 | vipApps = append(vipApps, vipApp) 45 | } 46 | return vipApps 47 | } 48 | 49 | // InsertVipApp create new port forwarding 50 | func (dal *MyDAL) InsertVipApp(vipAppName string, listenPort int64, isTCP bool, owner string, description string) (newID int64) { 51 | const sqlInsertVipApp = `INSERT INTO "vip_apps"("id","name","listen_port","is_tcp","owner","description") VALUES($1,$2,$3,$4,$5,$6) RETURNING "id"` 52 | snowID := utils.GenSnowflakeID() 53 | err := dal.db.QueryRow(sqlInsertVipApp, snowID, vipAppName, listenPort, isTCP, owner, description).Scan(&newID) 54 | if err != nil { 55 | utils.DebugPrintln("InsertVipApp", err) 56 | } 57 | return newID 58 | } 59 | 60 | // UpdateVipAppByID update an existed VipApp 61 | func (dal *MyDAL) UpdateVipAppByID(vipAppName string, listenPort int64, isTCP bool, owner string, description string, vipAppID int64) error { 62 | const sqlUpdateVipApp = `UPDATE "vip_apps" SET "name"=$1,"listen_port"=$2,"is_tcp"=$3,"owner"=$4,"description"=$5 WHERE "id"=$6` 63 | _, err := dal.db.Exec(sqlUpdateVipApp, vipAppName, listenPort, isTCP, owner, description, vipAppID) 64 | if err != nil { 65 | utils.DebugPrintln("InsertVipApp", err) 66 | } 67 | return err 68 | } 69 | 70 | // DeleteVipAppByID ... 71 | func (dal *MyDAL) DeleteVipAppByID(id int64) error { 72 | const sqlDeleteVipAppByID = `DELETE FROM "vip_apps" WHERE "id"=$1` 73 | _, err := dal.db.Exec(sqlDeleteVipAppByID, id) 74 | if err != nil { 75 | utils.DebugPrintln("DeleteVipAppByID", err) 76 | } 77 | return err 78 | } 79 | -------------------------------------------------------------------------------- /data/firewall_ip_policy.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2021-01-09 22:25:00 5 | * @Last Modified: U2, 2021-01-09 22:25:00 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "janusec/models" 12 | "janusec/utils" 13 | ) 14 | 15 | // CreateTableIfNotExistsIPPolicies ... 16 | func (dal *MyDAL) CreateTableIfNotExistsIPPolicies() error { 17 | const sqlCreateTableIfNotExistsIPPolicies = `CREATE TABLE IF NOT EXISTS "ip_policies"("id" bigserial PRIMARY KEY, "ip_addr" VARCHAR(128) NOT NULL, "is_allow" boolean, "apply_to_waf" boolean, "apply_to_cc" boolean, "create_time" BIGINT, "description" VARCHAR(1024) DEFAULT '')` 18 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsIPPolicies) 19 | return err 20 | } 21 | 22 | // InsertIPPolicy Insert IP Address to "ip_policies" 23 | func (dal *MyDAL) InsertIPPolicy(ipAddr string, isAllow bool, applyToWAF bool, applyToCC bool, createTime int64, description string) (newID int64) { 24 | const sqlInsertIPPolicy = `INSERT INTO "ip_policies"("id","ip_addr","is_allow","apply_to_waf","apply_to_cc","create_time","description") VALUES($1,$2,$3,$4,$5,$6,$7) RETURNING "id"` 25 | snowID := utils.GenSnowflakeID() 26 | err := dal.db.QueryRow(sqlInsertIPPolicy, snowID, ipAddr, isAllow, applyToWAF, applyToCC, createTime, description).Scan(&newID) 27 | if err != nil { 28 | utils.DebugPrintln("InsertIPPolicy", err) 29 | } 30 | return newID 31 | } 32 | 33 | // UpdateIPPolicy update IP address and policy 34 | func (dal *MyDAL) UpdateIPPolicy(id int64, ipAddr string, isAllow bool, applyToWAF bool, applyToCC bool, description string) error { 35 | const sqlUpdateIPPolicy = `UPDATE "ip_policies" SET "ip_addr"=$1,"is_allow"=$2,"apply_to_waf"=$3,"apply_to_cc"=$4,"description"=$5 WHERE "id"=$6` 36 | _, err := dal.db.Exec(sqlUpdateIPPolicy, ipAddr, isAllow, applyToWAF, applyToCC, description, id) 37 | return err 38 | } 39 | 40 | // DeleteIPPolicyByID ... 41 | func (dal *MyDAL) DeleteIPPolicyByID(id int64) error { 42 | const sqlDeleteIPPolicyByID = `DELETE FROM "ip_policies" WHERE "id"=$1` 43 | _, err := dal.db.Exec(sqlDeleteIPPolicyByID, id) 44 | return err 45 | } 46 | 47 | // LoadIPPolicies return the list of IPPolicy 48 | func (dal *MyDAL) LoadIPPolicies() []*models.IPPolicy { 49 | const sqlSelectAllowList = `SELECT "id","ip_addr","is_allow","apply_to_waf","apply_to_cc","create_time","description" FROM "ip_policies"` 50 | rows, err := dal.db.Query(sqlSelectAllowList) 51 | if err != nil { 52 | utils.DebugPrintln("GetIPPolicies", err) 53 | } 54 | defer rows.Close() 55 | var ipPolicies = []*models.IPPolicy{} 56 | for rows.Next() { 57 | ipPolicy := &models.IPPolicy{} 58 | err = rows.Scan( 59 | &ipPolicy.ID, 60 | &ipPolicy.IPAddr, 61 | &ipPolicy.IsAllow, 62 | &ipPolicy.ApplyToWAF, 63 | &ipPolicy.ApplyToCC, 64 | &ipPolicy.CreateTime, 65 | &ipPolicy.Description, 66 | ) 67 | if err != nil { 68 | utils.DebugPrintln("GetIPPolicies rows.Scan", err) 69 | } 70 | ipPolicies = append(ipPolicies, ipPolicy) 71 | } 72 | return ipPolicies 73 | } 74 | -------------------------------------------------------------------------------- /data/backend_dns_record.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2023-06-04 16:00 5 | */ 6 | 7 | package data 8 | 9 | import ( 10 | "janusec/models" 11 | "janusec/utils" 12 | ) 13 | 14 | // CreateTableIfNotExistsDNSRecords ... 15 | func (dal *MyDAL) CreateTableIfNotExistsDNSRecords() error { 16 | const sqlCreateTableIfNotExistsDNSRecords = `CREATE TABLE IF NOT EXISTS "dns_records"("id" BIGINT PRIMARY KEY, "dns_domain_id" BIGINT, "rrtype" BIGINT, "name" VARCHAR(256) NOT NULL, "value" VARCHAR(256), "ttl" BIGINT, "auto" BOOLEAN DEFAULT FALSE, "internal" BOOLEAN DEFAULT FALSE)` 17 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsDNSRecords) 18 | return err 19 | } 20 | 21 | // SelectDNSRecordsByDomainID ... 22 | func (dal *MyDAL) SelectDNSRecordsByDomainID(dnsDomainID int64) []*models.DNSRecord { 23 | const sqlSelectDNSRecords = `SELECT "id","rrtype","name","value","ttl","auto","internal" FROM "dns_records" WHERE "dns_domain_id"=$1 ORDER BY "rrtype"` 24 | rows, err := dal.db.Query(sqlSelectDNSRecords, dnsDomainID) 25 | if err != nil { 26 | utils.DebugPrintln("SelectDNSRecords", err) 27 | } 28 | defer rows.Close() 29 | dnsRecords := []*models.DNSRecord{} 30 | for rows.Next() { 31 | dnsRecord := &models.DNSRecord{DNSDomainID: dnsDomainID} 32 | _ = rows.Scan(&dnsRecord.ID, &dnsRecord.Rrtype, &dnsRecord.Name, &dnsRecord.Value, &dnsRecord.TTL, &dnsRecord.Auto, &dnsRecord.Internal) 33 | dnsRecords = append(dnsRecords, dnsRecord) 34 | } 35 | return dnsRecords 36 | } 37 | 38 | func (dal *MyDAL) InsertDNSRecord(dnsRecord *models.DNSRecord) error { 39 | const sqlInsertDNSRecord = `INSERT INTO "dns_records"("id","dns_domain_id","rrtype","name","value","ttl","auto","internal") VALUES($1,$2,$3,$4,$5,$6,$7,$8)` 40 | _, err := dal.db.Exec(sqlInsertDNSRecord, dnsRecord.ID, dnsRecord.DNSDomainID, dnsRecord.Rrtype, dnsRecord.Name, dnsRecord.Value, dnsRecord.TTL, dnsRecord.Auto, dnsRecord.Internal) 41 | return err 42 | } 43 | 44 | func (dal *MyDAL) UpdateDNSRecord(dnsRecord *models.DNSRecord) error { 45 | const sqlUpdateDNSRecord = `UPDATE "dns_records" SET "rrtype"=$1,"name"=$2,"value"=$3,"ttl"=$4,"auto"=$5,"internal"=$6 WHERE "id"=$7` 46 | _, err := dal.db.Exec(sqlUpdateDNSRecord, dnsRecord.Rrtype, dnsRecord.Name, dnsRecord.Value, dnsRecord.TTL, dnsRecord.Auto, dnsRecord.Internal, dnsRecord.ID) 47 | return err 48 | } 49 | 50 | func (dal *MyDAL) DeleteDNSRecordByID(id int64) error { 51 | const sqlDelDNSRecord = `DELETE FROM "dns_records" WHERE "id"=$1` 52 | _, err := dal.db.Exec(sqlDelDNSRecord, id) 53 | return err 54 | } 55 | 56 | func (dal *MyDAL) SelectDNSRecordByID(id int64) (*models.DNSRecord, error) { 57 | dnsRecord := models.DNSRecord{ 58 | ID: id, 59 | } 60 | const sqlSelectDNSRecords = `SELECT "dns_domain_id","rrtype","name","value","ttl","auto","internal" FROM "dns_records" WHERE "id"=$1` 61 | err := dal.db.QueryRow(sqlSelectDNSRecords, id).Scan(&dnsRecord.DNSDomainID, &dnsRecord.Rrtype, &dnsRecord.Name, &dnsRecord.Value, &dnsRecord.TTL, &dnsRecord.Auto, &dnsRecord.Internal) 62 | return &dnsRecord, err 63 | } 64 | -------------------------------------------------------------------------------- /firewall/nftables.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-09-26 13:06:51 5 | * @Last Modified: U2, 2020-09-26 13:06:51 6 | */ 7 | 8 | package firewall 9 | 10 | import ( 11 | "janusec/utils" 12 | "net" 13 | "time" 14 | 15 | "github.com/google/nftables" 16 | "github.com/google/nftables/expr" 17 | ) 18 | 19 | var conn *nftables.Conn 20 | var table *nftables.Table 21 | var chain *nftables.Chain 22 | var set *nftables.Set 23 | 24 | // InitNFTables Create Table janusec, chain input 25 | // nft add table inet janusec 26 | // nft add chain inet janusec input { type filter hook input priority 0\; } 27 | // nft add set inet janusec blocklist {type ipv4_addr\; flags timeout\; } 28 | // nft add rule inet janusec input ip saddr @blocklist drop 29 | // nft add rule inet janusec input tcp dport { 80, 443 } accept 30 | func InitNFTables() { 31 | //fmt.Println("InitNFTables") 32 | conn = &nftables.Conn{} 33 | table = conn.AddTable(&nftables.Table{ 34 | Family: nftables.TableFamilyINet, 35 | Name: "janusec", 36 | }) 37 | 38 | chain = conn.AddChain(&nftables.Chain{ 39 | Name: "input", 40 | Table: table, 41 | Type: nftables.ChainTypeFilter, 42 | Hooknum: nftables.ChainHookInput, 43 | Priority: nftables.ChainPriorityFilter, 44 | }) 45 | set = &nftables.Set{ 46 | Table: table, 47 | Name: "blocklist", 48 | HasTimeout: true, 49 | KeyType: nftables.TypeIPAddr, 50 | } 51 | err := conn.AddSet(set, []nftables.SetElement{}) 52 | if err != nil { 53 | utils.DebugPrintln("InitNFTables AddSet error", err) 54 | return 55 | } 56 | rules, _ := conn.GetRules(table, chain) 57 | if len(rules) == 0 { 58 | conn.AddRule(&nftables.Rule{ 59 | Table: table, 60 | Chain: chain, 61 | Exprs: []expr.Any{ 62 | &expr.Payload{ 63 | DestRegister: 1, 64 | Base: expr.PayloadBaseNetworkHeader, 65 | Offset: 12, 66 | Len: 4, 67 | }, 68 | &expr.Lookup{ 69 | SourceRegister: 1, 70 | SetName: set.Name, 71 | SetID: set.ID, 72 | }, 73 | &expr.Verdict{Kind: expr.VerdictDrop}, 74 | }, 75 | }) 76 | } 77 | err = conn.Flush() 78 | if err != nil { 79 | utils.DebugPrintln("nftables init error", err) 80 | } 81 | } 82 | 83 | // AddIP2NFTables add Source IP Address to Nftables Block list 84 | // nft add element inet janusec blocklist { 192.168.100.1 timeout 300s } 85 | func AddIP2NFTables(ip string, blockSeconds float64) { 86 | //fmt.Println("AddIP2NFTables", ip) 87 | rules, _ := conn.GetRules(table, chain) 88 | if len(rules) == 0 { 89 | InitNFTables() 90 | } 91 | err := conn.SetAddElements(set, []nftables.SetElement{ 92 | {Key: []byte(net.ParseIP(ip).To4()), Timeout: time.Duration(blockSeconds) * time.Second}, 93 | }) 94 | if err != nil { 95 | utils.DebugPrintln("AddIP2NFTables SetAddElements error", err) 96 | } 97 | err = conn.Flush() 98 | if err != nil { 99 | utils.DebugPrintln("AddIP2NFTables flush error", err) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /data/backend_cookie_ref.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2023-06-03 12:18:07 5 | */ 6 | 7 | package data 8 | 9 | import ( 10 | "janusec/models" 11 | "janusec/utils" 12 | ) 13 | 14 | // CreateTableIfNotExistsCookieRefs ... 15 | func (dal *MyDAL) CreateTableIfNotExistsCookieRefs() error { 16 | const sqlCreateTableIfNotExistsCookieRefs = `CREATE TABLE IF NOT EXISTS "cookie_refs"("id" bigserial PRIMARY KEY, "name" VARCHAR(256) NOT NULL, "vendor" VARCHAR(256), "type" bigint, "description" VARCHAR(512), "operation" bigint)` 17 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsCookieRefs) 18 | return err 19 | } 20 | 21 | // SelectCookieRefs ... 22 | func (dal *MyDAL) SelectCookieRefs() []*models.CookieRef { 23 | const sqlSelectCookieRefs = `SELECT "id","name","vendor","type","description","operation" FROM "cookie_refs" ORDER BY "type"` 24 | rows, err := dal.db.Query(sqlSelectCookieRefs) 25 | if err != nil { 26 | utils.DebugPrintln("SelectCookieRefsByAppID", err) 27 | } 28 | defer rows.Close() 29 | cookie_refs := []*models.CookieRef{} 30 | for rows.Next() { 31 | cookieRef := &models.CookieRef{} 32 | _ = rows.Scan(&cookieRef.ID, &cookieRef.Name, &cookieRef.Vendor, &cookieRef.Type, &cookieRef.Description, &cookieRef.Operation) 33 | cookie_refs = append(cookie_refs, cookieRef) 34 | } 35 | return cookie_refs 36 | } 37 | 38 | func (dal *MyDAL) InsertCookieRef(cookieRef *models.CookieRef) error { 39 | const sqlInsertCookie = `INSERT INTO "cookie_refs"("id","name","vendor","type","description","operation") VALUES($1,$2,$3,$4,$5,$6)` 40 | _, err := dal.db.Exec(sqlInsertCookie, cookieRef.ID, cookieRef.Name, cookieRef.Vendor, cookieRef.Type, cookieRef.Description, cookieRef.Operation) 41 | return err 42 | } 43 | 44 | func (dal *MyDAL) UpdateCookieRef(cookieRef *models.CookieRef) error { 45 | const sqlUpdateCookie = `UPDATE "cookie_refs" SET "name"=$1,"vendor"=$2,"type"=$3,"description"=$4,"operation"=$5 WHERE "id"=$6` 46 | _, err := dal.db.Exec(sqlUpdateCookie, cookieRef.Name, cookieRef.Vendor, cookieRef.Type, cookieRef.Description, cookieRef.Operation, cookieRef.ID) 47 | return err 48 | } 49 | 50 | func (dal *MyDAL) DeleteCookieRefByID(id int64) error { 51 | const sqlDelCookie = `DELETE FROM "cookie_refs" WHERE "id"=$1` 52 | _, err := dal.db.Exec(sqlDelCookie, id) 53 | return err 54 | } 55 | 56 | func (dal *MyDAL) SelectCookieRefByID(id int64) (*models.CookieRef, error) { 57 | cookieRef := models.CookieRef{ 58 | ID: id, 59 | } 60 | const sqlSelectCookieRefs = `SELECT "name","vendor","type","description","operation" FROM "cookie_refs" WHERE "id"=$1` 61 | err := dal.db.QueryRow(sqlSelectCookieRefs, id).Scan(&cookieRef.Name, &cookieRef.Vendor, &cookieRef.Type, &cookieRef.Description, &cookieRef.Operation) 62 | return &cookieRef, err 63 | } 64 | 65 | func (dal *MyDAL) SelectCookieRefsCount() int64 { 66 | var count int64 67 | const sqlSelectCookieRefsCount = `SELECT COUNT(1) FROM "cookie_refs"` 68 | err := dal.db.QueryRow(sqlSelectCookieRefsCount).Scan(&count) 69 | if err != nil { 70 | utils.DebugPrintln("SelectCookieRefsCount QueryRow", err) 71 | } 72 | return count 73 | } 74 | -------------------------------------------------------------------------------- /data/gateway_discovery.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2023-03-11 19:23:07 5 | */ 6 | 7 | package data 8 | 9 | import ( 10 | "janusec/models" 11 | "janusec/utils" 12 | ) 13 | 14 | // CreateTableIfNotExistsDiscoveryRules create table discovery_rules 15 | func (dal *MyDAL) CreateTableIfNotExistsDiscoveryRules() error { 16 | const sqlCreateTableIfNotExistsDiscoveryRules = `CREATE TABLE IF NOT EXISTS "discovery_rules"("id" bigserial PRIMARY KEY, "field_name" VARCHAR(256) NOT NULL, "sample" VARCHAR(512) NOT NULL, "regex" VARCHAR(512) NOT NULL, "description" VARCHAR(512) NOT NULL, "editor" VARCHAR(256) NOT NULL, "update_time" bigint)` 17 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsDiscoveryRules) 18 | return err 19 | } 20 | 21 | func (dal *MyDAL) InsertDiscoveryRule(discoveryRule *models.DiscoveryRule) (newID int64, err error) { 22 | const sqlInsertDiscoveryRule = `INSERT INTO "discovery_rules"("id","field_name", "sample", "regex", "description", "editor", "update_time") VALUES($1,$2,$3,$4,$5,$6,$7) RETURNING "id"` 23 | snowID := utils.GenSnowflakeID() 24 | err = dal.db.QueryRow(sqlInsertDiscoveryRule, snowID, discoveryRule.FieldName, discoveryRule.Sample, discoveryRule.Regex, discoveryRule.Description, discoveryRule.Editor, discoveryRule.UpdateTime).Scan(&newID) 25 | if err != nil { 26 | utils.DebugPrintln("InsertDiscoveryRule", err) 27 | } 28 | return newID, err 29 | } 30 | 31 | func (dal *MyDAL) GetAllDiscoveryRules() ([]*models.DiscoveryRule, error) { 32 | const sqlSelectAll = `SELECT * FROM "discovery_rules"` 33 | rows, err := dal.db.Query(sqlSelectAll) 34 | if err != nil { 35 | utils.DebugPrintln("GetAllDiscoveryRules", err) 36 | return []*models.DiscoveryRule{}, err 37 | } 38 | defer rows.Close() 39 | var discoveryRules []*models.DiscoveryRule 40 | for rows.Next() { 41 | discoveryRule := &models.DiscoveryRule{} 42 | err = rows.Scan( 43 | &discoveryRule.ID, 44 | &discoveryRule.FieldName, 45 | &discoveryRule.Sample, 46 | &discoveryRule.Regex, 47 | &discoveryRule.Description, 48 | &discoveryRule.Editor, 49 | &discoveryRule.UpdateTime) 50 | if err != nil { 51 | utils.DebugPrintln("GetAllDiscoveryRules rows.Scan", err) 52 | } 53 | discoveryRules = append(discoveryRules, discoveryRule) 54 | } 55 | return discoveryRules, err 56 | } 57 | 58 | func (dal *MyDAL) UpdateDiscoveryRule(discoveryRule *models.DiscoveryRule) error { 59 | const sqlUpdateDiscoveryRule = `UPDATE "discovery_rules" SET "field_name"=$1, "sample"=$2, "regex"=$3, "description"=$4, "editor"=$5, "update_time"=$6 WHERE "id"=$7` 60 | _, err := dal.db.Exec(sqlUpdateDiscoveryRule, discoveryRule.FieldName, discoveryRule.Sample, discoveryRule.Regex, discoveryRule.Description, discoveryRule.Editor, discoveryRule.UpdateTime, discoveryRule.ID) 61 | if err != nil { 62 | utils.DebugPrintln("UpdateDiscoveryRule", err) 63 | } 64 | return err 65 | } 66 | 67 | // DeleteDiscoveryRuleByID ... 68 | func (dal *MyDAL) DeleteDiscoveryRuleByID(id int64) error { 69 | const sqlDeleteDiscoveryRuleByID = `DELETE FROM "discovery_rules" WHERE "id"=$1` 70 | _, err := dal.db.Exec(sqlDeleteDiscoveryRuleByID, id) 71 | if err != nil { 72 | utils.DebugPrintln("DeleteDiscoveryRuleByID", err) 73 | } 74 | return err 75 | } 76 | -------------------------------------------------------------------------------- /gateway/auth_ldap.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-05-16 15:03:30 5 | * @Last Modified: U2, 2020-05-16 15:03:30 6 | */ 7 | 8 | package gateway 9 | 10 | import ( 11 | "janusec/data" 12 | "net/http" 13 | "text/template" 14 | ) 15 | 16 | var ( 17 | ldapLoginTemplate = template.Must(template.New("ldap").Parse(ldapTemplate)) 18 | ) 19 | 20 | // LDAPContext LDAP Context 21 | type LDAPContext struct { 22 | DisplayName string 23 | State string 24 | AuthCodeEnabled bool 25 | } 26 | 27 | // ShowLDAPLoginUI Show LDAP Login UI 28 | func ShowLDAPLoginUI(w http.ResponseWriter, r *http.Request) { 29 | state := r.FormValue("state") 30 | ldapContext := LDAPContext{ 31 | DisplayName: data.NodeSetting.AuthConfig.LDAP.DisplayName, 32 | State: state, 33 | AuthCodeEnabled: data.NodeSetting.AuthConfig.LDAP.AuthenticatorEnabled} 34 | if err := ldapLoginTemplate.Execute(w, &ldapContext); err != nil { 35 | http.Error(w, err.Error(), http.StatusInternalServerError) 36 | } 37 | } 38 | 39 | const ldapTemplate = ` 40 | 41 | 42 | 43 | LDAP Authenticaiton 44 | 45 | 96 | 97 | 98 |

{{ .DisplayName }}

99 | 100 |
101 |
102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | {{ if .AuthCodeEnabled }} 111 | 112 | 113 | {{ end }} 114 | 115 |
116 | 中文 117 | English 118 |
119 | 122 | 123 | ` 124 | -------------------------------------------------------------------------------- /backend/dns_record.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2023-06-04 14:05 5 | */ 6 | 7 | package backend 8 | 9 | import ( 10 | "encoding/json" 11 | "errors" 12 | "janusec/data" 13 | "janusec/models" 14 | "janusec/utils" 15 | 16 | "github.com/miekg/dns" 17 | ) 18 | 19 | func GetDNSRecordsByDomainID(authUser *models.AuthUser, dnsDomainID int64) ([]*models.DNSRecord, error) { 20 | if authUser.IsSuperAdmin { 21 | dnsDomain, err := GetDNSDomainByID(dnsDomainID) 22 | if err == nil { 23 | return dnsDomain.DNSRecords, nil 24 | } 25 | } 26 | return []*models.DNSRecord{}, errors.New("not found or no privileges") 27 | } 28 | 29 | func UpdateDNSRecord(body []byte, clientIP string, authUser *models.AuthUser) (*models.DNSRecord, error) { 30 | var rpcDNSRecordRequest models.APIDNSRecordRequest 31 | if err := json.Unmarshal(body, &rpcDNSRecordRequest); err != nil { 32 | utils.DebugPrintln("UpdateDNSRecord Unmarshal", err) 33 | return nil, err 34 | } 35 | dnsRecord := rpcDNSRecordRequest.Object 36 | dnsDomain, err := GetDNSDomainByID(dnsRecord.DNSDomainID) 37 | if err != nil { 38 | return nil, err 39 | } 40 | if uint16(dnsRecord.Rrtype) == dns.TypeMX { 41 | // Modify Name for MX 42 | dnsRecord.Name = dnsDomain.Name + "." 43 | } 44 | if dnsRecord.ID == 0 { 45 | // new dnsRecord 46 | dnsRecord.ID = utils.GenSnowflakeID() 47 | err = data.DAL.InsertDNSRecord(dnsRecord) 48 | if err != nil { 49 | utils.DebugPrintln("InsertDNSRecord", err) 50 | } 51 | dnsDomain.DNSRecords = append(dnsDomain.DNSRecords, dnsRecord) 52 | go utils.OperationLog(clientIP, authUser.Username, "Add DNSRecord", dnsRecord.Name) 53 | } else { 54 | // update 55 | err := data.DAL.UpdateDNSRecord(dnsRecord) 56 | if err != nil { 57 | utils.DebugPrintln("UpdateDNSRecord", err) 58 | } 59 | // update dnsRecord pointer dnsRecords 60 | UpdateDNSRecords(dnsDomain, dnsRecord) 61 | go utils.OperationLog(clientIP, authUser.Username, "Update DNSRecord", dnsRecord.Name) 62 | } 63 | return dnsRecord, nil 64 | } 65 | 66 | func UpdateDNSRecords(dnsDomain *models.DNSDomain, dnsRecord *models.DNSRecord) { 67 | for i, obj := range dnsDomain.DNSRecords { 68 | if obj.ID == dnsRecord.ID { 69 | dnsDomain.DNSRecords[i] = dnsRecord 70 | } 71 | } 72 | } 73 | 74 | func DeleteDNSRecord(dnsRecordID int64, clientIP string, authUser *models.AuthUser) error { 75 | dnsRecord, err := data.DAL.SelectDNSRecordByID(dnsRecordID) 76 | if err != nil { 77 | return err 78 | } 79 | err = data.DAL.DeleteDNSRecordByID(dnsRecord.ID) 80 | if err != nil { 81 | utils.DebugPrintln("DeleteDNSRecord ", err) 82 | return err 83 | } 84 | dnsDomain, err := GetDNSDomainByID(dnsRecord.DNSDomainID) 85 | if err != nil { 86 | return err 87 | } 88 | err = DeleteDNSRecordFromDNSDomain(dnsDomain, dnsRecord) 89 | if err != nil { 90 | utils.DebugPrintln("DeleteDNSRecordFromDNSRecords", err) 91 | } 92 | go utils.OperationLog(clientIP, authUser.Username, "Delete DNSRecord", dnsRecord.Name) 93 | return nil 94 | } 95 | 96 | func DeleteDNSRecordFromDNSDomain(dnsDomain *models.DNSDomain, dnsRecordA *models.DNSRecord) error { 97 | for i, dnsRecord := range dnsDomain.DNSRecords { 98 | if dnsRecord.ID == dnsRecordA.ID { 99 | dnsDomain.DNSRecords = append(dnsDomain.DNSRecords[:i], dnsDomain.DNSRecords[i+1:]...) 100 | return nil 101 | } 102 | } 103 | return errors.New("dnsRecord not found") 104 | } 105 | -------------------------------------------------------------------------------- /data/backend_vip_target.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-11-01 09:14:07 5 | * @Last Modified: U2, 2020-11-01 09:14:07 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "janusec/models" 12 | "janusec/utils" 13 | ) 14 | 15 | // CreateTableIfNotExistsVipTargets create vip_targets 16 | func (dal *MyDAL) CreateTableIfNotExistsVipTargets() error { 17 | const sqlCreateTableIfNotExistsVipTargets = `CREATE TABLE IF NOT EXISTS "vip_targets"("id" bigserial PRIMARY KEY, "vip_app_id" bigint NOT NULL,"route_type" bigint default 1, "destination" VARCHAR(128) DEFAULT '',"pods_api" VARCHAR(512) DEFAULT '',"pod_port" VARCHAR(128) DEFAULT '',"pods" VARCHAR(1024) DEFAULT '')` 18 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsVipTargets) 19 | return err 20 | } 21 | 22 | // SelectVipTargetsByAppID ... 23 | func (dal *MyDAL) SelectVipTargetsByAppID(vipAppID int64) []*models.VipTarget { 24 | targets := []*models.VipTarget{} 25 | const sqlSelectVipTargetsByAppID = `SELECT "id","route_type","destination","pods_api","pod_port" FROM "vip_targets" WHERE "vip_app_id"=$1` 26 | rows, err := dal.db.Query(sqlSelectVipTargetsByAppID, vipAppID) 27 | if err != nil { 28 | utils.DebugPrintln("SelectVipTargetsByAppID", err) 29 | return targets 30 | } 31 | defer rows.Close() 32 | for rows.Next() { 33 | vipTarget := &models.VipTarget{VipAppID: vipAppID, Online: true} 34 | err = rows.Scan(&vipTarget.ID, &vipTarget.RouteType, &vipTarget.Destination, &vipTarget.PodsAPI, &vipTarget.PodPort) 35 | if err != nil { 36 | utils.DebugPrintln("SelectVipTargetsByAppID rows.Scan", err) 37 | } 38 | targets = append(targets, vipTarget) 39 | } 40 | return targets 41 | } 42 | 43 | // UpdateVipTarget ... update port forwarding target 44 | func (dal *MyDAL) UpdateVipTarget(vipAppID int64, routeType int64, destination string, podsAPI string, podPort string, id int64) error { 45 | const sqlUpdateTarget = `UPDATE "vip_targets" SET "vip_app_id"=$1,"route_type"=$2,"destination"=$3,"pods_api"=$4,"pod_port"=$5 WHERE "id"=$6` 46 | _, err := dal.db.Exec(sqlUpdateTarget, vipAppID, routeType, destination, podsAPI, podPort, id) 47 | if err != nil { 48 | utils.DebugPrintln("UpdateVipTarget", err) 49 | } 50 | return err 51 | } 52 | 53 | // InsertVipTarget create new VipTarget 54 | func (dal *MyDAL) InsertVipTarget(vipAppID int64, routeType int64, destination string, podsAPI string, podPort string) (newID int64, err error) { 55 | const sqlInsertTarget = `INSERT INTO "vip_targets"("id","vip_app_id", "route_type", "destination", "pods_api", "pod_port") VALUES($1,$2,$3,$4,$5,$6) RETURNING "id"` 56 | snowID := utils.GenSnowflakeID() 57 | err = dal.db.QueryRow(sqlInsertTarget, snowID, vipAppID, routeType, destination, podsAPI, podPort).Scan(&newID) 58 | if err != nil { 59 | utils.DebugPrintln("InsertVipTarget", err) 60 | } 61 | return newID, err 62 | } 63 | 64 | // DeleteVipTargetByID delete VipTarget by id 65 | func (dal *MyDAL) DeleteVipTargetByID(id int64) error { 66 | const sqlDeleteVipTargetByID = `DELETE FROM "vip_targets" WHERE "id"=$1` 67 | _, err := dal.db.Exec(sqlDeleteVipTargetByID, id) 68 | if err != nil { 69 | utils.DebugPrintln("DeleteDestinationByID", err) 70 | } 71 | return err 72 | } 73 | 74 | // DeleteVipTargetsByVipAppID delete all targets for one port forwarding app 75 | func (dal *MyDAL) DeleteVipTargetsByVipAppID(vipAppID int64) error { 76 | const sqlDeleteVipTargetsByVipAppID = `DELETE FROM "vip_targets" WHERE "vip_app_id"=$1` 77 | _, err := dal.db.Exec(sqlDeleteVipTargetsByVipAppID, vipAppID) 78 | if err != nil { 79 | utils.DebugPrintln("DeleteVipTargetsByVipAppID", err) 80 | } 81 | return err 82 | } 83 | -------------------------------------------------------------------------------- /data/backend_domain.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:24:42 5 | * @Last Modified: U2, 2018-07-14 16:24:42 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "janusec/models" 12 | "janusec/utils" 13 | ) 14 | 15 | const ( 16 | sqlCreateTableIfNotExistsDomains = `CREATE TABLE IF NOT EXISTS "domains"("id" bigserial PRIMARY KEY, "name" VARCHAR(256) NOT NULL, "app_id" bigint NOT NULL, "cert_id" bigint, "redirect" boolean, "location" VARCHAR(256))` 17 | sqlSelectDomainsCountByCertID = `SELECT COUNT(1) FROM "domains" WHERE "cert_id"=$1` 18 | sqlSelectDomains = `SELECT "id", "name", "app_id", "cert_id", "redirect", "location" FROM "domains"` 19 | sqlInsertDomain = `INSERT INTO "domains"("id","name", "app_id", "cert_id", "redirect", "location") VALUES($1,$2,$3,$4,$5,$6) RETURNING "id"` 20 | sqlUpdateDomain = `UPDATE "domains" SET "name"=$1,"app_id"=$2,"cert_id"=$3,"redirect"=$4,"location"=$5 WHERE "id"=$6` 21 | sqlDeleteDomainByDomainID = `DELETE FROM "domains" WHERE "id"=$1` 22 | sqlDeleteDomainByAppID = `DELETE FROM "domains" WHERE "app_id"=$1` 23 | ) 24 | 25 | // CreateTableIfNotExistsDomains ... 26 | func (dal *MyDAL) CreateTableIfNotExistsDomains() error { 27 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsDomains) 28 | return err 29 | } 30 | 31 | // SelectDomains ... 32 | func (dal *MyDAL) SelectDomains() []*models.DBDomain { 33 | rows, err := dal.db.Query(sqlSelectDomains) 34 | if err != nil { 35 | utils.DebugPrintln("SelectDomains", err) 36 | } 37 | defer rows.Close() 38 | dbDomains := []*models.DBDomain{} 39 | for rows.Next() { 40 | dbDomain := &models.DBDomain{} 41 | _ = rows.Scan(&dbDomain.ID, &dbDomain.Name, &dbDomain.AppID, &dbDomain.CertID, &dbDomain.Redirect, &dbDomain.Location) 42 | dbDomains = append(dbDomains, dbDomain) 43 | } 44 | return dbDomains 45 | } 46 | 47 | // SelectDomainsCountByCertID ... 48 | func (dal *MyDAL) SelectDomainsCountByCertID(certID int64) int64 { 49 | var certDomainsCount int64 50 | err := dal.db.QueryRow(sqlSelectDomainsCountByCertID, certID).Scan(&certDomainsCount) 51 | if err != nil { 52 | utils.DebugPrintln("SelectDomainsCountByCertID", err) 53 | } 54 | return certDomainsCount 55 | } 56 | 57 | // InsertDomain ... 58 | func (dal *MyDAL) InsertDomain(name string, appID int64, certID int64, redirect bool, location string) (newID int64) { 59 | id := utils.GenSnowflakeID() 60 | err := dal.db.QueryRow(sqlInsertDomain, id, name, appID, certID, redirect, location).Scan(&newID) 61 | if err != nil { 62 | utils.DebugPrintln("InsertDomain", err) 63 | } 64 | return newID 65 | } 66 | 67 | // UpdateDomain ... 68 | func (dal *MyDAL) UpdateDomain(name string, appID int64, certID int64, redirect bool, location string, domainID int64) error { 69 | _, err := dal.db.Exec(sqlUpdateDomain, name, appID, certID, redirect, location, domainID) 70 | if err != nil { 71 | utils.DebugPrintln("UpdateDomain", err) 72 | } 73 | return err 74 | } 75 | 76 | // DeleteDomainByDomainID ... 77 | func (dal *MyDAL) DeleteDomainByDomainID(domainID int64) error { 78 | stmt, _ := dal.db.Prepare(sqlDeleteDomainByDomainID) 79 | defer stmt.Close() 80 | _, err := stmt.Exec(domainID) 81 | if err != nil { 82 | utils.DebugPrintln("DeleteDomainByDomainID", err) 83 | } 84 | return err 85 | } 86 | 87 | // DeleteDomainByAppID ... 88 | func (dal *MyDAL) DeleteDomainByAppID(appID int64) error { 89 | stmt, _ := dal.db.Prepare(sqlDeleteDomainByAppID) 90 | defer stmt.Close() 91 | _, err := stmt.Exec(appID) 92 | if err != nil { 93 | utils.DebugPrintln("DeleteDomainByAppID", err) 94 | } 95 | return err 96 | } 97 | -------------------------------------------------------------------------------- /data/backend_certificate.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:24:23 5 | * @Last Modified: U2, 2018-07-14 16:24:23 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "crypto/x509" 12 | "encoding/pem" 13 | 14 | "janusec/models" 15 | "janusec/utils" 16 | ) 17 | 18 | const ( 19 | sqlCreateTableIfNotExistsCertificates = `CREATE TABLE IF NOT EXISTS "certificates"("id" bigserial primary key,"common_name" VARCHAR(256) not null,"pub_cert" VARCHAR(16384) not null,"priv_key" bytea not null,"expire_time" bigint,"description" VARCHAR(256))` 20 | sqlSelectCertificates = `SELECT "id","common_name","pub_cert","priv_key","expire_time","description" FROM "certificates"` 21 | sqlInsertCertificate = `INSERT INTO "certificates"("id","common_name","pub_cert","priv_key","expire_time","description") VALUES($1,$2,$3,$4,$5,$6) RETURNING "id"` 22 | sqlUpdateCertificate = `UPDATE "certificates" SET "common_name"=$1,"pub_cert"=$2,"priv_key"=$3,"expire_time"=$4,"description"=$5 WHERE "id"=$6` 23 | sqlDeleteCertificate = `DELETE FROM "certificates" WHERE "id"=$1` 24 | ) 25 | 26 | // CreateTableIfNotExistsCertificates ... 27 | func (dal *MyDAL) CreateTableIfNotExistsCertificates() error { 28 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsCertificates) 29 | return err 30 | } 31 | 32 | // SelectCertificates ... 33 | func (dal *MyDAL) SelectCertificates() []*models.DBCertItem { 34 | rows, err := dal.db.Query(sqlSelectCertificates) 35 | if err != nil { 36 | utils.DebugPrintln("SelectCertificates", err) 37 | } 38 | defer rows.Close() 39 | var dbCerts = []*models.DBCertItem{} 40 | for rows.Next() { 41 | dbCert := &models.DBCertItem{} 42 | _ = rows.Scan(&dbCert.ID, &dbCert.CommonName, 43 | &dbCert.CertContent, &dbCert.EncryptedPrivKey, 44 | &dbCert.ExpireTime, &dbCert.Description) 45 | dbCerts = append(dbCerts, dbCert) 46 | } 47 | return dbCerts 48 | } 49 | 50 | // InsertCertificate ... 51 | func (dal *MyDAL) InsertCertificate(commonName string, certContent string, encryptedPrivKey []byte, expireTime int64, description string) (newID int64) { 52 | id := utils.GenSnowflakeID() 53 | err := dal.db.QueryRow(sqlInsertCertificate, id, commonName, certContent, encryptedPrivKey, expireTime, description).Scan(&newID) 54 | if err != nil { 55 | utils.DebugPrintln("InsertCertificate", err) 56 | } 57 | return newID 58 | } 59 | 60 | // UpdateCertificate ... 61 | func (dal *MyDAL) UpdateCertificate(commonName string, certContent string, encryptedPrivKey []byte, expireTime int64, description string, id int64) error { 62 | stmt, _ := dal.db.Prepare(sqlUpdateCertificate) 63 | defer stmt.Close() 64 | _, err := stmt.Exec(commonName, certContent, encryptedPrivKey, expireTime, description, id) 65 | if err != nil { 66 | utils.DebugPrintln("UpdateCertificate", err) 67 | } 68 | return err 69 | } 70 | 71 | // DeleteCertificate by id 72 | func (dal *MyDAL) DeleteCertificate(certID int64) error { 73 | stmt, _ := dal.db.Prepare(sqlDeleteCertificate) 74 | defer stmt.Close() 75 | _, err := stmt.Exec(certID) 76 | if err != nil { 77 | utils.DebugPrintln("DeleteCertificate", err) 78 | } 79 | return err 80 | } 81 | 82 | // GetCertificateExpiryTime ... 83 | func GetCertificateExpiryTime(certPem string) int64 { 84 | block, _ := pem.Decode([]byte(certPem)) 85 | if block == nil { 86 | //fmt.Println("GetCertificateExpiryTime: failed to parse certificate PEM") 87 | return 0 88 | } 89 | cert, err := x509.ParseCertificate(block.Bytes) 90 | if err != nil { 91 | utils.DebugPrintln("GetCertificateExpiryTime", err) 92 | return 0 93 | } 94 | return cert.NotAfter.Unix() 95 | } 96 | -------------------------------------------------------------------------------- /data/firewall_check_item.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:25:43 5 | * @Last Modified: U2, 2018-07-14 16:25:43 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "janusec/models" 12 | "janusec/utils" 13 | ) 14 | 15 | const ( 16 | sqlCreateTableIfNotExistCheckItems = `CREATE TABLE IF NOT EXISTS "check_items"("id" bigserial primary key,"check_point" bigint,"operation" bigint,"key_name" VARCHAR(256) NOT NULL DEFAULT '',"regex_policy" VARCHAR(512) NOT NULL,"group_policy_id" bigint)` 17 | sqlInsertCheckItem = `INSERT INTO "check_items"("id","check_point","operation","key_name","regex_policy","group_policy_id") VALUES($1,$2,$3,$4,$5,$6) RETURNING "id"` 18 | sqlSelectCheckItemsByGroupID = `SELECT "id","check_point","operation","key_name","regex_policy" FROM "check_items" WHERE "group_policy_id"=$1` 19 | sqlDeleteCheckItemByID = `DELETE FROM "check_items" WHERE "id"=$1` 20 | sqlUpdateCheckItemByID = `UPDATE "check_items" SET "check_point"=$1,"operation"=$2,"key_name"=$3,"regex_policy"=$4,"group_policy_id"=$5 WHERE "id"=$6` 21 | ) 22 | 23 | // CreateTableIfNotExistCheckItems ... 24 | func (dal *MyDAL) CreateTableIfNotExistCheckItems() error { 25 | _, err := dal.db.Exec(sqlCreateTableIfNotExistCheckItems) 26 | if err != nil { 27 | utils.DebugPrintln("CreateTableIfNotExistCheckItems", err) 28 | } 29 | return err 30 | } 31 | 32 | // InsertCheckItem ... 33 | func (dal *MyDAL) InsertCheckItem(checkPoint models.ChkPoint, operation models.Operation, keyName string, regexPolicy string, groupPolicyID int64) (newID int64, err error) { 34 | stmt, err := dal.db.Prepare(sqlInsertCheckItem) 35 | if err != nil { 36 | utils.DebugPrintln("sqlInsertCheckItem Prepare", err) 37 | } 38 | defer stmt.Close() 39 | snowID := utils.GenSnowflakeID() 40 | err = stmt.QueryRow(snowID, checkPoint, operation, keyName, regexPolicy, groupPolicyID).Scan(&newID) 41 | if err != nil { 42 | utils.DebugPrintln("sqlInsertCheckItem Scan", err) 43 | } 44 | return newID, err 45 | } 46 | 47 | // SelectCheckItemsByGroupID ... 48 | func (dal *MyDAL) SelectCheckItemsByGroupID(groupPolicyID int64) ([]*models.DBCheckItem, error) { 49 | checkItems := []*models.DBCheckItem{} 50 | rows, err := dal.db.Query(sqlSelectCheckItemsByGroupID, groupPolicyID) 51 | if err != nil { 52 | utils.DebugPrintln("SelectCheckItemsByGroupID", err) 53 | return nil, err 54 | } 55 | defer rows.Close() 56 | for rows.Next() { 57 | checkItem := &models.DBCheckItem{} 58 | err = rows.Scan(&checkItem.ID, &checkItem.CheckPoint, &checkItem.Operation, &checkItem.KeyName, &checkItem.RegexPolicy) 59 | if err != nil { 60 | utils.DebugPrintln("SelectCheckItemsByGroupID Scan", err) 61 | } 62 | checkItems = append(checkItems, checkItem) 63 | } 64 | return checkItems, nil 65 | } 66 | 67 | // DeleteCheckItemByID ... 68 | func (dal *MyDAL) DeleteCheckItemByID(id int64) error { 69 | _, err := dal.db.Exec(sqlDeleteCheckItemByID, id) 70 | if err != nil { 71 | utils.DebugPrintln("DeleteCheckItemByID", err) 72 | } 73 | return err 74 | } 75 | 76 | // UpdateCheckItemByID ... 77 | func (dal *MyDAL) UpdateCheckItemByID(checkPoint models.ChkPoint, operation models.Operation, keyName string, regexPolicy string, groupPolicyID int64, checkItemID int64) error { 78 | stmt, err := dal.db.Prepare(sqlUpdateCheckItemByID) 79 | if err != nil { 80 | utils.DebugPrintln("UpdateCheckItemByID Prepare", err) 81 | } 82 | defer stmt.Close() 83 | _, err = stmt.Exec(checkPoint, operation, keyName, regexPolicy, groupPolicyID, checkItemID) 84 | if err != nil { 85 | utils.DebugPrintln("UpdateCheckItemByID Exec", err) 86 | } 87 | return err 88 | } 89 | -------------------------------------------------------------------------------- /backend/dns_domain.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2023-06-04 18:52 5 | */ 6 | 7 | package backend 8 | 9 | import ( 10 | "encoding/json" 11 | "errors" 12 | "janusec/data" 13 | "janusec/models" 14 | "janusec/utils" 15 | ) 16 | 17 | var ( 18 | dnsDomains = []*models.DNSDomain{} 19 | ) 20 | 21 | func LoadDNSDomains() { 22 | dnsDomains = dnsDomains[0:0] 23 | dnsDomains = data.DAL.SelectDNSDomains() 24 | for _, dnsDomain := range dnsDomains { 25 | dnsDomain.DNSRecords = data.DAL.SelectDNSRecordsByDomainID(dnsDomain.ID) 26 | } 27 | } 28 | 29 | func GetDNSDomains(authUser *models.AuthUser) ([]*models.DNSDomain, error) { 30 | if authUser.IsSuperAdmin { 31 | return dnsDomains, nil 32 | } 33 | return nil, errors.New("no privileges") 34 | } 35 | 36 | func GetDNSDomainByID(dnsDomainID int64) (*models.DNSDomain, error) { 37 | for _, dnsDomain := range dnsDomains { 38 | if dnsDomain.ID == dnsDomainID { 39 | return dnsDomain, nil 40 | } 41 | } 42 | return nil, errors.New("not found") 43 | } 44 | 45 | func GetDNSDomainByName(domainName string) (*models.DNSDomain, error) { 46 | for _, dnsDomain := range dnsDomains { 47 | if dnsDomain.Name == domainName { 48 | return dnsDomain, nil 49 | } 50 | } 51 | return nil, errors.New("not found") 52 | } 53 | 54 | func UpdateDNSDomain(body []byte, clientIP string, authUser *models.AuthUser) (*models.DNSDomain, error) { 55 | var rpcDNSDomainRequest models.APIDNSDomainRequest 56 | if err := json.Unmarshal(body, &rpcDNSDomainRequest); err != nil { 57 | utils.DebugPrintln("UpdateDNSDomain", err) 58 | return nil, err 59 | } 60 | dnsDomain := rpcDNSDomainRequest.Object 61 | if dnsDomain.ID == 0 { 62 | // new dnsDomain 63 | dnsDomain.ID = utils.GenSnowflakeID() 64 | err := data.DAL.InsertDNSDomain(dnsDomain) 65 | if err != nil { 66 | utils.DebugPrintln("InsertDNSDomain", err) 67 | return nil, err 68 | } 69 | dnsDomains = append(dnsDomains, dnsDomain) 70 | go utils.OperationLog(clientIP, authUser.Username, "Add DNSDomain", dnsDomain.Name) 71 | } else { 72 | // update 73 | err := data.DAL.UpdateDNSDomain(dnsDomain) 74 | if err != nil { 75 | utils.DebugPrintln("UpdateDNSDomain", err) 76 | } 77 | UpdateDNSDomains(dnsDomain) 78 | go utils.OperationLog(clientIP, authUser.Username, "Update DNSDomain", dnsDomain.Name) 79 | } 80 | return dnsDomain, nil 81 | } 82 | 83 | func UpdateDNSDomains(dnsDomain *models.DNSDomain) { 84 | for i, obj := range dnsDomains { 85 | if obj.ID == dnsDomain.ID { 86 | dnsDomains[i] = dnsDomain 87 | } 88 | } 89 | } 90 | 91 | func DeleteDNSDomain(dnsDomainID int64, clientIP string, authUser *models.AuthUser) error { 92 | if !authUser.IsSuperAdmin { 93 | return errors.New("no privileges") 94 | } 95 | dnsDomain, err := GetDNSDomainByID(dnsDomainID) 96 | if err != nil { 97 | return err 98 | } 99 | if len(dnsDomain.DNSRecords) > 0 { 100 | return errors.New("there exists resource records for this domain name, can not be deleted") 101 | } 102 | err = data.DAL.DeleteDNSDomainByID(dnsDomain.ID) 103 | if err != nil { 104 | utils.DebugPrintln("DeleteDNSDomain ", err) 105 | return err 106 | } 107 | err = DeleteFromDNSDomains(dnsDomain) 108 | if err != nil { 109 | utils.DebugPrintln("DeleteDNSRecordFromDNSRecords", err) 110 | } 111 | go utils.OperationLog(clientIP, authUser.Username, "Delete DNSDomain", dnsDomain.Name) 112 | return nil 113 | } 114 | 115 | func DeleteFromDNSDomains(dnsDomain *models.DNSDomain) error { 116 | for i, obj := range dnsDomains { 117 | if obj.ID == dnsDomain.ID { 118 | dnsDomains = append(dnsDomains[:i], dnsDomains[i+1:]...) 119 | return nil 120 | } 121 | } 122 | return errors.New("dnsRecord not found") 123 | } 124 | -------------------------------------------------------------------------------- /models/rpc_node.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:39:23 5 | * @Last Modified: U2, 2018-07-14 16:39:23 6 | */ 7 | 8 | package models 9 | 10 | type RPCResponse struct { 11 | Error *string `json:"err"` 12 | Object interface{} `json:"object"` 13 | } 14 | 15 | type RPCRequest struct { 16 | Action string `json:"action"` 17 | ObjectID int64 `json:"id,string"` 18 | NodeVersion string `json:"node_version"` 19 | AuthKey string `json:"auth_key"` 20 | Object interface{} `json:"object"` 21 | 22 | // PublicIP used for dns load balance, added v1.4.1 23 | PublicIP string `json:"public_ip"` 24 | } 25 | 26 | type RPCGroupHitLogRequest struct { 27 | Action string `json:"action"` 28 | ObjectID int64 `json:"id,string"` 29 | NodeID int64 `json:"node_id,string"` 30 | AuthKey string `json:"auth_key"` 31 | Object *GroupHitLog `json:"object"` 32 | } 33 | 34 | type RPCCCLogRequest struct { 35 | Action string `json:"action"` 36 | ObjectID int64 `json:"id,string"` 37 | NodeID int64 `json:"node_id,string"` 38 | AuthKey string `json:"auth_key"` 39 | Object *CCLog `json:"object"` 40 | } 41 | 42 | type RPCCertItems struct { 43 | Error *string `json:"err"` 44 | Object []*CertItem `json:"object"` 45 | } 46 | 47 | type RPCApplications struct { 48 | Error *string `json:"err"` 49 | Object []*Application `json:"object"` 50 | } 51 | 52 | type RPCVipApps struct { 53 | Error *string `json:"err"` 54 | Object []*VipApp `json:"object"` 55 | } 56 | 57 | type RPCDBDomains struct { 58 | Error *string `json:"err"` 59 | Object []*DBDomain `json:"object"` 60 | } 61 | 62 | type RPCCCPolicies struct { 63 | Error *string `json:"err"` 64 | Object []*CCPolicy `json:"object"` 65 | } 66 | 67 | type RPCGroupPolicies struct { 68 | Error *string `json:"err"` 69 | Object []*GroupPolicy `json:"object"` 70 | } 71 | 72 | type RPCVulntypes struct { 73 | Error *string `json:"err"` 74 | Object []*VulnType `json:"object"` 75 | } 76 | 77 | /* 78 | type RPCSettings struct { 79 | Error *string `json:"err"` 80 | Object []*Setting `json:"object"` 81 | } 82 | */ 83 | 84 | type RPCOAuthConfig struct { 85 | Error *string `json:"err"` 86 | Object *OAuthConfig `json:"object"` 87 | } 88 | 89 | type RPCTOTP struct { 90 | Error *string `json:"err"` 91 | Object *TOTP `json:"object"` 92 | } 93 | 94 | type RPCStatRequest struct { 95 | Action string `json:"action"` 96 | ObjectID int64 `json:"id,string"` 97 | NodeID int64 `json:"node_id,string"` 98 | AuthKey string `json:"auth_key"` 99 | Object []*AccessStat `json:"object"` 100 | } 101 | 102 | type RPCRefererRequest struct { 103 | Action string `json:"action"` 104 | ObjectID int64 `json:"id,string"` 105 | NodeID int64 `json:"node_id,string"` 106 | AuthKey string `json:"auth_key"` 107 | Object *map[int64]map[string]map[string]map[string]int64 `json:"object"` 108 | } 109 | 110 | type RPCNodeSetting struct { 111 | Error *string `json:"err"` 112 | Object *NodeShareSetting `json:"object"` 113 | } 114 | 115 | // PrimarySettingRequest for update NodeSetting 116 | type PrimarySettingRequest struct { 117 | Action string `json:"action"` 118 | Object *PrimarySetting `json:"object"` 119 | } 120 | 121 | type RPCDiscoveryRules struct { 122 | Error *string `json:"err"` 123 | Object []*DiscoveryRule `json:"object"` 124 | } 125 | 126 | type RPCCookieRefs struct { 127 | Error *string `json:"err"` 128 | Object []*CookieRef `json:"object"` 129 | } 130 | -------------------------------------------------------------------------------- /backend/vip_target.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-11-04 22:56:54 5 | * @Last Modified: U2, 2020-10-30 22:56:54 6 | */ 7 | 8 | package backend 9 | 10 | import ( 11 | "janusec/data" 12 | "janusec/models" 13 | "janusec/utils" 14 | "net" 15 | "time" 16 | 17 | "github.com/patrickmn/go-cache" 18 | ) 19 | 20 | // DeleteVipTargetsByAppID delete backend targets for port forwarding 21 | func DeleteVipTargetsByAppID(id int64) { 22 | err := data.DAL.DeleteVipTargetsByVipAppID(id) 23 | if err != nil { 24 | utils.DebugPrintln("DeleteVipTargetsByAppID", err) 25 | } 26 | } 27 | 28 | // CheckOfflineVipTargets check offline targets and reset the online status 29 | func CheckOfflineVipTargets(nowTimeStamp int64) { 30 | for _, vipApp := range VipApps { 31 | for _, target := range vipApp.Targets { 32 | if !target.Online { 33 | go func(vApp *models.VipApp, vTarget *models.VipTarget) { 34 | var conn net.Conn 35 | var err error 36 | if vApp.IsTCP { 37 | conn, err = net.DialTimeout("tcp", vTarget.Destination, time.Second) 38 | if err == nil { 39 | defer conn.Close() 40 | vTarget.Online = true 41 | vTarget.CheckTime = nowTimeStamp 42 | } 43 | } else { 44 | targetAddr, _ := net.ResolveUDPAddr("udp", vTarget.Destination) 45 | udpTargetConn, err := net.DialUDP("udp", nil, targetAddr) 46 | if err != nil { 47 | SetVipTargetOffline(vTarget) 48 | return 49 | } 50 | // udpTargetConn will be closed in go thread 51 | udpTargetConn.SetDeadline(time.Now().Add(10 * time.Second)) 52 | go func(udpConn *net.UDPConn, vipTarget *models.VipTarget) { 53 | data := make([]byte, 2048) 54 | _, _, err := udpConn.ReadFromUDP(data) 55 | if err != nil { 56 | SetVipTargetOffline(vipTarget) 57 | } else { 58 | vipTarget.Online = true 59 | } 60 | udpConn.Close() 61 | }(udpTargetConn, vTarget) 62 | 63 | // send test data to target 64 | _, err = udpTargetConn.Write([]byte("Hi")) 65 | if err != nil { 66 | SetVipTargetOffline(vTarget) 67 | return 68 | } 69 | } 70 | }(vipApp, target) 71 | } 72 | } 73 | } 74 | } 75 | 76 | func ContainsTargetID(targets []*models.VipTarget, targetID int64) bool { 77 | for _, target := range targets { 78 | if target.ID == targetID { 79 | return true 80 | } 81 | } 82 | return false 83 | } 84 | 85 | func SetVipTargetOffline(dest *models.VipTarget) { 86 | target := dest.Destination 87 | if dest.RouteType == models.K8S_Ingress { 88 | target = dest.PodsAPI 89 | } 90 | if count, ok := offlineCache.Get(target); !ok { 91 | offlineCache.Set(target, int64(1), cache.DefaultExpiration) 92 | } else { 93 | nowCount := count.(int64) + int64(1) 94 | if nowCount > 5 { 95 | // more than 5 requests timeout 96 | dest.Online = false 97 | app, err := GetVipAppByID(dest.VipAppID) 98 | if err == nil { 99 | sendVIPOfflineNotification(app, target) 100 | } 101 | } 102 | offlineCache.Set(target, nowCount, cache.DefaultExpiration) 103 | } 104 | } 105 | 106 | // sendVIPOfflineNotification ... 107 | func sendVIPOfflineNotification(app *models.VipApp, dest string) { 108 | var emails string 109 | if data.IsPrimary { 110 | emails = data.DAL.GetAppAdminAndOwnerEmails(app.Owner) 111 | } else { 112 | emails = data.NodeSetting.SMTP.AdminEmails 113 | } 114 | mailBody := "Backend virtual IP: " + dest + " (" + app.Name + ") was offline." 115 | if len(mailBody) > 0 && len(emails) > 0 { 116 | go utils.SendEmail(data.NodeSetting.SMTP.SMTPServer, 117 | data.NodeSetting.SMTP.SMTPPort, 118 | data.NodeSetting.SMTP.SMTPAccount, 119 | data.NodeSetting.SMTP.SMTPPassword, 120 | emails, 121 | "[JANUSEC] Backend server offline notification", 122 | mailBody) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /backend/k8s.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2023-01-23 10:21:54 5 | */ 6 | 7 | package backend 8 | 9 | import ( 10 | "encoding/json" 11 | "hash/fnv" 12 | "janusec/models" 13 | "janusec/utils" 14 | "net/http" 15 | "strings" 16 | "sync" 17 | "time" 18 | ) 19 | 20 | func UpdatePods(dest *models.Destination, nowTimeStamp int64) { 21 | dest.IsUpdating = true 22 | dest.Mutex.Lock() // write lock 23 | defer dest.Mutex.Unlock() 24 | request, _ := http.NewRequest("GET", dest.PodsAPI, nil) 25 | request.Header.Set("Content-Type", "application/json") 26 | resp, err := utils.GetResponse(request) 27 | if err != nil { 28 | utils.DebugPrintln("Check K8S API GetResponse", err) 29 | dest.CheckTime = nowTimeStamp 30 | SetDestinationOffline(dest) 31 | } 32 | pods := models.PODS{} 33 | err = json.Unmarshal(resp, &pods) 34 | if err != nil { 35 | utils.DebugPrintln("Unmarshal K8S API", err) 36 | } 37 | dest.Pods = "" 38 | for _, podItem := range pods.Items { 39 | if podItem.Status.Phase == "Running" { 40 | if len(dest.Pods) > 0 { 41 | dest.Pods += "|" 42 | } 43 | dest.Pods += podItem.Status.PodIP + ":" + dest.PodPort 44 | } 45 | } 46 | dest.IsUpdating = false 47 | } 48 | 49 | func SelectPodFromDestination(dest *models.Destination, srcIP string, r *http.Request) string { 50 | nowTimeStamp := time.Now().Unix() 51 | var isEmptyPods bool 52 | if len(dest.Pods) == 0 { 53 | isEmptyPods = true 54 | } else { 55 | isEmptyPods = false 56 | } 57 | wg := new(sync.WaitGroup) 58 | if !dest.IsUpdating && (isEmptyPods || (nowTimeStamp-dest.CheckTime) > 60) { 59 | if isEmptyPods { 60 | wg.Add(1) 61 | } 62 | // check k8s api if exceed 60 seconds 63 | go func(dest *models.Destination, nowTimeStamp int64, wg *sync.WaitGroup) { 64 | UpdatePods(dest, nowTimeStamp) 65 | if isEmptyPods { 66 | wg.Done() 67 | } 68 | }(dest, nowTimeStamp, wg) 69 | } 70 | if isEmptyPods { 71 | wg.Wait() 72 | } 73 | dest.Mutex.RLock() 74 | // select target pod from dest.Pods directly 75 | dests := strings.Split(dest.Pods, "|") 76 | // According to Hash(IP+UA) 77 | h := fnv.New32a() 78 | _, err := h.Write([]byte(srcIP + r.UserAgent())) 79 | if err != nil { 80 | utils.DebugPrintln("SelectPodFromDestination h.Write", err) 81 | } 82 | hashUInt32 := h.Sum32() 83 | destIndex := hashUInt32 % uint32(len(dests)) 84 | dest.Mutex.RUnlock() 85 | return dests[destIndex] 86 | } 87 | 88 | // SelectPodFromVIPTarget get pod for Layer-4 forward 89 | func SelectPodFromVIPTarget(dest *models.VipTarget, srcIP string) string { 90 | nowTimeStamp := time.Now().Unix() 91 | if len(dest.Pods) == 0 || (nowTimeStamp-dest.CheckTime) > 60 { 92 | // check k8s api 93 | request, _ := http.NewRequest("GET", dest.PodsAPI, nil) 94 | request.Header.Set("Content-Type", "application/json") 95 | resp, err := utils.GetResponse(request) 96 | if err != nil { 97 | utils.DebugPrintln("Check K8S API GetResponse", err) 98 | dest.CheckTime = nowTimeStamp 99 | SetVipTargetOffline(dest) 100 | } 101 | pods := models.PODS{} 102 | err = json.Unmarshal(resp, &pods) 103 | if err != nil { 104 | utils.DebugPrintln("Unmarshal K8S API", err) 105 | } 106 | dest.Pods = "" 107 | for _, podItem := range pods.Items { 108 | if podItem.Status.Phase == "Running" { 109 | if len(dest.Pods) > 0 { 110 | dest.Pods += "|" 111 | } 112 | dest.Pods += podItem.Status.PodIP + ":" + dest.PodPort 113 | } 114 | } 115 | } 116 | // select target pod from dest.Pods directly 117 | dests := strings.Split(dest.Pods, "|") 118 | // According to Hash(IP+UA) 119 | h := fnv.New32a() 120 | _, err := h.Write([]byte(srcIP)) 121 | if err != nil { 122 | utils.DebugPrintln("SelectPodFromVIPTarget h.Write", err) 123 | } 124 | hashUInt32 := h.Sum32() 125 | destIndex := hashUInt32 % uint32(len(dests)) 126 | return dests[destIndex] 127 | } 128 | -------------------------------------------------------------------------------- /backend/domain.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:22:10 5 | * @Last Modified: U2, 2018-07-14 16:22:10 6 | */ 7 | 8 | package backend 9 | 10 | import ( 11 | "strings" 12 | "sync" 13 | 14 | "janusec/data" 15 | "janusec/models" 16 | "janusec/utils" 17 | ) 18 | 19 | var ( 20 | // Domains for all domains 21 | Domains = []*models.Domain{} 22 | 23 | // DomainsMap (string, models.DomainRelation) 24 | DomainsMap = sync.Map{} 25 | ) 26 | 27 | // LoadDomains ... 28 | func LoadDomains() { 29 | Domains = Domains[0:0] 30 | DomainsMap.Range(func(key, value interface{}) bool { 31 | DomainsMap.Delete(key) 32 | return true 33 | }) 34 | var dbDomains []*models.DBDomain 35 | if data.IsPrimary { 36 | dbDomains = data.DAL.SelectDomains() 37 | } else { 38 | dbDomains = RPCSelectDomains() 39 | } 40 | for _, dbDomain := range dbDomains { 41 | pApp, _ := GetApplicationByID(dbDomain.AppID) 42 | pCert, _ := SysCallGetCertByID(dbDomain.CertID) 43 | domain := &models.Domain{ 44 | ID: dbDomain.ID, 45 | Name: dbDomain.Name, 46 | AppID: dbDomain.AppID, 47 | CertID: dbDomain.CertID, 48 | Redirect: dbDomain.Redirect, 49 | Location: dbDomain.Location, 50 | App: pApp, 51 | Cert: pCert} 52 | Domains = append(Domains, domain) 53 | DomainsMap.Store(domain.Name, models.DomainRelation{App: pApp, Cert: pCert, Redirect: dbDomain.Redirect, Location: dbDomain.Location}) 54 | } 55 | } 56 | 57 | // GetDomainByID ... 58 | func GetDomainByID(id int64) *models.Domain { 59 | for _, domain := range Domains { 60 | if domain.ID == id { 61 | return domain 62 | } 63 | } 64 | return nil 65 | } 66 | 67 | // GetDomainByName ... 68 | func GetDomainByName(domainName string) *models.Domain { 69 | for _, domain := range Domains { 70 | if domain.Name == domainName { 71 | return domain 72 | } 73 | } 74 | return nil 75 | } 76 | 77 | // UpdateDomain ... 78 | func UpdateDomain(app *models.Application, newDomain *models.Domain) *models.Domain { 79 | // First convert domain name to lowercase 80 | newDomain.Name = strings.ToLower(strings.TrimSpace(newDomain.Name)) 81 | if newDomain.ID == 0 { 82 | // New domain 83 | newDomain.ID = data.DAL.InsertDomain(newDomain.Name, app.ID, newDomain.CertID, newDomain.Redirect, newDomain.Location) 84 | Domains = append(Domains, newDomain) 85 | } else { 86 | oldDomain := GetDomainByID(newDomain.ID) 87 | err := data.DAL.UpdateDomain(newDomain.Name, app.ID, newDomain.CertID, newDomain.Redirect, newDomain.Location, oldDomain.ID) 88 | if err != nil { 89 | utils.DebugPrintln("UpdateDomain", err) 90 | } 91 | oldDomain = newDomain 92 | } 93 | newDomain.AppID = app.ID 94 | newDomain.App = app 95 | pCert, _ := SysCallGetCertByID(newDomain.CertID) 96 | newDomain.Cert = pCert 97 | DomainsMap.Store(newDomain.Name, models.DomainRelation{App: app, Cert: pCert, Redirect: newDomain.Redirect, Location: newDomain.Location}) 98 | return newDomain 99 | } 100 | 101 | // GetDomainIndex ... 102 | func GetDomainIndex(domain *models.Domain) int { 103 | for i := 0; i < len(Domains); i++ { 104 | if Domains[i].ID == domain.ID { 105 | return i 106 | } 107 | } 108 | return -1 109 | } 110 | 111 | // DeleteDomain ... 112 | func DeleteDomain(domain *models.Domain) { 113 | i := GetDomainIndex(domain) 114 | Domains = append(Domains[:i], Domains[i+1:]...) 115 | } 116 | 117 | // DeleteDomainsByApp ... 118 | func DeleteDomainsByApp(app *models.Application) { 119 | for _, domain := range app.Domains { 120 | DeleteDomain(domain) 121 | DomainsMap.Delete(domain.Name) 122 | } 123 | err := data.DAL.DeleteDomainByAppID(app.ID) 124 | if err != nil { 125 | utils.DebugPrintln("DeleteDomainsByAppID", err) 126 | } 127 | } 128 | 129 | // ContainsDomainID ... 130 | func ContainsDomainID(domains []*models.Domain, domainID int64) bool { 131 | for _, domain := range domains { 132 | if domain.ID == domainID { 133 | return true 134 | } 135 | } 136 | return false 137 | } 138 | -------------------------------------------------------------------------------- /models/data_config.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:38:49 5 | * @Last Modified: U2, 2018-07-14 16:38:49 6 | */ 7 | 8 | package models 9 | 10 | // Config is the format of config.json 11 | type Config struct { 12 | NodeRole string `json:"node_role"` 13 | ListenHTTP string `json:"listen_http"` 14 | ListenHTTPS string `json:"listen_https"` 15 | PrimaryNode PrimaryNodeConfig `json:"primary_node"` 16 | ReplicaNode ReplicaNodeConfig `json:"replica_node"` 17 | } 18 | 19 | type OAuthConfig struct { 20 | Enabled bool `json:"enabled"` 21 | Provider string `json:"provider"` 22 | Wxwork *WxworkConfig `json:"wxwork"` 23 | Dingtalk *DingtalkConfig `json:"dingtalk"` 24 | Feishu *FeishuConfig `json:"feishu"` 25 | Lark *LarkConfig `json:"lark"` 26 | LDAP *LDAPConfig `json:"ldap"` 27 | CAS2 *CAS2Config `json:"cas2"` 28 | } 29 | 30 | type PrimaryNodeConfig struct { 31 | Admin AdminConfig `json:"admin"` 32 | DatabaseType string `json:"database_type"` 33 | Database DBConfig `json:"database"` 34 | } 35 | 36 | type ReplicaNodeConfig struct { 37 | NodeKey string `json:"node_key"` 38 | SyncAddr string `json:"sync_addr"` 39 | } 40 | 41 | type AdminConfig struct { 42 | Listen bool `json:"listen"` 43 | ListenHTTP string `json:"listen_http"` 44 | ListenHTTPS string `json:"listen_https"` 45 | Portal string `json:"portal"` 46 | WebSSHEnabled bool `json:"webssh_enabled"` 47 | } 48 | 49 | type DBConfig struct { 50 | Host string `json:"host"` 51 | Port string `json:"port"` 52 | User string `json:"user"` 53 | Password string `json:"password"` 54 | DBName string `json:"dbname"` 55 | } 56 | 57 | type EncryptedConfig struct { 58 | NodeRole string `json:"node_role"` 59 | ListenHTTP string `json:"listen_http"` 60 | ListenHTTPS string `json:"listen_https"` 61 | PrimaryNode PrimaryNodeConfig `json:"primary_node"` 62 | ReplicaNode ReplicaNodeConfig `json:"replica_node"` 63 | } 64 | 65 | type WxworkConfig struct { 66 | DisplayName string `json:"display_name"` 67 | Callback string `json:"callback"` 68 | CorpID string `json:"corpid"` 69 | AgentID string `json:"agentid"` 70 | CorpSecret string `json:"corpsecret"` 71 | } 72 | 73 | type DingtalkConfig struct { 74 | DisplayName string `json:"display_name"` 75 | Callback string `json:"callback"` 76 | AppID string `json:"appid"` 77 | AppSecret string `json:"appsecret"` 78 | 79 | // CorpID for API v2, added on Mar 23, 2024, v1.5.0 80 | CorpID string `json:"corpid"` 81 | } 82 | 83 | type FeishuConfig struct { 84 | DisplayName string `json:"display_name"` 85 | Callback string `json:"callback"` 86 | AppID string `json:"appid"` 87 | AppSecret string `json:"appsecret"` 88 | } 89 | 90 | type LarkConfig struct { 91 | DisplayName string `json:"display_name"` 92 | Callback string `json:"callback"` 93 | AppID string `json:"appid"` 94 | AppSecret string `json:"appsecret"` 95 | } 96 | 97 | type CAS2Config struct { 98 | DisplayName string `json:"display_name"` 99 | Entrance string `json:"entrance"` 100 | Callback string `json:"callback"` 101 | } 102 | 103 | type LDAPConfig struct { 104 | DisplayName string `json:"display_name"` 105 | Entrance string `json:"entrance"` 106 | Address string `json:"address"` 107 | DN string `json:"dn"` 108 | UsingTLS bool `json:"using_tls"` 109 | AuthenticatorEnabled bool `json:"authenticator_enabled"` 110 | 111 | // BindRequired v1.2.6, for active directory, usually bind an adminitrator account 112 | BindRequired bool `json:"bind_required"` 113 | // BaseDN 114 | BaseDN string `json:"base_dn"` 115 | // BindUsername v1.2.6 116 | BindUsername string `json:"bind_username"` 117 | // BindPassword v1.2.6 118 | BindPassword string `json:"bind_password"` 119 | } 120 | -------------------------------------------------------------------------------- /backend/destination.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:21:54 5 | * @Last Modified: U2, 2018-07-14 16:21:54 6 | */ 7 | 8 | package backend 9 | 10 | import ( 11 | "encoding/json" 12 | "janusec/data" 13 | "janusec/models" 14 | "janusec/utils" 15 | "net" 16 | "net/http" 17 | "time" 18 | 19 | "github.com/patrickmn/go-cache" 20 | ) 21 | 22 | var ( 23 | offlineCache = cache.New(30*time.Second, 30*time.Second) 24 | ) 25 | 26 | // ContainsDestinationID ... 27 | // destination example: [{"id":16,"route_type":1,"request_route":"/","backend_route":"/","destination":"127.0.0.1:8800","app_id":14,"node_id":0,"online":true,"check_time":0}] 28 | func ContainsDestinationID(destinations []*models.Destination, destID int64) bool { 29 | for _, destination := range destinations { 30 | if destination.ID == destID { 31 | return true 32 | } 33 | } 34 | return false 35 | } 36 | 37 | // CheckOfflineDestinations check offline destinations and reset the online status 38 | func CheckOfflineDestinations(nowTimeStamp int64) { 39 | for _, app := range Apps { 40 | for _, dest := range app.Destinations { 41 | dest.Mutex.Lock() 42 | defer dest.Mutex.Unlock() 43 | if dest.RouteType == models.ReverseProxyRoute && !dest.Online { 44 | go func(dest2 *models.Destination) { 45 | conn, err := net.DialTimeout("tcp", dest2.Destination, time.Second) 46 | if err == nil { 47 | defer conn.Close() 48 | dest2.Mutex.Lock() 49 | defer dest2.Mutex.Unlock() 50 | dest2.Online = true 51 | dest2.CheckTime = nowTimeStamp 52 | } 53 | }(dest) 54 | } else if dest.RouteType == models.K8S_Ingress && !dest.Online { 55 | // check k8s api 56 | request, _ := http.NewRequest("GET", dest.PodsAPI, nil) 57 | request.Header.Set("Content-Type", "application/json") 58 | resp, err := utils.GetResponse(request) 59 | if err != nil { 60 | dest.CheckTime = nowTimeStamp 61 | continue 62 | } 63 | pods := models.PODS{} 64 | err = json.Unmarshal(resp, &pods) 65 | if err != nil { 66 | utils.DebugPrintln("Unmarshal K8S API", err) 67 | } 68 | dest.Pods = "" 69 | for _, podItem := range pods.Items { 70 | if podItem.Status.Phase == "Running" { 71 | if len(dest.Pods) > 0 { 72 | dest.Pods += "|" 73 | } 74 | dest.Pods += podItem.Status.PodIP + ":" + dest.PodPort 75 | } 76 | } 77 | dest.CheckTime = nowTimeStamp 78 | dest.Online = true 79 | } 80 | } 81 | } 82 | } 83 | 84 | // SetDestinationOffline added on Mar 23, 2024, v1.5.0 85 | func SetDestinationOffline(dest *models.Destination) { 86 | targetDest := dest.Destination 87 | if dest.RouteType == models.K8S_Ingress { 88 | targetDest = dest.PodsAPI 89 | } 90 | if count, ok := offlineCache.Get(targetDest); !ok { 91 | offlineCache.Set(targetDest, int64(1), cache.DefaultExpiration) 92 | } else { 93 | nowCount := count.(int64) + int64(1) 94 | if nowCount > 5 { 95 | // more than 5 requests timeout 96 | dest.Online = false 97 | app, err := GetApplicationByID(dest.AppID) 98 | if err == nil { 99 | sendOfflineNotification(app, targetDest) 100 | } 101 | } 102 | offlineCache.Set(targetDest, nowCount, cache.DefaultExpiration) 103 | } 104 | } 105 | 106 | // sendOfflineNotification ... 107 | func sendOfflineNotification(app *models.Application, dest string) { 108 | var emails string 109 | if data.IsPrimary { 110 | emails = data.DAL.GetAppAdminAndOwnerEmails(app.Owner) 111 | } else { 112 | emails = data.NodeSetting.SMTP.AdminEmails 113 | } 114 | mailBody := "Backend server: " + dest + " (" + app.Name + ") was offline." 115 | if len(mailBody) > 0 && len(emails) > 0 { 116 | go utils.SendEmail(data.NodeSetting.SMTP.SMTPServer, 117 | data.NodeSetting.SMTP.SMTPPort, 118 | data.NodeSetting.SMTP.SMTPAccount, 119 | data.NodeSetting.SMTP.SMTPPassword, 120 | emails, 121 | "[JANUSEC] Backend server offline notification", 122 | mailBody) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /firewall/ip_policy.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2021-01-10 12:16:51 5 | * @Last Modified: U2, 2021-01-10 12:16:51 6 | */ 7 | 8 | package firewall 9 | 10 | import ( 11 | "encoding/json" 12 | "errors" 13 | "janusec/data" 14 | "janusec/models" 15 | "janusec/utils" 16 | "strconv" 17 | "time" 18 | ) 19 | 20 | var globalIPPolicies []*models.IPPolicy 21 | 22 | // InitIPPolicies load IP Policies to memory 23 | func InitIPPolicies() { 24 | if data.IsPrimary { 25 | data.DAL.CreateTableIfNotExistsIPPolicies() 26 | globalIPPolicies = data.DAL.LoadIPPolicies() 27 | return 28 | } 29 | // Replica nodes 30 | globalIPPolicies = RPCLoadIPPolicies() 31 | } 32 | 33 | // GetIPPolicies return Allow List and Block List 34 | func GetIPPolicies() ([]*models.IPPolicy, error) { 35 | return globalIPPolicies, nil 36 | } 37 | 38 | // UpdateIPPolicy update IP policy 39 | func UpdateIPPolicy(body []byte, clientIP string, authUser *models.AuthUser) (*models.IPPolicy, error) { 40 | if !authUser.IsSuperAdmin { 41 | return nil, errors.New("only super administrators can perform this operation") 42 | } 43 | var rpcIPRequest models.APIIPPolicyRequest 44 | if err := json.Unmarshal(body, &rpcIPRequest); err != nil { 45 | utils.DebugPrintln("UpdateIPPolicy", err) 46 | return nil, err 47 | } 48 | ipPolicy := rpcIPRequest.Object 49 | if ipPolicy.ID == 0 { 50 | // New IP 51 | ipPolicy.CreateTime = time.Now().Unix() 52 | ipPolicy.ID = data.DAL.InsertIPPolicy(ipPolicy.IPAddr, ipPolicy.IsAllow, ipPolicy.ApplyToWAF, ipPolicy.ApplyToCC, ipPolicy.CreateTime, ipPolicy.Description) 53 | globalIPPolicies = append(globalIPPolicies, ipPolicy) 54 | go utils.OperationLog(clientIP, authUser.Username, "Add IP Policy", ipPolicy.IPAddr) 55 | data.UpdateFirewallLastModified() 56 | return ipPolicy, nil 57 | } 58 | // Update 59 | err := data.DAL.UpdateIPPolicy(ipPolicy.ID, ipPolicy.IPAddr, ipPolicy.IsAllow, ipPolicy.ApplyToWAF, ipPolicy.ApplyToCC, ipPolicy.Description) 60 | if err != nil { 61 | utils.DebugPrintln("UpdateIPPolicy", err) 62 | return nil, err 63 | } 64 | globalIPPolicies = data.DAL.LoadIPPolicies() 65 | go utils.OperationLog(clientIP, authUser.Username, "Update IP Policy", ipPolicy.IPAddr) 66 | data.UpdateFirewallLastModified() 67 | return ipPolicy, nil 68 | } 69 | 70 | // DeleteIPPolicyByID ... 71 | func DeleteIPPolicyByID(id int64, clientIP string, authUser *models.AuthUser) error { 72 | if !authUser.IsSuperAdmin { 73 | return errors.New("only super administrators can perform this operation") 74 | } 75 | for i, ipPolicy := range globalIPPolicies { 76 | if ipPolicy.ID == id { 77 | globalIPPolicies = append(globalIPPolicies[:i], globalIPPolicies[i+1:]...) 78 | break 79 | } 80 | } 81 | err := data.DAL.DeleteIPPolicyByID(id) 82 | go utils.OperationLog(clientIP, authUser.Username, "Delete IP Policy by ID", strconv.FormatInt(id, 10)) 83 | data.UpdateFirewallLastModified() 84 | return err 85 | } 86 | 87 | // GetIPPolicyByID find item in globalIPPolicies 88 | func GetIPPolicyByID(id int64) (*models.IPPolicy, error) { 89 | for _, ipPolicy := range globalIPPolicies { 90 | if ipPolicy.ID == id { 91 | return ipPolicy, nil 92 | } 93 | } 94 | return nil, errors.New("not found") 95 | } 96 | 97 | // GetIPPolicyByIPAddr get IP Policy 98 | func GetIPPolicyByIPAddr(srcIP string) *models.IPPolicy { 99 | for _, ipPolicy := range globalIPPolicies { 100 | if ipPolicy.IPAddr == srcIP { 101 | return ipPolicy 102 | } 103 | } 104 | return nil 105 | } 106 | 107 | // RPCLoadIPPolicies for replica nodes get IP Policies 108 | func RPCLoadIPPolicies() []*models.IPPolicy { 109 | rpcRequest := &models.RPCRequest{ 110 | Action: "get_ip_policies", Object: nil} 111 | resp, err := data.GetRPCResponse(rpcRequest) 112 | if err != nil { 113 | utils.DebugPrintln("RPCLoadIPPolicies GetResponse", err) 114 | return nil 115 | } 116 | rpcIPPolicies := &models.RPCIPPolicies{} 117 | if err := json.Unmarshal(resp, rpcIPPolicies); err != nil { 118 | utils.DebugPrintln("RPCLoadIPPolicies Unmarshal", err) 119 | return nil 120 | } 121 | ipPolicies := rpcIPPolicies.Object 122 | return ipPolicies 123 | } 124 | -------------------------------------------------------------------------------- /data/backend_cookie.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2023-05-28 14:28:07 5 | */ 6 | 7 | package data 8 | 9 | import ( 10 | "fmt" 11 | "janusec/models" 12 | "janusec/utils" 13 | "strings" 14 | ) 15 | 16 | // CreateTableIfNotExistsCookies ... 17 | func (dal *MyDAL) CreateTableIfNotExistsCookies() error { 18 | const sqlCreateTableIfNotExistsCookies = `CREATE TABLE IF NOT EXISTS "cookies"("id" bigserial PRIMARY KEY, "app_id" bigint, "name" VARCHAR(256) NOT NULL, "domain" VARCHAR(256), "path" VARCHAR(256), "duration" VARCHAR(256), "vendor" VARCHAR(256), "type" bigint, "description" VARCHAR(512), "access_time" bigint, "source" VARCHAR(512))` 19 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsCookies) 20 | return err 21 | } 22 | 23 | // SelectCookies ... 24 | func (dal *MyDAL) SelectCookiesByAppID(appID int64) []*models.Cookie { 25 | const sqlSelectCookies = `SELECT "id","name","domain","path","duration","vendor","type","description","access_time","source" FROM "cookies" WHERE "app_id"=$1 ORDER BY "type"` 26 | rows, err := dal.db.Query(sqlSelectCookies, appID) 27 | if err != nil { 28 | utils.DebugPrintln("SelectCookiesByAppID", err) 29 | } 30 | defer rows.Close() 31 | cookies := []*models.Cookie{} 32 | for rows.Next() { 33 | cookie := &models.Cookie{AppID: appID} 34 | _ = rows.Scan(&cookie.ID, &cookie.Name, &cookie.Domain, &cookie.Path, &cookie.Duration, &cookie.Vendor, &cookie.Type, &cookie.Description, &cookie.AccessTime, &cookie.Source) 35 | cookies = append(cookies, cookie) 36 | } 37 | return cookies 38 | } 39 | 40 | func (dal *MyDAL) InsertCookie(cookie *models.Cookie) error { 41 | const sqlInsertCookie = `INSERT INTO "cookies"("id","app_id","name","domain","path","duration","vendor","type","description","access_time","source") VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)` 42 | _, err := dal.db.Exec(sqlInsertCookie, cookie.ID, cookie.AppID, cookie.Name, cookie.Domain, cookie.Path, cookie.Duration, cookie.Vendor, cookie.Type, cookie.Description, cookie.AccessTime, cookie.Source) 43 | return err 44 | } 45 | 46 | func (dal *MyDAL) UpdateCookie(cookie *models.Cookie) error { 47 | const sqlUpdateCookie = `UPDATE "cookies" SET "name"=$1,"domain"=$2,"path"=$3,"duration"=$4,"vendor"=$5,"type"=$6,"description"=$7,"access_time"=$8,"source"=$9 WHERE "id"=$10` 48 | _, err := dal.db.Exec(sqlUpdateCookie, cookie.Name, cookie.Domain, cookie.Path, cookie.Duration, cookie.Vendor, cookie.Type, cookie.Description, cookie.AccessTime, cookie.Source, cookie.ID) 49 | return err 50 | } 51 | 52 | func (dal *MyDAL) DeleteCookieByID(id int64) error { 53 | const sqlDelCookie = `DELETE FROM "cookies" WHERE "id"=$1` 54 | _, err := dal.db.Exec(sqlDelCookie, id) 55 | return err 56 | } 57 | 58 | func (dal *MyDAL) DeleteCookies(cookieIDs []int64) error { 59 | // Generate placeholders: $1, $2, $3 60 | placeholders := make([]string, len(cookieIDs)) 61 | args := make([]interface{}, len(cookieIDs)) 62 | for i, id := range cookieIDs { 63 | placeholders[i] = fmt.Sprintf("$%d", i+1) 64 | args[i] = id 65 | } 66 | // Create SQL 67 | sqlDelCookies := fmt.Sprintf("DELETE FROM \"cookies\" WHERE \"id\" IN (%s)", strings.Join(placeholders, ", ")) 68 | // Delete Cookies 69 | _, err := dal.db.Exec(sqlDelCookies, args...) 70 | return err 71 | } 72 | 73 | func (dal *MyDAL) DeleteCookiesByAppID(appID int64) error { 74 | const sqlDelCookies = `DELETE FROM "cookies" WHERE "app_id"=$1` 75 | _, err := dal.db.Exec(sqlDelCookies, appID) 76 | return err 77 | } 78 | 79 | func (dal *MyDAL) SelectCookieByID(id int64) (*models.Cookie, error) { 80 | cookie := models.Cookie{ 81 | ID: id, 82 | } 83 | const sqlSelectCookies = `SELECT "app_id","name","domain","path","duration","vendor","type","description","access_time","source" FROM "cookies" WHERE "id"=$1` 84 | err := dal.db.QueryRow(sqlSelectCookies, id).Scan(&cookie.AppID, &cookie.Name, &cookie.Domain, &cookie.Path, &cookie.Duration, &cookie.Vendor, &cookie.Type, &cookie.Description, &cookie.AccessTime, &cookie.Source) 85 | return &cookie, err 86 | } 87 | 88 | func (dal *MyDAL) SelectCookiesCount(appID int64) int64 { 89 | var count int64 90 | const sqlSelectCookiesCount = `SELECT COUNT(1) FROM "cookies" WHERE "app_id"=$1` 91 | err := dal.db.QueryRow(sqlSelectCookiesCount, appID).Scan(&count) 92 | if err != nil { 93 | utils.DebugPrintln("SelectCookiesCount QueryRow", err) 94 | } 95 | return count 96 | } 97 | -------------------------------------------------------------------------------- /data/firewall_cc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:25:35 5 | * @Last Modified: U2, 2018-07-14 16:25:35 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "janusec/models" 12 | "janusec/utils" 13 | ) 14 | 15 | const ( 16 | sqlCreateTableIfNotExistsCCPolicy = `CREATE TABLE IF NOT EXISTS "ccpolicies"("app_id" bigint primary key,"interval_milliseconds" double precision,"max_count" bigint,"block_seconds" double precision,"action" bigint,"stat_by_url" boolean,"stat_by_ua" boolean,"stat_by_cookie" boolean,"is_enabled" boolean)` 17 | sqlExistsCCPolicy = `SELECT COALESCE((SELECT 1 FROM "ccpolicies" LIMIT 1),0)` 18 | sqlExistsCCPolicyByAppID = `SELECT COALESCE((SELECT 1 FROM "ccpolicies" WHERE "app_id"=$1 LIMIT 1),0)` 19 | sqlInsertCCPolicy = `INSERT INTO "ccpolicies"("app_id","interval_milliseconds","max_count","block_seconds","action","stat_by_url","stat_by_ua","stat_by_cookie","is_enabled") VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9)` 20 | sqlSelectCCPolicies = `SELECT "app_id","interval_milliseconds","max_count","block_seconds","action","stat_by_url","stat_by_ua","stat_by_cookie","is_enabled" FROM "ccpolicies"` 21 | sqlUpdateCCPolicy = `UPDATE "ccpolicies" SET "interval_milliseconds"=$1,"max_count"=$2,"block_seconds"=$3,"action"=$4,"stat_by_url"=$5,"stat_by_ua"=$6,"stat_by_cookie"=$7,"is_enabled"=$8 where "app_id"=$9` 22 | sqlDeleteCCPolicy = `DELETE FROM "ccpolicies" WHERE "app_id"=$1` 23 | ) 24 | 25 | // CreateTableIfNotExistsCCPolicy ... 26 | func (dal *MyDAL) CreateTableIfNotExistsCCPolicy() error { 27 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsCCPolicy) 28 | return err 29 | } 30 | 31 | // DeleteCCPolicy ... 32 | func (dal *MyDAL) DeleteCCPolicy(appID int64) error { 33 | stmt, _ := dal.db.Prepare(sqlDeleteCCPolicy) 34 | defer stmt.Close() 35 | _, err := stmt.Exec(appID) 36 | if err != nil { 37 | utils.DebugPrintln("DeleteCCPolicy", err) 38 | } 39 | return err 40 | } 41 | 42 | // UpdateCCPolicy ... 43 | func (dal *MyDAL) UpdateCCPolicy(IntervalMilliSeconds float64, maxCount int64, blockSeconds float64, action models.PolicyAction, 44 | statByURL bool, statByUA bool, statByCookie bool, isEnabled bool, appID int64) error { 45 | stmt, _ := dal.db.Prepare(sqlUpdateCCPolicy) 46 | defer stmt.Close() 47 | _, err := stmt.Exec(IntervalMilliSeconds, maxCount, blockSeconds, action, 48 | statByURL, statByUA, statByCookie, isEnabled, appID) 49 | if err != nil { 50 | utils.DebugPrintln("UpdateCCPolicy", err) 51 | } 52 | return err 53 | } 54 | 55 | // ExistsCCPolicy ... 56 | func (dal *MyDAL) ExistsCCPolicy() bool { 57 | var existCCPolicy int 58 | err := dal.db.QueryRow(sqlExistsCCPolicy).Scan(&existCCPolicy) 59 | if err != nil { 60 | utils.DebugPrintln("ExistsCCPolicy", err) 61 | } 62 | return existCCPolicy != 0 63 | } 64 | 65 | // ExistsCCPolicyByAppID ... 66 | func (dal *MyDAL) ExistsCCPolicyByAppID(appID int64) bool { 67 | var existCCPolicy int 68 | err := dal.db.QueryRow(sqlExistsCCPolicyByAppID, appID).Scan(&existCCPolicy) 69 | if err != nil { 70 | utils.DebugPrintln("ExistsCCPolicyByAppID", err) 71 | } 72 | return existCCPolicy != 0 73 | } 74 | 75 | // InsertCCPolicy ... 76 | func (dal *MyDAL) InsertCCPolicy(appID int64, IntervalMilliSeconds float64, maxCount int64, blockSeconds float64, 77 | action models.PolicyAction, statByURL bool, statByUA bool, statByCookie bool, isEnabled bool) error { 78 | _, err := dal.db.Exec(sqlInsertCCPolicy, appID, IntervalMilliSeconds, maxCount, blockSeconds, 79 | action, statByURL, statByUA, statByCookie, isEnabled) 80 | if err != nil { 81 | utils.DebugPrintln("InsertCCPolicy", err) 82 | } 83 | return err 84 | } 85 | 86 | // SelectCCPolicies ... 87 | func (dal *MyDAL) SelectCCPolicies() []*models.CCPolicy { 88 | ccPolicies := []*models.CCPolicy{} 89 | rows, err := dal.db.Query(sqlSelectCCPolicies) 90 | if err != nil { 91 | utils.DebugPrintln("SelectCCPolicies", err) 92 | } 93 | defer rows.Close() 94 | for rows.Next() { 95 | ccPolicy := &models.CCPolicy{} 96 | err = rows.Scan(&ccPolicy.AppID, &ccPolicy.IntervalMilliSeconds, &ccPolicy.MaxCount, &ccPolicy.BlockSeconds, 97 | &ccPolicy.Action, &ccPolicy.StatByURL, &ccPolicy.StatByUserAgent, &ccPolicy.StatByCookie, &ccPolicy.IsEnabled) 98 | if err != nil { 99 | utils.DebugPrintln("SelectCCPolicies rows.Scan", err) 100 | } 101 | ccPolicies = append(ccPolicies, ccPolicy) 102 | } 103 | return ccPolicies 104 | } 105 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # check root 3 | if [ $UID -ne 0 ]; then 4 | printf "Failed! Please switch to root and install again. \n"; 5 | exit 1 6 | fi 7 | # check systemd 8 | if ! command -v systemctl > /dev/null; then 9 | printf "Failed! systemd not found, please use CentOS/RHEL 7/8+ or Debian 9/10+ . \n"; 10 | exit 1 11 | fi 12 | 13 | install_dir="/usr/local/janusec" 14 | #pg_version=`psql -V | awk -F " " '{print $3}'` 15 | 16 | printf "Installing Janusec Application Gateway... \n" 17 | printf "Requirements:\n" 18 | printf "* Debian 9/10/11+, x86_64, with systemd and nftables \n" 19 | printf "* PostgreSQL 10/11/12+ (Primary Node Only) \n" 20 | printf "* Port TCP/UDP 53 not occupied (Primary Node Only) \n\n" 21 | 22 | printf "Installation Path: ${install_dir}/ \n" 23 | printf "Please select one of the following node types: \n" 24 | printf "1. Primary Node (default, there must be one primary node) \n" 25 | printf "2. Replica Node (optional) \n" 26 | printf "3. Exit (No Installation) \n" 27 | printf "Your option(1/2/3):" 28 | read option 29 | 30 | 31 | 32 | case $option in 33 | 1) printf "Installing as Primary Node \n" 34 | if [ ! -d ${install_dir}/log ]; then 35 | mkdir -p ${install_dir}/log 36 | fi 37 | if [ ! -f ${install_dir}/config.json ]; then 38 | \cp ./config.json.primary_bak ${install_dir}/config.json 39 | else 40 | # update config.json, change master/slave to primary/replica 41 | \cp -f ${install_dir}/config.json ${install_dir}/config.json.bak 42 | sed -i "s/master/primary/g" ${install_dir}/config.json 43 | sed -i "s/slave/replica/g" ${install_dir}/config.json 44 | fi 45 | ;; 46 | 2) printf "Installing as Replica Node \n" 47 | if [ ! -d ${install_dir}/log ]; then 48 | mkdir -p ${install_dir}/log 49 | fi 50 | if [ ! -f ${install_dir}/config.json ]; then 51 | \cp ./config.json.replica_bak ${install_dir}/config.json 52 | else 53 | # update config.json, change master/slave to primary/replica 54 | \cp -f ${install_dir}/config.json ${install_dir}/config.json.bak 55 | sed -i "s/master/primary/g" ${install_dir}/config.json 56 | sed -i "s/slave/replica/g" ${install_dir}/config.json 57 | fi 58 | ;; 59 | 3) printf "Bye! \n" 60 | exit 0 61 | ;; 62 | esac 63 | 64 | \cp -f ./janusec ${install_dir}/ 65 | rm -rf ${install_dir}/static/janusec-admin 66 | mkdir -p ${install_dir}/static/janusec-admin 67 | \cp -R ./static/janusec-admin ${install_dir}/static/janusec-admin -T 68 | if [ ! -d ${install_dir}/static/welcome ]; then 69 | mkdir -p ${install_dir}/static/welcome 70 | \cp -R ./static/welcome ${install_dir}/static/welcome -T 71 | fi 72 | 73 | # copy keepalived files 74 | if [ ! -f ${install_dir}/check_pid.sh ]; then 75 | \cp ./check_pid.sh ${install_dir}/check_pid.sh 76 | \cp ./keepalived.conf ${install_dir}/keepalived.conf 77 | fi 78 | 79 | # Check OS from /etc/os-release, ID="centos" or ID=debian or ID="rhel" 80 | os=`cat /etc/os-release | grep "^ID\=" | awk -F "=" '{print $2}' | sed 's/\"//g'` 81 | full_service_path=/lib/systemd/system/janusec.service 82 | if [ $os == "centos" ] || [ $os == "rhel" ]; then 83 | full_service_path=/usr/lib/systemd/system/janusec.service 84 | elif [ $os == "debian" ]; then 85 | full_service_path=/lib/systemd/system/janusec.service 86 | fi 87 | 88 | 89 | printf "Installation path: ${install_dir}/ \n" 90 | printf "The config file is ${install_dir}/config.json \n" 91 | printf "The following steps should be handled manually. \n" 92 | 93 | if [ $option == 1 ]; then 94 | old_pg=`cat ./janusec.service |grep postgres | awk -F "=" '{print $2}'` 95 | new_pg=`systemctl list-unit-files | grep postgres | head -1 | awk -F " " '{print $1}'` 96 | if [ -z "$new_pg" ]; then 97 | # No PostgreSQL, delete After=postgresql.service 98 | sed -i '/postgres/d' ./janusec.service 99 | else 100 | # Exist PostgreSQL 101 | sed -i "s/$old_pg/$new_pg/" ./janusec.service 102 | fi 103 | \cp -f ./janusec.service ${full_service_path} 104 | printf "* PostgreSQL and prepare dbname,username,password \n" 105 | printf "* Fill in the config.json with dbname,username,password \n" 106 | else 107 | \cp -f ./janusec.service ./janusec-replica.service 108 | sed -i '/postgres/d' ./janusec-replica.service 109 | \cp -f ./janusec-replica.service ${full_service_path} 110 | printf "* Fill in the config.json with: \n" 111 | printf "* node_key (generated by admin and primary node) \n" 112 | printf "* sync_addr (for sync with the primary node) \n" 113 | fi 114 | 115 | systemctl enable janusec.service 116 | 117 | printf "Done. \n" 118 | -------------------------------------------------------------------------------- /usermgmt/oauth_cas2.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-12-20 14:37:39 5 | * @Last Modified: U2, 2020-12-20 14:37:39 6 | */ 7 | 8 | package usermgmt 9 | 10 | import ( 11 | "encoding/xml" 12 | "fmt" 13 | "janusec/data" 14 | "janusec/models" 15 | "janusec/utils" 16 | "net/http" 17 | 18 | "github.com/gorilla/sessions" 19 | "github.com/patrickmn/go-cache" 20 | ) 21 | 22 | // CASServiceResponse is the validation response from CAS Server 23 | type CASServiceResponse struct { 24 | XMLName xml.Name `xml:"http://www.yale.edu/tp/cas serviceResponse"` 25 | AuthenticationSuccess AuthenticationSuccess `xml:"http://www.yale.edu/tp/cas authenticationSuccess"` 26 | } 27 | 28 | // AuthenticationSuccess ... 29 | type AuthenticationSuccess struct { 30 | CASUser string `xml:"http://www.yale.edu/tp/cas user"` 31 | } 32 | 33 | // CAS2CallbackWithCode Doc: https://apereo.github.io/cas/5.3.x/protocol/CAS-Protocol-V2-Specification.html 34 | // Step 1: GET http://192.168.100.109:8080/cas/login?service=http://xxx.xxx.xxx/oauth/cas2?state=admin 35 | // For janusec-admin, state=admin. For applications, state!=admin 36 | func CAS2CallbackWithCode(w http.ResponseWriter, r *http.Request) { 37 | // Step 2.1: Callback with ticket, http://xxx.xxx.xxx/oauth/cas2?state=admin&ticket=ST-1-1uYs7tNVYUEjpyJOHwLTZ6Cxv0ICentOS8X 38 | state := r.FormValue("state") 39 | ticket := r.FormValue("ticket") 40 | // Step 2.2 validate: http://192.168.100.109:8080/cas/serviceValidate?service=http://iknow.janusec.com&ticket=ST-1-1uYs7tNVYUEjpyJOHwLTZ6Cxv0ICentOS8X 41 | validateURL := fmt.Sprintf("%s/serviceValidate?service=%s?state=%s&ticket=%s", data.NodeSetting.AuthConfig.CAS2.Entrance, data.NodeSetting.AuthConfig.CAS2.Callback, state, ticket) 42 | request, _ := http.NewRequest("GET", validateURL, nil) 43 | resp, err := utils.GetResponse(request) 44 | if err != nil { 45 | utils.DebugPrintln("CAS2CallbackWithCode GetResponse", err) 46 | } 47 | var casServiceResponse CASServiceResponse 48 | err = xml.Unmarshal(resp, &casServiceResponse) 49 | if err != nil { 50 | w.WriteHeader(403) 51 | w.Write([]byte("Error: " + err.Error())) 52 | return 53 | } 54 | /* 55 | 56 | 57 | casuser 58 | 59 | 60 | */ 61 | /* 62 | 63 | 64 | Ticket 'ST-9-eHr4cRTrh8APWt...;. 65 | 66 | 67 | */ 68 | casUser := casServiceResponse.AuthenticationSuccess.CASUser 69 | 70 | if state == "admin" { 71 | appUser := data.DAL.SelectAppUserByName(casUser) 72 | var userID int64 73 | if appUser == nil { 74 | // Insert into db if not existed 75 | userID, err = data.DAL.InsertIfNotExistsAppUser(casUser, "", "", "", false, false, false, false) 76 | if err != nil { 77 | w.WriteHeader(403) 78 | w.Write([]byte("Error: " + err.Error())) 79 | return 80 | } 81 | } else { 82 | userID = appUser.ID 83 | } 84 | // create session 85 | authUser := &models.AuthUser{ 86 | UserID: userID, 87 | Username: casUser, 88 | Logged: true, 89 | IsSuperAdmin: appUser.IsSuperAdmin, 90 | IsCertAdmin: appUser.IsCertAdmin, 91 | IsAppAdmin: appUser.IsAppAdmin, 92 | NeedModifyPWD: false} 93 | session, _ := store.Get(r, "sessionid") 94 | session.Values["authuser"] = authUser 95 | session.Options = &sessions.Options{Path: "/janusec-admin/", MaxAge: 86400} 96 | err = session.Save(r, w) 97 | if err != nil { 98 | utils.DebugPrintln("CAS2CallbackWithCode session save error", err) 99 | } 100 | RecordAuthLog(r, authUser.Username, "CAS2", data.CFG.PrimaryNode.Admin.Portal) 101 | http.Redirect(w, r, data.CFG.PrimaryNode.Admin.Portal, http.StatusTemporaryRedirect) 102 | return 103 | 104 | } else { 105 | // for applications 106 | oauthStateI, found := OAuthCache.Get(state) 107 | if found { 108 | oauthState := oauthStateI.(models.OAuthState) 109 | oauthState.UserID = casUser 110 | OAuthCache.Set(state, oauthState, cache.DefaultExpiration) 111 | RecordAuthLog(r, oauthState.UserID, "CAS2", oauthState.CallbackURL) 112 | //fmt.Println("307 to:", oauthState.CallbackURL) 113 | http.Redirect(w, r, oauthState.CallbackURL, http.StatusTemporaryRedirect) 114 | return 115 | } 116 | //fmt.Println("Time expired") 117 | http.Redirect(w, r, "/", http.StatusTemporaryRedirect) 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /data/backend_destination.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:24:35 5 | * @Last Modified: U2, 2018-07-14 16:24:35 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "janusec/models" 12 | "janusec/utils" 13 | ) 14 | 15 | // UpdateDestinationNode ... 16 | func (dal *MyDAL) UpdateDestinationNode(routeType int64, requestRoute string, backendRoute string, destination string, podsAPI string, podPort string, appID int64, nodeID int64, id int64) error { 17 | const sqlUpdateDestinationNode = `UPDATE "destinations" SET "route_type"=$1,"request_route"=$2,"backend_route"=$3,"destination"=$4,"pods_api"=$5,"pod_port"=$6,"app_id"=$7,"node_id"=$8 WHERE "id"=$9` 18 | stmt, _ := dal.db.Prepare(sqlUpdateDestinationNode) 19 | defer stmt.Close() 20 | _, err := stmt.Exec(routeType, requestRoute, backendRoute, destination, podsAPI, podPort, appID, nodeID, id) 21 | if err != nil { 22 | utils.DebugPrintln("UpdateDestinationNode", err) 23 | } 24 | return err 25 | } 26 | 27 | // ExistsDestinationID ... 28 | func (dal *MyDAL) ExistsDestinationID(id int64) bool { 29 | var exist int 30 | const sqlExistsDestinationID = `SELECT COALESCE((SELECT 1 FROM "destinations" WHERE "id"=$1 limit 1),0)` 31 | err := dal.db.QueryRow(sqlExistsDestinationID, id).Scan(&exist) 32 | if err != nil { 33 | utils.DebugPrintln("ExistsDestinationID", err) 34 | } 35 | return exist != 0 36 | } 37 | 38 | // CreateTableIfNotExistsDestinations ... 39 | func (dal *MyDAL) CreateTableIfNotExistsDestinations() error { 40 | const sqlCreateTableIfNotExistsDestinations = `CREATE TABLE IF NOT EXISTS "destinations"("id" bigserial PRIMARY KEY,"route_type" bigint default 1,"request_route" VARCHAR(128) NOT NULL DEFAULT '/',"backend_route" VARCHAR(128) NOT NULL DEFAULT '/',"destination" VARCHAR(128) DEFAULT '',"pods_api" VARCHAR(512) DEFAULT '',"pod_port" VARCHAR(128) DEFAULT '',"pods" VARCHAR(1024) DEFAULT '',"app_id" bigint NOT NULL,"node_id" bigint NOT NULL)` 41 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsDestinations) 42 | if err != nil { 43 | utils.DebugPrintln("CreateTableIfNotExistsDestinations", err) 44 | } 45 | return err 46 | } 47 | 48 | // SelectDestinationsByAppID ... 49 | func (dal *MyDAL) SelectDestinationsByAppID(appID int64) []*models.Destination { 50 | dests := []*models.Destination{} 51 | const sqlSelectDestinationsByAppID = `SELECT "id","route_type","request_route","backend_route","destination","pods_api","pod_port","node_id" FROM "destinations" WHERE "app_id"=$1` 52 | rows, err := dal.db.Query(sqlSelectDestinationsByAppID, appID) 53 | if err != nil { 54 | utils.DebugPrintln("SelectDestinationsByAppID", err) 55 | return dests 56 | } 57 | defer rows.Close() 58 | for rows.Next() { 59 | dest := &models.Destination{AppID: appID, Online: true} 60 | err = rows.Scan(&dest.ID, &dest.RouteType, &dest.RequestRoute, &dest.BackendRoute, &dest.Destination, &dest.PodsAPI, &dest.PodPort, &dest.NodeID) 61 | if err != nil { 62 | utils.DebugPrintln("SelectDestinationsByAppID rows.Scan", err) 63 | } 64 | dests = append(dests, dest) 65 | } 66 | return dests 67 | } 68 | 69 | // InsertDestination ... 70 | func (dal *MyDAL) InsertDestination(routeType int64, requestRoute string, backendRoute string, dest string, podsAPI string, podPort string, appID int64, nodeID int64) (newID int64, err error) { 71 | const sqlInsertDestination = `INSERT INTO "destinations"("id","route_type","request_route","backend_route","destination","pods_api","pod_port","app_id","node_id") VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING "id"` 72 | id := utils.GenSnowflakeID() 73 | err = dal.db.QueryRow(sqlInsertDestination, id, routeType, requestRoute, backendRoute, dest, podsAPI, podPort, appID, nodeID).Scan(&newID) 74 | if err != nil { 75 | utils.DebugPrintln("InsertDestination", err) 76 | } 77 | return newID, err 78 | } 79 | 80 | // DeleteDestinationByID ... 81 | func (dal *MyDAL) DeleteDestinationByID(id int64) error { 82 | const sqlDeleteDestinationByID = `DELETE FROM "destinations" WHERE "id"=$1` 83 | stmt, _ := dal.db.Prepare(sqlDeleteDestinationByID) 84 | defer stmt.Close() 85 | _, err := stmt.Exec(id) 86 | if err != nil { 87 | utils.DebugPrintln("DeleteDestinationByID", err) 88 | } 89 | return err 90 | } 91 | 92 | // DeleteDestinationsByAppID ... 93 | func (dal *MyDAL) DeleteDestinationsByAppID(appID int64) error { 94 | const sqlDeleteDestinationsByAppID = `DELETE FROM "destinations" WHERE "app_id"=$1` 95 | stmt, _ := dal.db.Prepare(sqlDeleteDestinationsByAppID) 96 | defer stmt.Close() 97 | _, err := stmt.Exec(appID) 98 | if err != nil { 99 | utils.DebugPrintln("DeleteDestinationsByAppID", err) 100 | } 101 | return err 102 | } 103 | -------------------------------------------------------------------------------- /usermgmt/authenticator.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-05-17 20:21:48 5 | * @Last Modified: U2, 2020-05-17 20:21:48 6 | */ 7 | 8 | package usermgmt 9 | 10 | import ( 11 | "bytes" 12 | "crypto/hmac" 13 | "crypto/sha1" 14 | "encoding/base32" 15 | "encoding/binary" 16 | "encoding/json" 17 | "strings" 18 | "time" 19 | 20 | "janusec/data" 21 | "janusec/models" 22 | "janusec/utils" 23 | ) 24 | 25 | func toBytes(value int64) []byte { 26 | var result []byte 27 | mask := int64(0xFF) 28 | shifts := [8]uint16{56, 48, 40, 32, 24, 16, 8, 0} 29 | for _, shift := range shifts { 30 | result = append(result, byte((value>>shift)&mask)) 31 | } 32 | return result 33 | } 34 | 35 | func toUint32(bytes []byte) uint32 { 36 | return (uint32(bytes[0]) << 24) + (uint32(bytes[1]) << 16) + 37 | (uint32(bytes[2]) << 8) + uint32(bytes[3]) 38 | } 39 | 40 | // getCode ... 41 | func getCode(secretKey string, timestamp int64) (code uint32) { 42 | secretKeyUpper := strings.ToUpper(secretKey) 43 | key, err := base32.StdEncoding.DecodeString(secretKeyUpper) 44 | if err != nil { 45 | utils.DebugPrintln("getCode base32.StdEncoding.DecodeString error", err) 46 | return 47 | } 48 | hmacSha1 := hmac.New(sha1.New, key) 49 | _, err = hmacSha1.Write(toBytes(timestamp / 30)) 50 | if err != nil { 51 | utils.DebugPrintln("getCode hmacSha1.Write error", err) 52 | } 53 | hash := hmacSha1.Sum(nil) 54 | offset := hash[len(hash)-1] & 0x0F 55 | hashParts := hash[offset : offset+4] 56 | hashParts[0] = hashParts[0] & 0x7F 57 | number := toUint32(hashParts) 58 | code = number % 1000000 59 | return code 60 | } 61 | 62 | // VerifyCode is ok or not 63 | func VerifyCode(secretKey string, code uint32) bool { 64 | timestamp := time.Now().Unix() 65 | tempCode := getCode(secretKey, timestamp) 66 | if code == tempCode { 67 | return true 68 | } 69 | for _, newTimestamp := range []int64{timestamp - 30, timestamp + 30, timestamp - 60, timestamp + 60} { 70 | tempCode = getCode(secretKey, newTimestamp) 71 | if code == tempCode { 72 | return true 73 | } 74 | } 75 | return false 76 | } 77 | 78 | func hmacSha1(key, data []byte) []byte { 79 | h := hmac.New(sha1.New, key) 80 | if total := len(data); total > 0 { 81 | _, err := h.Write(data) 82 | if err != nil { 83 | utils.DebugPrintln("hmacSha1 h.Write error", err) 84 | } 85 | } 86 | return h.Sum(nil) 87 | } 88 | 89 | func genKey() string { 90 | var buf bytes.Buffer 91 | err := binary.Write(&buf, binary.BigEndian, time.Now().UnixNano()) 92 | if err != nil { 93 | utils.DebugPrintln("genKey error", err) 94 | } 95 | key := strings.ToUpper(base32.StdEncoding.EncodeToString(hmacSha1(buf.Bytes(), nil)))[0:16] 96 | return key 97 | } 98 | 99 | // GetTOTPByUID return TOTP Key related to uid 100 | func GetTOTPByUID(uid string) (totpItem *models.TOTP, err error) { 101 | if data.IsPrimary { 102 | totpItem, err = data.DAL.GetTOTPItemByUID(uid) 103 | return totpItem, err 104 | } 105 | // RPC Get or insert TOTP Item 106 | totpItem = &models.TOTP{ 107 | ID: 0, 108 | UID: uid, 109 | TOTPKey: genKey(), 110 | TOTPVerified: false, 111 | } 112 | rpcRequest := &models.RPCRequest{ 113 | Action: "get_totp_key", Object: totpItem} 114 | resp, err := data.GetRPCResponse(rpcRequest) 115 | if err != nil { 116 | utils.DebugPrintln("GetTOTPByUID", err) 117 | } 118 | rpcTOTP := new(models.RPCTOTP) 119 | if err = json.Unmarshal(resp, rpcTOTP); err != nil { 120 | utils.DebugPrintln("RPC GetTOTPByUID Unmarshal", err) 121 | } 122 | return rpcTOTP.Object, err 123 | } 124 | 125 | // GetOrInsertTOTPItem for replica nodes 126 | func GetOrInsertTOTPItem(param map[string]interface{}) (totpItem *models.TOTP, err error) { 127 | totpI := param["object"].(map[string]interface{}) 128 | uid := totpI["uid"].(string) 129 | totpItem, err = data.DAL.GetTOTPItemByUID(uid) 130 | if err != nil { 131 | totpKey := totpI["totp_key"].(string) 132 | id, err := data.DAL.InsertTOTPItem(uid, totpKey, false) 133 | totpItem.ID = id 134 | return totpItem, err 135 | } 136 | return totpItem, nil 137 | } 138 | 139 | // UpdateTOTPVerified set verified = true 140 | func UpdateTOTPVerified(id int64) (*models.TOTP, error) { 141 | if data.IsPrimary { 142 | err := data.DAL.UpdateTOTPVerified(true, id) 143 | if err != nil { 144 | utils.DebugPrintln("UpdateTOTPVerified error", err) 145 | } 146 | return nil, nil 147 | } 148 | // RPC called 149 | rpcRequest := &models.RPCRequest{ 150 | Action: "update_totp", ObjectID: id, Object: nil} 151 | _, err := data.GetRPCResponse(rpcRequest) 152 | if err != nil { 153 | utils.DebugPrintln("UpdateTOTPVerified", err) 154 | } 155 | return nil, nil 156 | } 157 | -------------------------------------------------------------------------------- /usermgmt/oauth_wxwork.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-03-14 09:58:15 5 | * @Last Modified: U2, 2020-03-14 09:58:15 6 | */ 7 | 8 | package usermgmt 9 | 10 | import ( 11 | "encoding/json" 12 | "fmt" 13 | "net/http" 14 | "time" 15 | 16 | "janusec/data" 17 | "janusec/models" 18 | "janusec/utils" 19 | 20 | "github.com/gorilla/sessions" 21 | 22 | "github.com/patrickmn/go-cache" 23 | ) 24 | 25 | var ( 26 | OAuthCache = cache.New(5*time.Minute, 5*time.Minute) 27 | ) 28 | 29 | type WxworkAccessToken struct { 30 | ErrCode int64 `json:"errcode"` 31 | ErrMsg string `json:"errmsg"` 32 | AccessToken string `json:"access_token"` 33 | ExpiresIn int `json:"expires_in"` 34 | } 35 | 36 | type WxworkUser struct { 37 | ErrCode int64 `json:"errcode"` 38 | ErrMsg string `json:"errmsg"` 39 | UserID string `json:"UserId"` 40 | } 41 | 42 | // https://work.weixin.qq.com/api/doc/90000/90135/91025 43 | // Step 1: To https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=CORPID&agentid=AGENTID&redirect_uri=REDIRECT_URI&state=admin 44 | // If state==admin, for janusec-admin; else for frontend applications 45 | func WxworkCallbackWithCode(w http.ResponseWriter, r *http.Request) { 46 | // Step 2.1: Callback with code, http://gate.janusec.com/?code=BM8k8U6RwtQtNY&state=admin&appid=wwd03ba1f8 47 | code := r.FormValue("code") 48 | state := r.FormValue("state") 49 | // Step 2.2: Within Callback, get access_token 50 | // https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=wwd03ba1f8&corpsecret=NdZI 51 | // Response format: https://work.weixin.qq.com/api/doc/90000/90135/91039 52 | accessTokenURL := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", 53 | data.NodeSetting.AuthConfig.Wxwork.CorpID, data.NodeSetting.AuthConfig.Wxwork.CorpSecret) 54 | request, _ := http.NewRequest("GET", accessTokenURL, nil) 55 | resp, err := utils.GetResponse(request) 56 | if err != nil { 57 | utils.DebugPrintln("WxworkCallbackWithCode GetResponse", err) 58 | } 59 | tokenResponse := WxworkAccessToken{} 60 | err = json.Unmarshal(resp, &tokenResponse) 61 | if err != nil { 62 | utils.DebugPrintln("WxworkCallbackWithCode json.Unmarshal error", err) 63 | } 64 | // Step 2.3: Get UserID, https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE 65 | userURL := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s", tokenResponse.AccessToken, code) 66 | request, _ = http.NewRequest("GET", userURL, nil) 67 | resp, err = utils.GetResponse(request) 68 | if err != nil { 69 | utils.DebugPrintln("WxworkCallbackWithCode GetResponse", err) 70 | } 71 | wxworkUser := WxworkUser{} 72 | err = json.Unmarshal(resp, &wxworkUser) 73 | if err != nil { 74 | utils.DebugPrintln("WxworkCallbackWithCode json.Unmarshal error", err) 75 | } 76 | if state == "admin" { 77 | appUser := data.DAL.SelectAppUserByName(wxworkUser.UserID) 78 | var userID int64 79 | if appUser == nil { 80 | // Insert into db if not existed 81 | userID, err = data.DAL.InsertIfNotExistsAppUser(wxworkUser.UserID, "", "", "", false, false, false, false) 82 | if err != nil { 83 | w.WriteHeader(403) 84 | w.Write([]byte("Error: " + err.Error())) 85 | return 86 | } 87 | } else { 88 | userID = appUser.ID 89 | } 90 | // create session 91 | authUser := &models.AuthUser{ 92 | UserID: userID, 93 | Username: wxworkUser.UserID, 94 | Logged: true, 95 | IsSuperAdmin: appUser.IsSuperAdmin, 96 | IsCertAdmin: appUser.IsCertAdmin, 97 | IsAppAdmin: appUser.IsAppAdmin, 98 | NeedModifyPWD: false} 99 | session, _ := store.Get(r, "sessionid") 100 | session.Values["authuser"] = authUser 101 | session.Options = &sessions.Options{Path: "/janusec-admin/", MaxAge: tokenResponse.ExpiresIn} 102 | err = session.Save(r, w) 103 | if err != nil { 104 | utils.DebugPrintln("WxworkCallbackWithCode session save error", err) 105 | } 106 | RecordAuthLog(r, authUser.Username, "WxWork", data.CFG.PrimaryNode.Admin.Portal) 107 | http.Redirect(w, r, data.CFG.PrimaryNode.Admin.Portal, http.StatusTemporaryRedirect) 108 | return 109 | } 110 | // Gateway OAuth for employees and internal application 111 | oauthStateI, found := OAuthCache.Get(state) 112 | if found { 113 | oauthState := oauthStateI.(models.OAuthState) 114 | oauthState.UserID = wxworkUser.UserID 115 | oauthState.AccessToken = tokenResponse.AccessToken 116 | OAuthCache.Set(state, oauthState, cache.DefaultExpiration) 117 | RecordAuthLog(r, oauthState.UserID, "WxWork", oauthState.CallbackURL) 118 | //fmt.Println("307 to:", oauthState.CallbackURL) 119 | http.Redirect(w, r, oauthState.CallbackURL, http.StatusTemporaryRedirect) 120 | return 121 | } 122 | //fmt.Println("Time expired") 123 | http.Redirect(w, r, "/", http.StatusTemporaryRedirect) 124 | } 125 | -------------------------------------------------------------------------------- /gateway/waf_captcha.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:36:54 5 | * @Last Modified: U2, 2018-07-14 16:36:54 6 | */ 7 | 8 | package gateway 9 | 10 | import ( 11 | "html" 12 | "net/http" 13 | "sync" 14 | "text/template" 15 | "time" 16 | 17 | "janusec/firewall" 18 | "janusec/models" 19 | 20 | "github.com/dchest/captcha" 21 | ) 22 | 23 | var ( 24 | captchaHitInfo = sync.Map{} // (clientID string, *HitInfo) 25 | formTemplate = template.Must(template.New("captcha").Parse(formTemplateSrc)) 26 | ) 27 | 28 | const ( 29 | // CaptchaEntrance : captcha confirm url 30 | CaptchaEntrance = "/captcha/confirm" 31 | ) 32 | 33 | // ShowCaptchaHandlerFunc ... 34 | func ShowCaptchaHandlerFunc(w http.ResponseWriter, r *http.Request) { 35 | go ClearExpiredCapthchaHitInfo() 36 | id := html.EscapeString(r.FormValue("id")) 37 | captchaContext := models.CaptchaContext{CaptchaId: captcha.New(), ClientID: id} 38 | if err := formTemplate.Execute(w, &captchaContext); err != nil { 39 | http.Error(w, err.Error(), http.StatusInternalServerError) 40 | } 41 | } 42 | 43 | // ValidateCaptchaHandlerFunc ... 44 | func ValidateCaptchaHandlerFunc(w http.ResponseWriter, r *http.Request) { 45 | w.Header().Set("Content-Type", "text/html; charset=utf-8") 46 | clientID := r.FormValue("client_id") 47 | if !captcha.VerifyString(r.FormValue("captcha_id"), r.FormValue("captcha_solution")) { 48 | captchaURL := CaptchaEntrance + "?id=" + clientID 49 | http.Redirect(w, r, captchaURL, http.StatusTemporaryRedirect) 50 | } else { 51 | if mapHitInfo, ok := captchaHitInfo.Load(clientID); ok { 52 | hitInfo := mapHitInfo.(*models.HitInfo) 53 | captchaHitInfo.Delete(clientID) 54 | if hitInfo.TypeID == 1 { 55 | firewall.ClearCCStatByClientID(hitInfo.PolicyID, clientID) 56 | http.Redirect(w, r, hitInfo.TargetURL, http.StatusFound) 57 | } else { 58 | http.Redirect(w, r, "/", http.StatusFound) 59 | } 60 | return 61 | } 62 | http.Redirect(w, r, "/", http.StatusFound) 63 | } 64 | } 65 | 66 | // ShowCaptchaImage ... 67 | func ShowCaptchaImage() http.Handler { 68 | return captcha.Server(captcha.StdWidth, captcha.StdHeight) 69 | } 70 | 71 | // ClearExpiredCapthchaHitInfo ... 72 | func ClearExpiredCapthchaHitInfo() { 73 | captchaHitInfo.Range(func(key, value interface{}) bool { 74 | clientID := key.(string) 75 | hitInfo := value.(*models.HitInfo) 76 | curTime := time.Now().Unix() 77 | if curTime-hitInfo.BlockTime > 600 { 78 | captchaHitInfo.Delete(clientID) 79 | } 80 | return true 81 | }) 82 | } 83 | 84 | const formTemplateSrc = ` 85 | 86 | 87 | 88 | CAPTCHA 89 | 90 | 140 | 141 | 142 |
143 |
144 | 145 | 146 |

147 |

Captcha image

148 | 149 |
150 | 151 | 152 | 153 |
154 | 中文 155 | English 156 |
157 | 158 | 175 | 176 | 177 | ` 178 | -------------------------------------------------------------------------------- /gateway/auth_code.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-05-17 21:45:58 5 | * @Last Modified: U2, 2020-05-17 21:45:58 6 | */ 7 | 8 | package gateway 9 | 10 | import ( 11 | "encoding/base64" 12 | "fmt" 13 | "net/http" 14 | "strconv" 15 | "text/template" 16 | 17 | "janusec/usermgmt" 18 | "janusec/utils" 19 | 20 | qrcode "github.com/skip2/go-qrcode" 21 | ) 22 | 23 | var ( 24 | authCodeUITemplate = template.Must(template.New("authcode").Parse(authcodeTemplate)) 25 | ) 26 | 27 | // AuthCodeContext authenticator code context 28 | type AuthCodeContext struct { 29 | UID string 30 | TOTPKey string 31 | ImageData string 32 | } 33 | 34 | // AuthCodeVerifyFunc Register TOTP in Mobile APP 35 | func AuthCodeVerifyFunc(w http.ResponseWriter, r *http.Request) { 36 | uid := r.FormValue("uid") 37 | totpCode := r.FormValue("code") 38 | totpCodeInt, _ := strconv.ParseUint(totpCode, 10, 32) 39 | totpItem, _ := usermgmt.GetTOTPByUID(uid) //data.DAL.GetTOTPItemByUID(uid) 40 | verifyOK := usermgmt.VerifyCode(totpItem.TOTPKey, uint32(totpCodeInt)) 41 | if verifyOK { 42 | _, err := usermgmt.UpdateTOTPVerified(totpItem.ID) 43 | if err != nil { 44 | utils.DebugPrintln("UpdateTOTPVerified error", err) 45 | } 46 | http.Redirect(w, r, "/", http.StatusTemporaryRedirect) 47 | return 48 | } 49 | http.Redirect(w, r, "/oauth/code/register?uid="+uid, http.StatusTemporaryRedirect) 50 | } 51 | 52 | // ShowAuthCodeRegisterUI used for Authenticator Code register UI 53 | func ShowAuthCodeRegisterUI(w http.ResponseWriter, r *http.Request) { 54 | uid := r.FormValue("uid") 55 | totpItem, _ := usermgmt.GetTOTPByUID(uid) 56 | // Format: otpauth://totp/uid?secret=XBSWY3DPEHPK3PXP&issuer=JANUSEC 57 | totpLink := fmt.Sprintf("otpauth://totp/%s?secret=%s&issuer=JANUSEC", uid, totpItem.TOTPKey) 58 | var png []byte 59 | png, err := qrcode.Encode(totpLink, qrcode.Medium, 256) 60 | if err != nil { 61 | utils.DebugPrintln("qrcode.Encode", err) 62 | } 63 | codeImageText := "data:image/png;base64," + base64.StdEncoding.EncodeToString(png) 64 | authCodeContext := AuthCodeContext{ 65 | UID: uid, 66 | TOTPKey: totpItem.TOTPKey, 67 | ImageData: codeImageText, 68 | } 69 | if err := authCodeUITemplate.Execute(w, &authCodeContext); err != nil { 70 | http.Error(w, err.Error(), http.StatusInternalServerError) 71 | } 72 | } 73 | 74 | const authcodeTemplate = ` 75 | 76 | 77 | 78 | Authenticator Registration 79 | 80 | 145 | 146 | 147 |
148 | 149 | 150 |

151 | 中文 152 | English 153 |
154 |

155 |
    156 |
  • Google Authenticator
  • 157 |
  • Microsoft Authenticator
  • 158 |
159 | 160 | 161 | 162 |

163 |

{{ .TOTPKey }}

164 |
165 |

166 | 167 |
168 | 169 | 170 | 171 |
172 |
173 | 176 | 177 | ` 178 | -------------------------------------------------------------------------------- /data/data.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:23:50 5 | * @Last Modified: U2, 2018-07-14 16:23:50 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "database/sql" 12 | "fmt" 13 | "os" 14 | "strings" 15 | 16 | "janusec/models" 17 | "janusec/utils" 18 | 19 | // PostgreSQL 20 | _ "github.com/lib/pq" 21 | //_ "github.com/mattn/go-sqlite3" 22 | _ "github.com/glebarez/go-sqlite" 23 | ) 24 | 25 | // MyDAL used for data access layer 26 | type MyDAL struct { 27 | db *sql.DB 28 | } 29 | 30 | var ( 31 | // DAL is Data Access Layer 32 | DAL *MyDAL 33 | // CFG is config 34 | CFG *models.Config 35 | // IsPrimary i.e. Is Primary Node 36 | IsPrimary bool 37 | // Version of JANUSEC 38 | Version = "1.6.0" 39 | ) 40 | 41 | // InitConfig init Data Access Layer 42 | func InitConfig() { 43 | DAL = &MyDAL{} 44 | var err error 45 | CFG, err = NewConfig("./config.json") 46 | if err != nil { 47 | utils.DebugPrintln("InitConfig", err) 48 | os.Exit(1) 49 | } 50 | nodeRole := strings.ToLower(CFG.NodeRole) 51 | if nodeRole != "primary" && nodeRole != "replica" { 52 | fmt.Printf("Error: node_role %s is not supported, it should be primary or replica, please check config.json \n", nodeRole) 53 | utils.DebugPrintln("Error: node_role ", nodeRole, " is not supported, it should be primary or replica, please check config.json") 54 | os.Exit(1) 55 | } 56 | IsPrimary = (nodeRole == "primary") 57 | if IsPrimary { 58 | switch CFG.PrimaryNode.DatabaseType { 59 | case "sqlite": 60 | // SQLite 61 | conn := "file:./data.sqlite3?_busy_timeout=9999999" 62 | DAL.db, err = sql.Open("sqlite", conn) 63 | if err != nil { 64 | utils.DebugPrintln("InitConfig sql.Open:", err) 65 | os.Exit(1) 66 | } 67 | // Set max conns 1 68 | DAL.db.SetMaxOpenConns(1) 69 | default: 70 | // PostgreSQL 71 | conn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", 72 | CFG.PrimaryNode.Database.Host, 73 | CFG.PrimaryNode.Database.Port, 74 | CFG.PrimaryNode.Database.User, 75 | CFG.PrimaryNode.Database.Password, 76 | CFG.PrimaryNode.Database.DBName) 77 | DAL.db, err = sql.Open("postgres", conn) 78 | if err != nil { 79 | utils.DebugPrintln("InitConfig sql.Open:", err) 80 | os.Exit(1) 81 | } 82 | // Check if the User and Password are Correct 83 | _, err = DAL.db.Query("select 1") 84 | if err != nil { 85 | utils.DebugPrintln("InitConfig Failed, Please check the database user and password. Error:", err) 86 | os.Exit(1) 87 | } 88 | // Set max conns 99 89 | DAL.db.SetMaxOpenConns(99) 90 | } 91 | } else { 92 | // Init Nodes Key for replica node 93 | NodesKey = NodeHexKeyToCryptKey(CFG.ReplicaNode.NodeKey) 94 | } 95 | } 96 | 97 | // ExecSQL Exec SQL Directly 98 | func (dal *MyDAL) ExecSQL(sql string) error { 99 | _, err := dal.db.Exec(sql) 100 | return err 101 | } 102 | 103 | // ExistColumnInTable ... 104 | func (dal *MyDAL) ExistColumnInTable(tableName string, columnName string) bool { 105 | var count int64 106 | var sql string 107 | var err error 108 | switch CFG.PrimaryNode.DatabaseType { 109 | case "sqlite": 110 | // SQLite 111 | // This statement has a bug, if a_uid exists, then uid will be considered as exists 112 | // sql = `SELECT count(1) FROM sqlite_master WHERE name=? and sql like ?` 113 | // err = dal.db.QueryRow(sql, tableName, "%"+columnName+"%").Scan(&count) 114 | sql = fmt.Sprintf(`SELECT count(%s) FROM %s`, columnName, tableName) 115 | err = dal.db.QueryRow(sql).Scan(&count) 116 | if err != nil { 117 | // Not exists 118 | return false 119 | } 120 | return true 121 | default: 122 | // PostgreSQL 123 | sql = `SELECT count(1) FROM information_schema.columns WHERE table_name=$1 AND column_name=$2` 124 | err = dal.db.QueryRow(sql, tableName, columnName).Scan(&count) 125 | if err != nil { 126 | utils.DebugPrintln("PostgreSQL ExistColumnInTable QueryRow", tableName, columnName, err) 127 | } 128 | return count > 0 129 | } 130 | } 131 | 132 | // ExistConstraint ... 133 | func (dal *MyDAL) ExistConstraint(tableName string, constraintName string) bool { 134 | var count int64 135 | var sql string 136 | var err error 137 | switch CFG.PrimaryNode.DatabaseType { 138 | case "sqlite": 139 | // SQLite 140 | // select * from sqlite_master where type='index' and tbl_name='test' and name='uid' 141 | // For SQLite, create unique index uid on table_name(column1, column2); 142 | sql = `SELECT count(1) FROM sqlite_master WHERE type='index' AND tbl_name=$1 AND name=$2` 143 | err = dal.db.QueryRow(sql, tableName, constraintName).Scan(&count) 144 | if err != nil { 145 | utils.DebugPrintln("ExistConstraint QueryRow", err) 146 | } 147 | return count > 0 148 | default: 149 | // PostgreSQL 150 | sql = `SELECT count(1) FROM information_schema.constraint_column_usage WHERE table_name=$1 and constraint_name=$2` 151 | err = dal.db.QueryRow(sql, tableName, constraintName).Scan(&count) 152 | if err != nil { 153 | utils.DebugPrintln("ExistConstraint QueryRow", err) 154 | } 155 | return count > 0 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /data/firewall_group_policy.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:31:06 5 | * @Last Modified: U2, 2018-07-14 16:31:06 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "janusec/models" 12 | "janusec/utils" 13 | ) 14 | 15 | const ( 16 | sqlCreateTableIfNotExistsGroupPolicy = `CREATE TABLE IF NOT EXISTS "group_policies"("id" bigserial primary key,"description" VARCHAR(256) NOT NULL DEFAULT '',"app_id" bigint,"vuln_id" bigint,"hit_value" bigint,"action" bigint,"is_enabled" boolean,"user_id" bigint,"update_time" bigint)` 17 | sqlExistsGroupPolicy = `SELECT COALESCE((SELECT 1 FROM "group_policies" limit 1),0)` 18 | sqlSelectGroupPolicies = `SELECT "id","description","app_id","vuln_id","hit_value","action","is_enabled","user_id","update_time" FROM "group_policies"` 19 | sqlSelectGroupPoliciesByAppID = `SELECT "id","description","vuln_id","hit_value","action","is_enabled","user_id","update_time" FROM "group_policies" WHERE "app_id"=$1` 20 | sqlInsertGroupPolicy = `INSERT INTO "group_policies"("id","description","app_id","vuln_id","hit_value","action","is_enabled","user_id","update_time") VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING "id"` 21 | sqlUpdateGroupPolicy = `UPDATE "group_policies" SET "description"=$1,"app_id"=$2,"vuln_id"=$3,"hit_value"=$4,"action"=$5,"is_enabled"=$6,"user_id"=$7,"update_time"=$8 WHERE "id"=$9` 22 | sqlDeleteGroupPolicyByID = `DELETE FROM "group_policies" WHERE "id"=$1` 23 | ) 24 | 25 | // CreateTableIfNotExistsGroupPolicy ... 26 | func (dal *MyDAL) CreateTableIfNotExistsGroupPolicy() error { 27 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsGroupPolicy) 28 | if err != nil { 29 | utils.DebugPrintln("CreateTableIfNotExistsGroupPolicy", err) 30 | } 31 | return err 32 | } 33 | 34 | // DeleteGroupPolicyByID ... 35 | func (dal *MyDAL) DeleteGroupPolicyByID(id int64) error { 36 | _, err := dal.db.Exec(sqlDeleteGroupPolicyByID, id) 37 | if err != nil { 38 | utils.DebugPrintln("DeleteGroupPolicyByID", err) 39 | } 40 | return err 41 | } 42 | 43 | // UpdateGroupPolicy ... 44 | func (dal *MyDAL) UpdateGroupPolicy(description string, appID int64, vulnID int64, hitValue int64, action models.PolicyAction, isEnabled bool, userID int64, updateTime int64, id int64) error { 45 | stmt, _ := dal.db.Prepare(sqlUpdateGroupPolicy) 46 | defer stmt.Close() 47 | _, err := stmt.Exec(description, appID, vulnID, hitValue, action, isEnabled, userID, updateTime, id) 48 | if err != nil { 49 | utils.DebugPrintln("UpdateGroupPolicy", err) 50 | } 51 | return err 52 | } 53 | 54 | // SelectGroupPolicies ... 55 | func (dal *MyDAL) SelectGroupPolicies() []*models.GroupPolicy { 56 | groupPolicies := []*models.GroupPolicy{} 57 | rows, err := dal.db.Query(sqlSelectGroupPolicies) 58 | if err != nil { 59 | utils.DebugPrintln("SelectGroupPolicies", err) 60 | } 61 | defer rows.Close() 62 | for rows.Next() { 63 | groupPolicy := &models.GroupPolicy{} 64 | err = rows.Scan(&groupPolicy.ID, &groupPolicy.Description, &groupPolicy.AppID, &groupPolicy.VulnID, 65 | &groupPolicy.HitValue, &groupPolicy.Action, &groupPolicy.IsEnabled, &groupPolicy.UserID, &groupPolicy.UpdateTime) 66 | if err != nil { 67 | utils.DebugPrintln("SelectGroupPolicies Scan", err) 68 | } 69 | groupPolicies = append(groupPolicies, groupPolicy) 70 | } 71 | return groupPolicies 72 | } 73 | 74 | // SelectGroupPoliciesByAppID ... 75 | func (dal *MyDAL) SelectGroupPoliciesByAppID(appID int64) ([]*models.GroupPolicy, error) { 76 | groupPolicies := []*models.GroupPolicy{} 77 | rows, err := dal.db.Query(sqlSelectGroupPoliciesByAppID, appID) 78 | if err != nil { 79 | utils.DebugPrintln("SelectGroupPoliciesByAppID", err) 80 | } 81 | defer rows.Close() 82 | for rows.Next() { 83 | groupPolicy := &models.GroupPolicy{} 84 | groupPolicy.AppID = appID 85 | err = rows.Scan(&groupPolicy.ID, &groupPolicy.Description, &groupPolicy.VulnID, 86 | &groupPolicy.HitValue, &groupPolicy.Action, &groupPolicy.IsEnabled, &groupPolicy.UserID, &groupPolicy.UpdateTime) 87 | if err != nil { 88 | utils.DebugPrintln("SelectGroupPoliciesByAppID Scan", err) 89 | return groupPolicies, err 90 | } 91 | groupPolicies = append(groupPolicies, groupPolicy) 92 | } 93 | return groupPolicies, err 94 | } 95 | 96 | // InsertGroupPolicy ... 97 | func (dal *MyDAL) InsertGroupPolicy(description string, appID int64, vulnID int64, hitValue int64, action models.PolicyAction, isEnabled bool, userID int64, updateTime int64) (newID int64, err error) { 98 | stmt, err := dal.db.Prepare(sqlInsertGroupPolicy) 99 | if err != nil { 100 | utils.DebugPrintln("InsertGroupPolicy Prepare", err) 101 | } 102 | defer stmt.Close() 103 | id := utils.GenSnowflakeID() 104 | err = stmt.QueryRow(id, description, appID, vulnID, hitValue, action, isEnabled, userID, updateTime).Scan(&newID) 105 | if err != nil { 106 | utils.DebugPrintln("InsertGroupPolicy Scan", err) 107 | } 108 | return newID, err 109 | } 110 | 111 | // ExistsGroupPolicy ... 112 | func (dal *MyDAL) ExistsGroupPolicy() bool { 113 | var exist int 114 | err := dal.db.QueryRow(sqlExistsGroupPolicy).Scan(&exist) 115 | if err != nil { 116 | utils.DebugPrintln("ExistsGroupPolicy", err) 117 | } 118 | return exist != 0 119 | } 120 | -------------------------------------------------------------------------------- /gateway/auth_oauth.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-03-14 18:47:18 5 | * @Last Modified: U2, 2020-03-14 18:47:18 6 | */ 7 | 8 | package gateway 9 | 10 | import ( 11 | "fmt" 12 | "net/http" 13 | 14 | "janusec/data" 15 | "janusec/usermgmt" 16 | ) 17 | 18 | // OAuthInfo OAuth Information 19 | type OAuthInfo struct { 20 | UseOAuth bool `json:"use_oauth"` 21 | DisplayName string `json:"display_name"` 22 | EntranceURL string `json:"entrance_url"` 23 | 24 | // AuthenticatorEnabled added in v1.2.2, for janusec-admin login 25 | AuthenticatorEnabled bool `json:"authenticator_enabled"` 26 | } 27 | 28 | // WxworkCallBackHandleFunc for Wxwork CallBack 29 | func WxworkCallBackHandleFunc(w http.ResponseWriter, r *http.Request) { 30 | usermgmt.WxworkCallbackWithCode(w, r) 31 | } 32 | 33 | // DingtalkCallBackHandleFunc for Dingtalk CallBack 34 | func DingtalkCallBackHandleFunc(w http.ResponseWriter, r *http.Request) { 35 | usermgmt.DingtalkCallbackWithCode(w, r) 36 | } 37 | 38 | // FeishuCallBackHandleFunc for Feishu CallBack 39 | func FeishuCallBackHandleFunc(w http.ResponseWriter, r *http.Request) { 40 | usermgmt.FeishuCallbackWithCode(w, r) 41 | } 42 | 43 | // LarkCallBackHandleFunc for Lark CallBack 44 | func LarkCallBackHandleFunc(w http.ResponseWriter, r *http.Request) { 45 | usermgmt.LarkCallbackWithCode(w, r) 46 | } 47 | 48 | // CAS2CallBackHandleFunc for CAS2 CallBack 49 | func CAS2CallBackHandleFunc(w http.ResponseWriter, r *http.Request) { 50 | usermgmt.CAS2CallbackWithCode(w, r) 51 | } 52 | 53 | // LDAPCallBackHandleFunc for LDAP CallBack 54 | func LDAPCallBackHandleFunc(w http.ResponseWriter, r *http.Request) { 55 | usermgmt.LDAPAuthFunc(w, r) 56 | } 57 | 58 | // OAuthGetHandleFunc Get OAuth Information and Response 59 | func OAuthGetHandleFunc(w http.ResponseWriter, r *http.Request) { 60 | obj, err := GetOAuthInfo() 61 | GenResponseByObject(w, obj, err) 62 | } 63 | 64 | // GetOAuthInfo Get OAuth Information 65 | func GetOAuthInfo() (*OAuthInfo, error) { 66 | oauthInfo := OAuthInfo{} 67 | oauthInfo.AuthenticatorEnabled = data.PrimarySetting.AuthenticatorEnabled 68 | if !data.NodeSetting.AuthConfig.Enabled { 69 | return &oauthInfo, nil 70 | } 71 | switch data.NodeSetting.AuthConfig.Provider { 72 | case "wxwork": 73 | entranceURL := fmt.Sprintf("https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=%s&agentid=%s&redirect_uri=%s&state=admin", 74 | data.NodeSetting.AuthConfig.Wxwork.CorpID, 75 | data.NodeSetting.AuthConfig.Wxwork.AgentID, 76 | data.NodeSetting.AuthConfig.Wxwork.Callback) 77 | oauthInfo.UseOAuth = true 78 | oauthInfo.DisplayName = data.NodeSetting.AuthConfig.Wxwork.DisplayName 79 | oauthInfo.EntranceURL = entranceURL 80 | return &oauthInfo, nil 81 | case "dingtalk": 82 | /* API v1 83 | entranceURL := fmt.Sprintf("https://oapi.dingtalk.com/connect/qrconnect?appid=%s&response_type=code&scope=snsapi_login&state=admin&redirect_uri=%s", 84 | data.NodeSetting.AuthConfig.Dingtalk.AppID, 85 | data.NodeSetting.AuthConfig.Dingtalk.Callback) 86 | */ 87 | // API v2, added on Mar 23, 2024, v1.5.0 88 | 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`, 89 | data.NodeSetting.AuthConfig.Dingtalk.Callback, 90 | data.NodeSetting.AuthConfig.Dingtalk.CorpID, 91 | data.NodeSetting.AuthConfig.Dingtalk.AppID) 92 | oauthInfo.UseOAuth = true 93 | oauthInfo.DisplayName = data.NodeSetting.AuthConfig.Dingtalk.DisplayName 94 | oauthInfo.EntranceURL = entranceURL 95 | return &oauthInfo, nil 96 | case "feishu": 97 | entranceURL := fmt.Sprintf("https://open.feishu.cn/open-apis/authen/v1/index?redirect_uri=%s&app_id=%s&state=admin", 98 | data.NodeSetting.AuthConfig.Feishu.Callback, 99 | data.NodeSetting.AuthConfig.Feishu.AppID) 100 | oauthInfo.UseOAuth = true 101 | oauthInfo.DisplayName = data.NodeSetting.AuthConfig.Feishu.DisplayName 102 | oauthInfo.EntranceURL = entranceURL 103 | return &oauthInfo, nil 104 | case "lark": 105 | entranceURL := fmt.Sprintf("https://open.larksuite.com/open-apis/authen/v1/index?redirect_uri=%s&app_id=%s&state=admin", 106 | data.NodeSetting.AuthConfig.Lark.Callback, 107 | data.NodeSetting.AuthConfig.Lark.AppID) 108 | oauthInfo.UseOAuth = true 109 | oauthInfo.DisplayName = data.NodeSetting.AuthConfig.Lark.DisplayName 110 | oauthInfo.EntranceURL = entranceURL 111 | return &oauthInfo, nil 112 | case "ldap": 113 | entranceURL := data.NodeSetting.AuthConfig.LDAP.Entrance + "?state=admin" 114 | oauthInfo.UseOAuth = true 115 | oauthInfo.DisplayName = data.NodeSetting.AuthConfig.LDAP.DisplayName 116 | oauthInfo.EntranceURL = entranceURL 117 | return &oauthInfo, nil 118 | case "cas2": 119 | entranceURL := fmt.Sprintf("%s/login?renew=true&service=%s?state=admin", 120 | data.NodeSetting.AuthConfig.CAS2.Entrance, data.NodeSetting.AuthConfig.CAS2.Callback) 121 | oauthInfo.UseOAuth = true 122 | oauthInfo.DisplayName = data.NodeSetting.AuthConfig.CAS2.DisplayName 123 | oauthInfo.EntranceURL = entranceURL 124 | return &oauthInfo, nil 125 | } 126 | oauthInfo.UseOAuth = false 127 | return &oauthInfo, nil // errors.New("No OAuth2 provider, you can enable it in config.json") 128 | } 129 | -------------------------------------------------------------------------------- /usermgmt/oauth_feishu.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-03-23 21:02:39 5 | * @Last Modified: U2, 2020-03-23 21:02:39 6 | */ 7 | 8 | package usermgmt 9 | 10 | import ( 11 | "bytes" 12 | "encoding/json" 13 | "fmt" 14 | "net/http" 15 | 16 | "janusec/utils" 17 | 18 | "janusec/data" 19 | "janusec/models" 20 | 21 | "github.com/gorilla/sessions" 22 | "github.com/patrickmn/go-cache" 23 | ) 24 | 25 | type FeishuAccessToken struct { 26 | Code int64 `json:"code"` 27 | Msg string `json:"msg"` 28 | AppAccessToken string `json:"app_access_token"` 29 | Expire int `json:"expire"` 30 | } 31 | 32 | // https://open.feishu.cn/document/ukTMukTMukTM/uEDO4UjLxgDO14SM4gTN 33 | type FeishuUserReqBody struct { 34 | AppAccessToken string `json:"app_access_token"` 35 | GrantType string `json:"grant_type"` 36 | Code string `json:"code"` 37 | } 38 | 39 | // https://open.feishu.cn/document/ukTMukTMukTM/uEDO4UjLxgDO14SM4gTN 40 | type FeishuUser struct { 41 | Code int64 `json:"code"` 42 | Msg string `json:"msg"` 43 | Data FeishuAuthData `json:"data"` 44 | } 45 | 46 | type FeishuAuthData struct { 47 | AccessToken string `json:"access_token"` 48 | EnName string `json:"en_name"` 49 | } 50 | 51 | // Doc: https://open.feishu.cn/document/ukTMukTMukTM/ukzN4UjL5cDO14SO3gTN 52 | // Step 1: GET https://open.feishu.cn/open-apis/authen/v1/index?redirect_uri={REDIRECT_URI}&app_id={APPID}&state={STATE} 53 | // If state==admin, for janusec-admin; else for frontend applications 54 | func FeishuCallbackWithCode(w http.ResponseWriter, r *http.Request) { 55 | // Step 2.1: Callback with code and state, http://gate.janusec.com/?code=BM8k8U6RwtQtNY&state=admin 56 | code := r.FormValue("code") 57 | state := r.FormValue("state") 58 | // Step 2.2: Within Callback, get app_access_token 59 | // Doc: https://open.feishu.cn/document/ukTMukTMukTM/uADN14CM0UjLwQTN 60 | // POST https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal/ 61 | // {"app_id":"cli_slkdasd", "app_secret":"dskLLdkasdKK"} 62 | // accessTokenURL := "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal/" 63 | body := fmt.Sprintf(`{"app_id":"%s", "app_secret":"%s"}`, 64 | data.NodeSetting.AuthConfig.Feishu.AppID, 65 | data.NodeSetting.AuthConfig.Feishu.AppSecret) 66 | request, _ := http.NewRequest("POST", 67 | "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal/", 68 | bytes.NewReader([]byte(body))) 69 | resp, err := utils.GetResponse(request) 70 | if err != nil { 71 | utils.DebugPrintln("FeishuCallbackWithCode GetResponse", err) 72 | } 73 | tokenResponse := FeishuAccessToken{} 74 | err = json.Unmarshal(resp, &tokenResponse) 75 | if err != nil { 76 | utils.DebugPrintln("FeishuCallbackWithCode json.Unmarshal error", err) 77 | } 78 | // Step 2.3: Get User name 79 | // https://open.feishu.cn/document/ukTMukTMukTM/uEDO4UjLxgDO14SM4gTN 80 | userURL := "https://open.feishu.cn/open-apis/authen/v1/access_token" 81 | feishuUserReqBody := FeishuUserReqBody{ 82 | AppAccessToken: tokenResponse.AppAccessToken, 83 | GrantType: "authorization_code", 84 | Code: code, 85 | } 86 | bytesData, _ := json.Marshal(feishuUserReqBody) 87 | request, _ = http.NewRequest("POST", userURL, bytes.NewReader(bytesData)) 88 | request.Header.Set("Content-Type", "application/json") 89 | 90 | resp, err = utils.GetResponse(request) 91 | if err != nil { 92 | utils.DebugPrintln("FeishuCallbackWithCode GetResponse", err) 93 | } 94 | feishuUser := FeishuUser{} 95 | err = json.Unmarshal(resp, &feishuUser) 96 | if err != nil { 97 | utils.DebugPrintln("FeishuCallbackWithCode json.Unmarshal error", err) 98 | } 99 | if state == "admin" { 100 | appUser := data.DAL.SelectAppUserByName(feishuUser.Data.EnName) 101 | var userID int64 102 | if appUser == nil { 103 | // Insert into db if not existed 104 | userID, err = data.DAL.InsertIfNotExistsAppUser(feishuUser.Data.EnName, "", "", "", false, false, false, false) 105 | if err != nil { 106 | w.WriteHeader(403) 107 | w.Write([]byte("Error: " + err.Error())) 108 | return 109 | } 110 | } else { 111 | userID = appUser.ID 112 | } 113 | // create session 114 | authUser := &models.AuthUser{ 115 | UserID: userID, 116 | Username: feishuUser.Data.EnName, 117 | Logged: true, 118 | IsSuperAdmin: appUser.IsSuperAdmin, 119 | IsCertAdmin: appUser.IsCertAdmin, 120 | IsAppAdmin: appUser.IsAppAdmin, 121 | NeedModifyPWD: false} 122 | session, _ := store.Get(r, "sessionid") 123 | session.Values["authuser"] = authUser 124 | session.Options = &sessions.Options{Path: "/janusec-admin/", MaxAge: tokenResponse.Expire} 125 | err = session.Save(r, w) 126 | if err != nil { 127 | utils.DebugPrintln("FeishuCallbackWithCode session save error", err) 128 | } 129 | RecordAuthLog(r, authUser.Username, "Feishu", data.CFG.PrimaryNode.Admin.Portal) 130 | http.Redirect(w, r, data.CFG.PrimaryNode.Admin.Portal, http.StatusTemporaryRedirect) 131 | return 132 | } 133 | // Gateway OAuth for employees and internal application 134 | oauthStateI, found := OAuthCache.Get(state) 135 | if found { 136 | oauthState := oauthStateI.(models.OAuthState) 137 | oauthState.UserID = feishuUser.Data.EnName 138 | oauthState.AccessToken = feishuUser.Data.AccessToken 139 | OAuthCache.Set(state, oauthState, cache.DefaultExpiration) 140 | RecordAuthLog(r, oauthState.UserID, "Feishu", oauthState.CallbackURL) 141 | http.Redirect(w, r, oauthState.CallbackURL, http.StatusTemporaryRedirect) 142 | return 143 | } 144 | http.Redirect(w, r, "/", http.StatusTemporaryRedirect) 145 | } 146 | -------------------------------------------------------------------------------- /data/backend_setting.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2018-07-14 16:24:56 5 | * @Last Modified: U2, 2018-07-14 16:24:56 6 | */ 7 | 8 | package data 9 | 10 | import ( 11 | "janusec/utils" 12 | ) 13 | 14 | const ( 15 | sqlCreateTableIfNotExistsSettings = `CREATE TABLE IF NOT EXISTS "settings"("id" bigserial PRIMARY KEY, "name" VARCHAR(128) NOT NULL,"bool_value" boolean,"int_value" bigint,"float_value" decimal,"string_value" VARCHAR(16384))` 16 | sqlCountSettings = `SELECT COUNT(1) FROM "settings"` 17 | sqlInsertBoolSetting = `INSERT INTO "settings"("id","name","bool_value") VALUES($1,$2,$3)` 18 | sqlInsertIntSetting = `INSERT INTO "settings"("id","name","int_value") VALUES($1,$2,$3)` 19 | sqlInsertFloatSetting = `INSERT INTO "settings"("id","name","float_value") VALUES($1,$2,$3)` 20 | sqlInsertStringSetting = `INSERT INTO "settings"("id","name","string_value") VALUES($1,$2,$3)` 21 | sqlUpdateBoolSetting = `UPDATE "settings" SET "bool_value"=$1 WHERE "name"=$2` 22 | sqlUpdateIntSetting = `UPDATE "settings" SET "int_value"=$1 WHERE "name"=$2` 23 | sqlUpdateFloatSetting = `UPDATE "settings" SET "float_value"=$1 WHERE "name"=$2` 24 | sqlUpdateStringSetting = `UPDATE "settings" SET "string_value"=$1 WHERE "name"=$2` 25 | sqlSelectBoolSetting = `SELECT "bool_value" FROM "settings" WHERE "name"=$1` 26 | sqlSelectIntSetting = `SELECT "int_value" FROM "settings" WHERE "name"=$1` 27 | sqlSelectFloatSetting = `SELECT "float_value" FROM "settings" WHERE "name"=$1` 28 | sqlSelectStringSetting = `SELECT "string_value" FROM "settings" WHERE "name"=$1` 29 | sqlExistsSetting = `SELECT COALESCE((SELECT 1 FROM "settings" WHERE "name"=$1 limit 1),0)` 30 | ) 31 | 32 | // ExistsSetting ... 33 | func (dal *MyDAL) ExistsSetting(name string) bool { 34 | var exist int 35 | err := dal.db.QueryRow(sqlExistsSetting, name).Scan(&exist) 36 | if err != nil { 37 | utils.DebugPrintln("Check ExistsSetting: "+name, err) 38 | } 39 | return exist != 0 40 | } 41 | 42 | // SelectBoolSetting ... 43 | func (dal *MyDAL) SelectBoolSetting(name string) (value bool) { 44 | err := dal.db.QueryRow(sqlSelectBoolSetting, name).Scan(&value) 45 | if err != nil { 46 | utils.DebugPrintln("SelectBoolSetting: "+name, err) 47 | } 48 | return value 49 | } 50 | 51 | // SelectIntSetting ... 52 | func (dal *MyDAL) SelectIntSetting(name string) (value int64) { 53 | err := dal.db.QueryRow(sqlSelectIntSetting, name).Scan(&value) 54 | if err != nil { 55 | utils.DebugPrintln("SelectIntSetting: "+name, err) 56 | } 57 | return value 58 | } 59 | 60 | // SelectFloatSetting ... 61 | func (dal *MyDAL) SelectFloatSetting(name string) (value float64) { 62 | err := dal.db.QueryRow(sqlSelectFloatSetting, name).Scan(&value) 63 | if err != nil { 64 | utils.DebugPrintln("SelectFloatSetting: "+name, err) 65 | } 66 | return value 67 | } 68 | 69 | // SelectStringSetting ... 70 | func (dal *MyDAL) SelectStringSetting(name string) (value string) { 71 | err := dal.db.QueryRow(sqlSelectStringSetting, name).Scan(&value) 72 | if err != nil { 73 | utils.DebugPrintln("SelectStringSetting: "+name, err) 74 | } 75 | return value 76 | } 77 | 78 | // SaveBoolSetting ... 79 | func (dal *MyDAL) SaveBoolSetting(name string, value bool) (err error) { 80 | if dal.ExistsSetting(name) { 81 | _, err = dal.db.Exec(sqlUpdateBoolSetting, value, name) 82 | } else { 83 | id := utils.GenSnowflakeID() 84 | _, err = dal.db.Exec(sqlInsertBoolSetting, id, name, value) 85 | } 86 | if err != nil { 87 | utils.DebugPrintln("SaveBoolSetting: "+name, err) 88 | } 89 | return err 90 | } 91 | 92 | // SaveIntSetting ... 93 | func (dal *MyDAL) SaveIntSetting(name string, value int64) (err error) { 94 | if dal.ExistsSetting(name) { 95 | _, err = dal.db.Exec(sqlUpdateIntSetting, value, name) 96 | } else { 97 | id := utils.GenSnowflakeID() 98 | _, err = dal.db.Exec(sqlInsertIntSetting, id, name, value) 99 | } 100 | if err != nil { 101 | utils.DebugPrintln("SaveIntSetting: "+name, err) 102 | } 103 | return err 104 | } 105 | 106 | // SaveFloatSetting ... 107 | func (dal *MyDAL) SaveFloatSetting(name string, value float64) (err error) { 108 | if dal.ExistsSetting(name) { 109 | _, err = dal.db.Exec(sqlUpdateFloatSetting, value, name) 110 | } else { 111 | id := utils.GenSnowflakeID() 112 | _, err = dal.db.Exec(sqlInsertFloatSetting, id, name, value) 113 | } 114 | if err != nil { 115 | utils.DebugPrintln("SaveFloatSetting: "+name, err) 116 | } 117 | return err 118 | } 119 | 120 | // SaveStringSetting ... 121 | func (dal *MyDAL) SaveStringSetting(name string, value string) (err error) { 122 | if dal.ExistsSetting(name) { 123 | _, err = dal.db.Exec(sqlUpdateStringSetting, value, name) 124 | } else { 125 | id := utils.GenSnowflakeID() 126 | _, err = dal.db.Exec(sqlInsertStringSetting, id, name, value) 127 | } 128 | if err != nil { 129 | utils.DebugPrintln("SaveStringSetting: "+name, err) 130 | } 131 | return err 132 | } 133 | 134 | // CreateTableIfNotExistsSettings ... 135 | func (dal *MyDAL) CreateTableIfNotExistsSettings() error { 136 | _, err := dal.db.Exec(sqlCreateTableIfNotExistsSettings) 137 | if err != nil { 138 | utils.DebugPrintln("CreateTableIfNotExistsSettings", err) 139 | } 140 | return err 141 | } 142 | 143 | // CountSettings ... 144 | func (dal *MyDAL) CountSettings() int64 { 145 | var settingsCount int64 146 | err := dal.db.QueryRow(sqlCountSettings).Scan(&settingsCount) 147 | if err != nil { 148 | utils.DebugPrintln("CountSettings", err) 149 | } 150 | return settingsCount 151 | } 152 | -------------------------------------------------------------------------------- /usermgmt/oauth_lark.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-03-23 21:02:39 5 | * @Last Modified: U2, 2020-03-23 21:02:39 6 | */ 7 | 8 | package usermgmt 9 | 10 | import ( 11 | "bytes" 12 | "encoding/json" 13 | "fmt" 14 | "net/http" 15 | 16 | "janusec/utils" 17 | 18 | "janusec/data" 19 | "janusec/models" 20 | 21 | "github.com/gorilla/sessions" 22 | "github.com/patrickmn/go-cache" 23 | ) 24 | 25 | type LarkAccessToken struct { 26 | Code int64 `json:"code"` 27 | Msg string `json:"msg"` 28 | AppAccessToken string `json:"app_access_token"` 29 | Expire int `json:"expire"` 30 | } 31 | 32 | // https://open.larksuite.com/document/ukTMukTMukTM/uEDO4UjLxgDO14SM4gTN 33 | type LarkUserReqBody struct { 34 | AppAccessToken string `json:"app_access_token"` 35 | GrantType string `json:"grant_type"` 36 | Code string `json:"code"` 37 | } 38 | 39 | // https://open.larksuite.com/document/uMzMyEjLzMjMx4yMzITM/ukTN0EjL5UDNx4SO1QTM 40 | type LarkUser struct { 41 | Code int64 `json:"code"` 42 | Msg string `json:"msg"` 43 | Data LarkAuthData `json:"data"` 44 | } 45 | 46 | type LarkAuthData struct { 47 | AccessToken string `json:"access_token"` 48 | EnName string `json:"en_name"` 49 | } 50 | 51 | // Doc: https://open.larksuite.com/document/uMzMyEjLzMjMx4yMzITM/ugTN0EjL4UDNx4CO1QTM 52 | // Step 1: GET https://open.larksuite.com/open-apis/authen/v1/index?redirect_uri={REDIRECT_URI}&app_id={APPID}&state={STATE} 53 | // If state==admin, for janusec-admin; else for frontend applications 54 | func LarkCallbackWithCode(w http.ResponseWriter, r *http.Request) { 55 | // Step 2.1: Callback with code and state, http://gate.janusec.com/?code=BM8k8U6RwtQtNY&state=admin 56 | code := r.FormValue("code") 57 | state := r.FormValue("state") 58 | // Step 2.2: Within Callback, get app_access_token 59 | // Doc: https://open.larksuite.com/document/uMzMyEjLzMjMx4yMzITM/uMjN0EjLzYDNx4yM2QTM 60 | // POST https://open.larksuite.com/open-apis/auth/v3/app_access_token/internal/ 61 | // {"app_id":"cli_slkdasd", "app_secret":"dskLLdkasdKK"} 62 | // accessTokenURL := "https://open.larksuite.com/open-apis/auth/v3/app_access_token/internal/" 63 | body := fmt.Sprintf(`{"app_id":"%s", "app_secret":"%s"}`, 64 | data.NodeSetting.AuthConfig.Lark.AppID, 65 | data.NodeSetting.AuthConfig.Lark.AppSecret) 66 | request, _ := http.NewRequest("POST", 67 | "https://open.larksuite.com/open-apis/auth/v3/app_access_token/internal", 68 | bytes.NewReader([]byte(body))) 69 | resp, err := utils.GetResponse(request) 70 | if err != nil { 71 | utils.DebugPrintln("LarkCallbackWithCode GetResponse", err) 72 | } 73 | tokenResponse := LarkAccessToken{} 74 | err = json.Unmarshal(resp, &tokenResponse) 75 | if err != nil { 76 | utils.DebugPrintln("LarkCallbackWithCode json.Unmarshal error", err) 77 | } 78 | // Step 2.3: Get User name 79 | // https://open.larksuite.com/document/uMzMyEjLzMjMx4yMzITM/ukTN0EjL5UDNx4SO1QTM 80 | userURL := "https://open.larksuite.com/open-apis/authen/v1/access_token" 81 | larkUserReqBody := LarkUserReqBody{ 82 | AppAccessToken: tokenResponse.AppAccessToken, 83 | GrantType: "authorization_code", 84 | Code: code, 85 | } 86 | bytesData, err := json.Marshal(larkUserReqBody) 87 | if err != nil { 88 | utils.DebugPrintln("LarkCallbackWithCode json.Marshal", err) 89 | } 90 | request, err = http.NewRequest("POST", userURL, bytes.NewReader(bytesData)) 91 | if err != nil { 92 | utils.DebugPrintln("LarkCallbackWithCode http.NewRequest", err) 93 | } 94 | request.Header.Set("Content-Type", "application/json") 95 | 96 | resp, err = utils.GetResponse(request) 97 | if err != nil { 98 | utils.DebugPrintln("LarkCallbackWithCode GetResponse", err) 99 | } 100 | larkUser := LarkUser{} 101 | err = json.Unmarshal(resp, &larkUser) 102 | if err != nil { 103 | utils.DebugPrintln("LarkCallbackWithCode json.Unmarshal error", err) 104 | } 105 | if state == "admin" { 106 | appUser := data.DAL.SelectAppUserByName(larkUser.Data.EnName) 107 | var userID int64 108 | if appUser == nil { 109 | // Insert into db if not existed 110 | userID, err = data.DAL.InsertIfNotExistsAppUser(larkUser.Data.EnName, "", "", "", false, false, false, false) 111 | if err != nil { 112 | w.WriteHeader(403) 113 | w.Write([]byte("Error: " + err.Error())) 114 | return 115 | } 116 | } else { 117 | userID = appUser.ID 118 | } 119 | // create session 120 | authUser := &models.AuthUser{ 121 | UserID: userID, 122 | Username: larkUser.Data.EnName, 123 | Logged: true, 124 | IsSuperAdmin: appUser.IsSuperAdmin, 125 | IsCertAdmin: appUser.IsCertAdmin, 126 | IsAppAdmin: appUser.IsAppAdmin, 127 | NeedModifyPWD: false} 128 | session, _ := store.Get(r, "sessionid") 129 | session.Values["authuser"] = authUser 130 | session.Options = &sessions.Options{Path: "/janusec-admin/", MaxAge: tokenResponse.Expire} 131 | err = session.Save(r, w) 132 | if err != nil { 133 | utils.DebugPrintln("LarkCallbackWithCode session save error", err) 134 | } 135 | RecordAuthLog(r, authUser.Username, "Lark", data.CFG.PrimaryNode.Admin.Portal) 136 | http.Redirect(w, r, data.CFG.PrimaryNode.Admin.Portal, http.StatusTemporaryRedirect) 137 | return 138 | } 139 | // Gateway OAuth for employees and internal application 140 | oauthStateI, found := OAuthCache.Get(state) 141 | if found { 142 | oauthState := oauthStateI.(models.OAuthState) 143 | oauthState.UserID = larkUser.Data.EnName 144 | oauthState.AccessToken = larkUser.Data.AccessToken 145 | OAuthCache.Set(state, oauthState, cache.DefaultExpiration) 146 | RecordAuthLog(r, oauthState.UserID, "Lark", oauthState.CallbackURL) 147 | http.Redirect(w, r, oauthState.CallbackURL, http.StatusTemporaryRedirect) 148 | return 149 | } 150 | http.Redirect(w, r, "/", http.StatusTemporaryRedirect) 151 | } 152 | -------------------------------------------------------------------------------- /gateway/webssh.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Copyright Reserved By Janusec (https://www.janusec.com/). 3 | * @Author: U2 4 | * @Date: 2020-02-10 22:07:47 5 | * @Last Modified: U2, 2020-02-10 22:07:47 6 | */ 7 | 8 | package gateway 9 | 10 | import ( 11 | "bytes" 12 | "encoding/json" 13 | "errors" 14 | "io" 15 | "log" 16 | "net/http" 17 | "time" 18 | 19 | "janusec/data" 20 | 21 | "janusec/usermgmt" 22 | "janusec/utils" 23 | 24 | "github.com/gorilla/websocket" 25 | "golang.org/x/crypto/ssh" 26 | ) 27 | 28 | // HostInfo : the information of remote Host 29 | type HostInfo struct { 30 | IP string `json:"ip"` 31 | Port string `json:"port"` 32 | Username string `json:"username"` 33 | Password string `json:"password"` 34 | } 35 | 36 | // SSH build connection 37 | func SSH(sshInput *io.WriteCloser, sshOutput *io.Reader, host *HostInfo, errChan chan<- error) { 38 | sshClient, err := ssh.Dial("tcp", host.IP+":"+host.Port, &ssh.ClientConfig{ 39 | User: host.Username, 40 | Auth: []ssh.AuthMethod{ssh.Password(host.Password)}, 41 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), 42 | }) 43 | if err != nil { 44 | errChan <- err 45 | utils.DebugPrintln("errChan", err) 46 | return 47 | } 48 | sshSession, err := sshClient.NewSession() 49 | if err != nil { 50 | utils.DebugPrintln("new ssh session", err) 51 | } 52 | defer sshSession.Close() 53 | *sshInput, err = sshSession.StdinPipe() 54 | if err != nil { 55 | utils.DebugPrintln("sshInput", err) 56 | } 57 | *sshOutput, err = sshSession.StdoutPipe() 58 | if err != nil { 59 | utils.DebugPrintln("sshOuput", err) 60 | } 61 | errChan <- err 62 | modes := ssh.TerminalModes{ 63 | ssh.ECHO: 1, 64 | ssh.TTY_OP_ISPEED: 14400, 65 | ssh.TTY_OP_OSPEED: 14400, 66 | } 67 | err = sshSession.RequestPty("xterm", 40, 120, modes) 68 | if err != nil { 69 | utils.DebugPrintln("request pty", err) 70 | } 71 | err = sshSession.Shell() 72 | if err != nil { 73 | utils.DebugPrintln("start shell", err) 74 | } 75 | err = sshSession.Wait() 76 | errChan <- err 77 | } 78 | 79 | // RoutineOutput update the console display 80 | func RoutineOutput(outputTicker *time.Ticker, wsConn *websocket.Conn, sshOutput *io.Reader) { 81 | for range outputTicker.C { 82 | cmdOutput := make([]byte, 1024*10) 83 | n, err := (*sshOutput).Read(cmdOutput) 84 | if err != nil { 85 | // EOF 86 | return 87 | } 88 | if n > 0 { 89 | err := wsConn.WriteMessage(websocket.TextMessage, cmdOutput) 90 | if err != nil { 91 | return 92 | } 93 | } 94 | } 95 | } 96 | 97 | // WebSSHHandlerFunc Handle Web SSH 98 | func WebSSHHandlerFunc(w http.ResponseWriter, r *http.Request) { 99 | var isLogin bool 100 | isLogin, _ = usermgmt.IsLogIn(w, r) 101 | if !isLogin { 102 | GenResponseByObject(w, nil, errors.New("please login")) 103 | return 104 | } 105 | username := usermgmt.GetLoginUsername(r) 106 | var sshInput io.WriteCloser 107 | var sshOutput io.Reader //bytes.Buffer 108 | upgrader := websocket.Upgrader{ 109 | ReadBufferSize: 1024, 110 | WriteBufferSize: 1024 * 10, 111 | CheckOrigin: func(r *http.Request) bool { 112 | return true 113 | }, 114 | } 115 | wsConn, err := upgrader.Upgrade(w, r, nil) 116 | // websocket.Upgrade deprecated, add upgrader.Upgrade above, v1.2.0 117 | // wsConn, err := websocket.Upgrade(w, r, nil, 1024, 1024*10) 118 | if err != nil { 119 | log.Println("upgrade:", err) 120 | return 121 | } 122 | defer wsConn.Close() 123 | // Read SSH Parameters 124 | _, msg, err2 := wsConn.ReadMessage() 125 | if err2 != nil { 126 | utils.DebugPrintln("ReadMessage SSH Parameters Error:", err2) 127 | return 128 | } 129 | if !data.PrimarySetting.WebSSHEnabled { 130 | err = wsConn.WriteMessage(websocket.TextMessage, []byte("WebSSH disabled in settings!\r\n")) 131 | if err != nil { 132 | utils.DebugPrintln("WebSSHHandlerFunc wsConn.WriteMessage error", err) 133 | } 134 | return 135 | } 136 | var host HostInfo 137 | err = json.Unmarshal(msg, &host) 138 | if err != nil { 139 | utils.DebugPrintln("WebSSHHandlerFunc json.Unmarshal error", err) 140 | } 141 | if err = wsConn.WriteMessage(websocket.TextMessage, []byte("Connecting "+host.IP+":"+host.Port+" ... Please wait a moment!\r\n")); err != nil { 142 | return 143 | } 144 | errChan := make(chan error) 145 | go SSH(&sshInput, &sshOutput, &host, errChan) 146 | err = <-errChan 147 | if err != nil { 148 | err2 := wsConn.WriteMessage(websocket.TextMessage, []byte(err.Error())) 149 | if err2 != nil { 150 | utils.DebugPrintln("WebSSHHandlerFunc wsConn.WriteMessage error", err2) 151 | } 152 | return 153 | } 154 | var logBuf bytes.Buffer 155 | outputTicker := time.NewTicker(100 * time.Millisecond) 156 | go RoutineOutput(outputTicker, wsConn, &sshOutput) 157 | for { 158 | select { 159 | case <-errChan: 160 | err2 := wsConn.WriteMessage(websocket.TextMessage, []byte(err.Error())) 161 | if err2 != nil { 162 | utils.DebugPrintln("WebSSHHandlerFunc wsConn.WriteMessage error", err2) 163 | } 164 | return 165 | default: 166 | if wsConn == nil { 167 | return 168 | } 169 | _, msg, err2 := wsConn.ReadMessage() 170 | if err2 != nil { 171 | return 172 | } 173 | //log.Printf("Received: %s %v\n", string(msg), msg) 174 | if sshInput != nil { 175 | go CmdLog(&logBuf, username, &host, &msg) 176 | if _, err = sshInput.Write(msg); err != nil { 177 | return 178 | } 179 | } 180 | } 181 | } 182 | } 183 | 184 | // CmdLog write to log files 185 | func CmdLog(logBuf *bytes.Buffer, username string, host *HostInfo, cmdChars *[]byte) { 186 | for i := 0; i < len(*cmdChars); i++ { 187 | cmdChar := (*cmdChars)[i] 188 | switch cmdChar { 189 | case '\r', '\n': 190 | cmdStr := logBuf.String() 191 | hostInfo := host.Username + "@" + host.IP + ":" + host.Port 192 | utils.DebugPrintln("WebSSH User:", username, hostInfo, "Command:", cmdStr) 193 | logBuf.Reset() 194 | default: 195 | logBuf.WriteByte(cmdChar) 196 | } 197 | } 198 | } 199 | --------------------------------------------------------------------------------