├── .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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
18 |
19 |
27 |
--------------------------------------------------------------------------------
/frontend/src/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
33 |
34 |
36 |
--------------------------------------------------------------------------------
/frontend/src/Navbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
57 |
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 |
2 |
3 |
Account Setting
4 |
Change Password
5 |
6 |
7 | New passwords do not match
8 |
9 |
10 |
11 | Old password is incorrect
12 |
13 |
14 |
15 | Old password and new password are the same
16 |
17 |
18 |
19 | Password Updated!
20 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
34 |
35 |
36 |
37 |
44 |
45 |
46 |
47 |
48 |
49 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
117 |
118 |
132 |
--------------------------------------------------------------------------------
/frontend/src/components/CollectorManager.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Collector Management
4 |
Current Collectors
5 |
6 |
7 | Error: {{ downloadError }}
8 |
9 |
10 |
11 | Error getting collectors: {{ getCollectorsError }}
12 |
13 |
14 |
15 | Error deleting collector: {{ collectorDeletedError }}
16 |
17 |
18 |
19 | Collector Deleted
20 |
21 |
22 |
23 |
24 | Name |
25 | Certificate |
26 | Key |
27 | Server Certificate |
28 | Delete |
29 |
30 |
31 |
32 |
33 | {{ collector }} |
34 | Download |
35 | Download |
36 | Download |
37 |
38 |
39 | |
40 |
41 |
42 |
43 |
Add Collector
44 |
45 |
46 | Collector created
47 |
48 |
49 |
50 | Error creating collector: {{ this.collectorCreatedError }}
51 |
52 |
53 |
54 |
55 |
56 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
197 |
198 |
212 |
--------------------------------------------------------------------------------
/frontend/src/components/Dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
version: {{ this.$store.getters.currentUser.version }}
4 |
5 |
6 |
7 |
14 |
15 |
20 |
--------------------------------------------------------------------------------
/frontend/src/components/IDS.vue:
--------------------------------------------------------------------------------
1 |
2 | IDS
3 |
4 |
5 |
14 |
15 |
20 |
--------------------------------------------------------------------------------
/frontend/src/components/Rules.vue:
--------------------------------------------------------------------------------
1 |
2 | Rules
3 |
4 |
5 |
14 |
15 |
20 |
--------------------------------------------------------------------------------
/frontend/src/components/Unauthorized.vue:
--------------------------------------------------------------------------------
1 |
2 | Unauthorized
3 |
4 |
5 |
10 |
11 |
16 |
--------------------------------------------------------------------------------
/frontend/src/components/UserManager.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
User Management
4 |
Current Users
5 |
6 |
7 | Error getting users: {{ getUsersError }}
8 |
9 |
10 |
11 | New password has been assigned
12 |
13 |
14 |
15 | User has been deleted
16 |
17 |
18 |
19 | Error assigning password: {{ passwordSetError }}
20 |
21 |
22 |
23 | Error deleting user: {{ userDeleteError }}
24 |
25 |
26 |
27 |
28 | Username |
29 | Admin |
30 | Active Sessions |
31 | Password Reset |
32 | Delete User |
33 |
34 |
35 |
36 |
37 | {{ user.username }} |
38 | {{ user.admin }} |
39 | {{ user.sessions}} |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | |
50 |
51 |
52 | |
53 |
54 |
55 |
56 |
Add User
57 |
58 |
59 | User created
60 |
61 |
62 |
63 | Error creating user: {{ this.userCreatedError }}
64 |
65 |
66 |
67 |
68 |
69 |
76 |
77 |
78 |
79 |
80 |
81 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
209 |
210 |
224 |
--------------------------------------------------------------------------------
/frontend/src/components/Visualization.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Selected Node:
8 |
9 |
10 |
11 | MAC |
12 | {{ selectedNodeMAC }} |
13 |
14 |
15 | Vendor |
16 | {{ selectedNodeVendor }} |
17 |
18 |
19 | Probed For |
20 | {{ selectedNodeProbedFor }} |
21 |
22 |
23 |
24 |
25 |
Selected Association:
26 |
27 |
28 |
31 |
32 |
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
--------------------------------------------------------------------------------