Skip to content

Commit c779bd5

Browse files
committed
feat(panel): add manual alert analysis endpoint with SSL support
1 parent ea7d894 commit c779bd5

File tree

7 files changed

+115
-73
lines changed

7 files changed

+115
-73
lines changed

backend/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@
285285
<dependency>
286286
<groupId>com.utmstack</groupId>
287287
<artifactId>opensearch-connector</artifactId>
288-
<version>1.0.4</version>
288+
<version>1.0.5</version>
289289
</dependency>
290290
<dependency>
291291
<groupId>jakarta.json</groupId>
Lines changed: 58 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,97 @@
11
package com.park.utmstack.service.soc_ai;
22

33
import com.google.gson.Gson;
4-
import com.park.utmstack.domain.UtmAlertSocaiProcessingRequest;
5-
import com.park.utmstack.domain.application_modules.enums.ModuleName;
6-
import com.park.utmstack.service.UtmAlertSocaiProcessingRequestService;
4+
import com.park.utmstack.config.Constants;
5+
import com.park.utmstack.domain.shared_types.alert.UtmAlert;
76
import com.park.utmstack.service.application_modules.UtmModuleService;
87
import okhttp3.*;
98
import org.slf4j.Logger;
109
import org.slf4j.LoggerFactory;
11-
import org.springframework.data.domain.Page;
12-
import org.springframework.data.domain.PageRequest;
13-
import org.springframework.scheduling.annotation.Async;
14-
import org.springframework.scheduling.annotation.Scheduled;
1510
import org.springframework.stereotype.Service;
1611
import org.springframework.util.StringUtils;
1712

18-
import java.util.List;
13+
import javax.net.ssl.SSLContext;
14+
import javax.net.ssl.TrustManager;
15+
import javax.net.ssl.X509TrustManager;
16+
import java.security.cert.X509Certificate;
1917
import java.util.concurrent.TimeUnit;
20-
import java.util.stream.Collectors;
2118

