Files
xingrin/backend/config/settings.py
yyhuni 9b63203b5a refactor(migrations,frontend,backend): reorganize app structure and enhance target management UI
- Consolidate common migrations into dedicated common app module
- Remove asset search materialized view migration (0002) and simplify migration structure
- Reorganize target detail page with new overview and settings sub-routes
- Add target overview component displaying key asset information
- Add target settings component for configuration management
- Enhance scan history UI with improved data table and column definitions
- Update scheduled scan dialog with better form handling
- Refactor target service with improved API integration
- Update scan hooks (use-scans, use-scheduled-scans) with better state management
- Add internationalization strings for new target management features
- Update Docker initialization and startup scripts for new app structure
- Bump Django to 5.2.7 and update dependencies in requirements.txt
- Add WeChat group contact information to README
- Improve UI tabs component with better accessibility and styling
2026-01-06 10:42:38 +08:00

386 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Django settings for config project.
Generated by 'django-admin startproject' using Django 5.2.7.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
from pathlib import Path
import os
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
def get_bool_env(key: str, default: bool = False) -> bool:
"""获取布尔值环境变量兼容多种写法true/True/TRUE/1/yes"""
value = os.getenv(key, str(default)).lower()
return value in ('true', '1', 'yes', 'on')
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv('SECRET_KEY', 'django-insecure-default-key')
# SECURITY WARNING: don't run with debug turned on in production!
# 安全优先:默认为 False开发环境需显式设置 DEBUG=True
DEBUG = get_bool_env('DEBUG', False)
ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 第三方应用
'rest_framework',
'django_filters', # DRF 过滤器支持
'drf_yasg',
'corsheaders',
'channels', # WebSocket 支持
# 业务应用
'apps.common', # 通用工具
'apps.targets', # 扫描目标管理
'apps.scan', # 扫描任务管理
'apps.engine', # 扫描引擎管理
'apps.asset', # 资产管理
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware', # CORS 中间件(必须在 CommonMiddleware 之前)
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware', # 已禁用 CSRF 校验
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'config.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # 添加自定义模板目录
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'config.wsgi.application'
# ASGI 应用配置(用于 WebSocket
ASGI_APPLICATION = 'config.asgi.application'
# Database - PostgreSQL 配置
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': os.getenv('DB_ENGINE', 'django.db.backends.postgresql'),
'NAME': os.getenv('DB_NAME', 'xingrin'),
'USER': os.getenv('DB_USER', 'postgres'),
'PASSWORD': os.getenv('DB_PASSWORD', ''),
'HOST': os.getenv('DB_HOST', 'localhost'),
'PORT': os.getenv('DB_PORT', '5432'),
# 连接池配置:针对长时间扫描任务优化
# 说明:
# - 0: 每次请求后关闭(适合不稳定的远程连接)
# - 60-120: 推荐值(平衡性能和资源占用)
# - 300+: 适合长时间任务(减少连接重建开销)
# - None: 永久连接(仅适合专用连接池,不推荐)
'CONN_MAX_AGE': int(os.getenv('DB_CONN_MAX_AGE', '60')), # 降低到 60 秒,更快检测失效连接
# Django 4.1+ 连接健康检查:每次使用前验证连接是否有效
# 解决 "server closed the connection unexpectedly" 问题
'CONN_HEALTH_CHECKS': True,
# PostgreSQL 特定选项 - 针对远程数据库优化
'OPTIONS': {
'connect_timeout': 30, # 连接超时 30 秒(远程数据库需要更长时间)
'options': '-c statement_timeout=60000 -c idle_in_transaction_session_timeout=300000', # SQL 语句超时 60 秒,事务空闲超时 5 分钟
'keepalives': 1, # 启用 TCP keepalive
'keepalives_idle': 30, # TCP keepalive 空闲时间 30 秒(更积极地检测断连)
'keepalives_interval': 10, # TCP keepalive 间隔 10 秒
'keepalives_count': 5, # TCP keepalive 重试次数
# 性能优化参数
'sslmode': 'disable', # 禁用SSL以减少连接延迟如果网络安全可控
'application_name': 'xingrin_scanner', # 标识应用名称,便于监控
}
}
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = os.getenv('LANGUAGE_CODE', 'zh-hans')
TIME_ZONE = os.getenv('TIME_ZONE', 'Asia/Shanghai')
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# ==================== Worker API Key 配置 ====================
# Worker 节点认证密钥(从环境变量读取)
WORKER_API_KEY = os.environ.get('WORKER_API_KEY', '')
# ==================== REST Framework 配置 ====================
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'apps.common.pagination.BasePagination', # 使用基础分页器
# Session 认证(禁用 CSRF前后端分离项目通过 CORS 控制跨域)
'DEFAULT_AUTHENTICATION_CLASSES': [
'apps.common.authentication.CsrfExemptSessionAuthentication',
],
# 全局权限配置:默认需要认证,公开端点和 Worker 端点在权限类中单独处理
'DEFAULT_PERMISSION_CLASSES': [
'apps.common.permissions.IsAuthenticatedOrPublic',
],
# 自定义异常处理器:统一 401/403 错误响应格式
'EXCEPTION_HANDLER': 'apps.common.exception_handlers.custom_exception_handler',
# JSON 命名格式转换:后端 snake_case ↔ 前端 camelCase
'DEFAULT_RENDERER_CLASSES': (
'djangorestframework_camel_case.render.CamelCaseJSONRenderer', # 响应数据转换为 camelCase
'djangorestframework_camel_case.render.CamelCaseBrowsableAPIRenderer', # 浏览器 API 也使用 camelCase
),
'DEFAULT_PARSER_CLASSES': (
'djangorestframework_camel_case.parser.CamelCaseJSONParser', # 请求数据从 camelCase 转换为 snake_case
'djangorestframework_camel_case.parser.CamelCaseFormParser', # 表单数据也支持转换
'djangorestframework_camel_case.parser.CamelCaseMultiPartParser', # 文件上传支持转换
),
# 转换配置
'JSON_UNDERSCOREIZE': {
'no_underscore_before_number': True, # 数字前不加下划线 (field1 不会变成 field_1)
},
}
# ==================== CORS 配置 ====================
# 允许所有来源(前后端分离项目,安全性由认证系统保障)
CORS_ALLOW_ALL_ORIGINS = os.getenv('CORS_ALLOW_ALL_ORIGINS', 'True').lower() == 'true'
CORS_ALLOW_CREDENTIALS = True
# 暴露额外的响应头给前端Content-Disposition 用于文件下载获取文件名)
CORS_EXPOSE_HEADERS = ['Content-Disposition']
# ==================== CSRF 配置 ====================
CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost:3000,http://127.0.0.1:3000').split(',')
# ==================== Session 配置 ====================
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 # 7 天
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax' # 跨站请求时发送 cookie
# ==================== 扫描结果存储和清理配置 ====================
# 扫描结果存储目录(智能路径配置)
_scan_results_dir_env = os.getenv('SCAN_RESULTS_DIR')
if _scan_results_dir_env:
# 使用环境变量指定的路径(支持相对和绝对路径)
if os.path.isabs(_scan_results_dir_env):
SCAN_RESULTS_DIR = _scan_results_dir_env # 绝对路径
else:
SCAN_RESULTS_DIR = str(BASE_DIR / _scan_results_dir_env) # 相对路径转绝对路径
else:
# 默认使用项目目录下的 results 文件夹
SCAN_RESULTS_DIR = str(BASE_DIR / 'results')
# 扫描结果保留时间(单位:天)
SCAN_RESULTS_RETENTION_DAYS = int(os.getenv('SCAN_RETENTION_DAYS', '3'))
# ==================== Redis 配置 ====================
# Redis 配置(用于 WebSocket Channel Layer
REDIS_HOST = os.getenv('REDIS_HOST', 'localhost')
REDIS_PORT = int(os.getenv('REDIS_PORT', 6379))
REDIS_DB = int(os.getenv('REDIS_DB', 0))
# Channels Layer 配置WebSocket 后端)
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
'hosts': [(REDIS_HOST, REDIS_PORT)],
'capacity': 1500, # 单个通道最大消息数
'expiry': 10, # 消息过期时间(秒)
},
},
}
# ==================== 日志配置 ====================
# 日志配置说明:
# 1. 开发环境DEBUG=True
# - 默认 DEBUG 级别,输出详细调试信息
# - 控制台彩色输出,便于调试
# - 可选文件输出(配置 LOG_DIR
#
# 2. 生产环境DEBUG=False
# - 默认 INFO 级别,只输出关键信息
# - 同时输出到控制台和文件(配置 LOG_DIR
# - 文件自动轮转,避免日志文件过大
#
# 3. 环境变量控制:
# - LOG_LEVEL: 全局日志级别DEBUG/INFO/WARNING/ERROR/CRITICAL
# - LOG_DIR: 日志文件目录(留空则不输出文件)
#
from config.logging_config import get_logging_config
LOGGING = get_logging_config(debug=DEBUG)
# 命令执行日志开关(供 apps.scan.utils.command_executor 使用)
ENABLE_COMMAND_LOGGING = get_bool_env('ENABLE_COMMAND_LOGGING', True)
# ==================== 数据目录配置(统一使用 /opt/xingrin ====================
# 所有数据目录统一挂载到 /opt/xingrin便于管理和备份
# 扫描工具基础路径worker 容器内,符合 FHS 标准)
# 使用 /opt/xingrin-tools/bin 隔离项目专用扫描工具,避免与系统工具或 Python 包冲突
SCAN_TOOLS_BASE_PATH = os.getenv('SCAN_TOOLS_PATH', '/opt/xingrin-tools/bin')
# 字典文件基础路径
WORDLISTS_BASE_PATH = os.getenv('WORDLISTS_PATH', '/opt/xingrin/wordlists')
# 指纹库基础路径
FINGERPRINTS_BASE_PATH = os.getenv('FINGERPRINTS_PATH', '/opt/xingrin/fingerprints')
# Nuclei 模板仓库根目录(存放 git clone 的仓库)
NUCLEI_TEMPLATES_REPOS_BASE_DIR = os.getenv('NUCLEI_TEMPLATES_REPOS_DIR', '/opt/xingrin/nuclei-repos')
# Nuclei 模板基础路径custom / public 两类模板目录,已废弃,保留兼容)
NUCLEI_CUSTOM_TEMPLATES_DIR = os.getenv('NUCLEI_CUSTOM_TEMPLATES_DIR', '/opt/xingrin/nuclei-templates/custom')
NUCLEI_PUBLIC_TEMPLATES_DIR = os.getenv('NUCLEI_PUBLIC_TEMPLATES_DIR', '/opt/xingrin/nuclei-templates/public')
# Nuclei 官方模板仓库地址
NUCLEI_TEMPLATES_REPO_URL = os.getenv('NUCLEI_TEMPLATES_REPO_URL', 'https://github.com/projectdiscovery/nuclei-templates.git')
# 对外访问主机与端口(供 Worker 访问 Django 使用)
PUBLIC_HOST = os.getenv('PUBLIC_HOST', 'localhost').strip()
PUBLIC_PORT = os.getenv('PUBLIC_PORT', '8083').strip() # 对外 HTTPS 端口
SERVER_PORT = os.getenv('SERVER_PORT', '8888')
# ============================================
# 任务分发器配置(负载感知调度)
# ============================================
# Docker 镜像版本配置(统一管理,确保所有组件版本一致)
DOCKER_USER = os.getenv('DOCKER_USER', 'yyhuni')
IMAGE_TAG = os.getenv('IMAGE_TAG', '')
# 任务执行器镜像配置task_distributor 用于创建 worker 容器执行扫描任务)
#
# 版本一致性说明:
# - 生产环境IMAGE_TAG 锁定版本,确保 server 和 worker 版本一致
# - 开发环境:可通过 TASK_EXECUTOR_IMAGE 环境变量指向本地构建镜像
#
# 环境区分逻辑:
# 1. 主服务器环境:
# - 有 IMAGE_TAG 环境变量(从 docker/.env 读取install.sh 写入)
# - 需要 TASK_EXECUTOR_IMAGE 来创建 worker 容器执行任务
# - 默认使用 {DOCKER_USER}/xingrin-worker:{IMAGE_TAG}(版本锁定)
# - 开发时可设置 TASK_EXECUTOR_IMAGE=本地镜像名 覆盖
#
# 2. Worker 容器环境:
# - 无 IMAGE_TAG 环境变量(容器启动时未注入)
# - 无 .env 文件
# - 不需要 TASK_EXECUTOR_IMAGEworker 只执行任务,不会再创建其他容器)
# - 设为空字符串避免 AttributeError确保 settings 模块正常加载
#
# 开发环境配置示例:
# 在 docker/.env 中添加TASK_EXECUTOR_IMAGE=docker-agent:test-v1.0.0
if IMAGE_TAG:
# 主服务器场景:构建完整镜像名
TASK_EXECUTOR_IMAGE = os.getenv('TASK_EXECUTOR_IMAGE', f'{DOCKER_USER}/xingrin-worker:{IMAGE_TAG}')
else:
# Worker 容器场景:空值,防止加载错误
TASK_EXECUTOR_IMAGE = os.getenv('TASK_EXECUTOR_IMAGE', '')
# 任务提交间隔(秒)- 防止短时间内重复分配到同一节点
# 应大于心跳间隔3秒确保负载数据已更新
TASK_SUBMIT_INTERVAL = int(os.getenv('TASK_SUBMIT_INTERVAL', '6'))
# 本地 Worker Docker 网络名称(与 docker-compose.yml 中定义的一致)
DOCKER_NETWORK_NAME = os.getenv('DOCKER_NETWORK_NAME', 'xingrin_network')
# 宿主机挂载源路径(所有节点统一使用固定路径)
# 部署前需创建mkdir -p /opt/xingrin
HOST_RESULTS_DIR = '/opt/xingrin/results'
HOST_LOGS_DIR = '/opt/xingrin/logs'
HOST_FINGERPRINTS_DIR = '/opt/xingrin/fingerprints'
HOST_WORDLISTS_DIR = '/opt/xingrin/wordlists'
# ============================================
# Worker 配置中心(任务容器从 /api/workers/config/ 获取)
# ============================================
# Worker 数据库地址由 worker_views.py 的 config API 动态返回
# 根据请求来源(本地/远程)返回不同的配置:
# - 本地 WorkerDocker 网络内):使用内部服务名 postgres
# - 远程 Worker公网访问使用 PUBLIC_HOST
#
# 注意Redis 仅在 Server 容器内使用Worker 不需要直接连接 Redis
_db_host = DATABASES['default']['HOST']
_is_internal_db = _db_host in ('postgres', 'localhost', '127.0.0.1')
WORKER_DB_HOST = os.getenv('WORKER_DB_HOST', _db_host)
# 容器内挂载目标路径(统一使用 /opt/xingrin
CONTAINER_RESULTS_MOUNT = '/opt/xingrin/results'
CONTAINER_LOGS_MOUNT = '/opt/xingrin/logs'