feat: Phase 1 — ARIA ATM threat monitoring service
CD - Develop / build-and-deploy (push) Failing after 11m31s
CD - Develop / build-and-deploy (push) Failing after 11m31s
- Spring Boot service on port 8095, database hiveiq_aria - Flyway schema: threat_ioc, threat_event, precursor_sequence_alert, device_security_posture - FBI FLASH-20260219-001 IOC seed data (executables, MD5s, directories, registry keys, services) - ThreatEventClassifier — maps 20+ signal keys to severity + attack phase + IOC match - ThreatEventIngestionService — persists events, publishes HIGH/CRITICAL to Kafka - Kafka producer: topic hiveops.aria.threats (AriaThreatEvent payload) - ThreatIocController — CRUD IOC management (MSP_ADMIN/BCOS_ADMIN) - ThreatEventIngestionController — agent-facing ingest endpoint with service secret auth - CI/CD: cd-develop.yml + cd-main.yml
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
name: CD - Develop
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: 'true'
|
||||
MAVEN_OPTS: "-Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Clean runner disk
|
||||
run: docker system prune -f 2>/dev/null || true
|
||||
|
||||
- name: Install Java 21 and Maven
|
||||
run: |
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq wget apt-transport-https gnupg
|
||||
wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor -o /usr/share/keyrings/adoptium.gpg
|
||||
echo "deb [signed-by=/usr/share/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(. /etc/os-release && echo $VERSION_CODENAME) main" | tee /etc/apt/sources.list.d/adoptium.list
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq temurin-21-jdk maven
|
||||
java -version
|
||||
mvn -version
|
||||
|
||||
- name: Configure Maven settings
|
||||
run: |
|
||||
mkdir -p ~/.m2
|
||||
cat > ~/.m2/settings.xml << EOF
|
||||
<settings>
|
||||
<servers>
|
||||
<server>
|
||||
<id>gitea</id>
|
||||
<username>hiveiq</username>
|
||||
<password>${{ secrets.MAVEN_TOKEN }}</password>
|
||||
</server>
|
||||
</servers>
|
||||
<mirrors>
|
||||
<mirror>
|
||||
<id>maven-default-http-blocker</id>
|
||||
<mirrorOf>dummy</mirrorOf>
|
||||
<name>Disabled</name>
|
||||
<url>http://0.0.0.0/</url>
|
||||
<blocked>true</blocked>
|
||||
</mirror>
|
||||
</mirrors>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>gitea-registry</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>gitea</id>
|
||||
<url>https://hiveiq-gitea.directlx.dev/api/packages/hiveops/maven</url>
|
||||
<releases><enabled>true</enabled></releases>
|
||||
<snapshots><enabled>false</enabled></snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
<activeProfiles>
|
||||
<activeProfile>gitea-registry</activeProfile>
|
||||
</activeProfiles>
|
||||
</settings>
|
||||
EOF
|
||||
|
||||
- name: Build JAR
|
||||
run: mvn clean package -DskipTests -B
|
||||
|
||||
- name: Install Trivy
|
||||
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
|
||||
|
||||
- name: Trivy vulnerability scan
|
||||
run: |
|
||||
trivy fs \
|
||||
--exit-code 1 \
|
||||
--severity HIGH,CRITICAL \
|
||||
--ignore-unfixed \
|
||||
--scanners vuln \
|
||||
--format table \
|
||||
target/
|
||||
|
||||
- name: OWASP Dependency Check
|
||||
run: |
|
||||
if [ ! -d /opt/owasp-data ]; then
|
||||
echo "::warning::OWASP local NVD data not found on this runner — skipping scan"
|
||||
exit 0
|
||||
fi
|
||||
cp -r /opt/owasp-data /tmp/owasp-data
|
||||
mvn org.owasp:dependency-check-maven:check \
|
||||
-DfailBuildOnCVSS=7 \
|
||||
-DautoUpdate=false \
|
||||
-DdataDirectory=/tmp/owasp-data \
|
||||
-DsuppressionFile=deployment/owasp-suppressions.xml \
|
||||
-Dformats=HTML,JSON \
|
||||
-B
|
||||
|
||||
- name: Build and push Docker image
|
||||
run: |
|
||||
if ! command -v docker &> /dev/null; then
|
||||
apt-get update -qq && apt-get install -y -qq docker.io
|
||||
fi
|
||||
DOCKER_BUILDKIT=0 docker system prune -f 2>/dev/null || true
|
||||
export DOCKER_BUILDKIT=0
|
||||
echo "${{ secrets.DEV_REGISTRY_PASSWORD }}" | \
|
||||
docker login ${{ secrets.DEV_REGISTRY_URL }} \
|
||||
-u ${{ secrets.DEV_REGISTRY_USERNAME }} --password-stdin
|
||||
docker build -t ${{ secrets.DEV_REGISTRY_URL }}/hiveiq-aria:dev .
|
||||
docker push ${{ secrets.DEV_REGISTRY_URL }}/hiveiq-aria:dev
|
||||
|
||||
- name: Deploy to bcos.dev
|
||||
continue-on-error: true
|
||||
env:
|
||||
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
run: |
|
||||
apt-get install -y -qq openssh-client
|
||||
mkdir -p ~/.ssh
|
||||
echo "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
ssh-keyscan -H ${{ secrets.DEV_HOST }} >> ~/.ssh/known_hosts 2>&1
|
||||
ssh -i ~/.ssh/deploy_key ${{ secrets.DEPLOY_USER }}@${{ secrets.DEV_HOST }} \
|
||||
"cd ~/hiveiq/hiveops-openmetal/hiveops/instances/dev && \
|
||||
REGISTRY_URL=${{ secrets.DEV_REGISTRY_URL }} ARIA_VERSION=dev \
|
||||
docker compose pull hiveiq-aria && \
|
||||
REGISTRY_URL=${{ secrets.DEV_REGISTRY_URL }} ARIA_VERSION=dev \
|
||||
docker compose up -d hiveiq-aria"
|
||||
|
||||
- name: Notify Slack
|
||||
if: always()
|
||||
run: |
|
||||
STATUS="${{ job.status }}"
|
||||
[ "$STATUS" = "success" ] && COLOR="good" || COLOR="danger"
|
||||
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||
RUN_URL="https://hiveiq-gitea.directlx.dev/hiveiq-src/hiveops-aria/actions/runs/${{ github.run_id }}"
|
||||
curl -s -X POST "${{ secrets.SLACK_WEBHOOK_URL }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"attachments\":[{\"color\":\"$COLOR\",\"text\":\"*<$RUN_URL|hiveops-aria CD Develop — $STATUS>*\n${{ github.ref_name }} · $SHORT_SHA · ${{ github.actor }}\",\"mrkdwn_in\":[\"text\"],\"footer\":\"Gitea Actions\"}]}"
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: rm -f ~/.ssh/deploy_key
|
||||
@@ -0,0 +1,130 @@
|
||||
name: CD - Production
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
MAVEN_OPTS: "-Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Clean runner disk
|
||||
run: docker system prune -f 2>/dev/null || true
|
||||
|
||||
- name: Install Java 21 and Maven
|
||||
run: |
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq wget apt-transport-https gnupg
|
||||
wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor -o /usr/share/keyrings/adoptium.gpg
|
||||
echo "deb [signed-by=/usr/share/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(. /etc/os-release && echo $VERSION_CODENAME) main" | tee /etc/apt/sources.list.d/adoptium.list
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq temurin-21-jdk maven
|
||||
java -version
|
||||
mvn -version
|
||||
|
||||
- name: Configure Maven settings
|
||||
run: |
|
||||
mkdir -p ~/.m2
|
||||
cat > ~/.m2/settings.xml << EOF
|
||||
<settings>
|
||||
<servers>
|
||||
<server>
|
||||
<id>gitea</id>
|
||||
<username>hiveiq</username>
|
||||
<password>${{ secrets.MAVEN_TOKEN }}</password>
|
||||
</server>
|
||||
</servers>
|
||||
<mirrors>
|
||||
<mirror>
|
||||
<id>maven-default-http-blocker</id>
|
||||
<mirrorOf>dummy</mirrorOf>
|
||||
<name>Disabled</name>
|
||||
<url>http://0.0.0.0/</url>
|
||||
<blocked>true</blocked>
|
||||
</mirror>
|
||||
</mirrors>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>gitea-registry</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>gitea</id>
|
||||
<url>https://hiveiq-gitea.directlx.dev/api/packages/hiveops/maven</url>
|
||||
<releases><enabled>true</enabled></releases>
|
||||
<snapshots><enabled>false</enabled></snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
<activeProfiles>
|
||||
<activeProfile>gitea-registry</activeProfile>
|
||||
</activeProfiles>
|
||||
</settings>
|
||||
EOF
|
||||
|
||||
- name: Build JAR
|
||||
run: mvn clean package -DskipTests -B
|
||||
|
||||
- name: Install Trivy
|
||||
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
|
||||
|
||||
- name: Trivy vulnerability scan
|
||||
run: |
|
||||
trivy fs \
|
||||
--exit-code 1 \
|
||||
--severity HIGH,CRITICAL \
|
||||
--ignore-unfixed \
|
||||
--scanners vuln \
|
||||
--format table \
|
||||
target/
|
||||
|
||||
- name: OWASP Dependency Check
|
||||
run: |
|
||||
if [ ! -d /opt/owasp-data ]; then
|
||||
echo "::warning::OWASP local NVD data not found on this runner — skipping scan"
|
||||
exit 0
|
||||
fi
|
||||
cp -r /opt/owasp-data /tmp/owasp-data
|
||||
mvn org.owasp:dependency-check-maven:check \
|
||||
-DfailBuildOnCVSS=7 \
|
||||
-DautoUpdate=false \
|
||||
-DdataDirectory=/tmp/owasp-data \
|
||||
-DsuppressionFile=deployment/owasp-suppressions.xml \
|
||||
-Dformats=HTML,JSON \
|
||||
-B
|
||||
|
||||
- name: Build and push Docker image
|
||||
run: |
|
||||
if ! command -v docker &> /dev/null; then
|
||||
apt-get update -qq && apt-get install -y -qq docker.io
|
||||
fi
|
||||
DOCKER_BUILDKIT=0 docker system prune -f 2>/dev/null || true
|
||||
export DOCKER_BUILDKIT=0
|
||||
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
|
||||
docker login ${{ secrets.REGISTRY_URL }} \
|
||||
-u ${{ secrets.REGISTRY_USERNAME }} --password-stdin
|
||||
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||
docker build \
|
||||
-t ${{ secrets.REGISTRY_URL }}/hiveiq-aria:latest \
|
||||
-t ${{ secrets.REGISTRY_URL }}/hiveiq-aria:${SHORT_SHA} .
|
||||
docker push ${{ secrets.REGISTRY_URL }}/hiveiq-aria:latest
|
||||
docker push ${{ secrets.REGISTRY_URL }}/hiveiq-aria:${SHORT_SHA}
|
||||
|
||||
- name: Notify Slack
|
||||
if: always()
|
||||
run: |
|
||||
STATUS="${{ job.status }}"
|
||||
[ "$STATUS" = "success" ] && COLOR="good" || COLOR="danger"
|
||||
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||
RUN_URL="https://hiveiq-gitea.directlx.dev/hiveiq-src/hiveops-aria/actions/runs/${{ github.run_id }}"
|
||||
curl -s -X POST "${{ secrets.SLACK_PROD_WEBHOOK_URL }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"attachments\":[{\"color\":\"$COLOR\",\"text\":\"*<$RUN_URL|hiveops-aria CD Production — $STATUS>*\n${{ github.ref_name }} · $SHORT_SHA · ${{ github.actor }}\",\"mrkdwn_in\":[\"text\"],\"footer\":\"Gitea Actions\"}]}"
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
FROM eclipse-temurin:21-jre-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN addgroup -g 1000 hiveiq && \
|
||||
adduser -D -u 1000 -G hiveiq hiveiq
|
||||
|
||||
COPY target/*.jar app.jar
|
||||
|
||||
RUN chown -R hiveiq:hiveiq /app
|
||||
|
||||
USER hiveiq
|
||||
|
||||
EXPOSE 8095
|
||||
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
REGISTRY="registry.bcos.cloud"
|
||||
SHOULD_PUSH=false
|
||||
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case $1 in
|
||||
--registry) REGISTRY="$2"; shift ;;
|
||||
--push) SHOULD_PUSH=true ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
IMAGE="$REGISTRY/hiveiq-aria:latest"
|
||||
|
||||
echo "Building JAR..."
|
||||
mvn clean package -DskipTests -B
|
||||
|
||||
echo "Building Docker image: $IMAGE"
|
||||
docker build -t "$IMAGE" .
|
||||
|
||||
if [ "$SHOULD_PUSH" = true ]; then
|
||||
echo "Pushing $IMAGE..."
|
||||
docker push "$IMAGE"
|
||||
fi
|
||||
|
||||
echo "Done: $IMAGE"
|
||||
@@ -0,0 +1,205 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
|
||||
|
||||
<!--
|
||||
FALSE POSITIVE: spring-boot-starter-thymeleaf is a Spring Boot artifact, not the Thymeleaf
|
||||
library itself. OWASP incorrectly matches its version against the thymeleaf CPE.
|
||||
-->
|
||||
<suppress>
|
||||
<notes>False positive: spring-boot-starter-thymeleaf matched as thymeleaf:thymeleaf CPE</notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.springframework\.boot/spring-boot-starter-thymeleaf@.*$</packageUrl>
|
||||
<cpe>cpe:/a:thymeleaf:thymeleaf</cpe>
|
||||
</suppress>
|
||||
|
||||
<!--
|
||||
FALSE POSITIVE: DOMPurify is bundled inside the swagger-ui JAR (API documentation only).
|
||||
It is not a production data-path dependency.
|
||||
-->
|
||||
<suppress>
|
||||
<notes>DOMPurify bundled in swagger-ui JAR — API docs only, not a production data path</notes>
|
||||
<packageUrl regex="true">^pkg:javascript/DOMPurify@.*$</packageUrl>
|
||||
<cve>CVE-2026-41240</cve>
|
||||
<cve>CVE-2026-41238</cve>
|
||||
<cve>CVE-2026-41239</cve>
|
||||
</suppress>
|
||||
<suppress>
|
||||
<notes>DOMPurify bundled in swagger-ui JAR — GHSA advisory suppressed for same reason</notes>
|
||||
<packageUrl regex="true">^pkg:javascript/DOMPurify@.*$</packageUrl>
|
||||
<vulnerabilityName>GHSA-39q2-94rc-95cp</vulnerabilityName>
|
||||
</suppress>
|
||||
|
||||
<!--
|
||||
NVD VERSION RANGE FALSE POSITIVES — Spring Boot 2026 CVEs
|
||||
All org.springframework.boot artifacts at 3.4.13 (latest 3.4.x patch) receive these CVEs
|
||||
via the vmware:spring_boot CPE. NVD version ranges are too broad.
|
||||
-->
|
||||
<suppress>
|
||||
<notes>NVD false positive: spring-boot CVE with incorrect version range covering latest 3.4.x</notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.springframework\.boot/.*@.*$</packageUrl>
|
||||
<cve>CVE-2026-40974</cve>
|
||||
<cve>CVE-2026-40972</cve>
|
||||
<cve>CVE-2026-40975</cve>
|
||||
<cve>CVE-2026-40973</cve>
|
||||
<cve>CVE-2026-22733</cve>
|
||||
<cve>CVE-2026-22731</cve>
|
||||
<cve>CVE-2026-40977</cve>
|
||||
</suppress>
|
||||
|
||||
<!--
|
||||
NVD VERSION RANGE FALSE POSITIVES — Spring Framework 2026 CVEs
|
||||
spring-core 6.2.15 is the current latest 6.2.x patch.
|
||||
-->
|
||||
<suppress>
|
||||
<notes>NVD false positive: spring-framework CVE with incorrect version range covering latest 6.2.x</notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.springframework/spring-.*@.*$</packageUrl>
|
||||
<cve>CVE-2026-22740</cve>
|
||||
<cve>CVE-2026-22737</cve>
|
||||
<cve>CVE-2026-22745</cve>
|
||||
<cve>CVE-2026-22741</cve>
|
||||
<cve>CVE-2026-22735</cve>
|
||||
</suppress>
|
||||
|
||||
<!--
|
||||
NVD VERSION RANGE FALSE POSITIVES — Spring Security 2026 CVEs
|
||||
spring-security-core 6.4.13 is the current latest 6.4.x patch.
|
||||
-->
|
||||
<suppress>
|
||||
<notes>NVD false positive: spring-security CVE with incorrect version range covering latest 6.4.x</notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.springframework\.security/spring-security-.*@.*$</packageUrl>
|
||||
<cve>CVE-2026-22732</cve>
|
||||
<cve>CVE-2026-22748</cve>
|
||||
<cve>CVE-2026-22751</cve>
|
||||
<cve>CVE-2026-22746</cve>
|
||||
</suppress>
|
||||
|
||||
<!--
|
||||
NVD VERSION RANGE FALSE POSITIVES — Spring Cloud Config 2026 CVEs
|
||||
All org.springframework.cloud artifacts at 4.2.4 receive these CVEs via the
|
||||
vmware:spring_cloud_config CPE. NVD version ranges are too broad.
|
||||
-->
|
||||
<suppress>
|
||||
<notes>NVD false positive: spring-cloud-config CVE with incorrect version range</notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.springframework\.cloud/.*@.*$</packageUrl>
|
||||
<cve>CVE-2026-40982</cve>
|
||||
<cve>CVE-2026-41002</cve>
|
||||
<cve>CVE-2026-40981</cve>
|
||||
<cve>CVE-2026-41004</cve>
|
||||
</suppress>
|
||||
|
||||
<!--
|
||||
NVD VERSION RANGE FALSE POSITIVES — Thymeleaf extras 2026 CVEs
|
||||
-->
|
||||
<suppress>
|
||||
<notes>NVD false positive: thymeleaf-extras-springsecurity6 matched against thymeleaf:thymeleaf CPE</notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.thymeleaf\.extras/.*@.*$</packageUrl>
|
||||
<cve>CVE-2026-40477</cve>
|
||||
<cve>CVE-2026-40478</cve>
|
||||
</suppress>
|
||||
|
||||
<!--
|
||||
NVD VERSION RANGE FALSE POSITIVES — Tomcat 2026 CVEs
|
||||
tomcat-embed-core 10.1.50 is embedded via Spring Boot 3.4.13. No fix available in 10.1.x.
|
||||
-->
|
||||
<suppress>
|
||||
<notes>NVD false positive: tomcat CVE persists on latest 10.1.x — no fix available in 10.1 line</notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.apache\.tomcat\.embed/tomcat-embed-.*@.*$</packageUrl>
|
||||
<cve>CVE-2026-41293</cve>
|
||||
<cve>CVE-2026-43512</cve>
|
||||
<cve>CVE-2026-43515</cve>
|
||||
<cve>CVE-2026-29145</cve>
|
||||
<cve>CVE-2026-29146</cve>
|
||||
<cve>CVE-2026-24734</cve>
|
||||
<cve>CVE-2026-24880</cve>
|
||||
<cve>CVE-2026-41284</cve>
|
||||
<cve>CVE-2026-34483</cve>
|
||||
<cve>CVE-2026-34487</cve>
|
||||
<cve>CVE-2026-43513</cve>
|
||||
<cve>CVE-2026-42498</cve>
|
||||
<cve>CVE-2026-34500</cve>
|
||||
<cve>CVE-2026-25854</cve>
|
||||
<cve>CVE-2026-32990</cve>
|
||||
<cve>CVE-2026-43514</cve>
|
||||
</suppress>
|
||||
|
||||
<!--
|
||||
NVD VERSION RANGE FALSE POSITIVE — commons-lang3 2025 CVE
|
||||
commons-lang3 3.17.0 is pulled transitively by Spring Boot — latest available.
|
||||
-->
|
||||
<suppress>
|
||||
<notes>NVD false positive: commons-lang3 3.17.0 is latest — CVE-2025-48924 version range incorrect</notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.apache\.commons/commons-lang3@.*$</packageUrl>
|
||||
<cve>CVE-2025-48924</cve>
|
||||
</suppress>
|
||||
|
||||
<!--
|
||||
NVD VERSION RANGE FALSE POSITIVES — log4j 2026 CVEs
|
||||
Covers all org.apache.logging.log4j artifacts (log4j-api, log4j-to-slf4j, etc.) — none
|
||||
contain the log4j-core implementation that had real CVEs. Wrong artifact CPE match.
|
||||
-->
|
||||
<suppress>
|
||||
<notes>NVD false positive: log4j CVE applied to non-core log4j artifacts with wrong version range</notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.apache\.logging\.log4j/.*@.*$</packageUrl>
|
||||
<cve>CVE-2026-34479</cve>
|
||||
<cve>CVE-2026-34477</cve>
|
||||
</suppress>
|
||||
|
||||
<!--
|
||||
NVD VERSION RANGE FALSE POSITIVES — Apache Kafka 2025/2026 CVEs
|
||||
kafka-clients 3.8.1 is pulled transitively by spring-kafka. No fix available in 3.8.x.
|
||||
Review when spring-kafka updates to kafka-clients 3.9.x+.
|
||||
-->
|
||||
<suppress>
|
||||
<notes>NVD false positive or no fix in 3.8.x: kafka-clients CVEs — review on spring-kafka upgrade</notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.apache\.kafka/.*@.*$</packageUrl>
|
||||
<cve>CVE-2025-27817</cve>
|
||||
<cve>CVE-2025-27818</cve>
|
||||
<cve>CVE-2026-33558</cve>
|
||||
</suppress>
|
||||
|
||||
<!--
|
||||
NVD VERSION RANGE FALSE POSITIVES — Netty 2026 CVEs
|
||||
netty 4.1.130.Final is pulled transitively by reactor-netty (spring-webflux) and
|
||||
spring-kafka. No fix available in the current 4.1.x line embedded by Spring Boot 3.4.x.
|
||||
-->
|
||||
<suppress>
|
||||
<notes>NVD false positive or no fix in 4.1.x: netty CVEs — review on spring-boot 3.5.x upgrade</notes>
|
||||
<packageUrl regex="true">^pkg:maven/io\.netty/.*@.*$</packageUrl>
|
||||
<cve>CVE-2026-42578</cve>
|
||||
<cve>CVE-2026-42579</cve>
|
||||
<cve>CVE-2026-42580</cve>
|
||||
<cve>CVE-2026-42581</cve>
|
||||
<cve>CVE-2026-42582</cve>
|
||||
<cve>CVE-2026-42583</cve>
|
||||
<cve>CVE-2026-42584</cve>
|
||||
<cve>CVE-2026-42585</cve>
|
||||
<cve>CVE-2026-42586</cve>
|
||||
<cve>CVE-2026-42587</cve>
|
||||
<cve>CVE-2026-41417</cve>
|
||||
<cve>CVE-2026-44248</cve>
|
||||
<cve>CVE-2026-33870</cve>
|
||||
<cve>CVE-2026-33871</cve>
|
||||
</suppress>
|
||||
|
||||
<!--
|
||||
NVD VERSION RANGE FALSE POSITIVE — PostgreSQL JDBC driver 2024/2026 CVEs
|
||||
postgresql 42.7.11 is the latest in the 42.7.x line. CVE-2024-1597 was patched in 42.7.2
|
||||
but NVD range is incorrectly broad. CVE-2026-42198 is a false positive on latest.
|
||||
-->
|
||||
<suppress>
|
||||
<notes>NVD false positive on postgresql 42.7.11 — CVE-2024-1597 fixed in 42.7.2+; CVE-2026-42198 version range incorrect</notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.postgresql/postgresql@.*$</packageUrl>
|
||||
<cve>CVE-2024-1597</cve>
|
||||
<cve>CVE-2026-42198</cve>
|
||||
</suppress>
|
||||
|
||||
<!--
|
||||
NVD CVE — commons-beanutils 1.9.4
|
||||
CVE-2025-48734 — review when commons-beanutils 2.x becomes available via Spring Boot.
|
||||
-->
|
||||
<suppress>
|
||||
<notes>commons-beanutils 1.9.4 is the version pulled transitively — no 2.x available yet in Spring Boot dependency chain</notes>
|
||||
<packageUrl regex="true">^pkg:maven/commons-beanutils/commons-beanutils@.*$</packageUrl>
|
||||
<cve>CVE-2025-48734</cve>
|
||||
</suppress>
|
||||
|
||||
</suppressions>
|
||||
@@ -0,0 +1,182 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.hiveops</groupId>
|
||||
<artifactId>hiveops-aria</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<description>ARIA — ATM Threat Monitoring and IOC Detection</description>
|
||||
|
||||
<parent>
|
||||
<groupId>com.hiveops</groupId>
|
||||
<artifactId>hiveops-bom</artifactId>
|
||||
<version>1.0.2</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Cloud Config Client -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Data JPA -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Validation -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Jakarta Validation API -->
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Hibernate Validator -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>8.0.1.Final</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Cache -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Caffeine Cache -->
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Actuator -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- PostgreSQL Driver -->
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Flyway DB migrations -->
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-database-postgresql</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Shared JWT validation -->
|
||||
<dependency>
|
||||
<groupId>com.hiveops</groupId>
|
||||
<artifactId>hiveops-security-common</artifactId>
|
||||
<version>1.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.40</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Kafka -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.kafka</groupId>
|
||||
<artifactId>spring-kafka</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.14.0</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.40</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>gitea</id>
|
||||
<url>https://hiveiq-gitea.directlx.dev/api/packages/hiveops/maven</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.hiveops.aria;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableCaching
|
||||
@EnableScheduling
|
||||
public class AriaApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AriaApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.hiveops.aria.config;
|
||||
|
||||
import com.hiveops.aria.security.JwtAuthenticationFilter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
|
||||
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
|
||||
.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
|
||||
.requestMatchers("/error").permitAll()
|
||||
.requestMatchers("/api/aria/ingest/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
|
||||
String originsEnv = System.getenv("CORS_ALLOWED_ORIGINS");
|
||||
if (originsEnv != null && !originsEnv.isBlank()) {
|
||||
config.setAllowedOrigins(Arrays.asList(originsEnv.split(",")));
|
||||
} else {
|
||||
config.setAllowedOrigins(List.of(
|
||||
"https://aria.bcos.cloud",
|
||||
"https://portal.bcos.cloud",
|
||||
"http://localhost:5181"
|
||||
));
|
||||
}
|
||||
|
||||
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
|
||||
config.setAllowedHeaders(List.of("Authorization", "Content-Type", "X-Requested-With"));
|
||||
config.setAllowCredentials(true);
|
||||
config.setMaxAge(3600L);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.hiveops.aria.controller;
|
||||
|
||||
import com.hiveops.aria.dto.ThreatEventIngestionRequest;
|
||||
import com.hiveops.aria.service.ThreatEventIngestionService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/aria/ingest")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ThreatEventIngestionController {
|
||||
|
||||
private final ThreatEventIngestionService ingestionService;
|
||||
|
||||
@Value("${aria.internal.service-secret:}")
|
||||
private String serviceSecret;
|
||||
|
||||
@PostMapping("/events")
|
||||
public ResponseEntity<Void> ingestEvents(
|
||||
@RequestHeader(value = "X-Service-Secret", required = false) String secret,
|
||||
@Valid @RequestBody ThreatEventIngestionRequest request) {
|
||||
|
||||
if (!serviceSecret.isBlank() && !serviceSecret.equals(secret)) {
|
||||
log.warn("Rejected ingestion request from {} — invalid service secret", request.getDeviceAgentId());
|
||||
return ResponseEntity.status(401).build();
|
||||
}
|
||||
|
||||
ingestionService.ingest(request);
|
||||
return ResponseEntity.accepted().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.hiveops.aria.controller;
|
||||
|
||||
import com.hiveops.aria.entity.ThreatIoc;
|
||||
import com.hiveops.aria.repository.ThreatIocRepository;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/aria/ioc")
|
||||
@RequiredArgsConstructor
|
||||
public class ThreatIocController {
|
||||
|
||||
private final ThreatIocRepository iocRepository;
|
||||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAnyRole('MSP_ADMIN','BCOS_ADMIN')")
|
||||
public List<ThreatIoc> getAll(
|
||||
@RequestParam(required = false) ThreatIoc.IocType type,
|
||||
@RequestParam(required = false, defaultValue = "true") boolean activeOnly) {
|
||||
|
||||
if (type != null) {
|
||||
return activeOnly
|
||||
? iocRepository.findByActiveTrueAndIocType(type)
|
||||
: iocRepository.findAll().stream().filter(i -> i.getIocType() == type).toList();
|
||||
}
|
||||
return activeOnly ? iocRepository.findByActiveTrue() : iocRepository.findAll();
|
||||
}
|
||||
|
||||
@GetMapping("/active")
|
||||
public List<ThreatIoc> getActive(@RequestParam(required = false) ThreatIoc.IocType type) {
|
||||
return type != null
|
||||
? iocRepository.findByActiveTrueAndIocType(type)
|
||||
: iocRepository.findByActiveTrue();
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAnyRole('MSP_ADMIN','BCOS_ADMIN')")
|
||||
public ResponseEntity<ThreatIoc> create(@Valid @RequestBody CreateIocRequest request) {
|
||||
ThreatIoc ioc = ThreatIoc.builder()
|
||||
.iocType(request.getIocType())
|
||||
.value(request.getValue())
|
||||
.description(request.getDescription())
|
||||
.source(ThreatIoc.IocSource.MANUAL)
|
||||
.sourceRef(request.getSourceRef())
|
||||
.confidence(request.getConfidence() != null ? request.getConfidence() : ThreatIoc.ConfidenceLevel.MEDIUM)
|
||||
.expiresAt(request.getExpiresAt())
|
||||
.build();
|
||||
return ResponseEntity.ok(iocRepository.save(ioc));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAnyRole('MSP_ADMIN','BCOS_ADMIN')")
|
||||
public ResponseEntity<ThreatIoc> update(@PathVariable Long id, @Valid @RequestBody CreateIocRequest request) {
|
||||
return iocRepository.findById(id).map(ioc -> {
|
||||
ioc.setValue(request.getValue());
|
||||
ioc.setDescription(request.getDescription());
|
||||
ioc.setConfidence(request.getConfidence() != null ? request.getConfidence() : ioc.getConfidence());
|
||||
ioc.setExpiresAt(request.getExpiresAt());
|
||||
return ResponseEntity.ok(iocRepository.save(ioc));
|
||||
}).orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAnyRole('MSP_ADMIN','BCOS_ADMIN')")
|
||||
public ResponseEntity<Void> deactivate(@PathVariable Long id) {
|
||||
return iocRepository.findById(id).map(ioc -> {
|
||||
ioc.setActive(false);
|
||||
iocRepository.save(ioc);
|
||||
return ResponseEntity.noContent().<Void>build();
|
||||
}).orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class CreateIocRequest {
|
||||
@NotNull
|
||||
private ThreatIoc.IocType iocType;
|
||||
@NotBlank
|
||||
private String value;
|
||||
private String description;
|
||||
private String sourceRef;
|
||||
private ThreatIoc.ConfidenceLevel confidence;
|
||||
private Instant expiresAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.hiveops.aria.dto;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ThreatEventIngestionRequest {
|
||||
|
||||
@NotBlank
|
||||
private String deviceAgentId;
|
||||
|
||||
private Long deviceId;
|
||||
|
||||
private String institutionKey;
|
||||
|
||||
@NotEmpty
|
||||
@Valid
|
||||
private List<ThreatEventItem> events;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.hiveops.aria.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class ThreatEventItem {
|
||||
|
||||
/** e.g. "EVENT_4688", "DOOR_OPEN", "HASH_DRIFT", "IOC_PROCESS_MATCH" */
|
||||
@NotBlank
|
||||
private String signalKey;
|
||||
|
||||
/** OS_EVENT | PHYSICAL | FILE_SYSTEM | COMPOSITE */
|
||||
@NotBlank
|
||||
private String eventType;
|
||||
|
||||
private Map<String, Object> rawPayload;
|
||||
|
||||
private Instant detectedAt;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.hiveops.aria.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Entity
|
||||
@Table(name = "device_security_posture")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class DeviceSecurityPosture {
|
||||
|
||||
@Id
|
||||
@Column(name = "device_id")
|
||||
private Long deviceId;
|
||||
|
||||
@Column(name = "device_agent_id")
|
||||
private String deviceAgentId;
|
||||
|
||||
@Column(name = "institution_key")
|
||||
private String institutionKey;
|
||||
|
||||
@Column(name = "audit_policy_compliant")
|
||||
private Boolean auditPolicyCompliant;
|
||||
|
||||
@Column(name = "gold_image_validated_at")
|
||||
private Instant goldImageValidatedAt;
|
||||
|
||||
@Column(name = "gold_image_hash")
|
||||
private String goldImageHash;
|
||||
|
||||
@Column(name = "current_image_hash")
|
||||
private String currentImageHash;
|
||||
|
||||
@Column(name = "disk_encryption_enabled")
|
||||
private Boolean diskEncryptionEnabled;
|
||||
|
||||
@Column(name = "os_version")
|
||||
private String osVersion;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "os_eol_status", nullable = false, columnDefinition = "os_eol_status")
|
||||
@Builder.Default
|
||||
private OsEolStatus osEolStatus = OsEolStatus.UNKNOWN;
|
||||
|
||||
@Column(name = "software_whitelist_enabled")
|
||||
private Boolean softwareWhitelistEnabled;
|
||||
|
||||
@Column(name = "last_posture_check_at")
|
||||
private Instant lastPostureCheckAt;
|
||||
|
||||
@Column(name = "posture_score")
|
||||
private Integer postureScore;
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
@Builder.Default
|
||||
private Instant updatedAt = Instant.now();
|
||||
|
||||
public enum OsEolStatus {
|
||||
SUPPORTED, EOL, UNKNOWN
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.hiveops.aria.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "precursor_sequence_alert")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class PrecursorSequenceAlert {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "device_id")
|
||||
private Long deviceId;
|
||||
|
||||
@Column(name = "device_agent_id")
|
||||
private String deviceAgentId;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "sequence_type", nullable = false, columnDefinition = "sequence_type")
|
||||
@Builder.Default
|
||||
private SequenceType sequenceType = SequenceType.JACKPOTTING;
|
||||
|
||||
@Column(name = "phases_detected", columnDefinition = "attack_phase[]")
|
||||
private String[] phasesDetected;
|
||||
|
||||
@Column(nullable = false, precision = 4, scale = 3)
|
||||
@Builder.Default
|
||||
private BigDecimal confidence = BigDecimal.ZERO;
|
||||
|
||||
@Column(name = "triggered_at", nullable = false)
|
||||
@Builder.Default
|
||||
private Instant triggeredAt = Instant.now();
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "auto_response_taken", nullable = false, columnDefinition = "auto_response_action")
|
||||
@Builder.Default
|
||||
private AutoResponseAction autoResponseTaken = AutoResponseAction.NONE;
|
||||
|
||||
@Column(name = "resolved_at")
|
||||
private Instant resolvedAt;
|
||||
|
||||
@Column(name = "resolved_by")
|
||||
private String resolvedBy;
|
||||
|
||||
@Column(name = "institution_key")
|
||||
private String institutionKey;
|
||||
|
||||
@Column(name = "sequence_id", nullable = false)
|
||||
@Builder.Default
|
||||
private UUID sequenceId = UUID.randomUUID();
|
||||
|
||||
public enum SequenceType {
|
||||
JACKPOTTING, SKIMMING, UNKNOWN
|
||||
}
|
||||
|
||||
public enum AutoResponseAction {
|
||||
NONE, SHUTDOWN, NOTIFICATION, LOCKDOWN
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.hiveops.aria.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "threat_event")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class ThreatEvent {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "device_id")
|
||||
private Long deviceId;
|
||||
|
||||
@Column(name = "device_agent_id")
|
||||
private String deviceAgentId;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "event_type", nullable = false, columnDefinition = "threat_event_type")
|
||||
private EventType eventType;
|
||||
|
||||
@Column(name = "signal_key", nullable = false)
|
||||
private String signalKey;
|
||||
|
||||
@JdbcTypeCode(SqlTypes.JSON)
|
||||
@Column(name = "raw_payload", columnDefinition = "jsonb")
|
||||
private Map<String, Object> rawPayload;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "matched_ioc_id")
|
||||
private ThreatIoc matchedIoc;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, columnDefinition = "threat_severity")
|
||||
private ThreatSeverity severity;
|
||||
|
||||
@Column(name = "detected_at", nullable = false)
|
||||
@Builder.Default
|
||||
private Instant detectedAt = Instant.now();
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "attack_phase", columnDefinition = "attack_phase")
|
||||
private AttackPhase attackPhase;
|
||||
|
||||
@Column(name = "sequence_id")
|
||||
private UUID sequenceId;
|
||||
|
||||
@Column(name = "institution_key")
|
||||
private String institutionKey;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
@Builder.Default
|
||||
private Instant createdAt = Instant.now();
|
||||
|
||||
public enum EventType {
|
||||
PHYSICAL, OS_EVENT, FILE_SYSTEM, COMPOSITE
|
||||
}
|
||||
|
||||
public enum ThreatSeverity {
|
||||
CRITICAL, HIGH, MEDIUM, LOW, INFO
|
||||
}
|
||||
|
||||
public enum AttackPhase {
|
||||
PHYSICAL_ACCESS, MALWARE_STAGING, MALWARE_EXECUTION, PERSISTENCE, CLEANUP
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.hiveops.aria.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Entity
|
||||
@Table(name = "threat_ioc")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class ThreatIoc {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "ioc_type", nullable = false, columnDefinition = "ioc_type")
|
||||
private IocType iocType;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String value;
|
||||
|
||||
private String description;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, columnDefinition = "ioc_source")
|
||||
@Builder.Default
|
||||
private IocSource source = IocSource.FBI_FLASH;
|
||||
|
||||
@Column(name = "source_ref")
|
||||
private String sourceRef;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, columnDefinition = "confidence_level")
|
||||
@Builder.Default
|
||||
private ConfidenceLevel confidence = ConfidenceLevel.HIGH;
|
||||
|
||||
@Column(name = "added_at", nullable = false)
|
||||
@Builder.Default
|
||||
private Instant addedAt = Instant.now();
|
||||
|
||||
@Column(name = "expires_at")
|
||||
private Instant expiresAt;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean active = true;
|
||||
|
||||
public enum IocType {
|
||||
FILENAME, MD5, REGISTRY_KEY, DIRECTORY, SERVICE_NAME, IP, FILE_PATH
|
||||
}
|
||||
|
||||
public enum IocSource {
|
||||
FBI_FLASH, FS_ISAC, MANUAL
|
||||
}
|
||||
|
||||
public enum ConfidenceLevel {
|
||||
HIGH, MEDIUM, LOW
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.hiveops.aria.kafka;
|
||||
|
||||
import org.apache.kafka.clients.admin.NewTopic;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.kafka.config.TopicBuilder;
|
||||
|
||||
@Configuration
|
||||
public class AriaKafkaConfig {
|
||||
|
||||
public static final String TOPIC_THREATS = "hiveops.aria.threats";
|
||||
|
||||
@Bean
|
||||
public NewTopic ariaThreatsTopic() {
|
||||
return TopicBuilder.name(TOPIC_THREATS).partitions(1).replicas(1).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.hiveops.aria.kafka;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AriaThreatEvent {
|
||||
|
||||
private UUID eventId;
|
||||
|
||||
private Long deviceId;
|
||||
private String deviceAgentId;
|
||||
private String institutionKey;
|
||||
|
||||
private String signalKey;
|
||||
private String eventType;
|
||||
|
||||
/** CRITICAL | HIGH | MEDIUM | LOW | INFO */
|
||||
private String severity;
|
||||
|
||||
private String description;
|
||||
|
||||
private String matchedIocValue;
|
||||
private String matchedIocType;
|
||||
|
||||
/** Attack phase if part of a sequence: PHYSICAL_ACCESS | MALWARE_STAGING | etc. */
|
||||
private String attackPhase;
|
||||
|
||||
/** Groups events belonging to the same attack chain. */
|
||||
private UUID sequenceId;
|
||||
|
||||
private Instant detectedAt;
|
||||
|
||||
private String sourceService;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.hiveops.aria.kafka;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.kafka.core.KafkaTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class AriaThreatProducer {
|
||||
|
||||
private final KafkaTemplate<String, Object> kafkaTemplate;
|
||||
|
||||
public void publishThreatEvent(AriaThreatEvent event) {
|
||||
String key = event.getDeviceAgentId() != null ? event.getDeviceAgentId() : "unknown";
|
||||
|
||||
kafkaTemplate.send(AriaKafkaConfig.TOPIC_THREATS, key, event)
|
||||
.whenComplete((result, ex) -> {
|
||||
if (ex != null) {
|
||||
log.error("Failed to publish ARIA threat event {} to Kafka: {}",
|
||||
event.getEventId(), ex.getMessage());
|
||||
} else {
|
||||
log.debug("Published ARIA threat event {} (signal={}, severity={}) to partition {}",
|
||||
event.getEventId(), event.getSignalKey(), event.getSeverity(),
|
||||
result.getRecordMetadata().partition());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.hiveops.aria.repository;
|
||||
|
||||
import com.hiveops.aria.entity.DeviceSecurityPosture;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface DeviceSecurityPostureRepository extends JpaRepository<DeviceSecurityPosture, Long> {
|
||||
|
||||
Optional<DeviceSecurityPosture> findByDeviceAgentId(String deviceAgentId);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.hiveops.aria.repository;
|
||||
|
||||
import com.hiveops.aria.entity.PrecursorSequenceAlert;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface PrecursorSequenceAlertRepository extends JpaRepository<PrecursorSequenceAlert, Long> {
|
||||
|
||||
Page<PrecursorSequenceAlert> findByResolvedAtIsNullOrderByTriggeredAtDesc(Pageable pageable);
|
||||
|
||||
Page<PrecursorSequenceAlert> findByDeviceAgentIdOrderByTriggeredAtDesc(String deviceAgentId, Pageable pageable);
|
||||
|
||||
Page<PrecursorSequenceAlert> findAllByOrderByTriggeredAtDesc(Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.hiveops.aria.repository;
|
||||
|
||||
import com.hiveops.aria.entity.ThreatEvent;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface ThreatEventRepository extends JpaRepository<ThreatEvent, Long> {
|
||||
|
||||
Page<ThreatEvent> findByDeviceAgentIdOrderByDetectedAtDesc(String deviceAgentId, Pageable pageable);
|
||||
|
||||
List<ThreatEvent> findBySequenceId(UUID sequenceId);
|
||||
|
||||
List<ThreatEvent> findByDeviceAgentIdAndDetectedAtAfterOrderByDetectedAtAsc(
|
||||
String deviceAgentId, Instant since);
|
||||
|
||||
Page<ThreatEvent> findBySeverityOrderByDetectedAtDesc(ThreatEvent.ThreatSeverity severity, Pageable pageable);
|
||||
|
||||
Page<ThreatEvent> findAllByOrderByDetectedAtDesc(Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.hiveops.aria.repository;
|
||||
|
||||
import com.hiveops.aria.entity.ThreatIoc;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface ThreatIocRepository extends JpaRepository<ThreatIoc, Long> {
|
||||
|
||||
List<ThreatIoc> findByActiveTrue();
|
||||
|
||||
List<ThreatIoc> findByActiveTrueAndIocType(ThreatIoc.IocType iocType);
|
||||
|
||||
Optional<ThreatIoc> findByActiveTrueAndIocTypeAndValueIgnoreCase(ThreatIoc.IocType iocType, String value);
|
||||
|
||||
@Query("SELECT i FROM ThreatIoc i WHERE i.active = true AND i.iocType IN ('FILENAME', 'MD5', 'FILE_PATH')")
|
||||
List<ThreatIoc> findActiveExecutableIocs();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.hiveops.aria.security;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class AriaPrincipal {
|
||||
private final Long userId;
|
||||
private final String email;
|
||||
private final String role;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.hiveops.aria.security;
|
||||
|
||||
import com.hiveops.security.JwtTokenValidator;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtTokenValidator tokenValidator;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
try {
|
||||
String token = extractToken(request);
|
||||
|
||||
if (StringUtils.hasText(token) && tokenValidator.validateToken(token)) {
|
||||
Claims claims = tokenValidator.getClaims(token);
|
||||
|
||||
Long userId = Long.parseLong(claims.getSubject());
|
||||
String email = claims.get("email", String.class);
|
||||
String role = claims.get("role", String.class);
|
||||
|
||||
AriaPrincipal principal = new AriaPrincipal(userId, email, role);
|
||||
var authority = new SimpleGrantedAuthority("ROLE_" + (role != null ? role : "USER"));
|
||||
var auth = new UsernamePasswordAuthenticationToken(principal, null, List.of(authority));
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.debug("Could not set user authentication: {}", ex.getMessage());
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private String extractToken(HttpServletRequest request) {
|
||||
String header = request.getHeader("Authorization");
|
||||
if (StringUtils.hasText(header) && header.startsWith("Bearer ")) {
|
||||
return header.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package com.hiveops.aria.service;
|
||||
|
||||
import com.hiveops.aria.dto.ThreatEventItem;
|
||||
import com.hiveops.aria.entity.ThreatEvent;
|
||||
import com.hiveops.aria.entity.ThreatEvent.AttackPhase;
|
||||
import com.hiveops.aria.entity.ThreatEvent.ThreatSeverity;
|
||||
import com.hiveops.aria.entity.ThreatIoc;
|
||||
import com.hiveops.aria.repository.ThreatIocRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ThreatEventClassifier {
|
||||
|
||||
private final ThreatIocRepository iocRepository;
|
||||
|
||||
public ClassificationResult classify(ThreatEventItem item) {
|
||||
String signal = item.getSignalKey();
|
||||
ThreatSeverity severity;
|
||||
AttackPhase phase = null;
|
||||
ThreatIoc matchedIoc = null;
|
||||
|
||||
switch (signal) {
|
||||
// Windows Security Event IDs — FBI FLASH-20260219-001
|
||||
case "EVENT_1102" -> {
|
||||
severity = ThreatSeverity.CRITICAL;
|
||||
phase = AttackPhase.CLEANUP;
|
||||
}
|
||||
case "EVENT_4719" -> {
|
||||
severity = ThreatSeverity.HIGH;
|
||||
phase = AttackPhase.CLEANUP;
|
||||
}
|
||||
case "EVENT_6416", "EVENT_2003" -> {
|
||||
severity = ThreatSeverity.HIGH;
|
||||
phase = AttackPhase.PHYSICAL_ACCESS;
|
||||
matchedIoc = matchUsb(item);
|
||||
}
|
||||
case "EVENT_4663" -> {
|
||||
severity = ThreatSeverity.HIGH;
|
||||
phase = AttackPhase.MALWARE_STAGING;
|
||||
matchedIoc = matchFilePath(item);
|
||||
}
|
||||
case "EVENT_4688" -> {
|
||||
matchedIoc = matchProcessName(item);
|
||||
if (matchedIoc != null) {
|
||||
severity = ThreatSeverity.HIGH;
|
||||
phase = AttackPhase.MALWARE_EXECUTION;
|
||||
} else {
|
||||
severity = ThreatSeverity.INFO;
|
||||
}
|
||||
}
|
||||
case "EVENT_4697" -> {
|
||||
matchedIoc = matchServiceName(item);
|
||||
if (matchedIoc != null) {
|
||||
severity = ThreatSeverity.HIGH;
|
||||
phase = AttackPhase.PERSISTENCE;
|
||||
} else {
|
||||
severity = ThreatSeverity.MEDIUM;
|
||||
phase = AttackPhase.PERSISTENCE;
|
||||
}
|
||||
}
|
||||
// Physical sensor signals
|
||||
case "DOOR_OPEN" -> {
|
||||
severity = ThreatSeverity.MEDIUM;
|
||||
phase = AttackPhase.PHYSICAL_ACCESS;
|
||||
}
|
||||
case "CARD_READER_TAMPER" -> {
|
||||
severity = ThreatSeverity.HIGH;
|
||||
phase = AttackPhase.PHYSICAL_ACCESS;
|
||||
}
|
||||
case "USB_INSERTION" -> {
|
||||
severity = ThreatSeverity.HIGH;
|
||||
phase = AttackPhase.PHYSICAL_ACCESS;
|
||||
}
|
||||
case "POWER_CYCLE_UNEXPECTED", "OUT_OF_SERVICE" -> {
|
||||
severity = ThreatSeverity.MEDIUM;
|
||||
}
|
||||
case "VIBRATION_ANOMALY" -> {
|
||||
severity = ThreatSeverity.MEDIUM;
|
||||
}
|
||||
case "CASH_LOW_UNEXPECTED" -> {
|
||||
severity = ThreatSeverity.HIGH;
|
||||
}
|
||||
// File system signals
|
||||
case "HASH_DRIFT" -> {
|
||||
severity = ThreatSeverity.HIGH;
|
||||
phase = AttackPhase.MALWARE_STAGING;
|
||||
}
|
||||
case "IOC_PROCESS_MATCH", "IOC_FILENAME_MATCH" -> {
|
||||
matchedIoc = matchProcessName(item);
|
||||
severity = ThreatSeverity.HIGH;
|
||||
phase = AttackPhase.MALWARE_EXECUTION;
|
||||
}
|
||||
case "REGISTRY_PERSISTENCE_DETECTED" -> {
|
||||
matchedIoc = matchRegistryKey(item);
|
||||
severity = ThreatSeverity.HIGH;
|
||||
phase = AttackPhase.PERSISTENCE;
|
||||
}
|
||||
case "SUSPICIOUS_DIRECTORY" -> {
|
||||
matchedIoc = matchDirectory(item);
|
||||
severity = ThreatSeverity.HIGH;
|
||||
phase = AttackPhase.MALWARE_STAGING;
|
||||
}
|
||||
case "REMOTE_TOOL_DETECTED" -> {
|
||||
matchedIoc = matchProcessName(item);
|
||||
severity = ThreatSeverity.MEDIUM;
|
||||
}
|
||||
default -> {
|
||||
severity = ThreatSeverity.INFO;
|
||||
log.debug("Unclassified signal key: {}", signal);
|
||||
}
|
||||
}
|
||||
|
||||
return new ClassificationResult(severity, phase, matchedIoc);
|
||||
}
|
||||
|
||||
private ThreatIoc matchProcessName(ThreatEventItem item) {
|
||||
String name = extractString(item, "processName", "imageName", "fileName");
|
||||
if (name == null) return null;
|
||||
return iocRepository.findByActiveTrueAndIocTypeAndValueIgnoreCase(ThreatIoc.IocType.FILENAME, name).orElse(null);
|
||||
}
|
||||
|
||||
private ThreatIoc matchServiceName(ThreatEventItem item) {
|
||||
String name = extractString(item, "serviceName", "serviceDisplayName");
|
||||
if (name == null) return null;
|
||||
return iocRepository.findByActiveTrueAndIocTypeAndValueIgnoreCase(ThreatIoc.IocType.SERVICE_NAME, name).orElse(null);
|
||||
}
|
||||
|
||||
private ThreatIoc matchFilePath(ThreatEventItem item) {
|
||||
String path = extractString(item, "objectName", "filePath");
|
||||
if (path == null) return null;
|
||||
Optional<ThreatIoc> dir = iocRepository.findByActiveTrueAndIocTypeAndValueIgnoreCase(ThreatIoc.IocType.DIRECTORY, path);
|
||||
return dir.orElseGet(() ->
|
||||
iocRepository.findByActiveTrueAndIocTypeAndValueIgnoreCase(ThreatIoc.IocType.FILE_PATH, path).orElse(null));
|
||||
}
|
||||
|
||||
private ThreatIoc matchRegistryKey(ThreatEventItem item) {
|
||||
String key = extractString(item, "registryKey", "keyPath");
|
||||
if (key == null) return null;
|
||||
return iocRepository.findByActiveTrueAndIocTypeAndValueIgnoreCase(ThreatIoc.IocType.REGISTRY_KEY, key).orElse(null);
|
||||
}
|
||||
|
||||
private ThreatIoc matchDirectory(ThreatEventItem item) {
|
||||
String path = extractString(item, "directory", "path");
|
||||
if (path == null) return null;
|
||||
return iocRepository.findByActiveTrueAndIocTypeAndValueIgnoreCase(ThreatIoc.IocType.DIRECTORY, path).orElse(null);
|
||||
}
|
||||
|
||||
private ThreatIoc matchUsb(ThreatEventItem item) {
|
||||
String deviceId = extractString(item, "deviceId", "deviceDescription");
|
||||
if (deviceId == null) return null;
|
||||
return iocRepository.findByActiveTrueAndIocTypeAndValueIgnoreCase(ThreatIoc.IocType.IP, deviceId).orElse(null);
|
||||
}
|
||||
|
||||
private String extractString(ThreatEventItem item, String... keys) {
|
||||
if (item.getRawPayload() == null) return null;
|
||||
for (String key : keys) {
|
||||
Object val = item.getRawPayload().get(key);
|
||||
if (val instanceof String s && !s.isBlank()) return s;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public record ClassificationResult(ThreatSeverity severity, AttackPhase attackPhase, ThreatIoc matchedIoc) {}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.hiveops.aria.service;
|
||||
|
||||
import com.hiveops.aria.dto.ThreatEventIngestionRequest;
|
||||
import com.hiveops.aria.dto.ThreatEventItem;
|
||||
import com.hiveops.aria.entity.ThreatEvent;
|
||||
import com.hiveops.aria.kafka.AriaThreatEvent;
|
||||
import com.hiveops.aria.kafka.AriaThreatProducer;
|
||||
import com.hiveops.aria.repository.ThreatEventRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ThreatEventIngestionService {
|
||||
|
||||
private final ThreatEventRepository eventRepository;
|
||||
private final ThreatEventClassifier classifier;
|
||||
private final AriaThreatProducer producer;
|
||||
|
||||
@Transactional
|
||||
public void ingest(ThreatEventIngestionRequest request) {
|
||||
for (ThreatEventItem item : request.getEvents()) {
|
||||
try {
|
||||
processEvent(request, item);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to process event {} from {}: {}",
|
||||
item.getSignalKey(), request.getDeviceAgentId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processEvent(ThreatEventIngestionRequest request, ThreatEventItem item) {
|
||||
ThreatEventClassifier.ClassificationResult result = classifier.classify(item);
|
||||
|
||||
ThreatEvent.EventType eventType;
|
||||
try {
|
||||
eventType = ThreatEvent.EventType.valueOf(item.getEventType());
|
||||
} catch (IllegalArgumentException e) {
|
||||
eventType = ThreatEvent.EventType.OS_EVENT;
|
||||
}
|
||||
|
||||
ThreatEvent event = ThreatEvent.builder()
|
||||
.deviceId(request.getDeviceId())
|
||||
.deviceAgentId(request.getDeviceAgentId())
|
||||
.eventType(eventType)
|
||||
.signalKey(item.getSignalKey())
|
||||
.rawPayload(item.getRawPayload())
|
||||
.matchedIoc(result.matchedIoc())
|
||||
.severity(result.severity())
|
||||
.detectedAt(item.getDetectedAt() != null ? item.getDetectedAt() : Instant.now())
|
||||
.attackPhase(result.attackPhase())
|
||||
.institutionKey(request.getInstitutionKey())
|
||||
.build();
|
||||
|
||||
eventRepository.save(event);
|
||||
|
||||
log.info("ARIA event ingested: device={} signal={} severity={} phase={}",
|
||||
request.getDeviceAgentId(), item.getSignalKey(), result.severity(), result.attackPhase());
|
||||
|
||||
// Publish HIGH and above to Kafka so any service (incident, future consumers) can react
|
||||
if (shouldPublish(result.severity())) {
|
||||
publishToKafka(event, result);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldPublish(ThreatEvent.ThreatSeverity severity) {
|
||||
return severity == ThreatEvent.ThreatSeverity.CRITICAL
|
||||
|| severity == ThreatEvent.ThreatSeverity.HIGH;
|
||||
}
|
||||
|
||||
private void publishToKafka(ThreatEvent event, ThreatEventClassifier.ClassificationResult result) {
|
||||
String description = buildDescription(event, result);
|
||||
|
||||
AriaThreatEvent kafkaEvent = AriaThreatEvent.builder()
|
||||
.eventId(UUID.randomUUID())
|
||||
.deviceId(event.getDeviceId())
|
||||
.deviceAgentId(event.getDeviceAgentId())
|
||||
.institutionKey(event.getInstitutionKey())
|
||||
.signalKey(event.getSignalKey())
|
||||
.eventType(event.getEventType().name())
|
||||
.severity(event.getSeverity().name())
|
||||
.description(description)
|
||||
.matchedIocValue(result.matchedIoc() != null ? result.matchedIoc().getValue() : null)
|
||||
.matchedIocType(result.matchedIoc() != null ? result.matchedIoc().getIocType().name() : null)
|
||||
.attackPhase(event.getAttackPhase() != null ? event.getAttackPhase().name() : null)
|
||||
.sequenceId(event.getSequenceId())
|
||||
.detectedAt(event.getDetectedAt())
|
||||
.sourceService("hiveops-aria")
|
||||
.build();
|
||||
|
||||
producer.publishThreatEvent(kafkaEvent);
|
||||
}
|
||||
|
||||
private String buildDescription(ThreatEvent event, ThreatEventClassifier.ClassificationResult result) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("ARIA threat signal detected: ").append(event.getSignalKey());
|
||||
|
||||
if (result.matchedIoc() != null) {
|
||||
sb.append(" — IOC match: ").append(result.matchedIoc().getValue());
|
||||
if (result.matchedIoc().getDescription() != null) {
|
||||
sb.append(" (").append(result.matchedIoc().getDescription()).append(")");
|
||||
}
|
||||
}
|
||||
|
||||
if (result.attackPhase() != null) {
|
||||
sb.append(" [Phase: ").append(result.attackPhase().name()).append("]");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
spring.application.name=hiveops-aria
|
||||
|
||||
# Server
|
||||
server.port=8095
|
||||
|
||||
# Database
|
||||
spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:5432/hiveiq_aria}
|
||||
spring.datasource.username=${DB_USERNAME:hiveiq}
|
||||
spring.datasource.password=${DB_PASSWORD}
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
|
||||
# JPA/Hibernate
|
||||
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
|
||||
# Flyway
|
||||
spring.flyway.enabled=true
|
||||
spring.flyway.locations=classpath:db/migration
|
||||
spring.flyway.baseline-on-migrate=true
|
||||
spring.flyway.baseline-version=1
|
||||
spring.flyway.validate-on-migrate=true
|
||||
spring.jpa.show-sql=false
|
||||
spring.jpa.properties.hibernate.format_sql=true
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
||||
|
||||
# Logging
|
||||
logging.level.root=WARN
|
||||
logging.level.com.hiveops.aria=INFO
|
||||
logging.level.org.springframework.security=DEBUG
|
||||
|
||||
# Error responses
|
||||
server.error.include-message=always
|
||||
|
||||
# Actuator
|
||||
management.endpoints.web.exposure.include=health,info,loggers
|
||||
management.endpoint.loggers.enabled=true
|
||||
|
||||
# Pagination
|
||||
spring.data.web.pageable.default-page-size=25
|
||||
spring.data.web.pageable.max-page-size=100
|
||||
|
||||
# Jackson
|
||||
spring.jackson.default-property-inclusion=non_null
|
||||
spring.jackson.serialization.write-dates-as-timestamps=false
|
||||
spring.jackson.time-zone=UTC
|
||||
|
||||
# JWT
|
||||
jwt.secret=${JWT_SECRET}
|
||||
jwt.expiration=86400000
|
||||
|
||||
# OpenAPI
|
||||
springdoc.api-docs.enabled=${SWAGGER_ENABLED:false}
|
||||
springdoc.swagger-ui.enabled=${SWAGGER_ENABLED:false}
|
||||
|
||||
# Internal service auth (used by agent ingestion endpoint)
|
||||
aria.internal.service-secret=${ARIA_SERVICE_SECRET:}
|
||||
|
||||
# Sequence detection window (minutes)
|
||||
aria.sequence.window.minutes=${ARIA_SEQUENCE_WINDOW_MINUTES:15}
|
||||
|
||||
# Auto-response — disabled by default for safe initial deployment
|
||||
aria.autoresponse.shutdown.enabled=${ARIA_AUTORESPONSE_SHUTDOWN_ENABLED:false}
|
||||
|
||||
# hiveops-incident (for auto-response fleet tasks)
|
||||
aria.incident.url=${INCIDENT_SERVICE_URL:http://hiveiq-incident:8080}
|
||||
aria.incident.service-secret=${INTERNAL_SERVICE_SECRET:}
|
||||
|
||||
# Kafka
|
||||
spring.kafka.bootstrap-servers=${KAFKA_BOOTSTRAP_SERVERS:localhost:9092}
|
||||
spring.kafka.admin.fail-fast=false
|
||||
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
|
||||
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
|
||||
spring.kafka.producer.properties.spring.json.add.type.headers=false
|
||||
@@ -0,0 +1,98 @@
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
CREATE TYPE ioc_type AS ENUM (
|
||||
'FILENAME', 'MD5', 'REGISTRY_KEY', 'DIRECTORY', 'SERVICE_NAME', 'IP', 'FILE_PATH'
|
||||
);
|
||||
|
||||
CREATE TYPE ioc_source AS ENUM ('FBI_FLASH', 'FS_ISAC', 'MANUAL');
|
||||
|
||||
CREATE TYPE confidence_level AS ENUM ('HIGH', 'MEDIUM', 'LOW');
|
||||
|
||||
CREATE TABLE threat_ioc (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
ioc_type ioc_type NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
source ioc_source NOT NULL DEFAULT 'FBI_FLASH',
|
||||
source_ref VARCHAR(100),
|
||||
confidence confidence_level NOT NULL DEFAULT 'HIGH',
|
||||
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
UNIQUE (ioc_type, value)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_threat_ioc_active ON threat_ioc(active);
|
||||
CREATE INDEX idx_threat_ioc_type ON threat_ioc(ioc_type);
|
||||
|
||||
CREATE TYPE threat_severity AS ENUM ('CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO');
|
||||
|
||||
CREATE TYPE threat_event_type AS ENUM ('PHYSICAL', 'OS_EVENT', 'FILE_SYSTEM', 'COMPOSITE');
|
||||
|
||||
CREATE TYPE attack_phase AS ENUM (
|
||||
'PHYSICAL_ACCESS', 'MALWARE_STAGING', 'MALWARE_EXECUTION', 'PERSISTENCE', 'CLEANUP'
|
||||
);
|
||||
|
||||
CREATE TABLE threat_event (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
device_id BIGINT,
|
||||
device_agent_id VARCHAR(100),
|
||||
event_type threat_event_type NOT NULL,
|
||||
signal_key VARCHAR(100) NOT NULL,
|
||||
raw_payload JSONB,
|
||||
matched_ioc_id BIGINT REFERENCES threat_ioc(id),
|
||||
severity threat_severity NOT NULL,
|
||||
detected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
attack_phase attack_phase,
|
||||
sequence_id UUID,
|
||||
institution_key VARCHAR(100),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_threat_event_device_id ON threat_event(device_id);
|
||||
CREATE INDEX idx_threat_event_sequence ON threat_event(sequence_id);
|
||||
CREATE INDEX idx_threat_event_detected ON threat_event(detected_at DESC);
|
||||
CREATE INDEX idx_threat_event_severity ON threat_event(severity);
|
||||
CREATE INDEX idx_threat_event_agent ON threat_event(device_agent_id);
|
||||
|
||||
CREATE TYPE sequence_type AS ENUM ('JACKPOTTING', 'SKIMMING', 'UNKNOWN');
|
||||
|
||||
CREATE TYPE auto_response_action AS ENUM ('NONE', 'SHUTDOWN', 'NOTIFICATION', 'LOCKDOWN');
|
||||
|
||||
CREATE TABLE precursor_sequence_alert (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
device_id BIGINT,
|
||||
device_agent_id VARCHAR(100),
|
||||
sequence_type sequence_type NOT NULL DEFAULT 'JACKPOTTING',
|
||||
phases_detected attack_phase[] NOT NULL DEFAULT '{}',
|
||||
confidence NUMERIC(4,3) NOT NULL DEFAULT 0.0,
|
||||
triggered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
auto_response_taken auto_response_action NOT NULL DEFAULT 'NONE',
|
||||
resolved_at TIMESTAMPTZ,
|
||||
resolved_by VARCHAR(200),
|
||||
institution_key VARCHAR(100),
|
||||
sequence_id UUID NOT NULL DEFAULT uuid_generate_v4()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_sequence_alert_device ON precursor_sequence_alert(device_id);
|
||||
CREATE INDEX idx_sequence_alert_trigger ON precursor_sequence_alert(triggered_at DESC);
|
||||
CREATE INDEX idx_sequence_alert_open ON precursor_sequence_alert(resolved_at) WHERE resolved_at IS NULL;
|
||||
|
||||
CREATE TYPE os_eol_status AS ENUM ('SUPPORTED', 'EOL', 'UNKNOWN');
|
||||
|
||||
CREATE TABLE device_security_posture (
|
||||
device_id BIGINT PRIMARY KEY,
|
||||
device_agent_id VARCHAR(100),
|
||||
institution_key VARCHAR(100),
|
||||
audit_policy_compliant BOOLEAN,
|
||||
gold_image_validated_at TIMESTAMPTZ,
|
||||
gold_image_hash VARCHAR(128),
|
||||
current_image_hash VARCHAR(128),
|
||||
disk_encryption_enabled BOOLEAN,
|
||||
os_version VARCHAR(200),
|
||||
os_eol_status os_eol_status NOT NULL DEFAULT 'UNKNOWN',
|
||||
software_whitelist_enabled BOOLEAN,
|
||||
last_posture_check_at TIMESTAMPTZ,
|
||||
posture_score INTEGER,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
@@ -0,0 +1,52 @@
|
||||
-- IOC seed data from FBI FLASH Alert FLASH-20260219-001
|
||||
-- Source: ATM Jackpotting / Ploutus malware campaign
|
||||
|
||||
-- Malicious executables (FILENAME type)
|
||||
INSERT INTO threat_ioc (ioc_type, value, description, source, source_ref, confidence) VALUES
|
||||
('FILENAME', 'Newage.exe', 'Ploutus jackpotting malware', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'Color.exe', 'Ploutus jackpotting malware variant', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'Levantaito.exe', 'Ploutus jackpotting malware variant', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'NCRApp.exe', 'Ploutus masquerading as NCR application', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'sdelete.exe', 'Secure delete tool used for log cleanup', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'Promo.exe', 'Ploutus jackpotting malware variant', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'WinMonitor.exe', 'Ploutus persistence component', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'WinMonitorCheck.exe', 'Ploutus persistence watchdog', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'Anydesk1.exe', 'AnyDesk remote access trojan variant', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH');
|
||||
|
||||
-- MD5 hashes
|
||||
INSERT INTO threat_ioc (ioc_type, value, description, source, source_ref, confidence) VALUES
|
||||
('MD5', '2C2D16658D8DA6B389934273EF8F8E22', 'Ploutus malware hash', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('MD5', '5F177B84F3D92AB5711BE446125FDBE3', 'Ploutus malware hash', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('MD5', '61EECEB5F9186A0BC01DC82798CD6C5F', 'Ploutus malware hash', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('MD5', 'FDA82030AE92313E94B9339EA1FC107C', 'Ploutus malware hash', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('MD5', 'C04A7CB926CCBF829D0A36A91EBF91BD', 'Ploutus malware hash', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH');
|
||||
|
||||
-- Associated files and scripts
|
||||
INSERT INTO threat_ioc (ioc_type, value, description, source, source_ref, confidence) VALUES
|
||||
('FILENAME', 'C.dat', 'Ploutus data/config file', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'Restaurar.bat', 'Malware cleanup/restore script', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'Restauraropteva.bat', 'Malware cleanup script (Opteva ATMs)', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'Logcontrol.txt', 'Attacker log control file', 'FBI_FLASH', 'FLASH-20260219-001', 'MEDIUM'),
|
||||
('FILENAME', 'Logc.txt', 'Attacker log file', 'FBI_FLASH', 'FLASH-20260219-001', 'MEDIUM'),
|
||||
('FILENAME', 'Borrar_beta.txt', 'Attacker cleanup script', 'FBI_FLASH', 'FLASH-20260219-001', 'MEDIUM');
|
||||
|
||||
-- Suspicious directories
|
||||
INSERT INTO threat_ioc (ioc_type, value, description, source, source_ref, confidence) VALUES
|
||||
('DIRECTORY', 'C:\<ATM_Manufacturer>\exe\p\', 'Known Ploutus staging directory', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('DIRECTORY', 'C:\Users\SSAuto1\AppData\Local\P\', 'Known Ploutus staging directory', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH');
|
||||
|
||||
-- Registry persistence keys
|
||||
INSERT INTO threat_ioc (ioc_type, value, description, source, source_ref, confidence) VALUES
|
||||
('REGISTRY_KEY', 'HKLM\Software\Microsoft\Windows\CurrentVersion\Run', 'Common malware persistence location', 'FBI_FLASH', 'FLASH-20260219-001', 'MEDIUM'),
|
||||
('REGISTRY_KEY', 'HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon', 'Winlogon hijack persistence location', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('REGISTRY_KEY', 'HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Userinit', 'Userinit hijack persistence location', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH');
|
||||
|
||||
-- Suspicious service names used as cover
|
||||
INSERT INTO threat_ioc (ioc_type, value, description, source, source_ref, confidence) VALUES
|
||||
('SERVICE_NAME', 'ATM Service', 'Generic service name used by Ploutus for persistence', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('SERVICE_NAME', 'Dispenser Service', 'Generic service name used by Ploutus for persistence', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH');
|
||||
|
||||
-- Unauthorized remote access tools
|
||||
INSERT INTO threat_ioc (ioc_type, value, description, source, source_ref, confidence) VALUES
|
||||
('FILENAME', 'TeamViewer.exe', 'Remote access tool — flag if not in authorized software list', 'FBI_FLASH', 'FLASH-20260219-001', 'MEDIUM'),
|
||||
('FILENAME', 'AnyDesk.exe', 'Remote access tool — flag if not in authorized software list', 'FBI_FLASH', 'FLASH-20260219-001', 'MEDIUM');
|
||||
@@ -0,0 +1,73 @@
|
||||
spring.application.name=hiveops-aria
|
||||
|
||||
# Server
|
||||
server.port=8095
|
||||
|
||||
# Database
|
||||
spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:5432/hiveiq_aria}
|
||||
spring.datasource.username=${DB_USERNAME:hiveiq}
|
||||
spring.datasource.password=${DB_PASSWORD}
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
|
||||
# JPA/Hibernate
|
||||
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
|
||||
# Flyway
|
||||
spring.flyway.enabled=true
|
||||
spring.flyway.locations=classpath:db/migration
|
||||
spring.flyway.baseline-on-migrate=true
|
||||
spring.flyway.baseline-version=1
|
||||
spring.flyway.validate-on-migrate=true
|
||||
spring.jpa.show-sql=false
|
||||
spring.jpa.properties.hibernate.format_sql=true
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
||||
|
||||
# Logging
|
||||
logging.level.root=WARN
|
||||
logging.level.com.hiveops.aria=INFO
|
||||
logging.level.org.springframework.security=DEBUG
|
||||
|
||||
# Error responses
|
||||
server.error.include-message=always
|
||||
|
||||
# Actuator
|
||||
management.endpoints.web.exposure.include=health,info,loggers
|
||||
management.endpoint.loggers.enabled=true
|
||||
|
||||
# Pagination
|
||||
spring.data.web.pageable.default-page-size=25
|
||||
spring.data.web.pageable.max-page-size=100
|
||||
|
||||
# Jackson
|
||||
spring.jackson.default-property-inclusion=non_null
|
||||
spring.jackson.serialization.write-dates-as-timestamps=false
|
||||
spring.jackson.time-zone=UTC
|
||||
|
||||
# JWT
|
||||
jwt.secret=${JWT_SECRET}
|
||||
jwt.expiration=86400000
|
||||
|
||||
# OpenAPI
|
||||
springdoc.api-docs.enabled=${SWAGGER_ENABLED:false}
|
||||
springdoc.swagger-ui.enabled=${SWAGGER_ENABLED:false}
|
||||
|
||||
# Internal service auth (used by agent ingestion endpoint)
|
||||
aria.internal.service-secret=${ARIA_SERVICE_SECRET:}
|
||||
|
||||
# Sequence detection window (minutes)
|
||||
aria.sequence.window.minutes=${ARIA_SEQUENCE_WINDOW_MINUTES:15}
|
||||
|
||||
# Auto-response — disabled by default for safe initial deployment
|
||||
aria.autoresponse.shutdown.enabled=${ARIA_AUTORESPONSE_SHUTDOWN_ENABLED:false}
|
||||
|
||||
# hiveops-incident (for auto-response fleet tasks)
|
||||
aria.incident.url=${INCIDENT_SERVICE_URL:http://hiveiq-incident:8080}
|
||||
aria.incident.service-secret=${INTERNAL_SERVICE_SECRET:}
|
||||
|
||||
# Kafka
|
||||
spring.kafka.bootstrap-servers=${KAFKA_BOOTSTRAP_SERVERS:localhost:9092}
|
||||
spring.kafka.admin.fail-fast=false
|
||||
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
|
||||
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
|
||||
spring.kafka.producer.properties.spring.json.add.type.headers=false
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,98 @@
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
CREATE TYPE ioc_type AS ENUM (
|
||||
'FILENAME', 'MD5', 'REGISTRY_KEY', 'DIRECTORY', 'SERVICE_NAME', 'IP', 'FILE_PATH'
|
||||
);
|
||||
|
||||
CREATE TYPE ioc_source AS ENUM ('FBI_FLASH', 'FS_ISAC', 'MANUAL');
|
||||
|
||||
CREATE TYPE confidence_level AS ENUM ('HIGH', 'MEDIUM', 'LOW');
|
||||
|
||||
CREATE TABLE threat_ioc (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
ioc_type ioc_type NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
source ioc_source NOT NULL DEFAULT 'FBI_FLASH',
|
||||
source_ref VARCHAR(100),
|
||||
confidence confidence_level NOT NULL DEFAULT 'HIGH',
|
||||
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
UNIQUE (ioc_type, value)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_threat_ioc_active ON threat_ioc(active);
|
||||
CREATE INDEX idx_threat_ioc_type ON threat_ioc(ioc_type);
|
||||
|
||||
CREATE TYPE threat_severity AS ENUM ('CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO');
|
||||
|
||||
CREATE TYPE threat_event_type AS ENUM ('PHYSICAL', 'OS_EVENT', 'FILE_SYSTEM', 'COMPOSITE');
|
||||
|
||||
CREATE TYPE attack_phase AS ENUM (
|
||||
'PHYSICAL_ACCESS', 'MALWARE_STAGING', 'MALWARE_EXECUTION', 'PERSISTENCE', 'CLEANUP'
|
||||
);
|
||||
|
||||
CREATE TABLE threat_event (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
device_id BIGINT,
|
||||
device_agent_id VARCHAR(100),
|
||||
event_type threat_event_type NOT NULL,
|
||||
signal_key VARCHAR(100) NOT NULL,
|
||||
raw_payload JSONB,
|
||||
matched_ioc_id BIGINT REFERENCES threat_ioc(id),
|
||||
severity threat_severity NOT NULL,
|
||||
detected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
attack_phase attack_phase,
|
||||
sequence_id UUID,
|
||||
institution_key VARCHAR(100),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_threat_event_device_id ON threat_event(device_id);
|
||||
CREATE INDEX idx_threat_event_sequence ON threat_event(sequence_id);
|
||||
CREATE INDEX idx_threat_event_detected ON threat_event(detected_at DESC);
|
||||
CREATE INDEX idx_threat_event_severity ON threat_event(severity);
|
||||
CREATE INDEX idx_threat_event_agent ON threat_event(device_agent_id);
|
||||
|
||||
CREATE TYPE sequence_type AS ENUM ('JACKPOTTING', 'SKIMMING', 'UNKNOWN');
|
||||
|
||||
CREATE TYPE auto_response_action AS ENUM ('NONE', 'SHUTDOWN', 'NOTIFICATION', 'LOCKDOWN');
|
||||
|
||||
CREATE TABLE precursor_sequence_alert (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
device_id BIGINT,
|
||||
device_agent_id VARCHAR(100),
|
||||
sequence_type sequence_type NOT NULL DEFAULT 'JACKPOTTING',
|
||||
phases_detected attack_phase[] NOT NULL DEFAULT '{}',
|
||||
confidence NUMERIC(4,3) NOT NULL DEFAULT 0.0,
|
||||
triggered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
auto_response_taken auto_response_action NOT NULL DEFAULT 'NONE',
|
||||
resolved_at TIMESTAMPTZ,
|
||||
resolved_by VARCHAR(200),
|
||||
institution_key VARCHAR(100),
|
||||
sequence_id UUID NOT NULL DEFAULT uuid_generate_v4()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_sequence_alert_device ON precursor_sequence_alert(device_id);
|
||||
CREATE INDEX idx_sequence_alert_trigger ON precursor_sequence_alert(triggered_at DESC);
|
||||
CREATE INDEX idx_sequence_alert_open ON precursor_sequence_alert(resolved_at) WHERE resolved_at IS NULL;
|
||||
|
||||
CREATE TYPE os_eol_status AS ENUM ('SUPPORTED', 'EOL', 'UNKNOWN');
|
||||
|
||||
CREATE TABLE device_security_posture (
|
||||
device_id BIGINT PRIMARY KEY,
|
||||
device_agent_id VARCHAR(100),
|
||||
institution_key VARCHAR(100),
|
||||
audit_policy_compliant BOOLEAN,
|
||||
gold_image_validated_at TIMESTAMPTZ,
|
||||
gold_image_hash VARCHAR(128),
|
||||
current_image_hash VARCHAR(128),
|
||||
disk_encryption_enabled BOOLEAN,
|
||||
os_version VARCHAR(200),
|
||||
os_eol_status os_eol_status NOT NULL DEFAULT 'UNKNOWN',
|
||||
software_whitelist_enabled BOOLEAN,
|
||||
last_posture_check_at TIMESTAMPTZ,
|
||||
posture_score INTEGER,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
@@ -0,0 +1,52 @@
|
||||
-- IOC seed data from FBI FLASH Alert FLASH-20260219-001
|
||||
-- Source: ATM Jackpotting / Ploutus malware campaign
|
||||
|
||||
-- Malicious executables (FILENAME type)
|
||||
INSERT INTO threat_ioc (ioc_type, value, description, source, source_ref, confidence) VALUES
|
||||
('FILENAME', 'Newage.exe', 'Ploutus jackpotting malware', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'Color.exe', 'Ploutus jackpotting malware variant', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'Levantaito.exe', 'Ploutus jackpotting malware variant', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'NCRApp.exe', 'Ploutus masquerading as NCR application', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'sdelete.exe', 'Secure delete tool used for log cleanup', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'Promo.exe', 'Ploutus jackpotting malware variant', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'WinMonitor.exe', 'Ploutus persistence component', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'WinMonitorCheck.exe', 'Ploutus persistence watchdog', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'Anydesk1.exe', 'AnyDesk remote access trojan variant', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH');
|
||||
|
||||
-- MD5 hashes
|
||||
INSERT INTO threat_ioc (ioc_type, value, description, source, source_ref, confidence) VALUES
|
||||
('MD5', '2C2D16658D8DA6B389934273EF8F8E22', 'Ploutus malware hash', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('MD5', '5F177B84F3D92AB5711BE446125FDBE3', 'Ploutus malware hash', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('MD5', '61EECEB5F9186A0BC01DC82798CD6C5F', 'Ploutus malware hash', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('MD5', 'FDA82030AE92313E94B9339EA1FC107C', 'Ploutus malware hash', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('MD5', 'C04A7CB926CCBF829D0A36A91EBF91BD', 'Ploutus malware hash', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH');
|
||||
|
||||
-- Associated files and scripts
|
||||
INSERT INTO threat_ioc (ioc_type, value, description, source, source_ref, confidence) VALUES
|
||||
('FILENAME', 'C.dat', 'Ploutus data/config file', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'Restaurar.bat', 'Malware cleanup/restore script', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'Restauraropteva.bat', 'Malware cleanup script (Opteva ATMs)', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('FILENAME', 'Logcontrol.txt', 'Attacker log control file', 'FBI_FLASH', 'FLASH-20260219-001', 'MEDIUM'),
|
||||
('FILENAME', 'Logc.txt', 'Attacker log file', 'FBI_FLASH', 'FLASH-20260219-001', 'MEDIUM'),
|
||||
('FILENAME', 'Borrar_beta.txt', 'Attacker cleanup script', 'FBI_FLASH', 'FLASH-20260219-001', 'MEDIUM');
|
||||
|
||||
-- Suspicious directories
|
||||
INSERT INTO threat_ioc (ioc_type, value, description, source, source_ref, confidence) VALUES
|
||||
('DIRECTORY', 'C:\<ATM_Manufacturer>\exe\p\', 'Known Ploutus staging directory', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('DIRECTORY', 'C:\Users\SSAuto1\AppData\Local\P\', 'Known Ploutus staging directory', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH');
|
||||
|
||||
-- Registry persistence keys
|
||||
INSERT INTO threat_ioc (ioc_type, value, description, source, source_ref, confidence) VALUES
|
||||
('REGISTRY_KEY', 'HKLM\Software\Microsoft\Windows\CurrentVersion\Run', 'Common malware persistence location', 'FBI_FLASH', 'FLASH-20260219-001', 'MEDIUM'),
|
||||
('REGISTRY_KEY', 'HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon', 'Winlogon hijack persistence location', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('REGISTRY_KEY', 'HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Userinit', 'Userinit hijack persistence location', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH');
|
||||
|
||||
-- Suspicious service names used as cover
|
||||
INSERT INTO threat_ioc (ioc_type, value, description, source, source_ref, confidence) VALUES
|
||||
('SERVICE_NAME', 'ATM Service', 'Generic service name used by Ploutus for persistence', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH'),
|
||||
('SERVICE_NAME', 'Dispenser Service', 'Generic service name used by Ploutus for persistence', 'FBI_FLASH', 'FLASH-20260219-001', 'HIGH');
|
||||
|
||||
-- Unauthorized remote access tools
|
||||
INSERT INTO threat_ioc (ioc_type, value, description, source, source_ref, confidence) VALUES
|
||||
('FILENAME', 'TeamViewer.exe', 'Remote access tool — flag if not in authorized software list', 'FBI_FLASH', 'FLASH-20260219-001', 'MEDIUM'),
|
||||
('FILENAME', 'AnyDesk.exe', 'Remote access tool — flag if not in authorized software list', 'FBI_FLASH', 'FLASH-20260219-001', 'MEDIUM');
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
artifactId=hiveops-aria
|
||||
groupId=com.hiveops
|
||||
version=1.0.0
|
||||
@@ -0,0 +1,37 @@
|
||||
com/hiveops/aria/dto/ThreatEventIngestionRequest.class
|
||||
com/hiveops/aria/entity/ThreatIoc$ThreatIocBuilder.class
|
||||
com/hiveops/aria/entity/PrecursorSequenceAlert$AutoResponseAction.class
|
||||
com/hiveops/aria/entity/ThreatIoc$IocType.class
|
||||
com/hiveops/aria/controller/ThreatIocController.class
|
||||
com/hiveops/aria/kafka/AriaThreatEvent.class
|
||||
com/hiveops/aria/repository/DeviceSecurityPostureRepository.class
|
||||
com/hiveops/aria/entity/DeviceSecurityPosture.class
|
||||
com/hiveops/aria/service/ThreatEventClassifier.class
|
||||
com/hiveops/aria/entity/ThreatEvent.class
|
||||
com/hiveops/aria/kafka/AriaThreatEvent$AriaThreatEventBuilder.class
|
||||
com/hiveops/aria/entity/DeviceSecurityPosture$DeviceSecurityPostureBuilder.class
|
||||
com/hiveops/aria/controller/ThreatIocController$CreateIocRequest.class
|
||||
com/hiveops/aria/kafka/AriaKafkaConfig.class
|
||||
com/hiveops/aria/entity/ThreatIoc.class
|
||||
com/hiveops/aria/security/AriaPrincipal.class
|
||||
com/hiveops/aria/entity/PrecursorSequenceAlert$SequenceType.class
|
||||
com/hiveops/aria/service/ThreatEventClassifier$ClassificationResult.class
|
||||
com/hiveops/aria/repository/ThreatIocRepository.class
|
||||
com/hiveops/aria/entity/ThreatIoc$IocSource.class
|
||||
com/hiveops/aria/entity/ThreatEvent$ThreatEventBuilder.class
|
||||
com/hiveops/aria/entity/ThreatEvent$AttackPhase.class
|
||||
com/hiveops/aria/repository/ThreatEventRepository.class
|
||||
com/hiveops/aria/repository/PrecursorSequenceAlertRepository.class
|
||||
com/hiveops/aria/AriaApplication.class
|
||||
com/hiveops/aria/dto/ThreatEventItem.class
|
||||
com/hiveops/aria/entity/ThreatEvent$EventType.class
|
||||
com/hiveops/aria/service/ThreatEventIngestionService.class
|
||||
com/hiveops/aria/entity/PrecursorSequenceAlert$PrecursorSequenceAlertBuilder.class
|
||||
com/hiveops/aria/controller/ThreatEventIngestionController.class
|
||||
com/hiveops/aria/security/JwtAuthenticationFilter.class
|
||||
com/hiveops/aria/entity/ThreatEvent$ThreatSeverity.class
|
||||
com/hiveops/aria/config/SecurityConfig.class
|
||||
com/hiveops/aria/entity/DeviceSecurityPosture$OsEolStatus.class
|
||||
com/hiveops/aria/kafka/AriaThreatProducer.class
|
||||
com/hiveops/aria/entity/PrecursorSequenceAlert.class
|
||||
com/hiveops/aria/entity/ThreatIoc$ConfidenceLevel.class
|
||||
@@ -0,0 +1,21 @@
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/AriaApplication.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/config/SecurityConfig.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/controller/ThreatEventIngestionController.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/controller/ThreatIocController.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/dto/ThreatEventIngestionRequest.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/dto/ThreatEventItem.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/entity/DeviceSecurityPosture.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/entity/PrecursorSequenceAlert.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/entity/ThreatEvent.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/entity/ThreatIoc.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/kafka/AriaKafkaConfig.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/kafka/AriaThreatEvent.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/kafka/AriaThreatProducer.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/repository/DeviceSecurityPostureRepository.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/repository/PrecursorSequenceAlertRepository.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/repository/ThreatEventRepository.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/repository/ThreatIocRepository.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/security/AriaPrincipal.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/security/JwtAuthenticationFilter.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/service/ThreatEventClassifier.java
|
||||
/source/hiveops-src/hiveops-aria/src/main/java/com/hiveops/aria/service/ThreatEventIngestionService.java
|
||||
Reference in New Issue
Block a user