""" 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'