Skip to content

Commit 69e446f

Browse files
committed
Initial commit
0 parents  commit 69e446f

File tree

11 files changed

+507
-0
lines changed

11 files changed

+507
-0
lines changed

.gitignore

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
### Python ###
2+
# Byte-compiled / optimized / DLL files
3+
__pycache__/
4+
*.py[cod]
5+
*$py.class
6+
7+
# C extensions
8+
*.so
9+
10+
# Distribution / packaging
11+
.Python
12+
build/
13+
develop-eggs/
14+
dist/
15+
downloads/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
MANIFEST
28+
29+
# PyInstaller
30+
# Usually these files are written by a python script from a template
31+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
32+
*.manifest
33+
*.spec
34+
35+
# Installer logs
36+
pip-log.txt
37+
pip-delete-this-directory.txt
38+
39+
# Unit test / coverage reports
40+
htmlcov/
41+
.tox/
42+
.nox/
43+
.coverage
44+
.coverage.*
45+
.cache
46+
nosetests.xml
47+
coverage.xml
48+
*.cover
49+
.hypothesis/
50+
.pytest_cache/
51+
52+
# Translations
53+
*.mo
54+
*.pot
55+
56+
# Django stuff:
57+
*.log
58+
local_settings.py
59+
db.sqlite3
60+
61+
# Flask stuff:
62+
instance/
63+
.webassets-cache
64+
65+
# Scrapy stuff:
66+
.scrapy
67+
68+
# Sphinx documentation
69+
docs/_build/
70+
71+
# PyBuilder
72+
target/
73+
74+
# Jupyter Notebook
75+
.ipynb_checkpoints
76+
77+
# IPython
78+
profile_default/
79+
ipython_config.py
80+
81+
# pyenv
82+
.python-version
83+
84+
# celery beat schedule file
85+
celerybeat-schedule
86+
87+
# SageMath parsed files
88+
*.sage.py
89+
90+
# Environments
91+
.env
92+
.venv
93+
env/
94+
venv/
95+
ENV/
96+
env.bak/
97+
venv.bak/
98+
99+
# Spyder project settings
100+
.spyderproject
101+
.spyproject
102+
103+
# Rope project settings
104+
.ropeproject
105+
106+
# mkdocs documentation
107+
/site
108+
109+
# mypy
110+
.mypy_cache/
111+
.dmypy.json
112+
dmypy.json
113+
114+
### Python Patch ###
115+
.venv/
116+
117+
### Python.VirtualEnv Stack ###
118+
# Virtualenv
119+
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
120+
[Bb]in
121+
[Ii]nclude
122+
[Ll]ib
123+
[Ll]ib64
124+
[Ll]ocal
125+
[Ss]cripts
126+
pyvenv.cfg
127+
pip-selfcheck.json
128+
129+
130+
# End of https://www.gitignore.io/api/python

Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM python:3-alpine
2+
3+
MAINTAINER Victor Rachieru
4+
5+
WORKDIR /app
6+
COPY . .
7+
8+
RUN apk --update --no-cache add openssh-client \
9+
&& pip install -r requirements.txt
10+
11+
EXPOSE 80
12+
13+
CMD [ "python", "app.py" ]

