Melhores práticas de segurança para mssql-django

Este artigo aborda práticas de segurança para aplicações Django que se ligam ao SQL Server através do mssql-django backend. Estas práticas complementam as funcionalidades de segurança integradas da Django e o modelo de segurança do SQL Server.

Use autenticação Microsoft Entra em vez de palavras-passe

A autenticação Microsoft Entra elimina palavras-passe armazenadas na base de dados. Usa-o para todas as ligaçõ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 dos métodos de autenticação, as limitações atuais e TOKEN exemplos com DefaultAzureCredential e ManagedIdentityCredential, consulte Autenticação do Microsoft Entra com o mssql-django.

Gerir credenciais de forma segura

Quando precisares de autenticação SQL, mantém as credenciais fora do código-fonte.

Variáveis ambientais

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 implementações de produção, obtenha 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",
        },
    },
}

Caution

Nunca comprometas credenciais com o controlo de versão. Adicionar .env ficheiros a .gitignore. Utilize git-secrets ou ganchos de pré-commit para detetar commits acidentais com credenciais.

Aplicar encriptação TLS

As ligações ao SQL Server devem estar sempre encriptadas. O driver ODBC encripta as ligações por defeito, começando pelo 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 usar o Driver ODBC 17, ative explicitamente a encriptação:

"extra_params": "Encrypt=yes"

Caution

Uso TrustServerCertificate=yes apenas para desenvolvimento local com certificados auto-assinados. Não o uses na produção. Desativa a validação da cadeia de certificados e aumenta o risco de ataque de intermediário. Instale um certificado de confiança no servidor e ligue-se a TrustServerCertificate=no.

Aplicar o princípio do menor privilégio

Crie logins dedicados no SQL Server apenas com as permissões que a sua aplicação necessita:

-- 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 migrações a partir de uma etapa de implementação separada e mais privilegiada:

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

Escolha o papel certo

Os papéis fixos da base de dados do SQL Server estão ordenados do menos ao mais privilegiado. Escolha o cargo menos privilegiado que cubra a sua carga de trabalho e só escale quando necessário:

Função Grants Quando utilizar
db_datareader SELECT em todas as tabelas e vistas de utilizadores Utilizadores que reportam apenas leitura
db_datawriter INSERT, UPDATE, DELETE em todas as tabelas de utilizador Utilizador de aplicação de runtime (combinar com db_datareader)
db_ddladmin Criar, alterar e eliminar objetos de esquema Migração ou implementação apenas para utilizadores
db_owner Todas as permissões da base de dados, incluindo segurança Evitar para aplicações; reservar para DBAs

Para um controlo mais granular do que as funções fixas permitem, crie uma função personalizada da base de dados e GRANT apenas as permissões necessárias no esquema específico que a sua aplicação utiliza. Manter todos os objetos da aplicação num esquema dedicado (por exemplo, app) permite-lhe delimitar as permissões com GRANT ... ON SCHEMA::app em vez de depender das funções db_datareader e db_datawriter ao nível de toda a base de dados.

Note

Não utilize a conta sa nem a função de base de dados fixa db_owner para ligações da aplicação. Se a aplicação for comprometida, um atacante ganha controlo total da base de dados.

Prevenir a injeção SQL

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

Seguro: Consultas ORM

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

Safe: 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],
    )

Inseguro: Formatação de strings em 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

Django extra() e RawSQL() aceitam fragmentos SQL brutos. Use sempre 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 uses extra() ou RawSQL() na formatação de cadeias de caracteres. Estas contornam a parametrização automática do ORM.

Configurar middleware de segurança Django

Ative o middleware de segurança incorporado da Django para proteger a camada web. Embora estes não sejam específicos da base de dados, protegem a aplicação que se liga à sua base 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

Acesso à base de dados de auditoria

Ative a auditoria do SQL Server para acompanhar as operações da base de dados a partir da sua aplicação 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 Base de Dados SQL do Azure, ative a auditoria através 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 sensíveis com Always Encrypted

Para encriptação ao nível de coluna de dados sensíveis como SSNs, números de cartões de crédito ou dados salariais, use o Always Encrypted. O driver ODBC gere a encriptação e a desencriptação de forma transparente:

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

Para uma configuração detalhada, incluindo gestão de chaves com Azure Key Vault, veja Always Encrypted with mssql-django.

Lista de verificação de segurança

Categoria Practice Prioridade
Authentication Utilize a autenticação do Microsoft Entra para o SQL do Azure. Alto
Credentials Guardar segredos em variáveis de ambiente ou no Azure Key Vault. Alto
Encryption Utilize o controlador ODBC 18 (encriptação ativada por predefinição) ou Encrypt=yes. Alto
Injeção Utilize consultas ORM ou SQL em bruto parametrizado. Nunca SQL em formato de string. Alto
Privilégio mínimo Crie logins dedicados com permissões mínimas necessárias. Alto
TLS Não uses TrustServerCertificate=yes em produção. Alto
Django Ativar SECURE_SSL_REDIRECT, proteger cookies, HSTS. Medium
Auditing Ative a auditoria do SQL Server ou da auditoria SQL do Azure. Medium
Encriptação de colunas Use o Always Encrypted para colunas altamente sensíveis. Low
Conexão Definir CONN_MAX_AGE e CONN_HEALTH_CHECKS para evitar ligações obsoletas. Low