updates
This commit is contained in:
Binary file not shown.
BIN
backEnd/gnx/middleware/__pycache__/api_security.cpython-312.pyc
Normal file
BIN
backEnd/gnx/middleware/__pycache__/api_security.cpython-312.pyc
Normal file
Binary file not shown.
145
backEnd/gnx/middleware/api_security.py
Normal file
145
backEnd/gnx/middleware/api_security.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""
|
||||
API Security Middleware
|
||||
Validates that API requests come from the frontend through nginx reverse proxy
|
||||
This ensures the API is only accessible from the frontend, not directly from the internet
|
||||
"""
|
||||
|
||||
from django.http import HttpResponseForbidden, JsonResponse
|
||||
from django.conf import settings
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
import logging
|
||||
import re
|
||||
|
||||
logger = logging.getLogger('django.security')
|
||||
|
||||
|
||||
class FrontendAPIProxyMiddleware(MiddlewareMixin):
|
||||
"""
|
||||
Enterprise Security: Only allow API requests from frontend through nginx
|
||||
|
||||
This middleware validates:
|
||||
1. Custom header (X-Internal-API-Key) that nginx adds to prove request came through proxy
|
||||
2. Origin/Referer header matches allowed frontend domains
|
||||
3. Request comes from internal network (nginx proxy)
|
||||
|
||||
In production, this ensures the API cannot be accessed directly from the internet.
|
||||
All requests must come through the frontend (nginx reverse proxy).
|
||||
"""
|
||||
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
super().__init__(get_response)
|
||||
|
||||
# Get API key from settings (nginx will add this header)
|
||||
self.required_api_key = getattr(settings, 'INTERNAL_API_KEY', None)
|
||||
|
||||
# Get allowed frontend origins
|
||||
self.allowed_origins = getattr(settings, 'CORS_ALLOWED_ORIGINS', [])
|
||||
|
||||
# Get allowed referer patterns
|
||||
self.allowed_referer_patterns = getattr(settings, 'ALLOWED_REFERER_PATTERNS', [])
|
||||
|
||||
# API paths that require validation (default: all /api/ paths)
|
||||
self.api_path_pattern = getattr(settings, 'API_SECURITY_PATH_PATTERN', r'^/api/')
|
||||
|
||||
# Skip validation in DEBUG mode for development
|
||||
self.enforce_in_debug = getattr(settings, 'ENFORCE_API_SECURITY_IN_DEBUG', False)
|
||||
|
||||
def process_request(self, request):
|
||||
"""
|
||||
Validate API requests come from frontend through nginx
|
||||
"""
|
||||
# Skip validation for non-API paths
|
||||
if not re.match(self.api_path_pattern, request.path):
|
||||
return None
|
||||
|
||||
# Skip validation in DEBUG mode unless explicitly enabled
|
||||
if settings.DEBUG and not self.enforce_in_debug:
|
||||
return None
|
||||
|
||||
# Skip validation for OPTIONS requests (CORS preflight)
|
||||
if request.method == 'OPTIONS':
|
||||
return None
|
||||
|
||||
# Get the internal API key from header (added by nginx)
|
||||
internal_api_key = request.META.get('HTTP_X_INTERNAL_API_KEY', '')
|
||||
|
||||
# Validate API key if configured
|
||||
if self.required_api_key:
|
||||
if internal_api_key != self.required_api_key:
|
||||
logger.warning(
|
||||
f"Blocked API request: Missing or invalid X-Internal-API-Key header. "
|
||||
f"Path: {request.path}, IP: {self._get_client_ip(request)}"
|
||||
)
|
||||
return JsonResponse(
|
||||
{
|
||||
'error': 'Access Denied',
|
||||
'message': 'This API is only accessible through the frontend application.',
|
||||
'code': 'API_ACCESS_DENIED'
|
||||
},
|
||||
status=403
|
||||
)
|
||||
|
||||
# Validate Origin header (for CORS requests)
|
||||
origin = request.META.get('HTTP_ORIGIN', '')
|
||||
if origin:
|
||||
if origin not in self.allowed_origins:
|
||||
logger.warning(
|
||||
f"Blocked API request: Invalid Origin header. "
|
||||
f"Origin: {origin}, Path: {request.path}, IP: {self._get_client_ip(request)}"
|
||||
)
|
||||
return JsonResponse(
|
||||
{
|
||||
'error': 'Access Denied',
|
||||
'message': 'Invalid origin. This API is only accessible from authorized domains.',
|
||||
'code': 'INVALID_ORIGIN'
|
||||
},
|
||||
status=403
|
||||
)
|
||||
|
||||
# Validate Referer header (for same-origin requests)
|
||||
referer = request.META.get('HTTP_REFERER', '')
|
||||
if referer and not origin: # Only check referer if no origin header
|
||||
is_valid_referer = False
|
||||
|
||||
# Check against allowed origins
|
||||
for allowed_origin in self.allowed_origins:
|
||||
if referer.startswith(allowed_origin):
|
||||
is_valid_referer = True
|
||||
break
|
||||
|
||||
# Check against referer patterns
|
||||
if not is_valid_referer and self.allowed_referer_patterns:
|
||||
for pattern in self.allowed_referer_patterns:
|
||||
if re.match(pattern, referer):
|
||||
is_valid_referer = True
|
||||
break
|
||||
|
||||
if not is_valid_referer:
|
||||
logger.warning(
|
||||
f"Blocked API request: Invalid Referer header. "
|
||||
f"Referer: {referer}, Path: {request.path}, IP: {self._get_client_ip(request)}"
|
||||
)
|
||||
return JsonResponse(
|
||||
{
|
||||
'error': 'Access Denied',
|
||||
'message': 'Invalid referer. This API is only accessible from authorized domains.',
|
||||
'code': 'INVALID_REFERER'
|
||||
},
|
||||
status=403
|
||||
)
|
||||
|
||||
# Additional validation: Check if request comes from nginx (internal network)
|
||||
# This is a secondary check - the IP whitelist middleware should handle this,
|
||||
# but we can add additional validation here if needed
|
||||
|
||||
# Request is valid, continue processing
|
||||
return None
|
||||
|
||||
def _get_client_ip(self, request):
|
||||
"""Get client IP address from request"""
|
||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
||||
if x_forwarded_for:
|
||||
return x_forwarded_for.split(',')[0].strip()
|
||||
return request.META.get('REMOTE_ADDR', 'unknown')
|
||||
|
||||
@@ -62,6 +62,7 @@ MIDDLEWARE = [
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'gnx.middleware.ip_whitelist.IPWhitelistMiddleware', # Production: Block external access
|
||||
'gnx.middleware.api_security.FrontendAPIProxyMiddleware', # Validate requests from frontend/nginx
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
@@ -151,7 +152,7 @@ CSRF_COOKIE_HTTPONLY = True
|
||||
CSRF_COOKIE_SAMESITE = 'Strict'
|
||||
CSRF_TRUSTED_ORIGINS = config(
|
||||
'CSRF_TRUSTED_ORIGINS',
|
||||
default='https://gnxsoft.com',
|
||||
default='https://gnxsoft.com,https://www.gnxsoft.com',
|
||||
cast=lambda v: [s.strip() for s in v.split(',')]
|
||||
)
|
||||
|
||||
@@ -229,7 +230,31 @@ REST_FRAMEWORK = {
|
||||
},
|
||||
}
|
||||
|
||||
# CORS Configuration
|
||||
# ============================================================================
|
||||
# API SECURITY CONFIGURATION
|
||||
# API is only accessible from frontend through nginx reverse proxy
|
||||
# ============================================================================
|
||||
|
||||
# Internal API Key - nginx will add this header to prove request came through proxy
|
||||
# Generate a strong random key: python -c "import secrets; print(secrets.token_urlsafe(32))"
|
||||
INTERNAL_API_KEY = config('INTERNAL_API_KEY', default='' if not DEBUG else 'dev-key-change-in-production')
|
||||
|
||||
# API security path pattern (default: all /api/ paths)
|
||||
API_SECURITY_PATH_PATTERN = config('API_SECURITY_PATH_PATTERN', default=r'^/api/')
|
||||
|
||||
# Enforce API security even in DEBUG mode (for testing)
|
||||
ENFORCE_API_SECURITY_IN_DEBUG = config('ENFORCE_API_SECURITY_IN_DEBUG', default=False, cast=bool)
|
||||
|
||||
# Allowed referer patterns (regex patterns for referer validation)
|
||||
ALLOWED_REFERER_PATTERNS = config(
|
||||
'ALLOWED_REFERER_PATTERNS',
|
||||
default='',
|
||||
cast=lambda v: [s.strip() for s in v.split(',') if s.strip()]
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# CORS Configuration - Strict origin validation
|
||||
# ============================================================================
|
||||
CORS_ALLOWED_ORIGINS = [
|
||||
"http://localhost:3000", # React development server
|
||||
"http://127.0.0.1:3000",
|
||||
@@ -237,14 +262,43 @@ CORS_ALLOWED_ORIGINS = [
|
||||
"http://127.0.0.1:3001",
|
||||
]
|
||||
|
||||
# Add production origins if configured
|
||||
PRODUCTION_ORIGINS = config('PRODUCTION_ORIGINS', default='', cast=lambda v: [s.strip() for s in v.split(',') if s.strip()])
|
||||
# Add production origins if configured (defaults to gnxsoft.com domains)
|
||||
PRODUCTION_ORIGINS = config(
|
||||
'PRODUCTION_ORIGINS',
|
||||
default='https://gnxsoft.com,https://www.gnxsoft.com',
|
||||
cast=lambda v: [s.strip() for s in v.split(',') if s.strip()]
|
||||
)
|
||||
if PRODUCTION_ORIGINS:
|
||||
CORS_ALLOWED_ORIGINS.extend(PRODUCTION_ORIGINS)
|
||||
|
||||
# Strict CORS configuration - only allow configured origins
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
CORS_ALLOW_ALL_ORIGINS = False # Never allow all origins, even in development
|
||||
CORS_ALLOWED_ORIGIN_REGEXES = [] # No regex patterns by default
|
||||
|
||||
CORS_ALLOW_ALL_ORIGINS = DEBUG # Only allow all origins in development
|
||||
# CORS allowed methods
|
||||
CORS_ALLOW_METHODS = [
|
||||
'DELETE',
|
||||
'GET',
|
||||
'OPTIONS',
|
||||
'PATCH',
|
||||
'POST',
|
||||
'PUT',
|
||||
]
|
||||
|
||||
# CORS allowed headers
|
||||
CORS_ALLOW_HEADERS = [
|
||||
'accept',
|
||||
'accept-encoding',
|
||||
'authorization',
|
||||
'content-type',
|
||||
'dnt',
|
||||
'origin',
|
||||
'user-agent',
|
||||
'x-csrftoken',
|
||||
'x-requested-with',
|
||||
'x-internal-api-key', # Custom header for nginx validation
|
||||
]
|
||||
|
||||
# Django URL configuration
|
||||
APPEND_SLASH = True
|
||||
|
||||
@@ -32835,3 +32835,45 @@ INFO 2025-11-24 06:12:16,803 basehttp 108393 126550263457472 "GET /api/services/
|
||||
INFO 2025-11-24 06:12:16,840 basehttp 108393 126550263457472 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:13:09,003 basehttp 108393 126550263457472 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:13:09,043 basehttp 108393 126550263457472 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:36:47,411 basehttp 108393 126550271850176 "GET /api/career/jobs HTTP/1.1" 200 4675
|
||||
INFO 2025-11-24 06:36:47,412 basehttp 108393 126550263457472 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:36:47,426 basehttp 108393 126550271850176 "GET /api/career/jobs HTTP/1.1" 200 4675
|
||||
INFO 2025-11-24 06:36:47,430 basehttp 108393 126550263457472 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:36:47,488 basehttp 108393 126550263457472 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:36:47,493 basehttp 108393 126550271850176 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:36:57,635 basehttp 108393 126550271850176 "OPTIONS /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 0
|
||||
INFO 2025-11-24 06:36:57,637 basehttp 108393 126550271850176 "OPTIONS /api/career/jobs HTTP/1.1" 200 0
|
||||
INFO 2025-11-24 06:36:57,658 basehttp 108393 126550263457472 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:36:57,668 basehttp 108393 126550263457472 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:36:57,693 basehttp 108393 126550271850176 "GET /api/career/jobs HTTP/1.1" 200 4675
|
||||
INFO 2025-11-24 06:37:09,801 basehttp 108393 126550263457472 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:37:09,806 basehttp 108393 126550632552128 "OPTIONS /api/home/banner/ HTTP/1.1" 200 0
|
||||
INFO 2025-11-24 06:37:09,807 basehttp 108393 126550623110848 "OPTIONS /api/case-studies/?ordering=display_order&page_size=5 HTTP/1.1" 200 0
|
||||
INFO 2025-11-24 06:37:09,826 basehttp 108393 126550649337536 "OPTIONS /api/blog/posts/latest/?limit=12 HTTP/1.1" 200 0
|
||||
INFO 2025-11-24 06:37:09,842 basehttp 108393 126550263457472 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:37:09,843 basehttp 108393 126550271850176 "GET /api/career/jobs HTTP/1.1" 200 4675
|
||||
INFO 2025-11-24 06:37:09,847 basehttp 108393 126550640944832 "GET /api/home/banner/ HTTP/1.1" 200 3438
|
||||
INFO 2025-11-24 06:37:09,863 basehttp 108393 126550632552128 "GET /api/case-studies/?ordering=display_order&page_size=5 HTTP/1.1" 200 4744
|
||||
INFO 2025-11-24 06:37:09,868 basehttp 108393 126550640944832 "GET /api/home/banner/ HTTP/1.1" 200 3438
|
||||
INFO 2025-11-24 06:37:09,878 basehttp 108393 126550271850176 "GET /api/career/jobs HTTP/1.1" 200 4675
|
||||
INFO 2025-11-24 06:37:09,898 basehttp 108393 126550263457472 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:37:09,909 basehttp 108393 126550623110848 "GET /api/blog/posts/latest/?limit=12 HTTP/1.1" 200 8374
|
||||
INFO 2025-11-24 06:37:09,915 basehttp 108393 126550632552128 "GET /api/case-studies/?ordering=display_order&page_size=5 HTTP/1.1" 200 4744
|
||||
INFO 2025-11-24 06:37:09,955 basehttp 108393 126550263457472 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:37:09,976 basehttp 108393 126550623110848 "GET /api/blog/posts/latest/?limit=12 HTTP/1.1" 200 8374
|
||||
INFO 2025-11-24 06:37:10,010 basehttp 108393 126550263457472 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:37:10,060 basehttp 108393 126550263457472 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
|
||||
INFO 2025-11-24 06:39:11,537 autoreload 108393 126550715764864 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
|
||||
INFO 2025-11-24 06:39:12,172 autoreload 139522 135175989801088 Watching for file changes with StatReloader
|
||||
INFO 2025-11-24 06:39:24,941 autoreload 139522 135175989801088 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
|
||||
INFO 2025-11-24 06:39:25,492 autoreload 139604 123745838387328 Watching for file changes with StatReloader
|
||||
INFO 2025-11-24 06:39:59,747 autoreload 139604 123745838387328 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
|
||||
INFO 2025-11-24 06:40:00,322 autoreload 139793 134765910855808 Watching for file changes with StatReloader
|
||||
INFO 2025-11-24 06:40:19,231 autoreload 139793 134765910855808 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
|
||||
INFO 2025-11-24 06:40:19,802 autoreload 139883 138604311842944 Watching for file changes with StatReloader
|
||||
INFO 2025-11-24 06:40:25,359 autoreload 139883 138604311842944 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
|
||||
INFO 2025-11-24 06:40:25,924 autoreload 139932 123776047718528 Watching for file changes with StatReloader
|
||||
INFO 2025-11-24 06:40:57,132 autoreload 139932 123776047718528 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
|
||||
INFO 2025-11-24 06:40:57,712 autoreload 140119 136966227431552 Watching for file changes with StatReloader
|
||||
INFO 2025-11-24 06:41:38,174 autoreload 140119 136966227431552 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
|
||||
INFO 2025-11-24 06:41:38,716 autoreload 140239 133780171337856 Watching for file changes with StatReloader
|
||||
|
||||
173
backEnd/nginx.conf.example
Normal file
173
backEnd/nginx.conf.example
Normal file
@@ -0,0 +1,173 @@
|
||||
# Nginx Configuration Example for GNX Web Application
|
||||
# This configuration shows how to set up nginx as a reverse proxy
|
||||
# to secure the Django API backend
|
||||
|
||||
# Generate a secure API key for INTERNAL_API_KEY:
|
||||
# python -c "import secrets; print(secrets.token_urlsafe(32))"
|
||||
# Add this key to your Django .env file as INTERNAL_API_KEY
|
||||
|
||||
upstream django_backend {
|
||||
# Django backend running on internal network only
|
||||
server 127.0.0.1:8000;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
upstream nextjs_frontend {
|
||||
# Next.js frontend
|
||||
server 127.0.0.1:3000;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
# Rate limiting zones
|
||||
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
|
||||
limit_req_zone $binary_remote_addr zone=general_limit:10m rate=30r/s;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name gnxsoft.com www.gnxsoft.com;
|
||||
|
||||
# Redirect HTTP to HTTPS
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name gnxsoft.com www.gnxsoft.com;
|
||||
|
||||
# SSL Configuration
|
||||
ssl_certificate /etc/letsencrypt/live/gnxsoft.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/gnxsoft.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
|
||||
# Security Headers
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
|
||||
|
||||
# Logging
|
||||
access_log /var/log/nginx/gnxsoft_access.log;
|
||||
error_log /var/log/nginx/gnxsoft_error.log;
|
||||
|
||||
# Client body size limit
|
||||
client_max_body_size 10M;
|
||||
|
||||
# API Endpoints - Proxy to Django backend
|
||||
# These requests will have the X-Internal-API-Key header added
|
||||
location /api/ {
|
||||
# Rate limiting
|
||||
limit_req zone=api_limit burst=20 nodelay;
|
||||
|
||||
# Add custom header to prove request came through nginx
|
||||
# This value must match INTERNAL_API_KEY in Django settings
|
||||
set $api_key "YOUR_SECURE_API_KEY_HERE";
|
||||
proxy_set_header X-Internal-API-Key $api_key;
|
||||
|
||||
# Standard proxy headers
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
|
||||
# Preserve original request headers
|
||||
proxy_set_header Origin $http_origin;
|
||||
proxy_set_header Referer $http_referer;
|
||||
|
||||
# Proxy settings
|
||||
proxy_pass http://django_backend;
|
||||
proxy_redirect off;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
# Timeouts
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
|
||||
# Buffering
|
||||
proxy_buffering on;
|
||||
proxy_buffer_size 4k;
|
||||
proxy_buffers 8 4k;
|
||||
proxy_busy_buffers_size 8k;
|
||||
}
|
||||
|
||||
# Media files - Serve from Django
|
||||
location /media/ {
|
||||
alias /path/to/gnx/backEnd/media/;
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# Static files - Serve from Django
|
||||
location /static/ {
|
||||
alias /path/to/gnx/backEnd/staticfiles/;
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# Admin panel - Only allow from specific IPs (optional)
|
||||
location /admin/ {
|
||||
# Uncomment to restrict admin access
|
||||
# allow 192.168.1.0/24;
|
||||
# allow 10.0.0.0/8;
|
||||
# deny all;
|
||||
|
||||
# Same proxy settings as /api/
|
||||
set $api_key "YOUR_SECURE_API_KEY_HERE";
|
||||
proxy_set_header X-Internal-API-Key $api_key;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_pass http://django_backend;
|
||||
proxy_redirect off;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
}
|
||||
|
||||
# Frontend - Proxy to Next.js
|
||||
location / {
|
||||
# Rate limiting
|
||||
limit_req zone=general_limit burst=50 nodelay;
|
||||
|
||||
# Proxy to Next.js frontend
|
||||
proxy_pass http://nextjs_frontend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# Timeouts
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
# Health check endpoint (optional)
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
|
||||
# Block direct access to backend port 8000 from external IPs
|
||||
# This should be done at the firewall level:
|
||||
# ufw deny 8000
|
||||
# iptables -A INPUT -p tcp --dport 8000 ! -s 127.0.0.1 -j DROP
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# Django Settings
|
||||
SECRET_KEY=your-super-secret-production-key-here
|
||||
DEBUG=False
|
||||
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com,your-server-ip
|
||||
ALLOWED_HOSTS=gnxsoft.com,www.gnxsoft.com,your-server-ip
|
||||
|
||||
# Database - Using SQLite (default)
|
||||
# SQLite is configured in settings.py - no DATABASE_URL needed
|
||||
@@ -37,9 +37,16 @@ SECURE_BROWSER_XSS_FILTER=True
|
||||
X_FRAME_OPTIONS=DENY
|
||||
|
||||
# CORS Settings (Production)
|
||||
CORS_ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com
|
||||
PRODUCTION_ORIGINS=https://gnxsoft.com,https://www.gnxsoft.com
|
||||
CORS_ALLOW_CREDENTIALS=True
|
||||
|
||||
# CSRF Trusted Origins
|
||||
CSRF_TRUSTED_ORIGINS=https://gnxsoft.com,https://www.gnxsoft.com
|
||||
|
||||
# API Security - Internal API Key (nginx will add this header)
|
||||
# Generate a secure key: python -c "import secrets; print(secrets.token_urlsafe(32))"
|
||||
INTERNAL_API_KEY=your-secure-api-key-here-change-this-in-production
|
||||
|
||||
# Static Files
|
||||
STATIC_ROOT=/var/www/gnx/staticfiles/
|
||||
MEDIA_ROOT=/var/www/gnx/media/
|
||||
|
||||
Reference in New Issue
Block a user