mirror of
https://github.com/block/buzz.git
synced 2026-10-30 22:11:36 +00:00
feat(relay): route git pre-receive policy callback over UDS when configured
The pre-receive hook curls the relay's own /internal/git/policy callback. By
default it targets the TCP loopback 127.0.0.1:{bind_port}. In a mesh deployment
(e.g. Istio) that loopback port can be iptables-captured by the sidecar, so the
relay's call to itself fails with 'network error reaching policy service' and
every push is rejected.
The relay already serves the same router over its UDS listener (main.rs) when
BUZZ_UDS_PATH is set, so the policy endpoint is already reachable over the
socket — only the hook's curl target needs to change.
- transport.rs: when config.uds_path is set, point BUZZ_HOOK_URL at a
placeholder host (curl ignores host:port under --unix-socket) and pass the
socket path via BUZZ_HOOK_SOCKET. Extract policy_hook_url() as a pure,
unit-testable helper. When uds_path is None, behavior is byte-identical to
before (TCP loopback) — local/dev/CI are untouched.
- hook.rs: add --unix-socket "$BUZZ_HOOK_SOCKET" to the policy curl iff
BUZZ_HOOK_SOCKET is non-empty. --unix-socket is stock libcurl.
- Unit tests for both URL branches.
Pairs with a tf-stacks chart change that sets BUZZ_UDS_PATH + a shared emptyDir
mount; that removes the relay's dependency on the mesh loopback entirely.
Refs RESEARCH/SPROUT_GIT_POLICY_DEPLOY_MISMATCH.md.
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
This commit is contained in:
parent
aca40b6289
commit
eb76c81909
@@ -122,7 +122,17 @@ fi
|
||||
SAFE_REPO_ID=$(printf '%s' "$BUZZ_REPO_ID" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||||
BODY="{\"repo_id\":\"${SAFE_REPO_ID}\",\"repo_owner\":\"${BUZZ_REPO_OWNER}\",\"pusher_pubkey\":\"${BUZZ_PUSHER_PUBKEY}\",\"ref_updates\":[${REFS}],\"timestamp\":${TIMESTAMP},\"signature\":\"${SIGNATURE}\"}"
|
||||
|
||||
# Route the policy callback over a unix socket when the relay configured one
|
||||
# (BUZZ_HOOK_SOCKET). This lets the hook reach the relay's own endpoint even
|
||||
# when the loopback TCP port is intercepted in-pod (e.g. by an Istio sidecar).
|
||||
# When unset/empty, fall through to the TCP URL exactly as before.
|
||||
CURL_TRANSPORT=()
|
||||
if [ -n "${BUZZ_HOOK_SOCKET:-}" ]; then
|
||||
CURL_TRANSPORT=(--unix-socket "$BUZZ_HOOK_SOCKET")
|
||||
fi
|
||||
|
||||
HTTP_CODE=$(curl --silent --max-time 10 \
|
||||
"${CURL_TRANSPORT[@]}" \
|
||||
-o "$RESP_FILE" \
|
||||
-w "%{http_code}" \
|
||||
-X POST \
|
||||
|
||||
@@ -627,6 +627,21 @@ pub async fn upload_pack(
|
||||
)
|
||||
}
|
||||
|
||||
/// Build the URL the pre-receive hook curls for the `/internal/git/policy`
|
||||
/// callback.
|
||||
///
|
||||
/// When a UDS path is configured the hook routes over the unix socket (the
|
||||
/// caller adds `--unix-socket`), and curl ignores the URL host:port — so the
|
||||
/// host here is a placeholder. Otherwise the hook curls the relay's own TCP
|
||||
/// loopback on its bind port. Kept as a small pure fn so the host/port
|
||||
/// selection is unit-testable without standing up a relay.
|
||||
fn policy_hook_url(uds_path: Option<&str>, bind_port: u16) -> String {
|
||||
match uds_path {
|
||||
Some(_) => "http://localhost/internal/git/policy".to_string(),
|
||||
None => format!("http://127.0.0.1:{bind_port}/internal/git/policy"),
|
||||
}
|
||||
}
|
||||
|
||||
/// `POST /git/{owner}/{repo}/git-receive-pack`
|
||||
///
|
||||
/// Handles push — client sends ref updates + pack data.
|
||||
@@ -695,12 +710,23 @@ pub async fn receive_pack(
|
||||
})?;
|
||||
|
||||
// Build hook env vars for the pre-receive hook.
|
||||
let hook_url = format!(
|
||||
"http://127.0.0.1:{}/internal/git/policy",
|
||||
state.config.bind_addr.port()
|
||||
//
|
||||
// The hook curls the relay's own /internal/git/policy callback. By default
|
||||
// it targets the TCP loopback `127.0.0.1:{bind_port}`. When a UDS is
|
||||
// configured (BUZZ_UDS_PATH), the same router is also served over the unix
|
||||
// socket (see main.rs), so we point the hook at the socket via curl's
|
||||
// --unix-socket. This dodges any in-pod L4 interception of the loopback
|
||||
// TCP port (e.g. an Istio sidecar's iptables capture of the app port),
|
||||
// which otherwise breaks the relay's call to itself.
|
||||
//
|
||||
// Under --unix-socket curl ignores the URL host:port and uses only the
|
||||
// path, so the URL host is a placeholder when a socket is set.
|
||||
let hook_url = policy_hook_url(
|
||||
state.config.uds_path.as_deref(),
|
||||
state.config.bind_addr.port(),
|
||||
);
|
||||
let hooks_dir = repo.path().join("hooks").display().to_string();
|
||||
let hook_env = vec![
|
||||
let mut hook_env = vec![
|
||||
("BUZZ_HOOK_URL", hook_url),
|
||||
(
|
||||
"BUZZ_HOOK_SECRET",
|
||||
@@ -716,6 +742,12 @@ pub async fn receive_pack(
|
||||
("GIT_CONFIG_KEY_0", "core.hooksPath".to_string()),
|
||||
("GIT_CONFIG_VALUE_0", hooks_dir),
|
||||
];
|
||||
// When a UDS is configured, route the policy callback over it. The hook
|
||||
// only honors BUZZ_HOOK_SOCKET when it is non-empty, so the TCP path is
|
||||
// untouched for local/dev/CI where BUZZ_UDS_PATH is unset.
|
||||
if let Some(ref uds_path) = state.config.uds_path {
|
||||
hook_env.push(("BUZZ_HOOK_SOCKET", uds_path.clone()));
|
||||
}
|
||||
|
||||
// Run receive-pack against the tempdir. Returns the *owned* subprocess
|
||||
// output (PackOutput) — crucially NOT a Response, so the post-push
|
||||
@@ -1329,4 +1361,29 @@ mod track_c_tests {
|
||||
let caps = String::from_utf8_lossy(&body);
|
||||
assert!(caps.contains("object-format=sha256"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn policy_hook_url_uses_tcp_loopback_without_uds() {
|
||||
// No UDS configured → hook curls the relay's own TCP loopback on its
|
||||
// bind port. Unchanged behavior for local/dev/CI.
|
||||
assert_eq!(
|
||||
policy_hook_url(None, 3000),
|
||||
"http://127.0.0.1:3000/internal/git/policy"
|
||||
);
|
||||
assert_eq!(
|
||||
policy_hook_url(None, 8080),
|
||||
"http://127.0.0.1:8080/internal/git/policy"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn policy_hook_url_uses_placeholder_host_with_uds() {
|
||||
// UDS configured → curl routes over --unix-socket and ignores the URL
|
||||
// host:port, so the bind port must not leak into the URL. The path is
|
||||
// what matters; the host is a placeholder.
|
||||
let url = policy_hook_url(Some("/run/buzz/relay.sock"), 3000);
|
||||
assert_eq!(url, "http://localhost/internal/git/policy");
|
||||
assert!(!url.contains("3000"));
|
||||
assert!(!url.contains("127.0.0.1"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user