Рекомендации по безопасности для mssql-django

В этой статье рассматриваются рекомендации по безопасности для приложений Django, которые подключаются к SQL Server через backend mssql-django. Эти методики дополняют встроенные функции безопасности Django и модель безопасности SQL Server.

Используйте проверку подлинности Microsoft Entra вместо паролей

Microsoft Entra проверка подлинности устраняет сохраненные пароли базы данных. Используйте его для всех Azure SQL подключений.

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "HOST": "<your-server>.database.windows.net",
        "PORT": "1433",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            "extra_params": "Authentication=ActiveDirectoryMsi",
        },
    },
}

Полный список методов аутентификации, текущих ограничений и примеры с TOKEN, DefaultAzureCredential и ManagedIdentityCredential см. в разделе Аутентификация Microsoft Entra с mssql-django.

Безопасное управление учетными данными

Если требуется проверка подлинности SQL, не храните учетные данные в исходном коде.

Переменные среды

import os

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": os.environ["DB_NAME"],
        "USER": os.environ["DB_USER"],
        "PASSWORD": os.environ["DB_PASSWORD"],
        "HOST": os.environ["DB_HOST"],
        "PORT": os.environ.get("DB_PORT", "1433"),
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
        },
    },
}

Azure Key Vault

Для рабочих развертываний извлеките секреты из Azure Key Vault:

from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient

credential = DefaultAzureCredential()
client = SecretClient(vault_url="https://<your-vault>.vault.azure.net/", credential=credential)

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": client.get_secret("db-name").value,
        "USER": client.get_secret("db-user").value,
        "PASSWORD": client.get_secret("db-password").value,
        "HOST": client.get_secret("db-host").value,
        "PORT": "1433",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
        },
    },
}

Caution

Никогда не коммитьте учетные данные в систему контроля версий. Добавить файлы .env в .gitignore. Используйте git-secrets или хуки pre-commit для проверки на случайную фиксацию учетных данных в коммитах.

Принудительное шифрование TLS

SQL Server подключения всегда должны быть зашифрованы. Драйвер ODBC шифрует подключения по умолчанию, начиная с ДРАЙВЕРА ODBC 18:

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "HOST": "<your-server>.database.windows.net",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            # Encryption is on by default with Driver 18
        },
    },
}

Если вы используете драйвер ODBC 17, включите шифрование явным образом:

"extra_params": "Encrypt=yes"

Caution

Используйте TrustServerCertificate=yes только для локальной разработки с самоподписанными сертификатами. Не используйте его в рабочей среде. Это отключает проверку цепочки сертификатов и повышает риск атаки «злоумышленник посередине». Установите доверенный сертификат на сервер и подключитесь с помощью TrustServerCertificate=no.

Применяйте принцип минимально необходимых привилегий

Создайте отдельные учетные записи для входа в SQL Server только с теми разрешениями, которые необходимы вашему приложению:

-- Create a login and user for the application
CREATE LOGIN [django_app]
WITH PASSWORD = '<strong-password>';

USE [<your-database>];

CREATE USER [django_app] FOR LOGIN [django_app];

-- Grant minimum required permissions
-- Read and write data
ALTER ROLE db_datareader ADD MEMBER [django_app];
ALTER ROLE db_datawriter ADD MEMBER [django_app];

-- Allow Django to create and alter tables during migrations
GRANT ALTER ON SCHEMA::dbo TO [django_app];
GRANT CREATE TABLE TO [django_app];
GRANT REFERENCES ON SCHEMA::dbo TO [django_app];

Для приложений, которые не выполняют миграции в рабочей среде, опустите ALTER и CREATE TABLE разрешения:

-- Production application user (read/write only)
ALTER ROLE db_datareader ADD MEMBER [django_app];
ALTER ROLE db_datawriter ADD MEMBER [django_app];

GRANT EXECUTE ON SCHEMA::dbo TO [django_app]; -- If using stored procedures

Выполните миграции в рамках отдельного этапа развертывания с повышенными привилегиями:

-- Migration user (used only during deployments)
ALTER ROLE db_ddladmin ADD MEMBER [django_migrations];

Выбор правой роли

Фиксированные роли базы данных SQL Server упорядочены от наименее привилегированных до наиболее привилегированных. Выберите роль с наименьшими привилегиями, которая подходит для вашей рабочей нагрузки, и повышайте привилегии только при необходимости.

Role Grants Когда использовать
db_datareader SELECT для всех пользовательских таблиц и представлений Пользователи с доступом к отчетам только для чтения
db_datawriter INSERT, UPDATEDELETE во всех пользовательских таблицах Пользователь приложения среды выполнения (в сочетании с db_datareader)
db_ddladmin Создание, изменение и удаление объектов схемы Только пользователь миграции или развертывания
db_owner Все разрешения базы данных, включая безопасность Не используйте для приложений; оставьте для администраторов баз данных

