bdc2504635
CD - Develop / build-and-deploy (push) Failing after 52s
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>
68 lines
2.7 KiB
Java
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));
|
|
}
|
|
}
|