Improve webhook signature handling

This commit is contained in:
AppFactory Bot
2026-05-27 13:13:07 +02:00
parent 0d68accb40
commit 3b8a6423c2
+19 -15
View File
@@ -13,7 +13,6 @@ def read_env_file_value(key: str, default: str = "") -> str:
with open("/opt/appfactory/config/appfactory.env", "r", encoding="utf-8") as f: with open("/opt/appfactory/config/appfactory.env", "r", encoding="utf-8") as f:
for line in f: for line in f:
line = line.strip() line = line.strip()
if line.startswith(f"{key}="): if line.startswith(f"{key}="):
return line.split("=", 1)[1].strip().strip('"') return line.split("=", 1)[1].strip().strip('"')
except Exception: except Exception:
@@ -24,7 +23,7 @@ def read_env_file_value(key: str, default: str = "") -> str:
WEBHOOK_SECRET = os.getenv( WEBHOOK_SECRET = os.getenv(
"WEBHOOK_SECRET", "WEBHOOK_SECRET",
read_env_file_value("WEBHOOK_SECRET", "change-me-webhook-secret") read_env_file_value("WEBHOOK_SECRET", "")
) )
APP_DEPLOY_SCRIPT = "/tools/deploy-app.sh" APP_DEPLOY_SCRIPT = "/tools/deploy-app.sh"
@@ -35,7 +34,6 @@ CORE_SERVICES = {
"appfactory-webhook", "appfactory-webhook",
} }
app = FastAPI(title="AppFactory Webhook") app = FastAPI(title="AppFactory Webhook")
@@ -45,6 +43,9 @@ def health():
def verify_signature(body: bytes, signature: str | None): def verify_signature(body: bytes, signature: str | None):
if not WEBHOOK_SECRET:
raise HTTPException(status_code=500, detail="Webhook secret is not configured")
if not signature: if not signature:
raise HTTPException(status_code=401, detail="Missing signature") raise HTTPException(status_code=401, detail="Missing signature")
@@ -54,12 +55,17 @@ def verify_signature(body: bytes, signature: str | None):
hashlib.sha256 hashlib.sha256
).hexdigest() ).hexdigest()
valid_signatures = { accepted = {
digest, digest,
f"sha256={digest}", f"sha256={digest}",
} }
if not any(hmac.compare_digest(signature, valid) for valid in valid_signatures): signature = signature.strip()
if not any(hmac.compare_digest(signature, item) for item in accepted):
print("Invalid signature", flush=True)
print(f"Received prefix: {signature[:16]}", flush=True)
print(f"Expected prefix: {digest[:16]}", flush=True)
raise HTTPException(status_code=401, detail="Invalid signature") raise HTTPException(status_code=401, detail="Invalid signature")
@@ -67,11 +73,7 @@ def run_command(command: list[str], repo_name: str):
print(f"Deploy started: {repo_name}", flush=True) print(f"Deploy started: {repo_name}", flush=True)
print(f"Command: {' '.join(command)}", flush=True) print(f"Command: {' '.join(command)}", flush=True)
result = subprocess.run( result = subprocess.run(command, capture_output=True, text=True)
command,
capture_output=True,
text=True
)
print(f"Deploy finished: {repo_name}", flush=True) print(f"Deploy finished: {repo_name}", flush=True)
print(f"Return code: {result.returncode}", flush=True) print(f"Return code: {result.returncode}", flush=True)
@@ -89,13 +91,16 @@ def run_command(command: list[str], repo_name: str):
async def gitea_webhook( async def gitea_webhook(
request: Request, request: Request,
x_gitea_signature: str | None = Header(default=None), x_gitea_signature: str | None = Header(default=None),
x_hub_signature: str | None = Header(default=None),
x_hub_signature_256: str | None = Header(default=None),
): ):
body = await request.body() body = await request.body()
verify_signature(body, x_gitea_signature) signature = x_gitea_signature or x_hub_signature_256 or x_hub_signature
verify_signature(body, signature)
payload = json.loads(body.decode("utf-8")) payload = json.loads(body.decode("utf-8"))
repo_name = payload.get("repository", {}).get("name") repo_name = payload.get("repository", {}).get("name")
if not repo_name: if not repo_name:
@@ -106,12 +111,11 @@ async def gitea_webhook(
else: else:
command = [APP_DEPLOY_SCRIPT, repo_name] command = [APP_DEPLOY_SCRIPT, repo_name]
thread = threading.Thread( threading.Thread(
target=run_command, target=run_command,
args=(command, repo_name), args=(command, repo_name),
daemon=True, daemon=True,
) ).start()
thread.start()
return { return {
"status": "accepted", "status": "accepted",