webhooks: use logging, add --debug and --unsafe for testing, split functions by logical blocks
This commit is contained in:
parent
00a3fbae21
commit
59172cd232
2 changed files with 51 additions and 22 deletions
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue