Files
hiveops-aria/src/main/java/com/hiveops/aria/controller/IocFeedController.java
T
johannes bdc2504635
CD - Develop / build-and-deploy (push) Failing after 52s
feat(aria): Phase 5 — IOC feed refresh from OTX, MalwareBazaar, ThreatFox
Adds automated daily IOC ingestion from three external threat feeds:
- AlienVault OTX: subscribed pulses + ATM-keyword searches (requires API key)
- MalwareBazaar (abuse.ch): ATM-tagged malware samples, no key required
- ThreatFox (abuse.ch): ATM malware family IOCs, no key required

Backend:
- IocFeedRun entity + V6 Flyway migration (ioc_feed_run table)
- OtxFeedClient / MalwareBazaarFeedClient / ThreatFoxFeedClient
- IocFeedService: upsert dedup + @Scheduled daily 03:00 refresh
- IocFeedController: GET /status, GET /runs, POST /refresh, POST /refresh/{feed}
- ThreatIoc.IocSource enum extended: OTX, MALWARE_BAZAAR, THREAT_FOX

Frontend:
- IocFeeds.svelte: feed status cards, run history table, manual refresh
- App.svelte: IOC Feeds nav item (📡), version string removed from sidebar
- api.ts: getFeedStatus, getFeedRuns, refreshAllFeeds, refreshFeed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 21:01:09 -04:00

68 lines
2.7 KiB
Java

package com.hiveops.aria.controller;
import com.hiveops.aria.entity.IocFeedRun;
import com.hiveops.aria.repository.IocFeedRunRepository;
import com.hiveops.aria.service.IocFeedService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@RestController
@RequestMapping("/api/aria/feeds")
@RequiredArgsConstructor
public class IocFeedController {
private final IocFeedService feedService;
private final IocFeedRunRepository feedRunRepository;
/** Latest run per feed — used by the status cards in the UI */
@GetMapping("/status")
@PreAuthorize("hasRole('BCOS_ADMIN')")
public ResponseEntity<Map<String, Object>> status() {
List<String> names = feedService.feedNames();
Map<String, Object> result = new java.util.LinkedHashMap<>();
for (String name : names) {
Optional<IocFeedRun> last = feedRunRepository.findTopByFeedNameOrderByStartedAtDesc(name);
result.put(name, last.orElse(null));
}
return ResponseEntity.ok(result);
}
/** Recent run history — default 50 rows */
@GetMapping("/runs")
@PreAuthorize("hasRole('BCOS_ADMIN')")
public ResponseEntity<List<IocFeedRun>> runs(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "50") int size) {
return ResponseEntity.ok(
feedRunRepository.findAllByOrderByStartedAtDesc(PageRequest.of(page, size)).getContent()
);
}
/** Trigger all feeds immediately */
@PostMapping("/refresh")
@PreAuthorize("hasRole('BCOS_ADMIN')")
public ResponseEntity<Map<String, String>> refreshAll() {
new Thread(() -> feedService.refreshAll(), "ioc-feed-manual-all").start();
return ResponseEntity.accepted().body(Map.of("status", "refresh_started"));
}
/** Trigger one specific feed */
@PostMapping("/refresh/{feedName}")
@PreAuthorize("hasRole('BCOS_ADMIN')")
public ResponseEntity<Map<String, String>> refreshOne(@PathVariable String feedName) {
String upper = feedName.toUpperCase();
if (!feedService.feedNames().contains(upper)) {
return ResponseEntity.badRequest().body(Map.of("error", "Unknown feed: " + feedName));
}
new Thread(() -> feedService.refreshFeed(upper), "ioc-feed-manual-" + upper).start();
return ResponseEntity.accepted().body(Map.of("status", "refresh_started", "feed", upper));
}
}