mssql-django のセキュリティのベスト プラクティス

この記事では、mssql-django バックエンドを介してSQL Serverに接続する 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",
        },
    },
}

認証方法の完全な一覧、現時点での注意事項、および DefaultAzureCredentialManagedIdentityCredential を使用した TOKEN の例については、mssql-django での Microsoft Entra 認証をご覧ください。

資格情報を安全に管理する

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

注意事項

資格情報をソース管理にコミットしないでください。 .envファイルを.gitignoreに追加します。 誤った資格情報のコミットをスキャンするには、 git-secrets またはコミット前フックを使用します。

TLS 暗号化を適用する

SQL Server接続は常に暗号化する必要があります。 ODBC ドライバーは、ODBC Driver 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 Driver 17 を使用する場合は、暗号化を明示的に有効にします。

"extra_params": "Encrypt=yes"

注意事項

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

運用環境で移行を実行しないアプリケーションの場合は、 ALTERCREATE 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 すべてのユーザー テーブル上のINSERTUPDATEDELETE ランタイム アプリケーション ユーザー ( db_datareaderと組み合わせる)
db_ddladmin スキーマ オブジェクトの作成、変更、および削除 移行またはデプロイユーザーのみ
db_owner セキュリティを含むすべてのデータベースアクセス許可 アプリケーションでは避け、DBA 専用としてください

固定ロールで許可されるよりもきめ細かな制御を行う場合は、カスタム データベース ロールを作成し、アプリで使用する特定のスキーマに対する特定のアクセス許可のみを GRANT します。 すべてのアプリケーション オブジェクトを専用スキーマ (たとえば app) に保持すると、データベース全体の db_datareader ロールや db_datawriter ロールに頼るのではなく、GRANT ... ON SCHEMA::app を使用して権限の範囲を限定できます。

Note

アプリケーション接続には、 sa アカウントや固定データベース ロール db_owner 使用しないでください。 アプリケーションが侵害された場合、攻撃者はデータベースを完全に制御できます。

SQL インジェクションを防止する

Django の ORM は、すべてのクエリを自動的にパラメーター化します。 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)

Extra と RawSQL

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 の組み込みのセキュリティ ミドルウェアを有効にして、Web レイヤーを保護します。 これらはデータベース固有ではありませんが、データベースに接続するアプリケーションを保護します。

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

Azure SQL Databaseの場合は、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を使用したキー管理を含む詳細なセットアップについては、mssql-django を使用した Always Encrypted に関する記事を参照してください。

セキュリティ チェックリスト

カテゴリ 演習 優先度
Authentication Azure SQL Microsoft Entra認証を使用します。
資格情報 環境変数またはAzure Key Vaultにシークレットを格納します。
暗号化 ODBC Driver 18 (既定では暗号化オン) または Encrypt=yesを使用します。
注入 ORM クエリまたはパラメーター化された生 SQL を使用します。 SQL を文字列形式にしない。
最小特権 必要最小限のアクセス許可を持つ専用ログインを作成します。
TLS 運用環境では TrustServerCertificate=yes を使用しないでください。
Django SECURE_SSL_REDIRECT、セキュリティで保護された Cookie、HSTS を有効にします。 中程度
Auditing SQL Server監査またはAzure SQL監査を有効にします。 中程度
列の暗号化 機密性の高い列には Always Encrypted を使用します。
接続 CONN_MAX_AGECONN_HEALTH_CHECKSを設定して、古い接続を防ぎます。