Práticas recomendadas de segurança para mssql-django

Este artigo aborda as práticas de segurança para aplicativos Django que se conectam a SQL Server por meio do mssql-django back-end. Essas práticas complementam os recursos de segurança internos do Django e o modelo de segurança do SQL Server.

Use a autenticação do Microsoft Entra em vez de senhas

A autenticação do Microsoft Entra elimina senhas armazenadas de bancos de dados. Use-o para todas as conexões SQL do Azure.

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",
        },
    },
}

Para obter a lista completa de métodos de autenticação, ressalvas atuais e exemplos com TOKEN, DefaultAzureCredential e ManagedIdentityCredential, consulte autenticação do Microsoft Entra com mssql-django.

Gerenciar credenciais com segurança

Quando precisar de autenticação SQL, mantenha as credenciais fora do código-fonte.

Variáveis de ambiente

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

Para implantações de produção, recupere segredos do 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",
        },
    },
}

Cuidado

Nunca confirme credenciais no controle do código-fonte. Adicionar .env arquivos a .gitignore. Use git-secrets ou pré-confirmar ganchos para verificar se há confirmações de credenciais acidentais.

Exigir criptografia TLS

SQL Server conexões devem ser sempre criptografadas. O driver ODBC criptografa conexões por padrão, começando com o Driver 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
        },
    },
}

Se você usar o ODBC Driver 17, habilite a criptografia explicitamente:

"extra_params": "Encrypt=yes"

Cuidado

Use TrustServerCertificate=yes somente para desenvolvimento local com certificados autoassinados. Não o use em produção. Desabilita a validação da cadeia de certificados e aumenta o risco de ataque de intermediário. Instale um certificado confiável no servidor e conecte-se com TrustServerCertificate=no.

Aplicar o princípio de privilégios mínimos

Crie logons SQL Server dedicados apenas com as permissões necessárias para seu aplicativo:

-- 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];

Para aplicações que não executam migrações em produção, omita as permissões ALTER e 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

Execute as migrações a partir de uma etapa de implantação separada, com mais privilégios:

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

Escolha a função certa

As funções de banco de dados fixas do SQL Server são ordenadas da menos privilegiada para a mais privilegiada. Escolha a função menos privilegiada que abrange sua carga de trabalho e só escalone quando necessário:

Função Grants Quando usar
db_datareader SELECT em todas as tabelas e exibições do usuário Usuários com acesso somente leitura a relatórios
db_datawriter INSERT, UPDATE, DELETE em todas as tabelas de usuários Usuário de aplicativo de runtime (combinar com db_datareader)
db_ddladmin Criar, alterar e soltar objetos de esquema Somente usuário de migração ou implantação
db_owner Todas as permissões de banco de dados, incluindo segurança Evite aplicativos; reserva para DBAs

Para um controle mais refinado do que as funções fixas permitem, crie uma função de banco de dados personalizada e GRANT apenas as permissões específicas no esquema específico que seu aplicativo usa. Manter todos os objetos do aplicativo em um esquema dedicado (por exemplo, app) permite restringir as concessões de permissões com GRANT ... ON SCHEMA::app, em vez de depender das funções db_datareader e db_datawriter aplicáveis a todo o banco de dados.

Note

Não use a conta sa nem a função fixa de banco de dados db_owner para conexões de aplicativos. Se o aplicativo estiver comprometido, um invasor obterá controle de banco de dados completo.

Impedir a injeção de SQL

O ORM do Django parametriza todas as consultas automaticamente. A injeção de SQL é apenas um risco quando você usa SQL bruto:

Consultas ORM seguras

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

Seguro: SQL bruto parametrizado

from django.db import connection

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

Não seguro: formatação de cadeia de caracteres no SQL bruto

# 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)

Extra e RawSQL

O Django extra() e RawSQL() aceitam fragmentos brutos de SQL. Sempre use parâmetros:

# 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])
)

Importante

Nunca use extra() ou RawSQL() com formatação de cadeia de caracteres. Elas ignoram a parametrização automática do ORM.

Configurar o middleware de segurança do Django

Habilite o middleware de segurança interno do Django para proteger a camada da Web. Embora elas não sejam específicas do banco de dados, elas protegem o aplicativo que se conecta ao banco de dados:

# 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

Auditar o acesso ao banco de dados

Habilite SQL Server auditoria para acompanhar as operações de banco de dados do aplicativo 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);

Para Banco de Dados SQL do Azure, habilite a auditoria por meio do portal Azure ou CLI do Azure:

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

Proteger colunas confidenciais com o Always Encrypted

Para criptografia em nível de coluna de dados confidenciais, como SSNs, números de cartão de crédito ou dados de salário, use Always Encrypted. O driver ODBC lida com criptografia e descriptografia de forma transparente:

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

Para obter uma configuração detalhada, incluindo o gerenciamento de chaves com Azure Key Vault, consulte Always Encrypted com mssql-django.

Lista de verificação de segurança

Categoria Prática Prioridade
Authentication Use a autenticação do Microsoft Entra para o SQL do Azure. Alto
Credentials Armazene segredos em variáveis de ambiente ou Azure Key Vault. Alto
Encryption Use o ODBC Driver 18 (criptografia ativada por padrão) ou Encrypt=yes. Alto
Injeção Use consultas de ORM ou SQL puro parametrizado. Nunca formate SQL como string. Alto
Privilégios mínimos Crie logons dedicados com permissões mínimas necessárias. Alto
TLS Não use TrustServerCertificate=yes em produção. Alto
Django Ativar SECURE_SSL_REDIRECT, cookies seguros, HSTS. Medium
Auditing Habilite a auditoria do SQL Server ou a Auditoria do SQL do Azure. Medium
Criptografia de coluna Use Always Encrypted para colunas altamente sensíveis. Baixo
Conexão Defina CONN_MAX_AGE e CONN_HEALTH_CHECKS para impedir conexões obsoletas. Baixo