fix(firecracker): pin the provisioned Git config
tracker-policy-pr / check-pr (pull_request) Successful in 10s
test / integration-docker (pull_request) Successful in 19s
lint / lint (push) Successful in 55s
test / unit (pull_request) Successful in 1m32s
test / integration-firecracker (pull_request) Failing after 3m4s
test / coverage (pull_request) Has been skipped
test / publish-infra (pull_request) Has been skipped

This commit is contained in:
2026-07-21 18:17:29 +00:00
parent 7512e3179a
commit c2c934899c
4 changed files with 43 additions and 7 deletions
+25 -5
View File
@@ -257,7 +257,7 @@ class AgentProvider(ABC):
Default: Debian/node — writes the git-gate insteadOf gitconfig Default: Debian/node — writes the git-gate insteadOf gitconfig
and sets user.name/email as node. Workspace copy runs through and sets user.name/email as node. Workspace copy runs through
BottleBackend.provision_workspace against the running bottle.""" BottleBackend.provision_workspace against the running bottle."""
from .log import info from .log import die, info
# Firecracker exports image rootfs files through an unprivileged host # Firecracker exports image rootfs files through an unprivileged host
# tar extraction, so image-time ownership of XDG directories is not # tar extraction, so image-time ownership of XDG directories is not
@@ -266,13 +266,17 @@ class AgentProvider(ABC):
# git-gate insteadOf rules below from taking effect. Repair this at # git-gate insteadOf rules below from taking effect. Repair this at
# runtime, after every backend's copy/export path has completed. # runtime, after every backend's copy/export path has completed.
git_xdg_dir = f"{plan.guest_home}/.config/git" git_xdg_dir = f"{plan.guest_home}/.config/git"
bottle.exec( repair = bottle.exec(
f"mkdir -p {shlex.quote(git_xdg_dir)} && " f"mkdir -p {shlex.quote(git_xdg_dir)} && "
f"chown -R node:node {shlex.quote(f'{plan.guest_home}/.config')} && " f"chown -R node:node {shlex.quote(f'{plan.guest_home}/.config')} && "
f"chmod 755 {shlex.quote(f'{plan.guest_home}/.config')} " f"chmod -R u+rwX,go+rX {shlex.quote(f'{plan.guest_home}/.config')}",
f"{shlex.quote(git_xdg_dir)}",
user="root", user="root",
) )
if repair.returncode != 0:
die(
"git provisioning: could not make the runtime Git config "
f"directory readable: {(repair.stderr or repair.stdout).strip()}"
)
manifest_bottle = plan.manifest.bottle manifest_bottle = plan.manifest.bottle
if manifest_bottle.git: if manifest_bottle.git:
@@ -295,11 +299,27 @@ class AgentProvider(ABC):
f"{len(manifest_bottle.git)} insteadOf rule(s)" f"{len(manifest_bottle.git)} insteadOf rule(s)"
) )
bottle.cp_in(str(config_file), guest_gitconfig) bottle.cp_in(str(config_file), guest_gitconfig)
bottle.exec( permissions = bottle.exec(
f"chown node:node {shlex.quote(guest_gitconfig)} && " f"chown node:node {shlex.quote(guest_gitconfig)} && "
f"chmod 644 {shlex.quote(guest_gitconfig)}", f"chmod 644 {shlex.quote(guest_gitconfig)}",
user="root", user="root",
) )
if permissions.returncode != 0:
die(
"git provisioning: could not set ownership on "
f"{guest_gitconfig}: "
f"{(permissions.stderr or permissions.stdout).strip()}"
)
configured = bottle.exec(
"git config --global --get-regexp '^url\\..*\\.insteadof$'",
user="node",
)
if configured.returncode != 0:
die(
"git provisioning: the runtime user cannot read the "
f"git-gate insteadOf rules from {guest_gitconfig}: "
f"{(configured.stderr or configured.stdout).strip()}"
)
gu = manifest_bottle.git_user gu = manifest_bottle.git_user
if not gu.is_empty(): if not gu.is_empty():
+5
View File
@@ -242,6 +242,11 @@ def _agent_guest_env(plan: FirecrackerBottlePlan, host_ip: str) -> dict[str, str
"HTTPS_PROXY": proxy_url, "HTTP_PROXY": proxy_url, "HTTPS_PROXY": proxy_url, "HTTP_PROXY": proxy_url,
"https_proxy": proxy_url, "http_proxy": proxy_url, "https_proxy": proxy_url, "http_proxy": proxy_url,
"NO_PROXY": no_proxy, "no_proxy": no_proxy, "NO_PROXY": no_proxy, "no_proxy": no_proxy,
# Rootfs export can leave Git's implicit XDG paths unreadable even
# after the runtime repair. Bypass that discovery and name the
# provisioned global config explicitly so insteadOf can never fall
# through to the credential-bearing upstream URL.
"GIT_CONFIG_GLOBAL": f"{plan.guest_home}/.gitconfig",
"NODE_EXTRA_CA_CERTS": AGENT_CA_PATH, "NODE_EXTRA_CA_CERTS": AGENT_CA_PATH,
"SSL_CERT_FILE": AGENT_CA_BUNDLE, "SSL_CERT_FILE": AGENT_CA_BUNDLE,
"REQUESTS_CA_BUNDLE": AGENT_CA_BUNDLE, "REQUESTS_CA_BUNDLE": AGENT_CA_BUNDLE,
+1 -1
View File
@@ -137,7 +137,7 @@ class TestProvisionGitUser(unittest.TestCase):
self.assertEqual("root", user) self.assertEqual("root", user)
self.assertIn("mkdir -p /home/node/.config/git", script) self.assertIn("mkdir -p /home/node/.config/git", script)
self.assertIn("chown -R node:node /home/node/.config", script) self.assertIn("chown -R node:node /home/node/.config", script)
self.assertIn("chmod 755 /home/node/.config /home/node/.config/git", script) self.assertIn("chmod -R u+rwX,go+rX /home/node/.config", script)
def test_sets_name_and_email(self): def test_sets_name_and_email(self):
plan = _plan( plan = _plan(
+12 -1
View File
@@ -422,10 +422,15 @@ class TestBottlePlanProperties(unittest.TestCase):
ap.command = "claude" ap.command = "claude"
ap.prompt_mode = "append_file" ap.prompt_mode = "append_file"
ap.template = "claude" ap.template = "claude"
ap.guest_home = "/home/node"
ap.guest_env = {}
egress_plan = cast(Any, MagicMock())
egress_plan.canary = ""
egress_plan.canary_env = ""
fields = dict( fields = dict(
spec=cast(Any, MagicMock()), manifest=cast(Any, MagicMock()), spec=cast(Any, MagicMock()), manifest=cast(Any, MagicMock()),
stage_dir=Path("/stage"), git_gate_plan=cast(Any, MagicMock()), stage_dir=Path("/stage"), git_gate_plan=cast(Any, MagicMock()),
egress_plan=cast(Any, MagicMock()), supervise_plan=None, egress_plan=egress_plan, supervise_plan=None,
agent_provision=ap, slug="demo-x", forwarded_env={}, agent_provision=ap, slug="demo-x", forwarded_env={},
) )
fields.update(overrides) fields.update(overrides)
@@ -451,6 +456,12 @@ class TestBottlePlanProperties(unittest.TestCase):
self.assertEqual("10.243.0.0:9420", p.git_gate_insteadof_host) self.assertEqual("10.243.0.0:9420", p.git_gate_insteadof_host)
self.assertEqual("http", p.git_gate_insteadof_scheme) self.assertEqual("http", p.git_gate_insteadof_scheme)
def test_guest_env_pins_global_git_config(self):
from bot_bottle.backend.firecracker.launch import _agent_guest_env
env = _agent_guest_env(self._plan(), "10.243.0.0")
self.assertEqual("/home/node/.gitconfig", env["GIT_CONFIG_GLOBAL"])
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()