1
0
Fork 0

webhooks: use logging, add --debug and --unsafe for testing, split functions by logical blocks

This commit is contained in:
Félix Piédallu 2024-06-01 10:26:23 +02:00 committed by Salamandar
parent 00a3fbae21
commit 59172cd232
2 changed files with 51 additions and 22 deletions

View file

@ -2,9 +2,11 @@
import sys import sys
import hashlib import hashlib
import argparse
import hmac import hmac
from functools import cache from functools import cache
import tempfile import tempfile
import logging
from pathlib import Path from pathlib import Path
from git import Actor, Repo from git import Actor, Repo
@ -17,18 +19,15 @@ from readme_generator.make_readme import generate_READMEs
TOOLS_DIR = Path(__file__).resolve().parent.parent TOOLS_DIR = Path(__file__).resolve().parent.parent
app = Sanic(__name__) DEBUG = False
UNSAFE = False
APP = Sanic(__name__)
@cache @cache
def github_webhook_secret() -> str: def github_webhook_secret() -> str:
return ( return (TOOLS_DIR / ".github_webhook_secret").open("r", encoding="utf-8").read().strip()
(TOOLS_DIR / ".github_webhook_secret")
.open("r", encoding="utf-8")
.read()
.strip()
)
@cache @cache
def github_login() -> str: def github_login() -> str:
@ -40,23 +39,35 @@ def github_token() -> str:
return (TOOLS_DIR / ".github_token").open("r", encoding="utf-8").read().strip() return (TOOLS_DIR / ".github_token").open("r", encoding="utf-8").read().strip()
@app.route("/github", methods=["GET"]) @APP.route("/github", methods=["GET"])
async def main_route(request: Request) -> HTTPResponse: async def github_get(request: Request) -> HTTPResponse:
return response.text( return response.text(
"You aren't supposed to go on this page using a browser, it's for webhooks push instead." "You aren't supposed to go on this page using a browser, it's for webhooks push instead."
) )
@app.route("/github", methods=["POST"]) @APP.route("/github", methods=["POST"])
async def on_push(request: Request) -> HTTPResponse: async def github_post(request: Request) -> HTTPResponse:
if UNSAFE and (signatures_reply := check_webhook_signatures(request)):
return signatures_reply
event = request.headers.get("X-Github-Event")
if event == "push":
return on_push(request)
return response.json({"error": f"Unknown event '{event}'"}, 422)
def check_webhook_signatures(request: Request) -> HTTPResponse | None:
logging.warning("Unsafe webhook!")
header_signature = request.headers.get("X-Hub-Signature") header_signature = request.headers.get("X-Hub-Signature")
if header_signature is None: if header_signature is None:
print("no header X-Hub-Signature") logging.error("no header X-Hub-Signature")
return response.json({"error": "No X-Hub-Signature"}, 403) return response.json({"error": "No X-Hub-Signature"}, 403)
sha_name, signature = header_signature.split("=") sha_name, signature = header_signature.split("=")
if sha_name != "sha1": if sha_name != "sha1":
print("signing algo isn't sha1, it's '%s'" % sha_name) logging.error("signing algo isn't sha1, it's '%s'" % sha_name)
return response.json({"error": "Signing algorightm is not sha1 ?!"}, 501) return response.json({"error": "Signing algorightm is not sha1 ?!"}, 501)
# HMAC requires the key to be bytes, but data is string # HMAC requires the key to be bytes, but data is string
@ -66,13 +77,14 @@ async def on_push(request: Request) -> HTTPResponse:
if not hmac.compare_digest(str(mac.hexdigest()), str(signature)): if not hmac.compare_digest(str(mac.hexdigest()), str(signature)):
return response.json({"error": "Bad signature ?!"}, 403) return response.json({"error": "Bad signature ?!"}, 403)
return None
def on_push(request: Request) -> HTTPResponse:
data = request.json data = request.json
repository = data["repository"]["full_name"] repository = data["repository"]["full_name"]
branch = data["ref"].split("/", 2)[2] branch = data["ref"].split("/", 2)[2]
print(f"{repository} -> branch '{branch}'") logging.info(f"{repository} -> branch '{branch}'")
with tempfile.TemporaryDirectory() as folder_str: with tempfile.TemporaryDirectory() as folder_str:
folder = Path(folder_str) folder = Path(folder_str)
@ -90,7 +102,7 @@ async def on_push(request: Request) -> HTTPResponse:
diff_empty = len(repo.index.diff("HEAD")) == 0 diff_empty = len(repo.index.diff("HEAD")) == 0
if diff_empty: if diff_empty:
print("nothing to do") logging.debug("nothing to do")
return response.text("nothing to do") return response.text("nothing to do")
repo.index.commit( repo.index.commit(
@ -101,5 +113,22 @@ async def on_push(request: Request) -> HTTPResponse:
return response.text("ok") return response.text("ok")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("-d", "--debug", action="store_true")
parser.add_argument("-u", "--unsafe", action="store_true",
help="Disable Github signature checks on webhooks, for debug only.")
args = parser.parse_args()
if args.debug:
logging.getLogger().setLevel(logging.DEBUG)
global DEBUG, UNSAFE
DEBUG = args.debug
UNSAFE = args.unsafe
APP.run(host="127.0.0.1", port=8123, debug=args.debug)
if __name__ == "__main__": if __name__ == "__main__":
app.run(host="127.0.0.1", port=8123, debug=True) main()

View file

@ -1,11 +1,11 @@
[Unit] [Unit]
Description=Auto-README webhook gunicorn daemon Description=Github webhooks for YunoHost-Apps management
After=network.target After=network.target
[Service] [Service]
PIDFile=/run/gunicorn/apps_webhooks-pid PIDFile=/run/gunicorn/yunohost_apps_webhooks-pid
User=apps_webhooks User=yunohost_apps_webhooks
Group=apps_webhooks Group=yunohost_apps_webhooks
WorkingDirectory=__PATH_TO_APPS_TOOLS__/webhooks WorkingDirectory=__PATH_TO_APPS_TOOLS__/webhooks
ExecStart=__PATH_TO_APPS_TOOLS__/webhooks/venv/bin/gunicorn -w 4 -b 127.0.0.1:8123 webhook:app ExecStart=__PATH_TO_APPS_TOOLS__/webhooks/venv/bin/gunicorn -w 4 -b 127.0.0.1:8123 webhook:app
ExecReload=/bin/kill -s HUP $MAINPID ExecReload=/bin/kill -s HUP $MAINPID