README.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<p align="center">
2+
<img src="static/img/logo.svg" width="200" border="0" alt="guest-wifi">
3+
<br/>
4+
<a href="https://github.com/vrachieru/guest-wifi/releases/latest">
5+
<img src="https://img.shields.io/badge/version-1.0-brightgreen.svg?style=flat-square" alt="Version">
6+
</a>
7+
<a href="https://hub.docker.com/r/vrachieru/guest-wifi/">
8+
<img src="https://img.shields.io/docker/stars/vrachieru/guest-wifi.svg?style=flat-square" />
9+
</a>
10+
<a href="https://hub.docker.com/r/vrachieru/guest-wifi/">
11+
<img src="https://img.shields.io/docker/pulls/vrachieru/guest-wifi.svg?style=flat-square" />
12+
</a>
13+
<br/>
14+
Easily share your wifi credentials with guests
15+
</p>
16+
17+
This is a solution to a problem that I (and maybe you) have: letting your guests use your wifi when they come to visit sucks.
18+
19+
Usually you have a few sucky options with their pros and cons:
20+
* Have a weak password that you can easly share
21+
* PRO - makes it easy for guests to connect
22+
* CON - typically means your password is guessable/susceptible to brute-force style attacks
23+
* CON - your password doesn't change (or at least doesn't change much), so over time you might not know who has access to your network
24+
* Have a complicated password
25+
* PRO - password probably isn't guessable/brute-forcible
26+
* CON - it is most likely a pain to share that password
27+
* CON - your password still probably doesn't change often, so once it is shared, it is shared for good
28+
* Rotate your (either strong or weak) password often
29+
* PRO - password is probably pretty safe because period of time that it's valid is sgnificantly less the time needed to brute-force it
30+
* CON - guests have to update the network password often
31+
* CON - you also have to actually change the password often
32+
33+
This project aims to provide an easy and reliable way of sharing and securing your guest wifi by automating the task of rotating strong passwords as well as facilitating their distribution via a info screen to show to your guests.
34+
35+
The QR code enables guests to just scan and join the network and avoid the trouble of manually typing your funky password.
36+
The iOS Camera App has support for WiFi QR codes since iOS 11 and Android users can make use of [this](https://play.google.com/store/apps/details?id=com.google.zxing.client.android) app from ZXing.
37+
38+
I personally have the service running on a Raspberry Pi and display the info page on my TV but your setup can be whatever you want it to be.
39+
40+
### Example
41+
42+
<p align="center">
43+
<img src="static/img/screenshot.png" border="0">
44+
</p>
45+
46+
47+
### Features
48+
49+
* Display connection information
50+
* Display QR code for quick network login
51+
* Rotate passwords automatically on predefined interval
52+
53+
54+
### Quick start
55+
56+
I recommend pulling the [latest image](https://hub.docker.com/r/vrachieru/guest-wifi/) from Docker hub as this is the easiest way:
57+
```bash
58+
$ docker pull vrachieru/guest-wifi
59+
```
60+
61+
If you'd like, you can build the Docker image yourself:
62+
```bash
63+
docker build -t <yourname>/guest-wifi .
64+
```
65+
66+
Specify your desired configuration and run the container:
67+
```bash
68+
$ docker run -<d|i> --rm \
69+
-e ROUTER_USERNAME='admin' \
70+
-e ROUTER_SSH_KEY='/app/ssh_key' \
71+
-e ROUTER_WIFI_INTERFACE='wl0.1' \
72+
-e TZ='Europe/Bucharest' \
73+
-v /host/path/to/ssh_key:/app/ssh_key:ro \
74+
-p <host_port>:80 \
75+
--name guest-wifi \
76+
vrachieru/guest-wifi
77+
```
78+
79+
You can stop the container using:
80+
```bash
81+
$ docker stop guest-wifi
82+
```
83+
84+
85+
### Configuration
86+
87+
You can configure the service via the following environment variables.
88+
89+
| Environment Variable | Default Value | Description |
90+
| --------------------- | ------------- | ----------- |
91+
| ROUTER_IP | 192.168.1.1 | IP address of the router |
92+
| ROUTER_SSH_PORT | 22 | Port on which the ssh daemon is running. |
93+
| ROUTER_SSH_KEY | - | Ssh key to use while connecting to router. Note that if set it takes precedence over password login |
94+
| ROUTER_USERNAME | admin | Username to use while connecting over ssh |
95+
| ROUTER_PASSWORD | admin | Password to use while connecting over ssh (if you have enabled password login) |
96+
| ROUTER_WIFI_INTERFACE | wl0.1 | Tipically your router can sustan multiple guest WLANs which are sub-interfaces of your main WLANs. In this case wl0 is the main WLAN on 2.4GHz and wl1 is the 5GHz equivalent. The default is the first guest interface available on 2.4GHz |
97+
| PASSWORD_RESET_CRON | 0 12 * * MON | A cron style expression representing how often to rotate the password. I find that doing so once every Monday at noon is a safe bet as there's a minimal chance to have that many people around |
98+
| PASSWORD_COMPLEXITY | 16 | A 16 character password within the letter+digit+punctuation charset is brute-forceable in about ... well, a really long time. If you're really paranoid you can go up to 63 |
99+
| TZ | UTC | Timezone of the container
100+
101+
102+
### Compatibility
103+
104+
Current implementation is based on `AsusWRT` routers although it may work on devices from other vendors as well without any modifications.
105+
Although providing your own custom implementation by extending the `Router` class shouldn't be much of a fuss.
106+
107+
108+
### License
109+
110+
MIT

app.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from os import environ
2+
3+
from flask import Flask, render_template
4+
5+
from apscheduler.schedulers.background import BackgroundScheduler
6+
from apscheduler.triggers.cron import CronTrigger
7+
8+
from router import AsusWRT
9+
from qr import QrCode
10+
11+
app = Flask(__name__)
12+
13+
scheduler = BackgroundScheduler(daemon=True)
14+
scheduler.start()
15+
16+
router = AsusWRT(
17+
environ.get('ROUTER_IP', '192.168.1.1'),
18+
int(environ.get('ROUTER_SSH_PORT', 22)),
19+
environ.get('ROUTER_USERNAME', 'admin'),
20+
environ.get('ROUTER_PASSWORD', 'admin'),
21+
environ.get('ROUTER_SSH_KEY'),
22+
environ.get('ROUTER_WIFI_INTERFACE', 'wl0.1')
23+
)
24+
25+
@app.route('/')
26+
def home():
27+
connection_details = router.connection_details
28+
valid_until = scheduler.get_job('reset_password').next_run_time
29+
qr_code = QrCode(**connection_details)
30+
31+
return render_template('index.html', **connection_details, valid_until=valid_until, qr=qr_code.base64svg)
32+
33+
@scheduler.scheduled_job(id='reset_password', trigger=CronTrigger.from_crontab(environ.get('PASSWORD_RESET_CRON', '0 12 * * MON')))
34+
def reset_password():
35+
router.reset_password()
36+
37+
if __name__ == '__main__':
38+
app.run(host='0.0.0.0', port=80)

qr.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from pyqrcode import create
2+
from io import BytesIO
3+
from base64 import b64encode
4+
5+
class QrCode:
6+
7+
def __init__(self, ssid, authentication_method, password, **kwargs):
8+
self._qr = create('WIFI:S:{ssid};T:{authentication_method};P:{password};;'.format(
9+
ssid = self._mecard_escape(ssid),
10+
authentication_method = self._authentication_method(authentication_method),
11+
password = self._mecard_escape(password)
12+
))
13+
14+
def _authentication_method(self, authentication_method):
15+
if authentication_method.lower() in ['wep']:
16+
return 'WEP'
17+
if authentication_method.lower() in ['wpa', 'wpa2', 'wpawpa2', 'psk', 'psk2', 'pskpsk2']:
18+
return 'WPA'
19+
return 'nopass'
20+
21+
def _mecard_escape(self, value):
22+
value = value.replace('\\', '\\\\')
23+
value = value.replace(';', '\\;')
24+
value = value.replace(':', '\\:')
25+
value = value.replace(',', '\\,')
26+
return value
27+
28+
@property
29+
def base64svg(self):
30+
stream = BytesIO()
31+
self._qr.svg(stream, scale=4)
32+
return b64encode(stream.getvalue()).decode('utf-8')

requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Flask==1.0.2
2+
pexpect==4.6.0
3+
PyQRCode==1.2.1
4+
apscheduler==3.5.3

0 commit comments

Comments
 (0)