this is the first commit for the Claude Iteration project.

This commit is contained in:
2025-06-18 02:30:36 -04:00
parent 3c7e9b9eee
commit 037191f981
22 changed files with 993 additions and 138 deletions

3
.env Normal file
View File

@@ -0,0 +1,3 @@
DATABASE_URL=postgresql://admin:secure_password_123@database:5432/threat_hunter
SECRET_KEY=your-very-secret-key-change-in-production
FLASK_ENV=production

23
backend/Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements first for better caching
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Create directories
RUN mkdir -p uploads output
EXPOSE 5000
CMD ["python", "app.py"]

24
backend/Dockerfile.prod Normal file
View File

@@ -0,0 +1,24 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN useradd --create-home --shell /bin/bash app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN chown -R app:app /app
USER app
EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:app"]

View File

@@ -1,9 +1,11 @@
import os
import logging
from datetime import datetime, timedelta
from flask import Flask, request, jsonify, send_from_directory
from flask_sqlalchemy import SQLAlchemy
from flask_jwt_extended import JWTManager, jwt_required, create_access_token, get_jwt_identity
from werkzeug.utils import secure_filename
from werkzeug.exceptions import RequestEntityTooLarge
from datetime import datetime
import bcrypt
# Try to import flask-cors
try:
@@ -19,6 +21,24 @@ logger = logging.getLogger(__name__)
app = Flask(__name__, static_folder="../frontend/dist")
# Configuration
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'postgresql://admin:secure_password_123@localhost:5432/threat_hunter')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['JWT_SECRET_KEY'] = os.getenv('SECRET_KEY', 'change-this-in-production')
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=24)
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024
app.config['UPLOAD_FOLDER'] = 'uploaded'
app.config['OUTPUT_FOLDER'] = 'output'
app.config['ALLOWED_EXTENSIONS'] = {'csv', 'json', 'txt', 'log'}
# Ensure upload directories exist
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs(app.config['OUTPUT_FOLDER'], exist_ok=True)
# Initialize extensions
db = SQLAlchemy(app)
jwt = JWTManager(app)
# Enable CORS
if CORS_AVAILABLE:
CORS(app)
@@ -30,16 +50,6 @@ else:
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
return response
# Configuration
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024
app.config['UPLOAD_FOLDER'] = 'uploaded'
app.config['OUTPUT_FOLDER'] = 'output'
app.config['ALLOWED_EXTENSIONS'] = {'csv', 'json', 'txt', 'log'}
# Ensure upload directories exist
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs(app.config['OUTPUT_FOLDER'], exist_ok=True)
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']
@@ -53,6 +63,144 @@ def handle_exception(e):
logger.error(f"Unhandled exception: {e}")
return jsonify({'error': 'Internal server error'}), 500
# Database Models
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.String, primary_key=True, default=lambda: str(uuid.uuid4()))
username = db.Column(db.String(50), unique=True, nullable=False)
email = db.Column(db.String(100), unique=True, nullable=False)
password_hash = db.Column(db.String(255), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
last_login = db.Column(db.DateTime)
class Hunt(db.Model):
__tablename__ = 'hunts'
id = db.Column(db.String, primary_key=True, default=lambda: str(uuid.uuid4()))
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text)
created_by = db.Column(db.String, db.ForeignKey('users.id'))
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow)
status = db.Column(db.String(20), default='active')
# Authentication Routes
@app.route('/api/auth/login', methods=['POST'])
def login():
try:
data = request.get_json()
username = data.get('username')
password = data.get('password')
user = User.query.filter_by(username=username).first()
if user and bcrypt.checkpw(password.encode('utf-8'), user.password_hash.encode('utf-8')):
access_token = create_access_token(identity=user.id)
user.last_login = datetime.utcnow()
db.session.commit()
return jsonify({
'access_token': access_token,
'user': {
'id': user.id,
'username': user.username,
'email': user.email
}
})
else:
return jsonify({'error': 'Invalid credentials'}), 401
except Exception as e:
logger.error(f"Login error: {e}")
return jsonify({'error': 'Login failed'}), 500
@app.route('/api/auth/register', methods=['POST'])
def register():
try:
data = request.get_json()
username = data.get('username')
email = data.get('email')
password = data.get('password')
# Check if user exists
if User.query.filter_by(username=username).first():
return jsonify({'error': 'Username already exists'}), 400
if User.query.filter_by(email=email).first():
return jsonify({'error': 'Email already exists'}), 400
# Hash password
password_hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
# Create user
user = User(username=username, email=email, password_hash=password_hash)
db.session.add(user)
db.session.commit()
access_token = create_access_token(identity=user.id)
return jsonify({
'access_token': access_token,
'user': {
'id': user.id,
'username': user.username,
'email': user.email
}
})
except Exception as e:
logger.error(f"Registration error: {e}")
return jsonify({'error': 'Registration failed'}), 500
# Hunt Management Routes
@app.route('/api/hunts', methods=['GET'])
@jwt_required()
def get_hunts():
try:
user_id = get_jwt_identity()
hunts = Hunt.query.filter_by(created_by=user_id).all()
return jsonify({
'hunts': [{
'id': hunt.id,
'name': hunt.name,
'description': hunt.description,
'created_at': hunt.created_at.isoformat(),
'status': hunt.status
} for hunt in hunts]
})
except Exception as e:
logger.error(f"Get hunts error: {e}")
return jsonify({'error': 'Failed to fetch hunts'}), 500
@app.route('/api/hunts', methods=['POST'])
@jwt_required()
def create_hunt():
try:
user_id = get_jwt_identity()
data = request.get_json()
hunt = Hunt(
name=data.get('name'),
description=data.get('description'),
created_by=user_id
)
db.session.add(hunt)
db.session.commit()
return jsonify({
'hunt': {
'id': hunt.id,
'name': hunt.name,
'description': hunt.description,
'created_at': hunt.created_at.isoformat(),
'status': hunt.status
}
})
except Exception as e:
logger.error(f"Create hunt error: {e}")
return jsonify({'error': 'Failed to create hunt'}), 500
# API Routes
@app.route('/api/health')
def health_check():
@@ -135,6 +283,10 @@ def get_stats():
return jsonify({'error': 'Failed to get stats'}), 500
# Static file serving for React app
@app.route("/assets/<path:path>")
def send_assets(path):
return send_from_directory(os.path.join(app.static_folder, "assets"), path)
@app.route("/")
def index():
if os.path.exists(os.path.join(app.static_folder, "index.html")):
@@ -151,68 +303,6 @@ def index():
]
})
if __name__ == "__main__":
print("=" * 50)
print("Starting Cyber Threat Hunter Backend...")
print("API available at: http://localhost:5000")
print("Health check: http://localhost:5000/api/health")
print("=" * 50)
app.run(host="0.0.0.0", port=5000, debug=True)
# Basic security tools detection
tools_found = []
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read().lower()
# Common security tools to detect
security_tools = [
'windows defender', 'antimalware', 'avast', 'norton', 'mcafee',
'crowdstrike', 'carbon black', 'sentinelone', 'cylance',
'kaspersky', 'bitdefender', 'sophos', 'trend micro',
'openvpn', 'nordvpn', 'expressvpn', 'cisco anyconnect'
]
for tool in security_tools:
if tool in content:
tools_found.append(tool)
return jsonify({
'filename': filename,
'tools_found': tools_found,
'total_tools': len(tools_found)
})
except Exception as e:
logger.error(f"Security tools analysis error: {e}")
return jsonify({'error': 'Analysis failed'}), 500
@app.route('/api/stats')
def get_stats():
try:
upload_dir = app.config['UPLOAD_FOLDER']
files_count = len([f for f in os.listdir(upload_dir) if os.path.isfile(os.path.join(upload_dir, f))])
return jsonify({
'filesUploaded': files_count,
'analysesCompleted': files_count,
'threatsDetected': 0
})
except Exception as e:
logger.error(f"Stats error: {e}")
return jsonify({'error': 'Failed to get stats'}), 500
# Static file serving for React app
@app.route("/assets/<path:path>")
def send_assets(path):
return send_from_directory(os.path.join(app.static_folder, "assets"), path)
@app.route("/")
def index():
if os.path.exists(os.path.join(app.static_folder, "index.html")):
return send_from_directory(app.static_folder, "index.html")
else:
return jsonify({'message': 'Cyber Threat Hunter API', 'status': 'running'})
# Catch-all route for React Router
@app.route("/<path:path>")
def catch_all(path):
@@ -222,69 +312,12 @@ def catch_all(path):
return jsonify({'error': 'Frontend not built yet'})
if __name__ == "__main__":
with app.app_context():
db.create_all()
print("=" * 50)
print("Starting Cyber Threat Hunter Backend...")
print("API available at: http://localhost:5000")
print("Health check: http://localhost:5000/api/health")
print("Available endpoints:")
print(" POST /api/upload - Upload files")
print(" GET /api/files - List uploaded files")
print(" GET /api/analyze/<filename> - Analyze file")
print(" GET /api/security-tools/<filename> - Detect security tools")
print(" GET /api/stats - Get statistics")
print("Database: Connected to PostgreSQL")
print("=" * 50)
app.run(host="0.0.0.0", port=5000, debug=True)
})
except Exception as e:
analysis['csv_error'] = str(e)
elif filename.lower().endswith('.csv'):
# Basic CSV analysis without pandas
try:
with open(filepath, 'r', encoding='utf-8') as f:
lines = f.readlines()
analysis.update({
'rows': len(lines) - 1, # Subtract header
'columns': len(lines[0].split(',')) if lines else 0,
'preview': lines[:6] if lines else []
})
except Exception as e:
analysis['csv_error'] = str(e)
return jsonify(analysis)
except Exception as e:
logger.error(f"Analysis error: {e}")
return jsonify({'error': 'Analysis failed'}), 500
# Stats endpoint
@app.route('/api/stats')
def get_stats():
try:
upload_dir = app.config['UPLOAD_FOLDER']
files_count = len([f for f in os.listdir(upload_dir) if os.path.isfile(os.path.join(upload_dir, f))])
return jsonify({
'filesUploaded': files_count,
'analysesCompleted': files_count, # Simplified
'threatsDetected': 0 # Placeholder
})
except Exception as e:
logger.error(f"Stats error: {e}")
return jsonify({'error': 'Failed to get stats'}), 500
# Static file serving
@app.route("/assets/<path:path>")
def send_assets(path):
return send_from_directory(os.path.join(app.static_folder, "assets"), path)
@app.route("/")
def index():
return send_from_directory(app.static_folder, "index.html")
# Catch-all route for React Router
@app.route("/<path:path>")
def catch_all(path):
return send_from_directory(app.static_folder, "index.html")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)