2219
@Service
2320
public class SocAIService {
2421
private static final String CLASSNAME = "SocAIService";
2522
private final Logger log = LoggerFactory.getLogger(SocAIService.class);
26-
private final String SOCAI_PROCESS_URL;
23+
private final String SOCAI_BASE_URL;
24+
private final String SOCAI_ANALYZE_ENDPOINT = "/api/v1/analyze";
25+
private final OkHttpClient httpClient;
2726

28-
private final UtmAlertSocaiProcessingRequestService socaiProcessingRequestService;
2927
private final UtmModuleService moduleService;
3028

31-
public SocAIService(UtmAlertSocaiProcessingRequestService socaiProcessingRequestService,
32-
UtmModuleService moduleService) {
33-
this.socaiProcessingRequestService = socaiProcessingRequestService;
29+
public SocAIService(UtmModuleService moduleService) {
3430
this.moduleService = moduleService;
35-
SOCAI_PROCESS_URL = System.getenv("SOC_AI_BASE_URL");
31+
SOCAI_BASE_URL = System.getenv("SOC_AI_BASE_URL");
32+
this.httpClient = createTrustAllClient();
3633
}
3734

38-
39-
public void sendData(Object data) {
40-
final String ctx = CLASSNAME + ".sendData";
35+
/**
36+
* Sends a complete alert to SOC-AI for analysis
37+
*/
38+
public void analyzeAlert(UtmAlert alert) {
39+
final String ctx = CLASSNAME + ".analyzeAlert";
4140
try {
42-
OkHttpClient client = new OkHttpClient.Builder()
43-
.connectTimeout(10, TimeUnit.SECONDS)
44-
.writeTimeout(10, TimeUnit.SECONDS)
45-
.readTimeout(30, TimeUnit.SECONDS)
46-
.build();
41+
if (!StringUtils.hasText(SOCAI_BASE_URL)) {
42+
throw new RuntimeException("SOC_AI_BASE_URL environment variable is not configured");
43+
}
44+
45+
String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY);
46+
if (!StringUtils.hasText(internalKey)) {
47+
throw new RuntimeException("INTERNAL_KEY environment variable is not configured");
48+
}
49+
4750
MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
48-
RequestBody body = RequestBody.create(new Gson().toJson(data), mediaType);
49-
Request request = new Request.Builder().url(SOCAI_PROCESS_URL).post(body)
50-
.addHeader("Content-Type", "application/json").build();
51+
RequestBody body = RequestBody.create(new Gson().toJson(alert), mediaType);
5152

52-
try (Response rs = client.newCall(request).execute()) {
53-
if (!rs.isSuccessful())
54-
throw new Exception(ctx + "Unexpected response: " + rs);
53+
String url = SOCAI_BASE_URL + SOCAI_ANALYZE_ENDPOINT;
54+
Request request = new Request.Builder()
55+
.url(url)
56+
.post(body)
57+
.addHeader("Content-Type", "application/json")
58+
.addHeader("X-Internal-Key", internalKey)
59+
.build();
60+
61+
try (Response rs = httpClient.newCall(request).execute()) {
62+
if (!rs.isSuccessful()) {
63+
String responseBody = rs.body() != null ? rs.body().string() : "No response body";
64+
throw new Exception("Unexpected response: " + rs.code() + " - " + responseBody);
65+
}
5566
}
5667
} catch (Exception e) {
5768
log.error(ctx + ": " + e.getLocalizedMessage());
5869
throw new RuntimeException(ctx + ": " + e.getLocalizedMessage());
5970
}
6071
}
6172

62-
/*@Scheduled(fixedDelay = 30000)*/
63-
public void sendRequests() {
64-
final String ctx = CLASSNAME + ".sendRequests";
73+
private OkHttpClient createTrustAllClient() {
6574
try {
66-
if (!moduleService.isModuleActive(ModuleName.SOC_AI))
67-
return;
75+
TrustManager[] trustAllCerts = new TrustManager[]{
76+
new X509TrustManager() {
77+
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
78+
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
79+
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
80+
}
81+
};
6882

69-
if (!StringUtils.hasText(SOCAI_PROCESS_URL)) {
70-
log.error(ctx + ": Environment variable SOC_AI_BASE_URL is missing or does not have a value");
71-
return;
72-
}
83+
SSLContext sslContext = SSLContext.getInstance("TLS");
84+
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
7385

74-
Page<UtmAlertSocaiProcessingRequest> requests = socaiProcessingRequestService.findAll(PageRequest.of(0, 20));
75-
if (!requests.hasContent())
76-
return;
77-
List<String> ids = requests.getContent().stream().map(UtmAlertSocaiProcessingRequest::getAlertId)
78-
.collect(Collectors.toList());
79-
try {
80-
sendData(ids);
81-
socaiProcessingRequestService.delete(ids);
82-
} catch (Exception e) {
83-
log.error(ctx + ": " + e.getLocalizedMessage());
84-
}
85-
} catch (Exception e) {
86-
throw new RuntimeException(e);
87-
}
88-
}
89-
90-
@Async
91-
public void requestSocAiProcess(List<String> alertIds) {
92-
final String ctx = CLASSNAME + ".requestSocAiProcess";
93-
try {
94-
List<UtmAlertSocaiProcessingRequest> ids = alertIds.stream().map(UtmAlertSocaiProcessingRequest::new)
95-
.collect(Collectors.toList());
96-
socaiProcessingRequestService.saveAll(ids);
86+
return new OkHttpClient.Builder()
87+
.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustAllCerts[0])
88+
.hostnameVerifier((hostname, session) -> true)
89+
.connectTimeout(10, TimeUnit.SECONDS)
90+
.writeTimeout(10, TimeUnit.SECONDS)
91+
.readTimeout(30, TimeUnit.SECONDS)
92+
.build();
9793
} catch (Exception e) {
98-
throw new RuntimeException(ctx + ": " + e.getLocalizedMessage());
94+
throw new RuntimeException("Failed to create SSL client: " + e.getMessage());
9995
}
10096
}
10197
}

backend/src/main/java/com/park/utmstack/web/rest/soc_ai/UtmSocAiResource.java

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.park.utmstack.web.rest.soc_ai;
22

33
import com.park.utmstack.domain.application_events.enums.ApplicationEventType;
4+
import com.park.utmstack.domain.shared_types.alert.UtmAlert;
45
import com.park.utmstack.service.application_events.ApplicationEventService;
56
import com.park.utmstack.service.soc_ai.SocAIService;
67
import com.park.utmstack.web.rest.AccountResource;
@@ -25,23 +26,42 @@ public class UtmSocAiResource {
2526

2627
private final ApplicationEventService applicationEventService;
2728
private final SocAIService socAIService;
29+
2830
public UtmSocAiResource(SocAIService socAIService, ApplicationEventService applicationEventService) {
2931
this.socAIService = socAIService;
3032
this.applicationEventService = applicationEventService;
3133
}
3234

33-
@PostMapping("/alerts")
34-
public ResponseEntity<Object> sendData(@RequestBody String[] alertsId) {
35-
final String ctx = CLASSNAME + ".sendAlertsIds";
35+
/**
36+
* POST /api/soc-ai/analyze : Submit an alert for SOC-AI analysis
37+
*
38+
* @param alert the complete alert object to analyze
39+
* @return status of the submission
40+
*/
41+
@PostMapping("/analyze")
42+
public ResponseEntity<Object> analyzeAlert(@RequestBody UtmAlert alert) {
43+
final String ctx = CLASSNAME + ".analyzeAlert";
3644
try {
37-
socAIService.sendData(alertsId);
38-
return ResponseEntity.ok().body(Map.of("status", "success", "message", "Processing successful"));
45+
if (alert == null || alert.getId() == null) {
46+
return ResponseEntity.badRequest().body(Map.of("status", "error", "message", "Alert ID is required"));
47+
}
48+
49+
socAIService.analyzeAlert(alert);
50+
return ResponseEntity.accepted().body(Map.of(
51+
"status", "queued",
52+
"alertId", alert.getId(),
53+
"message", "Alert queued for SOC-AI analysis"
54+
));
3955
} catch (Exception e) {
4056
String msg = ctx + ": " + e.getMessage();
4157
log.error(msg);
4258
applicationEventService.createEvent(msg, ApplicationEventType.ERROR);
4359

44-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("status", "error", "message", msg));
60+
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of(
61+
"status", "error",
62+
"message", msg
63+
));
4564
}
4665
}
66+
4767
}

frontend/src/app/data-management/alert-management/shared/components/alert-soc-ai/alert-soc-ai.component.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@
5252
<span class="font-weight-semibold">Info! </span>
5353
<span>The SOC-AI integration did not analyze this alert due to inactivity or a processing error.</span>
5454
</div>
55+
<button class="btn btn-primary" (click)="processAlert()" [disabled]="loadingProcess || !alert">
56+
<i class="icon-brain mr-1" *ngIf="!loadingProcess"></i>
57+
<i class="icon-spinner2 spinner mr-1" *ngIf="loadingProcess"></i>
58+
{{ loadingProcess ? 'Analyzing...' : 'Analyze with SOC-AI' }}
59+
</button>
5560
</div>
5661

62+
<!-- Re-analyze button when analysis is completed -->
63+
<div *ngIf="socAiResponse && socAiResponse.status === indexSocAiStatus.Completed && !loading" class="mt-3">
64+
<button class="btn btn-outline-primary btn-sm" (click)="processAlert()" [disabled]="loadingProcess || !alert">
65+
<i class="icon-refresh mr-1" *ngIf="!loadingProcess"></i>
66+
<i class="icon-spinner2 spinner mr-1" *ngIf="loadingProcess"></i>
67+
{{ loadingProcess ? 'Re-analyzing...' : 'Re-analyze' }}
68+
</button>
69+
</div>
5770

frontend/src/app/data-management/alert-management/shared/components/alert-soc-ai/alert-soc-ai.component.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {LOG_INDEX_PATTERN, SOC_AI_INDEX_PATTERN} from '../../../../../shared/con
55
import {ElasticOperatorsEnum} from '../../../../../shared/enums/elastic-operators.enum';
66
import {ElasticDataService} from '../../../../../shared/services/elasticsearch/elastic-data.service';
77
import {ElasticFilterType} from '../../../../../shared/types/filter/elastic-filter.type';
8+
import {UtmAlertType} from '../../../../../shared/types/alert/utm-alert.type';
89
import {AlertSocAiService} from '../../services/alert-soc-ai.service';
910
import {IndexSocAiStatus, SocAiType} from './soc-ai.type';
1011

@@ -15,6 +16,7 @@ import {IndexSocAiStatus, SocAiType} from './soc-ai.type';
1516
})
1617
export class AlertSocAiComponent implements OnInit, OnDestroy {
1718
@Input() alertID: string;
19+
@Input() alert: UtmAlertType;
1820
@Input() socAiActive: boolean;
1921
socAiResponse: SocAiType;
2022
indexSocAiStatus = IndexSocAiStatus;
@@ -75,9 +77,15 @@ export class AlertSocAiComponent implements OnInit, OnDestroy {
7577
}
7678

7779
processAlert() {
80+
if (!this.alert) {
81+
this.utmToastService.showError('Error', 'Alert data is not available.');
82+
return;
83+
}
84+
7885
this.loadingProcess = true;
79-
this.alertSocAiService.processAlertBySoc([this.alertID])
86+
this.alertSocAiService.analyzeAlert(this.alert)
8087
.subscribe((res) => {
88+
this.utmToastService.showSuccessBottom('Alert submitted for SOC-AI analysis');
8189
setTimeout(() => {
8290
this.loadingProcess = false;
8391
this.getSocAiResponse();

frontend/src/app/data-management/alert-management/shared/components/alert-view-detail/alert-view-detail.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@
198198
</div>
199199

200200
<div class="w-100 mt-3" *ngIf="view === alertDetailTabEnum.SOC_AI">
201-
<app-alert-soc-ai [alertID]="alert.id" [socAiActive]="socAi"></app-alert-soc-ai>
201+
<app-alert-soc-ai [alertID]="alert.id" [alert]="alert" [socAiActive]="socAi"></app-alert-soc-ai>
202202
</div>
203203

204204
<div class="w-100 mt-3" *ngIf="view=== alertDetailTabEnum.TAGS && relatedTagsRules">

frontend/src/app/data-management/alert-management/shared/services/alert-soc-ai.service.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {HttpClient, HttpResponse} from '@angular/common/http';
22
import {Injectable} from '@angular/core';
33
import {Observable} from 'rxjs';
44
import {SERVER_API_URL} from '../../../../app.constants';
5+
import {UtmAlertType} from '../../../../shared/types/alert/utm-alert.type';
56

67

78
@Injectable({
@@ -14,8 +15,12 @@ export class AlertSocAiService {
1415
constructor(private http: HttpClient) {
1516
}
1617

17-
processAlertBySoc(alertId: string[]): Observable<HttpResponse<any>> {
18-
return this.http.post<HttpResponse<any>>(this.resourceUrl + '/alerts', alertId, {observe: 'response'});
18+
/**
19+
* Submit an alert for SOC-AI analysis
20+
* @param alert The complete alert object to analyze
21+
*/
22+
analyzeAlert(alert: UtmAlertType): Observable<HttpResponse<any>> {
23+
return this.http.post<any>(this.resourceUrl + '/analyze', alert, {observe: 'response'});
1924
}
2025

2126
}

0 commit comments

Comments
 (0)