Skip to content

Commit 5e73b68

Browse files
authored
Report number of days since last security update (#880)
* report number of days * format * proto
1 parent a75d6e9 commit 5e73b68

4 files changed

Lines changed: 32 additions & 26 deletions

File tree

src-tauri/proto

src-tauri/src/enterprise/inspector/mod.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ use sysinfo::System;
1313

1414
use crate::{
1515
service::proto::defguard::enterprise::posture::v2::{
16-
bool_check, string_check, BoolCheck, DevicePostureData, StringCheck, UnavailableReason,
16+
bool_check, int32_check, string_check, BoolCheck, DevicePostureData, Int32Check,
17+
StringCheck, UnavailableReason,
1718
},
1819
VERSION,
1920
};
@@ -109,11 +110,11 @@ fn device_integrity() -> Result<bool, UnavailableReason> {
109110
Err(UnavailableReason::NotApplicable)
110111
}
111112

112-
/// Returns the security update status.
113-
fn security_update_status() -> Result<bool, UnavailableReason> {
113+
/// Returns the number of days since the last installed Windows security update.
114+
fn security_update_age_days() -> Result<i32, UnavailableReason> {
114115
#[cfg(windows)]
115116
{
116-
windows::security_update_status()
117+
windows::security_update_age_days()
117118
}
118119

119120
#[cfg(not(windows))]
@@ -134,6 +135,18 @@ impl From<Result<bool, UnavailableReason>> for BoolCheck {
134135
}
135136
}
136137

138+
/// Convert `Result` to `Int32Check`.
139+
impl From<Result<i32, UnavailableReason>> for Int32Check {
140+
fn from(value: Result<i32, UnavailableReason>) -> Self {
141+
Self {
142+
result: Some(match value {
143+
Ok(inner) => int32_check::Result::Value(inner),
144+
Err(err) => int32_check::Result::Unavailable(err as i32),
145+
}),
146+
}
147+
}
148+
}
149+
137150
/// Convert `Result` to `StringCheck`.
138151
impl From<Result<String, UnavailableReason>> for StringCheck {
139152
fn from(value: Result<String, UnavailableReason>) -> Self {
@@ -159,7 +172,7 @@ impl DevicePostureData {
159172
disk_encryption: Some(BoolCheck::from(disk_encryption_status())),
160173
antivirus_present: Some(BoolCheck::from(anti_virus_status())),
161174
windows_ad_domain_joined: Some(BoolCheck::from(part_of_domain())),
162-
windows_security_update_current: Some(BoolCheck::from(security_update_status())),
175+
windows_security_update_age_days: Some(Int32Check::from(security_update_age_days())),
163176
linux_kernel_version: Some(StringCheck::from(linux_kernel_version())),
164177
device_integrity: Some(BoolCheck::from(device_integrity())),
165178
}

src-tauri/src/enterprise/inspector/tests/windows.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::super::{
22
anti_virus_status, disk_encryption_status, os_name, os_version, part_of_domain,
3-
security_update_status,
3+
security_update_age_days,
44
};
55

66
#[test]
@@ -33,6 +33,6 @@ fn test_part_of_domain() {
3333

3434
#[test]
3535
#[ignore = "development machine only"]
36-
fn test_security_update_status() {
37-
assert!(security_update_status().unwrap());
36+
fn test_security_update_age_days() {
37+
assert!(security_update_age_days().unwrap() >= 0);
3838
}

src-tauri/src/enterprise/inspector/windows.rs

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@
22

33
use serde::Deserialize;
44
use time::{Date, OffsetDateTime};
5-
65
use wmi::{AuthLevel, WMIConnection, WMIError};
76

87
use super::UnavailableReason;
98

10-
const MAX_QUICKFIX_DAYS: i64 = 60;
11-
129
#[derive(Deserialize)]
1310
#[serde(rename = "Win32_EncryptableVolume")]
1411
#[serde(rename_all = "PascalCase")]
@@ -137,28 +134,24 @@ pub(super) fn part_of_domain() -> Result<bool, UnavailableReason> {
137134
Ok(system.part_of_domain)
138135
}
139136

140-
/// Find the latest security patch.
137+
/// Number of days since the most recently installed security patch.
141138
///
142139
/// Check manually in PowerShell:
143140
/// `Get-CimInstance -ClassName Win32_QuickFixEngineering`
144141
///
145142
/// Equivalent to PowerShell command:
146143
/// `Get-WmiObject -query "SELECT * FROM Win32_QuickFixEngineering"`
147-
pub(super) fn security_update_status() -> Result<bool, UnavailableReason> {
144+
pub(super) fn security_update_age_days() -> Result<i32, UnavailableReason> {
148145
let conn = WMIConnection::new()?;
149146
let fixes: Vec<Win32QuickFixEngineering> = conn.query()?;
150147

151-
// Days from today
152148
let today = OffsetDateTime::now_utc().date();
153-
let mut max_days = i64::MAX;
154-
for fix in fixes {
155-
if let Some(installed_on) = fix.installed_on {
156-
let days = (today - installed_on).whole_days();
157-
if days < max_days {
158-
max_days = days;
159-
}
160-
}
161-
}
162-
163-
Ok(max_days <= MAX_QUICKFIX_DAYS)
149+
let min_days = fixes
150+
.into_iter()
151+
.filter_map(|fix| fix.installed_on)
152+
.map(|installed_on| (today - installed_on).whole_days())
153+
.min()
154+
.ok_or(UnavailableReason::DetectionFailed)?;
155+
156+
i32::try_from(min_days).map_err(|_| UnavailableReason::DetectionFailed)
164157
}

0 commit comments

Comments
 (0)