View File

@@ -0,0 +1,9 @@
flask==3.0.0
flask-cors==4.0.0
flask-sqlalchemy==3.1.1
flask-jwt-extended==4.6.0
psycopg2-binary==2.9.9
python-dotenv==1.0.0
requests==2.31.0
werkzeug==3.0.1
bcrypt==4.1.2

52
database/init.sql Normal file
View File

@@ -0,0 +1,52 @@
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Users table
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP
);
-- Hunts table
CREATE TABLE hunts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(100) NOT NULL,
description TEXT,
created_by UUID REFERENCES users(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'completed', 'archived'))
);
-- Files table
CREATE TABLE files (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
hunt_id UUID REFERENCES hunts(id) ON DELETE CASCADE,
filename VARCHAR(255) NOT NULL,
original_filename VARCHAR(255) NOT NULL,
file_size BIGINT,
file_type VARCHAR(50),
uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
analysis_status VARCHAR(20) DEFAULT 'pending' CHECK (analysis_status IN ('pending', 'processing', 'completed', 'failed'))
);
-- Analysis results table
CREATE TABLE analysis_results (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
file_id UUID REFERENCES files(id) ON DELETE CASCADE,
analysis_type VARCHAR(50) NOT NULL,
results JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create indexes
CREATE INDEX idx_hunts_created_by ON hunts(created_by);
CREATE INDEX idx_files_hunt_id ON files(hunt_id);
CREATE INDEX idx_analysis_file_id ON analysis_results(file_id);
-- Insert default admin user (password: admin123)
INSERT INTO users (username, email, password_hash) VALUES
('admin', 'admin@threathunter.local', '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewKyIqhFrMpGLgGi');

31
deploy/aws-deploy.yml Normal file
View File

@@ -0,0 +1,31 @@
# AWS ECS Deployment Configuration
version: '3.8'
services:
database:
image: postgres:15
environment:
POSTGRES_DB: threat_hunter
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
backend:
image: your-registry/threat-hunter-backend:latest
environment:
DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@database:5432/threat_hunter
SECRET_KEY: ${SECRET_KEY}
FLASK_ENV: production
depends_on:
- database
frontend:
image: your-registry/threat-hunter-frontend:latest
ports:
- "80:3000"
depends_on:
- backend
volumes:
postgres_data:

18
deploy/backup/backup.sh Normal file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
# Database backup
BACKUP_DIR="/backups"
DATE=$(date +%Y%m%d_%H%M%S)
echo "Creating database backup..."
docker exec threat-hunter-db pg_dump -U admin threat_hunter > "$BACKUP_DIR/db_backup_$DATE.sql"
# File uploads backup
echo "Backing up uploads..."
tar -czf "$BACKUP_DIR/uploads_backup_$DATE.tar.gz" ./uploads
# Keep only last 7 days of backups
find $BACKUP_DIR -name "*.sql" -mtime +7 -delete
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
echo "Backup completed: $DATE"

28
deploy/deploy.sh Normal file
View File

@@ -0,0 +1,28 @@
#!/bin/bash
set -e
echo "🚀 Deploying Cyber Threat Hunter..."
# Build and push images
echo "📦 Building Docker images..."
docker build -t your-registry/threat-hunter-backend:latest ./backend
docker build -t your-registry/threat-hunter-frontend:latest ./frontend
echo "🔄 Pushing to registry..."
docker push your-registry/threat-hunter-backend:latest
docker push your-registry/threat-hunter-frontend:latest
# Deploy based on environment
if [ "$1" = "kubernetes" ]; then
echo "☸️ Deploying to Kubernetes..."
kubectl apply -f deploy/kubernetes/
elif [ "$1" = "swarm" ]; then
echo "🐳 Deploying to Docker Swarm..."
docker stack deploy -c deploy/docker-stack.yml threat-hunter
else
echo "🐙 Deploying with Docker Compose..."
docker-compose -f docker-compose.prod.yml up -d
fi
echo "✅ Deployment complete!"

55
deploy/docker-stack.yml Normal file
View File

@@ -0,0 +1,55 @@
version: '3.8'
services:
database:
image: postgres:15
environment:
POSTGRES_DB: threat_hunter
POSTGRES_USER_FILE: /run/secrets/db_user
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- postgres_data:/var/lib/postgresql/data
secrets:
- db_user
- db_password
deploy:
replicas: 1
placement:
constraints:
- node.role == manager
backend:
image: your-registry/threat-hunter-backend:latest
environment:
DATABASE_URL: postgresql://admin:secure_password_123@database:5432/threat_hunter
SECRET_KEY_FILE: /run/secrets/secret_key
secrets:
- secret_key
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
frontend:
image: your-registry/threat-hunter-frontend:latest
ports:
- "80:3000"
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
volumes:
postgres_data:
secrets:
db_user:
external: true
db_password:
external: true
secret_key:
external: true

View File

@@ -0,0 +1,37 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: threat-hunter-backend
spec:
replicas: 3
selector:
matchLabels:
app: threat-hunter-backend
template:
metadata:
labels:
app: threat-hunter-backend
spec:
containers:
- name: backend
image: your-registry/threat-hunter-backend:latest
ports:
- containerPort: 5000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: threat-hunter-secrets
key: database-url
---
apiVersion: v1
kind: Service
metadata:
name: threat-hunter-backend-service
spec:
selector:
app: threat-hunter-backend
ports:
- port: 5000
targetPort: 5000
type: LoadBalancer

View File

@@ -0,0 +1,21 @@
version: '3.8'
services:
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana
ports:
- "3001:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: admin
volumes:
- grafana_data:/var/lib/grafana
volumes:
grafana_data:

View File

@@ -0,0 +1,26 @@
# Security Deployment Checklist
## Pre-Deployment
- [ ] Change all default passwords
- [ ] Generate strong SECRET_KEY
- [ ] Setup SSL/TLS certificates
- [ ] Configure firewall rules
- [ ] Set up backup strategy
## Database Security
- [ ] Use strong database passwords
- [ ] Enable database encryption
- [ ] Configure database firewall
- [ ] Set up regular backups
## Application Security
- [ ] Update all dependencies
- [ ] Configure CORS properly
- [ ] Enable rate limiting
- [ ] Set up monitoring/logging
## Infrastructure Security
- [ ] Use private networks
- [ ] Configure load balancer
- [ ] Set up intrusion detection
- [ ] Regular security updates

29
deploy/setup-prod.bat Normal file
View File

@@ -0,0 +1,29 @@
@echo off
echo Setting up production environment...
REM Create environment file
echo Creating .env.prod file...
(
echo DB_USER=threat_hunter_user
echo DB_PASSWORD=%RANDOM%%RANDOM%
echo SECRET_KEY=%RANDOM%%RANDOM%%RANDOM%
echo FLASK_ENV=production
) > .env.prod
REM Setup SSL certificates
echo Setting up SSL certificates...
mkdir ssl
REM Add your SSL certificate generation here
REM Create backup directory
mkdir backups
mkdir logs
REM Setup firewall rules
echo Configuring firewall...
netsh advfirewall firewall add rule name="HTTP" dir=in action=allow protocol=TCP localport=80
netsh advfirewall firewall add rule name="HTTPS" dir=in action=allow protocol=TCP localport=443
echo Production setup complete!
echo Please update .env.prod with your actual values
pause

72
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,72 @@
version: '3.8'
services:
database:
image: postgres:15
environment:
POSTGRES_DB: threat_hunter
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./backups:/backups
restart: unless-stopped
networks:
- internal
backend:
build:
context: ./backend
dockerfile: Dockerfile.prod
environment:
DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@database:5432/threat_hunter
SECRET_KEY: ${SECRET_KEY}
FLASK_ENV: production
depends_on:
- database
volumes:
- ./uploads:/app/uploads
- ./logs:/app/logs
restart: unless-stopped
networks:
- internal
- web
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.prod
ports:
- "80:80"
- "443:443"
volumes:
- ./ssl:/etc/ssl/certs
depends_on:
- backend
restart: unless-stopped
networks:
- web
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/ssl/certs
depends_on:
- frontend
- backend
restart: unless-stopped
networks:
- web
volumes:
postgres_data:
networks:
web:
external: true
internal:
internal: true

55
docker-compose.yml Normal file
View File

@@ -0,0 +1,55 @@
version: "3.8"
services:
database:
image: postgres:15
container_name: threat-hunter-db
environment:
POSTGRES_DB: threat_hunter
POSTGRES_USER: admin
POSTGRES_PASSWORD: secure_password_123
volumes:
- postgres_data:/var/lib/postgresql/data
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
networks:
- threat-hunter-network
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: threat-hunter-backend
environment:
DATABASE_URL: postgresql://admin:secure_password_123@database:5432/threat_hunter
FLASK_ENV: production
SECRET_KEY: your-secret-key-change-in-production
depends_on:
- database
ports:
- "5000:5000"
volumes:
- ./uploads:/app/uploads
- ./output:/app/output
networks:
- threat-hunter-network
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: threat-hunter-frontend
ports:
- "3000:3000"
depends_on:
- backend
networks:
- threat-hunter-network
volumes:
postgres_data:
networks:
threat-hunter-network:
driver: bridge

20
docker-start.bat Normal file
View File

@@ -0,0 +1,20 @@
@echo off
echo Starting Cyber Threat Hunter with Docker...
docker-compose down
docker-compose build
docker-compose up -d
echo.
echo ========================================
echo Cyber Threat Hunter is starting...
echo Frontend: http://localhost:3000
echo Backend API: http://localhost:5000
echo Database: PostgreSQL on port 5432
echo ========================================
echo.
echo Default credentials:
echo Username: admin
echo Password: admin123
echo.
pause

20
frontend/Dockerfile Normal file
View File

@@ -0,0 +1,20 @@
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
RUN npm install
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Serve with a simple server
RUN npm install -g serve
EXPOSE 3000
CMD ["serve", "-s", "dist", "-l", "3000"]

19
frontend/Dockerfile.prod Normal file
View File

@@ -0,0 +1,19 @@
# Build stage
FROM node:18-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,5 +1,5 @@
import React, { Suspense } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import React, { Suspense, useState, useEffect } from "react";
import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router-dom";
import { CssBaseline } from "@mui/material";
import { createTheme, ThemeProvider } from "@mui/material/styles";
@@ -12,6 +12,9 @@ import CSVProcessing from "./pages/CSVProcessing";
import SettingsConfig from "./pages/SettingsConfig";
import VirusTotal from "./pages/VirusTotal";
import SecurityTools from "./pages/SecurityTools";
import LoginPage from "./pages/LoginPage";
import Dashboard from "./pages/Dashboard";
import HuntPage from "./pages/HuntPage";
const theme = createTheme({
palette: {
@@ -20,6 +23,27 @@ const theme = createTheme({
});
function App() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const token = localStorage.getItem("token");
if (token) {
// Verify token and get user info
// For now, just set a dummy user
setUser({ username: "User" });
}
setLoading(false);
}, []);
if (loading) {
return (
<div className="min-h-screen bg-zinc-900 flex items-center justify-center">
<div className="text-white">Loading...</div>
</div>
);
}
return (
<ThemeProvider theme={theme}>
<CssBaseline />
@@ -29,7 +53,10 @@ function App() {
<main className="flex-1 p-6 overflow-auto">
<Suspense fallback={<div className="text-white">Loading...</div>}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/login" element={!user ? <LoginPage onLogin={setUser} /> : <Navigate to="/dashboard" />} />
<Route path="/dashboard" element={user ? <Dashboard user={user} /> : <Navigate to="/login" />} />
<Route path="/hunt/:huntId" element={user ? <HuntPage user={user} /> : <Navigate to="/login" />} />
<Route path="/" element={<Navigate to={user ? "/dashboard" : "/login"} />} />
<Route path="/baseline" element={<Baseline />} />
<Route path="/networking" element={<Networking />} />
<Route path="/applications" element={<Applications />} />

View File

@@ -0,0 +1,149 @@
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { Plus, FolderOpen, Calendar, User } from 'lucide-react'
const Dashboard = ({ user }) => {
const [hunts, setHunts] = useState([])
const [loading, setLoading] = useState(true)
const [showNewHunt, setShowNewHunt] = useState(false)
const [newHunt, setNewHunt] = useState({ name: '', description: '' })
const navigate = useNavigate()
useEffect(() => {
fetchHunts()
}, [])
const fetchHunts = async () => {
try {
const token = localStorage.getItem('token')
const response = await fetch('/api/hunts', {
headers: { 'Authorization': `Bearer ${token}` }
})
if (response.ok) {
const data = await response.json()
setHunts(data.hunts)
}
} catch (err) {
console.error('Failed to fetch hunts:', err)
}
setLoading(false)
}
const createHunt = async (e) => {
e.preventDefault()
try {
const token = localStorage.getItem('token')
const response = await fetch('/api/hunts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(newHunt)
})
if (response.ok) {
const data = await response.json()
navigate(`/hunt/${data.hunt.id}`)
}
} catch (err) {
console.error('Failed to create hunt:', err)
}
}
if (loading) {
return <div className="flex items-center justify-center h-64">Loading...</div>
}
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold">Welcome back, {user.username}</h1>
<p className="text-zinc-400">Choose a hunt to continue or start a new investigation</p>
</div>
<button
onClick={() => setShowNewHunt(true)}
className="bg-cyan-600 hover:bg-cyan-700 text-white px-4 py-2 rounded-lg flex items-center"
>
<Plus className="w-4 h-4 mr-2" />
New Hunt
</button>
</div>
{showNewHunt && (
<div className="bg-zinc-800 p-6 rounded-lg">
<h2 className="text-xl font-bold mb-4">Create New Hunt</h2>
<form onSubmit={createHunt} className="space-y-4">
<input
type="text"
placeholder="Hunt Name"
value={newHunt.name}
onChange={(e) => setNewHunt({...newHunt, name: e.target.value})}
className="w-full p-3 bg-zinc-700 rounded-lg border border-zinc-600 focus:border-cyan-400 outline-none"
required
/>
<textarea
placeholder="Description"
value={newHunt.description}
onChange={(e) => setNewHunt({...newHunt, description: e.target.value})}
className="w-full p-3 bg-zinc-700 rounded-lg border border-zinc-600 focus:border-cyan-400 outline-none h-24"
/>
<div className="flex space-x-2">
<button type="submit" className="bg-cyan-600 hover:bg-cyan-700 text-white px-4 py-2 rounded-lg">
Create Hunt
</button>
<button
type="button"
onClick={() => setShowNewHunt(false)}
className="bg-zinc-600 hover:bg-zinc-700 text-white px-4 py-2 rounded-lg"
>
Cancel
</button>
</div>
</form>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{hunts.map(hunt => (
<div
key={hunt.id}
onClick={() => navigate(`/hunt/${hunt.id}`)}
className="bg-zinc-800 p-6 rounded-lg cursor-pointer hover:bg-zinc-750 transition-colors"
>
<div className="flex items-center mb-4">
<FolderOpen className="w-8 h-8 text-cyan-400 mr-3" />
<div>
<h3 className="font-semibold">{hunt.name}</h3>
<p className="text-sm text-zinc-400">{hunt.status}</p>
</div>
</div>
<p className="text-zinc-400 text-sm mb-3">{hunt.description}</p>
<div className="flex items-center text-xs text-zinc-500">
<Calendar className="w-3 h-3 mr-1" />
{new Date(hunt.created_at).toLocaleDateString()}
</div>
</div>
))}
</div>
{hunts.length === 0 && (
<div className="text-center py-12">
<FolderOpen className="w-16 h-16 text-zinc-600 mx-auto mb-4" />
<h3 className="text-xl font-semibold mb-2">No hunts yet</h3>
<p className="text-zinc-400 mb-4">Start your first threat hunting investigation</p>
<button
onClick={() => setShowNewHunt(true)}
className="bg-cyan-600 hover:bg-cyan-700 text-white px-6 py-3 rounded-lg"
>
Create Your First Hunt
</button>
</div>
)}
</div>
)
}
export default Dashboard

View File

@@ -0,0 +1,104 @@
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Shield, User, Lock } from 'lucide-react'
const LoginPage = ({ onLogin }) => {
const [credentials, setCredentials] = useState({ username: '', password: '' })
const [isLogin, setIsLogin] = useState(true)
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const navigate = useNavigate()
const handleSubmit = async (e) => {
e.preventDefault()
setLoading(true)
setError('')
try {
const endpoint = isLogin ? '/api/auth/login' : '/api/auth/register'
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
})
const data = await response.json()
if (response.ok) {
localStorage.setItem('token', data.access_token)
onLogin(data.user)
navigate('/dashboard')
} else {
setError(data.error)
}
} catch (err) {
setError('Connection failed')
}
setLoading(false)
}
return (
<div className="min-h-screen bg-zinc-900 flex items-center justify-center">
<div className="bg-zinc-800 p-8 rounded-lg shadow-lg w-96">
<div className="text-center mb-6">
<Shield className="w-16 h-16 text-cyan-400 mx-auto mb-4" />
<h1 className="text-2xl font-bold">Cyber Threat Hunter</h1>
<p className="text-zinc-400">Advanced threat hunting platform</p>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium mb-2">Username</label>
<div className="relative">
<User className="w-5 h-5 absolute left-3 top-3 text-zinc-400" />
<input
type="text"
value={credentials.username}
onChange={(e) => setCredentials({...credentials, username: e.target.value})}
className="w-full pl-10 p-3 bg-zinc-700 rounded-lg border border-zinc-600 focus:border-cyan-400 outline-none"
required
/>
</div>
</div>
<div>
<label className="block text-sm font-medium mb-2">Password</label>
<div className="relative">
<Lock className="w-5 h-5 absolute left-3 top-3 text-zinc-400" />
<input
type="password"
value={credentials.password}
onChange={(e) => setCredentials({...credentials, password: e.target.value})}
className="w-full pl-10 p-3 bg-zinc-700 rounded-lg border border-zinc-600 focus:border-cyan-400 outline-none"
required
/>
</div>
</div>
{error && (
<div className="text-red-400 text-sm">{error}</div>
)}
<button
type="submit"
disabled={loading}
className="w-full bg-cyan-600 hover:bg-cyan-700 text-white p-3 rounded-lg disabled:opacity-50"
>
{loading ? 'Please wait...' : (isLogin ? 'Login' : 'Register')}
</button>
</form>
<div className="text-center mt-4">
<button
onClick={() => setIsLogin(!isLogin)}
className="text-cyan-400 hover:text-cyan-300"
>
{isLogin ? 'Need an account? Register' : 'Already have an account? Login'}
</button>
</div>
</div>
</div>
)
}
export default LoginPage