こんにちは。
株式会社クラスアクト インフラストラクチャ事業部の大塚です。
この記事ではタイトルの通り、Let's Encryptで証明書を取得して、その取得した証明書を使ってDjangoをSSL化して、セキュアな通信環境を実現したいと思います。
OSはAmazon Linux 2023を使っております。
このOSにLet's Encrypy環境を作る方法は以下となります。
構築
Django環境構築
何度目かわかりませんが、Djangoをインストールしていきます。
Amazon Linux 2023ではPython3がデフォルトでインストールされていますが、pipがインストールされていないようなので、インストールしていきます。
その後、DjangoとSSL化するためのDjango-SSLServerをインストールします。
[ec2-user@ip-192-168-3-38 ~]$ sudo su -
Last login: Sat Jun 29 12:19:06 UTC 2024 on pts/1
[root@ip-192-168-3-38 ~]# dnf update && dnf upgrade -y
[root@ip-192-168-3-38 ~]# python3 -V
Python 3.9.16
[root@ip-192-168-3-38 ~]# pip -V
-bash: pip: command not found
[root@ip-192-168-3-38 ~]# pip3 -V
-bash: pip3: command not found
[root@ip-192-168-3-38 ~]# dnf install -y python3-pip
[root@ip-192-168-3-38 ~]# pip3 --version
pip 21.3.1 from /usr/lib/python3.9/site-packages/pip (python 3.9)
[root@ip-192-168-3-38 ~]# pip3 install django django-sslserver
適当にプロジェクトを作成します。settings.pyを編集してSSL化出来るようにします。
[root@ip-192-168-3-38 ~]# pwd
/root
[root@ip-192-168-3-38 ~]# django-admin startproject testPJ
[root@ip-192-168-3-38 ~]# ls -ltr
total 0
drwxr-xr-x. 3 root root 37 Jun 29 12:31 testPJ
[root@ip-192-168-3-38 ~]# cd testPJ/
[root@ip-192-168-3-38 testPJ]# ls -ltr
total 4
drwxr-xr-x. 2 root root 89 Jun 29 12:31 testPJ
-rwxr-xr-x. 1 root root 662 Jun 29 12:31 manage.py
[root@ip-192-168-3-38 testPJ]# cd testPJ/
[root@ip-192-168-3-38 testPJ]# ls -ltr
total 16
-rw-r--r--. 1 root root 389 Jun 29 12:31 wsgi.py
-rw-r--r--. 1 root root 762 Jun 29 12:31 urls.py
-rw-r--r--. 1 root root 3222 Jun 29 12:31 settings.py
-rw-r--r--. 1 root root 389 Jun 29 12:31 asgi.py
-rw-r--r--. 1 root root 0 Jun 29 12:31 __init__.py
以下がsettings.pyの内容となります。
★部分が修正したものになりますので、適宜ご確認ください。
特にこのコンフィグの最終行に追加したものがSSLの具体的な内容になるのですが、もっとセキュアにする方法があるようですので、今後勉強していきます。
今回は一旦SSL化することを目的に、いったん先に進みます。
[root@ip-192-168-3-38 testPJ]# vi settings.py
[root@ip-192-168-3-38 testPJ]# cat settings.py
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-+!_r_y8za^1w$$@6b6azsmtwezxxnw)04opdb&0q#1f^5^7ouz'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ["*"] ★
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'sslserver', ★
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'testPJ.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'testPJ.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'ja' ★
TIME_ZONE = 'Asia/Tokyo' ★
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# 2024/6/29 add SSL Server ★
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = False
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
Djangoを稼働させて、いつものロケットの画面が表示されることを確認していきます。Djangoへのアクセスは一旦非セキュアなHTTPを使います。
ここで問題ないことを確認したらSSL化していきたいと思います。
[root@ip-192-168-3-38 testPJ]# python3 manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying sessions.0001_initial... OK
[root@ip-192-168-3-38 testPJ]# python3 manage.py runserver 0.0.0.0:80
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
June 29, 2024 - 21:38:50
Django version 4.2.13, using settings 'testPJ.settings'
Starting development server at http://0.0.0.0:80/
Quit the server with CONTROL-C.
EC2のパブリックIPを指定してアクセスします。問題なさそうですね。
DjangoのSSL化
改めてLet's Encrypで入手した証明書等を確認します。
これの証明書ファイル(fullchain.pem)と秘密鍵ファイル(privkey.pem)を使っていきます。
[root@ip-192-168-3-38 ~]# cd /etc/letsencrypt/live/ohtsuka-aws.xyz/
[root@ip-192-168-3-38 ohtsuka-aws.xyz]# ls -ltr
total 4
lrwxrwxrwx. 1 root root 42 Jun 28 07:17 privkey.pem -> ../../archive/ohtsuka-aws.xyz/privkey1.pem
lrwxrwxrwx. 1 root root 44 Jun 28 07:17 fullchain.pem -> ../../archive/ohtsuka-aws.xyz/fullchain1.pem
lrwxrwxrwx. 1 root root 40 Jun 28 07:17 chain.pem -> ../../archive/ohtsuka-aws.xyz/chain1.pem
lrwxrwxrwx. 1 root root 39 Jun 28 07:17 cert.pem -> ../../archive/ohtsuka-aws.xyz/cert1.pem
-rw-r--r--. 1 root root 692 Jun 28 07:17 README
以下のコマンドを実行してSSL化した上でDjangoを稼働させます。
[root@ip-192-168-3-38 testPJ]# python3 manage.py runsslserver 0.0.0.0:443 --certificate /etc/letsencrypt/live/ohtsuka-aws.xyz/fullchain.pem --key /etc/letsen
crypt/live/ohtsuka-aws.xyz/privkey.pem
Watching for file changes with StatReloader
Validating models...
System check identified no issues (0 silenced).
June 29, 2024 - 21:50:37
Django version 4.2.13, using settings 'testPJ.settings'
Starting development server at https://0.0.0.0:443/
Using SSL certificate: /etc/letsencrypt/live/ohtsuka-aws.xyz/fullchain.pem
Using SSL key: /etc/letsencrypt/live/ohtsuka-aws.xyz/privkey.pem
Quit the server with CONTROL-C.
HTTPSでEC2のパブリックIPを指定してブラウジングのテストをしてみます。証明書でエラーが出ていますね。
最初は少し焦ったのですが、よくよく考えるとドメイン名でサーバにアクセスしていないから「証明書違うっぽいけど大丈夫?」的な感じで警告を出してきているのかなと・・・
これを修正するために、Route 53で管理しているAレコードを修正してドメイン名でDjangoのサーバにアクセスできるようにしていきます。
ドメイン名でサーバに接続してみます。今度は警告など特に出ずにセキュアな通信が出来ていることがわかりますね。