From f591f43e656ab65bb4597065b49a01fa630e20f8 Mon Sep 17 00:00:00 2001 From: ErenAri Date: Sun, 28 Jun 2026 19:24:58 +0300 Subject: [PATCH] chore(deploy): Cloudflare Tunnel on-ramp for the Marketplace webhook Make the GitHub Marketplace webhook reachable at api.kernelguard.net without the heavy KVM demo host or open inbound ports. - packaging/cloudflared/config.yml.example: tunnel ingress -> 127.0.0.1:8080 - scripts/cloudflared-setup.sh: login, create tunnel, route DNS, write config, install the cloudflared service; prints verify curls - packaging/systemd/bpfcompat-serve.env.example: document the webhook secret var - docs/github-marketplace.md: "Hosting via Cloudflare Tunnel" runbook No application code change; deploy/docs only. Co-Authored-By: Claude Opus 4.8 --- docs/github-marketplace.md | 34 ++++++++ packaging/cloudflared/config.yml.example | 22 ++++++ packaging/systemd/bpfcompat-serve.env.example | 7 ++ scripts/cloudflared-setup.sh | 77 +++++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100644 packaging/cloudflared/config.yml.example create mode 100755 scripts/cloudflared-setup.sh diff --git a/docs/github-marketplace.md b/docs/github-marketplace.md index 5ada0dd..6d99fa3 100644 --- a/docs/github-marketplace.md +++ b/docs/github-marketplace.md @@ -35,6 +35,40 @@ On the GitHub side (listing → *Webhook*): - **Secret:** the same value as the env var above - **SSL verification:** enabled +## Hosting via Cloudflare Tunnel + +The webhook is pure ingestion and does **not** need the KVM/QEMU demo host — it +can run on any always-on Linux box. Because `kernelguard.net` is already on +Cloudflare, the lowest-friction way to publish `api.kernelguard.net` is a +Cloudflare Tunnel: the box dials *out* to Cloudflare, so there are no inbound +ports to open, no public IP, and no Let's Encrypt on the box (TLS terminates at +the Cloudflare edge). + +On the box that runs `bpfcompat serve` (bound to `127.0.0.1:8080`): + +```bash +# 1. Make sure the server is up with the secret set: +# /etc/bpfcompat/serve.env -> BPFCOMPAT_GITHUB_MARKETPLACE_WEBHOOK_SECRET= +# sudo systemctl restart bpfcompat-serve + +# 2. Install cloudflared, then run the helper (interactive browser login): +./scripts/cloudflared-setup.sh +``` + +`scripts/cloudflared-setup.sh` logs in, creates a `bpfcompat-api` tunnel, routes +`api.kernelguard.net` to it (creating the proxied CNAME in Cloudflare DNS), +writes `/etc/cloudflared/config.yml` from +`packaging/cloudflared/config.yml.example`, and installs the `cloudflared` +system service. After it finishes: + +```bash +curl -i https://api.kernelguard.net/livez # 200 = tunnel + server up +curl -i -X POST https://api.kernelguard.net/github/marketplace/webhook # 401 = webhook live & verifying +``` + +Then on the GitHub listing webhook page → **Recent Deliveries → Redeliver** the +ping; expect a `200`. + ## Response contract | Status | When | diff --git a/packaging/cloudflared/config.yml.example b/packaging/cloudflared/config.yml.example new file mode 100644 index 0000000..f216155 --- /dev/null +++ b/packaging/cloudflared/config.yml.example @@ -0,0 +1,22 @@ +# cloudflared (Cloudflare Tunnel) config for the bpfcompat API host. +# +# Publishes api.kernelguard.net through Cloudflare's edge and tunnels requests +# to the local bpfcompat serve process on 127.0.0.1:8080 — no open inbound +# ports, no public IP, no Let's Encrypt on the box (Cloudflare terminates TLS +# at the edge). The tunnel dials OUT to Cloudflare, so it works behind NAT. +# +# Copy to /etc/cloudflared/config.yml and replace the two placeholders with the +# values printed by `cloudflared tunnel create bpfcompat-api` (see +# docs/github-marketplace.md → "Hosting via Cloudflare Tunnel"). + +# The tunnel UUID (or name) from `cloudflared tunnel create`. +tunnel: REPLACE_WITH_TUNNEL_UUID +# The credentials file written by that same command. +credentials-file: /etc/cloudflared/REPLACE_WITH_TUNNEL_UUID.json + +# Route the public hostname to the local bpfcompat server. The catch-all 404 +# rule is required and must be last. +ingress: + - hostname: api.kernelguard.net + service: http://127.0.0.1:8080 + - service: http_status:404 diff --git a/packaging/systemd/bpfcompat-serve.env.example b/packaging/systemd/bpfcompat-serve.env.example index ee50fcb..c4c7377 100644 --- a/packaging/systemd/bpfcompat-serve.env.example +++ b/packaging/systemd/bpfcompat-serve.env.example @@ -21,6 +21,13 @@ BPFCOMPAT_API_REDACT_RUNTIME_DETAILS=true # Do not mirror demo runs into a cloud registry. BPFCOMPAT_API_AUTO_SYNC_REGISTRY=false +# GitHub Marketplace purchase webhook (/github/marketplace/webhook). +# Set this to the SAME secret configured on the Marketplace listing's webhook so +# deliveries verify (HMAC-SHA256). It must match the GitHub value byte-for-byte. +# Leave unset to keep the endpoint disabled (it returns 503, never runs open). +# See docs/github-marketplace.md. +BPFCOMPAT_GITHUB_MARKETPLACE_WEBHOOK_SECRET= + # Path to the bundled "Try our aegis sample" artifact. The default # (examples/aegis-live/aegis.bpf.o, relative to WorkingDirectory) is git-ignored, # so on a fresh deployment copy the object out-of-band and point this at it (see diff --git a/scripts/cloudflared-setup.sh b/scripts/cloudflared-setup.sh new file mode 100755 index 0000000..564198f --- /dev/null +++ b/scripts/cloudflared-setup.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# Set up a Cloudflare Tunnel that publishes api.kernelguard.net and forwards to +# a local bpfcompat serve process (127.0.0.1:8080). Run this ON the box that +# runs `bpfcompat serve`. It is intentionally interactive at the login step +# (browser auth) and otherwise idempotent-ish — re-running reuses an existing +# tunnel of the same name. +# +# Prereqs on the box: +# - cloudflared installed (https://pkg.cloudflare.com / GitHub releases) +# - bpfcompat serve running on 127.0.0.1:8080 with +# BPFCOMPAT_GITHUB_MARKETPLACE_WEBHOOK_SECRET set (see +# packaging/systemd/bpfcompat-serve.env.example) +# +# Usage: +# ./scripts/cloudflared-setup.sh +# +# Override the defaults via env: +# TUNNEL_NAME (default: bpfcompat-api) +# HOSTNAME (default: api.kernelguard.net) +# BACKEND (default: http://127.0.0.1:8080) +set -euo pipefail + +TUNNEL_NAME="${TUNNEL_NAME:-bpfcompat-api}" +HOSTNAME="${HOSTNAME:-api.kernelguard.net}" +BACKEND="${BACKEND:-http://127.0.0.1:8080}" +CFG_DIR="/etc/cloudflared" + +if ! command -v cloudflared >/dev/null 2>&1; then + echo "[cloudflared-setup] cloudflared not found — install it first:" >&2 + echo " https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/" >&2 + exit 1 +fi + +echo "[cloudflared-setup] 1/5 authenticating to Cloudflare (pick the kernelguard.net zone in the browser)" +cloudflared tunnel login + +echo "[cloudflared-setup] 2/5 creating tunnel ${TUNNEL_NAME} (reused if it already exists)" +if ! cloudflared tunnel list | awk '{print $2}' | grep -qx "${TUNNEL_NAME}"; then + cloudflared tunnel create "${TUNNEL_NAME}" +fi + +TUNNEL_ID="$(cloudflared tunnel list | awk -v n="${TUNNEL_NAME}" '$2==n {print $1}' | head -n1)" +if [[ -z "${TUNNEL_ID}" ]]; then + echo "[cloudflared-setup] could not resolve tunnel id for ${TUNNEL_NAME}" >&2 + exit 1 +fi +echo "[cloudflared-setup] tunnel id: ${TUNNEL_ID}" + +echo "[cloudflared-setup] 3/5 routing DNS ${HOSTNAME} -> tunnel (creates the proxied CNAME in Cloudflare)" +cloudflared tunnel route dns "${TUNNEL_NAME}" "${HOSTNAME}" + +echo "[cloudflared-setup] 4/5 writing ${CFG_DIR}/config.yml" +sudo mkdir -p "${CFG_DIR}" +# The credentials file is created by `tunnel create` under ~/.cloudflared; move +# it where the system service can read it. +CRED_SRC="${HOME}/.cloudflared/${TUNNEL_ID}.json" +if [[ -f "${CRED_SRC}" ]]; then + sudo cp "${CRED_SRC}" "${CFG_DIR}/${TUNNEL_ID}.json" + sudo chmod 0600 "${CFG_DIR}/${TUNNEL_ID}.json" +fi +sudo tee "${CFG_DIR}/config.yml" >/dev/null <