fix(aria): feed clients — @Param binding, auth keys, thread error logging
CD - Develop / build-and-deploy (push) Failing after 12m28s
CD - Develop / build-and-deploy (push) Failing after 12m28s
- IocFeedRunRepository: add @Param("feedName") to countRunning JPQL query
(missing binding silently crashed background threads)
- IocFeedController: uncaught exception handler on feed threads so failures
appear in logs instead of disappearing silently
- MalwareBazaarFeedClient/ThreatFoxFeedClient: abuse.ch now requires API
key (auth.abuse.ch); add Auth-Key header + isConfigured() skip guard
- application.properties: add aria.feed.malware-bazaar.api-key and
aria.feed.threat-fox.api-key properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import com.hiveops.aria.entity.IocFeedRun;
|
|||||||
import com.hiveops.aria.repository.IocFeedRunRepository;
|
import com.hiveops.aria.repository.IocFeedRunRepository;
|
||||||
import com.hiveops.aria.service.IocFeedService;
|
import com.hiveops.aria.service.IocFeedService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
@@ -16,8 +17,16 @@ import java.util.Optional;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/aria/feeds")
|
@RequestMapping("/api/aria/feeds")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class IocFeedController {
|
public class IocFeedController {
|
||||||
|
|
||||||
|
private Thread feedThread(String name, Runnable task) {
|
||||||
|
Thread t = new Thread(task, name);
|
||||||
|
t.setUncaughtExceptionHandler((thread, ex) ->
|
||||||
|
log.error("IOC feed thread {} failed: {}", thread.getName(), ex.getMessage(), ex));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
private final IocFeedService feedService;
|
private final IocFeedService feedService;
|
||||||
private final IocFeedRunRepository feedRunRepository;
|
private final IocFeedRunRepository feedRunRepository;
|
||||||
|
|
||||||
@@ -49,7 +58,7 @@ public class IocFeedController {
|
|||||||
@PostMapping("/refresh")
|
@PostMapping("/refresh")
|
||||||
@PreAuthorize("hasRole('BCOS_ADMIN')")
|
@PreAuthorize("hasRole('BCOS_ADMIN')")
|
||||||
public ResponseEntity<Map<String, String>> refreshAll() {
|
public ResponseEntity<Map<String, String>> refreshAll() {
|
||||||
new Thread(() -> feedService.refreshAll(), "ioc-feed-manual-all").start();
|
feedThread("ioc-feed-manual-all", feedService::refreshAll).start();
|
||||||
return ResponseEntity.accepted().body(Map.of("status", "refresh_started"));
|
return ResponseEntity.accepted().body(Map.of("status", "refresh_started"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +70,7 @@ public class IocFeedController {
|
|||||||
if (!feedService.feedNames().contains(upper)) {
|
if (!feedService.feedNames().contains(upper)) {
|
||||||
return ResponseEntity.badRequest().body(Map.of("error", "Unknown feed: " + feedName));
|
return ResponseEntity.badRequest().body(Map.of("error", "Unknown feed: " + feedName));
|
||||||
}
|
}
|
||||||
new Thread(() -> feedService.refreshFeed(upper), "ioc-feed-manual-" + upper).start();
|
feedThread("ioc-feed-manual-" + upper, () -> feedService.refreshFeed(upper)).start();
|
||||||
return ResponseEntity.accepted().body(Map.of("status", "refresh_started", "feed", upper));
|
return ResponseEntity.accepted().body(Map.of("status", "refresh_started", "feed", upper));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.hiveops.aria.entity.ThreatIoc;
|
import com.hiveops.aria.entity.ThreatIoc;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -18,7 +19,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches ATM-related malware samples from MalwareBazaar (abuse.ch) — no API key required.
|
* Fetches ATM-related malware samples from MalwareBazaar (abuse.ch).
|
||||||
|
* Requires an API key from https://auth.abuse.ch/
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -27,9 +29,20 @@ public class MalwareBazaarFeedClient {
|
|||||||
private static final String API_URL = "https://mb-api.abuse.ch/api/v1/";
|
private static final String API_URL = "https://mb-api.abuse.ch/api/v1/";
|
||||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
|
@Value("${aria.feed.malware-bazaar.api-key:}")
|
||||||
|
private String apiKey;
|
||||||
|
|
||||||
private final RestTemplate restTemplate = new RestTemplate();
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
public boolean isConfigured() {
|
||||||
|
return apiKey != null && !apiKey.isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
public List<ThreatIoc> fetch() {
|
public List<ThreatIoc> fetch() {
|
||||||
|
if (!isConfigured()) {
|
||||||
|
log.info("MalwareBazaar: no API key configured, skipping");
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
List<ThreatIoc> result = new ArrayList<>();
|
List<ThreatIoc> result = new ArrayList<>();
|
||||||
|
|
||||||
// Query recent samples tagged with ATM-related tags
|
// Query recent samples tagged with ATM-related tags
|
||||||
@@ -46,6 +59,7 @@ public class MalwareBazaarFeedClient {
|
|||||||
try {
|
try {
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
headers.set("Auth-Key", apiKey);
|
||||||
|
|
||||||
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
||||||
body.add("query", "get_taginfo");
|
body.add("query", "get_taginfo");
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.hiveops.aria.entity.ThreatIoc;
|
import com.hiveops.aria.entity.ThreatIoc;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -17,7 +18,8 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches ATM-related IOCs from ThreatFox (abuse.ch) — no API key required.
|
* Fetches ATM-related IOCs from ThreatFox (abuse.ch).
|
||||||
|
* Requires an API key from https://auth.abuse.ch/
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -26,9 +28,20 @@ public class ThreatFoxFeedClient {
|
|||||||
private static final String API_URL = "https://threatfox-api.abuse.ch/api/v1/";
|
private static final String API_URL = "https://threatfox-api.abuse.ch/api/v1/";
|
||||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
|
@Value("${aria.feed.threat-fox.api-key:}")
|
||||||
|
private String apiKey;
|
||||||
|
|
||||||
private final RestTemplate restTemplate = new RestTemplate();
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
public boolean isConfigured() {
|
||||||
|
return apiKey != null && !apiKey.isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
public List<ThreatIoc> fetch() {
|
public List<ThreatIoc> fetch() {
|
||||||
|
if (!isConfigured()) {
|
||||||
|
log.info("ThreatFox: no API key configured, skipping");
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
List<ThreatIoc> result = new ArrayList<>();
|
List<ThreatIoc> result = new ArrayList<>();
|
||||||
|
|
||||||
// Search by ATM-specific malware families
|
// Search by ATM-specific malware families
|
||||||
@@ -48,6 +61,7 @@ public class ThreatFoxFeedClient {
|
|||||||
try {
|
try {
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
headers.set("Auth-Key", apiKey);
|
||||||
|
|
||||||
String body = MAPPER.writeValueAsString(Map.of(
|
String body = MAPPER.writeValueAsString(Map.of(
|
||||||
"query", "iocs_by_malware_family",
|
"query", "iocs_by_malware_family",
|
||||||
@@ -70,6 +84,7 @@ public class ThreatFoxFeedClient {
|
|||||||
try {
|
try {
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
headers.set("Auth-Key", apiKey);
|
||||||
|
|
||||||
String body = MAPPER.writeValueAsString(Map.of(
|
String body = MAPPER.writeValueAsString(Map.of(
|
||||||
"query", "get_iocs",
|
"query", "get_iocs",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.springframework.data.domain.Page;
|
|||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -15,5 +16,5 @@ public interface IocFeedRunRepository extends JpaRepository<IocFeedRun, Long> {
|
|||||||
Optional<IocFeedRun> findTopByFeedNameOrderByStartedAtDesc(String feedName);
|
Optional<IocFeedRun> findTopByFeedNameOrderByStartedAtDesc(String feedName);
|
||||||
|
|
||||||
@Query("SELECT COUNT(r) FROM IocFeedRun r WHERE r.feedName = :feedName AND r.status = 'RUNNING'")
|
@Query("SELECT COUNT(r) FROM IocFeedRun r WHERE r.feedName = :feedName AND r.status = 'RUNNING'")
|
||||||
long countRunning(String feedName);
|
long countRunning(@Param("feedName") String feedName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ aria.sequence.window.minutes=${ARIA_SEQUENCE_WINDOW_MINUTES:15}
|
|||||||
aria.feed.enabled=${ARIA_FEED_ENABLED:true}
|
aria.feed.enabled=${ARIA_FEED_ENABLED:true}
|
||||||
aria.feed.refresh.cron=${ARIA_FEED_REFRESH_CRON:0 0 3 * * *}
|
aria.feed.refresh.cron=${ARIA_FEED_REFRESH_CRON:0 0 3 * * *}
|
||||||
aria.feed.otx.api-key=${ARIA_FEED_OTX_API_KEY:}
|
aria.feed.otx.api-key=${ARIA_FEED_OTX_API_KEY:}
|
||||||
|
aria.feed.malware-bazaar.api-key=${ARIA_FEED_MALWARE_BAZAAR_API_KEY:}
|
||||||
|
aria.feed.threat-fox.api-key=${ARIA_FEED_THREAT_FOX_API_KEY:}
|
||||||
|
|
||||||
# Auto-response — disabled by default for safe initial deployment
|
# Auto-response — disabled by default for safe initial deployment
|
||||||
aria.autoresponse.shutdown.enabled=${ARIA_AUTORESPONSE_SHUTDOWN_ENABLED:false}
|
aria.autoresponse.shutdown.enabled=${ARIA_AUTORESPONSE_SHUTDOWN_ENABLED:false}
|
||||||
|
|||||||
Reference in New Issue
Block a user