From c8a063219640ff793b8b444fe7f7a2b47cf678aa Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 7 Jan 2026 09:17:33 +0100 Subject: [PATCH 01/17] Protos for reversed gateway gRPC (#26) --- wireguard/gateway.proto | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/wireguard/gateway.proto b/wireguard/gateway.proto index 4bef59d..643371a 100644 --- a/wireguard/gateway.proto +++ b/wireguard/gateway.proto @@ -5,9 +5,15 @@ import "firewall.proto"; import "google/protobuf/empty.proto"; message ConfigurationRequest { - optional string name = 1; + // DEPRECATED(2.0): Gateway needs to authenticate with `auth_token`. + optional string name = 1 [deprecated = true]; + string auth_token = 2; + string hostname = 3; } +/* + * Networking and VPN configuration send from Core to Gateway. + */ message Configuration { string name = 1; string prvkey = 2; @@ -52,18 +58,35 @@ message PeerStats { } /* - * Allow empty messages to keep the connection alive. + * CoreResponse represents messages send from Core to Gateway + * in response to CoreRequest. */ -message StatsUpdate { +message CoreResponse { uint64 id = 1; oneof payload { + // Allow empty messages to keep the connection alive. google.protobuf.Empty empty = 2; - PeerStats peer_stats = 3; + Configuration config = 3; + Update update = 4; } } -service GatewayService { - rpc Config(ConfigurationRequest) returns (Configuration); - rpc Updates(google.protobuf.Empty) returns (stream Update); - rpc Stats(stream StatsUpdate) returns (google.protobuf.Empty); +/* + * CoreRequest represents messages send from Gateway to Core. + */ +message CoreRequest { + uint64 id = 1; + oneof payload { + PeerStats peer_stats = 2; + ConfigurationRequest config_request = 3; + } +} + +/* + * Bi-directional communication between Core and Gateway. + * For security reasons, the connection has to be initiated by Core, + * so requests and responses are actually send in reverse. + */ +service Gateway { + rpc Bidi (stream CoreResponse) returns (stream CoreRequest); } From 0db6f5cb5ba834abcf29dddbe4076faa7f69e363 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 8 Jan 2026 14:25:31 +0100 Subject: [PATCH 02/17] Add MTU and FwMark to gateway.proto (#57) --- wireguard/gateway.proto | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wireguard/gateway.proto b/wireguard/gateway.proto index 643371a..dc7f348 100644 --- a/wireguard/gateway.proto +++ b/wireguard/gateway.proto @@ -22,6 +22,8 @@ message Configuration { repeated Peer peers = 5; repeated string addresses = 6; optional enterprise.firewall.FirewallConfig firewall_config = 7; + optional uint32 mtu = 8; + optional uint32 fwmark = 9; } enum UpdateType { @@ -88,5 +90,5 @@ message CoreRequest { * so requests and responses are actually send in reverse. */ service Gateway { - rpc Bidi (stream CoreResponse) returns (stream CoreRequest); + rpc Bidi(stream CoreResponse) returns (stream CoreRequest); } From c48340f72b9de3a69cf71318c75ff1361ebd7897 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Mon, 12 Jan 2026 10:36:26 +0100 Subject: [PATCH 03/17] Core certificate authority, part 1: Proxy (#58) * core ca part 1 * reduce custom types * Update proxy.proto --- core/proxy.proto | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/core/proxy.proto b/core/proxy.proto index 38d0d46..9c8dcc6 100644 --- a/core/proxy.proto +++ b/core/proxy.proto @@ -332,11 +332,25 @@ message CoreRequest { } } -/* - * 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. - */ +message InitialSetupInfo { + string cert_hostname = 1; +} + +message DerPayload { + bytes der_data = 1; +} + 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); } + +// Service used for initial Proxy setup, used for configuring TLS certificate on Proxy for gRPC communication. +service ProxySetup { + rpc Start(InitialSetupInfo) returns (DerPayload); + rpc SendCert(DerPayload) returns (google.protobuf.Empty); +} From 161c6c677662130924e8bac0c16421b8ed085d33 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:25:25 +0100 Subject: [PATCH 04/17] Core certificate authority, part 2: Gateway (#59) * remove tokens, add setup services * unformat protobufs --- wireguard/gateway.proto | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/wireguard/gateway.proto b/wireguard/gateway.proto index dc7f348..8a8ccd0 100644 --- a/wireguard/gateway.proto +++ b/wireguard/gateway.proto @@ -7,7 +7,6 @@ import "google/protobuf/empty.proto"; message ConfigurationRequest { // DEPRECATED(2.0): Gateway needs to authenticate with `auth_token`. optional string name = 1 [deprecated = true]; - string auth_token = 2; string hostname = 3; } @@ -92,3 +91,17 @@ message CoreRequest { service Gateway { rpc Bidi(stream CoreResponse) returns (stream CoreRequest); } + +message InitialSetupInfo { + string cert_hostname = 1; +} + +message DerPayload { + bytes der_data = 1; +} + +// Service used for initial Gateway setup, for configuring TLS certificate on Gateway for gRPC communication. +service GatewaySetup { + rpc Start(InitialSetupInfo) returns (DerPayload); + rpc SendCert(DerPayload) returns (google.protobuf.Empty); +} From 02d3f5f2d0243be774eb73979ce77f534150260d Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 20 Jan 2026 16:03:41 +0100 Subject: [PATCH 05/17] send cookie keys via protos (#63) --- core/proxy.proto | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/proxy.proto b/core/proxy.proto index 9c8dcc6..1f17fde 100644 --- a/core/proxy.proto +++ b/core/proxy.proto @@ -272,6 +272,10 @@ message CodeMfaSetupFinishResponse { repeated string recovery_codes = 1; } +message InitialInfo { + bytes private_cookies_key = 1; +} + /* * Error response variant. * Due to reverse proxy -> core communication this is how we @@ -302,6 +306,7 @@ message CoreResponse { ClientMfaTokenValidationResponse client_mfa_token_validation = 15; CodeMfaSetupStartResponse code_mfa_setup_start_response = 16; CodeMfaSetupFinishResponse code_mfa_setup_finish_response = 17; + InitialInfo initial_info = 18; } } From 906412eb50ac605f4904a355c2f325f3645c117e Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 23 Jan 2026 11:55:57 +0100 Subject: [PATCH 06/17] MTU and FwMark are not optional anymore (#64) --- wireguard/gateway.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wireguard/gateway.proto b/wireguard/gateway.proto index 8a8ccd0..034ac00 100644 --- a/wireguard/gateway.proto +++ b/wireguard/gateway.proto @@ -21,8 +21,8 @@ message Configuration { repeated Peer peers = 5; repeated string addresses = 6; optional enterprise.firewall.FirewallConfig firewall_config = 7; - optional uint32 mtu = 8; - optional uint32 fwmark = 9; + uint32 mtu = 8; + uint32 fwmark = 9; } enum UpdateType { From 4134358160e4f819515c9a6e5c014434cfb46d74 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Mon, 26 Jan 2026 12:18:25 +0100 Subject: [PATCH 07/17] Send logs from Proxy to Core during setup (#61) * send logs to proxy * Update proxy.proto --- core/proxy.proto | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/core/proxy.proto b/core/proxy.proto index 1f17fde..eca283b 100644 --- a/core/proxy.proto +++ b/core/proxy.proto @@ -337,7 +337,7 @@ message CoreRequest { } } -message InitialSetupInfo { +message CertificateInfo { string cert_hostname = 1; } @@ -354,8 +354,18 @@ service Proxy { rpc Bidi(stream CoreResponse) returns (stream CoreRequest); } +message LogEntry { + string level = 1; + string target = 2; + string message = 3; + string timestamp = 4; + map fields = 5; +} + // Service used for initial Proxy setup, used for configuring TLS certificate on Proxy for gRPC communication. service ProxySetup { - rpc Start(InitialSetupInfo) returns (DerPayload); + rpc Start(google.protobuf.Empty) returns (stream LogEntry); + rpc GetCsr(CertificateInfo) returns (DerPayload); rpc SendCert(DerPayload) returns (google.protobuf.Empty); } + From d01bfabe4e4f7f419de3682020e708f5861530da Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 27 Jan 2026 09:30:51 +0100 Subject: [PATCH 08/17] Add ClientRemoteMfaFinish RPC (#65) * Add ClientRemoteMfaFinish RPC * remove unused field * rename ClientRemoteMfa to AwaitRemoteMfa --- core/proxy.proto | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/proxy.proto b/core/proxy.proto index eca283b..4c2194b 100644 --- a/core/proxy.proto +++ b/core/proxy.proto @@ -272,6 +272,14 @@ message CodeMfaSetupFinishResponse { repeated string recovery_codes = 1; } +message AwaitRemoteMfaFinishRequest { + string token = 1; +} + +message AwaitRemoteMfaFinishResponse { + string preshared_key = 1; +} + message InitialInfo { bytes private_cookies_key = 1; } @@ -307,6 +315,7 @@ message CoreResponse { CodeMfaSetupStartResponse code_mfa_setup_start_response = 16; CodeMfaSetupFinishResponse code_mfa_setup_finish_response = 17; InitialInfo initial_info = 18; + AwaitRemoteMfaFinishResponse await_remote_mfa_finish = 19; } } @@ -334,6 +343,7 @@ message CoreRequest { ClientMfaTokenValidationRequest client_mfa_token_validation = 17; CodeMfaSetupStartRequest code_mfa_setup_start = 18; CodeMfaSetupFinishRequest code_mfa_setup_finish = 19; + AwaitRemoteMfaFinishRequest await_remote_mfa_finish = 20; } } From fdbe98caa9413b626833da210b5b588b287bb146 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:16:49 +0100 Subject: [PATCH 09/17] gateway wizard (#66) --- wireguard/gateway.proto | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/wireguard/gateway.proto b/wireguard/gateway.proto index 034ac00..aac7e14 100644 --- a/wireguard/gateway.proto +++ b/wireguard/gateway.proto @@ -92,16 +92,25 @@ service Gateway { rpc Bidi(stream CoreResponse) returns (stream CoreRequest); } -message InitialSetupInfo { +message DerPayload { + bytes der_data = 1; +} + +message CertificateInfo { string cert_hostname = 1; } -message DerPayload { - bytes der_data = 1; +message LogEntry { + string level = 1; + string target = 2; + string message = 3; + string timestamp = 4; + map fields = 5; } // Service used for initial Gateway setup, for configuring TLS certificate on Gateway for gRPC communication. service GatewaySetup { - rpc Start(InitialSetupInfo) returns (DerPayload); + rpc Start(google.protobuf.Empty) returns (stream LogEntry); + rpc GetCsr(CertificateInfo) returns (DerPayload); rpc SendCert(DerPayload) returns (google.protobuf.Empty); } From 5e87e357872341663b23114ceeff7aedf77e9aa5 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 12 Feb 2026 10:26:09 +0100 Subject: [PATCH 10/17] Purge proxy RPC (#67) --- core/proxy.proto | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/proxy.proto b/core/proxy.proto index 4c2194b..2d9d081 100644 --- a/core/proxy.proto +++ b/core/proxy.proto @@ -362,6 +362,11 @@ service Proxy { * 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); } message LogEntry { From d5ef18b694506c7ff0442101fce255ced440bdb5 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 17 Feb 2026 12:22:20 +0100 Subject: [PATCH 11/17] Gateway purge RPC (#68) --- wireguard/gateway.proto | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/wireguard/gateway.proto b/wireguard/gateway.proto index aac7e14..2502c3e 100644 --- a/wireguard/gateway.proto +++ b/wireguard/gateway.proto @@ -83,13 +83,18 @@ message CoreRequest { } } -/* - * Bi-directional communication between Core and Gateway. - * For security reasons, the connection has to be initiated by Core, - * so requests and responses are actually send in reverse. - */ 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 send 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); } message DerPayload { From 30c9e8e5c1d6c05c5aa8132fe48952eaca17f5b9 Mon Sep 17 00:00:00 2001 From: Maciek <19913370+wojcik91@users.noreply.github.com> Date: Wed, 25 Feb 2026 12:12:36 +0100 Subject: [PATCH 12/17] deprecate callback URLs (#69) * remove callback url * add auth flow type --- core/proxy.proto | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/core/proxy.proto b/core/proxy.proto index 2d9d081..e29fbf1 100644 --- a/core/proxy.proto +++ b/core/proxy.proto @@ -197,9 +197,17 @@ message ClientMfaFinishResponse { optional string token = 2; } +enum AuthFlowType { + AUTH_FLOW_TYPE_UNSPECIFIED = 0; + AUTH_FLOW_TYPE_ENROLLMENT = 1; + AUTH_FLOW_TYPE_MFA = 2; +} + message AuthInfoRequest { - string redirect_url = 1; + // DEPRECATED(2.0): superseeded by auth_flow_type + string redirect_url = 1 [deprecated = true]; optional string state = 2; + AuthFlowType auth_flow_type = 3; } message AuthInfoResponse { @@ -212,7 +220,8 @@ message AuthInfoResponse { message AuthCallbackRequest { string code = 1; string nonce = 2; - string callback_url = 3; + // DEPRECATED(2.0): superseeded by core-generated URL + string callback_url = 3 [deprecated = true]; } message AuthCallbackResponse { @@ -223,7 +232,8 @@ message AuthCallbackResponse { message ClientMfaOidcAuthenticateRequest { string code = 1; string state = 2; - string callback_url = 3; + // DEPRECATED(2.0): superseeded by core-generated URL + string callback_url = 3 [deprecated = true]; string nonce = 4; } From 28d962c6746e7b2bad365f8c9cc41fd23cf1b322 Mon Sep 17 00:00:00 2001 From: Maciek <19913370+wojcik91@users.noreply.github.com> Date: Tue, 24 Mar 2026 09:26:54 +0100 Subject: [PATCH 13/17] remove auth service protobuf (#70) --- core/auth.proto | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 core/auth.proto diff --git a/core/auth.proto b/core/auth.proto deleted file mode 100644 index 0653b08..0000000 --- a/core/auth.proto +++ /dev/null @@ -1,16 +0,0 @@ -syntax = "proto3"; - -package auth; - -service AuthService { - rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse); -} - -message AuthenticateRequest { - string username = 1; - string password = 2; -} - -message AuthenticateResponse { - string token = 1; -} From 0e247a24c3ae78052501dd052af4b73ab8f1e368 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:24:05 +0100 Subject: [PATCH 14/17] Https certificates provisioning (#72) --- core/proxy.proto | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/core/proxy.proto b/core/proxy.proto index e29fbf1..10261ee 100644 --- a/core/proxy.proto +++ b/core/proxy.proto @@ -326,6 +326,68 @@ message CoreResponse { CodeMfaSetupFinishResponse code_mfa_setup_finish_response = 17; InitialInfo initial_info = 18; AwaitRemoteMfaFinishResponse await_remote_mfa_finish = 19; + HttpsCerts https_certs = 20; + } +} + +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 = 3; +} + +/* + * 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; } } @@ -354,6 +416,7 @@ message CoreRequest { CodeMfaSetupStartRequest code_mfa_setup_start = 18; CodeMfaSetupFinishRequest code_mfa_setup_finish = 19; AwaitRemoteMfaFinishRequest await_remote_mfa_finish = 20; + AcmeCertificate acme_certificate = 21; } } @@ -377,6 +440,11 @@ service Proxy { * 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); } message LogEntry { From f600c25c2715d798225cc2441c4f79fdbba48af8 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 8 Apr 2026 16:35:45 +0200 Subject: [PATCH 15/17] Add ClearHttpsCerts message (#73) * clear https certs & restart server * use google.protobuf.Empty instead of ClearHttpsCerts --- core/proxy.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/proxy.proto b/core/proxy.proto index 10261ee..391d327 100644 --- a/core/proxy.proto +++ b/core/proxy.proto @@ -327,6 +327,7 @@ message CoreResponse { InitialInfo initial_info = 18; AwaitRemoteMfaFinishResponse await_remote_mfa_finish = 19; HttpsCerts https_certs = 20; + google.protobuf.Empty clear_https_certs = 21; } } @@ -461,4 +462,3 @@ service ProxySetup { rpc GetCsr(CertificateInfo) returns (DerPayload); rpc SendCert(DerPayload) returns (google.protobuf.Empty); } - From 7adfe3bfd1b7b701e58d25ddadd0c0c7a4a3e046 Mon Sep 17 00:00:00 2001 From: Maciek <19913370+wojcik91@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:43:52 +0200 Subject: [PATCH 16/17] setup protobuf versioning (#71) * snapshot current main branch as v1 * adjust module naming * restore top-level enterprise directory * snapshot dev branch as v2 * add missing firewall module * update imports * add buf CLI setup * formatting * add buf CLI section to readme * remove unnecessary file * add CI workflow * skip job * remove legacy services from v2 protos * extract client types into a shared module * adjust v2 structure * formatting * fix missing types * remove deprecated fields * restore AuthInfoRequest and migrate it to client types * extract remaining client-related types to shared module * update field order * sort messages * put cert-related messages in a shared package * update shared client types and use them in v1 * post-merge fix * use timestamp for wireguard handshake * align clean up naming and formatting * formatting --- .github/workflows/proto-validate.yml | 55 +++ README.md | 8 + buf.yaml | 14 + common/client_types.proto | 248 +++++++++++ core/proxy.proto | 464 -------------------- enterprise/{ => v1}/firewall/firewall.proto | 2 +- enterprise/v2/firewall/firewall.proto | 81 ++++ {client => v1/client}/client.proto | 11 +- v1/core/auth.proto | 16 + v1/core/proxy.proto | 135 ++++++ v1/wireguard/gateway.proto | 69 +++ {worker => v1/worker}/worker.proto | 2 +- v2/common.proto | 27 ++ v2/gateway.proto | 102 +++++ v2/proxy.proto | 230 ++++++++++ wireguard/gateway.proto | 121 ----- 16 files changed, 991 insertions(+), 594 deletions(-) create mode 100644 .github/workflows/proto-validate.yml create mode 100644 buf.yaml create mode 100644 common/client_types.proto delete mode 100644 core/proxy.proto rename enterprise/{ => v1}/firewall/firewall.proto (97%) create mode 100644 enterprise/v2/firewall/firewall.proto rename {client => v1/client}/client.proto (85%) create mode 100644 v1/core/auth.proto create mode 100644 v1/core/proxy.proto create mode 100644 v1/wireguard/gateway.proto rename {worker => v1/worker}/worker.proto (95%) create mode 100644 v2/common.proto create mode 100644 v2/gateway.proto create mode 100644 v2/proxy.proto delete mode 100644 wireguard/gateway.proto 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/common/client_types.proto b/common/client_types.proto new file mode 100644 index 0000000..5cc3b38 --- /dev/null +++ b/common/client_types.proto @@ -0,0 +1,248 @@ +syntax = "proto3"; +package defguard.client_types; + +/* + * 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; +} + +message AdminInfo { + string name = 1; + optional string phone_number = 2; + string email = 3; +} + +message InitialUserInfo { + string first_name = 1; + string last_name = 2; + string login = 3; + string email = 4; + optional string phone_number = 5; + bool is_active = 6; + repeated string device_names = 7; + bool enrolled = 8; + bool is_admin = 9; +} + +message EnrollmentSettings { + // Vpn step is skippable + bool vpn_setup_optional = 1; + // Manual WireGuard setup is disabled + 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 + bool smtp_configured = 4; + // MFA setup is not skippable + bool mfa_required = 5; +} + +message EnrollmentStartResponse { + AdminInfo admin = 1; + InitialUserInfo user = 2; + int64 deadline_timestamp = 3; + string final_page_content = 5; + InstanceInfo instance = 7; + EnrollmentSettings settings = 8; +} + +message ActivateUserRequest { + optional string phone_number = 1; + string password = 2; + optional string token = 3; +} + +message NewDevice { + string name = 1; + string pubkey = 2; + optional string token = 3; +} + +message ExistingDevice { + string pubkey = 1; + optional string token = 2; +} + +message Device { + int64 id = 1; + string name = 2; + string pubkey = 3; + int64 user_id = 4; + int64 created_at = 5; +} + +// Device configuration + +enum LocationMfaMode { + LOCATION_MFA_MODE_UNSPECIFIED = 0; + LOCATION_MFA_MODE_DISABLED = 1; + LOCATION_MFA_MODE_INTERNAL = 2; + LOCATION_MFA_MODE_EXTERNAL = 3; +} + +enum ServiceLocationMode { + SERVICE_LOCATION_MODE_UNSPECIFIED = 0; + SERVICE_LOCATION_MODE_DISABLED = 1; + SERVICE_LOCATION_MODE_PRELOGON = 2; + SERVICE_LOCATION_MODE_ALWAYSON = 3; +} + +message DeviceConfig { + int64 network_id = 1; + string network_name = 2; + string config = 3; + string endpoint = 4; + string assigned_ip = 5; + // network pubkey + string pubkey = 6; + string allowed_ips = 7; + optional string dns = 8; + // DEPRECATED(1.5): superseded by location_mfa_mode + bool mfa_enabled = 9 [deprecated = true]; + int32 keepalive_interval = 10; + optional LocationMfaMode location_mfa_mode = 11; + optional ServiceLocationMode service_location_mode = 12; +} + +enum ClientTrafficPolicy { + NONE = 0; + DISABLE_ALL_TRAFFIC = 1; + FORCE_ALL_TRAFFIC = 2; +} + +message InstanceInfo { + string id = 1; + string name = 2; + string url = 3; + string proxy_url = 4; + string username = 5; + bool enterprise_enabled = 6; + // 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; +} + +message DeviceConfigResponse { + Device device = 1; + repeated DeviceConfig configs = 2; + InstanceInfo instance = 3; + // polling token used for further client-core communication + optional string token = 4; +} + +// Configuration polling + +message InstanceInfoRequest { + string token = 1; +} + +message InstanceInfoResponse { + DeviceConfigResponse device_config = 1; +} + +// Platform info sent as a header with every request to the proxy + +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; + OIDC = 2; + BIOMETRIC = 3; + MOBILE_APPROVE = 4; +} + +message ClientMfaStartRequest { + int64 location_id = 1; + string pubkey = 2; + MfaMethod method = 3; +} + +message ClientMfaStartResponse { + string token = 1; + // for biometric mfa method + optional string challenge = 2; +} + +message ClientMfaFinishRequest { + string token = 1; + optional string code = 2; + optional string auth_pub_key = 3; +} + +message ClientMfaFinishResponse { + string preshared_key = 1; + optional string token = 2; +} + +message RegisterMobileAuthRequest { + string token = 1; + string auth_pub_key = 2; + string device_pub_key = 3; +} + +// TOTP and Email MFA Setup + +message CodeMfaSetupStartRequest { + MfaMethod method = 1; + string token = 2; +} + +// in case of email secret is empty +message CodeMfaSetupStartResponse { + optional string totp_secret = 1; +} + +message CodeMfaSetupFinishRequest { + string code = 1; + string token = 2; + MfaMethod method = 3; +} + +message CodeMfaSetupFinishResponse { + repeated string recovery_codes = 1; +} + +// OIDC authentication flow + +enum AuthFlowType { + AUTH_FLOW_TYPE_UNSPECIFIED = 0; + AUTH_FLOW_TYPE_ENROLLMENT = 1; + AUTH_FLOW_TYPE_MFA = 2; +} + +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/core/proxy.proto b/core/proxy.proto deleted file mode 100644 index 391d327..0000000 --- a/core/proxy.proto +++ /dev/null @@ -1,464 +0,0 @@ -syntax = "proto3"; -package defguard.proxy; - -import "google/protobuf/empty.proto"; - -// Enrollment & Desktop Client activation -message EnrollmentStartRequest { - string token = 1; -} - -message AdminInfo { - string name = 1; - optional string phone_number = 2; - string email = 3; -} - -message InitialUserInfo { - string first_name = 1; - string last_name = 2; - string login = 3; - string email = 4; - optional string phone_number = 5; - bool is_active = 6; - repeated string device_names = 7; - bool enrolled = 8; - bool is_admin = 9; -} - -message EnrollmentSettings { - // Vpn step is skippable - bool vpn_setup_optional = 1; - // Manual WireGuard setup is disabled - 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 - bool smtp_configured = 4; - // MFA setup is not skippable - bool mfa_required = 5; -} - -message EnrollmentStartResponse { - AdminInfo admin = 1; - InitialUserInfo user = 2; - int64 deadline_timestamp = 3; - string final_page_content = 5; - InstanceInfo instance = 7; - EnrollmentSettings settings = 8; -} - -message ActivateUserRequest { - optional string phone_number = 1; - string password = 2; - optional string token = 3; -} - -message NewDevice { - string name = 1; - string pubkey = 2; - optional string token = 3; -} - -message Device { - int64 id = 1; - string name = 2; - string pubkey = 3; - int64 user_id = 4; - int64 created_at = 5; -} - -enum LocationMfaMode { - LOCATION_MFA_MODE_UNSPECIFIED = 0; - LOCATION_MFA_MODE_DISABLED = 1; - LOCATION_MFA_MODE_INTERNAL = 2; - LOCATION_MFA_MODE_EXTERNAL = 3; -} - -enum ServiceLocationMode { - SERVICE_LOCATION_MODE_UNSPECIFIED = 0; - SERVICE_LOCATION_MODE_DISABLED = 1; - SERVICE_LOCATION_MODE_PRELOGON = 2; - SERVICE_LOCATION_MODE_ALWAYSON = 3; -} - -message DeviceConfig { - int64 network_id = 1; - string network_name = 2; - string config = 3; - string endpoint = 4; - string assigned_ip = 5; - // network pubkey - string pubkey = 6; - string allowed_ips = 7; - optional string dns = 8; - // DEPRECATED(1.5): superseeded by location_mfa_mode - bool mfa_enabled = 9 [deprecated = true]; - int32 keepalive_interval = 10; - optional LocationMfaMode location_mfa_mode = 11; - optional ServiceLocationMode service_location_mode = 12; -} - -enum ClientTrafficPolicy { - NONE = 0; - DISABLE_ALL_TRAFFIC = 1; - FORCE_ALL_TRAFFIC = 2; -} - -message InstanceInfo { - string id = 1; - string name = 2; - string url = 3; - string proxy_url = 4; - string username = 5; - bool enterprise_enabled = 6; - // DEPRECATED(1.6): superseeded by client_traffic_policy - bool disable_all_traffic = 7 [deprecated = true]; - optional string openid_display_name = 8; - optional ClientTrafficPolicy client_traffic_policy = 9; -} - -message DeviceConfigResponse { - Device device = 1; - repeated DeviceConfig configs = 2; - InstanceInfo instance = 3; - // polling token used for further client-core communication - optional string token = 4; -} - -message InstanceInfoRequest { - string token = 1; -} - -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; -} - -message PasswordResetRequest { - string password = 1; - optional string token = 2; -} - -// Client MFA -enum MfaMethod { - TOTP = 0; - EMAIL = 1; - OIDC = 2; - BIOMETRIC = 3; - MOBILE_APPROVE = 4; -} - -message ClientMfaTokenValidationRequest { - string token = 1; -} - -message ClientMfaTokenValidationResponse { - bool token_valid = 1; -} - -message ClientMfaStartRequest { - int64 location_id = 1; - string pubkey = 2; - MfaMethod method = 3; -} - -message ClientMfaStartResponse { - string token = 1; - // for biometric mfa method - optional string challenge = 2; -} - -message ClientMfaFinishRequest { - string token = 1; - optional string code = 2; - optional string auth_pub_key = 3; -} - -message ClientMfaFinishResponse { - string preshared_key = 1; - optional string token = 2; -} - -enum AuthFlowType { - AUTH_FLOW_TYPE_UNSPECIFIED = 0; - AUTH_FLOW_TYPE_ENROLLMENT = 1; - AUTH_FLOW_TYPE_MFA = 2; -} - -message AuthInfoRequest { - // DEPRECATED(2.0): superseeded by auth_flow_type - string redirect_url = 1 [deprecated = true]; - optional string state = 2; - AuthFlowType auth_flow_type = 3; -} - -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; - // DEPRECATED(2.0): superseeded by core-generated URL - string callback_url = 3 [deprecated = true]; -} - -message AuthCallbackResponse { - string url = 1; - string token = 2; -} - -message ClientMfaOidcAuthenticateRequest { - string code = 1; - string state = 2; - // DEPRECATED(2.0): superseeded by core-generated URL - string callback_url = 3 [deprecated = true]; - 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; - string device_pub_key = 3; -} - -// TOTP and Email MFA Setup -message CodeMfaSetupStartRequest { - MfaMethod method = 1; - string token = 2; -} - -// in case of email secret is empty -message CodeMfaSetupStartResponse { - optional string totp_secret = 1; -} - -message CodeMfaSetupFinishRequest { - string code = 1; - string token = 2; - MfaMethod method = 3; -} - -message CodeMfaSetupFinishResponse { - repeated string recovery_codes = 1; -} - -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; - 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; - InitialInfo initial_info = 18; - AwaitRemoteMfaFinishResponse await_remote_mfa_finish = 19; - HttpsCerts https_certs = 20; - google.protobuf.Empty clear_https_certs = 21; - } -} - -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 = 3; -} - -/* - * 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 { - 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; - AwaitRemoteMfaFinishRequest await_remote_mfa_finish = 20; - AcmeCertificate acme_certificate = 21; - } -} - -message CertificateInfo { - string cert_hostname = 1; -} - -message DerPayload { - bytes der_data = 1; -} - -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); -} - -message LogEntry { - string level = 1; - string target = 2; - string message = 3; - string timestamp = 4; - map fields = 5; -} - -// Service used for initial Proxy setup, used for configuring TLS certificate on Proxy for gRPC communication. -service ProxySetup { - rpc Start(google.protobuf.Empty) returns (stream LogEntry); - rpc GetCsr(CertificateInfo) returns (DerPayload); - rpc SendCert(DerPayload) returns (google.protobuf.Empty); -} 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/v1/core/auth.proto b/v1/core/auth.proto new file mode 100644 index 0000000..f57c610 --- /dev/null +++ b/v1/core/auth.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package defguard.auth.v1; + +service AuthService { + rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse); +} + +message AuthenticateRequest { + string username = 1; + string password = 2; +} + +message AuthenticateResponse { + string token = 1; +} 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/v1/wireguard/gateway.proto b/v1/wireguard/gateway.proto new file mode 100644 index 0000000..37c8886 --- /dev/null +++ b/v1/wireguard/gateway.proto @@ -0,0 +1,69 @@ +syntax = "proto3"; +package defguard.gateway.v1; + +import "enterprise/v1/firewall/firewall.proto"; +import "google/protobuf/empty.proto"; + +message ConfigurationRequest { + optional string name = 1; +} + +message Configuration { + string name = 1; + string prvkey = 2; + // string address = 3; // obsolete, use `addresses` + uint32 port = 4; + repeated Peer peers = 5; + repeated string addresses = 6; + optional defguard.enterprise.firewall.v1.FirewallConfig firewall_config = 7; +} + +enum UpdateType { + CREATE = 0; + MODIFY = 1; + DELETE = 2; +} + +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.v1.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; + uint64 latest_handshake = 6; + string allowed_ips = 7; +} + +/* + * Allow empty messages to keep the connection alive. + */ +message StatsUpdate { + uint64 id = 1; + oneof payload { + google.protobuf.Empty empty = 2; + PeerStats peer_stats = 3; + } +} + +service GatewayService { + rpc Config(ConfigurationRequest) returns (Configuration); + rpc Updates(google.protobuf.Empty) returns (stream Update); + rpc Stats(stream StatsUpdate) returns (google.protobuf.Empty); +} 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..127b9eb --- /dev/null +++ b/v2/common.proto @@ -0,0 +1,27 @@ +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; +} diff --git a/v2/gateway.proto b/v2/gateway.proto new file mode 100644 index 0000000..380bb11 --- /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.DerPayload) returns (google.protobuf.Empty); +} diff --git a/v2/proxy.proto b/v2/proxy.proto new file mode 100644 index 0000000..f4e17b1 --- /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.DerPayload) returns (google.protobuf.Empty); +} diff --git a/wireguard/gateway.proto b/wireguard/gateway.proto deleted file mode 100644 index 2502c3e..0000000 --- a/wireguard/gateway.proto +++ /dev/null @@ -1,121 +0,0 @@ -syntax = "proto3"; -package gateway; - -import "firewall.proto"; -import "google/protobuf/empty.proto"; - -message ConfigurationRequest { - // DEPRECATED(2.0): Gateway needs to authenticate with `auth_token`. - optional string name = 1 [deprecated = true]; - string hostname = 3; -} - -/* - * Networking and VPN configuration send from Core to Gateway. - */ -message Configuration { - string name = 1; - string prvkey = 2; - // string address = 3; // obsolete, use `addresses` - uint32 port = 4; - repeated Peer peers = 5; - repeated string addresses = 6; - optional enterprise.firewall.FirewallConfig firewall_config = 7; - uint32 mtu = 8; - uint32 fwmark = 9; -} - -enum UpdateType { - CREATE = 0; - MODIFY = 1; - DELETE = 2; -} - -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; - enterprise.firewall.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; - uint64 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; - ConfigurationRequest 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 send 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); -} - -message DerPayload { - bytes der_data = 1; -} - -message CertificateInfo { - string cert_hostname = 1; -} - -message LogEntry { - string level = 1; - string target = 2; - string message = 3; - string timestamp = 4; - map fields = 5; -} - -// Service used for initial Gateway setup, for configuring TLS certificate on Gateway for gRPC communication. -service GatewaySetup { - rpc Start(google.protobuf.Empty) returns (stream LogEntry); - rpc GetCsr(CertificateInfo) returns (DerPayload); - rpc SendCert(DerPayload) returns (google.protobuf.Empty); -} From 37bed3af781d157e7ff808686273f261ec546dac Mon Sep 17 00:00:00 2001 From: Maciek <19913370+wojcik91@users.noreply.github.com> Date: Tue, 21 Apr 2026 11:47:43 +0200 Subject: [PATCH 17/17] send core certs during component setup (#74) * send core certs during component setup * formatting --- v2/common.proto | 17 +++++++++++++++++ v2/gateway.proto | 2 +- v2/proxy.proto | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/v2/common.proto b/v2/common.proto index 127b9eb..b40126f 100644 --- a/v2/common.proto +++ b/v2/common.proto @@ -25,3 +25,20 @@ message LogEntry { 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 index 380bb11..8b15750 100644 --- a/v2/gateway.proto +++ b/v2/gateway.proto @@ -98,5 +98,5 @@ service Gateway { 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.DerPayload) returns (google.protobuf.Empty); + rpc SendCert(defguard.common.v2.CertBundle) returns (google.protobuf.Empty); } diff --git a/v2/proxy.proto b/v2/proxy.proto index f4e17b1..ba4bf51 100644 --- a/v2/proxy.proto +++ b/v2/proxy.proto @@ -226,5 +226,5 @@ service Proxy { 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.DerPayload) returns (google.protobuf.Empty); + rpc SendCert(defguard.common.v2.CertBundle) returns (google.protobuf.Empty); }