著者はCOVID-19救済基金を選択し、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

Django アプリケーションの開発は、そのアプローチが柔軟でスケーラブルであるため、迅速でクリーンなエクスペリエンスになります。 Djangoは、プロジェクトを本番環境にシームレスに準備するのに役立つさまざまなセキュリティ指向の設定も提供します。 ただし、本番環境への展開に関しては、プロジェクトをさらに保護する方法がいくつかあります。 設定を分割してプロジェクトを再構築すると、環境に基づいてさまざまな構成を簡単に設定できます。 dotenvを利用して環境変数や機密設定を非表示にすると、プロジェクトを危険にさらす可能性のある詳細を公開しないようになります。

これらのさまざまな戦略や機能の実装には最初は時間がかかるように思われるかもしれませんが、実用的なワークフローを開発することで、セキュリティや生産性を損なうことなくプロジェクトのリリースを展開できます。

このチュートリアルでは、環境ベースの設定、dotenv、およびDjangoの組み込みのセキュリティ設定を実装および構成することにより、Django開発にセキュリティ指向のワークフローを活用します。 これらの機能はすべて互いに補完し合っており、Djangoプロジェクトのバージョンは、デプロイに使用できるさまざまなアプローチに対応できるようになります。

前提条件

このガイドを開始する前に、次のものが必要です。

:既存のDjangoプロジェクトを使用している場合は、要件が異なる場合があります。 このチュートリアルでは、特定のプロジェクト構造を提案していますが、必要に応じて、このチュートリアルの各セクションを個別に使用することもできます。

ステップ1—Djangoの設定を再構築する

この最初のステップでは、settings.pyファイルを環境固有の構成に再配置することから始めます。 これは、開発と本番など、異なる環境間でプロジェクトを移動する必要がある場合に適しています。 この配置により、さまざまな環境での再構成が少なくなります。 代わりに、環境変数を使用して構成を切り替えます。これについては、チュートリアルの後半で説明します。

プロジェクトのサブディレクトリにsettingsという名前の新しいディレクトリを作成します。

  1. mkdir testsite/testsite/settings

(前提条件に従って、testsiteを使用していますが、ここでプロジェクトの名前に置き換えることができます。)

このディレクトリは、現在のsettings.py構成ファイルを置き換えます。 環境ベースの設定はすべて、このフォルダーに含まれる個別のファイルに含まれます。

新しいsettingsフォルダーに、次の3つのPythonファイルを作成します。

  1. cd testsite/testsite/settings
  2. 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に変更します。

  1. mv ../settings.py base.py

これで、新しい環境ベースの設定ディレクトリの概要が完成しました。 プロジェクトはまだ新しい構成を理解していないので、次にこれを修正します。

ステップ2—python-dotenvを使用する

現在、Djangoは新しい設定ディレクトリまたはその内部ファイルを認識しません。 したがって、環境ベースの設定で作業を続ける前に、Djangoをpython-dotenvで動作させる必要があります。 これは、.envファイルから環境変数をロードする依存関係です。 これは、Djangoがプロジェクトのルートディレクトリにある.envファイル内を調べて、使用する設定構成を決定することを意味します。

プロジェクトのルートディレクトリに移動します。

  1. cd ../../

python-dotenvをインストールします。

  1. pip install python-dotenv

次に、dotenvを使用するようにDjangoを構成する必要があります。 これを行うには、2つのファイルを編集します。開発用のmanage.pyと、本番用のwsgi.pyです。

manage.pyの編集から始めましょう。

  1. 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を開いて編集します。

  1. 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を作成しましょう。

  1. nano .env

次に、次の行を追加して、環境を開発に設定します。

testsite / .env
DJANGO_SETTINGS_MODULE="testsite.settings.development"

注: .env.gitignoreファイルに追加して、コミットに含まれないようにします。 このファイルを使用して、公開したくないパスワードやAPIキーなどのデータを含めます。 プロジェクトが実行されているすべての環境には、その特定の環境の設定を備えた独自の.envがあります。

プロジェクトに含める.env.exampleを作成して、必要な場所で新しい.envを簡単に作成できるようにすることをお勧めします。

そのため、デフォルトではDjangoはtestsite.settings.developmentを使用しますが、たとえばDJANGO_SETTINGS_MODULEtestsite.settings.productionに変更すると、本番構成の使用が開始されます。 次に、development.pyおよびproduction.py設定構成を入力します。

ステップ3—開発および本番環境の設定を作成する

次に、base.pyを開き、環境ごとに変更する構成を個別のdevelopment.pyファイルとproduction.pyファイルに追加します。 production.pyは本番データベースのクレデンシャルを使用する必要があるため、それらが利用可能であることを確認してください。

