Files
xingrin/backend/config/settings.py

362 lines
14 KiB
Python
Raw Normal View History

2025-12-12 18:04:57 +08:00
"""
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',
'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', '300')), # 远程数据库使用 300 秒,减少重连开销
# PostgreSQL 特定选项 - 针对远程数据库优化
'OPTIONS': {
'connect_timeout': 30, # 连接超时 30 秒(远程数据库需要更长时间)
'options': '-c statement_timeout=60000 -c idle_in_transaction_session_timeout=300000', # SQL 语句超时 60 秒,事务空闲超时 5 分钟
'keepalives_idle': 600, # TCP keepalive 空闲时间 10 分钟
'keepalives_interval': 30, # TCP keepalive 间隔 30 秒
'keepalives_count': 3, # 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'
# ==================== REST Framework 配置 ====================
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'apps.common.pagination.BasePagination', # 使用基础分页器
# Session 认证(禁用 CSRF前后端分离项目通过 CORS 控制跨域)
'DEFAULT_AUTHENTICATION_CLASSES': [
'apps.common.authentication.CsrfExemptSessionAuthentication',
],
# 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
# ==================== 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)
# 扫描工具基础路径(后端和 Worker 统一使用该路径前缀存放三方工具等文件)
SCAN_TOOLS_BASE_PATH = os.getenv('SCAN_TOOLS_PATH', '/opt/xingrin/tools')
# 字典文件基础路径(后端和 Worker 统一使用该路径前缀存放字典文件)
WORDLISTS_BASE_PATH = os.getenv('WORDLISTS_PATH', '/opt/xingrin/wordlists')
# 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()
SERVER_PORT = os.getenv('SERVER_PORT', '8888')
# ============================================
# 任务分发器配置(负载感知调度)
# ============================================
# Docker 镜像版本配置(统一管理,确保所有组件版本一致)
DOCKER_USER = os.getenv('DOCKER_USER', 'yyhuni')
2025-12-15 12:09:28 +08:00
IMAGE_TAG = os.getenv('IMAGE_TAG', '')
2025-12-17 09:47:56 +08:00
# 任务执行器镜像配置task_distributor 用于创建 worker 容器执行扫描任务)
#
# 版本一致性说明:
# - 生产环境IMAGE_TAG 锁定版本,确保 server 和 worker 版本一致
# - 开发环境:可通过 TASK_EXECUTOR_IMAGE 环境变量指向本地构建镜像
#
2025-12-17 09:47:56 +08:00
# 环境区分逻辑:
# 1. 主服务器环境:
# - 有 IMAGE_TAG 环境变量(从 docker/.env 读取install.sh 写入)
2025-12-17 09:47:56 +08:00
# - 需要 TASK_EXECUTOR_IMAGE 来创建 worker 容器执行任务
# - 默认使用 {DOCKER_USER}/xingrin-worker:{IMAGE_TAG}(版本锁定)
# - 开发时可设置 TASK_EXECUTOR_IMAGE=本地镜像名 覆盖
2025-12-17 09:47:56 +08:00
#
# 2. Worker 容器环境:
# - 无 IMAGE_TAG 环境变量(容器启动时未注入)
# - 无 .env 文件
# - 不需要 TASK_EXECUTOR_IMAGEworker 只执行任务,不会再创建其他容器)
# - 设为空字符串避免 AttributeError确保 settings 模块正常加载
#
# 开发环境配置示例:
# 在 docker/.env 中添加TASK_EXECUTOR_IMAGE=docker-agent:test-v1.0.0
2025-12-15 12:09:28 +08:00
if IMAGE_TAG:
2025-12-17 09:47:56 +08:00
# 主服务器场景:构建完整镜像名
2025-12-15 12:09:28 +08:00
TASK_EXECUTOR_IMAGE = os.getenv('TASK_EXECUTOR_IMAGE', f'{DOCKER_USER}/xingrin-worker:{IMAGE_TAG}')
else:
2025-12-17 09:47:56 +08:00
# Worker 容器场景:空值,防止加载错误
2025-12-15 12:09:28 +08:00
TASK_EXECUTOR_IMAGE = os.getenv('TASK_EXECUTOR_IMAGE', '')
2025-12-12 18:04:57 +08:00
# 任务提交间隔(秒)- 防止短时间内重复分配到同一节点
# 应大于心跳间隔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/{results,logs}
HOST_RESULTS_DIR = '/opt/xingrin/results'
HOST_LOGS_DIR = '/opt/xingrin/logs'
# ============================================
# Worker 配置中心(任务容器从 /api/workers/config/ 获取)
# ============================================
2025-12-17 11:02:42 +08:00
# Worker 数据库/Redis 地址由 worker_views.py 的 config API 动态返回
# 根据请求来源(本地/远程)返回不同的配置:
# - 本地 WorkerDocker 网络内使用内部服务名postgres, redis
# - 远程 Worker公网访问使用 PUBLIC_HOST
#
# 以下变量仅作为备用/兼容配置,实际配置由 API 动态生成
2025-12-12 18:04:57 +08:00
_db_host = DATABASES['default']['HOST']
_is_internal_db = _db_host in ('postgres', 'localhost', '127.0.0.1')
2025-12-17 11:02:42 +08:00
WORKER_DB_HOST = os.getenv('WORKER_DB_HOST', _db_host)
2025-12-12 18:04:57 +08:00
# 远程 Worker 访问 Redis 的地址(自动推导)
# - 如果 PUBLIC_HOST 是外部 IP → 使用 PUBLIC_HOST
# - 如果 PUBLIC_HOST 是 Docker 内部名 → 使用 redis本地部署
_is_internal_public = PUBLIC_HOST in ('server', 'localhost', '127.0.0.1')
WORKER_REDIS_URL = os.getenv(
'WORKER_REDIS_URL',
'redis://redis:6379/0' if _is_internal_public else f'redis://{PUBLIC_HOST}:6379/0'
)
# 容器内挂载目标路径(固定值,不需要修改)
CONTAINER_RESULTS_MOUNT = '/app/backend/results'
CONTAINER_LOGS_MOUNT = '/app/backend/logs'