forked from nsacyber/goSecure
-
Notifications
You must be signed in to change notification settings - Fork 2
/
gosecure_app.py
executable file
·502 lines (402 loc) · 16.7 KB
/
gosecure_app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
#!/usr/bin/env python3
import base64
import hashlib
import logging
import os
import pickle
import time
import flask_login as flask_login
from functools import wraps
from flask import (
Flask, render_template, request, Response, flash, redirect, url_for)
from forms import (
loginForm, initialSetupForm, userForm, wifiForm, vpnPskForm,
resetToDefaultForm, aboutForm, statusForm)
from scripts.pi_mgmt import (
pi_reboot, pi_shutdown, toggle_logging, start_ssh_service, update_client,
turn_on_led_green, turn_off_led_green)
from scripts.rpi_network_conn import (
add_wifi, internet_status, ping_status, reset_wifi)
from scripts.vpn_server_conn import (
set_vpn_params, reset_vpn_params, start_vpn, stop_vpn, restart_vpn,
vpn_status, vpn_configuration_status)
from systemd.journal import JournaldLogHandler
def default_loglevel():
if os.environ.get('DEBUG', '0') == '1' or os.path.exists('/home/pi/.debug'):
return logging.DEBUG
return logging.INFO
app = Flask(__name__)
login_manager = flask_login.LoginManager()
login_manager.init_app(app)
logger = logging.getLogger('goSecure')
journald_handler = JournaldLogHandler()
journald_handler.setFormatter(logging.Formatter(
'[%(levelname)s] [+] %(message)s'
))
logger.addHandler(journald_handler)
loglevel = default_loglevel()
logger.setLevel(loglevel)
logger.info("loglevel set to {}".format(
"DEBUG" if loglevel == logging.DEBUG else "INFO"))
# Flask-Login functions (for regular Pages)
# users = {"admin":{"password":"2074ad04839ae517751e5948ae13f0e3c90d186c9c9bbd29c3c88b9c6000dba5", "salt":"uOMbInZTYYpiCGvEaH8Byw==\n"}}
# default username=admin password=gosecure, user is prompted to change if default is being used.
with open("/home/pi/goSecure_Web_GUI/users_db.p", "rb") as fin:
users = pickle.load(fin)
class User(flask_login.UserMixin):
pass
@login_manager.user_loader
def user_loader(username):
if username not in users:
return
user = User()
user.id = username
return user
@login_manager.request_loader
def request_loader(request):
username = request.form.get('username')
if username not in users:
return None
user = User()
user.id = username
# DO NOT ever store passwords in plaintext and always compare password
# hashes using constant-time comparison!
if user_validate_credentials(request.form['username'], request.form['password']):
return None
return user
@login_manager.unauthorized_handler
def unauthorized_handler():
flash("Unauthorized, please log in.", "error")
return redirect(url_for("login"))
# Flask HTTP Basic Auth (for API)
def authenticate():
"""Sends a 401 response that enables basic auth"""
return Response(
"Unauthorized", 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
def requires_basic_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not user_validate_credentials(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
def flash_form_errors(form):
for field, errors in list(form.errors.items()):
for error in errors:
flash("Error in the %s field - %s" % (
getattr(form, field).label.text,
error
), "error")
# Auth helper functions
# return True is username and password pair match what's in the database
# else return False
def user_validate_credentials(username, password):
if username not in users:
return False
else:
stored_password = users[username]['password']
stored_salt = users[username]['salt']
userPasswordHash = hashlib.sha256(
str(stored_salt).encode('utf-8') + password.encode('utf-8')
).hexdigest()
return stored_password == userPasswordHash
# return True is password is changed successfully
# else return False
def user_change_credentials(username, password, new_password):
if username not in users:
return False
else:
# verify current password
if user_validate_credentials(username, password):
# change password
userPasswordHashSalt = base64.b64encode(os.urandom(16)).decode('utf-8')
userPasswordHash = hashlib.sha256(
str(userPasswordHashSalt).encode('utf-8') + new_password.encode('utf-8')
).hexdigest()
users[username]["salt"] = userPasswordHashSalt
users[username]["password"] = userPasswordHash
with open("/home/pi/goSecure_Web_GUI/users_db.p", "wb") as fout:
pickle.dump(users, fout)
return True
else:
return False
# return True if credentials are reset
# else return False
def user_reset_credentials(username, password):
return user_change_credentials(username, password, "gosecure")
# Routes for web pages
# 404 page
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
# Login Page
@app.route("/", methods=["GET", "POST"])
def login():
form = loginForm()
if request.method == "GET":
return render_template("login.html", form=form)
elif request.method == "POST":
if form.validate():
username = form.username.data
password = form.password.data
if user_validate_credentials(username, password):
user = User()
user.id = username
flask_login.login_user(user)
# check to see if default credentials are being used. If so, redirect to change password page.
if user_validate_credentials("admin", "gosecure"):
flash("Please change the default password.", "notice")
return redirect(url_for("user"))
else:
internet_status_bool = ping_status() and internet_status()
vpn_status_bool = vpn_status()
vpn_configuration_status_bool = vpn_configuration_status()
# check to see if network is up. If not, redirect to network page
if internet_status_bool is False and vpn_configuration_status_bool is True:
flash("Internet is not reachable.", "notice")
return redirect(url_for("wifi"))
# check to see if network and vpn are up. If not, redirect to initial setup page
elif internet_status_bool is False and vpn_status_bool is False:
return redirect(url_for("initial_setup"))
# check to see if vpn is up. If not, redirect to vpn page
elif vpn_status_bool is False:
flash("VPN is not established.", "notice")
turn_off_led_green()
return redirect(url_for("vpn_psk"))
else:
turn_on_led_green()
return redirect(request.args.get("next") or url_for("status"))
else:
flash("Invalid username or password. Please try again.", "error")
return render_template("login.html", form=form)
else:
flash_form_errors(form)
return render_template("login.html", form=form)
@app.route('/logout')
def logout():
flask_login.logout_user()
return redirect(url_for("login"))
@app.route("/about", methods=["POST"])
def about_action():
form = aboutForm()
if request.method == "POST":
return render_template('about.html', form=form)
# User page
@app.route("/status", methods=["GET", "POST"])
@flask_login.login_required
def status():
form = statusForm()
if request.method == "GET":
# check to see if network and vpn are active, red=not active, green=active
internet_status_color = "green" if internet_status() else "red"
vpn_status_color = "green" if vpn_status() else "red"
return render_template("status.html", form=form, internet_status_color=internet_status_color, vpn_status_color=vpn_status_color)
# User page
@app.route("/user", methods=["GET", "POST"])
@flask_login.login_required
def user():
form = userForm()
if request.method == "GET":
form.username.data = flask_login.current_user.id
return render_template("user.html", form=form)
elif request.method == "POST":
if form.validate():
username = form.username.data
password = form.password.data
new_password = form.new_password.data
if user_change_credentials(username, password, new_password):
flash("Your user information has been successfully changed. Please login with the new credentials.", "success")
return redirect(url_for("login"))
else:
flash("Invalid current username or password. Please try again.", "error")
return render_template("user.html", form=form)
else:
flash_form_errors(form)
return render_template("user.html", form=form)
# Initial setup page
@app.route("/initial_setup", methods=["GET", "POST"])
@flask_login.login_required
def initial_setup():
form = initialSetupForm()
if request.method == "GET":
return render_template("initial_setup.html", form=form)
elif request.method == "POST":
if form.validate():
ssid = form.ssid.data.rsplit("-", 1)[0]
psk = form.psk.data
add_wifi(ssid, psk)
if internet_status() is True:
vpn_server = form.vpn_server.data
user_id = form.user_id.data
user_psk = form.user_psk.data
set_vpn_params(vpn_server, user_id, user_psk)
restart_vpn()
flash("Wifi and VPN settings saved!", "success")
return redirect(url_for("status"))
else:
flash("Error! Cannot reach the internet...", "error")
return render_template("initial_setup.html", form=form)
else:
flash("Error! " + str(form.data), "error")
return render_template("initial_setup.html", form=form)
# Wifi page
@app.route("/wifi", methods=["GET", "POST"])
@flask_login.login_required
def wifi():
form = wifiForm()
if request.method == "GET":
return render_template("wifi.html", form=form)
elif request.method == "POST":
if form.validate():
ssid = form.ssid.data.rsplit("-", 1)[0]
psk = form.psk.data
add_wifi(ssid, psk)
time.sleep(5)
if internet_status() is True:
restart_vpn()
time.sleep(5)
flash("Wifi settings saved! VPN Restarted!", "success")
return redirect(url_for("status"))
else:
flash("Error! Cannot reach the internet...", "error")
return render_template("wifi.html", form=form)
else:
flash("Error! " + str(form.data), "error")
return render_template("wifi.html", form=form)
# VPN psk page
@app.route("/vpn_psk", methods=["GET", "POST"])
@flask_login.login_required
def vpn_psk():
form = vpnPskForm()
if request.method == "GET":
return render_template("vpn_psk.html", form=form)
elif request.method == "POST":
if form.validate():
vpn_server = form.vpn_server.data
user_id = form.user_id.data
user_psk = form.user_psk.data
set_vpn_params(vpn_server, user_id, user_psk)
restart_vpn()
if vpn_status():
flash("VPN settings saved and VPN restarted!", "success")
return redirect(url_for("status"))
else:
flash("VPN settings saved and VPN restarted! Unable to establish VPN connection.", "error")
return render_template("vpn_psk.html", form=form)
else:
flash("Error! " + str(form.data), "error")
return render_template("vpn_psk.html", form=form)
# Reset to default page
@app.route("/reset_to_default", methods=["GET", "POST"])
@flask_login.login_required
def reset_to_default():
form = resetToDefaultForm()
if request.method == "GET":
form.username.data = flask_login.current_user.id
return render_template("reset_to_default.html", form=form)
elif request.method == "POST":
if form.validate():
username = form.username.data
password = form.password.data
reset_vpn_params()
reset_wifi()
if user_reset_credentials(username, password):
flash("Your client has been successfully reset to default settings.", "success")
return redirect(url_for("logout"))
else:
flash("Error resetting client.", "error")
return render_template("reset_to_default.html", form=form)
else:
flash_form_errors(form)
return render_template("reset_to_default.html", form=form)
@app.route("/action", methods=["POST"])
@flask_login.login_required
def execute_action():
action = request.form["action"]
if action == "reboot":
pi_reboot()
elif action == "shutdown":
pi_shutdown()
elif action == "start_vpn":
start_vpn()
flash("VPN Started!", "notice")
elif action == "stop_vpn":
stop_vpn()
flash("VPN Stopped!", "notice")
elif action == "restart_vpn":
restart_vpn()
flash("VPN Restarted!", "notice")
elif action == "ssh_service":
start_ssh_service()
flash("SSH Service Started! It will be turned off on reboot.")
elif action == "logging":
level = toggle_logging()
flash("Logging level set to {}".format(level))
elif action == "update_client":
update_client()
flash("Client will reboot... please reload this page in 1 minute.")
else:
form = initialSetupForm()
flash("Error! Invalid Action!", "error")
return redirect(url_for("status"))
# REST API
@app.route("/v1.0/vpn/credentials", methods=["POST", "DELETE"])
@requires_basic_auth
def api_vpn_credentials():
if request.method == "POST":
form = initialSetupForm()
form.vpn_server.data = request.json["vpn_server"]
form.user_id.data = request.json["user_id"]
form.user_psk.data = request.json["user_psk"]
if request.headers['Content-Type'] == 'application/json':
if form.vpn_server.validate(form) and form.user_id.validate(form) and form.user_psk.validate(form):
set_vpn_params(form.vpn_server.data, form.user_id.data, form.user_psk.data)
return "Successfully set vpn_server, user_id, and psk for VPN"
else:
return "Invalid user_id or psk format"
else:
return "415 Unsupported Media Type - Use application/json"
elif request.method == "DELETE":
reset_vpn_params()
return "Successfully reset vpn_server, user_id, and psk for VPN"
else:
return "Only POST and DELETE methods are supported. Refer to the API Documentation"
@app.route("/v1.0/vpn/actions", methods=["POST"])
@requires_basic_auth
def api_vpn_actions():
if request.method == "POST":
if request.headers['Content-Type'] == 'application/json':
action = request.json["action"]
if action == "start_vpn":
if start_vpn():
return "VPN service started, VPN is ESTABLISHED"
else:
return "VPN service started, VPN is NOT ESTABLISHED"
elif action == "stop_vpn":
stop_vpn()
return "VPN service stopped, VPN is NOT ESTABLISHED"
elif action == "restart_vpn":
if restart_vpn():
return "VPN service restarted, VPN is ESTABLISHED"
else:
return "VPN service restarted, VPN is NOT ESTABLISHED"
else:
return "Error! Invalid Action!"
else:
return "415 Unsupported Media Type - Use application/json"
else:
return "Only POST method is supported. Refer to the API Documentation"
if __name__ == "__main__":
app.secret_key = os.urandom(24)
# Just because OCD
if vpn_status():
turn_on_led_green()
# if SSL key and certificate pair do not exist, create them.
if (os.path.exists("ssl.key") and os.path.exists("ssl.crt")) is not True:
os.system('openssl genrsa 2048 > ssl.key')
os.system('openssl req -new -x509 -nodes -sha256 -days 1095 -subj "/C=US/O=goSecure/CN=goSecureClient" -key ssl.key > ssl.crt')
os.system('sudo chown pi:pi ssl.crt ssl.key')
os.system('sudo chmod 440 ssl.crt ssl.key')
app.run(host="192.168.50.1", port=443, ssl_context=("ssl.crt", "ssl.key"))