-This structure facilitates easy identification and routing of data based on geographic location, port, host, and data type.
+## REST API Documentation
-### User Interface
+### `GET /api/hosts/search`
-A web-based **User Interface** is available for visualizing scan results and interacting with the data.
+- **Description**: Search for hosts based on query parameters.
+- **Query Parameters**:
+ - `filter` (optional, string): A JSON-encoded MongoDB-style query object used to filter hosts. The server applies this object directly as a MongoDB $match stage.
+ - `limit` (optional, integer): Maximum number of hosts to return. Defaults to 50. Minimum/invalid values default to 50. Maximum allowed value is 500.
+ - `page_token` (optional, string): Opaque pagination token returned from a previous response next_page_token. Pass this token to retrieve the next page of results.
-> **Note**: The user interface is in early development stages and requires manual startup.
+### `GET /api/facets`
-## Considerations
+- **Description**: Retrieve available facets for filtering search results.
+- **Query Parameters**:
+ - `filter` (optional, string): A JSON-encoded MongoDB-style query object to restrict the aggregation to a subset of hosts.
-1. **Network Capacity**: High scanning rates set in ZMap may consume significant network bandwidth, potentially causing other services to experience latency or connectivity issues.
-2. **Process Resilience**: Current processes do not automatically resume after a crash. If a service like the Banner Grabber fails, it will not pick up where it left off upon restarting. Enhancements to address this limitation are planned for future releases.
+## Acknowledgements
-## Future Enhancements
+We would like to thank the open-source community for their contributions and support in developing Rigour.
-- **ISP/Organization Mapping**: Incorporate mapping of IP addresses to Internet Service Providers or organizations to provide more context.
-- **DNS Mapping**: Implement DNS resolution to associate hostnames with IP addresses.
-- **Campaign Configurability**: Introduce configurable scanning campaigns, allowing users to specify ports, protocols, and data types to capture.
+Special thanks to the creators of [Fingerprintx](https://github.com/praetorian-inc/fingerprintx) and [Naabu](https://github.com/projectdiscovery/naabu) for their invaluable tools and resources.
diff --git a/docker-compose.override.yml b/docker-compose.override.yml
new file mode 100644
index 0000000..48c2e5a
--- /dev/null
+++ b/docker-compose.override.yml
@@ -0,0 +1,85 @@
+services:
+ # ============================================
+ # Kafka
+ # ============================================
+
+ zookeeper:
+ image: confluentinc/cp-zookeeper:7.3.1
+ container_name: zookeeper
+ environment:
+ ZOOKEEPER_CLIENT_PORT: 2181
+ ZOOKEEPER_TICK_TIME: 2000
+ ports:
+ - "2181:2181"
+ networks:
+ - rigour-network
+
+ kafka:
+ image: confluentinc/cp-kafka:7.3.1
+ container_name: kafka
+ depends_on:
+ - zookeeper
+ ports:
+ - "9092:9092"
+ - "29092:29092"
+ environment:
+ KAFKA_BROKER_ID: 1
+ KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ networks:
+ - rigour-network
+ healthcheck:
+ test: ["CMD", "bash", "-lc", "kafka-topics --bootstrap-server kafka:9092 --list >/dev/null 2>&1"]
+ interval: 10s
+ timeout: 5s
+ retries: 12
+ start_period: 20s
+
+ # kafka-ui:
+ # image: provectuslabs/kafka-ui:latest
+ # container_name: kafka-ui
+ # ports:
+ # - "9000:8080"
+ # environment:
+ # KAFKA_CLUSTERS_0_NAME: local
+ # KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
+ # KAFKA_CLUSTERS_0_ZOOKEEPER: disabled
+ # networks:
+ # - rigour-network
+
+ # ============================================
+ # MongoDB
+ # ============================================
+
+ mongo:
+ image: mongo:latest
+ container_name: mongo
+ ports:
+ - "27017:27017"
+ volumes:
+ - mongo-data:/data/db
+ networks:
+ - rigour-network
+
+ # ============================================
+ # GeoIP Data Puller
+ # ============================================
+
+ geoipupdate:
+ container_name: geoipupdate
+ image: ghcr.io/maxmind/geoipupdate
+ restart: unless-stopped
+ environment:
+ - GEOIPUPDATE_ACCOUNT_ID=${MAXMIND_ACCOUNT_ID}
+ - GEOIPUPDATE_LICENSE_KEY=${MAXMIND_LICENSE_KEY}
+ - 'GEOIPUPDATE_EDITION_IDS=GeoLite2-ASN GeoLite2-City'
+ - GEOIPUPDATE_FREQUENCY=72
+ volumes:
+ - 'geoipupdate_data:/usr/share/GeoIP'
+
+volumes:
+ mongo-data:
+ geoipupdate_data:
diff --git a/docker-compose.yml b/docker-compose.yml
index 3af29e4..d5a38f7 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,170 +1,77 @@
services:
- port-scanner:
+ crawler:
build:
- context: .
- dockerfile: rigour/ports/Dockerfile
- restart: unless-stopped
- network_mode: "host"
- environment:
- PORTS: "80,443,22,21,25565,27017,143,6379"
- NETWORKS: "10.0.0.0/8 192.168.0.0/16"
+ context: ./rigour
+ dockerfile: Dockerfile.crawler
+ container_name: rigour-crawler
depends_on:
- rabbitmq:
+ kafka:
condition: service_healthy
-
- banner-scanner-http-80:
- build:
- context: .
- dockerfile: rigour/banners/Dockerfile
+ command:
+ - "${CRAWLER_CIDR:-0.0.0.0/0}"
+ - "--kafka-brokers"
+ - "kafka:9092"
+ - "--top-ports"
+ - "${CRAWLER_DISCOVERY_TOP_PORTS:-1000}"
+ networks:
+ - rigour-network
restart: unless-stopped
- network_mode: "host"
- environment:
- SERVICE: http
- PORT: 80
- depends_on:
- rabbitmq:
- condition: service_healthy
- banner-scanner-http-443:
+ persistence:
build:
- context: .
- dockerfile: rigour/banners/Dockerfile
- restart: unless-stopped
- network_mode: "host"
- environment:
- SERVICE: http
- PORT: 443
+ context: ./rigour
+ dockerfile: Dockerfile.persistence
+ container_name: rigour-persistence
depends_on:
- rabbitmq:
+ kafka:
condition: service_healthy
-
- banner-scanner-ssh-22:
- build:
- context: .
- dockerfile: rigour/banners/Dockerfile
- restart: unless-stopped
- network_mode: "host"
- environment:
- SERVICE: ssh
- PORT: 22
- depends_on:
- rabbitmq:
+ mongo:
+ condition: service_started
+ geoipupdate:
condition: service_healthy
-
- banner-scanner-ftp-21:
- build:
- context: .
- dockerfile: rigour/banners/Dockerfile
+ command:
+ - "--brokers"
+ - "kafka:9092"
+ - "--mongo-uri"
+ - "mongodb://mongo:27017"
+ - "--geoip-path"
+ - "/data/geoip"
+ volumes:
+ - geoipupdate_data:/data/geoip
+ networks:
+ - rigour-network
restart: unless-stopped
- network_mode: "host"
- environment:
- SERVICE: ftp
- PORT: 21
- depends_on:
- rabbitmq:
- condition: service_healthy
- banner-scanner-imap-143:
+ api:
build:
- context: .
- dockerfile: rigour/banners/Dockerfile
- restart: unless-stopped
- network_mode: "host"
- environment:
- SERVICE: imap
- PORT: 143
+ context: ./rigour
+ dockerfile: Dockerfile.api
+ container_name: rigour-api
+ ports:
+ - "8080:8080"
+ command:
+ - "--mongo-uri"
+ - "mongodb://mongo:27017"
depends_on:
- rabbitmq:
- condition: service_healthy
-
- banner-scanner-redis-6379:
- build:
- context: .
- dockerfile: rigour/banners/Dockerfile
+ - mongo
+ networks:
+ - rigour-network
restart: unless-stopped
- network_mode: "host"
- environment:
- SERVICE: redis
- PORT: 6379
- depends_on:
- rabbitmq:
- condition: service_healthy
- banner-scanner-mongodb-27017:
+ ui:
build:
- context: .
- dockerfile: rigour/banners/Dockerfile
- restart: unless-stopped
- network_mode: "host"
- environment:
- SERVICE: mongodb
- PORT: 27017
+ context: ./rigour-ui
+ container_name: rigour-ui
depends_on:
- rabbitmq:
- condition: service_healthy
-
- banner-scanner-jarm-443:
- build:
- context: .
- dockerfile: rigour/banners/Dockerfile
- restart: unless-stopped
- network_mode: "host"
+ - api
+ ports:
+ - "3000:3000"
environment:
- SERVICE: jarm
- PORT: 443
- depends_on:
- rabbitmq:
- condition: service_healthy
-
- addon-minecraft-scanner:
- build:
- context: .
- dockerfile: rigour/addons/minecraft/Dockerfile
- restart: unless-stopped
- network_mode: "host"
- depends_on:
- rabbitmq:
- condition: service_healthy
-
- vuln-scanner:
- build:
- context: .
- dockerfile: rigour/vuln/Dockerfile
- restart: unless-stopped
- network_mode: "host"
- depends_on:
- rabbitmq:
- condition: service_healthy
-
- api:
- build:
- context: .
- dockerfile: rigour/api/Dockerfile
+ - NEXT_PUBLIC_API_BASE_URL=http://api:8080
+ networks:
+ - rigour-network
restart: unless-stopped
- network_mode: "host"
- depends_on:
- rabbitmq:
- condition: service_healthy
- mongodb:
- image: mongo
- restart: always
- ports:
- - "27017:27017"
- healthcheck:
- test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- rabbitmq:
- image: rabbitmq:3-management
- restart: unless-stopped
- ports:
- - "5672:5672"
- - "15672:15672"
- healthcheck:
- test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
- interval: 10s
- timeout: 5s
- retries: 5
+networks:
+ rigour-network:
+ driver: bridge
diff --git a/docs/MAXMIND_SETUP.md b/docs/MAXMIND_SETUP.md
new file mode 100644
index 0000000..1ca2f26
--- /dev/null
+++ b/docs/MAXMIND_SETUP.md
@@ -0,0 +1,33 @@
+# Get MaxMind License Key
+
+For GeoIP and ASN lookups, Rigour uses the MaxMind service. This service is completely free to use, but requires a license key. To obtain a MaxMind license key, follow these steps:
+
+1. **Create a MaxMind Account**:
+ - Setup a free account on the MaxMind website: [https://www.maxmind.com/en/geolite2/signup](https://www.maxmind.com/en/geolite2/signup).
+
+2. **Generate a License Key**:
+ - After logging into your MaxMind account, navigate to the "Manage License Keys" section.
+ - Click on "Create a New License Key".
+ - Provide a name for the key (e.g., "Rigour")
+ - Click Confirm to generate the key.
+
+3. **Record the License Key & Account ID**:
+ - Copy your Account ID and store it in your .env file as `MAXMIND_ACCOUNT_ID=your_account_id_here`.
+ - Copy the generated license key and store it in your .env file as `MAXMIND_LICENSE_KEY=your_license_key_here`.
+
+## Local Development
+
+If you are running Rigour locally without Docker, you will need to download the GeoIP databases manually, or use the `geoipupdate` tool provided by MaxMind.
+
+Follow these steps to set up the GeoIP databases:
+
+```shell
+# Run at the root of the rigour project
+docker run --rm \
+ -e GEOIPUPDATE_ACCOUNT_ID=YOUR_ACCOUNT_ID \
+ -e GEOIPUPDATE_LICENSE_KEY=YOUR_LICENSE_KEY \
+ -e "GEOIPUPDATE_EDITION_IDS=GeoLite2-City GeoLite2-ASN" \
+ -e GEOIPUPDATE_FREQUENCY=0 \
+ -v ./data/geoip:/usr/share/GeoIP \
+ ghcr.io/maxmind/geoipupdate
+```
diff --git a/docs/overview_diagram.png b/docs/overview_diagram.png
deleted file mode 100644
index 3fe9734..0000000
Binary files a/docs/overview_diagram.png and /dev/null differ
diff --git a/docs/ui.png b/docs/ui.png
new file mode 100644
index 0000000..6218d59
Binary files /dev/null and b/docs/ui.png differ
diff --git a/rigour-ui/.dockerignore b/rigour-ui/.dockerignore
new file mode 100644
index 0000000..fe6ed8b
--- /dev/null
+++ b/rigour-ui/.dockerignore
@@ -0,0 +1,127 @@
+# Dependencies
+node_modules
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage
+.grunt
+
+# Bower dependency directory
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons
+build/Release
+
+# Dependency directories
+jspm_packages/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+.env.local
+.env.production
+
+# parcel-bundler cache
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+
+# Storybook build outputs
+.out
+.storybook-out
+
+# Temporary folders
+tmp/
+temp/
+
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# IDE
+.vscode
+.idea
+*.swp
+*.swo
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Git
+.git
+.gitignore
+
+# Docker
+Dockerfile*
+docker-compose*
+.dockerignore
+
+# Documentation
+README.md
+*.md
+
+# Test files
+test/
+tests/
+__tests__/
+*.test.js
+*.test.ts
+*.spec.js
+*.spec.ts
+jest.config.js
diff --git a/rigour-ui/.gitignore b/rigour-ui/.gitignore
new file mode 100644
index 0000000..5ef6a52
--- /dev/null
+++ b/rigour-ui/.gitignore
@@ -0,0 +1,41 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files (can opt-in for committing if needed)
+.env*
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/rigour-ui/Dockerfile b/rigour-ui/Dockerfile
new file mode 100644
index 0000000..3b07092
--- /dev/null
+++ b/rigour-ui/Dockerfile
@@ -0,0 +1,51 @@
+FROM node:25-alpine AS deps
+
+WORKDIR /app
+
+# Install dependencies based on the preferred package manager
+COPY package.json package-lock.json /app/
+RUN npm ci
+
+# Rebuild the source code only when needed
+FROM node:25-alpine AS builder
+
+WORKDIR /app
+COPY --from=deps /app/node_modules ./node_modules
+COPY . .
+
+RUN npm run build
+
+# Production image, copy all the files and run next
+FROM node:25-alpine AS runner
+
+WORKDIR /app
+
+ENV NODE_ENV production
+
+RUN addgroup --system --gid 1001 nodejs
+RUN adduser --system --uid 1001 nextjs
+
+COPY --from=builder /app/public ./public
+
+############
+# Permissions to write files when executing entrypoint
+RUN chown -R nextjs:nodejs /app
+
+# Handle Entrypoint
+COPY --from=builder --chown=nextjs:nodejs /app/entrypoint.sh ./entrypoint.sh
+RUN chmod +x ./entrypoint.sh
+ENTRYPOINT ["/app/entrypoint.sh"]
+############
+
+# Automatically leverage output traces to reduce image size
+# https://nextjs.org/docs/advanced-features/output-file-tracing
+COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
+COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
+
+USER nextjs
+
+EXPOSE 3000
+
+ENV PORT 3000
+
+CMD ["node", "server.js"]
diff --git a/rigour-ui/README.md b/rigour-ui/README.md
new file mode 100644
index 0000000..e215bc4
--- /dev/null
+++ b/rigour-ui/README.md
@@ -0,0 +1,36 @@
+This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+
+This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
diff --git a/rigour-ui/app/(dashboard)/host/[slug]/page.tsx b/rigour-ui/app/(dashboard)/host/[slug]/page.tsx
new file mode 100644
index 0000000..57e495d
--- /dev/null
+++ b/rigour-ui/app/(dashboard)/host/[slug]/page.tsx
@@ -0,0 +1,374 @@
+import Link from 'next/link';
+import { getHostByIP } from '../../../../lib/api';
+import { formatDate, formatDateShort } from '../../../../lib/utils';
+import { Host } from '../../../../lib/types';
+import { Card, CardContent, CardHeader } from '../../../../components/ui/card';
+import { Badge } from '../../../../components/ui/badge';
+import { Button } from '../../../../components/ui/button';
+import {
+ Globe,
+ Network,
+ Server,
+ Clock,
+ MapPin,
+ ChevronLeft,
+ ExternalLink,
+ AlertCircle,
+ CheckCircle,
+ Wifi,
+ Shield,
+} from 'lucide-react';
+
+interface Params {
+ slug: string;
+}
+
+export default async function HostDetailsPage({
+ params
+}: {
+ params: Promise+ {error || 'Host not found'} +
+{host.id}
+ {host.asn.organization}
+{host.asn.country}
+{host.location.city}
+AS{host.asn.number}
+{host.location.timezone}
+{formatDate(host.first_seen)}
++ {formatDateShort(host.first_seen)} +
+{formatDate(host.last_seen)}
++ {formatDateShort(host.last_seen)} +
++ {host.location.coordinates[0].toFixed(4)}, {host.location.coordinates[1].toFixed(4)} +
+ + View on Google Maps ++ Number:{' '} + AS{host.asn.number} +
++ Org:{' '} + {host.asn.organization} +
++ Country:{' '} + {host.asn.country} +
+No services discovered
+ ) : ( ++ Status Code: + + {service.https.statusCode} + +
++ Status: + {service.https.status} +
+ {Object.keys(service.https.responseHeaders).length > 0 && ( +Headers:
++ {key}:{' '} + {(values as string[]).join(', ')} +
+ ) + )} ++ Status Code: + + {service.http.statusCode} + +
++ Status: + {service.http.status} +
+ {Object.keys(service.http.responseHeaders).length > 0 && ( +Headers:
++ {key}:{' '} + {(values as string[]).join(', ')} +
+ ) + )} +{service.ssh.banner}
+
+ {JSON.stringify(host, null, 2)}
+
+ + Make sure the API is running at {API_BASE_URL} +
+{host.asn.organization}
+No hosts found matching your criteria.
++ Internet-Connected Device Intelligence Platform +
+
+ Query using field syntax: field: value (e.g., services.protocol: ssh)
+