本番Djangoプロジェクトのセキュリティを強化する方法
著者はCOVID-19救済基金を選択し、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
Django アプリケーションの開発は、そのアプローチが柔軟でスケーラブルであるため、迅速でクリーンなエクスペリエンスになります。 Djangoは、プロジェクトを本番環境にシームレスに準備するのに役立つさまざまなセキュリティ指向の設定も提供します。 ただし、本番環境への展開に関しては、プロジェクトをさらに保護する方法がいくつかあります。 設定を分割してプロジェクトを再構築すると、環境に基づいてさまざまな構成を簡単に設定できます。 dotenv
を利用して環境変数や機密設定を非表示にすると、プロジェクトを危険にさらす可能性のある詳細を公開しないようになります。
これらのさまざまな戦略や機能の実装には最初は時間がかかるように思われるかもしれませんが、実用的なワークフローを開発することで、セキュリティや生産性を損なうことなくプロジェクトのリリースを展開できます。
このチュートリアルでは、環境ベースの設定、dotenv
、およびDjangoの組み込みのセキュリティ設定を実装および構成することにより、Django開発にセキュリティ指向のワークフローを活用します。 これらの機能はすべて互いに補完し合っており、Djangoプロジェクトのバージョンは、デプロイに使用できるさまざまなアプローチに対応できるようになります。
前提条件
このガイドを開始する前に、次のものが必要です。
- 既存のDjangoプロジェクト。 まだセットアップしていない場合は、Djangoのインストール方法と開発環境のセットアップチュートリアルを使用してセットアップできます。 このチュートリアルでは、このチュートリアルの
testsite
プロジェクトを例として使用します。 - このDjango開発チュートリアルシリーズは、Djangoのファイル構造とそのコア設定を理解するための優れた方法です。
注:既存のDjangoプロジェクトを使用している場合は、要件が異なる場合があります。 このチュートリアルでは、特定のプロジェクト構造を提案していますが、必要に応じて、このチュートリアルの各セクションを個別に使用することもできます。
ステップ1—Djangoの設定を再構築する
この最初のステップでは、settings.py
ファイルを環境固有の構成に再配置することから始めます。 これは、開発と本番など、異なる環境間でプロジェクトを移動する必要がある場合に適しています。 この配置は、さまざまな環境での再構成が少なくなることを意味します。 代わりに、環境変数を使用して構成を切り替えます。これについては、チュートリアルの後半で説明します。
プロジェクトのサブディレクトリにsettings
という名前の新しいディレクトリを作成します。
mkdir testsite/testsite/settings
(前提条件に従って、testsite
を使用していますが、ここでプロジェクトの名前に置き換えることができます。)
このディレクトリは、現在のsettings.py
構成ファイルを置き換えます。 環境ベースの設定はすべて、このフォルダーに含まれる個別のファイルに含まれます。
新しいsettings
フォルダーに、次の3つのPythonファイルを作成します。
cd testsite/testsite/settings touch base.py development.py production.py
development.py
ファイルには、開発中に通常使用する設定が含まれています。 また、production.py
には、運用サーバーで使用するための設定が含まれます。 本番構成では開発環境では機能しない設定が使用されるため、これらは別々に保持する必要があります。 たとえば、HTTPSの使用の強制、ヘッダーの追加、本番データベースの使用などです。
base.py
設定ファイルには、development.py
およびproduction.py
が継承する設定が含まれます。 これは、冗長性を減らし、コードをよりクリーンに保つためです。 これらのPythonファイルはsettings.py
に置き換わるため、Djangoの混乱を避けるために、settings.py
を削除します。
settings
ディレクトリにいる間に、次のコマンドを使用してsettings.py
の名前をbase.py
に変更します。
mv ../settings.py base.py
これで、新しい環境ベースの設定ディレクトリの概要が完成しました。 プロジェクトはまだ新しい構成を理解していないので、次にこれを修正します。
ステップ2—python-dotenv
を使用する
現在、Djangoは新しい設定ディレクトリまたはその内部ファイルを認識しません。 したがって、環境ベースの設定で作業を続ける前に、Djangoをpython-dotenv
で動作させる必要があります。 これは、.env
ファイルから環境変数をロードする依存関係です。 これは、Djangoがプロジェクトのルートディレクトリにある.env
ファイル内を調べて、使用する設定構成を決定することを意味します。
プロジェクトのルートディレクトリに移動します。
cd ../../
python-dotenvをインストールします。
pip install python-dotenv
次に、dotenv
を使用するようにDjangoを構成する必要があります。 これを行うには、2つのファイルを編集します。開発用のmanage.py
と、本番用のwsgi.py
です。
manage.py
の編集から始めましょう。
nano manage.py
次のコードを追加します。
testsite / manage.py
import os import sys import dotenv def main(): os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testsite.settings.development') if os.getenv('DJANGO_SETTINGS_MODULE'): os.environ['DJANGO_SETTINGS_MODULE'] = os.getenv('DJANGO_SETTINGS_MODULE') try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc execute_from_command_line(sys.argv) if __name__ == '__main__': main() dotenv.load_dotenv( os.path.join(os.path.dirname(__file__), '.env') )
manage.py
を保存して閉じ、wsgi.py
を開いて編集します。
nano testsite/wsgi.py
次の強調表示された行を追加します。
testsite / testsite / wsgi.py
import os import dotenv from django.core.wsgi import get_wsgi_application dotenv.load_dotenv( os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env') ) os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testsite.settings.development') if os.getenv('DJANGO_SETTINGS_MODULE'): os.environ['DJANGO_SETTINGS_MODULE'] = os.getenv('DJANGO_SETTINGS_MODULE') application = get_wsgi_application()
これらのファイルの両方に追加したコードは、2つのことを行います。 まず、Djangoを実行するたびに(開発を実行する場合はmanage.py
、本番環境を実行する場合はwsgi.py
)、.env
ファイルを探すように指示します。 ファイルが存在する場合は、.env
が推奨する設定ファイルを使用するようにDjangoに指示します。存在しない場合は、デフォルトで開発構成を使用します。
ファイルを保存して閉じます。
最後に、プロジェクトのルートディレクトリに.env
を作成しましょう。
nano .env
次に、次の行を追加して、環境を開発に設定します。
testsite / .env
DJANGO_SETTINGS_MODULE="testsite.settings.development"
注: .env
を.gitignore
ファイルに追加して、コミットに含まれないようにします。 このファイルを使用して、公開したくないパスワードやAPIキーなどのデータを含めます。 プロジェクトが実行されているすべての環境には、その特定の環境の設定を備えた独自の.env
があります。
プロジェクトに含める.env.example
を作成して、必要な場所で新しい.env
を簡単に作成できるようにすることをお勧めします。
そのため、デフォルトではDjangoはtestsite.settings.development
を使用しますが、たとえばDJANGO_SETTINGS_MODULE
をtestsite.settings.production
に変更すると、本番構成の使用が開始されます。 次に、development.py
およびproduction.py
設定構成を入力します。
ステップ3—開発および本番環境の設定を作成する
次に、base.py
を開き、環境ごとに変更する構成を個別のdevelopment.py
ファイルとproduction.py
ファイルに追加します。 production.py
は本番データベースのクレデンシャルを使用する必要があるため、それらが利用可能であることを確認してください。
注:環境に基づいて、構成する必要のある設定を決定するのはあなた次第です。 このチュートリアルでは、本番環境と開発環境の一般的な例(つまり、セキュリティ設定と個別のデータベース構成)についてのみ説明します。
このチュートリアルでは、前提条件のチュートリアルのDjangoプロジェクトをサンプルプロジェクトとして使用しています。 設定をbase.py
からdevelopment.py
に移動します。 development.py
を開くことから始めます。
nano testsite/settings/development.py
まず、base.py
からインポートします。このファイルはbase.py
から設定を継承します。 次に、開発環境用に変更する設定を転送します。
testsite / testsite / settings / development.py
from .base import * DEBUG = True DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }
この場合、開発に固有の設定は次のとおりです。DEBUG
、開発ではこのTrue
が必要ですが、本番では必要ありません。 DATABASES
、本番データベースではなくローカルデータベース。 ここでは開発用にSQLiteデータベースを使用しています。
注:セキュリティ上の理由から、DjangoのDEBUG出力には、API
、KEY
、PASS
、SECRET
、SIGNATURE
、またはTOKEN
。
これは、DEBUG
を有効にしたまま、誤ってプロジェクトを本番環境にデプロイした場合に、秘密が明らかにならないようにするためです。
そうは言っても、DEBUG
を有効にしてプロジェクトを公開しないでください。 プロジェクトのセキュリティを危険にさらすだけです。
次に、production.py
に追加しましょう。
nano testsite/settings/production.py
本番環境はdevelopment.py
と似ていますが、データベース構成が異なり、DEBUG
がFalse
に設定されています。
testsite / testsite / settings / Production.py
from .base import * DEBUG = False ALLOWED_HOSTS = [] DATABASES = { 'default': { 'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'), 'NAME': os.environ.get('SQL_DATABASE', os.path.join(BASE_DIR, 'db.sqlite3')), 'USER': os.environ.get('SQL_USER', 'user'), 'PASSWORD': os.environ.get('SQL_PASSWORD', 'password'), 'HOST': os.environ.get('SQL_HOST', 'localhost'), 'PORT': os.environ.get('SQL_PORT', ''), } }
与えられたデータベース構成の例では、dotenv
を使用して、デフォルトを含めて、与えられた各資格情報を構成できます。 プロジェクトの製品版用にデータベースをすでにセットアップしていると仮定すると、提供されている例の代わりに構成を使用してください。
これで、.env
のDJANGO_SETTINGS_MODULE
に基づいて異なる設定を使用するようにプロジェクトが構成されました。 使用した設定例を前提として、本番環境設定を使用するようにプロジェクトを設定すると、DEBUG
はFalse
になり、ALLOWED_HOSTS
が定義され、次のような別のデータベースの使用を開始します。サーバーで(すでに)構成されています。
ステップ4—Djangoのセキュリティ設定を操作する
Djangoには、プロジェクトに追加できるセキュリティ設定が含まれています。 このステップでは、本番プロジェクトに不可欠と見なされるセキュリティ設定をプロジェクトに追加します。 これらの設定は、プロジェクトが一般に公開されている場合に使用することを目的としています。 開発環境でこれらの設定を使用することはお勧めしません。 したがって、このステップでは、これらの設定をproduction.py
構成に制限します。
ほとんどの場合、これらの設定により、セッションCookie 、CSRF Cookie、HTTPからHTTPSへのアップグレードなどのさまざまなWeb機能にHTTPSの使用が強制されます。 したがって、サーバーを指すドメインをまだサーバーに設定していない場合は、このセクションをしばらくお待ちください。 サーバーを展開できるようにセットアップする必要がある場合は、結論でこれに関する推奨記事を確認してください。
最初に開くproduction.py
:
nano production.py
コードに続く説明に従って、ファイルに、プロジェクトで機能する強調表示された設定を追加します。
testsite / testsite / settings / Production.py
from .base import * DEBUG = False ALLOWED_HOSTS = ['your_domain', 'www.your_domain'] DATABASES = { 'default': { 'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'), 'NAME': os.environ.get('SQL_DATABASE', os.path.join(BASE_DIR, 'db.sqlite3')), 'USER': os.environ.get('SQL_USER', 'user'), 'PASSWORD': os.environ.get('SQL_PASSWORD', 'password'), 'HOST': os.environ.get('SQL_HOST', 'localhost'), 'PORT': os.environ.get('SQL_PORT', ''), } } SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_BROWSER_XSS_FILTER = True
SECURE_SSL_REDIRECT
は、すべてのHTTPリクエストをHTTPSにリダイレクトします(免除されている場合を除く)。 これは、プロジェクトが常に暗号化された接続を使用しようとすることを意味します。 これを機能させるには、サーバーでSSLを構成する必要があります。 NginxまたはApacheがすでにこれを行うように構成されている場合、この設定は冗長になることに注意してください。SESSION_COOKIE_SECURE
は、CookieはHTTPS経由でのみ処理できることをブラウザに通知します。 つまり、プロジェクトがログインなどのアクティビティ用に生成するCookieは、暗号化された接続でのみ機能します。CSRF_COOKIE_SECURE
はSESSION_COOKIE_SECURE
と同じですが、CSRFトークンに適用されます。 CSRFトークンはクロスサイトリクエストフォージェリから保護します。 Django CSRF保護は、プロジェクトに送信されたフォーム(ログイン、サインアップなど)がサードパーティではなく、プロジェクトによって作成されたことを確認することでこれを行います。SECURE_BROWSER_XSS_FILTER
は、X-XSS-Protection: 1; mode=block
ヘッダーをまだ持っていないすべての応答に設定します。 これにより、サードパーティがプロジェクトにスクリプトを挿入できないようになります。 たとえば、ユーザーがパブリックフィールドを使用してデータベースにスクリプトを保存している場合、そのスクリプトが取得されて他のユーザーに表示されると、スクリプトは実行されません。
Django内で利用可能なさまざまなセキュリティ設定について詳しく知りたい場合は、ドキュメントを確認してください。
警告: Djangoのドキュメントには、SECURE_BROWSER_XSS_FILTER
に完全に依存するべきではないと記載されています。 入力の検証とサニタイズを忘れないでください。
追加の設定
次の設定は、HTTP Strict Transport Security(HSTS)をサポートするためのものです。つまり、サイト全体で常にSSLを使用する必要があります。
SECURE_HSTS_SECONDS
は、HSTSが設定されている時間(秒単位)です。 これを1時間(秒単位)に設定すると、WebサイトのWebページにアクセスするたびに、次の1時間はHTTPSがサイトにアクセスできる唯一の方法であることがブラウザに通知されます。 その時間中にWebサイトの安全でない部分にアクセスすると、ブラウザにエラーが表示され、安全でないページにアクセスできなくなります。SECURE_HSTS_PRELOAD
は、SECURE_HSTS_SECONDS
が設定されている場合にのみ機能します。 このヘッダーは、ブラウザにサイトをプリロードするように指示します。 これは、FirefoxやChromeなどの一般的なブラウザに実装されているハードコードされたリストにWebサイトが追加されることを意味します。 これには、Webサイトが常に暗号化されている必要があります。 このヘッダーには注意することが重要です。 プロジェクトで暗号化を使用しないことにした場合はいつでも、HSTSプリロードリストから手動で削除するのに数週間かかる場合があります。SECURE_HSTS_INCLUDE_SUBDOMAINS
は、HSTSヘッダーをすべてのサブドメインに適用します。 このヘッダーを有効にすると、unsecure.your_domain
がこのDjangoプロジェクトに関連していない場合でも、your_domain
とunsecure.your_domain
の両方で暗号化が必要になります。
これらの設定が独自のDjangoプロジェクトでどのように機能するかを考慮する必要があります。 全体として、ここで説明する設定は、ほとんどのDjangoプロジェクトの優れた基盤です。 次に、dotENV
の使用法をさらに確認します。
ステップ5—シークレットにpython-dotenv
を使用する
このチュートリアルの最後の部分は、python-dotenv
を活用するのに役立ちます。 これにより、プロジェクトのSECRET_KEYや管理者のログインURLなどの特定の情報を非表示にできます。 これらのシークレットは公開されないため、GitHubやGitLabなどのプラットフォームでコードを公開する場合は、これは素晴らしいアイデアです。 代わりに、ローカル環境またはサーバーでプロジェクトを最初にセットアップするときはいつでも、新しい.env
ファイルを作成し、それらのシークレット変数を定義できます。
SECRET_KEY
を非表示にして、このセクションで作業を開始する必要があります。
.env
ファイルを開きます。
nano .env
そして、次の行を追加します。
testsite / .env
DJANGO_SETTINGS_MODULE="django_hardening.settings.development" SECRET_KEY="your_secret_key"
そしてあなたのbase.py
で:
nano testsite/settings/base.py
SECRET_KEY
変数を次のように更新しましょう。
testsite / testsite / settings / base.py
. . . SECRET_KEY = os.getenv('SECRET_KEY') . . .
これで、プロジェクトは.env
にあるSECRET_KEY
を使用します。
最後に、ランダムな文字の長い文字列を追加して、管理URLを非表示にします。 これにより、ボットがログインフィールドをブルートフォース攻撃したり、見知らぬ人がログインを推測したりすることができなくなります。
.env
をもう一度開きます。
nano .env
そして、SECRET_ADMIN_URL
変数を追加します。
testsite / .env
DJANGO_SETTINGS_MODULE="django_hardening.settings.development" SECRET_KEY="your_secret_key" SECRET_ADMIN_URL="very_secret_url"
次に、SECRET_ADMIN_URL
を使用して管理URLを非表示にするようにDjangoに指示しましょう。
nano /testsite/urls.py
注:your_secret_key
とvery_secret_url
を独自の秘密の文字列に置き換えることを忘れないでください。 また、very_secret_url
を独自のシークレットURLに置き換えることを忘れないでください。
これらの変数にランダムな文字列を使用したい場合、Pythonはそのような文字列を生成するための素晴らしいsecrets.pyライブラリを提供します。 それらが提供するexamplesは、安全なランダム文字列を生成するための小さなPythonプログラムを作成するための優れた方法です。
次のように管理URLを編集します。
testsite / testsite / urls.py
import os from django.contrib import admin from django.urls import path urlpatterns = [ path(os.getenv('SECRET_ADMIN_URL') + '/admin/', admin.site.urls), ]
/admin/
だけでなく、very_secret_url/admin/
に管理者ログインページが表示されるようになりました。
結論
このチュートリアルでは、さまざまな環境で簡単に使用できるように現在のDjangoプロジェクトを構成しました。 プロジェクトは、シークレットと設定を処理するためにpython-dotenv
を利用するようになりました。 また、本番環境では、Djangoの組み込みのセキュリティ機能が有効になっています。
推奨されるすべてのセキュリティコンポーネントを有効にし、指示に従って設定を再実装した場合、プロジェクトには次の主要な機能があります。
- すべての通信(サブドメイン、Cookie、CSRFなど)のSSL/HTTPS。
- XSS(クロスサイトスクリプティング)攻撃の防止。
- CSRF(クロスサイトリクエストフォージェリ)攻撃の防止。
- 隠されたプロジェクトの秘密鍵。
- ブルートフォース攻撃を防ぐ隠された管理者ログインURL。
- 開発と本番用に別々の設定。
Djangoについて詳しく知りたい場合は、Django開発に関するチュートリアルシリーズをご覧ください。
また、プロジェクトをまだ本番環境に移行していない場合は、 Ubuntu 20.04 でPostgres、Nginx、Gunicornを使用してDjangoをセットアップする方法に関するチュートリアルをご覧ください。 その他のチュートリアルについては、Djangoトピックページを確認することもできます。
そしてもちろん、詳細については、Djangoの設定ドキュメントをお読みください。