Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/syft-enclave/src/syft_enclaves/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ def _configure_logging(log_level: str) -> None:
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
# googleapiclient INFO-logs a "file_cache is only supported with
# oauth2client<4.0.0" line on every service build
logging.getLogger("googleapiclient.discovery_cache").setLevel(logging.WARNING)


def _load_settings() -> EnclaveSettings:
Expand Down
11 changes: 11 additions & 0 deletions packages/syft-enclave/src/syft_enclaves/client.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
Expand Down Expand Up @@ -36,6 +37,8 @@
make_private_dataset_immutability_filter,
)

logger = logging.getLogger(__name__)


class SyftEnclaveClient:
def __init__(
Expand Down Expand Up @@ -304,6 +307,14 @@ def _try_distribute_job(self, ref: JobRef):
if distributed_marker.exists():
return

# First time we see this job — the "new job arrived" moment.
logger.info(
"New job received: '%s' from %s (datasets: %s)",
ref.job_name,
ref.ds_email,
", ".join(config.datasets.keys()),
)

# Forward the job to the DOs referenced in the submission, but gate
# approval on the enclave's globally-configured data owners. The job is
# forwarded to the union so every required approver can review it.
Expand Down
27 changes: 20 additions & 7 deletions packages/syft-enclave/src/syft_enclaves/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def __init__(
self.fresh_state = fresh_state
self.post_init = post_init
self._shutdown_requested = False
self._ignored_peer_requests: set[str] = set()

# -- public API -------------------------------------------------------

Expand Down Expand Up @@ -157,17 +158,29 @@ def _loop(self) -> None:
self._sleep()

def _accept_peers(self) -> None:
"""Accept any pending peer requests."""
"""Accept pending peer requests — only from the configured data owners.

With no data owners configured, NO peers are accepted.
"""
self.client.load_peers()
allowed = {e.strip().lower() for e in self.client.data_owners}
for peer in self.client.peers:
if getattr(peer, "state", None) == "requested_by_peer":
try:
self.client.approve_peer_request(peer.email)
logger.info("Accepted peer: %s", peer.email)
except Exception:
if getattr(peer, "state", None) != "requested_by_peer":
continue
if peer.email.strip().lower() not in allowed:
if peer.email not in self._ignored_peer_requests:
self._ignored_peer_requests.add(peer.email)
logger.warning(
"Failed to accept peer: %s", peer.email, exc_info=True
"Ignoring peer request from %s — not in the configured "
"data owners (SYFT_ENCLAVE_DATA_OWNERS)",
peer.email,
)
continue
try:
self.client.approve_peer_request(peer.email)
logger.info("Accepted peer: %s", peer.email)
except Exception:
logger.warning("Failed to accept peer: %s", peer.email, exc_info=True)

# -- utilities --------------------------------------------------------

Expand Down
36 changes: 36 additions & 0 deletions packages/syft-enclave/tests/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,45 @@ def _make_client():
client.syftbox_folder = "/tmp/SyftBox_enclave"
# _on_peering needs these
client.peers = []
client.data_owners = []
return client


def _make_peer(email, state="requested_by_peer"):
peer = MagicMock()
peer.email = email
peer.state = state
return peer


def test_accept_peers_approves_only_data_owners():
"""Peer requests from emails outside data_owners are ignored."""
client = _make_client()
client.data_owners = ["do1@x.com", "DO2@Y.com"]
client.peers = [
_make_peer("do1@x.com"),
_make_peer("do2@y.com"), # allowlist match is case-insensitive
_make_peer("stranger@evil.com"),
]
runner = EnclaveRunner(client=client)

runner._accept_peers()

approved = [c.args[0] for c in client.approve_peer_request.call_args_list]
assert approved == ["do1@x.com", "do2@y.com"]


def test_accept_peers_empty_allowlist_accepts_none():
"""No data owners configured — no peer is ever accepted."""
client = _make_client()
client.peers = [_make_peer("anyone@x.com")]
runner = EnclaveRunner(client=client)

runner._accept_peers()

client.approve_peer_request.assert_not_called()


def test_fresh_state_true_invokes_delete_syftbox(tmp_path, monkeypatch):
"""With fresh_state=True (default), _on_initializing must wipe state once."""
# Make _on_attesting a no-op (no TEE socket present in unit tests).
Expand Down
Loading