fix(firecracker): pin the provisioned Git config
This commit is contained in:
@@ -261,7 +261,7 @@ class AgentProvider(ABC):
|
||||
Default: Debian/node — writes the git-gate insteadOf gitconfig
|
||||
and sets user.name/email as node. Workspace copy runs through
|
||||
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
|
||||
# tar extraction, so image-time ownership of XDG directories is not
|
||||
@@ -270,13 +270,17 @@ class AgentProvider(ABC):
|
||||
# git-gate insteadOf rules below from taking effect. Repair this at
|
||||
# runtime, after every backend's copy/export path has completed.
|
||||
git_xdg_dir = f"{plan.guest_home}/.config/git"
|
||||
bottle.exec(
|
||||
repair = bottle.exec(
|
||||
f"mkdir -p {shlex.quote(git_xdg_dir)} && "
|
||||
f"chown -R node:node {shlex.quote(f'{plan.guest_home}/.config')} && "
|
||||
f"chmod 755 {shlex.quote(f'{plan.guest_home}/.config')} "
|
||||
f"{shlex.quote(git_xdg_dir)}",
|
||||
f"chmod -R u+rwX,go+rX {shlex.quote(f'{plan.guest_home}/.config')}",
|
||||
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
|
||||
if manifest_bottle.git:
|
||||
@@ -299,11 +303,27 @@ class AgentProvider(ABC):
|
||||
f"{len(manifest_bottle.git)} insteadOf rule(s)"
|
||||
)
|
||||
bottle.cp_in(str(config_file), guest_gitconfig)
|
||||
bottle.exec(
|
||||
permissions = bottle.exec(
|
||||
f"chown node:node {shlex.quote(guest_gitconfig)} && "
|
||||
f"chmod 644 {shlex.quote(guest_gitconfig)}",
|
||||
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
|
||||
if not gu.is_empty():
|
||||
|
||||
@@ -263,6 +263,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,
|
||||
"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,
|
||||
"SSL_CERT_FILE": AGENT_CA_BUNDLE,
|
||||
"REQUESTS_CA_BUNDLE": AGENT_CA_BUNDLE,
|
||||
|
||||
@@ -137,7 +137,7 @@ class TestProvisionGitUser(unittest.TestCase):
|
||||
self.assertEqual("root", user)
|
||||
self.assertIn("mkdir -p /home/node/.config/git", 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):
|
||||
plan = _plan(
|
||||
|
||||
@@ -422,10 +422,15 @@ class TestBottlePlanProperties(unittest.TestCase):
|
||||
ap.command = "claude"
|
||||
ap.prompt_mode = "append_file"
|
||||
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(
|
||||
spec=cast(Any, MagicMock()), manifest=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={},
|
||||
)
|
||||
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("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__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user