Files
gyber/apps/web/config/settings.py
2025-09-15 13:33:34 +09:00

298 lines
12 KiB
Python

"""
Django settings for config project.
Generated by 'django-admin startproject' using Django 5.2.
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/
"""
import os
from pathlib import Path
import logging.handlers # 로테이팅 파일 핸들러 임포트
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent # 프로젝트 설정 폴더(config)의 부모, 즉 /data/gyber/apps/web/
# ==============================================================================
# 핵심 보안 설정 (프로덕션 환경)
# ==============================================================================
# SECRET_KEY: 환경 변수 'DJANGO_SECRET_KEY' 에서 로드. 프로덕션에서는 반드시 설정해야 함.
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
if not SECRET_KEY:
# 개발 환경에서는 임시 키를 사용할 수 있지만, 프로덕션에서는 에러 발생시킴
if os.environ.get('DJANGO_DEVELOPMENT_MODE') == 'True': # 개발 모드 식별용 환경 변수 (선택적)
SECRET_KEY = 'django-insecure-temporary-dev-key-for-gyber'
print("WARNING: Using a temporary SECRET_KEY for development. Set DJANGO_SECRET_KEY in production.")
else:
raise ValueError("프로덕션 환경에서는 DJANGO_SECRET_KEY 환경 변수를 반드시 설정해야 합니다.")
# DEBUG 모드: 프로덕션이므로 False 로 고정 (필수!)
DEBUG = False
# 허용 호스트: 외부 접속 도메인 및 필요한 내부 IP 명시
ALLOWED_HOSTS = [
'gyber.oneunivrs.com', # 실제 서비스 도메인
'192.168.100.10', # 내부 테스트/접근용 IP (필요시)
# 'localhost', '127.0.0.1' # 로컬 테스트용 (프로덕션에서는 제거 고려)
]
# 환경 변수에서 추가 호스트 로드 (선택적)
# additional_hosts = os.environ.get('DJANGO_ADDITIONAL_ALLOWED_HOSTS')
# if additional_hosts:
# ALLOWED_HOSTS.extend([h.strip() for h in additional_hosts.split(',')])
# ==============================================================================
# 애플리케이션 정의
# ==============================================================================
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'gyber', # Gyber 앱
# Third-party apps
'widget_tweaks',
'mozilla_django_oidc',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'mozilla_django_oidc.middleware.SessionRefresh',
]
ROOT_URLCONF = 'config.urls'
# ==============================================================================
# 템플릿 설정
# ==============================================================================
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # BASE_DIR 은 /data/gyber/apps/web/
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
# 'django.template.context_processors.debug', # DEBUG=False 이므로 주석 처리 또는 유지해도 무방
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'gyber.context_processors.theme_processor', # ★★★ 커스텀 테마 컨텍스트 프로세서 추가 ★★★
'gyber.context_processors.auth_context',
],
},
},
]
WSGI_APPLICATION = 'config.wsgi.application' # /data/gyber/apps/web/config/wsgi.py
# ==============================================================================
# 데이터베이스 설정
# ==============================================================================
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': os.environ.get('DB_NAME', 'gyber'),
'USER': os.environ.get('DB_USER', 'gyber'),
'PASSWORD': os.environ.get('DB_PASSWORD'), # 환경 변수에서 로드 (필수!)
'HOST': os.environ.get('DB_HOST', '127.0.0.1'), # DB 서버 IP 또는 호스트명
'PORT': os.environ.get('DB_PORT', '3306'),
'OPTIONS': {
'charset': 'utf8mb4',
},
}
}
if not DATABASES['default']['PASSWORD']:
raise ValueError("DB_PASSWORD 환경 변수가 설정되지 않았습니다.")
# ==============================================================================
# 비밀번호 검증
# ==============================================================================
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',},
]
# ==============================================================================
# 국제화 및 현지화
# ==============================================================================
LANGUAGE_CODE = 'ko-kr' # 한국어
TIME_ZONE = 'Asia/Seoul' # 한국 시간
USE_I18N = True
USE_TZ = True # 시간대 인식 날짜/시간 사용
# ==============================================================================
# 정적 파일 (Static files) 설정
# ==============================================================================
STATIC_URL = 'static/' # 템플릿에서 정적 파일 접근 시 URL 프리픽스
# 'collectstatic' 명령으로 모든 정적 파일을 모을 디렉토리 (Nginx 설정과 일치)
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') # /data/gyber/apps/web/staticfiles/
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'), # BASE_DIR 은 /data/gyber/apps/web/
]
# ==============================================================================
# 기본 Primary Key 필드 타입
# ==============================================================================
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# ==============================================================================
# 인증 및 권한 (OIDC - 프로덕션)
# ==============================================================================
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'gyber.oidc.CustomOIDCAuthenticationBackend',
)
# --- OIDC 설정 (환경 변수에서 민감 정보 로드) ---
OIDC_RP_CLIENT_ID = os.environ.get("OIDC_RP_CLIENT_ID", "8a61fd65-70ec-4d02-8792-cc516b0746bc") # 기본값 제공 가능 (개발용)
OIDC_RP_CLIENT_SECRET = os.environ.get("OIDC_RP_CLIENT_SECRET") # 환경 변수에서 로드 (필수!)
if not OIDC_RP_CLIENT_SECRET:
raise ValueError("OIDC_RP_CLIENT_SECRET 환경 변수가 설정되지 않았습니다.")
OIDC_RP_SIGN_ALGO = "RS256"
OIDC_RP_SCOPES = "profile email openid"
AZURE_TENANT_ID = os.environ.get("AZURE_TENANT_ID", "1e8605cc-8007-46b0-993f-b388917f9499") # 기본값 제공 가능
OIDC_OP_AUTHORIZATION_ENDPOINT = f"https://login.microsoftonline.com/{AZURE_TENANT_ID}/oauth2/v2.0/authorize"
OIDC_OP_TOKEN_ENDPOINT = f"https://login.microsoftonline.com/{AZURE_TENANT_ID}/oauth2/v2.0/token"
OIDC_OP_USER_ENDPOINT = "https://graph.microsoft.com/oidc/userinfo"
OIDC_OP_JWKS_ENDPOINT = f"https://login.microsoftonline.com/{AZURE_TENANT_ID}/discovery/v2.0/keys"
OIDC_OP_LOGOUT_ENDPOINT = f"https://login.microsoftonline.com/{AZURE_TENANT_ID}/oauth2/v2.0/logout"
OIDC_CREATE_USER_CALLBACK = "gyber.oidc.map_azure_user"
OIDC_UPDATE_USER_CALLBACK = "gyber.oidc.map_azure_user"
# 프로덕션 로그아웃 후 리디렉션 URI (HTTPS, 지정된 포트 포함)
OIDC_RP_POST_LOGOUT_REDIRECT_URI = f"https://gyber.oneunivrs.com:8438/oidc/authenticate/"
# 프로덕션 환경 OIDC 보안 설정 (HTTPS 사용 가정)
OIDC_VERIFY_SSL = True # 외부 CA 인증서 사용 시 True
OIDC_REDIRECT_STATE_SECURE = True # HTTPS 사용 시 True
OIDC_SESSION_MANAGEMENT_ENABLE = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
# --- 로그인/로그아웃 URL 설정 ---
LOGIN_URL = "/oidc/authenticate/"
LOGIN_REDIRECT_URL = "/dashboard/"
LOGOUT_REDIRECT_URL = LOGIN_URL # 로그아웃 후 다시 로그인 페이지로
# ==============================================================================
# 로깅 설정 (프로덕션 - 파일 로깅 중심)
# ==============================================================================
# 로그 파일을 저장할 디렉토리 경로 (/data/gyber/apps/web/logs)
LOGS_DIR = os.path.join(BASE_DIR, 'logs') # 앱별로 구분
# 로그 디렉토리가 없으면 생성 (Gunicorn 실행 사용자에게 쓰기 권한 필요)
os.makedirs(LOGS_DIR, exist_ok=True)
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} [{process:d}:{thread:d}] {message}',
'style': '{',
},
'simple': {
'format': '[{levelname}] {asctime} {module}: {message}', # 시간과 모듈 정보 추가
'style': '{',
},
},
'handlers': {
'console': { # 개발 및 systemd journal 확인용
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'simple',
},
'file_django': { # 일반 Django 및 앱 로그
'level': 'INFO', # 프로덕션에서는 INFO 부터 기록
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(LOGS_DIR, 'django_app.log'),
'maxBytes': 1024 * 1024 * 20, # 20MB
'backupCount': 5,
'formatter': 'verbose',
'encoding': 'utf-8',
},
'file_oidc': { # OIDC 관련 로그
'level': 'DEBUG', # OIDC는 문제 발생 시 상세 로그 필요
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(LOGS_DIR, 'oidc.log'),
'maxBytes': 1024 * 1024 * 10,
'backupCount': 3,
'formatter': 'verbose',
'encoding': 'utf-8',
},
},
'loggers': {
'django': {
'handlers': ['console', 'file_django'],
'level': 'INFO',
'propagate': False,
},
'django.request': { # 4xx, 5xx 에러 등
'handlers': ['file_django'], # 에러는 파일에 상세히
'level': 'WARNING',
'propagate': False,
},
'gyber': { # 'gyber' 앱의 로그
'handlers': ['console', 'file_django'],
'level': os.environ.get('DJANGO_GYBER_LOG_LEVEL', 'INFO'), # 환경 변수로 앱 로그 레벨 제어
'propagate': False,
},
'mozilla_django_oidc': {
'handlers': ['console', 'file_oidc'],
'level': os.environ.get('DJANGO_OIDC_LOG_LEVEL', 'INFO'), # OIDC 로그 레벨도 제어
'propagate': False,
},
},
# 루트 로거: 특별히 설정하지 않은 다른 모든 로거의 로그를 처리 (필요시 활성화)
# 'root': {
# 'handlers': ['console'],
# 'level': 'WARNING',
# },
}
# ==============================================================================
# 보안 강화 설정 (HTTPS 필수)
# ==============================================================================
# Nginx에서 SSL/TLS를 처리하고 X-Forwarded-Proto 헤더를 올바르게 전달한다고 가정
# Django가 프록시 뒤에서 HTTPS를 인지하도록 설정 (Nginx 설정과 일치해야 함)
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# HTTPS를 통해서만 쿠키 전송
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
# 브라우저가 항상 HTTPS로만 접속하도록 강제 (HSTS)
SECURE_HSTS_SECONDS = 31536000 # 1년
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True # HSTS 프리로드 리스트에 등록하려면 True (신중히 결정)
# 클릭재킹 방지
X_FRAME_OPTIONS = 'SAMEORIGIN' # 'DENY' 또는 'SAMEORIGIN'