Evaluate posture checks during client connection#2906
Conversation
There was a problem hiding this comment.
Pull request overview
Implements server-side device posture evaluation during client connection flows by introducing a new enterprise posture evaluation module, wiring a new DevicePostureCheck request/response through the proxy manager, and exposing a posture_check_required flag to clients based on whether a location has posture policies.
Changes:
- Add enterprise posture evaluation logic (policy lookup + signal evaluation + semver helpers) with extensive SQLx-backed tests.
- Add a new bidi-stream request handler for
DevicePostureCheckin proxy manager and core gRPC MFA server (ClientMfaServer). - Expose
posture_check_requiredin device config responses and add aWireguardNetwork::has_postures()helper for DB existence checks.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/defguard_proxy_manager/src/tests/proxy_manager/handler/support.rs | Updates MFA test helper to include new request field. |
| crates/defguard_proxy_manager/src/tests/proxy_manager/handler/mfa.rs | Updates MFA handler tests to include new request field. |
| crates/defguard_proxy_manager/src/handler.rs | Handles new DevicePostureCheck core request and maps outcomes to responses. |
| crates/defguard_proxy_manager/Cargo.toml | Reorders workspace dependencies (no functional change). |
| crates/defguard_proto/src/lib.rs | Re-exports new enterprise posture proto module and extends DeviceConfig conversion. |
| crates/defguard_proto/build.rs | Adds posture proto to code generation inputs. |
| crates/defguard_core/src/grpc/utils.rs | Adds posture_check_required to device config response (via DB existence check). |
| crates/defguard_core/src/grpc/proxy/client_mfa.rs | Adds posture-check handler and generalizes session creation helper. |
| crates/defguard_core/src/enterprise/posture/mod.rs | New posture module: error/result types + public API surface. |
| crates/defguard_core/src/enterprise/posture/evaluation.rs | New posture evaluation implementation (DB-backed). |
| crates/defguard_core/src/enterprise/posture/version.rs | New lenient semver parsing/comparison helpers with unit tests. |
| crates/defguard_core/src/enterprise/posture/tests.rs | New SQLx integration tests for posture evaluation. |
| crates/defguard_core/src/enterprise/mod.rs | Exposes the new enterprise::posture module. |
| crates/defguard_common/src/db/models/wireguard.rs | Adds WireguardNetwork::has_postures() helper. |
| crates/defguard_common/src/db/models/device.rs | Extends DeviceConfig with posture_check_required and populates it. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (2)
crates/defguard_core/src/grpc/proxy/client_mfa.rs:872
- After a successful posture check this handler stores a
VpnClientSessionwith a generatedpreshared_keyand returns the PSK to the client, but it never sends a gateway event (e.g.GatewayEvent::MfaSessionAuthorizedlike the MFAfinishflow does at lines 710–720). The gateway therefore never learns about the new PSK / authorized peer, so the PSK returned to the client will not actually allow connection. The handler should fetch the correspondingWireguardNetworkDeviceand emit an authorization event to the gateway in the same way the MFA finish path does.
self.create_new_session(
&mut conn,
&location,
&user,
&device,
None, // posture-only session has no MFA method
key.public.clone(),
)
.await?;
info!(
"Posture check passed for device {} (user {}) in location {}. Session created.",
device, user.username, location
);
Ok(PostureCheckOutcome::Approved {
preshared_key: key.public,
})
}
crates/defguard_core/src/grpc/proxy/client_mfa.rs:876
- The doc comment still says "while creating a new MFA session and send relevant gateway updates", but
create_new_sessionis now also used for posture-only sessions (wheremfa_methodisNone) and it does not send gateway updates itself — the caller is expected to. Please update the doc to reflect the broader purpose and clarify the gateway-event responsibility.
/// Helper used to close all existing active sessions while creating a new MFA session
/// and send relevant gateway updates
async fn create_new_session(
Related issue: #2864