注:環境に基づいて、構成する必要のある設定を決定するのはあなた次第です。 このチュートリアルでは、本番環境と開発環境の一般的な例(つまり、セキュリティ設定と個別のデータベース構成)についてのみ説明します。

このチュートリアルでは、前提条件のチュートリアルのDjangoプロジェクトをサンプルプロジェクトとして使用しています。 設定をbase.pyからdevelopment.pyに移動します。 development.pyを開くことから始めます。

  1. 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出力には、APIKEYPASSSECRETSIGNATURE、またはTOKEN

これは、DEBUGを有効にしたまま、誤ってプロジェクトを本番環境にデプロイした場合に、秘密が明らかにならないようにするためです。

そうは言っても、DEBUGを有効にしてプロジェクトを公開しないでください。 プロジェクトのセキュリティを危険にさらすだけです。

次に、production.pyに追加しましょう。

  1. nano testsite/settings/production.py

本番環境はdevelopment.pyと似ていますが、データベース構成が異なり、DEBUGFalseに設定されています。

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を使用して、デフォルトを含めて、与えられた各資格情報を構成できます。 プロジェクトの製品版用にデータベースをすでにセットアップしていると仮定すると、提供されている例の代わりに構成を使用してください。

これで、.envDJANGO_SETTINGS_MODULEに基づいて異なる設定を使用するようにプロジェクトが構成されました。 使用した設定例を前提として、本番環境設定を使用するようにプロジェクトを設定すると、DEBUGFalseになり、ALLOWED_HOSTSが定義され、次のような別のデータベースの使用を開始します。サーバーで(すでに)構成されています。

ステップ4—Djangoのセキュリティ設定を操作する

Djangoには、プロジェクトに追加できるセキュリティ設定が含まれています。 このステップでは、本番プロジェクトに不可欠と見なされるセキュリティ設定をプロジェクトに追加します。 これらの設定は、プロジェクトが一般に公開されている場合に使用することを目的としています。 開発環境でこれらの設定を使用することはお勧めしません。 したがって、このステップでは、これらの設定をproduction.py構成に制限します。

ほとんどの場合、これらの設定により、セッションCookie 、CSRF Cookie、HTTPからHTTPSへのアップグレードなどのさまざまなWeb機能にHTTPSの使用が強制されます。 したがって、サーバーを指すドメインをまだサーバーに設定していない場合は、このセクションをしばらくお待ちください。 サーバーを展開できるようにセットアップする必要がある場合は、結論でこれに関する推奨記事を確認してください。

最初に開くproduction.py

  1. 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_SECURESESSION_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_domainunsecure.your_domainの両方で暗号化が必要になります。

警告:これらの追加設定を誤って構成すると、サイトがかなりの時間破損する可能性があります。

これらの設定を実装する前に、HSTSに関するDjangoのドキュメントをお読みください。

これらの設定が独自のDjangoプロジェクトでどのように機能するかを考慮する必要があります。 全体として、ここで説明する設定は、ほとんどのDjangoプロジェクトの優れた基盤です。 次に、dotENVの使用法をさらに確認します。

ステップ5—シークレットにpython-dotenvを使用する

このチュートリアルの最後の部分は、python-dotenvを活用するのに役立ちます。 これにより、プロジェクトのSECRET_KEYや管理者のログインURLなどの特定の情報を非表示にできます。 これらのシークレットは公開されないため、GitHubやGitLabなどのプラットフォームでコードを公開する場合は、これは素晴らしいアイデアです。 代わりに、ローカル環境またはサーバーでプロジェクトを最初にセットアップするときはいつでも、新しい.envファイルを作成し、それらのシークレット変数を定義できます。

SECRET_KEYを非表示にして、このセクションで作業を開始する必要があります。

.envファイルを開きます。

  1. nano .env

そして、次の行を追加します。

testsite / .env
DJANGO_SETTINGS_MODULE="django_hardening.settings.development"
SECRET_KEY="your_secret_key"

そしてあなたのbase.pyで:

  1. nano testsite/settings/base.py

SECRET_KEY変数を次のように更新しましょう。

testsite / testsite / settings / base.py
. . .
SECRET_KEY = os.getenv('SECRET_KEY')
. . .

これで、プロジェクトは.envにあるSECRET_KEYを使用します。

最後に、ランダムな文字の長い文字列を追加して、管理URLを非表示にします。 これにより、ボットがログインフィールドをブルートフォース攻撃したり、見知らぬ人がログインを推測したりすることができなくなります。

.envをもう一度開きます。

  1. 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に指示しましょう。

  1. nano /testsite/urls.py

注:your_secret_keyvery_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の設定ドキュメントをお読みください。