mirror of
https://github.com/FAUSheppy/atlantis-event-dispatcher
synced 2025-12-08 23:38:32 +01:00
Compare commits
19 Commits
85f72290b8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e4f53b330 | |||
| 35f9fc2a99 | |||
| 53e6f32a18 | |||
| f2137e7e4c | |||
| c577802e63 | |||
| 2a74d9816f | |||
| 178ba5451d | |||
| 14612016af | |||
| df1dfd8b0c | |||
| acf88ffa6e | |||
| 7fa965a92c | |||
| e416149d35 | |||
| 181b3dae14 | |||
| c10bdf1fb7 | |||
| 6783426e5f | |||
| bc837169ff | |||
| 6e2e5e73da | |||
| 2305bc9789 | |||
| cdb4a8aeb9 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,3 +5,5 @@ instance/
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
signal_targets.txt
|
signal_targets.txt
|
||||||
sqlite.db
|
sqlite.db
|
||||||
|
substitutions.yaml
|
||||||
|
test.env
|
||||||
|
|||||||
@@ -54,15 +54,32 @@ def ntfy_api_get_topic(ntfy_api_server, ntfy_api_token, username):
|
|||||||
print(r.text)
|
print(r.text)
|
||||||
return r.json().get("topic")
|
return r.json().get("topic")
|
||||||
|
|
||||||
def ntfy_send(dispatch_uuid, user_topic, title, message, ntfy_push_target, ntfy_user, ntfy_pass):
|
def ntfy_send(dispatch_uuid, user_topic, title, message, link,
|
||||||
|
ntfy_push_target, ntfy_user, ntfy_pass):
|
||||||
'''Send message via NTFY topic'''
|
'''Send message via NTFY topic'''
|
||||||
|
|
||||||
|
# check message for links #
|
||||||
|
if not link:
|
||||||
|
pattern = r"https:\/\/[^\s]+"
|
||||||
|
match = re.search(pattern, text)
|
||||||
|
if match:
|
||||||
|
link = match.group(0)
|
||||||
|
|
||||||
|
# limit message length and title #
|
||||||
|
title = title or ""
|
||||||
|
message = message or ""
|
||||||
|
message = message[:1024]
|
||||||
|
title = title[:512]
|
||||||
|
|
||||||
|
|
||||||
if not user_topic:
|
if not user_topic:
|
||||||
report_failed_dispatch(dispatch_uuid, "No user topic")
|
report_failed_dispatch(dispatch_uuid, "No user topic")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# build message #
|
# build message #
|
||||||
payload = {
|
payload = {
|
||||||
"topic" : user_topic,
|
"topic" : user_topic,
|
||||||
@@ -71,13 +88,16 @@ def ntfy_send(dispatch_uuid, user_topic, title, message, ntfy_push_target, ntfy_
|
|||||||
#"tags" : [],
|
#"tags" : [],
|
||||||
"priority" : 4,
|
"priority" : 4,
|
||||||
#"attach" : None,
|
#"attach" : None,
|
||||||
"click" : "https://vid.pr0gramm.com/2022/11/06/ed66c8c5a9cd1a3b.mp4",
|
"click" : link,
|
||||||
#"actions" : []
|
#"actions" : []
|
||||||
}
|
}
|
||||||
|
|
||||||
# send #
|
# send #
|
||||||
r = requests.post(ntfy_push_target, auth=(ntfy_user, ntfy_pass), json=payload)
|
r = requests.post(ntfy_push_target, auth=(ntfy_user, ntfy_pass), json=payload)
|
||||||
print(r.status_code, r.text, payload)
|
print(r.status_code, r.text, payload)
|
||||||
|
if r.status_code == 429: # rate-limit
|
||||||
|
time.sleep(60)
|
||||||
|
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
# talk to dispatch #
|
# talk to dispatch #
|
||||||
@@ -181,6 +201,7 @@ if __name__ == "__main__":
|
|||||||
method = entry["method"]
|
method = entry["method"]
|
||||||
message = entry["message"]
|
message = entry["message"]
|
||||||
title = entry.get("title")
|
title = entry.get("title")
|
||||||
|
link = entry.get("link")
|
||||||
|
|
||||||
# method dependent fields #
|
# method dependent fields #
|
||||||
phone = entry.get("phone")
|
phone = entry.get("phone")
|
||||||
@@ -191,7 +212,7 @@ if __name__ == "__main__":
|
|||||||
pass
|
pass
|
||||||
elif method == "ntfy":
|
elif method == "ntfy":
|
||||||
user_topic = ntfy_api_get_topic(ntfy_api_server, ntfy_api_token, user)
|
user_topic = ntfy_api_get_topic(ntfy_api_server, ntfy_api_token, user)
|
||||||
ntfy_send(dispatch_uuid, user_topic, title, message,
|
ntfy_send(dispatch_uuid, user_topic, title, message, link,
|
||||||
ntfy_push_target, ntfy_user, ntfy_pass)
|
ntfy_push_target, ntfy_user, ntfy_pass)
|
||||||
elif method == "email":
|
elif method == "email":
|
||||||
email_send(dispatch_uuid, email_address, message, smtp_target,
|
email_send(dispatch_uuid, email_address, message, smtp_target,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ RUN python3 -m pip install --no-cache-dir --break-system-packages -r req.txt
|
|||||||
|
|
||||||
# precreate database directory for mount (will otherwise be created at before_first_request)
|
# precreate database directory for mount (will otherwise be created at before_first_request)
|
||||||
COPY ./ .
|
COPY ./ .
|
||||||
RUN mkdir /app/instance/
|
RUN mkdir -p /app/instance/
|
||||||
|
|
||||||
EXPOSE 5000/tcp
|
EXPOSE 5000/tcp
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import subprocess
|
|||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
import secrets
|
import secrets
|
||||||
|
import yaml
|
||||||
|
|
||||||
import ldaptools
|
import ldaptools
|
||||||
import messagetools
|
import messagetools
|
||||||
@@ -19,7 +20,7 @@ import sqlalchemy
|
|||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from sqlalchemy.sql.expression import func
|
from sqlalchemy.sql.expression import func
|
||||||
|
|
||||||
|
OPENSEARCH_HEADER_SEPERATOR = ","
|
||||||
HOST = "icinga.atlantishq.de"
|
HOST = "icinga.atlantishq.de"
|
||||||
app = flask.Flask("Signal Notification Gateway")
|
app = flask.Flask("Signal Notification Gateway")
|
||||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///sqlite.db"
|
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///sqlite.db"
|
||||||
@@ -27,6 +28,16 @@ db = SQLAlchemy(app)
|
|||||||
|
|
||||||
BAD_DISPATCH_ACCESS_TOKEN = "Invalid or missing dispatch-access-token parameter in URL"
|
BAD_DISPATCH_ACCESS_TOKEN = "Invalid or missing dispatch-access-token parameter in URL"
|
||||||
|
|
||||||
|
def _apply_substitution(string):
|
||||||
|
|
||||||
|
if not string:
|
||||||
|
return string
|
||||||
|
|
||||||
|
for replace, match in app.config["SUBSTITUTIONS"].items():
|
||||||
|
string = string.replace(match, replace)
|
||||||
|
|
||||||
|
return string
|
||||||
|
|
||||||
class WebHookPaths(db.Model):
|
class WebHookPaths(db.Model):
|
||||||
|
|
||||||
__tablename__ = "webhook_paths"
|
__tablename__ = "webhook_paths"
|
||||||
@@ -73,6 +84,7 @@ class DispatchObject(db.Model):
|
|||||||
title = Column(String)
|
title = Column(String)
|
||||||
message = Column(String, primary_key=True)
|
message = Column(String, primary_key=True)
|
||||||
method = Column(String)
|
method = Column(String)
|
||||||
|
link = Column(String)
|
||||||
|
|
||||||
dispatch_secret = Column(String)
|
dispatch_secret = Column(String)
|
||||||
dispatch_error = Column(String)
|
dispatch_error = Column(String)
|
||||||
@@ -85,8 +97,9 @@ class DispatchObject(db.Model):
|
|||||||
"timestamp" : self.timestamp,
|
"timestamp" : self.timestamp,
|
||||||
"phone" : self.phone,
|
"phone" : self.phone,
|
||||||
"email" : self.email,
|
"email" : self.email,
|
||||||
"title" : self.title,
|
"title" : _apply_substitution(self.title),
|
||||||
"message" : self.message,
|
"message" : _apply_substitution(self.message),
|
||||||
|
"link" : self.link,
|
||||||
"uuid" : self.dispatch_secret,
|
"uuid" : self.dispatch_secret,
|
||||||
"method" : self.method,
|
"method" : self.method,
|
||||||
"error" : self.dispatch_error,
|
"error" : self.dispatch_error,
|
||||||
@@ -158,6 +171,31 @@ def webhooks():
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
return ("", 204)
|
return ("", 204)
|
||||||
|
|
||||||
|
@app.route('/downtime', methods=["GET", "DELETE","POST"])
|
||||||
|
def downtime():
|
||||||
|
|
||||||
|
# check static access token #
|
||||||
|
token = flask.request.args.get("token")
|
||||||
|
if token != app.config["SETTINGS_ACCESS_TOKEN"]:
|
||||||
|
return ("SETTINGS_ACCESS_TOKEN incorrect. Refusing to access downtime settings", 401)
|
||||||
|
|
||||||
|
if flask.request.method == "DELETE":
|
||||||
|
app.config["DOWNTIME"] = datetime.datetime.now()
|
||||||
|
return ('Downtime successfully disabled', 200)
|
||||||
|
elif flask.request.method == "POST":
|
||||||
|
minutes = int(flask.request.args.get("minutes") or 5)
|
||||||
|
app.config["DOWNTIME"] = datetime.datetime.now() + datetime.timedelta(minutes=minutes)
|
||||||
|
return ('Downtime set to {}'.format(app.config["DOWNTIME"].isoformat(), 204))
|
||||||
|
elif flask.request.method == "GET":
|
||||||
|
dt = app.config["DOWNTIME"]
|
||||||
|
if dt < datetime.datetime.now():
|
||||||
|
return flask.jsonify({"title" : "No Downtime set at the moment", "message" : ""})
|
||||||
|
else:
|
||||||
|
delta = int((dt - datetime.datetime.now()).total_seconds()/60)
|
||||||
|
return flask.jsonify({"title" : "Downtime set for {}m until {}".format(delta, dt.isoformat()),
|
||||||
|
"message" : ""})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/settings', methods=["GET", "POST"])
|
@app.route('/settings', methods=["GET", "POST"])
|
||||||
def settings():
|
def settings():
|
||||||
|
|
||||||
@@ -321,16 +359,42 @@ def smart_send_to_clients(path=None):
|
|||||||
- supported struct of type "ICINGA|ZABBIX|GENERIC" (see docs) in field "data"
|
- supported struct of type "ICINGA|ZABBIX|GENERIC" (see docs) in field "data"
|
||||||
'''
|
'''
|
||||||
|
|
||||||
instructions = flask.request.json
|
if flask.request.headers.get("opensearch"):
|
||||||
|
|
||||||
users = instructions.get("users")
|
instructions = {}
|
||||||
groups = instructions.get("groups")
|
users = flask.request.headers.get("opensearch-users")
|
||||||
message = instructions.get("msg")
|
groups = flask.request.headers.get("opensearch-groups")
|
||||||
title = instructions.get("title")
|
|
||||||
method = instructions.get("method")
|
if groups and OPENSEARCH_HEADER_SEPERATOR in groups:
|
||||||
|
groups = groups.split(OPENSEARCH_HEADER_SEPERATOR)
|
||||||
|
|
||||||
|
if users and OPENSEARCH_HEADER_SEPERATOR in users:
|
||||||
|
users = users.split(OPENSEARCH_HEADER_SEPERATOR)
|
||||||
|
|
||||||
|
message = flask.request.get_data(as_text=True)
|
||||||
|
title = "Opensearch Alert"
|
||||||
|
method = None
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
instructions = flask.request.json
|
||||||
|
users = instructions.get("users")
|
||||||
|
groups = instructions.get("groups")
|
||||||
|
message = instructions.get("msg") or instructions.get("message")
|
||||||
|
title = instructions.get("title")
|
||||||
|
method = instructions.get("method")
|
||||||
|
link = instructions.get("link")
|
||||||
|
|
||||||
|
if app.config["DOWNTIME"] > datetime.datetime.now():
|
||||||
|
print("Ignoring because of Downtime:", title, message, users, file=sys.stderr)
|
||||||
|
print("Downtime until", app.config["DOWNTIME"].isoformat(), file=sys.stderr)
|
||||||
|
return ("Ignored because of Downtime", 200)
|
||||||
|
|
||||||
# authenticated by access token or webhook path #
|
# authenticated by access token or webhook path #
|
||||||
dispatch_acces_token = flask.request.args.get("dispatch-access-token") or ""
|
dispatch_acces_token = flask.request.args.get("dispatch-access-token") or ""
|
||||||
|
if not dispatch_acces_token:
|
||||||
|
dispatch_acces_token = flask.request.headers.get("Dispatcher-Token") or ""
|
||||||
|
|
||||||
print(path)
|
print(path)
|
||||||
if path:
|
if path:
|
||||||
webhook_path = db.session.query(WebHookPaths).filter(WebHookPaths.path==path).first()
|
webhook_path = db.session.query(WebHookPaths).filter(WebHookPaths.path==path).first()
|
||||||
@@ -360,7 +424,7 @@ def smart_send_to_clients(path=None):
|
|||||||
else:
|
else:
|
||||||
persons = ldaptools.select_targets(users, groups, app.config["LDAP_ARGS"])
|
persons = ldaptools.select_targets(users, groups, app.config["LDAP_ARGS"])
|
||||||
|
|
||||||
dispatch_secrets = save_in_dispatch_queue(persons, title, message, method)
|
dispatch_secrets = save_in_dispatch_queue(persons, title, message, method, link)
|
||||||
return flask.jsonify(dispatch_secrets)
|
return flask.jsonify(dispatch_secrets)
|
||||||
|
|
||||||
|
|
||||||
@@ -384,6 +448,7 @@ def save_in_dispatch_queue(persons, title, message, method):
|
|||||||
timestamp=datetime.datetime.now().timestamp(),
|
timestamp=datetime.datetime.now().timestamp(),
|
||||||
dispatch_secret=dispatch_secret,
|
dispatch_secret=dispatch_secret,
|
||||||
title=title,
|
title=title,
|
||||||
|
link=link,
|
||||||
message=message)
|
message=message)
|
||||||
|
|
||||||
db.session.merge(obj)
|
db.session.merge(obj)
|
||||||
@@ -414,6 +479,17 @@ def create_app():
|
|||||||
app.config["SETTINGS_ACCESS_TOKEN"] = os.environ["SETTINGS_ACCESS_TOKEN"]
|
app.config["SETTINGS_ACCESS_TOKEN"] = os.environ["SETTINGS_ACCESS_TOKEN"]
|
||||||
app.config["DISPATCH_ACCESS_TOKEN"] = os.environ["DISPATCH_ACCESS_TOKEN"]
|
app.config["DISPATCH_ACCESS_TOKEN"] = os.environ["DISPATCH_ACCESS_TOKEN"]
|
||||||
|
|
||||||
|
substitution_config_file = os.environ.get("SUBSTITUTION_MAP") or "substitutions.yaml"
|
||||||
|
app.config["SUBSTITUTIONS"] = {}
|
||||||
|
if os.path.isfile(substitution_config_file):
|
||||||
|
with open(substitution_config_file) as f:
|
||||||
|
app.config["SUBSTITUTIONS"] = yaml.safe_load(f)
|
||||||
|
|
||||||
|
print("Loaded subs:", substitution_config_file, app.config["SUBSTITUTIONS"], file=sys.stderr)
|
||||||
|
|
||||||
|
# set small downtime #
|
||||||
|
app.config["DOWNTIME"] = datetime.datetime.now() + datetime.timedelta(minutes=1)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Simple Telegram Notification Interface',
|
parser = argparse.ArgumentParser(description='Simple Telegram Notification Interface',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
python-ldap
|
python-ldap
|
||||||
|
pyyaml
|
||||||
flask
|
flask
|
||||||
flask-sqlalchemy
|
flask-sqlalchemy
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
|
|||||||
Reference in New Issue
Block a user