Skip to content

migration: phase 1 — restore local gRPC ingest in syva-core#48

Merged
yairfalse merged 2 commits into
mainfrom
migration/phase-1-local-ingest
Apr 26, 2026
Merged

migration: phase 1 — restore local gRPC ingest in syva-core#48
yairfalse merged 2 commits into
mainfrom
migration/phase-1-local-ingest

Conversation

@yairfalse
Copy link
Copy Markdown
Contributor

Summary

Phase 1 restores additive local-mode ingest in syva-core while keeping CP mode as the default.

  • Adds --policy-source {cp,local} with default cp.
  • Adds --socket-path for the local syva.core.v1 Unix socket.
  • Restores the SyvaCore gRPC server and wires RPC mutations through the existing ingest.rs local-action layer.
  • Adds shared container ID validation in syva-core.
  • Adds ignored Linux/root integration tests for local startup and register/list behavior.

Verification Run Here

Passed on this macOS host:

cargo check -p syva-core --target x86_64-unknown-linux-gnu
# Finished dev profile successfully

cargo check -p syva-core --target x86_64-unknown-linux-gnu --tests
# Finished dev profile successfully

cargo test -p syva-proto
# 2 passed; 0 failed

cargo test -p syva-ebpf-common
# 8 passed; 0 failed

Could not complete on this host:

cargo build --workspace
cargo test --workspace

Reason: this machine is macOS and the workspace includes Linux-only aya code. Native workspace build/test fails before this PR's code with missing Linux libc symbols such as AF_NETLINK, SYS_bpf, SYS_perf_event_open, and CLOCK_BOOTTIME.

Also attempted:

cargo check --workspace --target x86_64-unknown-linux-gnu

That was blocked by missing cross C compiler x86_64-linux-gnu-gcc for transitive TLS crates (ring / aws-lc-sys).

Must Test On Linux Before Merge

Run on a Linux host/runner:

cargo build --workspace
cargo test --workspace

Manual local-mode smoke tests, requiring root, BPF LSM support, and a syva group:

sudo -E cargo test -p syva-core --test local_mode_starts_server -- --ignored --nocapture
sudo -E cargo test -p syva-core --test local_mode_register_then_list -- --ignored --nocapture

Optional end-to-end check from the migration brief:

sudo target/debug/syva-core --policy-source local --socket-path /tmp/syva-core.sock &
SYVA_SOCKET=/tmp/syva-core.sock cargo test --manifest-path eval/oracle/Cargo.toml -- case_001 --exact --nocapture

Notes

The local socket path fails fast if it already exists. Socket permissions are set to 0660; ownership is changed to root:syva, and startup errors clearly if the syva group is missing.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Restores a local-mode gRPC ingest path in syva-core (while keeping CP mode as default) by reintroducing a syva.core.v1 Unix-socket server and routing RPC mutations through the existing local ingest layer.

Changes:

  • Adds CLI flags to select policy source (--policy-source {cp,local}) and configure the local Unix socket (--socket-path).
  • Implements a new SyvaCore gRPC service in syva-core and wires core mutations to ingest.rs.
  • Adds container ID validation helper plus ignored Linux/root integration tests for local-mode startup and register/list flows.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
syva-core/src/main.rs Adds policy source selection and local Unix-socket gRPC server startup/shutdown wiring.
syva-core/src/rpc/mod.rs New tonic gRPC service implementation for local-mode ingest/mutations and event streaming.
syva-core/src/container_id.rs Introduces shared container ID validation helper + unit tests.
syva-core/src/zone.rs Minor lint attribute tweak on all_zones.
syva-core/tests/common/mod.rs Adds helpers to spawn syva-core and connect over a Unix socket via tonic.
syva-core/tests/local_mode_starts_server.rs Adds ignored manual integration test for local-mode server startup.
syva-core/tests/local_mode_register_then_list.rs Adds ignored manual integration test for local-mode register/list behavior.
syva-core/Cargo.toml Adds tonic/tokio-stream and test-only deps needed for Unix-socket gRPC testing.
Cargo.lock Locks new dependencies introduced by syva-core changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread syva-core/src/main.rs Outdated
Comment on lines +363 to +364
configure_socket_permissions(&socket_path)?;

Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spawn_local_core_server binds the Unix socket before calling configure_socket_permissions. If configure_socket_permissions fails (e.g., missing syva group / not running as root), the socket file is left on disk; subsequent startups will then fail fast on socket_path.exists() even after the underlying problem is fixed. Consider ensuring the socket path is unlinked on any error after UnixListener::bind (e.g., scope guard / explicit cleanup on error paths).

