azure_lite_rs/api/
security.rs1use crate::{
9 AzureHttpClient, Result,
10 ops::security::SecurityOps,
11 types::security::{
12 Alert, AlertListResult, Assessment, AssessmentListResult, SecureScore,
13 SecureScoreListResult,
14 },
15};
16
17pub struct SecurityClient<'a> {
22 ops: SecurityOps<'a>,
23 client: &'a AzureHttpClient,
24}
25
26impl<'a> SecurityClient<'a> {
27 pub(crate) fn new(client: &'a AzureHttpClient) -> Self {
29 Self {
30 ops: SecurityOps::new(client),
31 client,
32 }
33 }
34
35 pub async fn list_alerts(&self) -> Result<AlertListResult> {
39 self.ops.list_alerts(self.client.subscription_id()).await
40 }
41
42 pub async fn get_alert(
47 &self,
48 resource_group_name: &str,
49 asc_location: &str,
50 alert_name: &str,
51 ) -> Result<Alert> {
52 self.ops
53 .get_alert(
54 self.client.subscription_id(),
55 resource_group_name,
56 asc_location,
57 alert_name,
58 )
59 .await
60 }
61
62 pub async fn update_alert_status(
67 &self,
68 resource_group_name: &str,
69 asc_location: &str,
70 alert_name: &str,
71 alert_update_action_type: &str,
72 ) -> Result<()> {
73 self.ops
74 .update_alert_status(
75 self.client.subscription_id(),
76 resource_group_name,
77 asc_location,
78 alert_name,
79 alert_update_action_type,
80 )
81 .await
82 }
83
84 pub async fn list_secure_scores(&self) -> Result<SecureScoreListResult> {
88 self.ops
89 .list_secure_scores(self.client.subscription_id())
90 .await
91 }
92
93 pub async fn get_secure_score(&self, secure_score_name: &str) -> Result<SecureScore> {
97 self.ops
98 .get_secure_score(self.client.subscription_id(), secure_score_name)
99 .await
100 }
101
102 pub async fn list_assessments(&self) -> Result<AssessmentListResult> {
106 self.ops
107 .list_assessments(self.client.subscription_id())
108 .await
109 }
110
111 pub async fn get_assessment(&self, assessment_name: &str) -> Result<Assessment> {
121 self.ops
122 .get_assessment(self.client.subscription_id(), assessment_name)
123 .await
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::MockClient;
131
132 const SUB_ID: &str = "test-subscription-id";
133 const ALERT_NAME: &str = "2517376_cloud-lite-test-alert";
134 const ASC_LOCATION: &str = "eastus";
135 const RG: &str = "test-rg";
136 const ASSESSMENT_NAME: &str = "050ac097-3dda-4d24-ab6d-82568e7a50cf";
137
138 fn make_client(mock: MockClient) -> AzureHttpClient {
139 AzureHttpClient::from_mock(mock)
140 }
141
142 fn alert_json() -> serde_json::Value {
143 serde_json::json!({
144 "id": format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Security/locations/{ASC_LOCATION}/alerts/{ALERT_NAME}"),
145 "name": ALERT_NAME,
146 "type": "Microsoft.Security/alerts",
147 "properties": {
148 "alertDisplayName": "Suspicious PowerShell activity",
149 "alertType": "VM_SuspiciousPowerShellActivity",
150 "severity": "High",
151 "status": "Active",
152 "description": "A suspicious PowerShell command was executed.",
153 "remediationSteps": ["Investigate the activity", "Revoke credentials"],
154 "intent": "Execution"
155 }
156 })
157 }
158
159 fn secure_score_json() -> serde_json::Value {
160 serde_json::json!({
161 "id": format!("/subscriptions/{SUB_ID}/providers/Microsoft.Security/secureScores/ascScore"),
162 "name": "ascScore",
163 "type": "Microsoft.Security/secureScores",
164 "properties": {
165 "displayName": "ASC score",
166 "score": {
167 "max": 21,
168 "current": 12.55,
169 "percentage": 0.5976
170 },
171 "weight": 1
172 }
173 })
174 }
175
176 fn assessment_json() -> serde_json::Value {
177 serde_json::json!({
178 "id": format!("/subscriptions/{SUB_ID}/providers/Microsoft.Security/assessments/{ASSESSMENT_NAME}"),
179 "name": ASSESSMENT_NAME,
180 "type": "Microsoft.Security/assessments",
181 "properties": {
182 "displayName": "Disabled accounts with owner permissions should be removed",
183 "status": {
184 "code": "Healthy",
185 "cause": "OffByPolicy",
186 "description": "All accounts are enabled"
187 }
188 }
189 })
190 }
191
192 #[tokio::test]
193 async fn list_alerts_returns_list() {
194 let mut mock = MockClient::new();
195 mock.expect_get(&format!(
196 "/subscriptions/{SUB_ID}/providers/Microsoft.Security/alerts"
197 ))
198 .returning_json(serde_json::json!({ "value": [alert_json()] }));
199 let client = make_client(mock);
200 let result = client
201 .security()
202 .list_alerts()
203 .await
204 .expect("list_alerts failed");
205 assert_eq!(result.value.len(), 1);
206 let a = &result.value[0];
207 assert_eq!(a.name.as_deref(), Some(ALERT_NAME));
208 let props = a.properties.as_ref().unwrap();
209 assert_eq!(props.severity.as_deref(), Some("High"));
210 assert_eq!(props.status.as_deref(), Some("Active"));
211 assert_eq!(props.remediation_steps.len(), 2);
212 }
213
214 #[tokio::test]
215 async fn get_alert_deserializes_properties() {
216 let mut mock = MockClient::new();
217 mock.expect_get(
218 &format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Security/locations/{ASC_LOCATION}/alerts/{ALERT_NAME}"),
219 )
220 .returning_json(alert_json());
221 let client = make_client(mock);
222 let a = client
223 .security()
224 .get_alert(RG, ASC_LOCATION, ALERT_NAME)
225 .await
226 .expect("get_alert failed");
227 assert_eq!(a.name.as_deref(), Some(ALERT_NAME));
228 let props = a.properties.as_ref().unwrap();
229 assert_eq!(
230 props.alert_type.as_deref(),
231 Some("VM_SuspiciousPowerShellActivity")
232 );
233 assert_eq!(props.intent.as_deref(), Some("Execution"));
234 }
235
236 #[tokio::test]
237 async fn update_alert_status_constructs_url() {
238 let mut mock = MockClient::new();
239 mock.expect_post(
240 &format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Security/locations/{ASC_LOCATION}/alerts/{ALERT_NAME}/dismiss"),
241 )
242 .returning_json(serde_json::json!({}));
243 let client = make_client(mock);
244 client
245 .security()
246 .update_alert_status(RG, ASC_LOCATION, ALERT_NAME, "dismiss")
247 .await
248 .expect("update_alert_status failed");
249 }
250
251 #[tokio::test]
252 async fn list_secure_scores_returns_asc_score() {
253 let mut mock = MockClient::new();
254 mock.expect_get(&format!(
255 "/subscriptions/{SUB_ID}/providers/Microsoft.Security/secureScores"
256 ))
257 .returning_json(serde_json::json!({ "value": [secure_score_json()] }));
258 let client = make_client(mock);
259 let result = client
260 .security()
261 .list_secure_scores()
262 .await
263 .expect("list_secure_scores failed");
264 assert_eq!(result.value.len(), 1);
265 let score = &result.value[0];
266 assert_eq!(score.name.as_deref(), Some("ascScore"));
267 let props = score.properties.as_ref().unwrap();
268 let details = props.score.as_ref().unwrap();
269 assert_eq!(details.max, Some(21));
270 assert_eq!(details.current, Some(12.55));
271 }
272
273 #[tokio::test]
274 async fn get_assessment_deserializes_status() {
275 let mut mock = MockClient::new();
276 mock.expect_get(&format!(
277 "/subscriptions/{SUB_ID}/providers/Microsoft.Security/assessments/{ASSESSMENT_NAME}"
278 ))
279 .returning_json(assessment_json());
280 let client = make_client(mock);
281 let a = client
282 .security()
283 .get_assessment(ASSESSMENT_NAME)
284 .await
285 .expect("get_assessment failed");
286 assert_eq!(a.name.as_deref(), Some(ASSESSMENT_NAME));
287 let props = a.properties.as_ref().unwrap();
288 let status = props.status.as_ref().unwrap();
289 assert_eq!(status.code.as_deref(), Some("Healthy"));
290 assert_eq!(status.cause.as_deref(), Some("OffByPolicy"));
291 }
292}