From b0852cb01cf2d026522d831ef3f43eab9a9b7697 Mon Sep 17 00:00:00 2001 From: Jenna Schwartz Date: Tue, 9 Apr 2019 21:33:12 -0400 Subject: [PATCH 1/3] alert system in place for SSH logins. --- config.example.yml | 12 ++-- config/alertsystem.go | 101 +++++++++++++++++++++++++++ config/deque.go | 155 ++++++++++++++++++++++++++++++++++++++++++ config/main.go | 14 ++++ config/structs.go | 13 ++++ docker-compose.yml | 21 ++++++ ssh/server.go | 31 ++++++++- web/auth.go | 7 +- web/authrules.go | 6 +- web/handlers.go | 2 +- web/livesessions.go | 10 +-- web/main.go | 47 ++++++------- web/sessions.go | 2 +- web/templates.go | 14 ++-- web/users.go | 6 +- 15 files changed, 389 insertions(+), 52 deletions(-) create mode 100644 config/alertsystem.go create mode 100644 config/deque.go create mode 100644 docker-compose.yml diff --git a/config.example.yml b/config.example.yml index 555b532..ce345c1 100644 --- a/config.example.yml +++ b/config.example.yml @@ -1,11 +1,11 @@ cookiesecret: s0secure dbinfo: host: localhost - name: example - pass: example + name: JennaSchwartz + pass: JennasPassword port: 3306 sqlite: true - user: example + user: jenna debug: db: enabled: true @@ -32,12 +32,12 @@ multihost: hostname: "" ip: "" oauthcredentials: - clientid: 0000000000000-foobar.apps.googleusercontent.com - clientsecret: foobar + clientid: 410608294662-ohk6vtn9hn97f3jm1au2kdecd3m57t9u.apps.googleusercontent.com + clientsecret: 3X72QfdjjXwFmHCalTlLvtbt endpoint: authurl: https://accounts.google.com/o/oauth2/auth tokenurl: https://accounts.google.com/o/oauth2/token - redirecturl: https://auth.example.com + redirecturl: http://localhost:8080 scopes: - https://www.googleapis.com/auth/userinfo.email otp: diff --git a/config/alertsystem.go b/config/alertsystem.go new file mode 100644 index 0000000..8258afc --- /dev/null +++ b/config/alertsystem.go @@ -0,0 +1,101 @@ +package config + +import ( + "math" + "time" +) + +func Alert(c chan AlertInfo, env *Env) { + go listenForLoginAlert(c, env) +} + +func listenForLoginAlert(c chan AlertInfo, env *Env) { + // key is username, value is last login time + sshLoginData := make(map[string]time.Time) + // key is string version of IP network address and val is bool + seenNetworks := make(map[string]bool) + sshLoginThresholdData := New() + const maxDataPoints = 1000000 + var doAlert bool + + for alertInfo := range c { + networkBeenSeen := seenNetworks[alertInfo.IP.String()] + if !networkBeenSeen { + alertInfo.NewNetwork = true + doAlert = true + } + if lastLoginTime, ok := sshLoginData[alertInfo.User]; ok { + timeSince := float64(alertInfo.Timestamp.Sub(lastLoginTime)) + + addToDeque(sshLoginThresholdData, timeSince, maxDataPoints) + mean := float64(mean(sshLoginThresholdData)) + threshold := (3 * stdev(sshLoginThresholdData, mean)) + mean + + if timeSince > threshold { + alertInfo.BeenAWhile = true + doAlert = true + } + } else { // first login for this user + alertInfo.FirstLogin = true + doAlert = true + } + sshLoginData[alertInfo.User] = alertInfo.Timestamp + if doAlert || !alertInfo.Success { + go printAlert(alertInfo, env) + } + } +} + +// deque is fixed size so it doesn't just keep growing infinitely. +func addToDeque(deque *Deque, timeSince float64, maxDataPoints int) { + if deque.Size() >= maxDataPoints { + deque.PopRight() + } + deque.PushLeft(timeSince) +} + +func mean(deque *Deque) float64 { + sum := 0.0 + for idx := 0; idx < deque.Size(); idx++ { + val := deque.PopRight().(float64) + sum += float64(val) + deque.PushLeft(val) + } + return sum / float64(deque.Size()) +} + +func stdev(deque *Deque, mean float64) float64 { + sum := 0.0 + for idx := 0; idx < deque.Size(); idx++ { + val := deque.PopRight().(float64) + sum += (mean - val) * (mean - val) + deque.PushLeft(val) + } + return math.Sqrt(sum / float64(deque.Size())) +} + +func printAlert(alertInfo AlertInfo, env *Env) { + //TODO: Test this file with smaller unit tests on each function, main function. + // TODO: practice 1-min and 3-min pitch. + // TODO: update poster with comments and submit for printing. + alertString := "ALERT!\n" + if alertInfo.NewNetwork { + alertString += "User just attempted connection from a new network.\n" + } + if !alertInfo.Success { + alertString += "User unsuccessfully attempted SSH connection.\n" + } + if alertInfo.BeenAWhile { + alertString += "This is the first time this user has attempted SSH connection in a long time.\n" + } + if alertInfo.FirstLogin { + alertString += "This user has never attempted SSH connection before.\n" + } + alertString += "SSH login details for this alert:\n" + alertString += "User: " + alertInfo.User + "\n" + alertString += "Timestamp: " + alertInfo.Timestamp.Format("Mon Jan _2 15:04:05 2006") + "\n" + alertString += "Network IP: " + alertInfo.IP.String() + "\n" + alertString += "If this information is expected, you may ignore this alert." + // atomic. + env.Blue.Println(alertString) +} diff --git a/config/deque.go b/config/deque.go new file mode 100644 index 0000000..bc3a619 --- /dev/null +++ b/config/deque.go @@ -0,0 +1,155 @@ +// CookieJar - A contestant's algorithm toolbox +// Copyright (c) 2013 Peter Szilagyi. All rights reserved. +// +// CookieJar is dual licensed: you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later +// version. +// +// The toolbox is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// Alternatively, the CookieJar toolbox may be used in accordance with the terms +// and conditions contained in a signed written agreement between you and the +// author(s). + +// Package deque implements a double ended queue supporting arbitrary types +// (even a mixture). +// +// Internally it uses a dynamically growing circular slice of blocks, resulting +// in faster resizes than a simple dynamic array/slice would allow, yet less gc +// overhead. +package config + +// The size of a block of data +const blockSize = 4096 + +// Double ended queue data structure. +type Deque struct { + leftIdx int + leftOff int + rightIdx int + rightOff int + + blocks [][]interface{} + left []interface{} + right []interface{} +} + +// Creates a new, empty deque. +func New() *Deque { + result := new(Deque) + result.blocks = [][]interface{}{make([]interface{}, blockSize)} + result.right = result.blocks[0] + result.left = result.blocks[0] + return result +} + +// Pushes a new element into the queue from the right, expanding it if necessary. +func (d *Deque) PushRight(data interface{}) { + d.right[d.rightOff] = data + d.rightOff++ + if d.rightOff == blockSize { + d.rightOff = 0 + d.rightIdx = (d.rightIdx + 1) % len(d.blocks) + + // If we wrapped over to the left, insert a new block and update indices + if d.rightIdx == d.leftIdx { + buffer := make([][]interface{}, len(d.blocks)+1) + copy(buffer[:d.rightIdx], d.blocks[:d.rightIdx]) + buffer[d.rightIdx] = make([]interface{}, blockSize) + copy(buffer[d.rightIdx+1:], d.blocks[d.rightIdx:]) + d.blocks = buffer + d.leftIdx++ + d.left = d.blocks[d.leftIdx] + } + d.right = d.blocks[d.rightIdx] + } +} + +// Pops out an element from the queue from the right. Note, no bounds checking are done. +func (d *Deque) PopRight() (res interface{}) { + d.rightOff-- + if d.rightOff < 0 { + d.rightOff = blockSize - 1 + d.rightIdx = (d.rightIdx - 1 + len(d.blocks)) % len(d.blocks) + d.right = d.blocks[d.rightIdx] + } + res, d.right[d.rightOff] = d.right[d.rightOff], nil + return +} + +// Returns the rightmost element from the deque. No bounds are checked. +func (d *Deque) Right() interface{} { + if d.rightOff > 0 { + return d.right[d.rightOff-1] + } else { + return d.blocks[(d.rightIdx-1+len(d.blocks))%len(d.blocks)][blockSize-1] + } +} + +// Pushes a new element into the queue from the left, expanding it if necessary. +func (d *Deque) PushLeft(data interface{}) { + d.leftOff-- + if d.leftOff < 0 { + d.leftOff = blockSize - 1 + d.leftIdx = (d.leftIdx - 1 + len(d.blocks)) % len(d.blocks) + + // If we wrapped over to the right, insert a new block and update indices + if d.leftIdx == d.rightIdx { + d.leftIdx++ + buffer := make([][]interface{}, len(d.blocks)+1) + copy(buffer[:d.leftIdx], d.blocks[:d.leftIdx]) + buffer[d.leftIdx] = make([]interface{}, blockSize) + copy(buffer[d.leftIdx+1:], d.blocks[d.leftIdx:]) + d.blocks = buffer + } + d.left = d.blocks[d.leftIdx] + } + d.left[d.leftOff] = data +} + +// Pops out an element from the queue from the left. Note, no bounds checking are done. +func (d *Deque) PopLeft() (res interface{}) { + res, d.left[d.leftOff] = d.left[d.leftOff], nil + d.leftOff++ + if d.leftOff == blockSize { + d.leftOff = 0 + d.leftIdx = (d.leftIdx + 1) % len(d.blocks) + d.left = d.blocks[d.leftIdx] + } + return +} + +// Returns the leftmost element from the deque. No bounds are checked. +func (d *Deque) Left() interface{} { + return d.left[d.leftOff] +} + +// Checks whether the queue is empty. +func (d *Deque) Empty() bool { + return d.leftIdx == d.rightIdx && d.leftOff == d.rightOff +} + +// Returns the number of elements in the queue. +func (d *Deque) Size() int { + if d.rightIdx > d.leftIdx { + return (d.rightIdx-d.leftIdx)*blockSize - d.leftOff + d.rightOff + } else if d.rightIdx < d.leftIdx { + return (len(d.blocks)-d.leftIdx+d.rightIdx)*blockSize - d.leftOff + d.rightOff + } else { + return d.rightOff - d.leftOff + } +} + +// Clears out the contents of the queue. +func (d *Deque) Reset() { + d.leftIdx = 0 + d.rightIdx = 0 + d.leftOff = 0 + d.rightOff = 0 + d.left = d.blocks[0] + d.right = d.blocks[0] +} diff --git a/config/main.go b/config/main.go index 7e15ab5..a942353 100644 --- a/config/main.go +++ b/config/main.go @@ -12,6 +12,8 @@ import ( "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" // Load MySQL for GORM _ "github.com/jinzhu/gorm/dialects/sqlite" // Load SQLite for GORM + + // "github.com/notion/bastion/alertsystem" "github.com/spf13/viper" "google.golang.org/api/option" ) @@ -20,6 +22,12 @@ const configFile = "config.yml" // Load initializes the Env pointer with data from the database and elsewhere func Load(forceCerts bool, webAddr string, sshAddr string, sshProxyAddr string, monAddr string) *Env { + fmt.Println(forceCerts) + fmt.Println(webAddr) + fmt.Println(sshAddr) + fmt.Println(sshProxyAddr) + fmt.Println(monAddr) + vconfig := viper.New() vconfig.SetConfigFile(configFile) @@ -80,6 +88,8 @@ func Load(forceCerts bool, webAddr string, sshAddr string, sshProxyAddr string, logsBucket = storageClient.Bucket(bucketName) } + alertChan := make(chan AlertInfo) + env := &Env{ ForceGeneration: forceCerts, PKPassphrase: vconfig.GetString("pkpassphrase"), @@ -95,12 +105,16 @@ func Load(forceCerts bool, webAddr string, sshAddr string, sshProxyAddr string, Yellow: yellow, Blue: blue, Magenta: magenta, + AlertChannel: alertChan, HTTPPort: webAddr, SSHPort: sshAddr, SSHProxyPort: sshProxyAddr, MonPort: monAddr, } + Alert(alertChan, env) + env.Blue.Printf("started configuration") + if vconfig.GetBool("multihost.enabled") { db.Delete(LiveSession{}, "bastion = ?", GetOutboundIP(env).String()+env.HTTPPort) } diff --git a/config/structs.go b/config/structs.go index 67fd4e8..a2d5458 100644 --- a/config/structs.go +++ b/config/structs.go @@ -29,12 +29,25 @@ type Env struct { Yellow *ColorLog Blue *ColorLog Magenta *ColorLog + AlertChannel chan AlertInfo SSHPort string SSHProxyPort string HTTPPort string MonPort string } +//used for email alerts on new logins, unsuccessful logins, and auths that occur after a long period of inactivity. +type AlertInfo struct { + User string + IP net.IP + Timestamp time.Time + LoginType string + Success bool + FirstLogin bool + NewNetwork bool + BeenAWhile bool +} + // WsClient is a struct that contains a websockets underlying data object type WsClient struct { Client *websocket.Conn diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..913820d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +version: '3.7' + +services: + bastion: + build: ./ + ports: + - "5222:5222" + - "8080:8080" + volumes: + - ./config.example.yml:/go/src/github.com/notion/bastion/config.example.yml + - ./bastion.db:/go/src/github.com/notion/bastion/bastion.db + ssh: + image: rastasheep/ubuntu-sshd + command: | + /bin/bash -c " + apt-get update && apt-get install net-tools + echo 'TrustedUserCAKeys /etc/ssh/ca_keys' >> /etc/ssh/sshd_config + echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrDZWuJDdOoW+0xpu+hyVqTGo5bmdQL3bZvtOLtbrLBIdrtGIAelSJpJVmjKceenYSoFPEAeSLklI1U0VEkLDRRaNt8MPBhTl8JHiaiLT3PizwKxXFBdN8lEIMQvyxeOEarxV4/Xi8qCS+sXEqpLs/IOY/83gs2eHuw6l38Z9KNd1KEyiQ2WHi6AnxXSuMTdHRmzxMBNnK77qPrQ+2ILgtZKiYDJdoNZU/Z+AEFz2KGlIUl71zxqaOebXf9fVKIGo+oFA3LhxtlfNCmY09U3Gfte6K9vLon+fAqXpwVIpkckLyAwDhffiS2g5KoAfAD2EnsSBjXpzL+9mPXlcoreM9' > /etc/ssh/ca_keys + ifconfig + /usr/sbin/sshd -D + " \ No newline at end of file diff --git a/ssh/server.go b/ssh/server.go index ddc19d9..02ed12d 100644 --- a/ssh/server.go +++ b/ssh/server.go @@ -11,7 +11,7 @@ import ( "time" "github.com/notion/bastion/proxyprotocol" - + // "github.com/notion/bastion/alertsystem" "github.com/notion/bastion/config" "golang.org/x/crypto/ssh" ) @@ -137,6 +137,35 @@ func handleSession(newChannel ssh.NewChannel, SSHConn *ssh.ServerConn, proxyAddr serverClient.ProxyTo = host rawProxyConn, err := net.Dial("tcp", proxyAddr) + + ipAddr := rawProxyConn.RemoteAddr().(*net.TCPAddr).IP + var network net.IP + if ipAddr.To4() != nil { + mask := net.CIDRMask(24, 32) + network = ipAddr.Mask(mask) + } else { + mask := net.CIDRMask(48, 128) + network = ipAddr.Mask(mask) + } + + alertInfo := &config.AlertInfo{ + User: SSHConn.User(), + IP: network, + Timestamp: time.Now(), + LoginType: "ssh", + Success: true, + FirstLogin: false, + NewNetwork: false, + BeenAWhile: false, + } + + if !authed { + alertInfo.Success = false + env.AlertChannel <- *alertInfo + } else { + env.AlertChannel <- *alertInfo + } + if err != nil { env.Red.Println("Unable to establish connection to TCP Socket:", err) serverClient.Errors = append(serverClient.Errors, fmt.Errorf("Unable to establish remote TCP Socket: %s", err)) diff --git a/web/auth.go b/web/auth.go index 03a2e40..5bc4a56 100644 --- a/web/auth.go +++ b/web/auth.go @@ -37,7 +37,10 @@ func authMiddleware(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { path := strings.TrimSpace(c.Request.URL.Path) session := sessions.Default(c) + // session.Set("loggedin", true) + // session.Set("otpauthed", true) + // session.Set("user", nil) auth := session.Get("loggedin") otpAuth := session.Get("otpauthed") if otpAuth != nil { @@ -71,7 +74,7 @@ func authMiddleware(env *config.Env) func(c *gin.Context) { } } -func checkOtp(env *config.Env) func(c *gin.Context) { +func CheckOtp(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) sessionUser := session.Get("user").(*config.User) @@ -117,7 +120,7 @@ func checkOtp(env *config.Env) func(c *gin.Context) { } } -func setupotp(env *config.Env) func(c *gin.Context) { +func SetupOtp(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) sessionUser := session.Get("user").(*config.User) diff --git a/web/authrules.go b/web/authrules.go index aace4db..e79e88e 100644 --- a/web/authrules.go +++ b/web/authrules.go @@ -7,7 +7,7 @@ import ( "github.com/notion/bastion/config" ) -func authRule(env *config.Env) func(c *gin.Context) { +func AuthRule(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { retData := make(map[string]interface{}) var authRules []config.AuthRules @@ -42,7 +42,7 @@ func createAuthRule(env *config.Env) func(c *gin.Context) { } } -func deleteAuthRule(env *config.Env) func(c *gin.Context) { +func DeleteAuthRule(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { id, _ := c.Params.Get("id") var authRule config.AuthRules @@ -54,7 +54,7 @@ func deleteAuthRule(env *config.Env) func(c *gin.Context) { } } -func updateAuthRule(env *config.Env) func(c *gin.Context) { +func UpdateAuthRule(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { id, _ := c.Params.Get("id") diff --git a/web/handlers.go b/web/handlers.go index 367823a..9b2e465 100644 --- a/web/handlers.go +++ b/web/handlers.go @@ -160,7 +160,7 @@ func indexIAP(env *config.Env, conf oauth2.Config) func(c *gin.Context) { } } -func logout(env *config.Env) func(c *gin.Context) { +func Logout(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) diff --git a/web/livesessions.go b/web/livesessions.go index 4c3fca1..cfd9b77 100644 --- a/web/livesessions.go +++ b/web/livesessions.go @@ -15,7 +15,7 @@ import ( "github.com/notion/bastion/config" ) -func liveSession(env *config.Env) func(c *gin.Context) { +func LiveSession(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { if env.Vconfig.GetBool("multihost.enabled") { liveSessionMultiHost(env)(c) @@ -207,7 +207,7 @@ func liveSessionMultiHost(env *config.Env) func(c *gin.Context) { } } -func disconnectLiveSession(env *config.Env) func(c *gin.Context) { +func DisconnectLiveSession(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { authcode, pathKey, sidKey := getLiveSessionParams(c) @@ -272,13 +272,13 @@ func disconnectLiveSessionMultiHost(env *config.Env) func(c *gin.Context) { c.AbortWithStatusJSON(http.StatusUnauthorized, map[string]interface{}{"status": false, "error": "Invalid auth code."}) return } else { - disconnectLiveSession(env)(c) + DisconnectLiveSession(env)(c) return } } } -func liveSessionWS(env *config.Env) func(c *gin.Context) { +func LiveSessionWS(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { authcode, pathKey, sidKey := getLiveSessionParams(c) @@ -402,7 +402,7 @@ func liveSessionWSMultiHost(env *config.Env) func(c *gin.Context) { c.AbortWithStatusJSON(http.StatusUnauthorized, map[string]interface{}{"status": false, "error": "Invalid auth code."}) return } else { - liveSessionWS(env)(c) + LiveSessionWS(env)(c) return } } diff --git a/web/main.go b/web/main.go index 17b2dcd..c10638f 100644 --- a/web/main.go +++ b/web/main.go @@ -1,11 +1,12 @@ package web import ( - "github.com/gin-contrib/static" "encoding/gob" "net/http" "runtime" + "github.com/gin-contrib/static" + "github.com/gin-contrib/pprof" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" @@ -52,44 +53,44 @@ func Serve(addr string, env *config.Env) { authedGroup := r.Group("/", authMiddleware(env)) { authedGroup.GET("", index(env, oauthConfig)) - authedGroup.GET("/logout", logout(env)) - authedGroup.GET("/sessions", sessionTempl(env)) - authedGroup.GET("/livesessions", liveSessionTempl(env)) - authedGroup.GET("/users", userTempl(env)) - authedGroup.GET("/authrules", authRuleTempl(env)) - authedGroup.GET("/noaccess", noaccessTempl(env)) - authedGroup.GET("/otp", otpTempl(env)) - authedGroup.GET("/setupotp", setupOtpTempl(env)) + authedGroup.GET("/logout", Logout(env)) + authedGroup.GET("/sessions", SessionTempl(env)) + authedGroup.GET("/livesessions", LiveSessionTempl(env)) + authedGroup.GET("/users", UserTempl(env)) + authedGroup.GET("/authrules", AuthRuleTempl(env)) + authedGroup.GET("/noaccess", NoaccessTempl(env)) + authedGroup.GET("/otp", OtpTempl(env)) + authedGroup.GET("/setupotp", SetupOtpTempl(env)) apiGroup := authedGroup.Group("/api") { - apiGroup.GET("/livesessions", liveSession(env)) + apiGroup.GET("/livesessions", LiveSession(env)) userGroup := apiGroup.Group("/users") { - userGroup.GET("", user(env)) - userGroup.POST("/:id", updateUser(env)) - userGroup.GET("/:id/keys", downloadKey(env)) + userGroup.GET("", User(env)) + userGroup.POST("/:id", UpdateUser(env)) + userGroup.GET("/:id/keys", DownloadKey(env)) } authRulesGroup := apiGroup.Group("/authrules") { - authRulesGroup.GET("", authRule(env)) - authRulesGroup.POST("/:id", updateAuthRule(env)) - authRulesGroup.GET("/:id/delete", deleteAuthRule(env)) + authRulesGroup.GET("", AuthRule(env)) + authRulesGroup.POST("/:id", UpdateAuthRule(env)) + authRulesGroup.GET("/:id/delete", DeleteAuthRule(env)) } wsGroup := apiGroup.Group("/ws") { - wsGroup.GET("/livesessions/:id", liveSessionWS(env)) - wsGroup.GET("/livesessions/:id/:sid", liveSessionWS(env)) + wsGroup.GET("/livesessions/:id", LiveSessionWS(env)) + wsGroup.GET("/livesessions/:id/:sid", LiveSessionWS(env)) } - apiGroup.GET("/disconnect/:id", disconnectLiveSession(env)) - apiGroup.GET("/disconnect/:id/:sid", disconnectLiveSession(env)) + apiGroup.GET("/disconnect/:id", DisconnectLiveSession(env)) + apiGroup.GET("/disconnect/:id/:sid", DisconnectLiveSession(env)) apiGroup.GET("/sessions", session(env)) - apiGroup.GET("/sessions/:id", sessionID(env)) + apiGroup.GET("/sessions/:id", SessionID(env)) - apiGroup.POST("/otp", checkOtp(env)) - apiGroup.GET("/setupotp", setupotp(env)) + apiGroup.POST("/otp", CheckOtp(env)) + apiGroup.GET("/setupotp", SetupOtp(env)) } } diff --git a/web/sessions.go b/web/sessions.go index dc161d3..70d5b8b 100644 --- a/web/sessions.go +++ b/web/sessions.go @@ -137,7 +137,7 @@ func session(env *config.Env) func(c *gin.Context) { } } -func sessionID(env *config.Env) func(c *gin.Context) { +func SessionID(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { id, _ := c.Params.Get("id") diff --git a/web/templates.go b/web/templates.go index fb93707..3de2e2d 100644 --- a/web/templates.go +++ b/web/templates.go @@ -8,7 +8,7 @@ import ( "github.com/notion/bastion/config" ) -func sessionTempl(env *config.Env) func(c *gin.Context) { +func SessionTempl(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) userData := session.Get("user").(*config.User) @@ -17,7 +17,7 @@ func sessionTempl(env *config.Env) func(c *gin.Context) { } } -func liveSessionTempl(env *config.Env) func(c *gin.Context) { +func LiveSessionTempl(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) userData := session.Get("user").(*config.User) @@ -26,7 +26,7 @@ func liveSessionTempl(env *config.Env) func(c *gin.Context) { } } -func userTempl(env *config.Env) func(c *gin.Context) { +func UserTempl(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) userData := session.Get("user").(*config.User) @@ -35,7 +35,7 @@ func userTempl(env *config.Env) func(c *gin.Context) { } } -func authRuleTempl(env *config.Env) func(c *gin.Context) { +func AuthRuleTempl(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) userData := session.Get("user").(*config.User) @@ -44,7 +44,7 @@ func authRuleTempl(env *config.Env) func(c *gin.Context) { } } -func noaccessTempl(env *config.Env) func(c *gin.Context) { +func NoaccessTempl(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) userData := session.Get("user").(*config.User) @@ -61,7 +61,7 @@ func noaccessTempl(env *config.Env) func(c *gin.Context) { } } -func otpTempl(env *config.Env) func(c *gin.Context) { +func OtpTempl(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) userData := session.Get("user").(*config.User) @@ -76,7 +76,7 @@ func otpTempl(env *config.Env) func(c *gin.Context) { } } -func setupOtpTempl(env *config.Env) func(c *gin.Context) { +func SetupOtpTempl(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) userData := session.Get("user").(*config.User) diff --git a/web/users.go b/web/users.go index 208f5b7..9b1831d 100644 --- a/web/users.go +++ b/web/users.go @@ -16,7 +16,7 @@ import ( cryptossh "golang.org/x/crypto/ssh" ) -func user(env *config.Env) func(c *gin.Context) { +func User(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { retData := make(map[string]interface{}) var users []config.User @@ -30,7 +30,7 @@ func user(env *config.Env) func(c *gin.Context) { } } -func updateUser(env *config.Env) func(c *gin.Context) { +func UpdateUser(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { id, _ := c.Params.Get("id") retData := make(map[string]interface{}) @@ -97,7 +97,7 @@ func updateUser(env *config.Env) func(c *gin.Context) { } } -func downloadKey(env *config.Env) func(c *gin.Context) { +func DownloadKey(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) sessionUser := session.Get("user").(*config.User) From 44c00263d152a0293257a26f49a9bb327ba772de Mon Sep 17 00:00:00 2001 From: Jenna Schwartz Date: Tue, 23 Apr 2019 10:28:55 -0400 Subject: [PATCH 2/3] cleaned up alertSystem code --- config/alertsystem.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/config/alertsystem.go b/config/alertsystem.go index 8258afc..4bc24e8 100644 --- a/config/alertsystem.go +++ b/config/alertsystem.go @@ -46,7 +46,7 @@ func listenForLoginAlert(c chan AlertInfo, env *Env) { } } -// deque is fixed size so it doesn't just keep growing infinitely. +// deque is fixed size so it doesn't just keep growing infinitely with more logins. func addToDeque(deque *Deque, timeSince float64, maxDataPoints int) { if deque.Size() >= maxDataPoints { deque.PopRight() @@ -75,9 +75,6 @@ func stdev(deque *Deque, mean float64) float64 { } func printAlert(alertInfo AlertInfo, env *Env) { - //TODO: Test this file with smaller unit tests on each function, main function. - // TODO: practice 1-min and 3-min pitch. - // TODO: update poster with comments and submit for printing. alertString := "ALERT!\n" if alertInfo.NewNetwork { alertString += "User just attempted connection from a new network.\n" From 67e9c31828e2a630becb213d3f1b8f7b387885a6 Mon Sep 17 00:00:00 2001 From: Jenna Schwartz Date: Tue, 23 Apr 2019 10:55:09 -0400 Subject: [PATCH 3/3] Reverting changes made for local development --- config.example.yml | 14 +++++++------- config/main.go | 6 ------ docker-compose.yml | 21 --------------------- ssh/server.go | 3 +-- web/auth.go | 8 ++------ web/authrules.go | 6 +++--- web/handlers.go | 2 +- web/livesessions.go | 10 +++++----- web/main.go | 28 ++++++++++++++-------------- web/sessions.go | 2 +- web/templates.go | 12 ++++++------ web/users.go | 6 +++--- 12 files changed, 43 insertions(+), 75 deletions(-) delete mode 100644 docker-compose.yml diff --git a/config.example.yml b/config.example.yml index c15dc3d..02ee96f 100644 --- a/config.example.yml +++ b/config.example.yml @@ -1,11 +1,11 @@ cookiesecret: s0secure dbinfo: host: localhost - name: JennaSchwartz - pass: JennasPassword + name: example + pass: example port: 3306 sqlite: true - user: jenna + user: example debug: db: enabled: true @@ -35,12 +35,12 @@ multihost: hostname: "" ip: "" oauthcredentials: - clientid: 410608294662-ohk6vtn9hn97f3jm1au2kdecd3m57t9u.apps.googleusercontent.com - clientsecret: 3X72QfdjjXwFmHCalTlLvtbt + clientid: 0000000000000-foobar.apps.googleusercontent.com + clientsecret: foobar endpoint: authurl: https://accounts.google.com/o/oauth2/auth tokenurl: https://accounts.google.com/o/oauth2/token - redirecturl: http://localhost:8080 + redirecturl: https://auth.example.com scopes: - https://www.googleapis.com/auth/userinfo.email otp: @@ -51,4 +51,4 @@ sessions: directory: sessions enabled: true sshcert: - graceperiod: 4h + graceperiod: 4h \ No newline at end of file diff --git a/config/main.go b/config/main.go index 81c77c6..fb187c7 100644 --- a/config/main.go +++ b/config/main.go @@ -13,7 +13,6 @@ import ( _ "github.com/jinzhu/gorm/dialects/mysql" // Load MySQL for GORM _ "github.com/jinzhu/gorm/dialects/sqlite" // Load SQLite for GORM - // "github.com/notion/bastion/alertsystem" "github.com/spf13/viper" "google.golang.org/api/option" ) @@ -22,11 +21,6 @@ const configFile = "config.yml" // Load initializes the Env pointer with data from the database and elsewhere func Load(forceCerts bool, webAddr string, sshAddr string, sshProxyAddr string, monAddr string) *Env { - fmt.Println(forceCerts) - fmt.Println(webAddr) - fmt.Println(sshAddr) - fmt.Println(sshProxyAddr) - fmt.Println(monAddr) vconfig := viper.New() diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 913820d..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,21 +0,0 @@ -version: '3.7' - -services: - bastion: - build: ./ - ports: - - "5222:5222" - - "8080:8080" - volumes: - - ./config.example.yml:/go/src/github.com/notion/bastion/config.example.yml - - ./bastion.db:/go/src/github.com/notion/bastion/bastion.db - ssh: - image: rastasheep/ubuntu-sshd - command: | - /bin/bash -c " - apt-get update && apt-get install net-tools - echo 'TrustedUserCAKeys /etc/ssh/ca_keys' >> /etc/ssh/sshd_config - echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrDZWuJDdOoW+0xpu+hyVqTGo5bmdQL3bZvtOLtbrLBIdrtGIAelSJpJVmjKceenYSoFPEAeSLklI1U0VEkLDRRaNt8MPBhTl8JHiaiLT3PizwKxXFBdN8lEIMQvyxeOEarxV4/Xi8qCS+sXEqpLs/IOY/83gs2eHuw6l38Z9KNd1KEyiQ2WHi6AnxXSuMTdHRmzxMBNnK77qPrQ+2ILgtZKiYDJdoNZU/Z+AEFz2KGlIUl71zxqaOebXf9fVKIGo+oFA3LhxtlfNCmY09U3Gfte6K9vLon+fAqXpwVIpkckLyAwDhffiS2g5KoAfAD2EnsSBjXpzL+9mPXlcoreM9' > /etc/ssh/ca_keys - ifconfig - /usr/sbin/sshd -D - " \ No newline at end of file diff --git a/ssh/server.go b/ssh/server.go index 705cf3a..1250ba7 100644 --- a/ssh/server.go +++ b/ssh/server.go @@ -10,9 +10,8 @@ import ( "sync" "time" - "github.com/notion/bastion/proxyprotocol" - // "github.com/notion/bastion/alertsystem" "github.com/notion/bastion/config" + "github.com/notion/bastion/proxyprotocol" "golang.org/x/crypto/ssh" ) diff --git a/web/auth.go b/web/auth.go index 525a1ef..e8cea31 100644 --- a/web/auth.go +++ b/web/auth.go @@ -37,10 +37,6 @@ func authMiddleware(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { path := strings.TrimSpace(c.Request.URL.Path) session := sessions.Default(c) - // session.Set("loggedin", true) - // session.Set("otpauthed", true) - - // session.Set("user", nil) auth := session.Get("loggedin") otpAuth := session.Get("otpauthed") if otpAuth != nil { @@ -74,7 +70,7 @@ func authMiddleware(env *config.Env) func(c *gin.Context) { } } -func CheckOtp(env *config.Env) func(c *gin.Context) { +func checkOtp(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) sessionUser := session.Get("user").(*config.User) @@ -120,7 +116,7 @@ func CheckOtp(env *config.Env) func(c *gin.Context) { } } -func SetupOtp(env *config.Env) func(c *gin.Context) { +func setupOtp(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) sessionUser := session.Get("user").(*config.User) diff --git a/web/authrules.go b/web/authrules.go index e79e88e..aace4db 100644 --- a/web/authrules.go +++ b/web/authrules.go @@ -7,7 +7,7 @@ import ( "github.com/notion/bastion/config" ) -func AuthRule(env *config.Env) func(c *gin.Context) { +func authRule(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { retData := make(map[string]interface{}) var authRules []config.AuthRules @@ -42,7 +42,7 @@ func createAuthRule(env *config.Env) func(c *gin.Context) { } } -func DeleteAuthRule(env *config.Env) func(c *gin.Context) { +func deleteAuthRule(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { id, _ := c.Params.Get("id") var authRule config.AuthRules @@ -54,7 +54,7 @@ func DeleteAuthRule(env *config.Env) func(c *gin.Context) { } } -func UpdateAuthRule(env *config.Env) func(c *gin.Context) { +func updateAuthRule(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { id, _ := c.Params.Get("id") diff --git a/web/handlers.go b/web/handlers.go index 9dba084..2fe9b7c 100644 --- a/web/handlers.go +++ b/web/handlers.go @@ -156,7 +156,7 @@ func indexIAP(env *config.Env, conf oauth2.Config) func(c *gin.Context) { } } -func Logout(env *config.Env) func(c *gin.Context) { +func logout(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) diff --git a/web/livesessions.go b/web/livesessions.go index cfd9b77..4c3fca1 100644 --- a/web/livesessions.go +++ b/web/livesessions.go @@ -15,7 +15,7 @@ import ( "github.com/notion/bastion/config" ) -func LiveSession(env *config.Env) func(c *gin.Context) { +func liveSession(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { if env.Vconfig.GetBool("multihost.enabled") { liveSessionMultiHost(env)(c) @@ -207,7 +207,7 @@ func liveSessionMultiHost(env *config.Env) func(c *gin.Context) { } } -func DisconnectLiveSession(env *config.Env) func(c *gin.Context) { +func disconnectLiveSession(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { authcode, pathKey, sidKey := getLiveSessionParams(c) @@ -272,13 +272,13 @@ func disconnectLiveSessionMultiHost(env *config.Env) func(c *gin.Context) { c.AbortWithStatusJSON(http.StatusUnauthorized, map[string]interface{}{"status": false, "error": "Invalid auth code."}) return } else { - DisconnectLiveSession(env)(c) + disconnectLiveSession(env)(c) return } } } -func LiveSessionWS(env *config.Env) func(c *gin.Context) { +func liveSessionWS(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { authcode, pathKey, sidKey := getLiveSessionParams(c) @@ -402,7 +402,7 @@ func liveSessionWSMultiHost(env *config.Env) func(c *gin.Context) { c.AbortWithStatusJSON(http.StatusUnauthorized, map[string]interface{}{"status": false, "error": "Invalid auth code."}) return } else { - LiveSessionWS(env)(c) + liveSessionWS(env)(c) return } } diff --git a/web/main.go b/web/main.go index 66d1985..a38ebb2 100644 --- a/web/main.go +++ b/web/main.go @@ -64,33 +64,33 @@ func Serve(addr string, env *config.Env) { apiGroup := authedGroup.Group("/api") { - apiGroup.GET("/livesessions", LiveSession(env)) + apiGroup.GET("/livesessions", liveSession(env)) userGroup := apiGroup.Group("/users") { - userGroup.GET("", User(env)) - userGroup.POST("/:id", UpdateUser(env)) - userGroup.GET("/:id/keys", DownloadKey(env)) + userGroup.GET("", user(env)) + userGroup.POST("/:id", updateUser(env)) + userGroup.GET("/:id/keys", downloadKey(env)) } authRulesGroup := apiGroup.Group("/authrules") { - authRulesGroup.GET("", AuthRule(env)) - authRulesGroup.POST("/:id", UpdateAuthRule(env)) - authRulesGroup.GET("/:id/delete", DeleteAuthRule(env)) + authRulesGroup.GET("", authRule(env)) + authRulesGroup.POST("/:id", updateAuthRule(env)) + authRulesGroup.GET("/:id/delete", deleteAuthRule(env)) } wsGroup := apiGroup.Group("/ws") { - wsGroup.GET("/livesessions/:id", LiveSessionWS(env)) - wsGroup.GET("/livesessions/:id/:sid", LiveSessionWS(env)) + wsGroup.GET("/livesessions/:id", liveSessionWS(env)) + wsGroup.GET("/livesessions/:id/:sid", liveSessionWS(env)) } - apiGroup.GET("/disconnect/:id", DisconnectLiveSession(env)) - apiGroup.GET("/disconnect/:id/:sid", DisconnectLiveSession(env)) + apiGroup.GET("/disconnect/:id", disconnectLiveSession(env)) + apiGroup.GET("/disconnect/:id/:sid", disconnectLiveSession(env)) apiGroup.GET("/sessions", session(env)) - apiGroup.GET("/sessions/:id", SessionID(env)) + apiGroup.GET("/sessions/:id", sessionID(env)) - apiGroup.POST("/otp", CheckOtp(env)) - apiGroup.GET("/setupotp", SetupOtp(env)) + apiGroup.POST("/otp", checkOtp(env)) + apiGroup.GET("/setupotp", setupOtp(env)) } } diff --git a/web/sessions.go b/web/sessions.go index 70d5b8b..dc161d3 100644 --- a/web/sessions.go +++ b/web/sessions.go @@ -137,7 +137,7 @@ func session(env *config.Env) func(c *gin.Context) { } } -func SessionID(env *config.Env) func(c *gin.Context) { +func sessionID(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { id, _ := c.Params.Get("id") diff --git a/web/templates.go b/web/templates.go index ab02ec4..dab5053 100644 --- a/web/templates.go +++ b/web/templates.go @@ -8,7 +8,7 @@ import ( "github.com/notion/bastion/config" ) -func SessionTempl(env *config.Env) func(c *gin.Context) { +func sessionTempl(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) userData := session.Get("user").(*config.User) @@ -17,7 +17,7 @@ func SessionTempl(env *config.Env) func(c *gin.Context) { } } -func LiveSessionTempl(env *config.Env) func(c *gin.Context) { +func liveSessionTempl(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) userData := session.Get("user").(*config.User) @@ -26,7 +26,7 @@ func LiveSessionTempl(env *config.Env) func(c *gin.Context) { } } -func UserTempl(env *config.Env) func(c *gin.Context) { +func userTempl(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) userData := session.Get("user").(*config.User) @@ -35,7 +35,7 @@ func UserTempl(env *config.Env) func(c *gin.Context) { } } -func AuthRuleTempl(env *config.Env) func(c *gin.Context) { +func authRuleTempl(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) userData := session.Get("user").(*config.User) @@ -61,7 +61,7 @@ func authTempl(env *config.Env) func(c *gin.Context) { } } -func OtpTempl(env *config.Env) func(c *gin.Context) { +func otpTempl(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) userData := session.Get("user").(*config.User) @@ -76,7 +76,7 @@ func OtpTempl(env *config.Env) func(c *gin.Context) { } } -func SetupOtpTempl(env *config.Env) func(c *gin.Context) { +func setupOtpTempl(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) userData := session.Get("user").(*config.User) diff --git a/web/users.go b/web/users.go index 9b1831d..208f5b7 100644 --- a/web/users.go +++ b/web/users.go @@ -16,7 +16,7 @@ import ( cryptossh "golang.org/x/crypto/ssh" ) -func User(env *config.Env) func(c *gin.Context) { +func user(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { retData := make(map[string]interface{}) var users []config.User @@ -30,7 +30,7 @@ func User(env *config.Env) func(c *gin.Context) { } } -func UpdateUser(env *config.Env) func(c *gin.Context) { +func updateUser(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { id, _ := c.Params.Get("id") retData := make(map[string]interface{}) @@ -97,7 +97,7 @@ func UpdateUser(env *config.Env) func(c *gin.Context) { } } -func DownloadKey(env *config.Env) func(c *gin.Context) { +func downloadKey(env *config.Env) func(c *gin.Context) { return func(c *gin.Context) { session := sessions.Default(c) sessionUser := session.Get("user").(*config.User)