mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-21 08:09:55 +00:00
Reinstates the SyncMappings RPC that landed on origin/main and the
client-side fallback to GetMappingUpdate.
- proto: SyncMappings RPC + SyncMappingsRequest{Init|Ack} +
SyncMappingsResponse messages.
- management proxy.go: SyncMappings server handler, recvSyncInit,
sendSnapshotSync (per-batch send-then-wait-for-ack), drainRecv,
waitForAck; proxyConnection.syncStream + sendResponse routes the
same sendChan onto the bidi stream when set.
- proxy/server.go: trySyncMappings + handleSyncMappingsStream that
acks after each batch is processed; outer loop tries SyncMappings
first and falls back to GetMappingUpdate on Unimplemented.
Capabilities lifted into proxyCapabilities() so both code paths
use the same flags.
379 lines
13 KiB
Protocol Buffer
379 lines
13 KiB
Protocol Buffer
syntax = "proto3";
|
|
|
|
package management;
|
|
|
|
option go_package = "/proto";
|
|
|
|
import "google/protobuf/duration.proto";
|
|
import "google/protobuf/timestamp.proto";
|
|
|
|
// ProxyService - Management is the SERVER, Proxy is the CLIENT
|
|
// Proxy initiates connection to management
|
|
service ProxyService {
|
|
rpc GetMappingUpdate(GetMappingUpdateRequest) returns (stream GetMappingUpdateResponse);
|
|
|
|
// SyncMappings is a bidirectional stream that replaces GetMappingUpdate for
|
|
// new proxies. The proxy sends an initial SyncMappingsRequest to start the
|
|
// stream and then sends an ack after each batch is fully processed.
|
|
// Management waits for the ack before sending the next batch, providing
|
|
// application-level back-pressure during large initial syncs.
|
|
// Old proxies continue using GetMappingUpdate; old management servers
|
|
// return Unimplemented for this RPC and proxies fall back.
|
|
rpc SyncMappings(stream SyncMappingsRequest) returns (stream SyncMappingsResponse);
|
|
|
|
rpc SendAccessLog(SendAccessLogRequest) returns (SendAccessLogResponse);
|
|
|
|
rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse);
|
|
|
|
rpc SendStatusUpdate(SendStatusUpdateRequest) returns (SendStatusUpdateResponse);
|
|
|
|
rpc CreateProxyPeer(CreateProxyPeerRequest) returns (CreateProxyPeerResponse);
|
|
|
|
rpc GetOIDCURL(GetOIDCURLRequest) returns (GetOIDCURLResponse);
|
|
|
|
// ValidateSession validates a session token and checks user access permissions.
|
|
// Called by the proxy after receiving a session token from OIDC callback.
|
|
rpc ValidateSession(ValidateSessionRequest) returns (ValidateSessionResponse);
|
|
|
|
// ValidateTunnelPeer resolves an inbound peer by its WireGuard tunnel IP and
|
|
// checks the resolved user's access against the service's required groups.
|
|
// Acts as a fast-path equivalent of OIDC for requests originating on the
|
|
// netbird mesh: when the source IP maps to a known peer in the calling
|
|
// account and that peer's user is in the service's distribution_groups,
|
|
// the proxy can issue a session cookie without redirecting through the
|
|
// OIDC flow. Mirrors ValidateSession's response shape.
|
|
rpc ValidateTunnelPeer(ValidateTunnelPeerRequest) returns (ValidateTunnelPeerResponse);
|
|
}
|
|
|
|
// ProxyCapabilities describes what a proxy can handle.
|
|
message ProxyCapabilities {
|
|
// Whether the proxy can bind arbitrary ports for TCP/UDP/TLS services.
|
|
optional bool supports_custom_ports = 1;
|
|
// Whether the proxy requires a subdomain label in front of its cluster domain.
|
|
// When true, accounts cannot use the cluster domain bare.
|
|
optional bool require_subdomain = 2;
|
|
// Whether the proxy has CrowdSec configured and can enforce IP reputation checks.
|
|
optional bool supports_crowdsec = 3;
|
|
// Whether the proxy is running embedded in the netbird client and serving
|
|
// exclusively over the WireGuard tunnel (i.e. `netbird proxy` rather than
|
|
// the standalone netbird-proxy binary). Surfaces upstream so dashboards can
|
|
// distinguish per-peer / private clusters from centralised ones.
|
|
optional bool private = 4;
|
|
// Whether the proxy enforces ProxyMapping.private (fails closed on ValidateTunnelPeer failure). Management MUST NOT stream private mappings to proxies that don't claim this.
|
|
optional bool supports_private_service = 5;
|
|
}
|
|
|
|
// GetMappingUpdateRequest is sent to initialise a mapping stream.
|
|
message GetMappingUpdateRequest {
|
|
string proxy_id = 1;
|
|
string version = 2;
|
|
google.protobuf.Timestamp started_at = 3;
|
|
string address = 4;
|
|
ProxyCapabilities capabilities = 5;
|
|
}
|
|
|
|
// GetMappingUpdateResponse contains zero or more ProxyMappings.
|
|
// No mappings may be sent to test the liveness of the Proxy.
|
|
// Mappings that are sent should be interpreted by the Proxy appropriately.
|
|
message GetMappingUpdateResponse {
|
|
repeated ProxyMapping mapping = 1;
|
|
// initial_sync_complete is set on the last message of the initial snapshot.
|
|
// The proxy uses this to signal that startup is complete.
|
|
bool initial_sync_complete = 2;
|
|
}
|
|
|
|
enum ProxyMappingUpdateType {
|
|
UPDATE_TYPE_CREATED = 0;
|
|
UPDATE_TYPE_MODIFIED = 1;
|
|
UPDATE_TYPE_REMOVED = 2;
|
|
}
|
|
|
|
enum PathRewriteMode {
|
|
PATH_REWRITE_DEFAULT = 0;
|
|
PATH_REWRITE_PRESERVE = 1;
|
|
}
|
|
|
|
message PathTargetOptions {
|
|
bool skip_tls_verify = 1;
|
|
google.protobuf.Duration request_timeout = 2;
|
|
PathRewriteMode path_rewrite = 3;
|
|
map<string, string> custom_headers = 4;
|
|
// Send PROXY protocol v2 header to this backend.
|
|
bool proxy_protocol = 5;
|
|
// Idle timeout before a UDP session is reaped.
|
|
google.protobuf.Duration session_idle_timeout = 6;
|
|
// When true, the proxy dials this target via the host's network stack
|
|
// instead of through the embedded NetBird client. Useful for upstreams
|
|
// reachable without WireGuard (public APIs, LAN services, localhost
|
|
// sidecars). Defaults to false — embedded client is the standard path.
|
|
bool direct_upstream = 7;
|
|
}
|
|
|
|
message PathMapping {
|
|
string path = 1;
|
|
string target = 2;
|
|
PathTargetOptions options = 3;
|
|
}
|
|
|
|
message HeaderAuth {
|
|
// Header name to check, e.g. "Authorization", "X-API-Key".
|
|
string header = 1;
|
|
// argon2id hash of the expected full header value.
|
|
string hashed_value = 2;
|
|
}
|
|
|
|
message Authentication {
|
|
string session_key = 1;
|
|
int64 max_session_age_seconds = 2;
|
|
bool password = 3;
|
|
bool pin = 4;
|
|
bool oidc = 5;
|
|
repeated HeaderAuth header_auths = 6;
|
|
}
|
|
|
|
message AccessRestrictions {
|
|
repeated string allowed_cidrs = 1;
|
|
repeated string blocked_cidrs = 2;
|
|
repeated string allowed_countries = 3;
|
|
repeated string blocked_countries = 4;
|
|
// CrowdSec IP reputation mode: "", "off", "enforce", or "observe".
|
|
string crowdsec_mode = 5;
|
|
}
|
|
|
|
message ProxyMapping {
|
|
ProxyMappingUpdateType type = 1;
|
|
string id = 2;
|
|
string account_id = 3;
|
|
string domain = 4;
|
|
repeated PathMapping path = 5;
|
|
string auth_token = 6;
|
|
Authentication auth = 7;
|
|
// When true, the original Host header from the client request is passed
|
|
// through to the backend instead of being rewritten to the backend's address.
|
|
bool pass_host_header = 8;
|
|
// When true, Location headers in backend responses are rewritten to replace
|
|
// the backend address with the public-facing domain.
|
|
bool rewrite_redirects = 9;
|
|
// Service mode: "http", "tcp", "udp", or "tls".
|
|
string mode = 10;
|
|
// For L4/TLS: the port the proxy listens on.
|
|
int32 listen_port = 11;
|
|
AccessRestrictions access_restrictions = 12;
|
|
// NetBird-only: the proxy MUST call ValidateTunnelPeer and fail closed; operator auth schemes are bypassed.
|
|
bool private = 13;
|
|
}
|
|
|
|
// SendAccessLogRequest consists of one or more AccessLogs from a Proxy.
|
|
message SendAccessLogRequest {
|
|
AccessLog log = 1;
|
|
}
|
|
|
|
// SendAccessLogResponse is intentionally empty to allow for future expansion.
|
|
message SendAccessLogResponse {}
|
|
|
|
message AccessLog {
|
|
google.protobuf.Timestamp timestamp = 1;
|
|
string log_id = 2;
|
|
string account_id = 3;
|
|
string service_id = 4;
|
|
string host = 5;
|
|
string path = 6;
|
|
int64 duration_ms = 7;
|
|
string method = 8;
|
|
int32 response_code = 9;
|
|
string source_ip = 10;
|
|
string auth_mechanism = 11;
|
|
string user_id = 12;
|
|
bool auth_success = 13;
|
|
int64 bytes_upload = 14;
|
|
int64 bytes_download = 15;
|
|
string protocol = 16;
|
|
// Extra key-value metadata for the access log entry (e.g. crowdsec_verdict, scenario).
|
|
map<string, string> metadata = 17;
|
|
}
|
|
|
|
message AuthenticateRequest {
|
|
string id = 1;
|
|
string account_id = 2;
|
|
oneof request {
|
|
PasswordRequest password = 3;
|
|
PinRequest pin = 4;
|
|
HeaderAuthRequest header_auth = 5;
|
|
}
|
|
}
|
|
|
|
message HeaderAuthRequest {
|
|
string header_value = 1;
|
|
string header_name = 2;
|
|
}
|
|
|
|
message PasswordRequest {
|
|
string password = 1;
|
|
}
|
|
|
|
message PinRequest {
|
|
string pin = 1;
|
|
}
|
|
|
|
message AuthenticateResponse {
|
|
bool success = 1;
|
|
string session_token = 2;
|
|
}
|
|
|
|
enum ProxyStatus {
|
|
PROXY_STATUS_PENDING = 0;
|
|
PROXY_STATUS_ACTIVE = 1;
|
|
PROXY_STATUS_TUNNEL_NOT_CREATED = 2;
|
|
PROXY_STATUS_CERTIFICATE_PENDING = 3;
|
|
PROXY_STATUS_CERTIFICATE_FAILED = 4;
|
|
PROXY_STATUS_ERROR = 5;
|
|
}
|
|
|
|
// SendStatusUpdateRequest is sent by the proxy to update its status
|
|
message SendStatusUpdateRequest {
|
|
string service_id = 1;
|
|
string account_id = 2;
|
|
ProxyStatus status = 3;
|
|
bool certificate_issued = 4;
|
|
optional string error_message = 5;
|
|
// Per-account inbound listener state for the account that owns
|
|
// service_id. Populated only when --private-inbound is enabled and the
|
|
// embedded client for the account is up. Field numbers >=50 reserved
|
|
// for observability extensions.
|
|
optional ProxyInboundListener inbound_listener = 50;
|
|
}
|
|
|
|
// ProxyInboundListener describes a per-account inbound listener that the
|
|
// proxy has bound on the embedded netstack of the account's WireGuard
|
|
// client. Surfaced so dashboards can render "this account is reachable
|
|
// at <tunnel_ip>:<https_port> on this proxy".
|
|
message ProxyInboundListener {
|
|
// Tunnel IP the embedded netstack listens on. Same address other peers
|
|
// in the account see for the proxy peer.
|
|
string tunnel_ip = 1;
|
|
// TLS port served on tunnel_ip (auto-detected, default 443).
|
|
uint32 https_port = 2;
|
|
// Plain-HTTP port served on tunnel_ip (auto-detected, default 80).
|
|
uint32 http_port = 3;
|
|
}
|
|
|
|
// SendStatusUpdateResponse is intentionally empty to allow for future expansion
|
|
message SendStatusUpdateResponse {}
|
|
|
|
// CreateProxyPeerRequest is sent by the proxy to create a peer connection
|
|
// The token is a one-time authentication token sent via ProxyMapping
|
|
message CreateProxyPeerRequest {
|
|
string service_id = 1;
|
|
string account_id = 2;
|
|
string token = 3;
|
|
string wireguard_public_key = 4;
|
|
string cluster = 5;
|
|
}
|
|
|
|
// CreateProxyPeerResponse contains the result of peer creation
|
|
message CreateProxyPeerResponse {
|
|
bool success = 1;
|
|
optional string error_message = 2;
|
|
}
|
|
|
|
message GetOIDCURLRequest {
|
|
string id = 1;
|
|
string account_id = 2;
|
|
string redirect_url = 3;
|
|
}
|
|
|
|
message GetOIDCURLResponse {
|
|
string url = 1;
|
|
}
|
|
|
|
message ValidateSessionRequest {
|
|
string domain = 1;
|
|
string session_token = 2;
|
|
}
|
|
|
|
message ValidateSessionResponse {
|
|
bool valid = 1;
|
|
string user_id = 2;
|
|
string user_email = 3;
|
|
string denied_reason = 4;
|
|
// peer_group_ids carries the calling user's group memberships so the
|
|
// proxy can authorise policy-aware middlewares without an additional
|
|
// management round-trip.
|
|
repeated string peer_group_ids = 5;
|
|
// peer_group_names carries the human-readable display names for the
|
|
// ids in peer_group_ids, ordered identically (positional pairing).
|
|
// Stamped onto upstream requests as X-NetBird-Groups so downstream
|
|
// services can read names rather than opaque ids.
|
|
repeated string peer_group_names = 6;
|
|
}
|
|
|
|
// ValidateTunnelPeerRequest carries the inbound peer's tunnel IP and the
|
|
// service domain whose group requirements should gate access. The calling
|
|
// account is inferred from the proxy's gRPC metadata (ProxyToken).
|
|
message ValidateTunnelPeerRequest {
|
|
string tunnel_ip = 1;
|
|
string domain = 2;
|
|
}
|
|
|
|
// ValidateTunnelPeerResponse mirrors ValidateSessionResponse plus a freshly
|
|
// minted session_token: when valid is true, the proxy installs the token as
|
|
// a session cookie so subsequent requests skip the management round-trip,
|
|
// matching the OIDC flow's UX. denied_reason values:
|
|
// "peer_not_found" — no peer with that tunnel IP in the calling account
|
|
// "no_user" — peer exists but is not bound to a user
|
|
// "service_not_found"
|
|
// "account_mismatch"
|
|
// "not_in_group" — user resolved but not in service.distribution_groups
|
|
message ValidateTunnelPeerResponse {
|
|
bool valid = 1;
|
|
string user_id = 2;
|
|
string user_email = 3;
|
|
string denied_reason = 4;
|
|
// session_token is set only when valid is true. Same shape as the JWT
|
|
// the OIDC flow produces — proxy installs it via setSessionCookie so the
|
|
// tunnel fast-path is indistinguishable from OIDC for subsequent requests.
|
|
string session_token = 5;
|
|
// peer_group_ids carries the resolved peer's user group memberships so
|
|
// the proxy can authorise policy-aware middlewares without an additional
|
|
// management round-trip.
|
|
repeated string peer_group_ids = 6;
|
|
// peer_group_names carries the human-readable display names for the
|
|
// ids in peer_group_ids, ordered identically (positional pairing).
|
|
// Stamped onto upstream requests as X-NetBird-Groups so downstream
|
|
// services can read names rather than opaque ids.
|
|
repeated string peer_group_names = 7;
|
|
}
|
|
|
|
// SyncMappingsRequest is sent by the proxy on the bidirectional SyncMappings
|
|
// stream. The first message MUST be an init; all subsequent messages MUST be
|
|
// acks.
|
|
message SyncMappingsRequest {
|
|
oneof msg {
|
|
SyncMappingsInit init = 1;
|
|
SyncMappingsAck ack = 2;
|
|
}
|
|
}
|
|
|
|
// SyncMappingsInit is the first message on the stream, carrying the same
|
|
// identification fields as GetMappingUpdateRequest.
|
|
message SyncMappingsInit {
|
|
string proxy_id = 1;
|
|
string version = 2;
|
|
google.protobuf.Timestamp started_at = 3;
|
|
string address = 4;
|
|
ProxyCapabilities capabilities = 5;
|
|
}
|
|
|
|
// SyncMappingsAck is sent by the proxy after it has fully processed a batch.
|
|
// Management waits for this before sending the next batch.
|
|
message SyncMappingsAck {}
|
|
|
|
// SyncMappingsResponse is a batch of mappings sent by management.
|
|
// Identical semantics to GetMappingUpdateResponse.
|
|
message SyncMappingsResponse {
|
|
repeated ProxyMapping mapping = 1;
|
|
// initial_sync_complete is set on the last message of the initial snapshot.
|
|
bool initial_sync_complete = 2;
|
|
}
|
|
|