Files
coder/tailnet/proto/tailnet.proto
Ethan b1298a3c1e feat: add WorkspaceUpdates tailnet RPC (#14847)
Closes #14716
Closes #14717

Adds a new user-scoped tailnet API endpoint (`api/v2/tailnet`) with a new RPC stream for receiving updates on workspaces owned by a specific user, as defined in #14716. 

When a stream is started, the `WorkspaceUpdatesProvider` will begin listening on the user-scoped pubsub events implemented in #14964. When a relevant event type is seen (such as a workspace state transition), the provider will query the DB for all the workspaces (and agents) owned by the user. This gets compared against the result of the previous query to produce a set of workspace updates. 

Workspace updates can be requested for any user ID, however only workspaces the authorised user is permitted to `ActionRead` will have their updates streamed.
Opening a tunnel to an agent requires that the user can perform `ActionSSH` against the workspace containing it.
2024-11-01 14:53:53 +11:00

245 lines
5.2 KiB
Protocol Buffer

syntax = "proto3";
option go_package = "github.com/coder/coder/v2/tailnet/proto";
package coder.tailnet.v2;
import "google/protobuf/timestamp.proto"; // Importing for time.Time
import "google/protobuf/duration.proto"; // Importing for time.Duration
import "google/protobuf/wrappers.proto"; // Importing for nullable types
message DERPMap {
message HomeParams {
map<int64, double> region_score = 1;
}
HomeParams home_params = 1;
message Region {
int64 region_id = 1;
bool embedded_relay = 2;
string region_code = 3;
string region_name = 4;
bool avoid = 5;
message Node {
string name = 1;
int64 region_id = 2;
string host_name = 3;
string cert_name = 4;
string ipv4 = 5;
string ipv6 = 6;
int32 stun_port = 7;
bool stun_only = 8;
int32 derp_port = 9;
bool insecure_for_tests = 10;
bool force_http = 11;
string stun_test_ip = 12;
bool can_port_80 = 13;
}
repeated Node nodes = 6;
}
map<int64, Region> regions = 2;
}
message StreamDERPMapsRequest {}
// defined in tailnet/coordinator.go
message Node {
int64 id = 1;
google.protobuf.Timestamp as_of = 2;
bytes key = 3;
string disco = 4;
int32 preferred_derp = 5;
map<string, double> derp_latency = 6;
map<int32, string> derp_forced_websocket = 7;
repeated string addresses = 8;
repeated string allowed_ips = 9;
repeated string endpoints = 10;
}
message RefreshResumeTokenRequest {}
message RefreshResumeTokenResponse {
string token = 1;
google.protobuf.Duration refresh_in = 2;
google.protobuf.Timestamp expires_at = 3;
}
message CoordinateRequest {
message UpdateSelf {
Node node = 1;
}
UpdateSelf update_self = 1;
message Disconnect {}
Disconnect disconnect = 2;
message Tunnel {
bytes id = 1;
}
Tunnel add_tunnel = 3;
Tunnel remove_tunnel = 4;
// ReadyForHandskales are sent from destinations back to the source,
// acknowledging receipt of the source's node. If the source starts pinging
// before a ReadyForHandshake, the Wireguard handshake will likely be
// dropped.
message ReadyForHandshake {
bytes id = 1;
}
repeated ReadyForHandshake ready_for_handshake = 5;
}
message CoordinateResponse {
message PeerUpdate {
bytes id = 1;
Node node = 2;
enum Kind {
KIND_UNSPECIFIED = 0;
NODE = 1;
DISCONNECTED = 2;
LOST = 3;
READY_FOR_HANDSHAKE = 4;
}
Kind kind = 3;
string reason = 4;
}
repeated PeerUpdate peer_updates = 1;
string error = 2;
}
message IPFields {
int32 version = 1;
enum IPClass {
PUBLIC = 0;
PRIVATE = 1;
LINK_LOCAL = 2;
LOOPBACK = 3;
}
IPClass class = 2;
}
message Netcheck {
bool UDP = 1;
bool IPv6 = 2;
bool IPv4 = 3;
bool IPv6CanSend = 4;
bool IPv4CanSend = 5;
bool ICMPv4 = 6;
google.protobuf.BoolValue OSHasIPv6 = 7;
google.protobuf.BoolValue MappingVariesByDestIP = 8;
google.protobuf.BoolValue HairPinning = 9;
google.protobuf.BoolValue UPnP = 10;
google.protobuf.BoolValue PMP = 11;
google.protobuf.BoolValue PCP = 12;
int64 PreferredDERP = 13; // 0 for unknown
map<int64, google.protobuf.Duration> RegionV4Latency = 14;
map<int64, google.protobuf.Duration> RegionV6Latency = 15;
message NetcheckIP {
string hash = 1;
IPFields fields = 2;
}
NetcheckIP GlobalV4 = 17;
NetcheckIP GlobalV6 = 18;
}
message TelemetryEvent {
enum Status {
CONNECTED = 0;
DISCONNECTED = 1;
}
enum ClientType {
CLI = 0;
AGENT = 1;
CODERD = 2;
WSPROXY = 3;
}
message P2PEndpoint {
string hash = 1;
int32 port = 2;
IPFields fields = 3;
}
bytes id = 1;
google.protobuf.Timestamp time = 2;
string application = 3;
Status status = 4;
// Previously the unused DisconnectionReason,
// reserved to ensure it's not re-used.
reserved 5;
ClientType client_type = 6;
string client_version = 19;
uint64 node_id_self = 7;
uint64 node_id_remote = 8;
P2PEndpoint p2p_endpoint = 9;
int32 home_derp = 10;
DERPMap derp_map = 11;
Netcheck latest_netcheck = 12;
google.protobuf.Duration connection_age = 13;
google.protobuf.Duration connection_setup = 14;
google.protobuf.Duration p2p_setup = 15;
google.protobuf.Duration derp_latency = 16;
google.protobuf.Duration p2p_latency = 17;
google.protobuf.FloatValue throughput_mbits = 18;
}
message TelemetryRequest {
repeated TelemetryEvent events = 1;
}
message TelemetryResponse {}
message WorkspaceUpdatesRequest {
bytes workspace_owner_id = 1; // UUID
}
message WorkspaceUpdate {
repeated Workspace upserted_workspaces = 1;
repeated Agent upserted_agents = 2;
repeated Workspace deleted_workspaces = 3;
repeated Agent deleted_agents = 4;
}
message Workspace {
bytes id = 1; // UUID
string name = 2;
enum Status {
UNKNOWN = 0;
PENDING = 1;
STARTING = 2;
RUNNING = 3;
STOPPING = 4;
STOPPED = 5;
FAILED = 6;
CANCELING = 7;
CANCELED = 8;
DELETING = 9;
DELETED = 10;
}
Status status = 3;
}
message Agent {
bytes id = 1; // UUID
string name = 2;
bytes workspace_id = 3; // UUID
}
service Tailnet {
rpc PostTelemetry(TelemetryRequest) returns (TelemetryResponse);
rpc StreamDERPMaps(StreamDERPMapsRequest) returns (stream DERPMap);
rpc RefreshResumeToken(RefreshResumeTokenRequest) returns (RefreshResumeTokenResponse);
rpc Coordinate(stream CoordinateRequest) returns (stream CoordinateResponse);
rpc WorkspaceUpdates(WorkspaceUpdatesRequest) returns (stream WorkspaceUpdate);
}