298 lines
12 KiB
Python
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' |