├── .env ├── .gitignore ├── LICENSE ├── Makefile ├── Procfile ├── README.md ├── controllers ├── collector.go ├── collector_test.go ├── controllers.go ├── controllers_test.go ├── router.go ├── session.go ├── session_test.go ├── status.go ├── tls.go ├── users.go ├── users_test.go └── visualizer.go ├── doc ├── api.md └── visualizer.md ├── docker-compose.yml ├── engines ├── ids │ ├── ids.go │ ├── ids_test.go │ └── rules │ │ └── example-rule.js └── visualizer │ ├── access_points.go │ ├── data.go │ ├── deauth.go │ ├── devices.go │ ├── null_data.go │ ├── probe_requests.go │ └── visualizer.go ├── frontend ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── Login.vue │ ├── Navbar.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── AccountSettings.vue │ │ ├── CollectorManager.vue │ │ ├── Dashboard.vue │ │ ├── IDS.vue │ │ ├── Rules.vue │ │ ├── Unauthorized.vue │ │ ├── UserManager.vue │ │ └── Visualization.vue │ ├── main.js │ ├── router.js │ └── store.js └── vue.config.js ├── go.mod ├── go.sum ├── helpers ├── crypto.go ├── database.go ├── environment.go ├── helpers.go ├── hostname.go └── tls.go ├── main.go ├── middleware ├── authentication.go └── embedded_assets.go ├── models ├── alert.go ├── association.go ├── collector.go ├── collector_test.go ├── database.go ├── device.go ├── models.go ├── network.go ├── session.go ├── session_test.go ├── tls.go ├── tls_test.go ├── user.go ├── user_factory.go ├── user_test.go └── wiress80211frame.go └── static ├── favicon.ico └── wave.svg /.env: -------------------------------------------------------------------------------- 1 | WAVE_HOSTNAME=localhost 2 | WAVE_BIND=127.0.0.1 3 | WAVE_PORT=8080 4 | WAVE_COLLECTOR_PORT=8888 5 | WAVE_ENV=development 6 | WAVE_TLS=false 7 | WAVE_DB_HOSTNAME=127.0.0.1 8 | WAVE_DB_USERNAME=postgres 9 | WAVE_DB_PASSWORD=postgres 10 | WAVE_DB_ADMIN_USERNAME=postgres 11 | WAVE_DB_ADMIN_PASSWORD=postgres 12 | WAVE_DB_NAME=wave_dev 13 | WAVE_DB_TLS=disable 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.sw? 2 | node_modules 3 | Wave 4 | static/bundle.js 5 | static/448c34a56d699c29117adc64c43affeb.woff2 6 | static/89889688147bd7575d6327160d64e760.svg 7 | static/e18bbf611f2a2e43afc071aa2f4e1512.ttf 8 | static/f4769f9bdb7466be65088239c12046d1.eot 9 | static/fa2772327f55d8198301fdb8bcfc8158.woff 10 | assets/*.js 11 | helpers/bindata.go 12 | bin/ 13 | engines/visualizer/metadata/nmap-mac-prefixes 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Hayden Parker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: clean test release 2 | 3 | # 4 | # Assuming only `go` and `npm` available, install all required utilities 5 | # 6 | deps: update-vendor-bytes 7 | go get github.com/cespare/reflex 8 | go get github.com/ddollar/forego 9 | go get -u github.com/jteeuwen/go-bindata/... 10 | make embed-assets 11 | go get -t 12 | npm install 13 | 14 | # 15 | # Remove all ignored files generated during build 16 | # 17 | clean: 18 | @rm -f Wave 19 | @rm -f assets/*.js 20 | @rm -f static/bundle.js 21 | @rm -f static/448c34a56d699c29117adc64c43affeb.woff2 22 | @rm -f static/89889688147bd7575d6327160d64e760.svg 23 | @rm -f static/e18bbf611f2a2e43afc071aa2f4e1512.ttf 24 | @rm -f static/f4769f9bdb7466be65088239c12046d1.eot 25 | @rm -f static/fa2772327f55d8198301fdb8bcfc8158.woff 26 | @rm -f helpers/bindata.go 27 | @rm -f bin/* 28 | 29 | # 30 | # Run go-bindata to create embedded assets for single-binary deployment 31 | # 32 | embed-assets: 33 | go-bindata -pkg=helpers -o=helpers/bindata.go static/ engines/ids/rules/ engines/visualizer/metadata 34 | 35 | # 36 | # Download list of vendor mac prefixes from nmap 37 | # 38 | update-vendor-bytes: 39 | mkdir -p engines/visualizer/metadata 40 | curl https://svn.nmap.org/nmap/nmap-mac-prefixes > engines/visualizer/metadata/nmap-mac-prefixes 41 | 42 | # 43 | # Run the Procfile for development 44 | # 45 | develop: clean embed-assets 46 | forego start 47 | 48 | # 49 | # Testing utilities 50 | # 51 | 52 | test-frontend: 53 | npm test 54 | 55 | test-backend: embed-assets 56 | go test ./... -cover 57 | 58 | test: test-frontend test-backend 59 | 60 | # 61 | # Building Utilities 62 | # 63 | 64 | build-frontend: 65 | ./node_modules/babel-cli/bin/babel.js frontend --out-dir assets 66 | ./node_modules/webpack/bin/webpack.js assets/* static/bundle.js 67 | 68 | build-backend: embed-assets 69 | go build 70 | 71 | build: build-frontend build-backend 72 | 73 | # 74 | # Build all Versions of Wave in the bin directory 75 | # 76 | release: clean test embed-assets update-vendor-bytes 77 | GOOS=linux GOARCH=amd64 go build -o bin/Wave-linux 78 | GOOS=linux GOARCH=arm go build -o bin/Wave-linux-arm 79 | GOOS=freebsd GOARCH=amd64 go build -o bin/Wave-freebsd 80 | GOOS=darwin GOARCH=amd64 go build -o bin/Wave-osx 81 | GOOS=windows GOARCH=amd64 go build -o bin/Wave-windows.exe 82 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | wave-api: reflex -r '.*.go$' -s -- sh -c 'go run main.go' 2 | wave-rules: reflex -r 'engines/ids/rules/.*.js$' -s -- sh -c 'make embed-assets' 3 | #wave-frontend: reflex -r 'frontend/.+\.js.?$' -s -- sh -c 'make build-frontend' 4 | wave-services: docker-compose up 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wave 2 | 3 | Wave is an 802.11 intrusion detection system, visualizer, and analytics platform. Wireless data is sent from [collector](https://github.com/hkparker/collector)s to Wave where it is analysed by various engines. 4 | 5 | ## Developing 6 | 7 | You'll need `npm`, `go`, and `docker-compose` available. 8 | 9 | ### Installing dependencies 10 | 11 | Install [reflex](https://github.com/cespare/reflex), [forego](https://github.com/ddollar/forego), and [go-bindata](https://github.com/jteeuwen/go-bindata), run `make embed-assets`, `go get -t`, and `npm install`. 12 | 13 | ``` 14 | make deps 15 | ``` 16 | 17 | ### Start instance 18 | 19 | Start postgres and an auto-rebuilding instance of Wave. 20 | 21 | ``` 22 | make develop 23 | ``` 24 | 25 | ### Running tests 26 | 27 | Run `go test ./... -cover` and `npm test`. 28 | 29 | ``` 30 | make test 31 | ``` 32 | 33 | ## Stack 34 | 35 | * [collector](https://github.com/hkparker/collector): go application to sniff 802.11 frames and send them to Wave via websocket 36 | * [gin](https://github.com/gin-gonic/gin): web framework 37 | * [gorilla/websocket](https://github.com/gorilla/websocket): websocket library 38 | * [gorm](https://github.com/jinzhu/gorm): ORM for go used for postgres 39 | * [postgres](https://github.com/postgres/postgres): storage of persistent data 40 | -------------------------------------------------------------------------------- /controllers/collector.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "crypto/sha256" 5 | "crypto/tls" 6 | "encoding/hex" 7 | "encoding/json" 8 | "github.com/gin-gonic/gin" 9 | "github.com/gorilla/websocket" 10 | "github.com/hkparker/Wave/engines/ids" 11 | "github.com/hkparker/Wave/engines/visualizer" 12 | "github.com/hkparker/Wave/models" 13 | log "github.com/sirupsen/logrus" 14 | ) 15 | 16 | func createCollector(c *gin.Context) { 17 | user_info, err := requestJSON(c) 18 | if err != nil { 19 | return 20 | } 21 | admin, err := currentUser(c) 22 | if err != nil { 23 | return 24 | } 25 | 26 | // Ensure the JSON contains a name 27 | name, ok := user_info["name"] 28 | if !ok || name == "" { 29 | name_error := "no name provided" 30 | c.JSON(400, gin.H{"error": name_error}) 31 | log.WithFields(log.Fields{ 32 | "at": "controllers.createCollector", 33 | "error": name_error, 34 | "admin": admin.Username, 35 | }).Error("error creating collector") 36 | return 37 | } 38 | 39 | collector, err := models.CreateCollector(name) 40 | if err != nil { 41 | c.JSON(400, gin.H{"error": err.Error()}) 42 | log.WithFields(log.Fields{ 43 | "at": "controllers.createCollector", 44 | "error": err.Error(), 45 | "admin": admin.Username, 46 | }).Error("error creating collector") 47 | return 48 | 49 | } 50 | 51 | c.JSON(200, gin.H{ 52 | "success": "true", 53 | "name": collector.Name, 54 | }) 55 | log.WithFields(log.Fields{ 56 | "at": "controllers.createCollector", 57 | "name": name, 58 | "admin": admin.Username, 59 | }).Info("created collector") 60 | } 61 | 62 | func getCollectors(c *gin.Context) { 63 | admin, err := currentUser(c) 64 | if err != nil { 65 | return 66 | } 67 | 68 | collectors, err := models.Collectors() 69 | if err != nil { 70 | c.JSON(500, gin.H{"error": err.Error()}) 71 | log.WithFields(log.Fields{ 72 | "at": "controllers.getCollectors", 73 | "error": err.Error(), 74 | "admin": admin.Username, 75 | }).Error("error looking up collectors") 76 | return 77 | } 78 | names := []string{} 79 | for _, collector := range collectors { 80 | names = append(names, collector.Name) 81 | } 82 | c.JSON(200, names) 83 | } 84 | 85 | func getCollectorCertificate(c *gin.Context) { 86 | collector_info, err := requestJSON(c) 87 | if err != nil { 88 | return 89 | } 90 | admin, err := currentUser(c) 91 | if err != nil { 92 | return 93 | } 94 | 95 | name, ok := collector_info["name"] 96 | if !ok || name == "" { 97 | name_error := "no name provided" 98 | c.JSON(400, gin.H{"error": name_error}) 99 | log.WithFields(log.Fields{ 100 | "at": "controllers.getCollectorCertificate", 101 | "error": name_error, 102 | "admin": admin.Username, 103 | }).Error("error getting collector") 104 | return 105 | } 106 | 107 | collector, err := models.CollectorByName(name) 108 | if err != nil { 109 | name_error := "name provided does not match collector" 110 | c.JSON(400, gin.H{"error": name_error}) 111 | log.WithFields(log.Fields{ 112 | "at": "controllers.getCollectorCertificate", 113 | "error": name_error, 114 | "admin": admin.Username, 115 | }).Error("error getting collector") 116 | return 117 | } 118 | 119 | c.Data(200, "application/x-pem-file", []byte(collector.CaCert)) 120 | } 121 | 122 | func getCollectorKey(c *gin.Context) { 123 | collector_info, err := requestJSON(c) 124 | if err != nil { 125 | return 126 | } 127 | admin, err := currentUser(c) 128 | if err != nil { 129 | return 130 | } 131 | 132 | name, ok := collector_info["name"] 133 | if !ok || name == "" { 134 | name_error := "no name provided" 135 | c.JSON(400, gin.H{"error": name_error}) 136 | log.WithFields(log.Fields{ 137 | "at": "controllers.getCollectorKey", 138 | "error": name_error, 139 | "admin": admin.Username, 140 | }).Error("error getting collector") 141 | return 142 | } 143 | 144 | collector, err := models.CollectorByName(name) 145 | if err != nil { 146 | name_error := "name provided does not match collector" 147 | c.JSON(400, gin.H{"error": name_error}) 148 | log.WithFields(log.Fields{ 149 | "at": "controllers.getCollectorKey", 150 | "error": name_error, 151 | "admin": admin.Username, 152 | }).Error("error getting collector") 153 | return 154 | } 155 | 156 | c.Data(200, "application/x-pem-file", []byte(collector.PrivateKey)) 157 | } 158 | 159 | func getServerCertificate(c *gin.Context) { 160 | cert_data, _ := models.APITLSData() 161 | c.Data(200, "application/x-pem-file", cert_data) 162 | } 163 | 164 | func deleteCollector(c *gin.Context) { 165 | user_info, err := requestJSON(c) 166 | if err != nil { 167 | return 168 | } 169 | admin, err := currentUser(c) 170 | if err != nil { 171 | return 172 | } 173 | 174 | // Ensure the JSON contains a name key 175 | name, ok := user_info["name"] 176 | if !ok || name == "" { 177 | name_error := "no name provided" 178 | c.JSON(400, gin.H{"error": name_error}) 179 | log.WithFields(log.Fields{ 180 | "at": "controllers.deleteCollector", 181 | "error": name_error, 182 | "admin": admin.Username, 183 | }).Error("error deleting collector") 184 | return 185 | } 186 | 187 | // Ensure the collector exists 188 | collector, err := models.CollectorByName(name) 189 | if err != nil { 190 | c.JSON(500, gin.H{"error": err.Error()}) 191 | log.WithFields(log.Fields{ 192 | "at": "controllers.deleteCollector", 193 | "name": name, 194 | "error": err.Error(), 195 | "admin": admin.Username, 196 | }).Error("error looking up collector to delete") 197 | return 198 | } 199 | 200 | // Delete the collector 201 | err = collector.Delete() 202 | if err != nil { 203 | c.JSON(500, gin.H{"error": err.Error()}) 204 | log.WithFields(log.Fields{ 205 | "at": "controllers.deleteCollector", 206 | "name": name, 207 | "error": err.Error(), 208 | "admin": admin.Username, 209 | }).Error("error deleting collector") 210 | } else { 211 | c.JSON(200, gin.H{"success": "true"}) 212 | log.WithFields(log.Fields{ 213 | "at": "controllers.deleteCollector", 214 | "name": name, 215 | "admin": admin.Username, 216 | }).Info("deleted collector") 217 | } 218 | } 219 | 220 | func pollCollector(c *gin.Context) { 221 | var upgrayedd websocket.Upgrader 222 | conn, err := upgrayedd.Upgrade(c.Writer, c.Request, nil) 223 | if err == nil { 224 | defer conn.Close() 225 | // Generate an ID for this collector from the certificate presented 226 | sig := conn.UnderlyingConn().(*tls.Conn).ConnectionState().PeerCertificates[0].Signature 227 | sha := sha256.Sum256(sig) 228 | collector_id := hex.EncodeToString(sha[:]) 229 | 230 | for { 231 | _, frame_bytes, err := conn.ReadMessage() 232 | if err != nil { 233 | break 234 | } 235 | 236 | var frame models.Wireless80211Frame 237 | json.Unmarshal(frame_bytes, &frame) 238 | 239 | visualizer.Insert(frame) 240 | ids.Insert(string(frame_bytes), frame, collector_id) 241 | } 242 | } else { 243 | log.WithFields(log.Fields{ 244 | "at": "controllers.pollCollector", 245 | "error": err.Error(), 246 | }).Warn("failed to upgrade websocket connection") 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /controllers/collector_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/hkparker/Wave/models" 7 | "github.com/stretchr/testify/assert" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func TestNewCollectorCreatesAndDeletes(t *testing.T) { 14 | assert := assert.New(t) 15 | 16 | admin := models.CreateTestUser([]string{"admin"}) 17 | session_id, _ := admin.NewSession() 18 | req, err := http.NewRequest( 19 | "POST", 20 | testing_endpoint+"/collectors/create", 21 | strings.NewReader(fmt.Sprintf( 22 | "{\"name\": \"living room channel 6\"}", 23 | )), 24 | ) 25 | 26 | session, err := models.SessionFromID(session_id) 27 | assert.Nil(err) 28 | cookie := session.HTTPCookie() 29 | req.AddCookie(&cookie) 30 | 31 | assert.Nil(err) 32 | resp, err := testing_client.Do(req) 33 | assert.Nil(err) 34 | assert.Equal(200, resp.StatusCode) 35 | decoder := json.NewDecoder(resp.Body) 36 | var params map[string]string 37 | err = decoder.Decode(¶ms) 38 | if !assert.Nil(err) { 39 | assert.Nil(err.Error()) 40 | } 41 | if assert.NotNil(params) { 42 | if assert.NotNil(params["success"]) { 43 | assert.Equal("true", params["success"]) 44 | } 45 | if assert.NotNil(params["name"]) { 46 | assert.Equal("living room channel 6", params["name"]) 47 | } 48 | if assert.NotNil(params["certificate"]) { 49 | assert.NotEqual("", params["certificate"]) 50 | } 51 | if assert.NotNil(params["private_key"]) { 52 | assert.NotEqual("", params["private_key"]) 53 | } 54 | } 55 | 56 | req, err = http.NewRequest( 57 | "POST", 58 | testing_endpoint+"/collectors/delete", 59 | strings.NewReader(fmt.Sprintf( 60 | "{\"name\": \"living room channel 6\"}", 61 | )), 62 | ) 63 | req.AddCookie(&cookie) 64 | assert.Nil(err) 65 | resp, err = testing_client.Do(req) 66 | assert.Nil(err) 67 | assert.Equal(200, resp.StatusCode) 68 | } 69 | 70 | func TestNewCollectorFailsWithoutAdmin(t *testing.T) { 71 | assert := assert.New(t) 72 | 73 | user := models.CreateTestUser([]string{}) 74 | session_id, _ := user.NewSession() 75 | req, err := http.NewRequest( 76 | "POST", 77 | testing_endpoint+"/collectors/create", 78 | strings.NewReader(fmt.Sprintf( 79 | "{\"name\": \"living room channel 1\"}", 80 | )), 81 | ) 82 | 83 | session, err := models.SessionFromID(session_id) 84 | assert.Nil(err) 85 | cookie := session.HTTPCookie() 86 | req.AddCookie(&cookie) 87 | 88 | assert.Nil(err) 89 | resp, err := testing_client.Do(req) 90 | assert.Nil(err) 91 | assert.Equal(401, resp.StatusCode) 92 | } 93 | 94 | func TestGetCollectors(t *testing.T) { 95 | assert := assert.New(t) 96 | 97 | admin := models.CreateTestUser([]string{"admin"}) 98 | session_id, _ := admin.NewSession() 99 | session, err := models.SessionFromID(session_id) 100 | assert.Nil(err) 101 | cookie := session.HTTPCookie() 102 | var names []string 103 | var params map[string]string 104 | 105 | req, err := http.NewRequest( 106 | "GET", 107 | testing_endpoint+"/collectors", 108 | strings.NewReader(fmt.Sprintf("")), 109 | ) 110 | req.AddCookie(&cookie) 111 | assert.Nil(err) 112 | resp, err := testing_client.Do(req) 113 | assert.Nil(err) 114 | assert.Equal(200, resp.StatusCode) 115 | decoder := json.NewDecoder(resp.Body) 116 | err = decoder.Decode(&names) 117 | if !assert.Nil(err) { 118 | assert.Nil(err.Error()) 119 | } 120 | if assert.NotNil(names) { 121 | assert.Equal([]string{}, names) 122 | } 123 | 124 | req, err = http.NewRequest( 125 | "POST", 126 | testing_endpoint+"/collectors/create", 127 | strings.NewReader(fmt.Sprintf( 128 | "{\"name\": \"living room channel 11\"}", 129 | )), 130 | ) 131 | req.AddCookie(&cookie) 132 | assert.Nil(err) 133 | resp, err = testing_client.Do(req) 134 | assert.Nil(err) 135 | assert.Equal(200, resp.StatusCode) 136 | decoder = json.NewDecoder(resp.Body) 137 | err = decoder.Decode(¶ms) 138 | if !assert.Nil(err) { 139 | assert.Nil(err.Error()) 140 | } 141 | if assert.NotNil(params) { 142 | if assert.NotNil(params["success"]) { 143 | assert.Equal("true", params["success"]) 144 | } 145 | if assert.NotNil(params["name"]) { 146 | assert.Equal("living room channel 11", params["name"]) 147 | } 148 | if assert.NotNil(params["certificate"]) { 149 | assert.NotEqual("", params["certificate"]) 150 | } 151 | if assert.NotNil(params["private_key"]) { 152 | assert.NotEqual("", params["private_key"]) 153 | } 154 | } 155 | 156 | req, err = http.NewRequest( 157 | "GET", 158 | testing_endpoint+"/collectors", 159 | strings.NewReader(fmt.Sprintf("")), 160 | ) 161 | req.AddCookie(&cookie) 162 | assert.Nil(err) 163 | resp, err = testing_client.Do(req) 164 | assert.Nil(err) 165 | assert.Equal(200, resp.StatusCode) 166 | decoder = json.NewDecoder(resp.Body) 167 | err = decoder.Decode(&names) 168 | if !assert.Nil(err) { 169 | assert.Nil(err.Error()) 170 | } 171 | if assert.NotNil(names) { 172 | assert.Equal([]string{"living room channel 11"}, names) 173 | } 174 | 175 | req, err = http.NewRequest( 176 | "POST", 177 | testing_endpoint+"/collectors/create", 178 | strings.NewReader(fmt.Sprintf( 179 | "{\"name\": \"living room channel 1\"}", 180 | )), 181 | ) 182 | req.AddCookie(&cookie) 183 | assert.Nil(err) 184 | resp, err = testing_client.Do(req) 185 | assert.Nil(err) 186 | assert.Equal(200, resp.StatusCode) 187 | decoder = json.NewDecoder(resp.Body) 188 | err = decoder.Decode(¶ms) 189 | if !assert.Nil(err) { 190 | assert.Nil(err.Error()) 191 | } 192 | if assert.NotNil(params) { 193 | if assert.NotNil(params["success"]) { 194 | assert.Equal("true", params["success"]) 195 | } 196 | if assert.NotNil(params["name"]) { 197 | assert.Equal("living room channel 1", params["name"]) 198 | } 199 | if assert.NotNil(params["certificate"]) { 200 | assert.NotEqual("", params["certificate"]) 201 | } 202 | if assert.NotNil(params["private_key"]) { 203 | assert.NotEqual("", params["private_key"]) 204 | } 205 | } 206 | 207 | req, err = http.NewRequest( 208 | "GET", 209 | testing_endpoint+"/collectors", 210 | strings.NewReader(fmt.Sprintf("")), 211 | ) 212 | req.AddCookie(&cookie) 213 | assert.Nil(err) 214 | resp, err = testing_client.Do(req) 215 | assert.Nil(err) 216 | assert.Equal(200, resp.StatusCode) 217 | decoder = json.NewDecoder(resp.Body) 218 | err = decoder.Decode(&names) 219 | if !assert.Nil(err) { 220 | assert.Nil(err.Error()) 221 | } 222 | if assert.NotNil(names) { 223 | assert.Equal([]string{"living room channel 11", "living room channel 1"}, names) 224 | } 225 | 226 | req, err = http.NewRequest( 227 | "POST", 228 | testing_endpoint+"/collectors/delete", 229 | strings.NewReader(fmt.Sprintf( 230 | "{\"name\": \"living room channel 11\"}", 231 | )), 232 | ) 233 | req.AddCookie(&cookie) 234 | assert.Nil(err) 235 | resp, err = testing_client.Do(req) 236 | assert.Nil(err) 237 | assert.Equal(200, resp.StatusCode) 238 | 239 | req, err = http.NewRequest( 240 | "GET", 241 | testing_endpoint+"/collectors", 242 | strings.NewReader(fmt.Sprintf("")), 243 | ) 244 | req.AddCookie(&cookie) 245 | assert.Nil(err) 246 | resp, err = testing_client.Do(req) 247 | assert.Nil(err) 248 | assert.Equal(200, resp.StatusCode) 249 | decoder = json.NewDecoder(resp.Body) 250 | err = decoder.Decode(&names) 251 | if !assert.Nil(err) { 252 | assert.Nil(err.Error()) 253 | } 254 | if assert.NotNil(names) { 255 | assert.Equal([]string{"living room channel 1"}, names) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /controllers/controllers.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "errors" 5 | "github.com/gin-gonic/gin" 6 | "github.com/hkparker/Wave/engines/visualizer" 7 | "github.com/hkparker/Wave/models" 8 | log "github.com/sirupsen/logrus" 9 | "net/http" 10 | ) 11 | 12 | func init() { 13 | go func() { 14 | for { 15 | event := <-visualizer.VisualEvents 16 | VisualClientMux.Lock() 17 | clients_list := VisualClients 18 | VisualClientMux.Unlock() 19 | for id, client := range clients_list { 20 | err := client.WriteJSON(event) 21 | if err != nil { 22 | log.WithFields(log.Fields{ 23 | "at": "controllers.init", 24 | "error": err.Error(), 25 | }).Info("visualizer client disconnected") 26 | VisualClientMux.Lock() 27 | delete(VisualClients, id) 28 | VisualClientMux.Unlock() 29 | } 30 | } 31 | } 32 | }() 33 | } 34 | 35 | func requestJSON(c *gin.Context) (json map[string]string, err error) { 36 | err = c.BindJSON(&json) 37 | if err != nil { 38 | c.JSON(400, gin.H{"error": "error parsing json"}) 39 | c.Abort() 40 | log.WithFields(log.Fields{ 41 | "at": "controllers.requestJSON", 42 | "error": err.Error(), 43 | }).Error("error parsing request") 44 | } 45 | return 46 | } 47 | 48 | func currentUser(c *gin.Context) (models.User, error) { 49 | userInterface, ok := c.Get("currentUser") 50 | if !ok { 51 | c.Status(500) 52 | c.Abort() 53 | errStr := "request context has no currentUser set" 54 | log.WithFields(log.Fields{ 55 | "at": "controllers.currentUser", 56 | }).Error(errStr) 57 | return models.User{}, errors.New(errStr) 58 | } 59 | user, ok := userInterface.(models.User) 60 | if !ok { 61 | c.Status(500) 62 | c.Abort() 63 | errStr := "unable to cast user interface to user model" 64 | log.WithFields(log.Fields{ 65 | "at": "controllers.currentUser", 66 | }).Error(errStr) 67 | return user, errors.New(errStr) 68 | } 69 | 70 | return user, nil 71 | } 72 | 73 | func sessionCookie(c *gin.Context) (session_cookie string, err error) { 74 | var session_cookie_obj *http.Cookie 75 | session_cookie_obj, err = c.Request.Cookie("wave_session") 76 | if err != nil { 77 | log.WithFields(log.Fields{ 78 | "at": "controllers.sessionCookie", 79 | "error": err.Error(), 80 | }).Error("session_cookie_missing") 81 | return 82 | } else { 83 | session_cookie = session_cookie_obj.Value 84 | } 85 | return 86 | } 87 | -------------------------------------------------------------------------------- /controllers/controllers_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | ) 7 | 8 | var ( 9 | testing_endpoint string 10 | testing_client http.Client 11 | ) 12 | 13 | func init() { 14 | server := httptest.NewServer(NewAPI()) 15 | testing_endpoint = server.URL 16 | testing_client = http.Client{} 17 | } 18 | -------------------------------------------------------------------------------- /controllers/router.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/contrib/static" 5 | "github.com/gin-gonic/gin" 6 | //"github.com/hkparker/Wave/engines/visualizer" 7 | "github.com/hkparker/Wave/helpers" 8 | "github.com/hkparker/Wave/middleware" 9 | cors "github.com/rs/cors/wrapper/gin" 10 | ) 11 | 12 | func NewAPI() *gin.Engine { 13 | router := gin.Default() 14 | 15 | if helpers.Development { // create a proper CORS for production 16 | c := cors.New(cors.Options{ 17 | AllowedOrigins: []string{"http://localhost:8080"}, 18 | AllowCredentials: true, 19 | //Debug: true, 20 | }) 21 | router.Use(c) 22 | } 23 | 24 | if helpers.Production { 25 | router.Use(middleware.EmbeddedAssets()) 26 | } else { 27 | router.Use(static.Serve("/", static.LocalFile("static", false))) 28 | router.GET("/", middleware.RenderWebpack) 29 | } 30 | router.Use(middleware.Authentication()) 31 | 32 | // Session routes 33 | router.POST("/sessions/create", newSession) 34 | router.POST("/sessions/destroy", deleteSession) 35 | 36 | // User routes 37 | router.GET("/users", getUsers) 38 | router.POST("/users/create", createUser) 39 | router.POST("/users/name", updateUserName) 40 | router.POST("/users/password", updateUserPassword) 41 | router.POST("/users/assign-password", assignUserPassword) 42 | router.POST("/users/delete", deleteUser) 43 | 44 | // Event streams 45 | router.GET("/streams/visualizer", streamVisualization) 46 | //router.GET("/streams/ids", streamAlerts) 47 | 48 | // Signature routes 49 | // Incident routes 50 | // Device routes 51 | // Version route 52 | 53 | router.GET("/status", getStatus) 54 | 55 | // Collector routes 56 | router.GET("/collectors", getCollectors) 57 | router.POST("/collector/certificate", getCollectorCertificate) 58 | router.POST("/collector/key", getCollectorKey) 59 | router.POST("/collector/server_certificate", getServerCertificate) 60 | router.POST("/collectors/create", createCollector) 61 | router.POST("/collectors/delete", deleteCollector) 62 | 63 | // Certificate Management 64 | router.GET("/tls", getTLS) 65 | router.POST("/tls", setTLS) 66 | 67 | return router 68 | } 69 | 70 | func NewCollector() *gin.Engine { 71 | //visualizer.Load() 72 | 73 | collector := gin.Default() 74 | collector.GET("/frames", pollCollector) 75 | return collector 76 | } 77 | -------------------------------------------------------------------------------- /controllers/session.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/hkparker/Wave/models" 6 | log "github.com/sirupsen/logrus" 7 | "net/http" 8 | ) 9 | 10 | // Handle login requests 11 | func newSession(c *gin.Context) { 12 | // Ensure the request is valid JSON 13 | user_info, err := requestJSON(c) 14 | if err != nil { 15 | return 16 | } 17 | 18 | // Ensure the JSON contains a username key with data 19 | username, ok := user_info["username"] 20 | if !ok || username == "" { 21 | err := "no user provided" 22 | c.JSON(400, gin.H{"error": err}) 23 | log.WithFields(log.Fields{ 24 | "at": "controllers.newSession", 25 | "error": err, 26 | }).Error("error creating session") 27 | return 28 | } 29 | 30 | // Ensure the JSON contains a password key 31 | password, ok := user_info["password"] 32 | if !ok { 33 | err := "no password provided" 34 | c.JSON(400, gin.H{"error": err}) 35 | log.WithFields(log.Fields{ 36 | "at": "controllers.newSession", 37 | "error": err, 38 | "username": username, 39 | }).Error("error creating session") 40 | return 41 | } 42 | 43 | // Ensure the user exists 44 | user, err := models.UserByUsername(username) 45 | if err != nil { 46 | err := "user does not exist" 47 | // Do not reveal if the username was a valid account 48 | c.JSON(401, gin.H{"error": "incorrect authentication"}) 49 | log.WithFields(log.Fields{ 50 | "at": "controllers.newSession", 51 | "error": err, 52 | "username": username, 53 | }).Error("error creating session") 54 | return 55 | 56 | } 57 | 58 | // Check if the provided password is correct 59 | if user.ValidAuthentication(password) { 60 | // Create a new sessions 61 | session_cookie, err := user.NewSession() 62 | if err != nil { 63 | c.JSON(500, gin.H{"error": err.Error()}) 64 | log.WithFields(log.Fields{ 65 | "at": "controllers.newSession", 66 | "error": err.Error(), 67 | "username": username, 68 | }).Error("error creating session") 69 | return 70 | } 71 | session, err := models.SessionFromID(session_cookie) 72 | if err != nil { 73 | c.JSON(500, gin.H{"error": err.Error()}) 74 | log.WithFields(log.Fields{ 75 | "at": "controllers.newSession", 76 | "error": err.Error(), 77 | "username": username, 78 | }).Error("error looking up new sessions") 79 | return 80 | } 81 | 82 | // Set the wave_session cookie 83 | cookie := session.HTTPCookie() 84 | http.SetCookie(c.Writer, &cookie) 85 | c.JSON( 86 | 200, 87 | gin.H{ 88 | "username": user.Username, 89 | "admin": user.Admin, 90 | "version": 0, 91 | }, 92 | ) 93 | log.WithFields(log.Fields{ 94 | "at": "controllers.newSession", 95 | "username": username, 96 | }).Info("session created") 97 | } else { 98 | err := "incorrect password" 99 | c.JSON(401, gin.H{"error": "incorrect authentication"}) 100 | log.WithFields(log.Fields{ 101 | "at": "controllers.newSession", 102 | "error": err, 103 | "username": username, 104 | }).Error("error creating session") 105 | } 106 | } 107 | 108 | func deleteSession(c *gin.Context) { 109 | session_cookie, serr := sessionCookie(c) 110 | 111 | if serr == nil { 112 | session, err := models.SessionFromID(session_cookie) 113 | if err != nil { 114 | c.JSON(401, gin.H{"error": "no session"}) 115 | log.WithFields(log.Fields{ 116 | "at": "controllers.deleteSession", 117 | "error": err.Error(), 118 | }).Error("error finding session for cookie") 119 | return 120 | } 121 | err = session.Delete() 122 | if err != nil { 123 | c.JSON(500, gin.H{"error": "error deleting session"}) 124 | log.WithFields(log.Fields{ 125 | "at": "controllers.deleteSession", 126 | "error": err.Error(), 127 | }).Error("error deleting session") 128 | return 129 | } 130 | } 131 | 132 | c.Status(200) 133 | c.Abort() 134 | } 135 | -------------------------------------------------------------------------------- /controllers/session_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hkparker/Wave/models" 6 | "github.com/stretchr/testify/assert" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestNewSessionCreatesSession(t *testing.T) { 13 | assert := assert.New(t) 14 | 15 | user := models.CreateTestUser([]string{}) 16 | user.SetPassword("hunter2") 17 | req, err := http.NewRequest( 18 | "POST", 19 | testing_endpoint+"/sessions/create", 20 | strings.NewReader(fmt.Sprintf( 21 | "{\"username\": \"%s\", \"password\": \"hunter2\"}", 22 | user.Username, 23 | )), 24 | ) 25 | 26 | assert.Nil(err) 27 | resp, err := testing_client.Do(req) 28 | assert.Nil(err) 29 | assert.Equal(200, resp.StatusCode) 30 | if header, ok := resp.Header["Set-Cookie"]; assert.Equal(true, ok) { 31 | if assert.NotEqual(len(header), 0) { 32 | components := strings.Split(header[0], " ") 33 | assert.NotEqual(0, len(components)) 34 | cookie := components[0] 35 | session_id := cookie[13 : len(cookie)-1] 36 | found_user, err := models.UserFromSessionCookie(session_id) 37 | assert.Nil(err) 38 | assert.Equal(user.Username, found_user.Username) 39 | } 40 | } 41 | } 42 | 43 | func TestNewSessionErrorWithInvalidData(t *testing.T) { 44 | assert := assert.New(t) 45 | 46 | models.CreateTestUser([]string{}) 47 | req, err := http.NewRequest( 48 | "POST", 49 | testing_endpoint+"/sessions/create", 50 | strings.NewReader(fmt.Sprintf( 51 | "let me in ok?", 52 | )), 53 | ) 54 | 55 | assert.Nil(err) 56 | resp, err := testing_client.Do(req) 57 | assert.Nil(err) 58 | assert.Equal(400, resp.StatusCode) 59 | _, ok := resp.Header["Set-Cookie"] 60 | assert.Equal(false, ok) 61 | } 62 | 63 | func TestNewSessionErrorWithInvalidCredentials(t *testing.T) { 64 | assert := assert.New(t) 65 | 66 | user := models.CreateTestUser([]string{}) 67 | user.SetPassword("hunter2") 68 | req, err := http.NewRequest( 69 | "POST", 70 | testing_endpoint+"/sessions/create", 71 | strings.NewReader(fmt.Sprintf( 72 | "{\"username\": \"%s\", \"password\": \"hunter3\"}", 73 | user.Username, 74 | )), 75 | ) 76 | 77 | assert.Nil(err) 78 | resp, err := testing_client.Do(req) 79 | assert.Nil(err) 80 | assert.Equal(401, resp.StatusCode) 81 | _, ok := resp.Header["Set-Cookie"] 82 | assert.Equal(false, ok) 83 | } 84 | 85 | func TestDeleteSessionDeletesSession(t *testing.T) { 86 | assert := assert.New(t) 87 | 88 | user := models.CreateTestUser([]string{}) 89 | session_id, _ := user.NewSession() 90 | req, err := http.NewRequest( 91 | "POST", 92 | testing_endpoint+"/sessions/delete", 93 | strings.NewReader(""), 94 | ) 95 | 96 | session, err := models.SessionFromID(session_id) 97 | assert.Nil(err) 98 | cookie := session.HTTPCookie() 99 | req.AddCookie(&cookie) 100 | 101 | resp, err := testing_client.Do(req) 102 | assert.Nil(err) 103 | assert.Equal("/login", resp.Request.URL.Path) 104 | 105 | _, err = models.SessionFromID(session_id) 106 | if assert.NotNil(err) { 107 | assert.Equal("record not found", err.Error()) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /controllers/status.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | //log "github.com/sirupsen/logrus" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func getStatus(c *gin.Context) { 9 | user, err := currentUser(c) 10 | if err != nil { 11 | return 12 | } 13 | 14 | c.JSON(200, gin.H{ 15 | "username": user.Username, 16 | "admin": user.Admin, 17 | "version": 0, 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /controllers/tls.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/hkparker/Wave/models" 6 | ) 7 | 8 | func getTLS(c *gin.Context) { 9 | cert_data, key_data := models.APITLSData() 10 | c.JSON( 11 | 200, 12 | gin.H{ 13 | "certificate": string(cert_data), 14 | "private_key": string(key_data), 15 | }, 16 | ) 17 | } 18 | 19 | func setTLS(c *gin.Context) { 20 | tls_info, err := requestJSON(c) 21 | if err != nil { 22 | c.JSON(400, gin.H{"error": err.Error()}) 23 | return 24 | } 25 | 26 | err = models.SetTLS(tls_info) 27 | if err != nil { 28 | c.JSON(500, gin.H{"error": err.Error()}) 29 | } else { 30 | c.JSON(200, gin.H{"success": "true"}) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /controllers/users.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/hkparker/Wave/models" 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | // Handle a request from administrator to create a new user 10 | func createUser(c *gin.Context) { 11 | // Ensure the request is valid JSON and get 12 | // the user from the current session 13 | user_info, err := requestJSON(c) 14 | if err != nil { 15 | return 16 | } 17 | admin, err := currentUser(c) 18 | if err != nil { 19 | return 20 | } 21 | 22 | // Ensure the JSON contains a username key with data 23 | username, ok := user_info["username"] 24 | if !ok || username == "" { 25 | username_error := "no username provided" 26 | c.JSON(400, gin.H{"error": username_error}) 27 | log.WithFields(log.Fields{ 28 | "at": "controllers.createUser", 29 | "error": username_error, 30 | "admin": admin.Username, 31 | }).Error("error creating user") 32 | return 33 | } 34 | 35 | // Ensure the JSON contains a password key with data 36 | password, ok := user_info["password"] 37 | if !ok || password == "" { 38 | password_error := "no password provided" 39 | c.JSON(400, gin.H{"error": password_error}) 40 | log.WithFields(log.Fields{ 41 | "at": "controllers.createUser", 42 | "error": password_error, 43 | "admin": admin.Username, 44 | }).Error("error creating user") 45 | return 46 | } 47 | 48 | // Ensure the JSON contains a valid admin key with data 49 | adminKey, ok := user_info["admin"] 50 | if !ok || !(adminKey == "true" || adminKey == "false") { 51 | admin_error := "no valid admin key provided" 52 | c.JSON(400, gin.H{"error": admin_error}) 53 | log.WithFields(log.Fields{ 54 | "at": "controllers.createUser", 55 | "error": admin_error, 56 | "admin": admin.Username, 57 | }).Error("error creating user") 58 | return 59 | } 60 | 61 | // Save the new user and create a link for the user 62 | // to set a password 63 | err = models.CreateUser(username, password, adminKey == "true") 64 | if err == nil { 65 | c.JSON(200, gin.H{ 66 | "success": "true", 67 | }) 68 | log.WithFields(log.Fields{ 69 | "at": "controllers.createUser", 70 | "username": username, 71 | "admin": admin.Username, 72 | }).Info("created user") 73 | } else { 74 | // Report if the user could not be created 75 | c.JSON(500, gin.H{"error": err.Error()}) 76 | log.WithFields(log.Fields{ 77 | "at": "controllers.createUser", 78 | "username": username, 79 | "error": err.Error(), 80 | "admin": admin.Username, 81 | }).Error("error creating user") 82 | } 83 | } 84 | 85 | // Handle requests for users to change their own names 86 | func updateUserName(c *gin.Context) { 87 | // Ensure the request is valid JSON and get 88 | // the user from the current session 89 | user_info, err := requestJSON(c) 90 | if err != nil { 91 | return 92 | } 93 | user, err := currentUser(c) 94 | if err != nil { 95 | return 96 | } 97 | 98 | // Ensure the JSON contains a username key with data 99 | username, ok := user_info["name"] 100 | if !ok || username == "" { 101 | name_error := "no username provided" 102 | c.JSON(400, gin.H{"error": name_error}) 103 | log.WithFields(log.Fields{ 104 | "at": "controllers.updateUserName", 105 | "error": name_error, 106 | "username": username, 107 | "user_id": user.ID, 108 | }).Error("error updating user name") 109 | return 110 | } 111 | 112 | // Update the users name and save to the database 113 | user.Name = username 114 | db_err := user.Save() 115 | if db_err != nil { 116 | // Return an error if the update could not be saved 117 | c.JSON(500, gin.H{"error": db_err.Error()}) 118 | log.WithFields(log.Fields{ 119 | "at": "controllers.updateUserName", 120 | "error": db_err.Error(), 121 | "username": username, 122 | "user_id": user.ID, 123 | }).Error("error updating user name") 124 | } else { 125 | c.JSON(200, gin.H{"success": "true"}) 126 | log.WithFields(log.Fields{ 127 | "at": "controllers.updateUserName", 128 | "username": username, 129 | "user_id": user.ID, 130 | }).Info("user name updated") 131 | } 132 | } 133 | 134 | // Handle requests for password changes from users 135 | func updateUserPassword(c *gin.Context) { 136 | // Ensure the request is valid JSON and get 137 | // the user from the current session 138 | user_info, err := requestJSON(c) 139 | if err != nil { 140 | return 141 | } 142 | user, err := currentUser(c) 143 | if err != nil { 144 | return 145 | } 146 | 147 | // Ensure the JSON contains an old_password key 148 | old_password, ok := user_info["old_password"] 149 | if !ok { 150 | err := "no old password provided" 151 | c.JSON(400, gin.H{"error": err}) 152 | log.WithFields(log.Fields{ 153 | "at": "controllers.updateUserPassword", 154 | "error": err, 155 | "user": user.Username, 156 | }).Error("error updating user password") 157 | return 158 | } 159 | 160 | // Ensure the JSON contains an new_password key 161 | new_password, ok := user_info["new_password"] 162 | if !ok { 163 | err := "no new password provided" 164 | c.JSON(400, gin.H{"error": err}) 165 | log.WithFields(log.Fields{ 166 | "at": "controllers.updateUserPassword", 167 | "error": err, 168 | "user": user.Name, 169 | }).Error("error updating user password") 170 | return 171 | } 172 | 173 | // Ensure the old password is valid authentication 174 | if !user.ValidAuthentication(old_password) { 175 | err := "old password incorrect" 176 | c.JSON(401, gin.H{"error": err}) 177 | log.WithFields(log.Fields{ 178 | "at": "controllers.updateUserPassword", 179 | "error": err, 180 | "user": user.Name, 181 | }).Error("error updating user password") 182 | return 183 | } 184 | 185 | // Set the new password for the user 186 | err = user.SetPassword(new_password) 187 | if err != nil { 188 | // Report an error if the user could not be saved 189 | c.JSON(500, gin.H{"error": err.Error()}) 190 | log.WithFields(log.Fields{ 191 | "at": "controllers.updateUserPassword", 192 | "error": err.Error(), 193 | "user": user.Name, 194 | }).Error("error setting user password") 195 | } else { 196 | // Invalidate all session except the one which made the request 197 | session_id, err := sessionCookie(c) 198 | if err != nil { 199 | c.JSON(500, gin.H{"error": err.Error()}) 200 | log.WithFields(log.Fields{ 201 | "at": "controllers.updateUserPassword", 202 | "error": err.Error(), 203 | "user": user.Name, 204 | }).Error("error getting session cookie from valid session") 205 | } else { 206 | user.DestroyAllOtherSessions(session_id) 207 | c.JSON(200, gin.H{"success": "true"}) 208 | log.WithFields(log.Fields{ 209 | "at": "controllers.updateUserPassword", 210 | "user": user.Name, 211 | }).Info("user password updated") 212 | } 213 | } 214 | } 215 | 216 | // Handle request from administrator to delete a user 217 | func deleteUser(c *gin.Context) { 218 | // Ensure the request is valid JSON and get 219 | // the user from the current session 220 | user_info, err := requestJSON(c) 221 | if err != nil { 222 | return 223 | } 224 | admin, err := currentUser(c) 225 | if err != nil { 226 | return 227 | } 228 | 229 | // Ensure the JSON contains a username key with data 230 | username, ok := user_info["username"] 231 | if !ok || username == "" { 232 | err := "no username provided" 233 | c.JSON(400, gin.H{"error": err}) 234 | log.WithFields(log.Fields{ 235 | "at": "controllers.deleteUser", 236 | "username": username, 237 | "error": err, 238 | "admin": admin.Username, 239 | }).Error("error updating user password") 240 | return 241 | } 242 | 243 | // Ensure the user exists 244 | user, err := models.UserByUsername(username) 245 | if err != nil { 246 | c.JSON(500, gin.H{"error": err.Error()}) 247 | log.WithFields(log.Fields{ 248 | "at": "controllers.deleteUser", 249 | "username": username, 250 | "error": err.Error(), 251 | "admin": admin.Username, 252 | }).Error("error looking up user to delete") 253 | return 254 | } 255 | 256 | // Ensure the user is not the only remaining administrator removing themselves 257 | only_admin, err := user.OnlyAdmin() 258 | if err != nil { 259 | c.JSON(500, gin.H{"error": err.Error()}) 260 | log.WithFields(log.Fields{ 261 | "at": "controllers.deleteUser", 262 | "username": username, 263 | "error": err.Error(), 264 | "admin": admin.Username, 265 | }).Error("error checking admin status of user to delete") 266 | return 267 | } 268 | if only_admin { 269 | err := "user is only remaining admin" 270 | c.JSON(500, gin.H{"error": err}) 271 | log.WithFields(log.Fields{ 272 | "at": "controllers.deleteUser", 273 | "username": username, 274 | "error": err, 275 | "admin": admin.Username, 276 | }).Error("error updating user password") 277 | return 278 | } 279 | 280 | // Destroy all sessions and delete the user 281 | user.DestroyAllSessions() 282 | err = user.Delete() 283 | if err != nil { 284 | // Return an error if the user could not be deleted 285 | c.JSON(500, gin.H{"error": err.Error()}) 286 | log.WithFields(log.Fields{ 287 | "at": "controllers.deleteUser", 288 | "username": username, 289 | "error": err.Error(), 290 | "admin": admin.Username, 291 | }).Error("error deleting user") 292 | } else { 293 | c.JSON(200, gin.H{"success": "true"}) 294 | log.WithFields(log.Fields{ 295 | "at": "controllers.deleteUser", 296 | "username": username, 297 | "admin": admin.Username, 298 | }).Info("deleted user") 299 | } 300 | } 301 | 302 | func assignUserPassword(c *gin.Context) { 303 | // Ensure the request is valid JSON and get 304 | // the user from the current session 305 | user_info, err := requestJSON(c) 306 | if err != nil { 307 | return 308 | } 309 | admin, err := currentUser(c) 310 | if err != nil { 311 | return 312 | } 313 | 314 | // Ensure the JSON contains a username key with data 315 | username, ok := user_info["username"] 316 | if !ok || username == "" { 317 | username_error := "no username provided" 318 | c.JSON(400, gin.H{"error": username_error}) 319 | log.WithFields(log.Fields{ 320 | "at": "controllers.assignUserPassword", 321 | "error": username_error, 322 | "admin": admin.Username, 323 | }).Error("error assigning user password") 324 | return 325 | } 326 | 327 | // Ensure the JSON contains a password key with data 328 | password, ok := user_info["password"] 329 | if !ok || password == "" { 330 | password_error := "no password provided" 331 | c.JSON(400, gin.H{"error": password_error}) 332 | log.WithFields(log.Fields{ 333 | "at": "controllers.assignUserPassword", 334 | "error": password_error, 335 | "admin": admin.Username, 336 | }).Error("error assigning user password") 337 | return 338 | } 339 | 340 | user, err := models.UserByUsername(username) 341 | if err != nil { 342 | c.JSON(500, gin.H{"error": "unable to load user"}) 343 | log.WithFields(log.Fields{ 344 | "at": "controllers.assignUserPassword", 345 | "error": err.Error(), 346 | "admin": admin.Username, 347 | }).Error("error loading user") 348 | return 349 | } 350 | 351 | user.SetPassword(password) 352 | if err != nil { 353 | c.JSON(500, gin.H{"error": "unable to set password"}) 354 | log.WithFields(log.Fields{ 355 | "at": "controllers.assignUserPassword", 356 | "error": err.Error(), 357 | "admin": admin.Username, 358 | }).Error("error setting password") 359 | return 360 | } 361 | 362 | user.DestroyAllSessions() 363 | c.JSON(200, gin.H{"success": "true"}) 364 | log.WithFields(log.Fields{ 365 | "at": "controllers.assignUserPassword", 366 | "username": username, 367 | "admin": admin.Username, 368 | }).Info("assigned password") 369 | } 370 | 371 | func getUsers(c *gin.Context) { 372 | admin, err := currentUser(c) 373 | if err != nil { 374 | return 375 | } 376 | 377 | users, err := models.Users() 378 | if err != nil { 379 | c.JSON(500, gin.H{"error": err.Error()}) 380 | log.WithFields(log.Fields{ 381 | "at": "controllers.getUsers", 382 | "error": err.Error(), 383 | "admin": admin.Username, 384 | }).Error("error looking up users") 385 | return 386 | } 387 | var data []gin.H 388 | for _, user := range users { 389 | data = append(data, gin.H{ 390 | "username": user.Username, 391 | "name": user.Name, 392 | "admin": func() string { 393 | if user.Admin { 394 | return "true" 395 | } 396 | return "false" 397 | }(), 398 | "sessions": user.SessionCount(), 399 | }) 400 | } 401 | c.JSON(200, data) 402 | } 403 | -------------------------------------------------------------------------------- /controllers/users_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/hkparker/Wave/models" 7 | "github.com/stretchr/testify/assert" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func TestAdminCanCreateUser(t *testing.T) { 14 | assert := assert.New(t) 15 | admin := models.CreateTestUser([]string{"admin"}) 16 | session_id, _ := admin.NewSession() 17 | req, err := http.NewRequest( 18 | "POST", 19 | testing_endpoint+"/users/create", 20 | strings.NewReader(fmt.Sprintf( 21 | "{\"username\": \"samsepi0l\"}", 22 | )), 23 | ) 24 | 25 | session, err := models.SessionFromID(session_id) 26 | assert.Nil(err) 27 | cookie := session.HTTPCookie() 28 | req.AddCookie(&cookie) 29 | 30 | assert.Nil(err) 31 | resp, err := testing_client.Do(req) 32 | assert.Nil(err) 33 | assert.Equal(200, resp.StatusCode) 34 | decoder := json.NewDecoder(resp.Body) 35 | var params map[string]string 36 | err = decoder.Decode(¶ms) 37 | if !assert.Nil(err) { 38 | assert.Nil(err.Error()) 39 | } 40 | if assert.NotNil(params) { 41 | if assert.NotNil(params["success"]) { 42 | assert.Equal("true", params["success"]) 43 | } 44 | } 45 | _, db_err := models.UserByUsername("samsepi0l") 46 | assert.Nil(db_err) 47 | } 48 | 49 | func TestUserCannotCreateUser(t *testing.T) { 50 | assert := assert.New(t) 51 | user := models.CreateTestUser([]string{}) 52 | session_id, _ := user.NewSession() 53 | req, err := http.NewRequest( 54 | "POST", 55 | testing_endpoint+"/users/create", 56 | strings.NewReader(fmt.Sprintf( 57 | "{\"username\": \"notallowed@example.com\"}", 58 | )), 59 | ) 60 | 61 | session, err := models.SessionFromID(session_id) 62 | assert.Nil(err) 63 | cookie := session.HTTPCookie() 64 | req.AddCookie(&cookie) 65 | 66 | assert.Nil(err) 67 | resp, err := testing_client.Do(req) 68 | assert.Nil(err) 69 | assert.Equal(401, resp.StatusCode) 70 | decoder := json.NewDecoder(resp.Body) 71 | var params map[string]string 72 | err = decoder.Decode(¶ms) 73 | if !assert.Nil(err) { 74 | assert.Nil(err.Error()) 75 | } 76 | if assert.NotNil(params) { 77 | if assert.NotNil(params["error"]) { 78 | assert.Equal("permission denied", params["error"]) 79 | } 80 | } 81 | } 82 | 83 | func TestCreateUserWithInvalidData(t *testing.T) { 84 | assert := assert.New(t) 85 | user := models.CreateTestUser([]string{"admin"}) 86 | session_id, _ := user.NewSession() 87 | req, err := http.NewRequest( 88 | "POST", 89 | testing_endpoint+"/users/create", 90 | strings.NewReader(fmt.Sprintf( 91 | "this is not valid json", 92 | )), 93 | ) 94 | 95 | session, err := models.SessionFromID(session_id) 96 | assert.Nil(err) 97 | cookie := session.HTTPCookie() 98 | req.AddCookie(&cookie) 99 | 100 | assert.Nil(err) 101 | resp, err := testing_client.Do(req) 102 | assert.Nil(err) 103 | assert.Equal(400, resp.StatusCode) 104 | decoder := json.NewDecoder(resp.Body) 105 | var params map[string]string 106 | err = decoder.Decode(¶ms) 107 | if !assert.Nil(err) { 108 | assert.Nil(err.Error()) 109 | } 110 | if assert.NotNil(params) { 111 | if assert.NotNil(params["error"]) { 112 | assert.Equal("error parsing json", params["error"]) 113 | } 114 | } 115 | } 116 | 117 | func TestCreateUserWithNoData(t *testing.T) { 118 | assert := assert.New(t) 119 | user := models.CreateTestUser([]string{"admin"}) 120 | session_id, _ := user.NewSession() 121 | req, err := http.NewRequest( 122 | "POST", 123 | testing_endpoint+"/users/create", 124 | strings.NewReader(fmt.Sprintf( 125 | "", 126 | )), 127 | ) 128 | 129 | session, err := models.SessionFromID(session_id) 130 | assert.Nil(err) 131 | cookie := session.HTTPCookie() 132 | req.AddCookie(&cookie) 133 | 134 | assert.Nil(err) 135 | resp, err := testing_client.Do(req) 136 | assert.Nil(err) 137 | assert.Equal(400, resp.StatusCode) 138 | decoder := json.NewDecoder(resp.Body) 139 | var params map[string]string 140 | err = decoder.Decode(¶ms) 141 | if !assert.Nil(err) { 142 | assert.Nil(err.Error()) 143 | } 144 | if assert.NotNil(params) { 145 | if assert.NotNil(params["error"]) { 146 | assert.Equal("error parsing json", params["error"]) 147 | } 148 | } 149 | 150 | } 151 | 152 | func TestUserCanChangeTheirName(t *testing.T) { 153 | assert := assert.New(t) 154 | 155 | user := models.CreateTestUser([]string{}) 156 | session_id, _ := user.NewSession() 157 | req, err := http.NewRequest( 158 | "POST", 159 | testing_endpoint+"/users/name", 160 | strings.NewReader(fmt.Sprintf( 161 | "{\"name\": \"Foober Doober\"}", 162 | )), 163 | ) 164 | 165 | session, err := models.SessionFromID(session_id) 166 | assert.Nil(err) 167 | cookie := session.HTTPCookie() 168 | req.AddCookie(&cookie) 169 | 170 | assert.Nil(err) 171 | resp, err := testing_client.Do(req) 172 | assert.Nil(err) 173 | assert.Equal(200, resp.StatusCode) 174 | decoder := json.NewDecoder(resp.Body) 175 | var params map[string]string 176 | err = decoder.Decode(¶ms) 177 | if !assert.Nil(err) { 178 | assert.Nil(err.Error()) 179 | } 180 | if assert.NotNil(params) { 181 | if assert.NotNil(params["success"]) { 182 | assert.Equal("true", params["success"]) 183 | } 184 | } 185 | user.Reload() 186 | assert.Equal("Foober Doober", user.Name) 187 | } 188 | 189 | func TestUserCannotChangeTheirNameWithNonJSONData(t *testing.T) { 190 | assert := assert.New(t) 191 | 192 | user := models.CreateTestUser([]string{}) 193 | session_id, _ := user.NewSession() 194 | req, err := http.NewRequest( 195 | "POST", 196 | testing_endpoint+"/users/name", 197 | strings.NewReader(fmt.Sprintf( 198 | "this isn't going to work", 199 | )), 200 | ) 201 | 202 | session, err := models.SessionFromID(session_id) 203 | assert.Nil(err) 204 | cookie := session.HTTPCookie() 205 | req.AddCookie(&cookie) 206 | 207 | resp, err := testing_client.Do(req) 208 | assert.Nil(err) 209 | assert.Equal(400, resp.StatusCode) 210 | decoder := json.NewDecoder(resp.Body) 211 | var params map[string]string 212 | err = decoder.Decode(¶ms) 213 | if !assert.Nil(err) { 214 | assert.Nil(err.Error()) 215 | } 216 | if assert.NotNil(params) { 217 | if assert.NotNil(params["error"]) { 218 | assert.Equal("error parsing json", params["error"]) 219 | } 220 | } 221 | } 222 | 223 | func TestUserCannotChangeNameWithMissingKey(t *testing.T) { 224 | assert := assert.New(t) 225 | 226 | user := models.CreateTestUser([]string{}) 227 | session_id, _ := user.NewSession() 228 | req, err := http.NewRequest( 229 | "POST", 230 | testing_endpoint+"/users/name", 231 | strings.NewReader(fmt.Sprintf( 232 | "{\"something\": \"else\"}", 233 | )), 234 | ) 235 | 236 | session, err := models.SessionFromID(session_id) 237 | assert.Nil(err) 238 | cookie := session.HTTPCookie() 239 | req.AddCookie(&cookie) 240 | 241 | assert.Nil(err) 242 | resp, err := testing_client.Do(req) 243 | assert.Nil(err) 244 | assert.Equal(400, resp.StatusCode) 245 | decoder := json.NewDecoder(resp.Body) 246 | var params map[string]string 247 | err = decoder.Decode(¶ms) 248 | if !assert.Nil(err) { 249 | assert.Nil(err.Error()) 250 | } 251 | if assert.NotNil(params) { 252 | if assert.NotNil(params["error"]) { 253 | assert.Equal("no username provided", params["error"]) 254 | } 255 | } 256 | } 257 | 258 | func TestUserCanUpdatePassword(t *testing.T) { 259 | assert := assert.New(t) 260 | 261 | user := models.CreateTestUser([]string{}) 262 | user.SetPassword("hunter2") 263 | session_id, _ := user.NewSession() 264 | user.NewSession() 265 | assert.Equal(2, len(user.Sessions)) 266 | req, err := http.NewRequest( 267 | "POST", 268 | testing_endpoint+"/users/password", 269 | strings.NewReader(fmt.Sprintf( 270 | "{\"old_password\": \"hunter2\", \"new_password\": \"1234\"}", 271 | )), 272 | ) 273 | 274 | session, err := models.SessionFromID(session_id) 275 | assert.Nil(err) 276 | cookie := session.HTTPCookie() 277 | req.AddCookie(&cookie) 278 | 279 | assert.Nil(err) 280 | resp, err := testing_client.Do(req) 281 | assert.Nil(err) 282 | assert.Equal(200, resp.StatusCode) 283 | user.Reload() 284 | assert.Equal(true, user.ValidAuthentication("1234")) 285 | var sessions []models.Session 286 | models.Orm.Model(&user).Related(&sessions) 287 | assert.Equal(1, len(sessions)) 288 | } 289 | 290 | func TestUserCannotUpdatePasswordWithBadPassword(t *testing.T) { 291 | assert := assert.New(t) 292 | 293 | user := models.CreateTestUser([]string{}) 294 | session_id, _ := user.NewSession() 295 | req, err := http.NewRequest( 296 | "POST", 297 | testing_endpoint+"/users/password", 298 | strings.NewReader(fmt.Sprintf( 299 | "{\"old_password\": \"wrong\", \"new_password\": \"1234\"}", 300 | )), 301 | ) 302 | 303 | session, err := models.SessionFromID(session_id) 304 | assert.Nil(err) 305 | cookie := session.HTTPCookie() 306 | req.AddCookie(&cookie) 307 | 308 | assert.Nil(err) 309 | resp, err := testing_client.Do(req) 310 | assert.Nil(err) 311 | assert.Equal(401, resp.StatusCode) 312 | } 313 | 314 | func TestAdminCanAssignUserPassword(t *testing.T) { 315 | assert := assert.New(t) 316 | 317 | user := models.CreateTestUser([]string{}) 318 | user.NewSession() 319 | admin := models.CreateTestUser([]string{"admin"}) 320 | session_id, _ := admin.NewSession() 321 | req, err := http.NewRequest( 322 | "POST", 323 | testing_endpoint+"/users/assign-password", 324 | strings.NewReader(fmt.Sprintf( 325 | "{\"username\": \"%s\", \"password\": \"1234\"}", 326 | user.Username, 327 | )), 328 | ) 329 | 330 | session, err := models.SessionFromID(session_id) 331 | assert.Nil(err) 332 | cookie := session.HTTPCookie() 333 | req.AddCookie(&cookie) 334 | 335 | assert.Nil(err) 336 | resp, err := testing_client.Do(req) 337 | assert.Nil(err) 338 | assert.Equal(200, resp.StatusCode) 339 | user.Reload() 340 | assert.Equal(true, user.ValidAuthentication("1234")) 341 | var sessions []models.Session 342 | models.Orm.Model(&user).Related(&sessions) 343 | assert.Equal(0, len(sessions)) 344 | } 345 | 346 | func TestUserCannotAssignUserPassword(t *testing.T) { 347 | assert := assert.New(t) 348 | 349 | user := models.CreateTestUser([]string{}) 350 | not_admin := models.CreateTestUser([]string{}) 351 | session_id, _ := not_admin.NewSession() 352 | req, err := http.NewRequest( 353 | "POST", 354 | testing_endpoint+"/users/assign-password", 355 | strings.NewReader(fmt.Sprintf( 356 | "{\"username\": \"%s\", \"password\": \"1234\"}", 357 | user.Username, 358 | )), 359 | ) 360 | 361 | session, err := models.SessionFromID(session_id) 362 | assert.Nil(err) 363 | cookie := session.HTTPCookie() 364 | req.AddCookie(&cookie) 365 | 366 | assert.Nil(err) 367 | resp, err := testing_client.Do(req) 368 | assert.Nil(err) 369 | assert.Equal(401, resp.StatusCode) 370 | } 371 | 372 | func TestAdminCanDeleteUser(t *testing.T) { 373 | assert := assert.New(t) 374 | 375 | user := models.CreateTestUser([]string{}) 376 | admin := models.CreateTestUser([]string{"admin"}) 377 | session_id, _ := admin.NewSession() 378 | req, err := http.NewRequest( 379 | "POST", 380 | testing_endpoint+"/users/delete", 381 | strings.NewReader(fmt.Sprintf( 382 | "{\"username\": \"%s\"}", 383 | user.Username, 384 | )), 385 | ) 386 | 387 | session, err := models.SessionFromID(session_id) 388 | assert.Nil(err) 389 | cookie := session.HTTPCookie() 390 | req.AddCookie(&cookie) 391 | 392 | assert.Nil(err) 393 | resp, err := testing_client.Do(req) 394 | assert.Nil(err) 395 | assert.Equal(200, resp.StatusCode) 396 | user.Reload() 397 | assert.NotNil(user.DeletedAt) 398 | } 399 | 400 | func TestGetUsers(t *testing.T) { 401 | assert := assert.New(t) 402 | models.DropUsers() 403 | 404 | admin := models.CreateTestUser([]string{"admin"}) 405 | session_id, _ := admin.NewSession() 406 | session, err := models.SessionFromID(session_id) 407 | assert.Nil(err) 408 | cookie := session.HTTPCookie() 409 | var names []map[string]string 410 | var params map[string]string 411 | 412 | req, err := http.NewRequest( 413 | "GET", 414 | testing_endpoint+"/users", 415 | strings.NewReader(fmt.Sprintf("")), 416 | ) 417 | req.AddCookie(&cookie) 418 | assert.Nil(err) 419 | resp, err := testing_client.Do(req) 420 | assert.Nil(err) 421 | assert.Equal(200, resp.StatusCode) 422 | decoder := json.NewDecoder(resp.Body) 423 | err = decoder.Decode(&names) 424 | if !assert.Nil(err) { 425 | assert.Nil(err.Error()) 426 | } 427 | if assert.NotNil(names) { 428 | assert.Equal(1, len(names)) 429 | for _, user := range names { 430 | if name, ok := user["name"]; assert.Equal(true, ok) { 431 | assert.Equal("Wifi Jackson", name) 432 | } 433 | if adm, ok := user["admin"]; assert.Equal(true, ok) { 434 | assert.Equal("true", adm) 435 | } 436 | } 437 | } 438 | 439 | req, err = http.NewRequest( 440 | "POST", 441 | testing_endpoint+"/users/create", 442 | strings.NewReader(fmt.Sprintf( 443 | "{\"username\": \"number1\"}", 444 | )), 445 | ) 446 | req.AddCookie(&cookie) 447 | assert.Nil(err) 448 | resp, err = testing_client.Do(req) 449 | assert.Nil(err) 450 | assert.Equal(200, resp.StatusCode) 451 | decoder = json.NewDecoder(resp.Body) 452 | err = decoder.Decode(¶ms) 453 | if !assert.Nil(err) { 454 | assert.Nil(err.Error()) 455 | } 456 | if assert.NotNil(params) { 457 | if assert.NotNil(params["success"]) { 458 | assert.Equal("true", params["success"]) 459 | } 460 | } 461 | 462 | req, err = http.NewRequest( 463 | "GET", 464 | testing_endpoint+"/users", 465 | strings.NewReader(fmt.Sprintf("")), 466 | ) 467 | req.AddCookie(&cookie) 468 | assert.Nil(err) 469 | resp, err = testing_client.Do(req) 470 | assert.Nil(err) 471 | assert.Equal(200, resp.StatusCode) 472 | decoder = json.NewDecoder(resp.Body) 473 | err = decoder.Decode(&names) 474 | if !assert.Nil(err) { 475 | assert.Nil(err.Error()) 476 | } 477 | if assert.NotNil(names) { 478 | assert.Equal(2, len(names)) 479 | } 480 | } 481 | -------------------------------------------------------------------------------- /controllers/visualizer.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/gorilla/websocket" 6 | "github.com/hkparker/Wave/engines/visualizer" 7 | "github.com/satori/go.uuid" 8 | log "github.com/sirupsen/logrus" 9 | "net/http" 10 | "sync" 11 | ) 12 | 13 | var VisualClients = make(map[string]*websocket.Conn, 0) 14 | var VisualClientMux sync.Mutex 15 | 16 | func streamVisualization(c *gin.Context) { 17 | upgrayedd := websocket.Upgrader{ 18 | CheckOrigin: func(r *http.Request) bool { 19 | return true 20 | }, 21 | } 22 | conn, err := upgrayedd.Upgrade(c.Writer, c.Request, nil) 23 | if err == nil { 24 | id := uuid.NewV4().String() 25 | for _, event := range visualizer.CatchupEvents() { 26 | if len(event) > 0 { 27 | err := conn.WriteJSON(event) 28 | if err != nil { 29 | log.WithFields(log.Fields{ 30 | "at": "controllers.streamVisualization", 31 | "error": err.Error(), 32 | }).Error("error writing catch-up event") 33 | } 34 | } 35 | } 36 | VisualClientMux.Lock() 37 | VisualClients[id] = conn 38 | VisualClientMux.Unlock() 39 | } else { 40 | log.WithFields(log.Fields{ 41 | "at": "controllers.streamVisualization", 42 | "error": err.Error(), 43 | }).Warn("failed to upgrade websocket connection") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /doc/api.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | 4 | 5 | ## Session 6 | 7 | ### Login 8 | 9 | ## User 10 | 11 | ### Create 12 | 13 | ### Name 14 | 15 | ### Password 16 | 17 | ### Destroy 18 | -------------------------------------------------------------------------------- /doc/visualizer.md: -------------------------------------------------------------------------------- 1 | # Visualizer 2 | 3 | ## Events 4 | 5 | Events are streamed from a websocket connection to `/streams/visualizer`. All events are JSON representations of the Go type `map[string]string`. 6 | 7 | ### Adding Items 8 | 9 | The following events are recieved when a new item should be added to the visualization. 10 | 11 | ##### NewDevice 12 | 13 | This event instructs views to add a new device to the visualization. 14 | 15 | ```js 16 | { 17 | "type": "NewDevice", 18 | "MAC": "11:22:33:44:55:66", 19 | "VendorByes": "", 20 | "IsAP": "false", 21 | } 22 | ``` 23 | 24 | ##### NewAssociation 25 | 26 | This event instructs views to associate two devices on the graph. 27 | 28 | ```js 29 | { 30 | "type": "NewAssociation", 31 | "MAC1": "11:22:33:44:55:66", 32 | "MAC2": "11:22:33:44:55:66", 33 | } 34 | ``` 35 | 36 | ### Updating Items 37 | 38 | ##### UpdateAssociation 39 | 40 | ```js 41 | { 42 | "type": "UpdateAssociation", 43 | "MAC1": "11:22:33:44:55:66", 44 | "MAC2": "11:22:33:44:55:66", 45 | "MAC1Tx": "16325" 46 | } 47 | ``` 48 | 49 | ### Drawing Events 50 | 51 | These events are one time animations that do not impact the state of the graph. 52 | 53 | ##### AnimateNullProbeRequest 54 | 55 | This animation indicates a device has probed for any available network. 56 | 57 | ```js 58 | { 59 | "type": "AnimateNullProbeRequest", 60 | "MAC1": "11:22:33:44:55:66", 61 | } 62 | ``` 63 | 64 | ##### AnimateProbeRequest 65 | 66 | This animation indicates a device has probed for a specific SSID. 67 | 68 | ```js 69 | { 70 | "type": "AnimateProbeRequest", 71 | "MAC1": "11:22:33:44:55:66", 72 | "SSID": "wifi" 73 | } 74 | ``` 75 | 76 | ### Removing Items 77 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | pg: 2 | image: postgres 3 | net: "host" 4 | ports: 5 | - "5432:5432" 6 | -------------------------------------------------------------------------------- /engines/ids/ids.go: -------------------------------------------------------------------------------- 1 | package ids 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/hkparker/Wave/helpers" 7 | "github.com/hkparker/Wave/models" 8 | "github.com/robertkrimen/otto" 9 | log "github.com/sirupsen/logrus" 10 | "sync" 11 | ) 12 | 13 | var VMs = make(map[string][]*otto.Otto, 0) 14 | var NewVMs = make(chan []*otto.Otto, 1) 15 | var Alerts = make(chan models.Alert, 0) 16 | 17 | func init() { 18 | go processAlerts() 19 | go prepareVMs() 20 | } 21 | 22 | var alerting_function = func(call otto.FunctionCall) otto.Value { 23 | new_alert := models.Alert{} 24 | err := json.Unmarshal([]byte(call.Argument(0).String()), &new_alert) 25 | if err != nil { 26 | log.WithFields(log.Fields{ 27 | "at": "ids.alerting_function", 28 | "error": err.Error(), 29 | }).Error("bad alert from rule") 30 | } else { 31 | Alerts <- new_alert 32 | } 33 | return otto.Value{} 34 | } 35 | 36 | func processAlerts() { 37 | for _ = range Alerts { 38 | // dedup between interfaces 39 | // save to database 40 | // send down websocket 41 | // update metadata relationships 42 | // email / message / page 43 | } 44 | } 45 | 46 | func prepareVMs() { 47 | for { 48 | NewVMs <- buildVMs() 49 | } 50 | } 51 | 52 | func buildVMs() (vm_set []*otto.Otto) { 53 | rule_path := "engines/ids/rules" 54 | rule_files, err := helpers.AssetDir(rule_path) 55 | if err != nil { 56 | log.WithFields(log.Fields{ 57 | "at": "ids.buildVMs", 58 | "error": err.Error(), 59 | }).Error("unable to load rules") 60 | return 61 | } 62 | for _, rule_file := range rule_files { 63 | if len(rule_file) < 3 { 64 | continue 65 | } else if rule_file[len(rule_file)-3:] != ".js" { 66 | continue 67 | } 68 | if rule_data, ferr := helpers.Asset(rule_path + "/" + rule_file); ferr == nil { 69 | vm := otto.New() 70 | _, err := vm.Run(string(rule_data)) 71 | if err != nil { 72 | log.WithFields(log.Fields{ 73 | "at": "ids.buildVMs", 74 | "file": rule_file, 75 | "error": err.Error(), 76 | }).Error("error loading rule data into VM") 77 | } 78 | vm.Set("alert_string", alerting_function) 79 | vm.Run("alert = function(event) { alert_string(JSON.stringify(event)) }") 80 | vm_set = append(vm_set, vm) 81 | } else { 82 | log.WithFields(log.Fields{ 83 | "at": "ids.buildVMs", 84 | "error": ferr.Error(), 85 | }).Error("unable to load rule file") 86 | } 87 | } 88 | return 89 | } 90 | 91 | func Insert(frame string, parsed models.Wireless80211Frame, collector_id string) { 92 | vm_set, ok := VMs[collector_id] 93 | if !ok { 94 | vm_set = <-NewVMs 95 | VMs[collector_id] = vm_set 96 | // set the collector id as a variable 97 | } 98 | var evals sync.WaitGroup 99 | for _, vm := range vm_set { 100 | evals.Add(1) 101 | go func(vm *otto.Otto) { 102 | defer evals.Done() 103 | _, err := vm.Run(fmt.Sprintf("evaluate(%s)", frame)) 104 | if err != nil { 105 | log.WithFields(log.Fields{}).Error(err) 106 | } 107 | }(vm) 108 | } 109 | evals.Wait() 110 | } 111 | -------------------------------------------------------------------------------- /engines/ids/ids_test.go: -------------------------------------------------------------------------------- 1 | package ids 2 | -------------------------------------------------------------------------------- /engines/ids/rules/example-rule.js: -------------------------------------------------------------------------------- 1 | // Every rule defines a manifest variable with some fields 2 | var manifest = { 3 | "Name": "Example Rule" // Name of the rule 4 | } 5 | 6 | // Every rule defines and evaluate function which accepts a models.Wireless80211Frame 7 | function evaluate(frame) { 8 | // We can pretty print each from by logging JSON.stringify 9 | //console.log(JSON.stringify(frame, null, 2)) 10 | 11 | // If this frame results in an IDS alert, we send an 12 | alert({ 13 | "Title": "Example rule got a frame", 14 | "Rule": manifest.Name, 15 | "Severity": "low" 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /engines/visualizer/access_points.go: -------------------------------------------------------------------------------- 1 | package visualizer 2 | 3 | import ( 4 | "bytes" 5 | "github.com/hkparker/Wave/models" 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func updateAccessPoints(frame models.Wireless80211Frame) { 10 | // Mgmt frame BSSID is Address3 11 | dev := Devices[frame.Address3] 12 | if !dev.AccessPoint { 13 | dev.AccessPoint = true 14 | Devices[frame.Address3] = dev 15 | } 16 | 17 | //ssid := string(frame.Elements["SSID"]) 18 | ssid := string(bytes.Trim(frame.Elements["SSID"], "\x00")) 19 | if ssid == "" { 20 | // Skip empty beacons (Closed System) 21 | return 22 | } 23 | 24 | net, ok := Networks[ssid] 25 | if !ok { 26 | net = models.Network{ 27 | SSID: ssid, 28 | } 29 | Networks[ssid] = net 30 | } 31 | 32 | associated := false 33 | for _, ap := range net.AccessPoints { 34 | if ap.MAC == dev.MAC { 35 | associated = true 36 | break 37 | } 38 | } 39 | if !associated { 40 | net.AccessPoints = append(net.AccessPoints, dev) 41 | Networks[ssid] = net 42 | visualizeNewAP(frame) 43 | } 44 | } 45 | 46 | func visualizeNewAP(frame models.Wireless80211Frame) { 47 | ssid := string(frame.Elements["SSID"]) 48 | VisualEvents <- VisualEvent{ 49 | TYPE: TYPE_UPDATE_AP, 50 | DEVICE_MAC: frame.Address3, 51 | DEVICE_ISAP: "true", 52 | SSID: ssid, 53 | } 54 | log.WithFields(log.Fields{ 55 | "at": "visualizer.visualizeNewAP", 56 | "mac": frame.Address3, 57 | "ssid": ssid, 58 | }).Debug("update device as ap") 59 | } 60 | -------------------------------------------------------------------------------- /engines/visualizer/data.go: -------------------------------------------------------------------------------- 1 | package visualizer 2 | 3 | import ( 4 | "github.com/hkparker/Wave/models" 5 | log "github.com/sirupsen/logrus" 6 | ) 7 | 8 | func updateAssociation(frame models.Wireless80211Frame) { 9 | mac1 := frame.Address1 10 | mac2 := frame.Address3 11 | 12 | if frame.Flags80211.ToDS() { 13 | if frame.Flags80211.FromDS() { 14 | //log.Warn("11") 15 | } else { 16 | //log.Warn("10") 17 | } 18 | } else { 19 | if frame.Flags80211.FromDS() { 20 | //log.Warn("01") 21 | } else { 22 | //log.Warn("00") 23 | } 24 | } 25 | 26 | if !memoized(mac1, mac2) { 27 | visualizeAssociation(mac1, mac2) 28 | } 29 | } 30 | 31 | func memoized(mac1, mac2 string) bool { 32 | if mac1 == "" || mac2 == "" { 33 | log.Warn("null mac") 34 | return true 35 | } 36 | if broadcast(mac1) || broadcast(mac2) { 37 | return true 38 | } 39 | 40 | key := deterministicKey(mac1, mac2) 41 | if _, exists := Associations[key]; exists { 42 | return true 43 | } else { 44 | Associations[key] = models.Association{ 45 | Source: mac1, 46 | Target: mac2, 47 | } 48 | } 49 | return false 50 | 51 | //if mac1 == mac2 { 52 | // log.Warn("association between identical mac " + mac1) 53 | // return true 54 | //} 55 | } 56 | 57 | func visualizeAssociation(mac1, mac2 string) { 58 | VisualEvents <- VisualEvent{ 59 | TYPE: "NewAssociation", 60 | "Key": deterministicKey(mac1, mac2), 61 | "target": mac1, 62 | "source": mac2, 63 | } 64 | log.WithFields(log.Fields{ 65 | "at": "visualizer.visualizeAssociation", 66 | "mac1": mac1, 67 | "mac2": mac2, 68 | }).Debug("associate devices") 69 | } 70 | -------------------------------------------------------------------------------- /engines/visualizer/deauth.go: -------------------------------------------------------------------------------- 1 | package visualizer 2 | 3 | import ( 4 | "github.com/hkparker/Wave/models" 5 | log "github.com/sirupsen/logrus" 6 | ) 7 | 8 | // With thanks to: 9 | // https://mrncciew.com/2014/10/11/802-11-mgmt-deauth-disassociation-frames/ 10 | 11 | func animateDeauth(frame models.Wireless80211Frame) { 12 | if frame.Address3 == frame.Address1 { 13 | log.Warn("duplicate deauth mac " + frame.Address1) 14 | return 15 | } 16 | key := deterministicKey(frame.Address1, frame.Address3) 17 | _, ok := Associations[key] 18 | if !ok { 19 | // deauth on unknown association 20 | return 21 | } 22 | delete(Associations, key) 23 | 24 | VisualEvents <- VisualEvent{ 25 | "type": "AnimateDeauth", 26 | "Target": frame.Address1, 27 | "Source": frame.Address3, 28 | } 29 | log.WithFields(log.Fields{ 30 | "at": "animateDeauth", 31 | }).Debug("animating deauth") 32 | } 33 | -------------------------------------------------------------------------------- /engines/visualizer/devices.go: -------------------------------------------------------------------------------- 1 | package visualizer 2 | 3 | import ( 4 | "github.com/hkparker/Wave/helpers" 5 | "github.com/hkparker/Wave/models" 6 | log "github.com/sirupsen/logrus" 7 | "strings" 8 | ) 9 | 10 | func updateKnownDevices(frame models.Wireless80211Frame) { 11 | if _, ok := Devices[frame.Address1]; !ok { 12 | if len(frame.Address1) == 0 { 13 | return 14 | } 15 | registerNewDevice(frame.Address1) 16 | } 17 | if _, ok := Devices[frame.Address2]; !ok { 18 | if len(frame.Address2) == 0 { 19 | return 20 | } 21 | registerNewDevice(frame.Address2) 22 | } 23 | if _, ok := Devices[frame.Address3]; !ok { 24 | if len(frame.Address3) == 0 { 25 | return 26 | } 27 | registerNewDevice(frame.Address3) 28 | } 29 | if _, ok := Devices[frame.Address4]; !ok { 30 | if len(frame.Address4) == 0 { 31 | return 32 | } 33 | registerNewDevice(frame.Address4) 34 | } 35 | } 36 | 37 | func registerNewDevice(mac string) { 38 | if len(mac) != 17 { 39 | log.WithFields(log.Fields{ 40 | "at": "visualizer.registerDevice", 41 | "mac": mac, 42 | }).Warn("malformed mac") 43 | return 44 | } 45 | if broadcast(mac) { 46 | return 47 | } 48 | vendor := "unknown" 49 | if len(mac) >= 8 { 50 | if vendor_string, ok := VendorBytes[strings.ToUpper(mac[0:8])]; ok { 51 | vendor = vendor_string 52 | } else { 53 | //log.Warn("unknown vendor for " + mac) 54 | //return 55 | } 56 | } 57 | device := models.Device{ 58 | MAC: mac, 59 | Vendor: vendor, 60 | } 61 | Devices[mac] = device 62 | visualizeNewDevice(device) 63 | } 64 | 65 | func broadcast(mac string) bool { 66 | // IPv6 multicast DHCP 67 | if mac[:5] == "33:33" { 68 | return true 69 | } 70 | // IPv4 multicast DHCP 71 | if mac[:8] == "01:00:5e" { 72 | return true 73 | } 74 | // Various uses 75 | // https://standards.ieee.org/products-services/regauth/grpmac/public.html 76 | if mac[:8] == "01:80:c2" { 77 | return true 78 | } 79 | return helpers.StringIncludedIn( 80 | []string{ 81 | "ff:ff:ff:ff:ff:ff", 82 | "00:00:00:00:00:00", 83 | }, 84 | mac, 85 | ) 86 | } 87 | 88 | func visualizeNewDevice(device models.Device) { 89 | VisualEvents <- device.VisualData() 90 | log.WithFields(log.Fields{ 91 | "at": "visualizeNewDevice", 92 | "mac": device.MAC, 93 | }).Debug("new device observed") 94 | } 95 | -------------------------------------------------------------------------------- /engines/visualizer/null_data.go: -------------------------------------------------------------------------------- 1 | package visualizer 2 | 3 | import ( 4 | "github.com/hkparker/Wave/models" 5 | ) 6 | 7 | // With thanks to: 8 | // http://www.my80211.com/home/2009/12/5/80211-null-data-frames.html 9 | 10 | func updateDataNull(frame models.Wireless80211Frame) { 11 | //DEVICE_POWERSTATE = "PowerState" 12 | //DEVICE_POWERSTATE_ON = "online" 13 | //DEVICE_POWERSTATE_OFF = "offline" 14 | } 15 | -------------------------------------------------------------------------------- /engines/visualizer/probe_requests.go: -------------------------------------------------------------------------------- 1 | package visualizer 2 | 3 | import ( 4 | "github.com/hkparker/Wave/models" 5 | log "github.com/sirupsen/logrus" 6 | ) 7 | 8 | // With thanks to: 9 | // https://mrncciew.com/2014/10/27/cwap-802-11-probe-requestresponse/ 10 | 11 | func updateProbeRequests(frame models.Wireless80211Frame) { 12 | ssid := string(frame.Elements["SSID"]) 13 | dev := Devices[frame.Address2] 14 | if len(ssid) == 0 { 15 | if !dev.Probing { 16 | dev.Probing = true 17 | Devices[frame.Address2] = dev 18 | visualizeNullProbe(frame.Address2) 19 | } 20 | } else { 21 | exists := false 22 | var network models.Network 23 | for _, net := range Networks { 24 | if net.SSID == ssid { 25 | network = net 26 | exists = true 27 | } 28 | } 29 | if !exists { 30 | network = models.Network{ 31 | SSID: ssid, 32 | } 33 | Networks[ssid] = network 34 | } 35 | 36 | associated := false 37 | for _, net := range dev.ProbedFor { 38 | if net.SSID == ssid { 39 | associated = true 40 | break 41 | } 42 | } 43 | if !associated { 44 | dev.ProbedFor = append(dev.ProbedFor, network) 45 | } 46 | 47 | visualizeProbeRequest(frame.Address2, ssid) 48 | VisualEvents <- dev.VisualData() 49 | } 50 | } 51 | 52 | func visualizeNullProbe(mac string) { 53 | VisualEvents <- VisualEvent{ 54 | TYPE: TYPE_NULL_PROBE_REQUEST, 55 | DEVICE_MAC: mac, 56 | } 57 | log.WithFields(log.Fields{ 58 | "at": "visualizer.visualizeNullProbe", 59 | DEVICE_MAC: mac, 60 | }).Debug("visualizing null probe") 61 | } 62 | 63 | func visualizeProbeRequest(mac string, ssid string) { 64 | VisualEvents <- VisualEvent{ 65 | TYPE: TYPE_PROBE_REQUEST, 66 | SSID: ssid, 67 | DEVICE_MAC: mac, 68 | } 69 | log.WithFields(log.Fields{ 70 | "at": "visualizer.visualizeProbeRequest", 71 | SSID: ssid, 72 | DEVICE_MAC: mac, 73 | }).Debug("visualizing probe request") 74 | } 75 | -------------------------------------------------------------------------------- /engines/visualizer/visualizer.go: -------------------------------------------------------------------------------- 1 | package visualizer 2 | 3 | import ( 4 | "github.com/hkparker/Wave/helpers" 5 | "github.com/hkparker/Wave/models" 6 | log "github.com/sirupsen/logrus" 7 | "sort" 8 | "strings" 9 | "sync" 10 | ) 11 | 12 | const ( 13 | DEVICE_ISAP = "IsAP" 14 | DEVICE_MAC = "MAC" 15 | DEVICE_NULLPROBE = "NullProbe" 16 | DEVICE_PROBE = "ProbedFor" 17 | DEVICE_POWERSTATE = "PowerState" 18 | DEVICE_POWERSTATE_ON = "online" 19 | DEVICE_POWERSTATE_OFF = "offline" 20 | 21 | TYPE_NULL_PROBE_REQUEST = "NullProbeRequest" 22 | TYPE_PROBE_REQUEST = "ProbeRequest" 23 | TYPE_UPDATE_AP = "UpdateAccessPoint" 24 | 25 | EVENT = "Event" 26 | SSID = "SSID" 27 | TYPE = "type" 28 | ) 29 | 30 | type VisualEvent map[string]string 31 | 32 | var VisualEvents = make(chan VisualEvent, 0) 33 | var VendorBytes = make(map[string]string) 34 | 35 | var Devices = make(map[string]models.Device) 36 | var DevicesMux sync.Mutex 37 | var Networks = make(map[string]models.Network) 38 | var NetworksMux sync.Mutex 39 | var Associations = make(map[string]models.Association) 40 | var AssociationsMux sync.Mutex 41 | 42 | func init() { 43 | loadMetadata() 44 | } 45 | 46 | func loadMetadata() { 47 | prefix_path := "engines/visualizer/metadata/nmap-mac-prefixes" 48 | vendor_data, err := helpers.Asset(prefix_path) 49 | if err != nil { 50 | log.WithFields(log.Fields{ 51 | "at": "visualizer.loadMetadata", 52 | "error": err.Error(), 53 | }).Error("unable to load vendor bytes") 54 | return 55 | } 56 | lines := strings.Split(string(vendor_data), "\n") 57 | for _, line := range lines { 58 | if len(line) == 0 || string(line[0]) == "#" { 59 | continue 60 | } 61 | raw_mac := line[0:6] 62 | name := line[7:] 63 | mac := raw_mac[0:2] + ":" + 64 | raw_mac[2:4] + ":" + 65 | raw_mac[4:6] 66 | VendorBytes[strings.ToUpper(mac)] = name 67 | } 68 | } 69 | 70 | func Insert(frame models.Wireless80211Frame) { 71 | DevicesMux.Lock() 72 | defer DevicesMux.Unlock() 73 | NetworksMux.Lock() 74 | defer NetworksMux.Unlock() 75 | AssociationsMux.Lock() 76 | defer AssociationsMux.Unlock() 77 | //updateKnownDevices(frame) 78 | 79 | if len(frame.Type) < 4 { 80 | log.WithFields(log.Fields{}).Warn("frame type too small") 81 | return 82 | } 83 | switch frame.Type[:4] { 84 | case "Mgmt": 85 | insertMgmt(frame) 86 | case "Data": 87 | insertData(frame) 88 | case "Ctrl": 89 | insertCtrl(frame) 90 | //case "Rese": 91 | default: 92 | log.WithFields(log.Fields{ 93 | "at": "visualizer.Insert", 94 | "frame.Type": frame.Type, 95 | }).Warn("unknown frame type") 96 | } 97 | } 98 | 99 | func insertMgmt(frame models.Wireless80211Frame) { 100 | switch frame.Type { 101 | case "MgmtAssociationReq": 102 | case "MgmtAssociationResp": 103 | case "MgmtReassociationReq": 104 | case "MgmtReassociationResp": 105 | case "MgmtProbeReq": 106 | updateKnownDevices(frame) 107 | updateProbeRequests(frame) 108 | case "MgmtProbeResp": 109 | case "MgmtMeasurementPilot": 110 | case "MgmtBeacon": 111 | updateKnownDevices(frame) 112 | updateAccessPoints(frame) 113 | case "MgmtATIM": 114 | case "MgmtDisassociation": 115 | updateKnownDevices(frame) 116 | animateDeauth(frame) 117 | case "MgmtAuthentication": 118 | case "MgmtDeauthentication": 119 | updateKnownDevices(frame) 120 | animateDeauth(frame) 121 | case "MgmtAction": 122 | case "MgmtActionNoAck": 123 | default: 124 | log.WithFields(log.Fields{ 125 | "at": "visualizer.insertMgmt", 126 | "type": frame.Type, 127 | }).Warn("unknown frame type") 128 | } 129 | } 130 | 131 | func insertData(frame models.Wireless80211Frame) { 132 | //updateTx() 133 | switch frame.Type { 134 | case "Data": 135 | updateKnownDevices(frame) 136 | updateAssociation(frame) 137 | case "DataCFAck": 138 | updateKnownDevices(frame) 139 | updateAssociation(frame) 140 | case "DataCFPoll": 141 | updateKnownDevices(frame) 142 | updateAssociation(frame) 143 | case "DataCFAckPoll": 144 | updateKnownDevices(frame) 145 | updateAssociation(frame) 146 | case "DataNull": 147 | updateKnownDevices(frame) 148 | updateDataNull(frame) 149 | case "DataCFAckNoData": 150 | case "DataCFPollNoData": 151 | case "DataCFAckPollNoData": 152 | case "DataQOSData": 153 | updateKnownDevices(frame) 154 | updateAssociation(frame) 155 | case "DataQOSDataCFAck": 156 | updateKnownDevices(frame) 157 | updateAssociation(frame) 158 | case "DataQOSDataCFPoll": 159 | updateKnownDevices(frame) 160 | updateAssociation(frame) 161 | case "DataQOSDataCFAckPoll": 162 | updateKnownDevices(frame) 163 | updateAssociation(frame) 164 | case "DataQOSNull": 165 | case "DataQOSCFPollNoData": 166 | case "DataQOSCFAckPollNoData": 167 | default: 168 | log.WithFields(log.Fields{ 169 | "at": "visualizer.insertData", 170 | "type": frame.Type, 171 | }).Warn("unknown frame type") 172 | } 173 | } 174 | 175 | func insertCtrl(frame models.Wireless80211Frame) { 176 | switch frame.Type { 177 | case "CtrlWrapper": 178 | case "CtrlBlockAckReq": 179 | case "CtrlBlockAck": 180 | case "CtrlPowersavePoll": 181 | case "CtrlRTS": 182 | case "CtrlCTS": 183 | case "CtrlAck": 184 | case "CtrlCFEnd": 185 | case "CtrlCFEndAck": 186 | default: 187 | log.WithFields(log.Fields{ 188 | "at": "visualizer.insertCtrl", 189 | "type": frame.Type, 190 | }).Warn("unknown frame type") 191 | } 192 | } 193 | 194 | // Given two MACs in any order, always return them 195 | // combined in sorted order 196 | func deterministicKey(mac1, mac2 string) string { 197 | set := []string{mac1, mac2} 198 | sort.Strings(set) 199 | return strings.Join(set, ":") 200 | } 201 | 202 | func CatchupEvents() []VisualEvent { 203 | catchup_events := make([]VisualEvent, 0) 204 | for _, device := range Devices { 205 | catchup_events = append(catchup_events, device.VisualData()) 206 | } 207 | for _, network := range Networks { 208 | ssid_set := network.VisualData() 209 | for _, network_event := range ssid_set { 210 | catchup_events = append(catchup_events, VisualEvent(network_event)) 211 | } 212 | } 213 | for _, association := range Associations { 214 | catchup_events = append( 215 | catchup_events, 216 | VisualEvent{ 217 | TYPE: "NewAssociation", 218 | "Key": deterministicKey(association.Source, association.Target), 219 | "target": association.Source, 220 | "source": association.Target, 221 | }, 222 | ) 223 | } 224 | catchup_events = append( 225 | catchup_events, 226 | VisualEvent{ 227 | TYPE: "CacheCleared", 228 | }, 229 | ) 230 | // add other resources, create other events 231 | return catchup_events 232 | } 233 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Wave 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | npm run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Wave", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "3d-force-graph": "^1.57.0", 12 | "axios": "^0.19.1", 13 | "bulma": "^0.7.5", 14 | "core-js": "^2.6.11", 15 | "js-file-download": "^0.4.9", 16 | "vue": "^2.6.11", 17 | "vue-router": "^3.1.5", 18 | "vuex": "^3.1.2" 19 | }, 20 | "devDependencies": { 21 | "@vue/cli-plugin-babel": "^3.12.1", 22 | "@vue/cli-plugin-eslint": "^3.12.1", 23 | "@vue/cli-service": "^4.1.2", 24 | "babel-eslint": "^10.0.3", 25 | "eslint": "^5.16.0", 26 | "eslint-plugin-vue": "^5.0.0", 27 | "vue-template-compiler": "^2.6.11" 28 | }, 29 | "eslintConfig": { 30 | "root": true, 31 | "env": { 32 | "node": true 33 | }, 34 | "extends": [ 35 | "plugin:vue/essential", 36 | "eslint:recommended" 37 | ], 38 | "rules": {}, 39 | "parserOptions": { 40 | "parser": "babel-eslint" 41 | } 42 | }, 43 | "postcss": { 44 | "plugins": { 45 | "autoprefixer": {} 46 | } 47 | }, 48 | "browserslist": [ 49 | "> 1%", 50 | "last 2 versions" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hkparker/Wave/9cdc9d29918d815d0219c7a441d2a38cc1bb5f97/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Wave 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 19 | 27 | -------------------------------------------------------------------------------- /frontend/src/Login.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 33 | 34 | 36 | -------------------------------------------------------------------------------- /frontend/src/Navbar.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 116 | 117 | 119 | -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hkparker/Wave/9cdc9d29918d815d0219c7a441d2a38cc1bb5f97/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/src/components/AccountSettings.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 117 | 118 | 132 | -------------------------------------------------------------------------------- /frontend/src/components/CollectorManager.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 197 | 198 | 212 | -------------------------------------------------------------------------------- /frontend/src/components/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /frontend/src/components/IDS.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /frontend/src/components/Rules.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /frontend/src/components/Unauthorized.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /frontend/src/components/UserManager.vue: -------------------------------------------------------------------------------- 1 | 120 | 121 | 209 | 210 | 224 | -------------------------------------------------------------------------------- /frontend/src/components/Visualization.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 145 | 146 | 178 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import './../node_modules/bulma/css/bulma.css'; 4 | import store from "./store.js" 5 | import router from "./router.js" 6 | 7 | Vue.config.productionTip = false 8 | 9 | new Vue({ 10 | router, 11 | render: h => h(App), 12 | store, 13 | beforeCreate () { 14 | this.$store.dispatch("setEnvironment") 15 | } 16 | }).$mount('#app') 17 | -------------------------------------------------------------------------------- /frontend/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Login from './Login.vue' 4 | import Dashboard from './components/Dashboard.vue' 5 | import AccountSettings from './components/AccountSettings.vue' 6 | import UserManager from './components/UserManager.vue' 7 | import CollectorManager from './components/CollectorManager.vue' 8 | import IDS from './components/IDS.vue' 9 | import Rules from './components/Rules.vue' 10 | import Visualization from './components/Visualization.vue' 11 | import Unauthorized from './components/Unauthorized.vue' 12 | import store from "./store.js" 13 | 14 | Vue.use(VueRouter) 15 | 16 | const requireLogin = (to, from, next) => { 17 | if (store.getters.loggedIn) { 18 | next() 19 | } else { 20 | next('/login') 21 | } 22 | } 23 | 24 | /* 25 | const requireLoginAdmin = (to, from, next) => { 26 | if (store.getters.loggedIn && store.getters.admin) { 27 | next() 28 | } else { 29 | next('/unauthorized') 30 | } 31 | } 32 | */ 33 | 34 | const requireLoggedOut = (to, from, next) => { 35 | if (!store.getters.loggedIn) { 36 | next() 37 | } else { 38 | next('/') 39 | } 40 | } 41 | 42 | export default new VueRouter({ 43 | mode: 'history', 44 | routes: [ 45 | { path: '/', component: Dashboard, beforeEnter: requireLogin }, 46 | { path: '/settings', component: AccountSettings, beforeEnter: requireLogin }, 47 | { path: '/users', component: UserManager, beforeEnter: requireLogin }, // require admin once works on refresh 48 | { path: '/collectors', component: CollectorManager, beforeEnter: requireLogin }, // require admin once works on refresh 49 | { path: '/login', component: Login, beforeEnter: requireLoggedOut }, 50 | { path: '/ids', component: IDS, beforeEnter: requireLogin }, 51 | { path: '/rules', component: Rules, beforeEnter: requireLogin }, 52 | { path: '/visualization', component: Visualization, beforeEnter: requireLogin }, 53 | { path: '/unauthorized', component: Unauthorized, beforeEnter: requireLogin }, 54 | ] 55 | }) 56 | -------------------------------------------------------------------------------- /frontend/src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import axios from 'axios' 4 | import router from "./router.js" 5 | 6 | Vue.use(Vuex) 7 | 8 | export default new Vuex.Store({ 9 | state: { 10 | authenticationState: "unauthenticated", 11 | loggedIn: true, 12 | version: 1, 13 | currentUser: {} 14 | }, 15 | getters: { 16 | loggedIn: state => state.loggedIn, 17 | authenticationState: state => state.authenticationState, 18 | version: state => state.version, 19 | currentUser: state => state.currentUser, 20 | admin: state => state.currentUser.admin 21 | }, 22 | actions: { 23 | authenticate ({commit}, credentials) { 24 | return axios({url: '/sessions/create', data: credentials, method: 'POST', crossdomain: true, withCredentials: true }) 25 | .then((resp) => { 26 | commit("setCurrentUser", resp.data) 27 | commit('authSuccess', credentials.username) 28 | }) 29 | .catch(err => { 30 | commit('authFailed', err) 31 | }) 32 | }, 33 | logout ({commit}) { 34 | return axios({url: '/sessions/destroy', method: 'POST', crossdomain: true, withCredentials: true }) 35 | .then(() => { 36 | commit('logout') 37 | }) 38 | }, 39 | settings ({commit}) { 40 | commit("settings") 41 | }, 42 | dashboard ({commit}) { 43 | commit("dashboard") 44 | }, 45 | userManager ({commit}) { 46 | commit("userManager") 47 | }, 48 | collectorManager ({commit}) { 49 | commit("collectorManager") 50 | }, 51 | ids ({commit}) { 52 | commit("ids") 53 | }, 54 | rules ({commit}) { 55 | commit("rules") 56 | }, 57 | visualization ({commit}) { 58 | commit("visualization") 59 | }, 60 | setEnvironment ({commit}) { 61 | axios({url: '/status', method: 'GET', crossdomain: true, withCredentials: true }) 62 | .then((resp) => { 63 | commit("setCurrentUser", resp.data) 64 | }) 65 | .catch(() => { 66 | commit('logout') 67 | }) 68 | } 69 | }, 70 | mutations: { 71 | setVersion: (state, newVersion) => { 72 | state.version = newVersion 73 | }, 74 | authRequest (state) { 75 | state.authenticationState = "loading" 76 | }, 77 | setCurrentUser: (state, user) => { 78 | state.currentUser = user 79 | }, 80 | authSuccess (state) { 81 | state.authenticationState = "success" 82 | state.loggedIn = true 83 | router.push('/') 84 | }, 85 | authFailed (state) { 86 | state.authenticationState = "failed" 87 | }, 88 | logout (state) { 89 | state.loggedIn = false 90 | state.authenticationState = "logged_out" 91 | router.push("/login") 92 | }, 93 | settings () { 94 | router.push("/settings") 95 | .catch((e) => { 96 | if (e.Name == "NavigationDuplicated") { 97 | // user clicked the page they were already on, no need to log eror 98 | } 99 | }) 100 | }, 101 | dashboard () { 102 | router.push("/") 103 | .catch((e) => { 104 | if (e.Name == "NavigationDuplicated") { 105 | // user clicked the page they were already on, no need to log eror 106 | } 107 | }) 108 | }, 109 | userManager () { 110 | router.push("/users") 111 | .catch((e) => { 112 | if (e.Name == "NavigationDuplicated") { 113 | // user clicked the page they were already on, no need to log eror 114 | } 115 | }) 116 | }, 117 | collectorManager () { 118 | router.push("/collectors") 119 | .catch((e) => { 120 | if (e.Name == "NavigationDuplicated") { 121 | // user clicked the page they were already on, no need to log eror 122 | } 123 | }) 124 | }, 125 | ids () { 126 | router.push("/ids") 127 | .catch((e) => { 128 | if (e.Name == "NavigationDuplicated") { 129 | // user clicked the page they were already on, no need to log eror 130 | } 131 | }) 132 | }, 133 | rules () { 134 | router.push("/rules") 135 | .catch((e) => { 136 | if (e.Name == "NavigationDuplicated") { 137 | // user clicked the page they were already on, no need to log eror 138 | } 139 | }) 140 | }, 141 | visualization () { 142 | router.push("/visualization") 143 | .catch((e) => { 144 | if (e.Name == "NavigationDuplicated") { 145 | // user clicked the page they were already on, no need to log eror 146 | } 147 | }) 148 | } 149 | } 150 | }) 151 | -------------------------------------------------------------------------------- /frontend/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devServer: { 3 | proxy: { 4 | '/streams/': { 5 | target: 'ws://localhost:8081', 6 | ws: true, 7 | }, 8 | '/sessions': { 9 | target: 'http://localhost:8081', 10 | }, 11 | '/users': { 12 | target: 'http://localhost:8081', 13 | }, 14 | '/status': { 15 | target: 'http://localhost:8081', 16 | }, 17 | '/collector': { 18 | target: 'http://localhost:8081', 19 | }, 20 | '/collectors': { 21 | target: 'http://localhost:8081', 22 | }, 23 | '/tls': { 24 | target: 'http://localhost:8081', 25 | }, 26 | '/favicon.ico': { 27 | target: 'http://localhost:8081' 28 | }, 29 | '/wave.svg': { 30 | target: 'http://localhost:8081' 31 | } 32 | } 33 | }, 34 | runtimeCompiler: true 35 | } 36 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hkparker/Wave 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/gin-contrib/cors v1.3.0 7 | github.com/gin-contrib/sse v0.1.0 // indirect 8 | github.com/gin-gonic/contrib v0.0.0-20190923054218-35076c1b2bea 9 | github.com/gin-gonic/gin v1.4.0 10 | github.com/golang/protobuf v1.3.2 // indirect 11 | github.com/google/gopacket v1.1.17 12 | github.com/gorilla/websocket v1.4.1 13 | github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6 14 | github.com/jinzhu/gorm v1.9.11 15 | github.com/joho/godotenv v1.3.0 16 | github.com/json-iterator/go v1.1.8 // indirect 17 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect 18 | github.com/lib/pq v1.2.0 // indirect 19 | github.com/mattn/go-isatty v0.0.10 // indirect 20 | github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d 21 | github.com/rs/cors v1.7.0 22 | github.com/satori/go.uuid v1.2.0 23 | github.com/sirupsen/logrus v1.4.2 24 | github.com/stretchr/testify v1.3.0 25 | github.com/ugorji/go v1.1.7 // indirect 26 | golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf 27 | golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c // indirect 28 | gopkg.in/sourcemap.v1 v1.0.5 // indirect 29 | gopkg.in/yaml.v2 v2.2.4 // indirect 30 | ) 31 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= 4 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 5 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= 6 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 7 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 8 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 9 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 10 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 11 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= 16 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 17 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= 18 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 19 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 20 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 21 | github.com/gin-contrib/cors v1.3.0 h1:PolezCc89peu+NgkIWt9OB01Kbzt6IP0J/JvkG6xxlg= 22 | github.com/gin-contrib/cors v1.3.0/go.mod h1:artPvLlhkF7oG06nK8v3U8TNz6IeX+w1uzCSEId5/Vc= 23 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= 24 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= 25 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 26 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 27 | github.com/gin-gonic/contrib v0.0.0-20190526021735-7fb7810ed2a0 h1:R0oj52DmXWiPxZEedx/Uxxbvs6yB0l8hLgrubGpKsq4= 28 | github.com/gin-gonic/contrib v0.0.0-20190526021735-7fb7810ed2a0/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg= 29 | github.com/gin-gonic/contrib v0.0.0-20190923054218-35076c1b2bea h1:tPQfr1S0mubDv/jvdbS1xbKOJzDgvIHi7db/MYr4EKg= 30 | github.com/gin-gonic/contrib v0.0.0-20190923054218-35076c1b2bea/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg= 31 | github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ= 32 | github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= 33 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 34 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 35 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 36 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 37 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 38 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 39 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 40 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 41 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 42 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 43 | github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= 44 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 45 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 46 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 47 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 48 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 49 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 50 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 51 | github.com/google/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY= 52 | github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= 53 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 54 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 55 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 56 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 57 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 58 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 59 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 60 | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= 61 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 62 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 63 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 64 | github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6 h1:4zOlv2my+vf98jT1nQt4bT/yKWUImevYPJ2H344CloE= 65 | github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6/go.mod h1:r/8JmuR0qjuCiEhAolkfvdZgmPiHTnJaG0UXCSeR1Zo= 66 | github.com/jinzhu/gorm v1.9.10 h1:HvrsqdhCW78xpJF67g1hMxS6eCToo9PZH4LDB8WKPac= 67 | github.com/jinzhu/gorm v1.9.10/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY= 68 | github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE= 69 | github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw= 70 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 71 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 72 | github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 73 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 74 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 75 | github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= 76 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 77 | github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= 78 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 79 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 80 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 81 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 82 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 83 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 84 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= 85 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 86 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 87 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 88 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 89 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 90 | github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= 91 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 92 | github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= 93 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 94 | github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= 95 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 96 | github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= 97 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 98 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 99 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 100 | github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= 101 | github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 102 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 103 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 104 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 105 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 106 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 107 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 108 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 109 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 110 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 111 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 112 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 113 | github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= 114 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 115 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 116 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 117 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 118 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 119 | github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= 120 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 121 | github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 122 | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 123 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 124 | github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 125 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 126 | github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4= 127 | github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= 128 | github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= 129 | github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 130 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 131 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 132 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 133 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 134 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 135 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 136 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 137 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 138 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 139 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 140 | github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= 141 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 142 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 143 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 144 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 145 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 146 | go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 147 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 148 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 149 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 150 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= 151 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 152 | golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf h1:fnPsqIDRbCSgumaMCRpoIoF2s4qxv0xSSS0BVZUE/ss= 153 | golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 154 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 155 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 156 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 157 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 158 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 159 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 160 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 161 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 162 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 163 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 164 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 165 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 166 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 167 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 168 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 169 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 170 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 171 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 172 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 173 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 174 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 175 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 176 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 177 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 178 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 179 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 180 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 181 | golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 182 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 183 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= 184 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 185 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= 186 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 187 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 188 | golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c h1:S/FtSvpNLtFBgjTqcKsRpsa6aVsI6iztaz1bQd9BJwE= 189 | golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 190 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 191 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 192 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 193 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 194 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 195 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 196 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 197 | google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 198 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 199 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 200 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 201 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 202 | google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 203 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 204 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 205 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 206 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 207 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 208 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 209 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 210 | gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= 211 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= 212 | gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= 213 | gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= 214 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 215 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 216 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 217 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 218 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 219 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 220 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 221 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 222 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 223 | -------------------------------------------------------------------------------- /helpers/crypto.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "crypto/rand" 5 | "github.com/jbenet/go-base58" 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func RandomString() (random_string string) { 10 | bytes := make([]byte, 64) 11 | _, err := rand.Read(bytes) 12 | if err != nil { 13 | log.WithFields(log.Fields{ 14 | "at": "helpers.RandomString", 15 | "error": err.Error(), 16 | }).Fatal("error generating cryptographically secure random string") 17 | } 18 | random_string = base58.Encode(bytes) 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /helpers/database.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | "os" 6 | ) 7 | 8 | var DBHostname string 9 | var DBUsername string 10 | var DBPassword string 11 | var DBAdminUsername string 12 | var DBAdminPassword string 13 | var DBName string 14 | var DBTLS string 15 | 16 | func setDatabase() { 17 | DBHostname = os.Getenv("WAVE_DB_HOSTNAME") 18 | if DBHostname == "" { 19 | log.Fatal("WAVE_DB_HOSTNAME envar must be provided") 20 | } 21 | 22 | DBUsername = os.Getenv("WAVE_DB_USERNAME") 23 | DBAdminUsername = os.Getenv("WAVE_DB_ADMIN_USERNAME") 24 | if DBUsername == "" { 25 | log.Fatal("WAVE_DB_USERNAME envar must be provided") 26 | } 27 | 28 | DBPassword = os.Getenv("WAVE_DB_PASSWORD") 29 | DBAdminPassword = os.Getenv("WAVE_DB_ADMIN_PASSWORD") 30 | if DBPassword == "" { 31 | log.Fatal("WAVE_DB_PASSWORD envar must be provided") 32 | } 33 | 34 | DBName = os.Getenv("WAVE_DB_NAME") 35 | if DBName == "" { 36 | log.Fatal("WAVE_DB_NAME envar must be provided") 37 | } 38 | 39 | DBTLS = os.Getenv("WAVE_DB_TLS") 40 | if DBTLS == "" { 41 | log.Fatal("WAVE_DB_TLS envar must be provided") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /helpers/environment.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | log "github.com/sirupsen/logrus" 6 | "os" 7 | ) 8 | 9 | var ( 10 | Production bool = false 11 | Development bool = false 12 | Testing bool = false 13 | ) 14 | 15 | func setEnvironment() { 16 | if TestingCmd() { 17 | Testing = true 18 | return 19 | } 20 | if devEnvar() { 21 | log.SetLevel(log.DebugLevel) 22 | Development = true 23 | return 24 | } 25 | Production = true 26 | log.SetFormatter(&log.JSONFormatter{}) 27 | gin.SetMode(gin.ReleaseMode) 28 | } 29 | 30 | func env() string { 31 | val := os.Getenv("WAVE_ENV") 32 | if val == "" { 33 | return "production" 34 | } 35 | return val 36 | } 37 | 38 | func devEnvar() bool { 39 | return env() == "development" 40 | } 41 | 42 | // Determine if the command was generated by `go test` 43 | func TestingCmd() bool { 44 | if len(os.Args) < 1 { 45 | return false 46 | } 47 | 48 | cmd := os.Args[0] 49 | 50 | if len(cmd) < 4 { 51 | return false 52 | } 53 | 54 | return cmd[len(cmd)-4:] == "test" 55 | } 56 | -------------------------------------------------------------------------------- /helpers/helpers.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | func Setup() { 4 | setEnvironment() 5 | setHostname() 6 | setDatabase() 7 | } 8 | 9 | func StringIncludedIn(set []string, member string) bool { 10 | for _, str := range set { 11 | if str == member { 12 | return true 13 | } 14 | } 15 | return false 16 | } 17 | 18 | func StringsExcept(set []string, member string) []string { 19 | new_set := make([]string, 0) 20 | for _, str := range set { 21 | if str != member { 22 | new_set = append(new_set, str) 23 | } 24 | } 25 | return new_set 26 | } 27 | -------------------------------------------------------------------------------- /helpers/hostname.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | "os" 6 | "strconv" 7 | ) 8 | 9 | var WaveAddress string 10 | var WaveBind string 11 | var WaveHostname string 12 | var WavePort int 13 | var CollectorPort int 14 | var TLS bool 15 | 16 | func init() { 17 | if TestingCmd() { 18 | WaveAddress = "http://127.0.0.1" 19 | WaveBind = "127.0.0.1" 20 | WaveHostname = "localhost" 21 | WavePort = 8080 22 | CollectorPort = 8888 23 | TLS = false 24 | } 25 | } 26 | 27 | func setHostname() { 28 | WaveHostname = os.Getenv("WAVE_HOSTNAME") 29 | if WaveHostname == "" { 30 | log.Fatal("WAVE_HOSTNAME envar must be provided") 31 | } 32 | WaveBind = os.Getenv("WAVE_BIND") 33 | if WaveBind == "" { 34 | log.Fatal("WAVE_BIND envar must be provided") 35 | } 36 | 37 | port_str := os.Getenv("WAVE_PORT") 38 | collector_port_str := os.Getenv("WAVE_COLLECTOR_PORT") 39 | if port_str == "" || collector_port_str == "" { 40 | log.Fatal("WAVE_PORT and WAVE_COLLECTOR_PORT envars must be provided") 41 | } else { 42 | var err error 43 | WavePort, err = strconv.Atoi(port_str) 44 | if err != nil { 45 | log.WithFields(log.Fields{ 46 | "at": "helpers.SetHostname", 47 | "value": port_str, 48 | }).Fatal("unable to assert WAVE_PORT as int") 49 | } 50 | CollectorPort, err = strconv.Atoi(collector_port_str) 51 | if err != nil { 52 | log.WithFields(log.Fields{ 53 | "at": "helpers.SetHostname", 54 | "value": port_str, 55 | }).Fatal("unable to assert WAVE_COLLECTOR_PORT as int") 56 | } 57 | } 58 | 59 | TLS = false 60 | if os.Getenv("WAVE_TLS") == "true" { 61 | TLS = true 62 | } 63 | 64 | if TLS { 65 | WaveAddress = "https://" 66 | } else { 67 | WaveAddress = "http://" 68 | } 69 | WaveAddress = WaveAddress + WaveHostname 70 | if !((!TLS && (WavePort == 80)) || (TLS && (WavePort == 443))) { 71 | WaveAddress = WaveAddress + ":" + strconv.Itoa(WavePort) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /helpers/tls.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "crypto/tls" 5 | log "github.com/sirupsen/logrus" 6 | "net" 7 | "net/http" 8 | ) 9 | 10 | func RunTLS(handler http.Handler, address string, config *tls.Config) { 11 | server := &http.Server{ 12 | Handler: handler, 13 | } 14 | tcp_listener, err := net.Listen("tcp", address) 15 | if err != nil { 16 | log.WithFields(log.Fields{ 17 | "at": "helpers.RunTLS", 18 | "address": address, 19 | "error": err.Error(), 20 | }).Fatal("unable to create tls listener") 21 | } 22 | tls_listener := tls.NewListener(tcp_listener, config) 23 | server.Serve(tls_listener) 24 | } 25 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hkparker/Wave/controllers" 6 | "github.com/hkparker/Wave/helpers" 7 | "github.com/hkparker/Wave/models" 8 | _ "github.com/joho/godotenv/autoload" 9 | ) 10 | 11 | func main() { 12 | // Setup environment 13 | helpers.Setup() 14 | models.Connect() 15 | 16 | // Start Collector server 17 | go helpers.RunTLS( 18 | controllers.NewCollector(), 19 | fmt.Sprintf( 20 | "%s:%d", 21 | helpers.WaveBind, 22 | helpers.CollectorPort, 23 | ), 24 | models.CollectorTLSConfig(), 25 | ) 26 | 27 | // Start Wave API 28 | if helpers.TLS { 29 | helpers.RunTLS( 30 | controllers.NewAPI(), 31 | fmt.Sprintf( 32 | "%s:%d", 33 | helpers.WaveBind, 34 | helpers.WavePort, 35 | ), 36 | models.APITLSConfig(), 37 | ) 38 | } else { 39 | controllers.NewAPI().Run(fmt.Sprintf( 40 | "%s:%d", 41 | helpers.WaveBind, 42 | helpers.WavePort, 43 | )) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /middleware/authentication.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/hkparker/Wave/models" 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | var public_endpoints map[string]bool 10 | var admin_endpoints map[string]bool 11 | 12 | func init() { 13 | public_endpoints = map[string]bool{ 14 | "/login": true, 15 | "/sessions/create": true, 16 | "/bundle.js": true, 17 | "/streams/visualizer": true, 18 | "/ids": true, 19 | } 20 | 21 | admin_endpoints = map[string]bool{ 22 | "/users": true, 23 | "/users/create": true, 24 | "/users/delete": true, 25 | "/users/assign-password": true, 26 | "/admin/tls": true, 27 | "/collectors": true, 28 | "/collectors/create": true, 29 | "/collectors/delete": true, 30 | } 31 | } 32 | 33 | // 34 | // Ensure that a request is authenticated 35 | // 36 | func Authentication() gin.HandlerFunc { 37 | return func(c *gin.Context) { 38 | endpoint := c.Request.URL.Path 39 | if _, public := public_endpoints[endpoint]; !public { 40 | session_cookie, err := c.Request.Cookie("wave_session") 41 | if err != nil { 42 | c.Redirect(302, "/login") 43 | c.Abort() 44 | log.WithFields(log.Fields{ 45 | "at": "middleware.Authentication", 46 | "reason": "missing wave_session cookie", 47 | "error": err.Error(), 48 | "endpoint": endpoint, 49 | }).Info("redirecting unauthenticated request") 50 | return 51 | } 52 | 53 | var user models.User 54 | if session, err := models.SessionFromID(session_cookie.Value); err == nil { 55 | if user, err = session.User(); err != nil { 56 | c.Status(401) 57 | c.Abort() 58 | log.WithFields(log.Fields{ 59 | "at": "middleware.Authentication", 60 | "reason": "could not find user for session", 61 | "endpoint": endpoint, 62 | }).Info("redirecting unauthenticated request") 63 | return 64 | } 65 | } else { 66 | c.Status(401) 67 | c.Abort() 68 | log.WithFields(log.Fields{ 69 | "at": "middleware.Authentication", 70 | "reason": "wave_session header does not exist in session record", 71 | "endpoint": endpoint, 72 | }).Info("redirecting unauthenticated request") 73 | return 74 | } 75 | 76 | c.Set("currentUser", user) 77 | 78 | if _, admin_protected := admin_endpoints[endpoint]; admin_protected && !user.Admin { 79 | c.JSON(401, gin.H{"error": "permission denied"}) 80 | c.Abort() 81 | log.WithFields(log.Fields{ 82 | "at": "middleware.Authentication", 83 | "reason": "user is not administrator", 84 | "user_id": user.ID, 85 | "endpoint": endpoint, 86 | }).Warn("blocking unauthenticated request") 87 | return 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /middleware/embedded_assets.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/hkparker/Wave/helpers" 6 | "path/filepath" 7 | ) 8 | 9 | func EmbeddedAssets() gin.HandlerFunc { 10 | return func(c *gin.Context) { 11 | path := c.Request.URL.Path 12 | switch path { 13 | case "/": 14 | RenderWebpack(c) 15 | default: 16 | data, err := helpers.Asset("static" + path) 17 | if err == nil { 18 | c.Writer.Header().Set("Content-Type", contentType(path)) 19 | c.String(200, string(data)) 20 | c.Abort() 21 | } 22 | } 23 | } 24 | } 25 | 26 | func RenderWebpack(c *gin.Context) { 27 | c.Writer.Header().Set("Content-Type", "text/html") 28 | c.String(200, 29 | ` 30 | 31 | 32 | Wave 33 | 34 | 35 |
36 | 37 | 38 | 39 | `, 40 | ) 41 | c.Abort() 42 | } 43 | 44 | func contentType(file string) string { 45 | extension := filepath.Ext(file) 46 | switch extension { 47 | case ".js": 48 | return "application/javascript" 49 | case ".svg": 50 | return "image/svg+xml" 51 | case ".ico": 52 | return "image/x-icon" 53 | case ".woff": 54 | return "application/font-woff" 55 | case ".woff2": 56 | return "application/font-woff" 57 | case ".ttf": 58 | return "application/font-sfnt" 59 | case ".eot": 60 | return "application/vnd.ms-fontobject" 61 | } 62 | return "application/octet-stream" 63 | } 64 | -------------------------------------------------------------------------------- /models/alert.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | ) 6 | 7 | type Alert struct { 8 | gorm.Model 9 | Title string 10 | Rule string 11 | Severity string 12 | } 13 | 14 | func (alert *Alert) Save() error { 15 | return Orm.Save(&alert).Error 16 | } 17 | 18 | func (alert *Alert) Delete() error { 19 | return Orm.Delete(&alert).Error 20 | } 21 | -------------------------------------------------------------------------------- /models/association.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // 4 | // An Assocation contains all the data about 5 | // interactions between two MAC addresses 6 | // 7 | type Association struct { 8 | Source string 9 | Target string 10 | DataToSource int64 11 | DataToTarget int64 12 | SourceTransmitting bool 13 | TargetTransmitting bool 14 | Direct bool 15 | } 16 | -------------------------------------------------------------------------------- /models/collector.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/tls" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "encoding/pem" 11 | "github.com/jinzhu/gorm" 12 | log "github.com/sirupsen/logrus" 13 | "time" 14 | ) 15 | 16 | type Collector struct { 17 | gorm.Model 18 | Name string `sql:"not null;unique"` 19 | CaCert string 20 | PrivateKey string 21 | } 22 | 23 | func CollectorTLSConfig() *tls.Config { 24 | // Validate the client was signed by the Wave API certificate 25 | cert_pool := x509.NewCertPool() 26 | cert, _ := APITLSData() 27 | cert_pool.AppendCertsFromPEM(cert) 28 | 29 | // Create client validating TLS config and return 30 | config := &tls.Config{ 31 | Certificates: APITLSConfig().Certificates, 32 | ClientCAs: cert_pool, 33 | ClientAuth: tls.RequireAndVerifyClientCert, 34 | } 35 | config.BuildNameToCertificate() 36 | return config 37 | } 38 | 39 | func Collectors() (collectors []Collector, err error) { 40 | err = Orm.Find(&collectors).Error 41 | return 42 | } 43 | 44 | func CreateCollector(name string) (collector Collector, err error) { 45 | cert_data, key_data, err := newCollectorKeys() 46 | if err != nil { 47 | log.WithFields(log.Fields{ 48 | "at": "models.CreateCollector", 49 | "error": err.Error(), 50 | }).Error("failed to create collector") 51 | return 52 | } 53 | collector = Collector{ 54 | Name: name, 55 | CaCert: string(cert_data), 56 | PrivateKey: string(key_data), 57 | } 58 | err = Orm.Save(&collector).Error 59 | return 60 | } 61 | 62 | func newCollectorKeys() (cert_data []byte, key_data []byte, err error) { 63 | api_cert := APITLSCertificate() 64 | ca, err := x509.ParseCertificate(api_cert.Certificate[0]) 65 | if err != nil { 66 | log.WithFields(log.Fields{ 67 | "at": "models.newColletorKeys", 68 | "error": err.Error(), 69 | }).Error("error parsing API TLS certificate for new collector") 70 | return 71 | } 72 | ca_key := api_cert.PrivateKey 73 | 74 | collector_cert := &x509.Certificate{ 75 | SerialNumber: randomSerial(), 76 | Subject: pkix.Name{ 77 | Organization: []string{"Wave"}, 78 | OrganizationalUnit: []string{"Wave"}, 79 | }, 80 | NotBefore: time.Now(), 81 | NotAfter: time.Now().AddDate(6, 0, 0), 82 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 83 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 84 | } 85 | collector_priv, err := rsa.GenerateKey(rand.Reader, 2048) 86 | if err != nil { 87 | log.WithFields(log.Fields{ 88 | "at": "models.newColletorKeys", 89 | "error": err.Error(), 90 | }).Error("error generating private key for new collector") 91 | return 92 | } 93 | collector_pub := &collector_priv.PublicKey 94 | collector_cert_data, err := x509.CreateCertificate(rand.Reader, collector_cert, ca, collector_pub, ca_key) 95 | if err != nil { 96 | log.WithFields(log.Fields{ 97 | "at": "models.newColletorKeys", 98 | "error": err.Error(), 99 | }).Error("error creating collector certificate") 100 | return 101 | } 102 | 103 | // Create PEM encoding of certificate 104 | var cert_buffer bytes.Buffer 105 | err = pem.Encode(&cert_buffer, &pem.Block{Type: "CERTIFICATE", Bytes: collector_cert_data}) 106 | if err != nil { 107 | log.WithFields(log.Fields{ 108 | "at": "models.newColletorKeys", 109 | "error": err.Error(), 110 | }).Error("could not PEM encode collector certificate data") 111 | return 112 | } 113 | cert_data = cert_buffer.Bytes() 114 | 115 | // Create PEM encoding of key 116 | var key_buffer bytes.Buffer 117 | err = pem.Encode(&key_buffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(collector_priv)}) 118 | if err != nil { 119 | log.WithFields(log.Fields{ 120 | "at": "models.newColletorKeys", 121 | "error": err.Error(), 122 | }).Error("could not PEM encode collector key data") 123 | return 124 | } 125 | key_data = key_buffer.Bytes() 126 | 127 | return 128 | } 129 | 130 | func (collector *Collector) Delete() error { 131 | return Orm.Delete(&collector).Error 132 | } 133 | 134 | func CollectorByName(name string) (collector Collector, err error) { 135 | err = Orm.First(&collector, "Name = ?", name).Error 136 | return 137 | } 138 | -------------------------------------------------------------------------------- /models/collector_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "bytes" 5 | "crypto/x509" 6 | "encoding/pem" 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TextCollectorTLSConfigCreatesCorrectConfig(t *testing.T) { 12 | assert := assert.New(t) 13 | 14 | collector1, err := CreateCollector("thing1") 15 | assert.Nil(err) 16 | collector2, err := CreateCollector("thing2") 17 | assert.Nil(err) 18 | 19 | config := CollectorTLSConfig() 20 | if assert.Equal(2, len(config.Certificates)) { 21 | assert.NotEqual(config.Certificates[0], config.Certificates[1]) 22 | } 23 | 24 | block1, _ := pem.Decode([]byte(collector1.CaCert)) 25 | client1, err := x509.ParseCertificate(block1.Bytes) 26 | assert.Nil(err) 27 | 28 | block2, _ := pem.Decode([]byte(collector2.CaCert)) 29 | client2, err := x509.ParseCertificate(block2.Bytes) 30 | assert.Nil(err) 31 | 32 | for _, cert := range config.Certificates { 33 | assert.Equal( 34 | true, 35 | bytes.Equal( 36 | cert.Certificate[0], 37 | client1.Raw, 38 | ) || bytes.Equal( 39 | cert.Certificate[0], 40 | client2.Raw, 41 | ), 42 | ) 43 | } 44 | } 45 | 46 | func TestNewCollectorKeysAreSignedByAPIKey(t *testing.T) { 47 | assert := assert.New(t) 48 | 49 | collector_cert_data, _, err := newCollectorKeys() 50 | assert.Nil(err) 51 | block, _ := pem.Decode(collector_cert_data) 52 | ca, err := x509.ParseCertificate(APITLSCertificate().Certificate[0]) 53 | assert.Nil(err) 54 | client, err := x509.ParseCertificate(block.Bytes) 55 | assert.Nil(err) 56 | 57 | err = client.CheckSignatureFrom(ca) 58 | assert.Nil(err) 59 | } 60 | -------------------------------------------------------------------------------- /models/database.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hkparker/Wave/helpers" 6 | "github.com/jinzhu/gorm" 7 | _ "github.com/jinzhu/gorm/dialects/postgres" 8 | _ "github.com/jinzhu/gorm/dialects/sqlite" 9 | log "github.com/sirupsen/logrus" 10 | ) 11 | 12 | var Orm *gorm.DB 13 | 14 | func Connect() { 15 | if helpers.DBAdminUsername != "" { 16 | db_check_args := fmt.Sprintf( 17 | "user=%s password=%s sslmode=%s", 18 | helpers.DBAdminUsername, 19 | helpers.DBAdminPassword, 20 | helpers.DBTLS, 21 | ) 22 | var err error 23 | check, err := gorm.Open("postgres", db_check_args) 24 | if err != nil { 25 | log.WithFields(log.Fields{ 26 | "at": "models.Connect", 27 | "user": helpers.DBAdminUsername, 28 | "ssl": helpers.DBTLS, 29 | "error": err.Error(), 30 | }).Fatal("unable to connect to database server") 31 | } 32 | if check.Exec(fmt.Sprintf("SELECT 1 FROM pg_database WHERE datname = '%s'", helpers.DBName)).RowsAffected != 1 { 33 | check.Exec(fmt.Sprintf("CREATE DATABASE %s", helpers.DBName)) 34 | log.WithFields(log.Fields{ 35 | "at": "models.Connect", 36 | "db_name": helpers.DBName, 37 | }).Info("created missing database") 38 | } 39 | } 40 | 41 | db_args := fmt.Sprintf( 42 | "user=%s password=%s sslmode=%s dbname=%s", 43 | helpers.DBUsername, 44 | helpers.DBPassword, 45 | helpers.DBTLS, 46 | helpers.DBName, 47 | ) 48 | var err error 49 | Orm, err = gorm.Open("postgres", db_args) 50 | if err != nil { 51 | log.WithFields(log.Fields{ 52 | "at": "models.Connect", 53 | "user": helpers.DBUsername, 54 | "ssl": helpers.DBTLS, 55 | "dbname": helpers.DBName, 56 | "error": err.Error(), 57 | }).Fatal("unable to connect to database server") 58 | } 59 | Setup() 60 | } 61 | -------------------------------------------------------------------------------- /models/device.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Device struct { 4 | MAC string 5 | Vendor string 6 | AccessPoint bool 7 | Probing bool 8 | ProbedFor []Network 9 | Online bool 10 | } 11 | 12 | func (device *Device) VisualData() map[string]string { 13 | is_ap := "false" 14 | if device.AccessPoint { 15 | is_ap = "true" 16 | } 17 | probing := "false" 18 | if device.Probing { 19 | probing = "true" 20 | } 21 | probed_for := "" 22 | for i, net := range device.ProbedFor { 23 | probed_for += net.SSID 24 | if i < len(device.ProbedFor)-1 { 25 | probed_for += "," 26 | } 27 | } 28 | return map[string]string{ 29 | "type": "NewDevice", 30 | "MAC": device.MAC, 31 | "Vendor": device.Vendor, 32 | "IsAP": is_ap, 33 | "Probing": probing, 34 | "ProbedFor": probed_for, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/hkparker/Wave/helpers" 5 | "github.com/jinzhu/gorm" 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func init() { 10 | if helpers.TestingCmd() && Orm == nil { 11 | var err error 12 | Orm, err = gorm.Open("sqlite3", ":memory:") 13 | if err != nil { 14 | log.WithFields(log.Fields{ 15 | "at": "models.init", 16 | "error": err.Error(), 17 | }).Fatal("unable to connect to testing database server") 18 | } 19 | } 20 | if Orm != nil { 21 | createTables() 22 | } 23 | } 24 | 25 | func Setup() { 26 | createTables() 27 | createAdmin() 28 | } 29 | 30 | func createTables() { 31 | if !Orm.HasTable(Alert{}) { 32 | Orm.CreateTable(Alert{}) 33 | log.WithFields(log.Fields{ 34 | "at": "models.createTables", 35 | }).Info("creating missing alert table") 36 | } 37 | 38 | if !Orm.HasTable(Collector{}) { 39 | Orm.CreateTable(Collector{}) 40 | log.WithFields(log.Fields{ 41 | "at": "models.createTables", 42 | }).Info("creating missing collector table") 43 | } 44 | 45 | if !Orm.HasTable(Session{}) { 46 | Orm.CreateTable(Session{}) 47 | log.WithFields(log.Fields{ 48 | "at": "models.createTables", 49 | }).Info("creating missing session table") 50 | } 51 | 52 | if !Orm.HasTable(TLS{}) { 53 | Orm.CreateTable(TLS{}) 54 | log.WithFields(log.Fields{ 55 | "at": "models.createTables", 56 | }).Info("creating missing tls configuration table") 57 | } 58 | 59 | if !Orm.HasTable(User{}) { 60 | Orm.CreateTable(User{}) 61 | log.WithFields(log.Fields{ 62 | "at": "models.createTables", 63 | }).Info("creating missing user table") 64 | } 65 | } 66 | 67 | func createAdmin() { 68 | var admins []User 69 | if err := Orm.Where("Admin = ?", true).Find(&admins).Error; err == nil { 70 | if len(admins) == 0 { 71 | var user User 72 | err = Orm.First(&user, "Username = ?", "root").Error 73 | if err == nil { 74 | Orm.Unscoped().Delete(&user) 75 | } 76 | admin := User{ 77 | Username: "root", 78 | Admin: true, 79 | } 80 | password := helpers.RandomString() 81 | err = admin.SetPassword(password) 82 | if err != nil { 83 | log.WithFields(log.Fields{ 84 | "at": "models.createAdmin", 85 | "err": err.Error(), 86 | }).Error("unable to create admin") 87 | return 88 | } 89 | err = admin.Save() 90 | if err != nil { 91 | log.WithFields(log.Fields{ 92 | "at": "models.createAdmin", 93 | "err": err.Error(), 94 | }).Error("unable to create admin") 95 | return 96 | } 97 | log.WithFields(log.Fields{ 98 | "at": "models.createAdmin", 99 | "username": "root", 100 | "password": password, 101 | }).Info("created_default_admin") 102 | } 103 | } else { 104 | log.WithFields(log.Fields{ 105 | "at": "models.createAdmin", 106 | "err": err.Error(), 107 | }).Error("error retrieving admins") 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /models/network.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Network struct { 4 | SSID string 5 | Clients []Device 6 | AccessPoints []Device 7 | } 8 | 9 | func (network *Network) VisualData() []map[string]string { 10 | set := make([]map[string]string, 0) 11 | for _, ap := range network.AccessPoints { 12 | set = append(set, map[string]string{ 13 | "type": "UpdateAccessPoint", 14 | "MAC": ap.MAC, 15 | "IsAP": "true", 16 | "SSID": network.SSID, 17 | }) 18 | } 19 | // add the client associations 20 | return set 21 | } 22 | -------------------------------------------------------------------------------- /models/session.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/hkparker/Wave/helpers" 5 | "github.com/jinzhu/gorm" 6 | log "github.com/sirupsen/logrus" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | type Session struct { 12 | gorm.Model 13 | UserID uint 14 | Cookie string 15 | OriginallyCreated time.Time 16 | LastUsed time.Time 17 | } 18 | 19 | func (session Session) HTTPCookie() http.Cookie { 20 | cookie := http.Cookie{ 21 | Name: "wave_session", 22 | Value: session.Cookie, 23 | Path: "/", 24 | Domain: helpers.WaveHostname, 25 | MaxAge: int(time.Now().AddDate(1, 0, 1).Unix()), 26 | Secure: helpers.TLS, 27 | HttpOnly: true, 28 | } 29 | 30 | return cookie 31 | } 32 | 33 | func (session Session) User() (user User, err error) { 34 | db_err := Orm.Model(&session).Related(&user) 35 | err = db_err.Error 36 | if err != nil { 37 | log.WithFields(log.Fields{ 38 | "at": "(Session) models.User", 39 | "error": err.Error(), 40 | }).Warn("error finding related user for session") 41 | } 42 | return 43 | } 44 | 45 | func (session *Session) Save() error { 46 | return Orm.Save(&session).Error 47 | } 48 | 49 | func (session *Session) Reload() error { 50 | return Orm.First(&session, "Cookie = ?", session.Cookie).Error 51 | } 52 | 53 | func (session *Session) Delete() error { 54 | return Orm.Delete(&session).Error 55 | } 56 | 57 | func SessionFromID(id string) (session Session, err error) { 58 | db_err := Orm.Where("Cookie = ?", id).First(&session) 59 | err = db_err.Error 60 | if err != nil { 61 | log.WithFields(log.Fields{ 62 | "at": "models.SessionFromID", 63 | "error": err.Error(), 64 | }).Warn("error looking up session") 65 | } else { 66 | session.LastUsed = time.Now() 67 | err = session.Save() 68 | } 69 | return 70 | } 71 | -------------------------------------------------------------------------------- /models/session_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestSessionFromIDLoadsValidSession(t *testing.T) { 9 | assert := assert.New(t) 10 | 11 | user := CreateTestUser([]string{}) 12 | cookie, err := user.NewSession() 13 | assert.Nil(err) 14 | 15 | session, err := SessionFromID(cookie) 16 | assert.Nil(err) 17 | 18 | var related User 19 | db_err := Orm.Model(&session).Related(&related) 20 | assert.Nil(db_err.Error) 21 | 22 | assert.Equal(related.ID, user.ID) 23 | } 24 | 25 | func TestSessionFromIDDoesntLoadInvalidSession(t *testing.T) { 26 | assert := assert.New(t) 27 | 28 | session, err := SessionFromID("foopoo") 29 | assert.NotNil(err) 30 | assert.Equal("", session.Cookie) 31 | } 32 | 33 | func TestUserReturnsUserWithSession(t *testing.T) { 34 | assert := assert.New(t) 35 | 36 | user := CreateTestUser([]string{}) 37 | cookie, err := user.NewSession() 38 | assert.Nil(err) 39 | 40 | session, err := SessionFromID(cookie) 41 | assert.Nil(err) 42 | 43 | active, err := session.User() 44 | assert.Nil(err) 45 | 46 | assert.Equal(active.ID, user.ID) 47 | } 48 | -------------------------------------------------------------------------------- /models/tls.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/tls" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "encoding/pem" 11 | "errors" 12 | "github.com/hkparker/Wave/helpers" 13 | "github.com/jinzhu/gorm" 14 | log "github.com/sirupsen/logrus" 15 | "math/big" 16 | "net" 17 | "time" 18 | ) 19 | 20 | type TLS struct { 21 | gorm.Model 22 | CaCert string 23 | PrivateKey string 24 | } 25 | 26 | func APITLSConfig() (config *tls.Config) { 27 | config = &tls.Config{ 28 | Certificates: []tls.Certificate{APITLSCertificate()}, 29 | } 30 | return 31 | } 32 | 33 | func APITLSCertificate() (pair tls.Certificate) { 34 | ca_cert, private_key := APITLSData() 35 | var err error 36 | pair, err = tls.X509KeyPair( 37 | ca_cert, 38 | private_key, 39 | ) 40 | if err != nil { 41 | log.WithFields(log.Fields{ 42 | "at": "models.APITLSCertificate", 43 | "error": err.Error(), 44 | }).Fatal("error reading TLS data from database") 45 | } 46 | return 47 | } 48 | 49 | func APITLSData() ([]byte, []byte) { 50 | createTLSIfMissing() 51 | var model TLS 52 | err := Orm.First(&model).Error 53 | if err != nil { 54 | log.WithFields(log.Fields{ 55 | "at": "models.APITLSData", 56 | "error": err.Error(), 57 | }).Fatal("error loading first TLS record for data") 58 | } 59 | return []byte(model.CaCert), []byte(model.PrivateKey) 60 | } 61 | 62 | func SetTLS(request map[string]string) (err error) { 63 | createTLSIfMissing() 64 | var collectors []Collector 65 | var collector_count int 66 | err = Orm.Find(&collectors).Count(&collector_count).Error 67 | if err != nil { 68 | log.WithFields(log.Fields{ 69 | "at": "models.SetTLS", 70 | "error": err.Error(), 71 | }).Error("unable to load collector count for setting tls") 72 | return 73 | } 74 | if collector_count != 0 { 75 | err_str := "cannot set TLS data until all collectors are deleted" 76 | log.WithFields(log.Fields{ 77 | "at": "models.SetTLS", 78 | }).Error(err_str) 79 | err = errors.New(err_str) 80 | return 81 | } 82 | var config TLS 83 | err = Orm.First(&config).Error 84 | if err != nil { 85 | log.WithFields(log.Fields{ 86 | "at": "models.SetTLS", 87 | "error": err.Error(), 88 | }).Error("error loading TLS config to set") 89 | return 90 | } 91 | 92 | if _, ok := request["ca_cert"]; !ok { 93 | err_str := "missing ca_cert key" 94 | log.WithFields(log.Fields{ 95 | "at": "models.SetTLS", 96 | }).Error(err_str) 97 | err = errors.New(err_str) 98 | return 99 | } 100 | 101 | if _, ok := request["private_key"]; !ok { 102 | err_str := "missing private_key key" 103 | log.WithFields(log.Fields{ 104 | "at": "models.SetTLS", 105 | }).Error(err_str) 106 | err = errors.New(err_str) 107 | return 108 | } 109 | 110 | config.CaCert = request["ca_cert"] 111 | config.PrivateKey = request["private_key"] 112 | err = Orm.Save(&config).Error 113 | if err != nil { 114 | log.WithFields(log.Fields{ 115 | "at": "models.SetTLS", 116 | "error": err.Error(), 117 | }).Error("error saving new TLS data") 118 | } 119 | return 120 | } 121 | 122 | // 123 | // Used to generate a new self-signed certificate if there is no 124 | // certificate in the database when loading certificate data. 125 | // 126 | func createTLSIfMissing() (err error) { 127 | var count int 128 | var tls []TLS 129 | err = Orm.Find(&tls).Count(&count).Error 130 | if err != nil { 131 | log.WithFields(log.Fields{ 132 | "at": "models.createTLSIfMissing", 133 | "error": err.Error(), 134 | }).Fatal("error loading tls count from database") 135 | } 136 | if count == 0 { 137 | cert_data, key_data := selfSignedCert() 138 | new_config := TLS{ 139 | CaCert: string(cert_data), 140 | PrivateKey: string(key_data), 141 | } 142 | err = Orm.Save(&new_config).Error 143 | if err != nil { 144 | log.WithFields(log.Fields{ 145 | "at": "models.createTLSIfMissing", 146 | "error": err.Error(), 147 | }).Fatal("error saving new self-signed certificate") 148 | } 149 | } 150 | return 151 | } 152 | 153 | func randomSerial() *big.Int { 154 | serial_number_limit := new(big.Int).Lsh(big.NewInt(1), 128) 155 | serial_number, err := rand.Int(rand.Reader, serial_number_limit) 156 | if err != nil { 157 | log.WithFields(log.Fields{ 158 | "at": "models.randomSerial", 159 | "error": err.Error(), 160 | }).Fatal("failed to generate serial number for self signed certificate") 161 | } 162 | return serial_number 163 | } 164 | 165 | // Generate a new self-signed certificate to be used if the --tls 166 | // flag is set but no TLS certificate and key are stored in the 167 | func selfSignedCert() (cert_data []byte, key_data []byte) { 168 | // Self signed certificate for provided hostname 169 | ca := &x509.Certificate{ 170 | SerialNumber: randomSerial(), 171 | Subject: pkix.Name{ 172 | Organization: []string{"Wave"}, 173 | CommonName: helpers.WaveHostname, 174 | }, 175 | IPAddresses: []net.IP{net.ParseIP(helpers.WaveBind)}, 176 | NotBefore: time.Now(), 177 | NotAfter: time.Now().AddDate(6, 0, 0), 178 | BasicConstraintsValid: true, 179 | IsCA: true, 180 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 181 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 182 | } 183 | 184 | // Generate key 185 | priv, err := rsa.GenerateKey(rand.Reader, 2048) 186 | if err != nil { 187 | log.WithFields(log.Fields{ 188 | "at": "models.selfSignedCert", 189 | "error": err.Error(), 190 | }).Fatal("failed to generate private key for self signed certificate") 191 | } 192 | pub := &priv.PublicKey 193 | 194 | // Create Certificate 195 | cert_der, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv) 196 | if err != nil { 197 | log.WithFields(log.Fields{ 198 | "at": "models.selfSignedCert", 199 | "error": err.Error(), 200 | }).Fatal("failed to create self signed certificate") 201 | } 202 | 203 | // Create PEM encoding of certificate 204 | var cert_buffer bytes.Buffer 205 | err = pem.Encode(&cert_buffer, &pem.Block{Type: "CERTIFICATE", Bytes: cert_der}) 206 | if err != nil { 207 | log.WithFields(log.Fields{ 208 | "at": "models.selfSignedCert", 209 | "error": err.Error(), 210 | }).Fatal("could not PEM encode certificate data") 211 | } 212 | cert_data = cert_buffer.Bytes() 213 | 214 | // Create PEM encoding of key 215 | var key_buffer bytes.Buffer 216 | err = pem.Encode(&key_buffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 217 | if err != nil { 218 | log.WithFields(log.Fields{ 219 | "at": "models.selfSignedCert", 220 | "error": err.Error(), 221 | }).Fatal("could not PEM encode key data") 222 | } 223 | key_data = key_buffer.Bytes() 224 | 225 | return 226 | } 227 | -------------------------------------------------------------------------------- /models/tls_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "crypto/tls" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestDefaultTLSCertificateCanBeParsed(t *testing.T) { 10 | assert := assert.New(t) 11 | 12 | ca_data, key_data := selfSignedCert() 13 | _, err := tls.X509KeyPair(ca_data, key_data) 14 | assert.Nil(err) 15 | } 16 | 17 | func TestSetTLSSetsTLS(t *testing.T) { 18 | assert := assert.New(t) 19 | 20 | original_cert, original_key := APITLSData() 21 | new_cert, new_key := selfSignedCert() 22 | assert.NotEqual(original_cert, new_cert) 23 | assert.NotEqual(original_key, new_key) 24 | 25 | err := SetTLS(map[string]string{ 26 | "ca_cert": string(new_cert), 27 | "private_key": string(new_key), 28 | }) 29 | assert.Nil(err) 30 | 31 | current_cert, current_key := APITLSData() 32 | assert.Equal(current_cert, new_cert) 33 | assert.Equal(current_key, new_key) 34 | } 35 | -------------------------------------------------------------------------------- /models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/hkparker/Wave/helpers" 5 | "github.com/jinzhu/gorm" 6 | log "github.com/sirupsen/logrus" 7 | "golang.org/x/crypto/bcrypt" 8 | "time" 9 | ) 10 | 11 | type User struct { 12 | gorm.Model 13 | Name string 14 | Password []byte 15 | Username string `sql:"not null;unique"` 16 | Admin bool 17 | Sessions []Session 18 | } 19 | 20 | func CreateUser(username, password string, admin bool) (err error) { 21 | user := User{ 22 | Username: username, 23 | Admin: admin, 24 | } 25 | db_err := Orm.Create(&user).Error 26 | if db_err != nil { 27 | err = db_err 28 | log.WithFields(log.Fields{ 29 | "at": "models.CreateUser", 30 | "UserID": user.ID, 31 | "error": err, 32 | }).Warn("error_saving_user") 33 | } else { 34 | db_err = user.Save() 35 | if db_err != nil { 36 | log.WithFields(log.Fields{ 37 | "at": "models.CreateUser", 38 | "UserID": user.ID, 39 | }).Warn("error_saving_user") 40 | err = db_err 41 | return 42 | } else { 43 | log.WithFields(log.Fields{ 44 | "at": "models.CreateUser", 45 | "UserID": user.ID, 46 | "username": user.Username, 47 | }).Info("user_created") 48 | } 49 | } 50 | 51 | err = user.SetPassword(password) 52 | if err != nil { 53 | log.WithFields(log.Fields{ 54 | "at": "models.CreateUser", 55 | "UserID": user.ID, 56 | "error": err.Error(), 57 | }).Warn("error_setting_new_user_password") 58 | } 59 | return 60 | } 61 | 62 | func (user *User) SetPassword(password string) (err error) { 63 | pw_data, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 64 | if err != nil { 65 | log.WithFields(log.Fields{ 66 | "at": "models.SetPassword", 67 | "UserID": user.ID, 68 | "error": err, 69 | }).Warn("bcrypt_error") 70 | return 71 | } 72 | user.Password = pw_data 73 | err = user.Save() 74 | if err != nil { 75 | log.WithFields(log.Fields{ 76 | "at": "models.SetPassword", 77 | "UserID": user.ID, 78 | "error": err.Error(), 79 | }).Error("error_setting_password") 80 | } else { 81 | log.WithFields(log.Fields{ 82 | "at": "models.SetPassword", 83 | "UserID": user.ID, 84 | }).Info("user_password_set") 85 | } 86 | return 87 | } 88 | 89 | func (user User) ValidAuthentication(password string) (valid bool) { 90 | valid = false 91 | 92 | err := bcrypt.CompareHashAndPassword(user.Password, []byte(password)) 93 | if err != nil { 94 | return 95 | } 96 | 97 | valid = true 98 | return 99 | } 100 | 101 | func (user *User) NewSession() (wave_session string, err error) { 102 | now := time.Now() 103 | wave_session = helpers.RandomString() 104 | session := Session{ 105 | UserID: user.ID, 106 | OriginallyCreated: now, 107 | LastUsed: now, 108 | Cookie: wave_session, 109 | } 110 | user.Sessions = append(user.Sessions, session) 111 | err = user.Save() 112 | if err != nil { 113 | log.WithFields(log.Fields{ 114 | "at": "models.NewSession", 115 | "UserID": user.ID, 116 | "error": err.Error(), 117 | }).Error("error_creating_sessions") 118 | } else { 119 | log.WithFields(log.Fields{ 120 | "at": "models.NewSession", 121 | "UserID": user.ID, 122 | }).Info("session_created") 123 | } 124 | return 125 | } 126 | 127 | func (user *User) SessionCount() int { 128 | return Orm.Model(&user).Association("Sessions").Count() 129 | } 130 | 131 | func (user *User) DestroyAllOtherSessions(session_cookie string) { 132 | var new_sessions []Session 133 | var sessions []Session 134 | Orm.Model(&user).Related(&sessions) 135 | for _, session := range sessions { 136 | if session.Cookie != session_cookie { 137 | Orm.Model(&user).Association("Sessions").Delete(&session) 138 | Orm.Unscoped().Delete(&session) 139 | } else { 140 | new_sessions = append(new_sessions, session) 141 | } 142 | } 143 | user.Sessions = new_sessions 144 | user.Save() 145 | } 146 | 147 | func (user *User) DestroyAllSessions() { 148 | var sessions []Session 149 | Orm.Model(&user).Related(&sessions) 150 | for _, session := range sessions { 151 | Orm.Model(&user).Association("Sessions").Delete(&session) 152 | Orm.Unscoped().Delete(&session) 153 | } 154 | user.Sessions = []Session{} 155 | user.Save() 156 | } 157 | 158 | func (user User) OnlyAdmin() (only_admin bool, err error) { 159 | only_admin = false 160 | if !user.Admin { 161 | return 162 | } 163 | 164 | var admins []User 165 | err = Orm.Where("Admin = ?", true).Find(&admins).Error 166 | if err != nil { 167 | return 168 | } 169 | 170 | if len(admins) == 1 { 171 | only_admin = true 172 | } 173 | return 174 | } 175 | 176 | func (user *User) Reload() error { 177 | return Orm.Unscoped().First(&user, "Username = ?", user.Username).Error 178 | } 179 | 180 | func (user *User) Save() error { 181 | return Orm.Save(&user).Error 182 | } 183 | 184 | func (user *User) Delete() error { 185 | return Orm.Delete(&user).Error 186 | } 187 | 188 | func UserByUsername(username string) (user User, err error) { 189 | err = Orm.First(&user, "Username = ?", username).Error 190 | return 191 | } 192 | 193 | func UserFromSessionCookie(session_cookie string) (user User, err error) { 194 | session, err := SessionFromID(session_cookie) 195 | if err != nil { 196 | log.WithFields(log.Fields{ 197 | "at": "models.UserFromSessionCookie", 198 | "error": err, 199 | }).Error("session_missing") 200 | return 201 | } 202 | user, err = session.User() 203 | return 204 | } 205 | 206 | func Users() (users []User, err error) { 207 | err = Orm.Find(&users).Error 208 | return 209 | } 210 | 211 | // Used for tests 212 | func DropUsers() { 213 | Orm.Delete(User{}) 214 | } 215 | -------------------------------------------------------------------------------- /models/user_factory.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/hkparker/Wave/helpers" 5 | ) 6 | 7 | func CreateTestUser(traits []string) (user User) { 8 | username := helpers.RandomString() 9 | user = User{ 10 | Name: "Turd Ferguson", 11 | Username: username, 12 | } 13 | user.SetPassword(helpers.RandomString()) 14 | 15 | for _, trait := range traits { 16 | switch trait { 17 | case "admin": 18 | user.Name = "Wifi Jackson" 19 | user.Admin = true 20 | } 21 | } 22 | 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /models/user_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/hkparker/Wave/helpers" 5 | "github.com/stretchr/testify/assert" 6 | "golang.org/x/crypto/bcrypt" 7 | "testing" 8 | ) 9 | 10 | func TestCreateUserCreatesUserInCorrectState(t *testing.T) { 11 | assert := assert.New(t) 12 | 13 | username := helpers.RandomString() 14 | err := CreateUser(username) 15 | assert.Nil(err) 16 | 17 | var user User 18 | db_err := Orm.First(&user, "Username = ?", username) 19 | assert.Nil(db_err.Error) 20 | 21 | assert.Equal(false, user.Admin) 22 | } 23 | 24 | func TestSetPasswordSetsPassword(t *testing.T) { 25 | assert := assert.New(t) 26 | 27 | user := CreateTestUser([]string{}) 28 | password := "figgindiggle" 29 | err := user.SetPassword(password) 30 | assert.Nil(err) 31 | 32 | err = bcrypt.CompareHashAndPassword(user.Password, []byte(password)) 33 | assert.Nil(err) 34 | } 35 | 36 | func TestValidAuthenticationWithValidAuthentication(t *testing.T) { 37 | assert := assert.New(t) 38 | 39 | user := CreateTestUser([]string{}) 40 | password := "flahblahblah" 41 | user.SetPassword(password) 42 | assert.Equal(true, user.ValidAuthentication(password)) 43 | } 44 | 45 | func TestValidAuthenticationWithBadPassword(t *testing.T) { 46 | assert := assert.New(t) 47 | 48 | user := CreateTestUser([]string{}) 49 | password := "flahblahblah" 50 | user.SetPassword(password) 51 | assert.Equal(false, user.ValidAuthentication("flooblublu")) 52 | } 53 | 54 | func TestNewSessionCreatesSession(t *testing.T) { 55 | assert := assert.New(t) 56 | 57 | user := CreateTestUser([]string{}) 58 | cookie, err := user.NewSession() 59 | assert.Nil(err) 60 | 61 | var session Session 62 | db_err := Orm.First(&session, "Cookie = ?", cookie) 63 | assert.Nil(db_err.Error) 64 | if assert.NotNil(session.UserID) { 65 | assert.Equal(session.UserID, user.ID) 66 | } 67 | } 68 | 69 | func TestDestroyAllSessionsDestroysAllSessions(t *testing.T) { 70 | assert := assert.New(t) 71 | 72 | user := CreateTestUser([]string{}) 73 | _, err := user.NewSession() 74 | assert.Nil(err) 75 | 76 | assert.Equal(1, len(user.Sessions)) 77 | user.DestroyAllSessions() 78 | assert.Equal(0, len(user.Sessions)) 79 | } 80 | -------------------------------------------------------------------------------- /models/wiress80211frame.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/google/gopacket" 5 | "github.com/google/gopacket/layers" 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | // https://github.com/torvalds/linux/blob/master/include/linux/ieee80211.h#L1787 10 | var ELEMENT_IDS = map[byte]string{ 11 | 0: "SSID", 12 | 1: "SUPPORTED_RATES", 13 | 5: "TRAFFIC_INDICATION_MAP", 14 | 7: "COUNTRY", 15 | 11: "QBSS", 16 | 42: "ERP_INFO", 17 | 48: "RSN", 18 | 50: "EXTENDED_SUPPORTED_RATES", 19 | 61: "HT_OPERATION", 20 | 3: "DS_PARAMETER_SET", 21 | 45: "HT_CAPABILITIES", 22 | 127: "EXTENDED_CAPABILITIES", 23 | 221: "VENDOR_SPECIFIC", 24 | /* 25 | WLAN_EID_FH_PARAMS = 2, // reserved now 26 | WLAN_EID_DS_PARAMS = 3, 27 | WLAN_EID_CF_PARAMS = 4, 28 | WLAN_EID_TIM = 5, 29 | WLAN_EID_IBSS_PARAMS = 6, 30 | WLAN_EID_COUNTRY = 7, 31 | // 8, 9 reserved 32 | WLAN_EID_REQUEST = 10, 33 | WLAN_EID_QBSS_LOAD = 11, 34 | WLAN_EID_EDCA_PARAM_SET = 12, 35 | WLAN_EID_TSPEC = 13, 36 | WLAN_EID_TCLAS = 14, 37 | WLAN_EID_SCHEDULE = 15, 38 | WLAN_EID_CHALLENGE = 16, 39 | // 17-31 reserved for challenge text extension 40 | WLAN_EID_PWR_CONSTRAINT = 32, 41 | WLAN_EID_PWR_CAPABILITY = 33, 42 | WLAN_EID_TPC_REQUEST = 34, 43 | WLAN_EID_TPC_REPORT = 35, 44 | WLAN_EID_SUPPORTED_CHANNELS = 36, 45 | WLAN_EID_CHANNEL_SWITCH = 37, 46 | WLAN_EID_MEASURE_REQUEST = 38, 47 | WLAN_EID_MEASURE_REPORT = 39, 48 | WLAN_EID_QUIET = 40, 49 | WLAN_EID_IBSS_DFS = 41, 50 | 51 | WLAN_EID_TS_DELAY = 43, 52 | WLAN_EID_TCLAS_PROCESSING = 44, 53 | WLAN_EID_HT_CAPABILITY = 45, 54 | WLAN_EID_QOS_CAPA = 46, 55 | // 47 reserved for Broadcom 56 | 57 | WLAN_EID_802_15_COEX = 49, 58 | WLAN_EID_EXT_SUPP_RATES = 50, 59 | WLAN_EID_AP_CHAN_REPORT = 51, 60 | WLAN_EID_NEIGHBOR_REPORT = 52, 61 | WLAN_EID_RCPI = 53, 62 | WLAN_EID_MOBILITY_DOMAIN = 54, 63 | WLAN_EID_FAST_BSS_TRANSITION = 55, 64 | WLAN_EID_TIMEOUT_INTERVAL = 56, 65 | WLAN_EID_RIC_DATA = 57, 66 | WLAN_EID_DSE_REGISTERED_LOCATION = 58, 67 | WLAN_EID_SUPPORTED_REGULATORY_CLASSES = 59, 68 | WLAN_EID_EXT_CHANSWITCH_ANN = 60, 69 | 70 | WLAN_EID_SECONDARY_CHANNEL_OFFSET = 62, 71 | WLAN_EID_BSS_AVG_ACCESS_DELAY = 63, 72 | WLAN_EID_ANTENNA_INFO = 64, 73 | WLAN_EID_RSNI = 65, 74 | WLAN_EID_MEASUREMENT_PILOT_TX_INFO = 66, 75 | WLAN_EID_BSS_AVAILABLE_CAPACITY = 67, 76 | WLAN_EID_BSS_AC_ACCESS_DELAY = 68, 77 | WLAN_EID_TIME_ADVERTISEMENT = 69, 78 | WLAN_EID_RRM_ENABLED_CAPABILITIES = 70, 79 | WLAN_EID_MULTIPLE_BSSID = 71, 80 | WLAN_EID_BSS_COEX_2040 = 72, 81 | WLAN_EID_BSS_INTOLERANT_CHL_REPORT = 73, 82 | WLAN_EID_OVERLAP_BSS_SCAN_PARAM = 74, 83 | WLAN_EID_RIC_DESCRIPTOR = 75, 84 | WLAN_EID_MMIE = 76, 85 | WLAN_EID_ASSOC_COMEBACK_TIME = 77, 86 | WLAN_EID_EVENT_REQUEST = 78, 87 | WLAN_EID_EVENT_REPORT = 79, 88 | WLAN_EID_DIAGNOSTIC_REQUEST = 80, 89 | WLAN_EID_DIAGNOSTIC_REPORT = 81, 90 | WLAN_EID_LOCATION_PARAMS = 82, 91 | WLAN_EID_NON_TX_BSSID_CAP = 83, 92 | WLAN_EID_SSID_LIST = 84, 93 | WLAN_EID_MULTI_BSSID_IDX = 85, 94 | WLAN_EID_FMS_DESCRIPTOR = 86, 95 | WLAN_EID_FMS_REQUEST = 87, 96 | WLAN_EID_FMS_RESPONSE = 88, 97 | WLAN_EID_QOS_TRAFFIC_CAPA = 89, 98 | WLAN_EID_BSS_MAX_IDLE_PERIOD = 90, 99 | WLAN_EID_TSF_REQUEST = 91, 100 | WLAN_EID_TSF_RESPOSNE = 92, 101 | WLAN_EID_WNM_SLEEP_MODE = 93, 102 | WLAN_EID_TIM_BCAST_REQ = 94, 103 | WLAN_EID_TIM_BCAST_RESP = 95, 104 | WLAN_EID_COLL_IF_REPORT = 96, 105 | WLAN_EID_CHANNEL_USAGE = 97, 106 | WLAN_EID_TIME_ZONE = 98, 107 | WLAN_EID_DMS_REQUEST = 99, 108 | WLAN_EID_DMS_RESPONSE = 100, 109 | WLAN_EID_LINK_ID = 101, 110 | WLAN_EID_WAKEUP_SCHEDUL = 102, 111 | // 103 reserved 112 | WLAN_EID_CHAN_SWITCH_TIMING = 104, 113 | WLAN_EID_PTI_CONTROL = 105, 114 | WLAN_EID_PU_BUFFER_STATUS = 106, 115 | WLAN_EID_INTERWORKING = 107, 116 | WLAN_EID_ADVERTISEMENT_PROTOCOL = 108, 117 | WLAN_EID_EXPEDITED_BW_REQ = 109, 118 | WLAN_EID_QOS_MAP_SET = 110, 119 | WLAN_EID_ROAMING_CONSORTIUM = 111, 120 | WLAN_EID_EMERGENCY_ALERT = 112, 121 | WLAN_EID_MESH_CONFIG = 113, 122 | WLAN_EID_MESH_ID = 114, 123 | WLAN_EID_LINK_METRIC_REPORT = 115, 124 | WLAN_EID_CONGESTION_NOTIFICATION = 116, 125 | WLAN_EID_PEER_MGMT = 117, 126 | WLAN_EID_CHAN_SWITCH_PARAM = 118, 127 | WLAN_EID_MESH_AWAKE_WINDOW = 119, 128 | WLAN_EID_BEACON_TIMING = 120, 129 | WLAN_EID_MCCAOP_SETUP_REQ = 121, 130 | WLAN_EID_MCCAOP_SETUP_RESP = 122, 131 | WLAN_EID_MCCAOP_ADVERT = 123, 132 | WLAN_EID_MCCAOP_TEARDOWN = 124, 133 | WLAN_EID_GANN = 125, 134 | WLAN_EID_RANN = 126, 135 | WLAN_EID_EXT_CAPABILITY = 127, 136 | // 128, 129 reserved for Agere 137 | WLAN_EID_PREQ = 130, 138 | WLAN_EID_PREP = 131, 139 | WLAN_EID_PERR = 132, 140 | // 133-136 reserved for Cisco 141 | WLAN_EID_PXU = 137, 142 | WLAN_EID_PXUC = 138, 143 | WLAN_EID_AUTH_MESH_PEER_EXCH = 139, 144 | WLAN_EID_MIC = 140, 145 | WLAN_EID_DESTINATION_URI = 141, 146 | WLAN_EID_UAPSD_COEX = 142, 147 | WLAN_EID_WAKEUP_SCHEDULE = 143, 148 | WLAN_EID_EXT_SCHEDULE = 144, 149 | WLAN_EID_STA_AVAILABILITY = 145, 150 | WLAN_EID_DMG_TSPEC = 146, 151 | WLAN_EID_DMG_AT = 147, 152 | WLAN_EID_DMG_CAP = 148, 153 | // 149 reserved for Cisco 154 | WLAN_EID_CISCO_VENDOR_SPECIFIC = 150, 155 | WLAN_EID_DMG_OPERATION = 151, 156 | WLAN_EID_DMG_BSS_PARAM_CHANGE = 152, 157 | WLAN_EID_DMG_BEAM_REFINEMENT = 153, 158 | WLAN_EID_CHANNEL_MEASURE_FEEDBACK = 154, 159 | // 155-156 reserved for Cisco 160 | WLAN_EID_AWAKE_WINDOW = 157, 161 | WLAN_EID_MULTI_BAND = 158, 162 | WLAN_EID_ADDBA_EXT = 159, 163 | WLAN_EID_NEXT_PCP_LIST = 160, 164 | WLAN_EID_PCP_HANDOVER = 161, 165 | WLAN_EID_DMG_LINK_MARGIN = 162, 166 | WLAN_EID_SWITCHING_STREAM = 163, 167 | WLAN_EID_SESSION_TRANSITION = 164, 168 | WLAN_EID_DYN_TONE_PAIRING_REPORT = 165, 169 | WLAN_EID_CLUSTER_REPORT = 166, 170 | WLAN_EID_RELAY_CAP = 167, 171 | WLAN_EID_RELAY_XFER_PARAM_SET = 168, 172 | WLAN_EID_BEAM_LINK_MAINT = 169, 173 | WLAN_EID_MULTIPLE_MAC_ADDR = 170, 174 | WLAN_EID_U_PID = 171, 175 | WLAN_EID_DMG_LINK_ADAPT_ACK = 172, 176 | // 173 reserved for Symbol 177 | WLAN_EID_MCCAOP_ADV_OVERVIEW = 174, 178 | WLAN_EID_QUIET_PERIOD_REQ = 175, 179 | // 176 reserved for Symbol 180 | WLAN_EID_QUIET_PERIOD_RESP = 177, 181 | // 178-179 reserved for Symbol 182 | // 180 reserved for ISO/IEC 20011 183 | WLAN_EID_EPAC_POLICY = 182, 184 | WLAN_EID_CLISTER_TIME_OFF = 183, 185 | WLAN_EID_INTER_AC_PRIO = 184, 186 | WLAN_EID_SCS_DESCRIPTOR = 185, 187 | WLAN_EID_QLOAD_REPORT = 186, 188 | WLAN_EID_HCCA_TXOP_UPDATE_COUNT = 187, 189 | WLAN_EID_HL_STREAM_ID = 188, 190 | WLAN_EID_GCR_GROUP_ADDR = 189, 191 | WLAN_EID_ANTENNA_SECTOR_ID_PATTERN = 190, 192 | WLAN_EID_VHT_CAPABILITY = 191, 193 | WLAN_EID_VHT_OPERATION = 192, 194 | WLAN_EID_EXTENDED_BSS_LOAD = 193, 195 | WLAN_EID_WIDE_BW_CHANNEL_SWITCH = 194, 196 | WLAN_EID_VHT_TX_POWER_ENVELOPE = 195, 197 | WLAN_EID_CHANNEL_SWITCH_WRAPPER = 196, 198 | WLAN_EID_AID = 197, 199 | WLAN_EID_QUIET_CHANNEL = 198, 200 | WLAN_EID_OPMODE_NOTIF = 199, 201 | 202 | WLAN_EID_VENDOR_SPECIFIC = 221, 203 | WLAN_EID_QOS_PARAMETER = 222, 204 | WLAN_EID_CAG_NUMBER = 237, 205 | WLAN_EID_AP_CSN = 239, 206 | WLAN_EID_FILS_INDICATION = 240, 207 | WLAN_EID_DILS = 241, 208 | WLAN_EID_FRAGMENT = 242, 209 | WLAN_EID_EXTENSION = 255 210 | */ 211 | 212 | } 213 | 214 | type Wireless80211Frame struct { 215 | Length uint16 216 | TSFT uint64 217 | FlagsRadio layers.RadioTapFlags 218 | Rate layers.RadioTapRate 219 | ChannelFrequency layers.RadioTapChannelFrequency 220 | ChannelFlags layers.RadioTapChannelFlags 221 | FHSS uint16 222 | DBMAntennaSignal int8 223 | DBMAntennaNoise int8 224 | LockQuality uint16 225 | TxAttenuation uint16 226 | DBTxAttenuation uint16 227 | DBMTxPower int8 228 | Antenna uint8 229 | DBAntennaSignal uint8 230 | DBAntennaNoise uint8 231 | RxFlags layers.RadioTapRxFlags 232 | TxFlags layers.RadioTapTxFlags 233 | RtsRetries uint8 234 | DataRetries uint8 235 | MCS layers.RadioTapMCS 236 | AMPDUStatus layers.RadioTapAMPDUStatus 237 | VHT layers.RadioTapVHT 238 | Type string 239 | Flags80211 layers.Dot11Flags 240 | Proto uint8 241 | DurationID uint16 242 | Address1 string 243 | Address2 string 244 | Address3 string 245 | Address4 string 246 | SequenceNumber uint16 247 | FragmentNumber uint16 248 | Checksum uint32 249 | Elements map[string][]byte 250 | Interface string 251 | } 252 | 253 | func (frame *Wireless80211Frame) ParseElements(packet gopacket.Packet, ether *layers.Dot11) { 254 | if probegreq, ok := packet.Layer(layers.LayerTypeDot11MgmtProbeReq).(*layers.Dot11MgmtProbeReq); ok { 255 | frame.Elements = ParseFrameElements(probegreq.LayerContents()) 256 | } else if beaconframe, ok := packet.Layer(layers.LayerTypeDot11MgmtBeacon).(*layers.Dot11MgmtBeacon); ok { 257 | frame.Elements = ParseFrameElements(beaconframe.LayerContents()[12:]) 258 | } else if _, ok := packet.Layer(layers.LayerTypeDot11Data).(*layers.Dot11Data); ok { 259 | } else if _, ok := packet.Layer(layers.LayerTypeDot11DataCFAck).(*layers.Dot11DataCFAck); ok { 260 | } else if _, ok := packet.Layer(layers.LayerTypeDot11DataCFAckNoData).(*layers.Dot11DataCFAckNoData); ok { 261 | } else if _, ok := packet.Layer(layers.LayerTypeDot11DataCFAckPoll).(*layers.Dot11DataCFAckPoll); ok { 262 | } else if _, ok := packet.Layer(layers.LayerTypeDot11DataCFAckPollNoData).(*layers.Dot11DataCFAckPollNoData); ok { 263 | } else if _, ok := packet.Layer(layers.LayerTypeDot11DataCFPoll).(*layers.Dot11DataCFPoll); ok { 264 | } else if _, ok := packet.Layer(layers.LayerTypeDot11DataCFPollNoData).(*layers.Dot11DataCFPollNoData); ok { 265 | } else if _, ok := packet.Layer(layers.LayerTypeDot11DataNull).(*layers.Dot11DataNull); ok { 266 | //} else if _, ok := packet.Layer(layers.LayerTypeDot11DataQOS).(*layers.Dot11DataQOS); ok { 267 | } else if _, ok := packet.Layer(layers.LayerTypeDot11DataQOSCFAckPollNoData).(*layers.Dot11DataQOSCFAckPollNoData); ok { 268 | } else if _, ok := packet.Layer(layers.LayerTypeDot11DataQOSCFPollNoData).(*layers.Dot11DataQOSCFPollNoData); ok { 269 | } else if _, ok := packet.Layer(layers.LayerTypeDot11DataQOSData).(*layers.Dot11DataQOSData); ok { 270 | } else if _, ok := packet.Layer(layers.LayerTypeDot11DataQOSDataCFAck).(*layers.Dot11DataQOSDataCFAck); ok { 271 | } else if _, ok := packet.Layer(layers.LayerTypeDot11DataQOSDataCFAckPoll).(*layers.Dot11DataQOSDataCFAckPoll); ok { 272 | } else if _, ok := packet.Layer(layers.LayerTypeDot11DataQOSDataCFPoll).(*layers.Dot11DataQOSDataCFPoll); ok { 273 | } else if _, ok := packet.Layer(layers.LayerTypeDot11DataQOSNull).(*layers.Dot11DataQOSNull); ok { 274 | } 275 | } 276 | 277 | func ParseFrameElements(stream []byte) (elements map[string][]byte) { 278 | elements = map[string][]byte{} 279 | for len(stream) > 0 { 280 | field_id, remainder := stream[0], stream[1:] 281 | stream = remainder 282 | 283 | field, ok := ELEMENT_IDS[field_id] 284 | if !ok { 285 | log.WithFields(log.Fields{ 286 | "at": "models.ParseFrameElements", 287 | "id": field_id, 288 | }).Warn("unknown element id") 289 | return 290 | } 291 | 292 | field_len, remainder := stream[0], stream[1:] 293 | stream = remainder 294 | if field_len == 0 { 295 | continue 296 | } 297 | 298 | field_data, remainder := stream[:field_len], stream[field_len:] 299 | stream = remainder 300 | 301 | elements[field] = field_data 302 | } 303 | return 304 | } 305 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hkparker/Wave/9cdc9d29918d815d0219c7a441d2a38cc1bb5f97/static/favicon.ico --------------------------------------------------------------------------------