Suggested change
configure_socket_permissions(&socket_path)?;
if let Err(error) = configure_socket_permissions(&socket_path) {
drop(listener);
if let Err(cleanup_error) = std::fs::remove_file(&socket_path) {
if cleanup_error.kind() != std::io::ErrorKind::NotFound {
return Err(anyhow::anyhow!(
"failed to configure syva-core socket permissions at {}: {error}; additionally failed to remove the socket file: {cleanup_error}",
socket_path.display()
));
}
}
return Err(error);
}

Copilot uses AI. Check for mistakes.
Comment thread syva-core/src/main.rs
Comment on lines +375 to +382
let task = tokio::spawn(async move {
tracing::info!(socket = display_path, "local syva.core.v1 server listening");
tonic::transport::Server::builder()
.add_service(SyvaCoreServer::new(service))
.serve_with_incoming_shutdown(incoming, shutdown)
.await
.map_err(|error| anyhow::anyhow!("local gRPC server failed: {error}"))
});
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local gRPC server task does not remove the Unix socket file when the server shuts down successfully. Because startup refuses to replace an existing socket, a clean shutdown will still prevent the next startup unless an external init system deletes the socket. Consider unlinking the socket path after serve_with_incoming_shutdown returns (and also on early failures) to make restarts reliable.

Copilot uses AI. Check for mistakes.
Comment thread syva-core/src/rpc/mod.rs Outdated
Comment on lines +46 to +55
let policy = req.policy.map(|proto_policy| CoreZonePolicyInput {
host_paths: proto_policy.host_paths,
allowed_zones: proto_policy.allowed_zones,
allow_ptrace: proto_policy.allow_ptrace,
zone_type: match proto_policy.zone_type {
1 => ZoneType::Privileged,
_ => ZoneType::NonGlobal,
},
});

Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ZonePolicy.zone_type has an explicit ZONE_TYPE_ISOLATED = 2 enum value in the proto, but this mapping treats any non-1 value as NonGlobal. That silently downgrades unsupported zone types instead of rejecting them. Consider returning invalid_argument for unknown/unsupported values (e.g., 2) until the semantics are implemented.

Suggested change
let policy = req.policy.map(|proto_policy| CoreZonePolicyInput {
host_paths: proto_policy.host_paths,
allowed_zones: proto_policy.allowed_zones,
allow_ptrace: proto_policy.allow_ptrace,
zone_type: match proto_policy.zone_type {
1 => ZoneType::Privileged,
_ => ZoneType::NonGlobal,
},
});
let policy = req
.policy
.map(|proto_policy| {
let zone_type = match proto_policy.zone_type {
0 => ZoneType::NonGlobal,
1 => ZoneType::Privileged,
value => {
return Err(Status::invalid_argument(format!(
"unsupported zone_type: {value}"
)))
}
};
Ok(CoreZonePolicyInput {
host_paths: proto_policy.host_paths,
allowed_zones: proto_policy.allowed_zones,
allow_ptrace: proto_policy.allow_ptrace,
zone_type,
})
})
.transpose()?;

Copilot uses AI. Check for mistakes.
Comment thread syva-core/src/rpc/mod.rs
Comment on lines +148 to +153
let mut ebpf = self.ebpf.lock().await;
if let Err(error) = ebpf.add_zone_member(req.cgroup_id, zone_id, ZoneType::NonGlobal) {
registry.remove_container(&req.container_id, None);
return Err(Status::internal(format!(
"BPF add_zone_member failed: {error}"
)));
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attach_container always programs the BPF membership map with ZoneType::NonGlobal, so zones registered as Privileged via RegisterZone will never get the privileged flag applied in the kernel. If zone type is intended to affect enforcement, the service needs to persist zone type in the registry (or derive it from policy) and pass the correct ZoneType into add_zone_member.

Copilot uses AI. Check for mistakes.
@yairfalse
Copy link
Copy Markdown
Contributor Author

Addressed the four review comments in 15024f2.

What changed:

  • local socket startup now unlinks the socket file if permission/ownership setup fails after bind, so a failed root:syva configuration does not poison the next restart
  • the local gRPC server now removes the Unix socket file on clean shutdown as well, so normal restarts do not fail on a stale path
  • RegisterZone now rejects unsupported proto zone_type values with invalid_argument instead of silently downgrading them to NonGlobal
  • zone_type is now persisted in ZoneRegistry and attach_container uses the stored zone type when programming ZONE_MEMBERSHIP, so Privileged zones keep their kernel flag on membership attach

Verification rerun here:

  • cargo check -p syva-core
  • cargo check -p syva-core --tests
  • cargo test -p syva-core
  • cargo clippy -p syva-core -- -D warnings
  • cargo build --workspace
  • DATABASE_URL=postgres://postgres:dev@localhost:5432/syva_cp_test cargo test --workspace

I also added zone-registry tests covering default and persisted zone_type lookups.

@yairfalse yairfalse merged commit c8242e6 into main Apr 26, 2026
6 checks passed
@yairfalse yairfalse deleted the migration/phase-1-local-ingest branch April 26, 2026 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants