Skip to content

Commit 0baf4f0

Browse files
author
root
committed
fix(server): use peer identity for recovery
1 parent a32c945 commit 0baf4f0

2 files changed

Lines changed: 95 additions & 14 deletions

File tree

src/agentnet/cli.py

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from contextlib import contextmanager
2626
from datetime import UTC, datetime, timedelta
2727
from pathlib import Path
28-
from urllib.parse import urlencode, urlsplit
28+
from urllib.parse import unquote, urlencode, urlsplit
2929
from typing import Any
3030
from uuid import NAMESPACE_URL, UUID, uuid4, uuid5
3131

@@ -1034,19 +1034,43 @@ def _open_server_agent_activation_store(
10341034
config.data_dir / "secrets" / "records.key",
10351035
create=False,
10361036
)
1037-
return PostgreSQLStore(
1038-
database_url_override or config.resolved_database_url(),
1039-
cipher,
1040-
instance_id=config.runtime_instance_id,
1041-
lease_owner_id=f"activation-{uuid4().hex}",
1042-
connect_timeout=config.postgres_connect_timeout_seconds,
1043-
statement_timeout_ms=config.postgres_statement_timeout_ms,
1044-
lock_timeout_ms=config.postgres_lock_timeout_ms,
1045-
lease_ttl_seconds=config.postgres_lease_ttl_seconds,
1046-
run_migrations=False,
1047-
start_lease_keeper=False,
1048-
require_recovery_topology=config.postgres_recovery_topology,
1049-
)
1037+
database_url = database_url_override or config.resolved_database_url()
1038+
1039+
def open_store() -> PostgreSQLStore:
1040+
return PostgreSQLStore(
1041+
database_url,
1042+
cipher,
1043+
instance_id=config.runtime_instance_id,
1044+
lease_owner_id=f"activation-{uuid4().hex}",
1045+
connect_timeout=config.postgres_connect_timeout_seconds,
1046+
statement_timeout_ms=config.postgres_statement_timeout_ms,
1047+
lock_timeout_ms=config.postgres_lock_timeout_ms,
1048+
lease_ttl_seconds=config.postgres_lease_ttl_seconds,
1049+
run_migrations=False,
1050+
start_lease_keeper=False,
1051+
require_recovery_topology=config.postgres_recovery_topology,
1052+
)
1053+
1054+
parsed = urlsplit(database_url)
1055+
if (
1056+
os.geteuid() == 0
1057+
and parsed.username == CORE_USER
1058+
and parsed.password is None
1059+
and unquote(parsed.hostname or "").startswith("/")
1060+
):
1061+
import pwd as posix_pwd
1062+
1063+
account = posix_pwd.getpwnam(CORE_USER)
1064+
original_uid = os.geteuid()
1065+
original_gid = os.getegid()
1066+
try:
1067+
os.setegid(account.pw_gid)
1068+
os.seteuid(account.pw_uid)
1069+
return open_store()
1070+
finally:
1071+
os.seteuid(original_uid)
1072+
os.setegid(original_gid)
1073+
return open_store()
10501074

10511075

10521076
def _require_server_agent_activation_binding(

tests/cli/test_server_agent_activation.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ def fake_postgres(database_url, passed_cipher, **kwargs):
413413
)
414414
assert reauthorization.func is cli.command_server_agent_reauthorize_expired_credential
415415
assert reauthorization.replace_terminal_state is False
416+
416417
defaults = parser.parse_args(["server-agent", "reauthorize-expired-credential"])
417418
assert defaults.config == str(cli.CORE_CONFIG)
418419
assert defaults.identity == str(cli.SERVER_AGENT_IDENTITY)
@@ -729,6 +730,62 @@ def replace_journal(path: Path, *, expected, replacement, uid, gid):
729730

730731

731732

733+
def test_root_activation_store_connects_to_exact_peer_socket_as_core_user(
734+
tmp_path: Path,
735+
monkeypatch: pytest.MonkeyPatch,
736+
) -> None:
737+
import pwd
738+
739+
state = activation_fixture(tmp_path)
740+
peer_url = "postgresql://agentnet@%2Fvar%2Frun%2Fpostgresql/agentnet"
741+
ids = {"uid": 0, "gid": 0}
742+
transitions: list[tuple[str, int]] = []
743+
fake_store = FakeStore()
744+
monkeypatch.setattr(
745+
cli.LocalEnvelopeCipher,
746+
"from_key_file",
747+
lambda _path, *, create: object(),
748+
)
749+
monkeypatch.setattr(cli.os, "geteuid", lambda: ids["uid"])
750+
monkeypatch.setattr(cli.os, "getegid", lambda: ids["gid"])
751+
monkeypatch.setattr(
752+
cli.os,
753+
"seteuid",
754+
lambda value: transitions.append(("uid", value)) or ids.update(uid=value),
755+
)
756+
monkeypatch.setattr(
757+
cli.os,
758+
"setegid",
759+
lambda value: transitions.append(("gid", value)) or ids.update(gid=value),
760+
)
761+
monkeypatch.setattr(
762+
pwd,
763+
"getpwnam",
764+
lambda name: SimpleNamespace(pw_uid=123, pw_gid=456)
765+
if name == cli.CORE_USER
766+
else pytest.fail("unexpected account"),
767+
)
768+
769+
def fake_postgres(database_url, _cipher, **_kwargs):
770+
assert database_url == peer_url
771+
assert ids == {"uid": 123, "gid": 456}
772+
return fake_store
773+
774+
monkeypatch.setattr(cli, "PostgreSQLStore", fake_postgres)
775+
776+
assert cli._open_server_agent_activation_store(
777+
state.config,
778+
database_url_override=peer_url,
779+
) is fake_store
780+
assert ids == {"uid": 0, "gid": 0}
781+
assert transitions == [
782+
("gid", 456),
783+
("uid", 123),
784+
("uid", 0),
785+
("gid", 0),
786+
]
787+
788+
732789
def test_managed_scope_replacement_is_resumable_and_waits_before_commit(
733790
tmp_path: Path,
734791
monkeypatch: pytest.MonkeyPatch,

0 commit comments

Comments
 (0)