From a747ed293ec4449ca15271f9476903905dbbfaf5 Mon Sep 17 00:00:00 2001 From: jaseg Date: Fri, 17 Jan 2020 11:11:52 +0100 Subject: deploy: add notification proxy --- .gitignore | 1 + nginx.conf | 38 ++++++++++++++++++ notification_proxy.py | 89 +++++++++++++++++++++++++++++++++++++++++ notification_proxy_config.py.j2 | 5 +++ playbook.yml | 3 ++ setup_notification_proxy.yml | 48 ++++++++++++++++++++++ setup_webserver.yml | 2 + uwsgi-notification-proxy.ini | 10 +++++ 8 files changed, 196 insertions(+) create mode 100644 notification_proxy.py create mode 100644 notification_proxy_config.py.j2 create mode 100644 setup_notification_proxy.yml create mode 100644 uwsgi-notification-proxy.ini diff --git a/.gitignore b/.gitignore index c3129a6..e681fba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *_secret.txt +*_apikey.txt playbook.retry diff --git a/nginx.conf b/nginx.conf index 8d21357..fb88cbd 100644 --- a/nginx.conf +++ b/nginx.conf @@ -129,6 +129,44 @@ http { } } + server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name automation.jaseg.de; + root /usr/share/nginx/html; + + ssl_certificate "/etc/letsencrypt/live/automation.jaseg.de/fullchain.pem"; + ssl_certificate_key "/etc/letsencrypt/live/automation.jaseg.de/privkey.pem"; + ssl_dhparam "/etc/letsencrypt/ssl-dhparams.pem"; + include /etc/letsencrypt/options-ssl-nginx.conf; + + ssl_stapling on; + ssl_stapling_verify on; + + resolver 67.207.67.2 67.207.67.3 valid=300s; + resolver_timeout 10s; + + add_header Strict-Transport-Security "max-age=86400"; + + # Load configuration files for the default server block. + include /etc/nginx/default.d/*.conf; + + location / { + include uwsgi_params; + uwsgi_pass unix:/run/uwsgi/notification-proxy.socket; + } + + error_page 404 /404.html; + location = /40x.html { + root /usr/share/nginx/html; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } + server { listen 443 ssl http2; listen [::]:443 ssl http2; diff --git a/notification_proxy.py b/notification_proxy.py new file mode 100644 index 0000000..ae4d73e --- /dev/null +++ b/notification_proxy.py @@ -0,0 +1,89 @@ +import smtplib +import ssl +import email.utils +import hmac +from email.mime.text import MIMEText +from datetime import datetime +import functools +import json +import binascii + +from flask import Flask, request, abort + +app = Flask(__name__) +app.config.from_pyfile('config.py') + +smtp_server = "smtp.sendgrid.net" +port = 465 + +mail_routes = {} +def mail_route(name, receiver, subject): + def wrap(func): + global routes + mail_routes[name] = (receiver, subject, func) + return func + return wrap + + +def authenticate(secret): + def wrap(func): + func.last_seqnum = 0 + @functools.wraps(func) + def wrapper(*args, **kwargs): + if not request.is_json: + abort(400) + + if not 'auth' in request.json and 'payload' in request.json: + abort(400) + + if not isinstance(request.json['auth'], str): + abort(400) + their_digest = binascii.unhexlify(request.json['auth']) + + our_digest = hmac.digest(secret.encode('utf-8'), request.json['payload'].encode('utf-8'), 'sha256') + if not hmac.compare_digest(their_digest, our_digest): + abort(403) + + try: + payload = json.loads(request.json['payload']) + except: + abort(400) + + if not isinstance(payload['seq'], int) or payload['seq'] <= func.last_seqnum: + abort(400) + + func.last_seqnum = payload['seq'] + del payload['seq'] + return func(payload) + return wrapper + return wrap + +@mail_route('klingel', 'computerstuff@jaseg.de', 'It rang!') +@authenticate(app.config['SECRET_KLINGEL']) +def klingel(_): + return f'Date: {datetime.utcnow().isoformat()}' + + +@app.route('/notify/', methods=['POST']) +def notify(route_name): + try: + context = ssl.create_default_context() + smtp = smtplib.SMTP_SSL(smtp_server, port) + smtp.login('apikey', app.config['SENDGRID_APIKEY']) + + sender = f'{route_name}@{app.config["DOMAIN"]}' + + receiver, subject, func = mail_routes[route_name] + + msg = MIMEText(func() or subject) + msg['Subject'] = subject + msg['From'] = sender + msg['To'] = receiver + msg['Date'] = email.utils.formatdate() + smtp.sendmail(sender, receiver, msg.as_string()) + finally: + smtp.quit() + return 'success' + +if __name__ == '__main__': + app.run() diff --git a/notification_proxy_config.py.j2 b/notification_proxy_config.py.j2 new file mode 100644 index 0000000..ea53e34 --- /dev/null +++ b/notification_proxy_config.py.j2 @@ -0,0 +1,5 @@ + +SENDGRID_APIKEY = '{{lookup('file', 'notification_proxy_sendgrid_apikey.txt')}}' +DOMAIN = 'automation.jaseg.de' + +SECRET_KLINGEL = '{{lookup('password', 'notification_proxy_klingel_secret.txt length=32')}}' diff --git a/playbook.yml b/playbook.yml index 0d16d7f..2db45bc 100644 --- a/playbook.yml +++ b/playbook.yml @@ -71,3 +71,6 @@ - name: Setup pogojig include_tasks: setup_pogojig.yml + - name: Setup notification proxy + include_tasks: setup_notification_proxy.yml + diff --git a/setup_notification_proxy.yml b/setup_notification_proxy.yml new file mode 100644 index 0000000..3f86412 --- /dev/null +++ b/setup_notification_proxy.yml @@ -0,0 +1,48 @@ +--- +- name: Create notification proxy worker user and group + user: + name: uwsgi-notification-proxy + create_home: no + group: uwsgi + password: '!' + shell: /sbin/nologin + system: yes + +- name: Create webapp dir + file: + path: /var/lib/notification-proxy + state: directory + owner: uwsgi-notification-proxy + group: uwsgi + mode: 0550 + +- name: Copy webapp sources + copy: + src: notification_proxy.py + dest: /var/lib/notification-proxy/ + owner: uwsgi-notification-proxy + group: uwsgi + mode: 0440 + +- name: Template webapp config + template: + src: notification_proxy_config.py.j2 + dest: /var/lib/notification-proxy/config.py + owner: uwsgi-notification-proxy + group: root + mode: 0660 + +- name: Copy uwsgi config + copy: + src: uwsgi-notification-proxy.ini + dest: /etc/uwsgi.d/notification-proxy.ini + owner: uwsgi-notification-proxy + group: uwsgi + mode: 0440 + +- name: Enable uwsgi systemd socket + systemd: + daemon-reload: yes + name: uwsgi-app@notification-proxy.socket + enabled: yes + diff --git a/setup_webserver.yml b/setup_webserver.yml index 1405bed..8f1f429 100644 --- a/setup_webserver.yml +++ b/setup_webserver.yml @@ -21,6 +21,7 @@ - kochbuch.jaseg.net - tracespace.jaseg.net - openjscad.jaseg.net + - automation.jaseg.de - name: Copy uwsgi systemd socket config copy: @@ -54,6 +55,7 @@ - tracespace.jaseg.net - openjscad.jaseg.net - pogojig.jaseg.net + - automation.jaseg.de - name: Copy final nginx config copy: diff --git a/uwsgi-notification-proxy.ini b/uwsgi-notification-proxy.ini new file mode 100644 index 0000000..aab2b5a --- /dev/null +++ b/uwsgi-notification-proxy.ini @@ -0,0 +1,10 @@ +[uwsgi] +master = True +cheap = True +die-on-idle = False +manage-script-name = True +log-format = [pid: %(pid)|app: -|req: -/-] %(addr) (%(user)) {%(vars) vars in %(pktsize) bytes} [%(ctime)] %(method) [URI hidden] => generated %(rsize) bytes in %(msecs) msecs (%(proto) %(status)) %(headers) headers in %(hsize) bytes (%(switches) switches on core %(core)) +plugins = python3 +chdir = /var/lib/notification-proxy +mount = /=notification_proxy:app + -- cgit