23 β Settings, env va xavfsizlik¶
β¬ οΈ Oldingi: 22 β Testlash Β· π README Β· Keyingi: 24 β Deployment (production) β‘οΈ
Bu bobda: ilovangizni production'ga chiqarishdan oldin uni xavfsiz qilishni o'rganamiz. Avval settings'ni bo'lishni ko'ramiz: bitta katta
settings.pyo'rnigabase.py(umumiy) +dev.py(ishlab chiqish) +prod.py(production) paketini tuzamiz vaDJANGO_SETTINGS_MODULEqaysi birini yuklashini boshqaramiz. Keyin eng muhim qoidaga o'tamiz: maxfiy ma'lumotlar (secrets) kod ichida saqlanmaydi βSECRET_KEY, parollar, API kalitlar muhit o'zgaruvchilari (environment variables) va.envfayl orqali keladi (django-environqanday ishlashini tushunamiz va xuddi shunday qiluvchi oddiy yordamchini o'zimiz yozamiz). So'ng production uchun uchta hayotiy-muhim sozlamani sozlaymiz:SECRET_KEY(uzun, tasodifiy, maxfiy),DEBUG=False(xato sahifasi sirlarni oshkor qilmasligi uchun),ALLOWED_HOSTS(Host header hujumiga qarshi). Keyin xavfsizlik qatlamlarini bosib o'tamiz: HTTPS/HSTS (SECURE_SSL_REDIRECT,SECURE_HSTS_SECONDS), xavfsiz cookie'lar (SESSION_COOKIE_SECURE,CSRF_COOKIE_SECURE), XSS (Django'ning avtomatik escape'i), clickjacking (X-Frame-Options), CSRF va CORS farqi. Hammasini yakuniy qurol βmanage.py check --deploybilan tekshiramiz, u sizning sozlamalaringizdagi xavfsizlik teshiklarini ro'yxatlab beradi. Oxirida keng tarqalgan xatolarni β DEBUG'ni yoqib qoldirish, secret'ni git'ga tushirish, ALLOWED_HOSTS'ni["*"]qilish β ko'rib chiqamiz. Bu bob Python'ni bilishni nazarda tutadi (Python qo'llanma); maxfiy ma'lumotlarni git'dan chetlatish Git qo'llanma bilan bog'liq; baza secret'lari SQL qo'llanma bilan. Hamma kod Django 6.0.6 (Python 3.14) da haqiqatan ishga tushirib tekshirilgan (baza uchun SQLite; HTTPS/gunicorn/nginx talab qiladigan bloklar illustrativ deb belgilangan).
Nega bu bob alohida ahamiyatga ega?¶
Avvalgi boblarda biz "ishlaydigan" kod yozdik. Bu bobda biz xavfsiz kod yozamiz. Farqi katta: noto'g'ri sozlangan Django ilovasi mukammal ishlaydi β toki birov uni buzguncha.
Eng achchiq haqiqat: production'dagi xavfsizlik buzilishlarining ko'pchiligi murakkab hujumlardan emas, balki oddiy konfiguratsiya xatolaridan kelib chiqadi:
DEBUG = Trueqoldirilgan β xato sahifasiSECRET_KEY'ni, baza parolini, fayl yo'llarini ko'rsatadi.SECRET_KEYto'g'ridan-to'g'risettings.pyichida β git tarixiga tushgan, GitHub'da hamma ko'rgan.ALLOWED_HOSTS = ["*"]β Host header hujumiga ochiq.- HTTPS yo'q β parollar ochiq matnda uzatiladi.
Yaxshi xabar: Django bularning hammasini oldindan ko'zda tutgan va sizga tayyor himoya qatlamlari beradi. Sizning vazifangiz β ularni to'g'ri yoqish. Bu bob aynan shu haqida.
Oltin qoida: Ishlab chiqishda qulaylik uchun yoqilgan narsalar (
DEBUG=True, ochiq xato sahifasi) production'da xavfli. Shuning uchun ikki muhitni alohida sozlash kerak. Undan boshlaymiz.
Settings'ni bo'lish: base / dev / prod¶
Yangi loyihada bitta config/settings.py bo'ladi. Muammo: ishlab chiqishda DEBUG=True, production'da DEBUG=False kerak. Agar bitta faylda if'lar bilan boshqarsak, fayl chalkashadi va xato qilish oson bo'ladi.
Yechim β settings.py faylini paketga (papkaga) aylantirish va uchta faylga bo'lish:
base.pyβ ikkala muhit uchun bir xil narsalar:INSTALLED_APPS,MIDDLEWARE,TEMPLATES,AUTH_PASSWORD_VALIDATORS.dev.pyβfrom .base import *qiladi, keyin faqat ishlab chiqishga xos narsalarni o'zgartiradi (DEBUG=True).prod.pyβ xuddi shunday, lekin xavfsizlikni yoqadi (DEBUG=False,SECURE_*).
Tuzilma quyidagicha:
base.py β umumiy poydevor¶
base.py ichida muhitga bog'liq bo'lmagan hamma narsa. E'tibor bering: BASE_DIR endi uch marta .parent qiladi, chunki fayl bir qavat chuqurroqda (config/settings/base.py):
# config/settings/base.py
"""Umumiy (dev/prod uchun bir xil) sozlamalar."""
from pathlib import Path
# config/settings/base.py -> uch qavat yuqoriga = loyiha ildizi
BASE_DIR = Path(__file__).resolve().parent.parent.parent
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"core",
]
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",
]
ROOT_URLCONF = "config.urls"
WSGI_APPLICATION = "config.wsgi.application"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
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 = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
dev.py β ishlab chiqish¶
dev.py base'dan hamma narsani oladi, keyin faqat dev'ga xos sozlamalarni qo'shadi:
# config/settings/dev.py
"""Ishlab chiqish (development) sozlamalari."""
from .base import * # noqa: F401,F403
DEBUG = True
SECRET_KEY = "django-insecure-dev-faqat-mahalliy"
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
# Email'ni jo'natmaymiz, konsolga chiqaramiz
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
# noqa β bu linter'ga "import * haqida ogohlantirma" deyish; bu yerda import * ataylab, chunki base'dagi hamma sozlamani olib kelyapmiz.
prod.py β production¶
prod.py xavfsizlikni yoqadi. Maxfiy qiymatlar (SECRET_KEY) bu yerda yozilmaydi β keyingi bo'limda ko'radigan muhit o'zgaruvchilaridan keladi:
# config/settings/prod.py
"""Production sozlamalari β xavfsizlik yoqilgan."""
from .base import * # noqa: F401,F403
DEBUG = False
# HTTPS va xavfsizlik (keyingi bo'limlarda tushuntiriladi)
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000 # 1 yil
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_CONTENT_TYPE_NOSNIFF = True
Qaysi settings yuklanadi? β DJANGO_SETTINGS_MODULE¶
Django qaysi sozlama faylini ishlatishni DJANGO_SETTINGS_MODULE muhit o'zgaruvchisidan biladi. manage.py, wsgi.py va asgi.py uni o'rnatadi. Standartni dev'ga qo'yamiz:
# manage.py (faqat o'zgargan qator)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.dev")
# config/wsgi.py va config/asgi.py da ham xuddi shunday
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.dev")
setdefault β agar muhitda allaqachon o'rnatilgan bo'lsa, ustidan yozmaydi. Demak production serverda DJANGO_SETTINGS_MODULE=config.settings.prod qo'yib, kodga tegmasdan prod'ga o'tasiz:
# ishlab chiqish (default dev):
python manage.py runserver
# production sozlamalar bilan tekshirish:
# Linux/macOS:
DJANGO_SETTINGS_MODULE=config.settings.prod python manage.py check
# Windows PowerShell:
$env:DJANGO_SETTINGS_MODULE="config.settings.prod"; python manage.py check
Yoki har safar yozmaslik uchun --settings flagidan foydalaning:
Bu tuzilma haqiqatan ishlaganini tasdiqlash uchun check ikkala muhitda ham ishlatildi: dev'da System check identified no issues, prod'da esa (kerakli muhit o'zgaruvchilari berilgach) yana no issues.
Maxfiy ma'lumotlar: muhit o'zgaruvchilari va .env¶
Endi eng muhim qoida. Maxfiy ma'lumotlar kod ichida bo'lmasligi kerak. Buni nima deymiz: SECRET_KEY, baza paroli, API kalitlar, tashqi servis tokenlari.
Nega? Chunki kod git'ga tushadi. Agar secret kodda bo'lsa:
- U git tarixiga tushadi β keyin o'chirsangiz ham, eski commitlarda qoladi.
- Repozitoriy hamkasblar, CI serverlar, ehtimol GitHub'da hammaga ko'rinadi.
- Secret'ni almashtirish endi qiyin: butun jamoa, hamma deploy yangilanishi kerak.
Yechim β secret'lar muhit o'zgaruvchilaridan (environment variables) o'qiladi. Kod faqat "shu nomli o'zgaruvchini ber" deydi, qiymatning o'zi koddan tashqarida:
.env fayl nima va django-environ qanday ishlaydi¶
Mahalliy ishlab chiqishda har safar muhit o'zgaruvchilarini qo'lda eksport qilish zerikarli. Shuning uchun ularni .env deb nomlangan oddiy faylga yozamiz:
# .env β bu fayl GIT'GA TUSHMASLIGI kerak!
DJANGO_SECRET_KEY=fayldan-kelgan-juda-uzun-tasodifiy-maxfiy-kalit
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=example.com,api.example.com
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
Mashhur django-environ kutubxonasi shu faylni o'qib, qiymatlarni os.environ'ga yuklaydi va qulay konvertorlar beradi (env.bool, env.list, env.db). Uning tushunchasi quyidagicha (haqiqiy django-environ kodi):
# django-environ uslubi (ILLUSTRATIV - kutubxona o'rnatilishi kerak)
import environ
env = environ.Env(
DEBUG=(bool, False), # (tur, default)
)
environ.Env.read_env(BASE_DIR / ".env") # .env ni o'qib os.environ'ga yuklaydi
SECRET_KEY = env("DJANGO_SECRET_KEY")
DEBUG = env("DJANGO_DEBUG") # avtomatik bool'ga aylantiriladi
ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS")
O'zimiz yozamiz: tashqi kutubxonasiz env o'qish¶
django-environ ichida sehr yo'q β u shunchaki .env'ni o'qib, satrlarni kerakli turga aylantiradi. Buni o'zimiz ham qila olamiz (hech narsa o'rnatmasdan). Quyidagi kod haqiqatan ishlaydi va base.py uchun mos:
# config/settings/base.py (yuqoriga qo'shiladi)
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
def load_env(path):
""".env faylini o'qib, qiymatlarni os.environ'ga yuklaydi.
Mavjud muhit o'zgaruvchisini ustidan yozmaydi (setdefault mantiqi).
"""
p = Path(path)
if not p.exists():
return
for line in p.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, _, value = line.partition("=")
os.environ.setdefault(key.strip(), value.strip())
def env(key, default=None):
"""Muhit o'zgaruvchisini satr sifatida o'qiydi."""
return os.environ.get(key, default)
def env_bool(key, default=False):
"""'True'/'1'/'yes'/'on' -> True; aks holda False."""
val = os.environ.get(key)
if val is None:
return default
return val.strip().lower() in ("1", "true", "yes", "on")
def env_list(key, default=None):
"""Vergul bilan ajratilgan satrni ro'yxatga aylantiradi."""
val = os.environ.get(key)
if not val:
return default or []
return [item.strip() for item in val.split(",") if item.strip()]
# .env faylni yuklaymiz (production'da fayl bo'lmasligi mumkin - muammo emas)
load_env(BASE_DIR / ".env")
SECRET_KEY = env("DJANGO_SECRET_KEY", "dev-faqat-mahalliy-uchun-xavfsiz-emas")
DEBUG = env_bool("DJANGO_DEBUG", False)
ALLOWED_HOSTS = env_list("DJANGO_ALLOWED_HOSTS", [])
Bu yordamchilar haqiqatan tekshirildi β .env faylidan DJANGO_DEBUG=True, DJANGO_ALLOWED_HOSTS=example.com,api.example.com va DATABASE_URL=... to'g'ri o'qildi (izoh qatorlari # va bo'sh satrlar e'tiborga olinmadi).
Production'da .env yo'q¶
Muhim nuance: .env fayl faqat mahalliy qulaylik uchun. Production serverda ko'pincha .env fayl bo'lmaydi β server boshqaruv paneli (Heroku, Railway, systemd, Docker, CI/CD) muhit o'zgaruvchilarini to'g'ridan-to'g'ri beradi. Kodimiz ikkalasida ham ishlaydi, chunki u faqat os.environ'dan o'qiydi; .env shunchaki o'sha os.environ'ni to'ldirishning bir usuli.
.env ni git'dan chetlatish¶
Bu hayotiy-muhim qadam. .gitignore'ga qo'shing:
Va jamoaga namuna sifatida .env.example (maxfiy qiymatlarsiz) qoldiring β bu git'ga tushadi:
# .env.example (bu git'ga tushadi - faqat tuzilma, qiymat yo'q)
DJANGO_SECRET_KEY=
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=
DATABASE_URL=
Git'da secret'larni boshqarish bo'yicha batafsil: Git va GitHub qo'llanma.
SECRET_KEY: uzun, tasodifiy, maxfiy¶
SECRET_KEY β Django'ning kriptografik imzolar uchun ishlatadigan asosiy kaliti. U bilan:
- Sessiya ma'lumotlari imzolanadi (cookie ichidagi
sessionid). - CSRF tokenlar yaratiladi.
- Parolni tiklash linklari imzolanadi.
signingmoduli (xavfsiz tokenlar) ishlaydi.
Agar hujumchi SECRET_KEY'ni bilsa, u soxta sessiya yarata oladi (boshqa foydalanuvchi sifatida kira oladi), CSRF himoyasini chetlab o'ta oladi. Shuning uchun u uzun, tasodifiy va maxfiy bo'lishi shart.
Yangi kalit yaratish¶
Django'da tayyor funksiya bor β u 50 belgili tasodifiy kalit beradi:
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
Tekshirildi: bu buyruq aynan 50 belgili kalit chiqaradi (masalan =21+jen*5mc_3(8rn-0q...). Bu qiymatni .env faylga (yoki server muhit o'zgaruvchisiga) yozasiz, koddagi joyga emas.
Xato va to'g'ri yondashuv¶
# config/settings/prod.py
# XATO - kalit kodda, git'ga tushadi, hammaga ko'rinadi
SECRET_KEY = "django-insecure-z(l4kt2$hi7g%oj2=4l$%8mxe2hq=ruj^02l_nbb6x4iew-4qc" # noqa
# TO'G'RI - muhit o'zgaruvchisidan
SECRET_KEY = env("DJANGO_SECRET_KEY")
Production'da SECRET_KEY majburiy bo'lishi kerak β agar o'rnatilmagan bo'lsa, ilova umuman ishga tushmasligi kerak (jim turib default ishlatishdan ko'ra, baland ovozda yiqilgani yaxshi):
# config/settings/prod.py
from django.core.exceptions import ImproperlyConfigured
def env_required(key):
"""Production'da majburiy o'zgaruvchi: yo'q bo'lsa darhol xato."""
try:
import os
return os.environ[key]
except KeyError:
raise ImproperlyConfigured(f"Muhit o'zgaruvchisi o'rnatilmagan: {key}")
SECRET_KEY = env_required("DJANGO_SECRET_KEY")
Tekshirildi: env_required("YOQ_OZGARUVCHI") aniq ImproperlyConfigured: Muhit o'zgaruvchisi o'rnatilmagan: YOQ_OZGARUVCHI xatosini berdi; mavjud o'zgaruvchida esa qiymatni qaytardi.
Maslahat: kalit oshkor bo'lib qolsa (masalan, xato bilan git'ga tushib ketdi) β uni darhol almashtiring. Eski kalit bilan imzolangan hamma sessiya va token bekor bo'ladi (foydalanuvchilar qayta kiradi), lekin bu xavfsizlik uchun arzon narx.
DEBUG=False: production'da majburiy¶
DEBUG=True ishlab chiqishda ajoyib: xato yuz berganda Django to'liq batafsil sahifani ko'rsatadi β traceback, lokal o'zgaruvchilar, sozlamalar, SQL so'rovlar. Bu debug qilishni osonlashtiradi.
Aynan shu sabab production'da u xavfli. O'sha batafsil xato sahifasi quyidagilarni hujumchiga ko'rsatadi:
SECRET_KEY(sozlamalar bo'limida).- Baza ulanish ma'lumotlari (parol bundan mustasno emas).
- Fayl yo'llari, kutubxona versiyalari, server tuzilishi.
- Lokal o'zgaruvchilardagi maxfiy qiymatlar.
Shuning uchun production'da doimo:
DEBUG=False bo'lganda Django xato sahifasi o'rniga oddiy "Server Error (500)" sahifasini ko'rsatadi (siz uni 500.html template bilan moslashtirishingiz mumkin). Batafsil xato esa logga yoziladi β foydalanuvchiga emas.
Muhim juftlik:
DEBUG=Falseqilganingizda,ALLOWED_HOSTSham to'ldirilishi shart (keyingi bo'lim) β aks holda Django umuman so'rov qabul qilmaydi.
DEBUG=False bilan yana bir nuance: statik fayllar endi Django tomonidan xizmat qilinmaydi (runserver ham). Production'da collectstatic + nginx/WhiteNoise kerak β bu keyingi bobning mavzusi.
ALLOWED_HOSTS: Host header hujumiga qarshi¶
ALLOWED_HOSTS β sizning saytingiz qaysi domen nomlari orqali xizmat qilishini Django'ga aytadigan ro'yxat. Brauzer har so'rovda Host: headerini yuboradi (Host: example.com). Django kelgan Host'ni shu ro'yxat bilan solishtiradi:
- Ro'yxatdagi domen bo'lsa β so'rov qabul qilinadi.
- Notanish domen bo'lsa β 400 Bad Request qaytaradi, view'ga umuman bormaydi.
Nega bu kerak? Host header hujumi uchun. Agar Django istalgan Host'ni qabul qilsa, hujumchi soxta Host yuborib, masalan parolni tiklash linkini o'z saytiga yo'naltira oladi (chunki Django ba'zi linklarni request.get_host()'dan quradi). Demak ALLOWED_HOSTS β bu "men faqat shu domenlar uchun ishlayman" degan kafolat.
# config/settings/prod.py
ALLOWED_HOSTS = env_list("DJANGO_ALLOWED_HOSTS", [])
# .env: DJANGO_ALLOWED_HOSTS=example.com,www.example.com
Bu xulq haqiqatan tekshirildi. ALLOWED_HOSTS=["example.com"] va DEBUG=False bo'lganda:
# tekshiruv natijasi (Django test Client orqali):
client.get("/", HTTP_HOST="yovuz-sayt.com") # -> status 400 (rad etildi)
client.get("/", HTTP_HOST="example.com") # -> status 200 (qabul qilindi)
β Eng tarqalgan xato: ALLOWED_HOSTS = ["*"]¶
Bu "400 xatoni tezda yo'qotaman" deb qilinadi, lekin u himoyani butunlay o'chiradi. To'g'ri yo'l β aniq domenlarni sanab chiqish. Agar domen oldindan noma'lum bo'lsa (masalan, ko'p subdomenli SaaS), aniq subdomen naqshlarini yozing: [".example.com"] (boshidagi nuqta β barcha subdomenlar).
DEBUG=True nuance: ishlab chiqishda
DEBUG=Truebo'lsa vaALLOWED_HOSTSbo'sh bo'lsa, Django avtomatiklocalhost,127.0.0.1va[::1]'ni qabul qiladi. Shuning uchun dev'da bu sozlama bilan kamdan-kam yuzma-yuz kelasiz β muammo aynan prod'da chiqadi.
Xavfsizlik qatlamlari: HTTPS, HSTS va xavfsiz cookie'lar¶
Endi production xavfsizligining yuragi. Django'ning SecurityMiddleware'i bir nechta himoyani boshqaradi β siz faqat sozlamalarni yoqasiz.
HTTPS ga majburlash¶
HTTP β ochiq matn. Parol, cookie, hamma narsa tarmoqda o'qilishi mumkin. HTTPS (TLS) buni shifrlaydi. Production'da doimo HTTPS ishlatiladi.
# config/settings/prod.py
# HTTP bilan kelgan so'rovni avtomatik HTTPS'ga (https://...) yo'naltiradi
SECURE_SSL_REDIRECT = True
Agar Django reverse-proxy (nginx) ortida tursa, proxy HTTPS'ni "tugatadi" va Django'ga HTTP yuboradi. Django HTTPS ekanini bilishi uchun proxy header'iga ishonadi:
# config/settings/prod.py - ILLUSTRATIV: nginx/proxy shu headerni qo'yishi kerak
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
Bu sozlamani faqat proxy haqiqatan
X-Forwarded-Proto'ni o'rnatishiga ishonchingiz komil bo'lsa yoqing β aks holda hujumchi soxta header yuborib aldashi mumkin.
HSTS: brauzerga "doimo HTTPS" deyish¶
HSTS (HTTP Strict Transport Security) β brauzerga "shu saytga endi faqat HTTPS bilan kel, hatto manzilni http:// bilan tersam ham" deydigan header. Bu birinchi so'rovdagi "downgrade" hujumini oldini oladi.
# config/settings/prod.py
SECURE_HSTS_SECONDS = 31536000 # 1 yil davomida eslab qol
SECURE_HSTS_INCLUDE_SUBDOMAINS = True # subdomenlarga ham tatbiq et
SECURE_HSTS_PRELOAD = True # brauzer preload ro'yxatiga kirish uchun
Ehtiyot bo'ling: HSTS qaytarib bo'lmaydigan ta'sirga ega β agar
includeSubDomainsyoqib, keyin biror subdomen faqat HTTP bilan ishlasa, u brauzerlarda yetib bo'lmas holga keladi (HSTS muddati tugaguncha). Avval kichikSECURE_HSTS_SECONDS(masalan 3600) bilan sinab ko'ring, ishonch hosil qilgach oshiring.
Xavfsiz cookie'lar¶
Sessiya va CSRF cookie'lari faqat HTTPS orqali yuborilishini ta'minlang β aks holda HTTP'da ular o'g'irlanishi mumkin:
# config/settings/prod.py
SESSION_COOKIE_SECURE = True # sessionid cookie faqat HTTPS'da
CSRF_COOKIE_SECURE = True # csrftoken cookie faqat HTTPS'da
SECURE_CONTENT_TYPE_NOSNIFF = True # X-Content-Type-Options: nosniff
SECURE_CONTENT_TYPE_NOSNIFF=True β brauzerga "Content-Type'ni o'zing taxmin qilma, men aytganini ishlat" deydi (MIME-sniffing hujumini oldini oladi). Tekshirildi: bu yoqilganda javobda X-Content-Type-Options: nosniff headeri paydo bo'ldi.
Hamma bularning to'g'riligi tasdiqlandi: prod sozlamalar bilan, kerakli muhit o'zgaruvchilari berilgach (DJANGO_SECRET_KEY, DJANGO_ALLOWED_HOSTS, DJANGO_DEBUG=False), python manage.py check --deploy aynan System check identified no issues natijasini berdi.
XSS, clickjacking, CSRF va CORS¶
Bu to'rtta atama tez-tez chalkashtiriladi. Har birini ajratamiz β Django ko'pchiligini avtomatik himoya qiladi.
XSS β Cross-Site Scripting¶
XSS β hujumchi sizning sahifangizga zararli JavaScript kiritishi (masalan, izoh maydoni orqali <script>...</script> yuborib). Django shablon tizimi buni avtomatik himoya qiladi: template'da chiqarilgan har qanday o'zgaruvchi autoescape qilinadi β <, >, &, ", ' belgilari HTML-entity'ga aylanadi:
<!-- template: foydalanuvchi izohi xavfsiz chiqariladi -->
<p>{{ izoh }}</p>
<!-- agar izoh = "<script>alert(1)</script>" bo'lsa, brauzer
uni kod sifatida emas, MATN sifatida ko'rsatadi:
<script>alert(1)</script> -->
Autoescape'ni faqat ishonchli HTML uchun, ataylab |safe filtri bilan o'chirasiz β va bu yerda ehtiyot bo'ling:
<!-- β XAVFLI: agar matn foydalanuvchidan kelsa, XSS teshigi ochiladi -->
<p>{{ foydalanuvchi_matni|safe }}</p>
Autoescape mexanizmi avvalroq batafsil ko'rilgan (04-bob, Template tizimi).
Clickjacking β X-Frame-Options¶
Clickjacking β hujumchi sizning saytingizni ko'rinmas <iframe> ichiga joylab, foydalanuvchini "boshqa narsani bosaman" deb aldab, aslida sizning saytingizdagi tugmani bostirishi. Django XFrameOptionsMiddleware bilan buni to'sadi β har javobga X-Frame-Options: DENY headerini qo'yadi (sahifa hech qanday frame ichida ko'rsatilmaydi):
# base.py MIDDLEWARE ichida allaqachon bor:
# "django.middleware.clickjacking.XFrameOptionsMiddleware"
# Default: DENY. Agar o'z saytingiz frame'iga ruxsat kerak bo'lsa:
X_FRAME_OPTIONS = "SAMEORIGIN"
Tekshirildi: oddiy view'ga so'rov yuborilganda javobda X-Frame-Options: DENY headeri haqiqatan mavjud edi.
CSRF β Cross-Site Request Forgery¶
CSRF β hujumchi boshqa saytdan foydalanuvchi nomidan sizning saytingizga yashirin POST yuborishi (masalan, "pul o'tkaz" formasini). Django CsrfViewMiddleware bilan himoya qiladi: har bir o'zgartiruvchi so'rov (POST/PUT/DELETE) yashirin CSRF token olib kelishi shart. Token yo'q bo'lsa β 403 Forbidden:
<!-- formada token shart -->
<form method="post">
{% csrf_token %}
<input name="nom">
<button>Yuborish</button>
</form>
Tekshirildi (CSRF tekshiruvi yoqilgan test Client bilan):
# POST token'siz -> 403 Forbidden
client.post("/forma/", {"nom": "Ali"}) # -> status 403
# GET xavfsiz, tekshirilmaydi -> 200
client.get("/forma/") # -> status 200
DRF API'larida JWT/Token auth ishlatilsa, CSRF odatda ta'sir qilmaydi (token header'da keladi, cookie'da emas) β bu 17-bobda ko'rilgan.
CORS β Cross-Origin Resource Sharing¶
CORS β CSRF emas, ko'pincha shu bilan chalkashtiriladi. CORS β brauzerning qoidasi: boshqa origindagi (boshqa domen/port) JavaScript sizning API'ngizga so'rov yubora oladimi yo'qmi. Buni server ruxsat berish uchun ishlatadi (masalan, app.example.com frontendi api.example.com backendiga murojaat qilishi uchun).
Django'da CORS o'rnatilgan emas β django-cors-headers kutubxonasi ishlatiladi:
# ILLUSTRATIV: pip install django-cors-headers kerak
INSTALLED_APPS += ["corsheaders"]
MIDDLEWARE = ["corsheaders.middleware.CorsMiddleware"] + MIDDLEWARE # eng yuqorida
CORS_ALLOWED_ORIGINS = [
"https://app.example.com",
]
Qisqacha farq:
- CSRF β sizning saytingizdan kelgan yashirin so'rovlarni soxtalashtirishdan himoya (server tomonida token).
- CORS β boshqa origin'dan kelgan brauzer-so'rovlariga ruxsat berish mexanizmi (server ruxsat headerini qo'yadi).
- CSRF Trusted Origins β bu uchinchisi:
DEBUG=Falseva HTTPS'da, boshqa subdomendan POST qilinganda CSRF'ni qabul qilish uchun:
# config/settings/prod.py
CSRF_TRUSTED_ORIGINS = ["https://www.example.com", "https://app.example.com"]
manage.py check --deploy: yakuniy tekshiruv¶
Django'da production'ga chiqishdan oldin ishlatadigan tayyor xavfsizlik tekshiruvi bor:
U sizning sozlamalaringizni skanerlaydi va xavfsizlik ogohlantirishlari ro'yxatini beradi. Har bir ogohlantirishning kodi (security.W00X) bor.
dev sozlamalar bilan ishga tushirsangiz (DEBUG=True, secret kodda), aynan quyidagi ogohlantirishlar chiqadi (haqiqiy natija):
System check identified some issues:
WARNINGS:
?: (security.W004) You have not set a value for the SECURE_HSTS_SECONDS setting...
?: (security.W008) Your SECURE_SSL_REDIRECT setting is not set to True...
?: (security.W009) Your SECRET_KEY has less than 50 characters... or it's prefixed with 'django-insecure-'...
?: (security.W012) SESSION_COOKIE_SECURE is not set to True...
?: (security.W016) ... you have not set CSRF_COOKIE_SECURE to True...
?: (security.W018) You should not have DEBUG set to True in deployment.
System check identified 6 issues (0 silenced).
Bu ro'yxat aynan biz bu bobda yoqgan sozlamalarga mos keladi. Endi prod sozlamalar bilan, to'g'ri muhit o'zgaruvchilari berib ishlatamiz:
# Linux/macOS:
DJANGO_SETTINGS_MODULE=config.settings.prod \
DJANGO_SECRET_KEY="haqiqiy-uzun-tasodifiy-50-belgili-kalit-..." \
DJANGO_DEBUG=False \
DJANGO_ALLOWED_HOSTS="example.com,www.example.com" \
python manage.py check --deploy
# Windows PowerShell:
$env:DJANGO_SETTINGS_MODULE="config.settings.prod"
$env:DJANGO_SECRET_KEY="haqiqiy-uzun-tasodifiy-50-belgili-kalit-..."
$env:DJANGO_DEBUG="False"
$env:DJANGO_ALLOWED_HOSTS="example.com,www.example.com"
python manage.py check --deploy
Natija (tekshirildi):
CI/CD'ga qo'shing:
check --deploy'ni deploy quvuringizning bir qadami qiling β agar yangi ogohlantirish chiqsa, deploy to'xtasin. Bu xavfsizlik regressiyalarini avtomatik ushlaydi. Git/CI qo'llanma.
Keng tarqalgan xatolar¶
Yangi (va tajribali) dasturchilar tushadigan tuzoqlar β har birini ataylab xato (β) va to'g'ri (β ) variant bilan ko'rsatamiz.
1. DEBUG'ni production'da yoqib qoldirish.
# β Eng xavfli xato - xato sahifasi SECRET_KEY va baza parolini ko'rsatadi
DEBUG = True
# β
Muhit o'zgaruvchisiga bog'lang, default False
DEBUG = env_bool("DJANGO_DEBUG", False)
2. SECRET_KEY'ni kodga yozish.
# β git tarixiga abadiy tushadi
SECRET_KEY = "django-insecure-..."
# β
muhitdan, production'da majburiy
SECRET_KEY = env_required("DJANGO_SECRET_KEY")
3. ALLOWED_HOSTS = ["*"] β Host header himoyasini o'chiradi (yuqorida ko'rilgan). Aniq domenlarni sanang.
4. .env'ni git'ga qo'shib qo'yish. Eng birinchi commitdan oldin .gitignore'ga .env qo'shing. Agar allaqachon tushib ketgan bo'lsa β secret'ni almashtiring (faqat git'dan o'chirish yetarli emas, tarixda qoladi).
5. Settings'ni import bilan emas, nusxa bilan bo'lish.
# β prod.py base.py ni nusxaladi - base o'zgarsa, prod eskirib qoladi
# (butun INSTALLED_APPS, MIDDLEWARE qayta yozilgan)
# β
from .base import * - bitta manba haqiqati
from .base import * # noqa
6. CSRF_TRUSTED_ORIGINS'siz subdomenlar aro POST. DEBUG=False'da boshqa subdomendan forma yuborilsa CSRF 403 beradi β kerakli origin'larni CSRF_TRUSTED_ORIGINS'ga qo'shing.
7. SECURE_PROXY_SSL_HEADER'ni proxy ishonchsiz holda yoqish β agar proxy X-Forwarded-Proto'ni majburan o'rnatmasa, hujumchi soxta header bilan Django'ni "HTTPS" deb aldashi mumkin.
8. check --deploy'ni unutish. Deploy'dan oldin doimo ishlating β u yuqoridagi ko'p xatolarni avtomatik aytib beradi.
Solishtirma: Node.js'da maxfiy ma'lumotlar odatda
dotenvpaketi vaprocess.envorqali boshqariladi β Django'dagi.env+os.environg'oyasi aynan o'sha (Node.js qo'llanma). Asosiy farq: Django sizgacheck --deploykabi tayyor xavfsizlik auditi vaSECURE_*sozlamalarini bir joyda beradi.
Mashqlar¶
Oson¶
-
Yangi loyihada
config/settings.pyfayliniconfig/settings/paketiga aylantiring:__init__.py,base.py,dev.py,prod.pyyarating.manage.py'daDJANGO_SETTINGS_MODULE'niconfig.settings.devga o'zgartiring. -
python -cbuyrug'i bilan yangiSECRET_KEYyarating va uning uzunligi 50 ekanini tasdiqlang. -
dev.py'daDEBUG=True,ALLOWED_HOSTS=["localhost", "127.0.0.1"]qiling.python manage.py checkishlashini tekshiring. -
.gitignorefayl yarating va unga.env,db.sqlite3,__pycache__/qatorlarini qo'shing. -
env_boolfunksiyasini yozing:"True","1","yes","on"(registrga befarq) uchunTrue, qolganlari uchunFalseqaytarsin. UniDJANGO_DEBUGo'zgaruvchisida sinab ko'ring. -
devsozlamalar bilanpython manage.py check --deployishlating va chiqqan ogohlantirishlar kodlarini (W004, W008, ...) sanang.
O'rta¶
-
env,env_bool,env_listyordamchilarinibase.py'da yozing.SECRET_KEY,DEBUG,ALLOWED_HOSTS'ni muhit o'zgaruvchilaridan o'qing (default'lar bilan). -
load_env(path)funksiyasini yozing:.envfaylini o'qib, izoh (#) va bo'sh qatorlarni o'tkazib yuborib, qolganlarinios.environ'gasetdefaultbilan yuklasin. -
prod.pyyozing:DEBUG=False, hammaSECURE_*(SSL_REDIRECT, HSTS, cookie secure, nosniff) yoqilgan. To'g'ri muhit o'zgaruvchilari bilancheck --deploy"no issues" berishini tasdiqlang. -
env_required(key)yozing: o'zgaruvchi yo'q bo'lsaImproperlyConfiguredko'tarsin.prod.py'daSECRET_KEY = env_required("DJANGO_SECRET_KEY")qiling va o'zgaruvchisiz ishga tushirib, xato chiqishini ko'ring. -
pytest-djangobilan test yozing:ALLOWED_HOSTS=["example.com"],DEBUG=Falseda notanishHostbilan so'rov 400, tanishHostbilan 200 qaytarishini@override_settingsorqali tekshiring. -
Test yozing:
X-Frame-Optionsheaderi javobdaDENYekanini vaSECURE_CONTENT_TYPE_NOSNIFF=True'daX-Content-Type-Options: nosniffpaydo bo'lishini tasdiqlang.
Qiyin¶
-
CSRF testini yozing:
Client(enforce_csrf_checks=True)bilan@csrf_protectview'ga token'siz POST yuborib 403 olganingizni, GET'da esa 200 olganingizni tasdiqlang. -
Uchta muhit qo'shing (
dev,staging,prod) vaDJANGO_SETTINGS_MODULE'ni--settingsflagi orqali almashtirib, har biridacheckishlashini ko'rsating.staging'daDEBUG=Falselekin batafsilroq logging bo'lsin. -
prod.py'ni shunday yozingki,ALLOWED_HOSTSbo'sh bo'lsa import vaqtidayoqRuntimeErrorko'tarsin (jim ishlab ketmasin). Bo'sh va to'ldirilgan holatlarda xulqni tekshiring.
Yechimlar
1.
# settings.py o'rniga paket:
mkdir config/settings
# settings.py mazmunini base.py'ga ko'chiring, keyin o'chiring
# yangi fayllar: __init__.py (bo'sh), base.py, dev.py, prod.py
# manage.py
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.dev")
# wsgi.py va asgi.py'da ham xuddi shunday
2.
python -c "from django.core.management.utils import get_random_secret_key; k=get_random_secret_key(); print(len(k), k)"
# Natija: 50 va tasodifiy kalit (masalan =21+jen*5mc_3...)
3.
# config/settings/dev.py
from .base import * # noqa
DEBUG = True
SECRET_KEY = "django-insecure-dev-faqat-mahalliy"
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
4.
5.
def env_bool(key, default=False):
val = os.environ.get(key)
if val is None:
return default
return val.strip().lower() in ("1", "true", "yes", "on")
# sinov:
os.environ["DJANGO_DEBUG"] = "True"
assert env_bool("DJANGO_DEBUG") is True
os.environ["DJANGO_DEBUG"] = "0"
assert env_bool("DJANGO_DEBUG") is False
6.
python manage.py check --deploy
# WARNINGS: W004 (HSTS), W008 (SSL_REDIRECT), W009 (SECRET_KEY),
# W012 (SESSION_COOKIE_SECURE), W016 (CSRF_COOKIE_SECURE), W018 (DEBUG)
# Jami: 6 issue
7.
# config/settings/base.py
import os
def env(key, default=None):
return os.environ.get(key, default)
def env_bool(key, default=False):
val = os.environ.get(key)
if val is None:
return default
return val.strip().lower() in ("1", "true", "yes", "on")
def env_list(key, default=None):
val = os.environ.get(key)
if not val:
return default or []
return [x.strip() for x in val.split(",") if x.strip()]
SECRET_KEY = env("DJANGO_SECRET_KEY", "dev-xavfsiz-emas")
DEBUG = env_bool("DJANGO_DEBUG", False)
ALLOWED_HOSTS = env_list("DJANGO_ALLOWED_HOSTS", [])
8.
from pathlib import Path
def load_env(path):
p = Path(path)
if not p.exists():
return
for line in p.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, _, value = line.partition("=")
os.environ.setdefault(key.strip(), value.strip())
load_env(BASE_DIR / ".env")
9.
# config/settings/prod.py
from .base import * # noqa
DEBUG = False
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_CONTENT_TYPE_NOSNIFF = True
DJANGO_SETTINGS_MODULE=config.settings.prod \
DJANGO_SECRET_KEY="uzun-50-belgili-tasodifiy-kalit-..." \
DJANGO_ALLOWED_HOSTS="example.com" \
python manage.py check --deploy
# System check identified no issues (0 silenced).
10.
import os
from django.core.exceptions import ImproperlyConfigured
def env_required(key):
try:
return os.environ[key]
except KeyError:
raise ImproperlyConfigured(f"Muhit o'zgaruvchisi o'rnatilmagan: {key}")
# prod.py:
SECRET_KEY = env_required("DJANGO_SECRET_KEY")
# o'zgaruvchisiz: ImproperlyConfigured: Muhit o'zgaruvchisi o'rnatilmagan: DJANGO_SECRET_KEY
11.
# test_security.py
from django.test import override_settings
@override_settings(ALLOWED_HOSTS=["example.com"], DEBUG=False)
def test_allowed_hosts_blocks_unknown(client):
r = client.get("/", HTTP_HOST="yovuz.com")
assert r.status_code == 400
@override_settings(ALLOWED_HOSTS=["example.com"], DEBUG=False)
def test_allowed_hosts_accepts_known(client):
r = client.get("/", HTTP_HOST="example.com")
assert r.status_code == 200
12.
def test_clickjacking_header(client):
r = client.get("/", HTTP_HOST="localhost")
assert r.status_code == 200
assert r.headers["X-Frame-Options"] == "DENY"
@override_settings(SECURE_CONTENT_TYPE_NOSNIFF=True)
def test_nosniff_header(client):
r = client.get("/", HTTP_HOST="localhost")
assert r.headers["X-Content-Type-Options"] == "nosniff"
13.
from django.views.decorators.csrf import csrf_protect
from django.http import HttpResponse
@csrf_protect
def forma(request):
if request.method == "POST":
return HttpResponse("Qabul qilindi")
return HttpResponse("Forma")
# test:
def test_csrf_blocks_post_without_token():
from django.test import Client
c = Client(enforce_csrf_checks=True)
r = c.post("/forma/", {"nom": "Ali"}, HTTP_HOST="localhost")
assert r.status_code == 403
def test_get_is_safe():
from django.test import Client
c = Client(enforce_csrf_checks=True)
r = c.get("/forma/", HTTP_HOST="localhost")
assert r.status_code == 200
14.
# config/settings/staging.py
from .base import * # noqa
DEBUG = False
ALLOWED_HOSTS = env_list("DJANGO_ALLOWED_HOSTS", ["staging.example.com"])
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {"console": {"class": "logging.StreamHandler"}},
"root": {"handlers": ["console"], "level": "DEBUG"}, # batafsilroq
}
python manage.py check --settings=config.settings.dev
python manage.py check --settings=config.settings.staging
# prod.py SECRET_KEY ni env'dan majburiy oladi (env_required), shuning uchun
# DJANGO_SECRET_KEY ham beriladi β aks holda ImproperlyConfigured yiqiladi:
DJANGO_SECRET_KEY="uzun-50-belgili-tasodifiy-kalit-..." \
DJANGO_ALLOWED_HOSTS=example.com \
python manage.py check --settings=config.settings.prod
15.
# config/settings/prod.py
from .base import * # noqa
DEBUG = False
if not ALLOWED_HOSTS:
raise RuntimeError("Production'da DJANGO_ALLOWED_HOSTS bo'sh bo'lmasligi kerak!")
# Tekshirildi:
# - bo'sh: RuntimeError ko'tariladi (import vaqtidayoq, ilova ishga tushmaydi)
# - DJANGO_ALLOWED_HOSTS="example.com": muammosiz yuklanadi
β¬ οΈ Oldingi: 22 β Testlash Β· π README Β· Keyingi: 24 β Deployment (production) β‘οΈ