Для более точного управления, чем обеспечивают фиксированные роли, создайте настраиваемую роль базы данных и GRANT назначьте только те разрешения для конкретной схемы, которую использует ваше приложение. Размещение всех объектов приложения в выделенной схеме (например, app) позволяет ограничить область действия разрешений с помощью GRANT ... ON SCHEMA::app, а не полагаться на роли db_datareader и db_datawriter, действующие на уровне всей базы данных.

Note

Не используйте учётную запись sa или предопределённую роль базы данных db_owner для подключений приложения. Если приложение скомпрометировано, злоумышленник получает полный контроль над базой данных.

Предотвращение SQL-инъекций

OrM Django параметризирует все запросы автоматически. Внедрение SQL — это только риск при использовании необработанного SQL:

Безопасные запросы ORM

# Django parameterizes these automatically
users = User.objects.filter(email=user_input)
products = Product.objects.filter(price__lte=max_price)

Безопасный: параметризованный необработанный SQL

from django.db import connection

with connection.cursor() as cursor:
    cursor.execute(
        "SELECT * FROM products WHERE category = %s AND price < %s",
        [category, max_price],
    )

Небезопасно: форматирование строк в необработанном SQL

# NEVER do this - vulnerable to SQL injection
cursor.execute(f"SELECT * FROM products WHERE category = '{category}'")
cursor.execute("SELECT * FROM products WHERE category = '%s'" % category)

Дополнительные и необработанные SQL

В Django extra() и RawSQL() принимают необработанные фрагменты SQL. Всегда используйте параметры:

# Safe - parameterized
Product.objects.extra(where=["category = %s"], params=[category])

from django.db.models.expressions import RawSQL
Product.objects.annotate(
    discount=RawSQL("price * %s", [discount_rate])
)

Important

Никогда не используйте extra() или RawSQL() при форматировании строк. Они обходят автоматическую параметризацию ORM.

Настройка ПО промежуточного слоя безопасности Django

Включите встроенное ПО промежуточного слоя безопасности Django для защиты веб-слоя. Хотя они не зависят от базы данных, они защищают приложение, которое подключается к базе данных:

# settings.py

# HTTPS enforcement
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000  # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

# Cookie security
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True

# Content security
SECURE_CONTENT_TYPE_NOSNIFF = True

Аудит доступа к базе данных

Включите аудит SQL Server для отслеживания операций базы данных из приложения Django:

-- Create a server audit (Azure SQL uses Azure SQL Auditing instead)
CREATE SERVER AUDIT [DjangoAudit]
TO FILE (FILEPATH = 'C:\Audits\')
WITH (ON_FAILURE = CONTINUE);

ALTER SERVER AUDIT [DjangoAudit] WITH (STATE = ON);

-- Create a database audit specification
USE [<your-database>];

CREATE DATABASE AUDIT SPECIFICATION [DjangoDbAudit]
FOR SERVER AUDIT [DjangoAudit]
ADD (SELECT, INSERT, UPDATE, DELETE ON SCHEMA::dbo BY [django_app])
WITH (STATE = ON);

Для База данных SQL Azure включите аудит через портал Azure или Azure CLI:

az sql db audit-policy update --resource-group <rg> --server <server> \
    --name <database> --state Enabled \
    --storage-account <storage-account>

Защита конфиденциальных столбцов с помощью Always Encrypted

Для шифрования конфиденциальных данных на уровне столбцов, таких как SSN, номера кредитной карты или данные заработной платы, используйте Always Encrypted. Драйвер ODBC прозрачно обрабатывает шифрование и расшифровку:

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            "extra_params": "ColumnEncryption=Enabled",
        },
    },
}

Подробные сведения о настройке, включая управление ключами с помощью Azure Key Vault, см. в разделе Always Encrypted с помощью mssql-django.

Контрольный список безопасности

Категория Практика Priority
Authentication Используйте проверку подлинности Microsoft Entra для Azure SQL. Высоко
Credentials Храните секреты в переменных среды или в хранилище Azure Key Vault. Высоко
Encryption Используйте драйвер ODBC 18 (шифрование по умолчанию) или Encrypt=yes. Высоко
Инъекция Используйте запросы ORM или параметризованные необработанные SQL. Никогда не форматируйте SQL-запросы с помощью строкового форматирования. Высоко
Наименьшие привилегии Создайте выделенные имена входа с минимальными необходимыми разрешениями. Высоко
TLS Не используйте TrustServerCertificate=yes в рабочей среде. Высоко
Django Включите SECURE_SSL_REDIRECT, безопасные файлы cookie, HSTS. Medium
Auditing Включите аудит SQL Server или аудит Azure SQL. Medium
Шифрование столбцов Используйте Always Encrypted для столбцов с высокой степенью конфиденциальности. Низкий уровень
Подключение Установите CONN_MAX_AGE и CONN_HEALTH_CHECKS, чтобы предотвратить неактуальные соединения. Низкий уровень