Simplify webhook module: deduplicate PEM decoding, extract delivery_id, remove redundant state

- Reuse jwt_auth::decode_pem_env in read_github_private_key instead of duplicating base64/PEM logic
- Extract delivery_id once at top of webhook_handler instead of 3 separate times
- Remove redundant funnel_port field from WebhookManager (derive from listener.port())
- Gate parse_event_metadata behind debug log level to avoid full JSON parse on every webhook

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-08 12:45:38 -04:00
parent d838fda004
commit 2355cd573d
3 changed files with 28 additions and 35 deletions

View file

@ -43,26 +43,23 @@ async fn webhook_handler(
headers: HeaderMap,
body: Bytes,
) -> StatusCode {
let delivery_id = headers
.get("x-github-delivery")
.and_then(|v| v.to_str().ok())
.unwrap_or("unknown");
let signature = match headers
.get("x-hub-signature-256")
.and_then(|v| v.to_str().ok())
{
Some(s) => s,
None => {
let delivery_id = headers
.get("x-github-delivery")
.and_then(|v| v.to_str().ok())
.unwrap_or("unknown");
warn!(delivery = %delivery_id, "Webhook signature verification failed");
return StatusCode::UNAUTHORIZED;
}
};
if !verify_signature(&state.secret, &body, signature) {
let delivery_id = headers
.get("x-github-delivery")
.and_then(|v| v.to_str().ok())
.unwrap_or("unknown");
warn!(delivery = %delivery_id, "Webhook signature verification failed");
return StatusCode::UNAUTHORIZED;
}
@ -71,20 +68,23 @@ async fn webhook_handler(
.get("x-github-event")
.and_then(|v| v.to_str().ok())
.unwrap_or("unknown");
let delivery_id = headers
.get("x-github-delivery")
.and_then(|v| v.to_str().ok())
.unwrap_or("unknown");
let (repo, action) = parse_event_metadata(&body);
debug!(
event = %event_type,
delivery = %delivery_id,
repo = %repo,
action = %action,
"Webhook received"
);
if tracing::enabled!(tracing::Level::DEBUG) {
let (repo, action) = parse_event_metadata(&body);
debug!(
event = %event_type,
delivery = %delivery_id,
repo = %repo,
action = %action,
"Webhook received"
);
} else {
info!(
event = %event_type,
delivery = %delivery_id,
"Webhook received"
);
}
StatusCode::OK
}
@ -151,7 +151,6 @@ pub async fn spawn_webhook_listener(secret: Vec<u8>) -> anyhow::Result<WebhookLi
/// Manage the full webhook lifecycle: listener + tailscale funnel + GitHub API.
pub struct WebhookManager {
listener: WebhookListener,
funnel_port: u16,
}
impl WebhookManager {
@ -188,15 +187,12 @@ impl WebhookManager {
info!(url = %webhook_url, "GitHub App webhook URL updated");
Ok(Self {
listener,
funnel_port: port,
})
Ok(Self { listener })
}
/// Shut down: disable funnel, stop listener.
pub async fn shutdown(self) {
disable_tailscale_funnel(self.funnel_port).await;
disable_tailscale_funnel(self.listener.port()).await;
self.listener.shutdown();
}
}

View file

@ -53,7 +53,7 @@ pub enum AuthMode {
pub struct PeerCertificates(pub Option<Vec<CertificateDer<'static>>>);
/// Decode a PEM env var that may be raw PEM or base64-encoded PEM.
fn decode_pem_env(name: &str, value: &str) -> String {
pub fn decode_pem_env(name: &str, value: &str) -> String {
if value.starts_with("-----") {
return value.to_string();
}

View file

@ -307,13 +307,10 @@ fn resolve_model_provider(
/// Read the GitHub App private key from the environment, decoding base64 if needed.
fn read_github_private_key() -> Option<String> {
let raw = std::env::var("GITHUB_APP_PRIVATE_KEY").ok()?;
if raw.starts_with("-----") {
Some(raw)
} else {
let pem_bytes =
base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &raw).ok()?;
String::from_utf8(pem_bytes).ok()
}
Some(crate::jwt_auth::decode_pem_env(
"GITHUB_APP_PRIVATE_KEY",
&raw,
))
}
/// Derive client certificate verification mode from the resolved auth strategies.