diff --git a/.github/workflows/proto-validate.yml b/.github/workflows/proto-validate.yml new file mode 100644 index 0000000..bebe6db --- /dev/null +++ b/.github/workflows/proto-validate.yml @@ -0,0 +1,55 @@ +name: Proto validation + +on: + pull_request: + types: + - opened + - synchronize + - reopened + paths: + - "**/*.proto" + - "buf.yaml" + - ".github/workflows/proto-validate.yml" + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Buf + uses: bufbuild/buf-setup-action@v1 + + - name: Check formatting + run: buf format --diff --exit-code + + - name: Build image + run: buf build + + - name: Lint protobuf + run: buf lint + + - name: Check breaking changes for v1 + # Remove once new file structure is actually merged into base branch. + continue-on-error: true + run: | + buf breaking \ + --against ".git#ref=${{ github.event.pull_request.base.sha }}" \ + --path v1 \ + --path enterprise/v1 + + - name: Check breaking changes for v2 + # Remove when v2 compatibility is enforced. + continue-on-error: true + run: | + buf breaking \ + --against ".git#ref=${{ github.event.pull_request.base.sha }}" \ + --path v2 \ + --path enterprise/v2 diff --git a/README.md b/README.md index 09cbae9..03fd5ba 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,14 @@ See the [documentation](https://defguard.gitbook.io) for more information about the system. +## Buf CLI + +This repository uses [Buf](https://buf.build/) to validate the protobuf module layout and schema quality across the versioned snapshots in `v1/`, `v2/`, `enterprise/v1/`, and `enterprise/v2/`. Imports are repo-root-relative. + +- `buf build` — verify that the module and imports resolve correctly. +- `buf lint` — run the repository's Buf lint rules. +- `buf format -w` — format .proto files. + ## Community and Support Find us on Matrix: [#defguard:teonite.com](https://matrix.to/#/#defguard:teonite.com) diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..2033ae5 --- /dev/null +++ b/buf.yaml @@ -0,0 +1,14 @@ +version: v2 +modules: + - path: . + excludes: + - .direnv +lint: + use: + - BASIC + except: + - DIRECTORY_SAME_PACKAGE + - PACKAGE_DIRECTORY_MATCH +breaking: + use: + - FILE diff --git a/core/proxy.proto b/common/client_types.proto similarity index 54% rename from core/proxy.proto rename to common/client_types.proto index 38d0d46..5cc3b38 100644 --- a/core/proxy.proto +++ b/common/client_types.proto @@ -1,9 +1,25 @@ syntax = "proto3"; -package defguard.proxy; +package defguard.client_types; -import "google/protobuf/empty.proto"; +/* + * Shared message and enum definitions used by Defguard desktop clients (desktop app and CLI). + * + * This module exists to decouple the desktop client from any specific proxy protocol version. + * The client only needs a stable, version-independent set of types for: + * - Enrollment and device configuration (DeviceConfigResponse and its dependencies) + * - Periodic configuration polling (InstanceInfoRequest/Response) + * - Platform info reporting (ClientPlatformInfo) + * + * Both v1 and v2 proxy protocol definitions import this file and reference these types in + * their CoreRequest/CoreResponse envelopes, ensuring that a single client build can + * communicate with proxies running either protocol version without any code changes. + * + * Types that are proxy-version-specific (e.g. gRPC envelope messages, setup/certificate + * provisioning, password reset flows) are intentionally NOT included here. + */ // Enrollment & Desktop Client activation + message EnrollmentStartRequest { string token = 1; } @@ -33,7 +49,7 @@ message EnrollmentSettings { bool only_client_activation = 2; // Only admins can add devices so vpn step is skipped bool admin_device_management = 3; - // Enable Email method for MFA setup + // Enable Email method for MFA setup bool smtp_configured = 4; // MFA setup is not skippable bool mfa_required = 5; @@ -60,6 +76,11 @@ message NewDevice { optional string token = 3; } +message ExistingDevice { + string pubkey = 1; + optional string token = 2; +} + message Device { int64 id = 1; string name = 2; @@ -68,6 +89,8 @@ message Device { int64 created_at = 5; } +// Device configuration + enum LocationMfaMode { LOCATION_MFA_MODE_UNSPECIFIED = 0; LOCATION_MFA_MODE_DISABLED = 1; @@ -92,7 +115,7 @@ message DeviceConfig { string pubkey = 6; string allowed_ips = 7; optional string dns = 8; - // DEPRECATED(1.5): superseeded by location_mfa_mode + // DEPRECATED(1.5): superseded by location_mfa_mode bool mfa_enabled = 9 [deprecated = true]; int32 keepalive_interval = 10; optional LocationMfaMode location_mfa_mode = 11; @@ -112,7 +135,7 @@ message InstanceInfo { string proxy_url = 4; string username = 5; bool enterprise_enabled = 6; - // DEPRECATED(1.6): superseeded by client_traffic_policy + // DEPRECATED(1.6): superseded by client_traffic_policy bool disable_all_traffic = 7 [deprecated = true]; optional string openid_display_name = 8; optional ClientTrafficPolicy client_traffic_policy = 9; @@ -126,6 +149,8 @@ message DeviceConfigResponse { optional string token = 4; } +// Configuration polling + message InstanceInfoRequest { string token = 1; } @@ -134,30 +159,20 @@ message InstanceInfoResponse { DeviceConfigResponse device_config = 1; } -message ExistingDevice { - string pubkey = 1; - optional string token = 2; -} - -// Password Reset -message PasswordResetStartRequest { - string token = 1; -} - -message PasswordResetInitializeRequest { - string email = 1; -} - -message PasswordResetStartResponse { - int64 deadline_timestamp = 1; -} +// Platform info sent as a header with every request to the proxy -message PasswordResetRequest { - string password = 1; - optional string token = 2; +message ClientPlatformInfo { + string os_family = 1; + string os_type = 2; + string version = 3; + optional string edition = 4; + optional string codename = 5; + optional string bitness = 6; + optional string architecture = 7; } // Client MFA + enum MfaMethod { TOTP = 0; EMAIL = 1; @@ -166,14 +181,6 @@ enum MfaMethod { MOBILE_APPROVE = 4; } -message ClientMfaTokenValidationRequest { - string token = 1; -} - -message ClientMfaTokenValidationResponse { - bool token_valid = 1; -} - message ClientMfaStartRequest { int64 location_id = 1; string pubkey = 2; @@ -197,54 +204,6 @@ message ClientMfaFinishResponse { optional string token = 2; } -message AuthInfoRequest { - string redirect_url = 1; - optional string state = 2; -} - -message AuthInfoResponse { - string url = 1; - string csrf_token = 2; - string nonce = 3; - optional string button_display_name = 4; -} - -message AuthCallbackRequest { - string code = 1; - string nonce = 2; - string callback_url = 3; -} - -message AuthCallbackResponse { - string url = 1; - string token = 2; -} - -message ClientMfaOidcAuthenticateRequest { - string code = 1; - string state = 2; - string callback_url = 3; - string nonce = 4; -} - -message ClientPlatformInfo { - string os_family = 1; - string os_type = 2; - string version = 3; - optional string edition = 4; - optional string codename = 5; - optional string bitness = 6; - optional string architecture = 7; -} - -// Common client info -message DeviceInfo { - string ip_address = 1; - optional string user_agent = 2; - optional string version = 3; - optional string platform = 4; -} - message RegisterMobileAuthRequest { string token = 1; string auth_pub_key = 2; @@ -252,6 +211,7 @@ message RegisterMobileAuthRequest { } // TOTP and Email MFA Setup + message CodeMfaSetupStartRequest { MfaMethod method = 1; string token = 2; @@ -272,71 +232,17 @@ message CodeMfaSetupFinishResponse { repeated string recovery_codes = 1; } -/* - * Error response variant. - * Due to reverse proxy -> core communication this is how we - * can return gRPC errors from core. - */ -message CoreError { - int32 status_code = 1; - string message = 2; -} - -/* - * CoreResponse represents messages send from core to proxy - * in response to CoreRequest. - */ -message CoreResponse { - uint64 id = 1; - oneof payload { - google.protobuf.Empty empty = 2; - EnrollmentStartResponse enrollment_start = 3; - DeviceConfigResponse device_config = 4; - PasswordResetStartResponse password_reset_start = 5; - ClientMfaStartResponse client_mfa_start = 6; - ClientMfaFinishResponse client_mfa_finish = 7; - CoreError core_error = 8; - InstanceInfoResponse instance_info = 9; - AuthInfoResponse auth_info = 13; - AuthCallbackResponse auth_callback = 14; - ClientMfaTokenValidationResponse client_mfa_token_validation = 15; - CodeMfaSetupStartResponse code_mfa_setup_start_response = 16; - CodeMfaSetupFinishResponse code_mfa_setup_finish_response = 17; - } -} +// OIDC authentication flow -/* - * CoreRequest represents messages send from proxy to core. - */ -message CoreRequest { - uint64 id = 1; - DeviceInfo device_info = 2; - oneof payload { - EnrollmentStartRequest enrollment_start = 3; - ActivateUserRequest activate_user = 4; - NewDevice new_device = 5; - ExistingDevice existing_device = 6; - PasswordResetInitializeRequest password_reset_init = 7; - PasswordResetStartRequest password_reset_start = 8; - PasswordResetRequest password_reset = 9; - ClientMfaStartRequest client_mfa_start = 10; - ClientMfaFinishRequest client_mfa_finish = 11; - InstanceInfoRequest instance_info = 12; - AuthInfoRequest auth_info = 13; - AuthCallbackRequest auth_callback = 14; - ClientMfaOidcAuthenticateRequest client_mfa_oidc_authenticate = 15; - RegisterMobileAuthRequest register_mobile_auth = 16; - ClientMfaTokenValidationRequest client_mfa_token_validation = 17; - CodeMfaSetupStartRequest code_mfa_setup_start = 18; - CodeMfaSetupFinishRequest code_mfa_setup_finish = 19; - } +enum AuthFlowType { + AUTH_FLOW_TYPE_UNSPECIFIED = 0; + AUTH_FLOW_TYPE_ENROLLMENT = 1; + AUTH_FLOW_TYPE_MFA = 2; } -/* - * Bi-directional communication between core and proxy. - * For security reasons, the connection has to be initiated by core, - * so requests and responses are actually sent in reverse. - */ -service Proxy { - rpc Bidi(stream CoreResponse) returns (stream CoreRequest); +message AuthInfoRequest { + // DEPRECATED(2.0): superseded by auth_flow_type; kept for legacy client compatibility + string redirect_url = 1 [deprecated = true]; + optional string state = 2; + AuthFlowType auth_flow_type = 3; } diff --git a/enterprise/firewall/firewall.proto b/enterprise/v1/firewall/firewall.proto similarity index 97% rename from enterprise/firewall/firewall.proto rename to enterprise/v1/firewall/firewall.proto index 6b7413a..e3b8373 100644 --- a/enterprise/firewall/firewall.proto +++ b/enterprise/v1/firewall/firewall.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package enterprise.firewall; +package defguard.enterprise.firewall.v1; // Describes target configuration of the firewall message FirewallConfig { diff --git a/enterprise/v2/firewall/firewall.proto b/enterprise/v2/firewall/firewall.proto new file mode 100644 index 0000000..370dedb --- /dev/null +++ b/enterprise/v2/firewall/firewall.proto @@ -0,0 +1,81 @@ +syntax = "proto3"; +package defguard.enterprise.firewall.v2; + +// Describes target configuration of the firewall +message FirewallConfig { + FirewallPolicy default_policy = 1; + repeated FirewallRule rules = 2; + repeated SnatBinding snat_bindings = 3; +} + +enum IpVersion { + IP_VERSION_UNSPECIFIED = 0; + IP_VERSION_IPV4 = 1; + IP_VERSION_IPV6 = 2; +} + +enum FirewallPolicy { + FIREWALL_POLICY_UNSPECIFIED = 0; + FIREWALL_POLICY_ALLOW = 1; + FIREWALL_POLICY_DENY = 2; +} + +message FirewallRule { + int64 id = 1; + repeated IpAddress source_addrs = 2; + repeated IpAddress destination_addrs = 3; + repeated Port destination_ports = 4; + repeated Protocol protocols = 5; + FirewallPolicy verdict = 6; + optional string comment = 7; + IpVersion ip_version = 8; +} + +message SnatBinding { + int64 id = 1; + repeated IpAddress source_addrs = 2; + string public_ip = 3; + optional string comment = 4; +} + +// IPv4 or IPv6 address +// expected type is determined by a given FirewallRule +message IpAddress { + oneof address { + // single IP address + string ip = 1; + // range of IPs, e.g. 10.0.10.1-10.0.20.3 + IpRange ip_range = 2; + // IP subnet using CIDR notation, e.g. 10.0.10.0/24 + string ip_subnet = 3; + } +} + +// inclusive IP range +message IpRange { + string start = 1; + string end = 2; +} + +// wrapper message since `oneof` itself cannot be repeated +message Port { + oneof port { + uint32 single_port = 1; + PortRange port_range = 2; + } +} + +// inclusive port range +message PortRange { + uint32 start = 1; + uint32 end = 2; +} + +// Specific IDs are used to align with the standard below: +// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/in.h +enum Protocol { + PROTOCOL_UNSPECIFIED = 0; + PROTOCOL_ICMP = 1; + PROTOCOL_TCP = 6; + PROTOCOL_UDP = 17; +} diff --git a/client/client.proto b/v1/client/client.proto similarity index 85% rename from client/client.proto rename to v1/client/client.proto index 5ff8e29..20ed0cf 100644 --- a/client/client.proto +++ b/v1/client/client.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package client; +package defguard.client.v1; import "google/protobuf/empty.proto"; @@ -79,10 +79,7 @@ message StopServiceLocationRequest { service DesktopDaemonService { rpc CreateInterface(CreateInterfaceRequest) returns (google.protobuf.Empty); rpc RemoveInterface(RemoveInterfaceRequest) returns (google.protobuf.Empty); - rpc ReadInterfaceData(ReadInterfaceDataRequest) - returns (stream InterfaceData); - rpc SaveServiceLocations(SaveServiceLocationsRequest) - returns (google.protobuf.Empty); - rpc DeleteServiceLocations(DeleteServiceLocationsRequest) - returns (google.protobuf.Empty); + rpc ReadInterfaceData(ReadInterfaceDataRequest) returns (stream InterfaceData); + rpc SaveServiceLocations(SaveServiceLocationsRequest) returns (google.protobuf.Empty); + rpc DeleteServiceLocations(DeleteServiceLocationsRequest) returns (google.protobuf.Empty); } diff --git a/core/auth.proto b/v1/core/auth.proto similarity index 90% rename from core/auth.proto rename to v1/core/auth.proto index 0653b08..f57c610 100644 --- a/core/auth.proto +++ b/v1/core/auth.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package auth; +package defguard.auth.v1; service AuthService { rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse); diff --git a/v1/core/proxy.proto b/v1/core/proxy.proto new file mode 100644 index 0000000..b160963 --- /dev/null +++ b/v1/core/proxy.proto @@ -0,0 +1,135 @@ +syntax = "proto3"; +package defguard.proxy.v1; + +import "common/client_types.proto"; +import "google/protobuf/empty.proto"; + +// Password Reset +message PasswordResetStartRequest { + string token = 1; +} + +message PasswordResetInitializeRequest { + string email = 1; +} + +message PasswordResetStartResponse { + int64 deadline_timestamp = 1; +} + +message PasswordResetRequest { + string password = 1; + optional string token = 2; +} + +// Client MFA (proxy-internal) + +message ClientMfaTokenValidationRequest { + string token = 1; +} + +message ClientMfaTokenValidationResponse { + bool token_valid = 1; +} + +message AuthInfoResponse { + string url = 1; + string csrf_token = 2; + string nonce = 3; + optional string button_display_name = 4; +} + +message AuthCallbackRequest { + string code = 1; + string nonce = 2; + string callback_url = 3; +} + +message AuthCallbackResponse { + string url = 1; + string token = 2; +} + +message ClientMfaOidcAuthenticateRequest { + string code = 1; + string state = 2; + string callback_url = 3; + string nonce = 4; +} + +// Common client info +message DeviceInfo { + string ip_address = 1; + optional string user_agent = 2; + optional string version = 3; + optional string platform = 4; +} + +/* + * Error response variant. + * Due to reverse proxy -> core communication this is how we + * can return gRPC errors from core. + */ +message CoreError { + int32 status_code = 1; + string message = 2; +} + +/* + * CoreResponse represents messages send from core to proxy + * in response to CoreRequest. + */ +message CoreResponse { + uint64 id = 1; + oneof payload { + google.protobuf.Empty empty = 2; + defguard.client_types.EnrollmentStartResponse enrollment_start = 3; + defguard.client_types.DeviceConfigResponse device_config = 4; + PasswordResetStartResponse password_reset_start = 5; + defguard.client_types.ClientMfaStartResponse client_mfa_start = 6; + defguard.client_types.ClientMfaFinishResponse client_mfa_finish = 7; + CoreError core_error = 8; + defguard.client_types.InstanceInfoResponse instance_info = 9; + AuthInfoResponse auth_info = 13; + AuthCallbackResponse auth_callback = 14; + ClientMfaTokenValidationResponse client_mfa_token_validation = 15; + defguard.client_types.CodeMfaSetupStartResponse code_mfa_setup_start_response = 16; + defguard.client_types.CodeMfaSetupFinishResponse code_mfa_setup_finish_response = 17; + } +} + +/* + * CoreRequest represents messages send from proxy to core. + */ +message CoreRequest { + uint64 id = 1; + DeviceInfo device_info = 2; + oneof payload { + defguard.client_types.EnrollmentStartRequest enrollment_start = 3; + defguard.client_types.ActivateUserRequest activate_user = 4; + defguard.client_types.NewDevice new_device = 5; + defguard.client_types.ExistingDevice existing_device = 6; + PasswordResetInitializeRequest password_reset_init = 7; + PasswordResetStartRequest password_reset_start = 8; + PasswordResetRequest password_reset = 9; + defguard.client_types.ClientMfaStartRequest client_mfa_start = 10; + defguard.client_types.ClientMfaFinishRequest client_mfa_finish = 11; + defguard.client_types.InstanceInfoRequest instance_info = 12; + defguard.client_types.AuthInfoRequest auth_info = 13; + AuthCallbackRequest auth_callback = 14; + ClientMfaOidcAuthenticateRequest client_mfa_oidc_authenticate = 15; + defguard.client_types.RegisterMobileAuthRequest register_mobile_auth = 16; + ClientMfaTokenValidationRequest client_mfa_token_validation = 17; + defguard.client_types.CodeMfaSetupStartRequest code_mfa_setup_start = 18; + defguard.client_types.CodeMfaSetupFinishRequest code_mfa_setup_finish = 19; + } +} + +/* + * Bi-directional communication between core and proxy. + * For security reasons, the connection has to be initiated by core, + * so requests and responses are actually sent in reverse. + */ +service Proxy { + rpc Bidi(stream CoreResponse) returns (stream CoreRequest); +} diff --git a/wireguard/gateway.proto b/v1/wireguard/gateway.proto similarity index 85% rename from wireguard/gateway.proto rename to v1/wireguard/gateway.proto index 4bef59d..37c8886 100644 --- a/wireguard/gateway.proto +++ b/v1/wireguard/gateway.proto @@ -1,7 +1,7 @@ syntax = "proto3"; -package gateway; +package defguard.gateway.v1; -import "firewall.proto"; +import "enterprise/v1/firewall/firewall.proto"; import "google/protobuf/empty.proto"; message ConfigurationRequest { @@ -15,7 +15,7 @@ message Configuration { uint32 port = 4; repeated Peer peers = 5; repeated string addresses = 6; - optional enterprise.firewall.FirewallConfig firewall_config = 7; + optional defguard.enterprise.firewall.v1.FirewallConfig firewall_config = 7; } enum UpdateType { @@ -36,7 +36,7 @@ message Update { oneof update { Peer peer = 2; Configuration network = 3; - enterprise.firewall.FirewallConfig firewall_config = 4; + defguard.enterprise.firewall.v1.FirewallConfig firewall_config = 4; google.protobuf.Empty disable_firewall = 5; } } diff --git a/worker/worker.proto b/v1/worker/worker.proto similarity index 95% rename from worker/worker.proto rename to v1/worker/worker.proto index 46dee21..7a722ec 100644 --- a/worker/worker.proto +++ b/v1/worker/worker.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package worker; +package defguard.worker.v1; import "google/protobuf/empty.proto"; diff --git a/v2/common.proto b/v2/common.proto new file mode 100644 index 0000000..b40126f --- /dev/null +++ b/v2/common.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; +package defguard.common.v2; + +/* + * Raw DER-encoded certificate or key payload. + */ +message DerPayload { + bytes der_data = 1; +} + +/* + * TLS certificate identity used to request a CSR from a component. + */ +message CertificateInfo { + string cert_hostname = 1; +} + +/* + * Structured log line streamed by a component during setup. + */ +message LogEntry { + string level = 1; + string target = 2; + string message = 3; + string timestamp = 4; + map fields = 5; +} + +/* + * TLS certificate bundle sent from Core to a component during setup. + * All fields are DER-encoded binary. + * + * component_cert_der - the component's signed server certificate. + * ca_cert_der - the CA certificate; used by the component to verify + * Core's client certificate chain during mTLS. + * core_client_cert_der - Core's client certificate; stored by the component + * and used to pin the exact cert Core must present on + * every subsequent gRPC connection. + */ +message CertBundle { + bytes component_cert_der = 1; + bytes ca_cert_der = 2; + bytes core_client_cert_der = 3; +} diff --git a/v2/gateway.proto b/v2/gateway.proto new file mode 100644 index 0000000..8b15750 --- /dev/null +++ b/v2/gateway.proto @@ -0,0 +1,102 @@ +syntax = "proto3"; +package defguard.gateway.v2; + +import "enterprise/v2/firewall/firewall.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "v2/common.proto"; + +/* + * Networking and VPN configuration send from Core to Gateway. + */ +message Configuration { + string name = 1; + string private_key = 2; + uint32 port = 3; + repeated Peer peers = 4; + repeated string addresses = 5; + optional defguard.enterprise.firewall.v2.FirewallConfig firewall_config = 6; + uint32 mtu = 7; + uint32 fwmark = 8; +} + +enum UpdateType { + UPDATE_TYPE_UNSPECIFIED = 0; + UPDATE_TYPE_CREATE = 1; + UPDATE_TYPE_MODIFY = 2; + UPDATE_TYPE_DELETE = 3; +} + +message Peer { + string pubkey = 1; + repeated string allowed_ips = 2; + optional string preshared_key = 3; + optional uint32 keepalive_interval = 4; +} + +message Update { + UpdateType update_type = 1; + oneof update { + Peer peer = 2; + Configuration network = 3; + defguard.enterprise.firewall.v2.FirewallConfig firewall_config = 4; + google.protobuf.Empty disable_firewall = 5; + } +} + +message PeerStats { + string public_key = 1; + string endpoint = 2; + uint64 upload = 3; + uint64 download = 4; + uint32 keepalive_interval = 5; + optional google.protobuf.Timestamp latest_handshake = 6; + string allowed_ips = 7; +} + +/* + * CoreResponse represents messages send from Core to Gateway + * in response to CoreRequest. + */ +message CoreResponse { + uint64 id = 1; + oneof payload { + // Allow empty messages to keep the connection alive. + google.protobuf.Empty empty = 2; + Configuration config = 3; + Update update = 4; + } +} + +/* + * CoreRequest represents messages send from Gateway to Core. + */ +message CoreRequest { + uint64 id = 1; + oneof payload { + PeerStats peer_stats = 2; + google.protobuf.Empty config_request = 3; + } +} + +service Gateway { + /* + * Bi-directional communication between Core and Gateway. + * For security reasons, the connection has to be initiated by Core, + * so requests and responses are actually sent in reverse. + */ + rpc Bidi(stream CoreResponse) returns (stream CoreRequest); + /* + * Purges existing on-disk gRPC TLS credentials and signals the server + * to re-enter setup mode so that core can provision new ones. + */ + rpc Purge(google.protobuf.Empty) returns (google.protobuf.Empty); +} + +// Service used for initial Gateway setup, for configuring TLS certificate +// on Gateway for gRPC communication. +service GatewaySetup { + rpc Start(google.protobuf.Empty) returns (stream defguard.common.v2.LogEntry); + rpc GetCsr(defguard.common.v2.CertificateInfo) returns (defguard.common.v2.DerPayload); + rpc SendCert(defguard.common.v2.CertBundle) returns (google.protobuf.Empty); +} diff --git a/v2/proxy.proto b/v2/proxy.proto new file mode 100644 index 0000000..ba4bf51 --- /dev/null +++ b/v2/proxy.proto @@ -0,0 +1,230 @@ +syntax = "proto3"; +package defguard.proxy.v2; + +import "common/client_types.proto"; +import "google/protobuf/empty.proto"; +import "v2/common.proto"; + +// Password Reset (proxy-internal) + +message PasswordResetInitializeRequest { + string email = 1; +} + +message PasswordResetStartRequest { + string token = 1; +} + +message PasswordResetStartResponse { + int64 deadline_timestamp = 1; +} + +message PasswordResetRequest { + string password = 1; + optional string token = 2; +} + +message ClientMfaTokenValidationRequest { + string token = 1; +} + +message ClientMfaTokenValidationResponse { + bool token_valid = 1; +} + +message AuthInfoResponse { + string url = 1; + string csrf_token = 2; + string nonce = 3; + optional string button_display_name = 4; +} + +message AuthCallbackRequest { + string code = 1; + string nonce = 2; +} + +message AuthCallbackResponse { + string url = 1; + string token = 2; +} + +message ClientMfaOidcAuthenticateRequest { + string code = 1; + string state = 2; + string nonce = 3; +} + +// Common client info +message DeviceInfo { + string ip_address = 1; + optional string user_agent = 2; + optional string version = 3; + optional string platform = 4; +} + +message AwaitRemoteMfaFinishRequest { + string token = 1; +} + +message AwaitRemoteMfaFinishResponse { + string preshared_key = 1; +} + +message InitialInfo { + bytes private_cookies_key = 1; +} + +/* + * Error response variant. + * Due to reverse proxy -> core communication this is how we + * can return gRPC errors from core. + */ +message CoreError { + int32 status_code = 1; + string message = 2; +} + +/* + * CoreResponse represents messages send from core to proxy + * in response to CoreRequest. + */ +message CoreResponse { + uint64 id = 1; + oneof payload { + google.protobuf.Empty empty = 2; + defguard.client_types.EnrollmentStartResponse enrollment_start = 3; + defguard.client_types.DeviceConfigResponse device_config = 4; + PasswordResetStartResponse password_reset_start = 5; + defguard.client_types.ClientMfaStartResponse client_mfa_start = 6; + defguard.client_types.ClientMfaFinishResponse client_mfa_finish = 7; + CoreError core_error = 8; + defguard.client_types.InstanceInfoResponse instance_info = 9; + AuthInfoResponse auth_info = 10; + AuthCallbackResponse auth_callback = 11; + ClientMfaTokenValidationResponse client_mfa_token_validation = 12; + defguard.client_types.CodeMfaSetupStartResponse code_mfa_setup_start_response = 13; + defguard.client_types.CodeMfaSetupFinishResponse code_mfa_setup_finish_response = 14; + InitialInfo initial_info = 15; + AwaitRemoteMfaFinishResponse await_remote_mfa_finish = 16; + HttpsCerts https_certs = 17; + google.protobuf.Empty clear_https_certs = 18; + } +} + +message HttpsCerts { + string cert_pem = 1; + string key_pem = 2; +} + +/* + * Sent from core to proxy to trigger ACME HTTP-01 certificate issuance. + */ +message AcmeChallenge { + string domain = 1; + // JSON-serialized instant-acme AccountCredentials; empty string means create a new account. + string account_credentials_json = 2; +} + +/* + * Sent from proxy to core after ACME certificate issuance completes. + */ +message AcmeCertificate { + string cert_pem = 1; + string key_pem = 2; + // JSON-serialized instant-acme AccountCredentials for reuse on renewal. + string account_credentials_json = 3; +} + +/* + * Progress steps emitted by the proxy during ACME HTTP-01 certificate issuance. + * Streamed as AcmeIssueEvent payloads before the final AcmeCertificate. + */ +enum AcmeStep { + ACME_STEP_UNSPECIFIED = 0; + ACME_STEP_CONNECTING = 1; + ACME_STEP_VALIDATING_DOMAIN = 2; + ACME_STEP_ISSUING_CERTIFICATE = 3; + ACME_STEP_CHECKING_DOMAIN = 4; +} + +message AcmeProgress { + AcmeStep step = 1; +} + +/* + * Log lines collected by the proxy during ACME certificate issuance. + * Sent once on failure, immediately before the gRPC error status. + */ +message AcmeLogs { + repeated string lines = 1; +} + +/* + * Wrapper message streamed by IssueAcme. + * Carries either a progress update, the final certificate, or (on failure) + * the collected proxy log lines. + */ +message AcmeIssueEvent { + oneof payload { + AcmeProgress progress = 1; + AcmeCertificate certificate = 2; + AcmeLogs logs = 3; + } +} + +/* + * CoreRequest represents messages send from proxy to core. + */ +message CoreRequest { + uint64 id = 1; + DeviceInfo device_info = 2; + oneof payload { + defguard.client_types.EnrollmentStartRequest enrollment_start = 3; + defguard.client_types.ActivateUserRequest activate_user = 4; + defguard.client_types.NewDevice new_device = 5; + defguard.client_types.ExistingDevice existing_device = 6; + PasswordResetInitializeRequest password_reset_init = 7; + PasswordResetStartRequest password_reset_start = 8; + PasswordResetRequest password_reset = 9; + defguard.client_types.ClientMfaStartRequest client_mfa_start = 10; + defguard.client_types.ClientMfaFinishRequest client_mfa_finish = 11; + defguard.client_types.InstanceInfoRequest instance_info = 12; + defguard.client_types.AuthInfoRequest auth_info = 13; + AuthCallbackRequest auth_callback = 14; + ClientMfaOidcAuthenticateRequest client_mfa_oidc_authenticate = 15; + defguard.client_types.RegisterMobileAuthRequest register_mobile_auth = 16; + ClientMfaTokenValidationRequest client_mfa_token_validation = 17; + defguard.client_types.CodeMfaSetupStartRequest code_mfa_setup_start = 18; + defguard.client_types.CodeMfaSetupFinishRequest code_mfa_setup_finish = 19; + AwaitRemoteMfaFinishRequest await_remote_mfa_finish = 20; + AcmeCertificate acme_certificate = 21; + } +} + +service Proxy { + /* + * Bi-directional communication between core and proxy. + * For security reasons, the connection has to be initiated by core, + * so requests and responses are actually sent in reverse. + */ + rpc Bidi(stream CoreResponse) returns (stream CoreRequest); + /* + * Purges existing on-disk gRPC TLS credentials and signals the server + * to re-enter setup mode so that core can provision new ones. + */ + rpc Purge(google.protobuf.Empty) returns (google.protobuf.Empty); + /* + * Triggers ACME HTTP-01 certificate issuance directly on the proxy. + * Streams progress events followed by the issued certificate. + */ + rpc TriggerAcme(AcmeChallenge) returns (stream AcmeIssueEvent); +} + +// Service used for initial Proxy setup, for configuring TLS certificate +// on Proxy for gRPC communication. +service ProxySetup { + rpc Start(google.protobuf.Empty) returns (stream defguard.common.v2.LogEntry); + rpc GetCsr(defguard.common.v2.CertificateInfo) returns (defguard.common.v2.DerPayload); + rpc SendCert(defguard.common.v2.CertBundle) returns (google.protobuf.Empty); +}