Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
vrachieru committed Sep 26, 2018
0 parents commit 69e446f
Show file tree
Hide file tree
Showing 11 changed files with 507 additions and 0 deletions.
130 changes: 130 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

### Python Patch ###
.venv/

### Python.VirtualEnv Stack ###
# Virtualenv
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
[Bb]in
[Ii]nclude
[Ll]ib
[Ll]ib64
[Ll]ocal
[Ss]cripts
pyvenv.cfg
pip-selfcheck.json


# End of https://www.gitignore.io/api/python
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM python:3-alpine

MAINTAINER Victor Rachieru

WORKDIR /app
COPY . .

RUN apk --update --no-cache add openssh-client \
&& pip install -r requirements.txt

EXPOSE 80

CMD [ "python", "app.py" ]
110 changes: 110 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<p align="center">
<img src="static/img/logo.svg" width="200" border="0" alt="guest-wifi">
<br/>
<a href="https://github.com/vrachieru/guest-wifi/releases/latest">
<img src="https://img.shields.io/badge/version-1.0-brightgreen.svg?style=flat-square" alt="Version">
</a>
<a href="https://hub.docker.com/r/vrachieru/guest-wifi/">
<img src="https://img.shields.io/docker/stars/vrachieru/guest-wifi.svg?style=flat-square" />
</a>
<a href="https://hub.docker.com/r/vrachieru/guest-wifi/">
<img src="https://img.shields.io/docker/pulls/vrachieru/guest-wifi.svg?style=flat-square" />
</a>
<br/>
Easily share your wifi credentials with guests
</p>

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.

Usually you have a few sucky options with their pros and cons:
* Have a weak password that you can easly share
* PRO - makes it easy for guests to connect
* CON - typically means your password is guessable/susceptible to brute-force style attacks
* 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
* Have a complicated password
* PRO - password probably isn't guessable/brute-forcible
* CON - it is most likely a pain to share that password
* CON - your password still probably doesn't change often, so once it is shared, it is shared for good
* Rotate your (either strong or weak) password often
* PRO - password is probably pretty safe because period of time that it's valid is sgnificantly less the time needed to brute-force it
* CON - guests have to update the network password often
* CON - you also have to actually change the password often

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.

The QR code enables guests to just scan and join the network and avoid the trouble of manually typing your funky password.
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.

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.

### Example

<p align="center">
<img src="static/img/screenshot.png" border="0">
</p>


### Features

* Display connection information
* Display QR code for quick network login
* Rotate passwords automatically on predefined interval


### Quick start

I recommend pulling the [latest image](https://hub.docker.com/r/vrachieru/guest-wifi/) from Docker hub as this is the easiest way:
```bash
$ docker pull vrachieru/guest-wifi
```

If you'd like, you can build the Docker image yourself:
```bash
docker build -t <yourname>/guest-wifi .
```

Specify your desired configuration and run the container:
```bash
$ docker run -<d|i> --rm \
-e ROUTER_USERNAME='admin' \
-e ROUTER_SSH_KEY='/app/ssh_key' \
-e ROUTER_WIFI_INTERFACE='wl0.1' \
-e TZ='Europe/Bucharest' \
-v /host/path/to/ssh_key:/app/ssh_key:ro \
-p <host_port>:80 \
--name guest-wifi \
vrachieru/guest-wifi
```

You can stop the container using:
```bash
$ docker stop guest-wifi
```


### Configuration

You can configure the service via the following environment variables.

| Environment Variable | Default Value | Description |
| --------------------- | ------------- | ----------- |
| ROUTER_IP | 192.168.1.1 | IP address of the router |
| ROUTER_SSH_PORT | 22 | Port on which the ssh daemon is running. |
| ROUTER_SSH_KEY | - | Ssh key to use while connecting to router. Note that if set it takes precedence over password login |
| ROUTER_USERNAME | admin | Username to use while connecting over ssh |
| ROUTER_PASSWORD | admin | Password to use while connecting over ssh (if you have enabled password login) |
| 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 |
| 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 |
| 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 |
| TZ | UTC | Timezone of the container


### Compatibility

Current implementation is based on `AsusWRT` routers although it may work on devices from other vendors as well without any modifications.
Although providing your own custom implementation by extending the `Router` class shouldn't be much of a fuss.


### License

MIT
38 changes: 38 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from os import environ

from flask import Flask, render_template

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger

from router import AsusWRT
from qr import QrCode

app = Flask(__name__)

scheduler = BackgroundScheduler(daemon=True)
scheduler.start()

router = AsusWRT(
environ.get('ROUTER_IP', '192.168.1.1'),
int(environ.get('ROUTER_SSH_PORT', 22)),
environ.get('ROUTER_USERNAME', 'admin'),
environ.get('ROUTER_PASSWORD', 'admin'),
environ.get('ROUTER_SSH_KEY'),
environ.get('ROUTER_WIFI_INTERFACE', 'wl0.1')
)

@app.route('/')
def home():
connection_details = router.connection_details
valid_until = scheduler.get_job('reset_password').next_run_time
qr_code = QrCode(**connection_details)

return render_template('index.html', **connection_details, valid_until=valid_until, qr=qr_code.base64svg)

@scheduler.scheduled_job(id='reset_password', trigger=CronTrigger.from_crontab(environ.get('PASSWORD_RESET_CRON', '0 12 * * MON')))
def reset_password():
router.reset_password()

if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
32 changes: 32 additions & 0 deletions qr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from pyqrcode import create
from io import BytesIO
from base64 import b64encode

class QrCode:

def __init__(self, ssid, authentication_method, password, **kwargs):
self._qr = create('WIFI:S:{ssid};T:{authentication_method};P:{password};;'.format(
ssid = self._mecard_escape(ssid),
authentication_method = self._authentication_method(authentication_method),
password = self._mecard_escape(password)
))

def _authentication_method(self, authentication_method):
if authentication_method.lower() in ['wep']:
return 'WEP'
if authentication_method.lower() in ['wpa', 'wpa2', 'wpawpa2', 'psk', 'psk2', 'pskpsk2']:
return 'WPA'
return 'nopass'

def _mecard_escape(self, value):
value = value.replace('\\', '\\\\')
value = value.replace(';', '\\;')
value = value.replace(':', '\\:')
value = value.replace(',', '\\,')
return value

@property
def base64svg(self):
stream = BytesIO()
self._qr.svg(stream, scale=4)
return b64encode(stream.getvalue()).decode('utf-8')
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Flask==1.0.2
pexpect==4.6.0
PyQRCode==1.2.1
apscheduler==3.5.3
Loading

0 comments on commit 69e446f

Please sign in to comment.