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); -}