mirror of
https://github.com/andrewyng/openworker.git
synced 2026-11-04 13:27:11 +00:00
The runner daemon puts that folder first on PATH. Live OpenShell test for grants added; passes on the Linux VM.
225 lines
11 KiB
Python
225 lines
11 KiB
Python
"""Credential grants (design doc, section 11b): the machine's list, the copy-in, the hosts,
|
|
and, on a Mac, the copies inside a real Seatbelt sandbox with SSH going through the proxy.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import socket
|
|
import stat
|
|
import sys
|
|
import threading
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from coworker.config import Config, load_config
|
|
from coworker.sandbox import credentials as creds
|
|
from coworker.sandbox import netproxy
|
|
from coworker.sandbox.providers import openshell_policy
|
|
|
|
|
|
def _home(tmp_path) -> Path:
|
|
home = tmp_path / "home"
|
|
(home / ".ssh").mkdir(parents=True)
|
|
(home / ".ssh" / "id_ed25519").write_text("PRIVATE KEY\n")
|
|
(home / ".ssh" / "id_ed25519.pub").write_text("ssh-ed25519 AAAA test\n")
|
|
(home / ".ssh" / "config").write_text("Host work\n HostName git.example.com\n")
|
|
(home / ".config" / "gh").mkdir(parents=True)
|
|
(home / ".config" / "gh" / "hosts.yml").write_text("github.com:\n oauth_token: gho_x\n")
|
|
(home / ".aws").mkdir()
|
|
(home / ".aws" / "credentials").write_text("[default]\naws_access_key_id = AKIA\n")
|
|
(home / ".kube").mkdir()
|
|
(home / ".kube" / "config").write_text("clusters:\n- cluster:\n server: https://k8s.example.com:6443\n")
|
|
(home / ".gitconfig").write_text("[user]\n\tname = Sam\n\temail = sam@example.com\n")
|
|
(home / "Documents").mkdir()
|
|
return home
|
|
|
|
|
|
# -- the list ---------------------------------------------------------------------------
|
|
def test_nothing_is_granted_unless_switched_on(tmp_path):
|
|
home = _home(tmp_path)
|
|
assert creds.granted(None, home=str(home)) == []
|
|
assert [e["enabled"] for e in creds.entries(None)] == [False] * 4
|
|
|
|
|
|
def test_a_user_entry_edits_or_adds_by_name(tmp_path):
|
|
home = _home(tmp_path)
|
|
configured = [
|
|
{"name": "ssh", "enabled": True},
|
|
{"name": "aws", "enabled": True, "path": "~/.aws-work"}, # edited path, missing here
|
|
{"name": "npm", "enabled": True, "path": "~/.npmrc", "hosts": ["registry.npmjs.org:443"]}, # added
|
|
{"name": "bogus", "enabled": True, "path": "~"}, # never the home folder itself
|
|
{"name": "outside", "enabled": True, "path": "/etc"}, # never outside the home folder
|
|
]
|
|
(home / ".npmrc").write_text("//registry.npmjs.org/:_authToken=x\n")
|
|
got = {g.name: g for g in creds.granted(configured, home=str(home))}
|
|
assert set(got) == {"ssh", "npm"}
|
|
assert got["ssh"].relative == ".ssh" and got["ssh"].hosts == ["github.com:22", "gitlab.com:22"]
|
|
assert got["npm"].hosts == ["registry.npmjs.org:443"]
|
|
|
|
|
|
def test_kube_hosts_come_from_the_kubeconfig(tmp_path):
|
|
home = _home(tmp_path)
|
|
(got,) = creds.granted([{"name": "kube", "enabled": True}], home=str(home))
|
|
assert got.hosts == ["k8s.example.com:6443"]
|
|
|
|
|
|
def test_the_setting_is_machine_level_only(tmp_path, monkeypatch):
|
|
g = tmp_path / "global.toml"
|
|
g.write_text('[[sandbox_credentials]]\nname = "ssh"\nenabled = true\n')
|
|
ws = tmp_path / "repo"
|
|
(ws / ".coworker").mkdir(parents=True)
|
|
(ws / ".coworker" / "config.toml").write_text('[[sandbox_credentials]]\nname = "aws"\nenabled = true\n')
|
|
cfg = load_config(ws, global_path=g, workspace_trusted=True)
|
|
assert cfg.sandbox_credentials == [{"name": "ssh", "enabled": True}]
|
|
assert Config().sandbox_credentials == []
|
|
|
|
|
|
# -- the copy ---------------------------------------------------------------------------
|
|
def test_copy_in_makes_a_private_home_and_the_tools_environment(tmp_path):
|
|
home = _home(tmp_path)
|
|
grants = creds.granted([{"name": n, "enabled": True} for n in ("ssh", "gh", "aws", "kube")], home=str(home))
|
|
run = tmp_path / "run"
|
|
run.mkdir()
|
|
copied = creds.copy_in(grants, str(run), home=str(home), ssh_proxy_command="/usr/bin/nc -X connect -x 127.0.0.1:4545 %h %p")
|
|
sb = Path(copied.home)
|
|
assert sb == run / "home"
|
|
assert (sb / ".ssh" / "id_ed25519").read_text() == "PRIVATE KEY\n"
|
|
assert stat.S_IMODE((sb / ".ssh" / "id_ed25519").stat().st_mode) == 0o600
|
|
assert stat.S_IMODE((sb / ".ssh").stat().st_mode) == 0o700
|
|
assert (sb / ".gitconfig").exists() # git's settings always come along
|
|
assert not (sb / "Documents").exists() # nothing that was not granted
|
|
config = (sb / ".ssh" / "config").read_text()
|
|
assert config.startswith("# Added by OpenWorker") and "ProxyCommand /usr/bin/nc -X connect -x 127.0.0.1:4545 %h %p" in config
|
|
assert f"IdentityFile {sb / '.ssh' / 'id_ed25519'}" in config and "IdentityAgent none" in config
|
|
assert config.endswith("Host work\n HostName git.example.com\n") # the user's own config still there, after ours
|
|
assert copied.env["GIT_SSH_COMMAND"] == f'/usr/bin/ssh -F "{sb / ".ssh" / "config"}"'
|
|
assert (sb / "bin" / "ssh").read_text().startswith("#!/bin/sh") and copied.env["OPENWORKER_PATH_PREPEND"] == str(sb / "bin")
|
|
assert copied.env["HOME"] == str(sb)
|
|
assert copied.env["GH_CONFIG_DIR"] == str(sb / ".config" / "gh")
|
|
assert copied.env["AWS_SHARED_CREDENTIALS_FILE"] == str(sb / ".aws" / "credentials")
|
|
assert copied.env["KUBECONFIG"] == str(sb / ".kube" / "config")
|
|
assert copied.hosts == ["*.amazonaws.com:443", "api.github.com:443", "github.com:22", "github.com:443", "gitlab.com:22", "k8s.example.com:6443"]
|
|
# the real files were not touched
|
|
assert (home / ".ssh" / "config").read_text() == "Host work\n HostName git.example.com\n"
|
|
text = creds.context_lines(copied)
|
|
assert "SSH keys (.ssh): you can push and pull over SSH" in text and "deleted when the session ends" in text
|
|
|
|
|
|
def test_no_grants_means_no_lines_for_the_agent(tmp_path):
|
|
assert creds.context_lines(None) == ""
|
|
(tmp_path / "run").mkdir()
|
|
copied = creds.copy_in([], str(tmp_path / "run"), home=str(_home(tmp_path)))
|
|
assert creds.context_lines(copied) == ""
|
|
assert copied.hosts == [] and (Path(copied.home) / ".gitconfig").exists()
|
|
|
|
|
|
# -- the network ------------------------------------------------------------------------
|
|
def test_proxy_allows_the_grants_hosts_including_ssh_and_wildcards():
|
|
proxy = netproxy.AllowListProxy("strict", extra_hosts=["github.com:22", "*.amazonaws.com:443", "k8s.example.com:6443"])
|
|
try:
|
|
assert proxy.allows("github.com", 22) and proxy.allows("github.com", 443)
|
|
assert proxy.allows("s3.us-east-1.amazonaws.com", 443) and not proxy.allows("amazonaws.com", 443)
|
|
assert proxy.allows("k8s.example.com", 6443) and not proxy.allows("k8s.example.com", 443)
|
|
assert not proxy.allows("gitlab.com", 22) and not proxy.allows("example.com", 443)
|
|
finally:
|
|
proxy.close()
|
|
|
|
|
|
def test_openshell_policy_carries_the_home_and_the_hosts():
|
|
policy = openshell_policy.render(
|
|
[{"path": "/home/sam/proj", "writable": True}], home="/tmp/ow-x/home", extra_hosts=["github.com:22", "*.amazonaws.com:443"]
|
|
)
|
|
assert "/tmp/ow-x/home" in policy["filesystem_policy"]["read_write"]
|
|
extra = policy["network_policies"]["credentials"]
|
|
assert extra["endpoints"] == [{"host": "github.com", "port": 22}, {"host": "*.amazonaws.com", "port": 443}]
|
|
mounts = openshell_policy.mounts([{"path": "/home/sam/proj", "writable": True}], "/opt/r", "/tmp/ow-x/home")["docker"]["mounts"]
|
|
assert {"type": "bind", "source": "/tmp/ow-x/home", "target": "/tmp/ow-x/home", "read_only": False} in mounts
|
|
|
|
|
|
# -- inside a real sandbox (macOS) ------------------------------------------------------
|
|
def _seatbelt_usable() -> bool:
|
|
if sys.platform != "darwin":
|
|
return False
|
|
from coworker.sandbox.providers import seatbelt
|
|
|
|
try:
|
|
seatbelt.preflight()
|
|
return True
|
|
except seatbelt.SeatbeltUnavailable:
|
|
return False
|
|
|
|
|
|
@pytest.mark.skipif(not _seatbelt_usable(), reason="needs a Mac where sandbox-exec can apply a profile")
|
|
def test_ssh_and_gh_copies_work_inside_seatbelt_and_ssh_goes_through_the_proxy(tmp_path, monkeypatch):
|
|
"""A fake `.ssh` and `.config/gh` are granted. Inside the sandbox: HOME is the copy,
|
|
`gh` finds its file, the real home is still hidden, and `ssh` reaches the listed host
|
|
through the proxy (a local listener stands in for github.com:22: the CONNECT arrives
|
|
at the proxy and is tunnelled; the SSH banner comes back)."""
|
|
from coworker.sandbox.bundle import build_runner_zipapp
|
|
from coworker.sandbox.providers import seatbelt
|
|
from coworker.sandbox.workspace import RunnerWorkspace
|
|
|
|
home = _home(tmp_path)
|
|
project = tmp_path / "project"
|
|
project.mkdir()
|
|
grants = creds.granted([{"name": "ssh", "enabled": True}, {"name": "gh", "enabled": True}], home=str(home))
|
|
# a stand-in for github.com:22 on this machine; the proxy resolves the host, so patch that
|
|
fake = socket.socket()
|
|
fake.bind(("127.0.0.1", 0))
|
|
fake.listen(1)
|
|
|
|
def banner() -> None:
|
|
conn, _ = fake.accept()
|
|
with conn:
|
|
conn.sendall(b"SSH-2.0-OpenSSH_9.9 fake\r\n")
|
|
conn.recv(64)
|
|
|
|
threading.Thread(target=banner, daemon=True).start()
|
|
real_connect = netproxy.socket.create_connection
|
|
|
|
def connect(address, *a, **k):
|
|
if address == ("github.com", 22):
|
|
return real_connect(fake.getsockname(), *a, **k)
|
|
return real_connect(address, *a, **k)
|
|
|
|
monkeypatch.setattr(netproxy.socket, "create_connection", connect)
|
|
provider = seatbelt.SeatbeltProvider(
|
|
roots=[{"path": str(project), "writable": True}], cwd=project, runner_path=build_runner_zipapp(tmp_path / "dist"), credentials=grants
|
|
)
|
|
ws = RunnerWorkspace(provider, cwd=project)
|
|
try:
|
|
run = ws.executor.run
|
|
assert run("echo $HOME")["output"].strip() == provider.copied.home
|
|
assert "PRIVATE KEY" in run("cat ~/.ssh/id_ed25519")["output"]
|
|
assert "gho_x" in run('cat "$GH_CONFIG_DIR/hosts.yml"')["output"]
|
|
assert "not permitted" in run(f"ls {home} 2>&1")["output"] # the real home stays hidden
|
|
assert ws.describe()["credentials"][0]["name"] == "ssh"
|
|
assert "SSH keys" in ws.context()
|
|
# ssh: the ProxyCommand in the copied config carries the connection through the proxy
|
|
assert run("command -v ssh")["output"].strip() == os.path.join(provider.copied.home, "bin", "ssh") # the wrapper wins
|
|
out = run("ssh -o BatchMode=yes -o ConnectTimeout=8 -o StrictHostKeyChecking=no -v git@github.com true 2>&1 | grep -i 'remote protocol version\\|proxy\\|banner' | head -3")
|
|
assert "SSH-2.0-OpenSSH_9.9 fake" in out["output"] or "Remote protocol version 2.0" in out["output"], out["output"]
|
|
finally:
|
|
ws.close()
|
|
fake.close()
|
|
assert not Path(provider.copied.home).exists() # the copies died with the sandbox
|
|
|
|
|
|
def test_a_failed_creation_leaves_no_copied_credential_behind(tmp_path, monkeypatch):
|
|
from coworker.sandbox.providers import seatbelt
|
|
|
|
home = _home(tmp_path)
|
|
grants = creds.granted([{"name": "ssh", "enabled": True}], home=str(home))
|
|
provider = seatbelt.SeatbeltProvider(roots=[{"path": str(tmp_path), "writable": True}], cwd=tmp_path, credentials=grants, network=False)
|
|
folder = provider._dir
|
|
|
|
def broken() -> None:
|
|
raise seatbelt.SeatbeltUnavailable("no sandbox today")
|
|
|
|
monkeypatch.setattr(seatbelt, "preflight", broken)
|
|
with pytest.raises(seatbelt.SeatbeltUnavailable):
|
|
provider.create()
|
|
assert not os.path.exists(folder)
|