Triển khai ứng dụng Django một cách an toàn với Gunicorn, Nginx và HTTPS

Bộ quy tắc bảo mật ban đầu cho ứng dụng Django

Đưa một Django từ phát triển đến sản xuất là một quá trình đòi hỏi nhiều khó khăn nhưng bổ ích. Hướng dẫn này sẽ đưa bạn qua quy trình đó từng bước, cung cấp hướng dẫn chuyên sâu bắt đầu từ hình vuông với ứng dụng Django đơn giản và thêm vào Gunicorn , Nginx , đăng ký miền tiêu đề HTTP . Sau khi xem qua hướng dẫn này, bạn sẽ được trang bị tốt hơn để đưa ứng dụng Django của mình vào sản xuất và phục vụ nó trên toàn thế giới.

Trong hướng dẫn này, bạn sẽ học :

  • Cách bạn có thể đưa ứng dụng Django của mình từ phát triển sang sản xuất
  • Cách bạn có thể lưu trữ ứng dụng của mình trên miền công cộng trong thế giới thực
  • Cách giới thiệu Gunicorn Nginx vào chuỗi yêu cầu và phản hồi
  • Cách tiêu đề HTTP có thể củng cố bảo mật HTTPS trên trang web của bạn

Để tận dụng tối đa hướng dẫn này, bạn nên có hiểu biết sơ cấp về Python , Django và cơ chế cấp cao của các yêu cầu HTTP.

Bạn có thể tải xuống dự án Django được sử dụng trong hướng dẫn này theo liên kết bên dưới:

Bắt đầu với Django và WSGIServer

Bạn sẽ sử dụng Django làm khuôn khổ cốt lõi của ứng dụng web của mình, sử dụng nó để định tuyến URL, hiển thị HTML, xác thực, quản trị và logic phụ trợ. Trong hướng dẫn này, bạn sẽ bổ sung thành phần Django với hai lớp khác, Gunicorn Nginx , để phục vụ ứng dụng một cách mở rộng. Nhưng trước khi làm điều đó, bạn sẽ cần thiết lập môi trường của mình và tự thiết lập và chạy ứng dụng Django.

Thiết lập máy ảo đám mây (VM)

Trước tiên, bạn sẽ cần khởi chạy và thiết lập một máy ảo (VM) để ứng dụng web chạy trên đó. Bạn nên tự làm quen với ít nhất một cơ sở hạ tầng là nhà cung cấp dịch vụ đám mây dịch vụ (IaaS) để cung cấp máy ảo. Phần này sẽ hướng dẫn bạn qua quy trình ở cấp độ cao nhưng sẽ không trình bày chi tiết từng bước.

Sử dụng máy ảo để cung cấp ứng dụng web là một ví dụ về IaaS, nơi bạn có toàn quyền kiểm soát phần mềm máy chủ. Các tùy chọn khác ngoài IaaS tồn tại:

  • máy Kiến cho phép bạn chỉ soạn ứng dụng Django và để một nhà cung cấp khung hoặc đám mây riêng xử lý phía cơ sở hạ tầng.
  • Phương chứa đựng cho phép nhiều ứng dụng chạy độc lập trên cùng một hệ điều hành máy chủ.

Tuy nhiên, đối với hướng dẫn này, bạn sẽ sử dụng lộ trình đã thử và đúng là phục vụ Nginx và Django trực tiếp trên IaaS.

Hai tùy chọn phổ biến cho máy ảo là Azure VM Amazon EC2 . Để được trợ giúp thêm về việc khởi chạy phiên bản, bạn nên tham khảo tài liệu dành cho nhà cung cấp dịch vụ đám mây của mình:

Dự án Django và mọi thứ khác liên quan đến hướng dẫn này nằm trên phiên bản t2.micro Amazon EC2 chạy Ubuntu Server 20.04.

Một thành phần quan trọng của thiết lập VM là các quy tắc bảo mật gửi đến . Đây là những quy tắc chi tiết kiểm soát lưu lượng truy cập vào phiên bản của bạn. Tạo các quy tắc bảo mật đầu vào sau cho quá trình phát triển ban đầu, bạn sẽ sửa đổi các quy tắc này trong quá trình sản xuất:

Tài liệu tham khảo Loại hình Giao thức Phạm vi cổng Nguồn
1 Phong tục TCP 8000 my-laptop-ip-address/32
2 Phong tục Tất cả các Tất cả các security-group-id
3 SSH TCP 22 my-laptop-ip-address/32

Bây giờ bạn sẽ đi qua từng cái một:

  1. Quy tắc 1 cho phép TCP qua cổng 8000 từ địa chỉ IPv4 của máy tính cá nhân của bạn, cho phép bạn gửi yêu cầu đến ứng dụng Django của mình khi bạn phân phát nó trong quá trình phát triển qua cổng 8000.
  2. Quy tắc 2 cho phép lưu lượng đến từ các giao diện mạng và phiên bản được gán cho cùng một nhóm bảo mật, sử dụng ID nhóm bảo mật làm nguồn. Đây là một quy tắc có trong nhóm bảo mật AWS mặc định mà bạn nên gắn với phiên bản của mình.
  3. Quy tắc 3 cho phép bạn truy cập máy ảo của mình thông qua SSH từ máy tính cá nhân của bạn.

Bạn cũng sẽ muốn thêm quy tắc gửi đi để cho phép lưu lượng ra ngoài thực hiện những việc như cài đặt gói:

Loại hình Giao thức Phạm vi cổng Nguồn
Phong tục Tất cả các Tất cả các 0.0.0.0/0

Liên kết tất cả lại với nhau, bộ quy tắc bảo mật AWS ban đầu của bạn có thể bao gồm ba quy tắc đến và một quy tắc đi. Lần lượt, các nhóm này đến từ ba nhóm bảo mật riêng biệt — nhóm mặc định, một nhóm cho quyền truy cập HTTP và một nhóm cho quyền truy cập SSH:

Bộ quy tắc bảo mật ban đầu cho ứng dụng Django
Bộ quy tắc nhóm bảo mật ban đầu

Từ máy tính cục bộ của bạn, sau đó bạn có thể SSH vào phiên bản:

$ ssh -i ~/.ssh/<privkey>.pem ubuntu@<instance-public-ip-address>

Lệnh này giúp bạn đăng nhập vào máy ảo của mình với tư cách là người dùng ubuntu. Nơi đây, ~/.ssh/<privkey>.pemlà đường dẫn đến khóa riêng tư nằm trong bộ thông tin xác thực bảo mật mà bạn đã gắn với máy ảo. VM là nơi mã ứng dụng Django sẽ nằm.

Với điều đó, tất cả bạn nên sẵn sàng để tiếp tục xây dựng ứng dụng của mình.

Sử dụng WSGIServer của Django trong Phát triển

Trong phần này, bạn sẽ kiểm tra máy chủ web phát triển của Django bằng cách sử dụng httpie, một ứng dụng khách HTTP dòng lệnh tuyệt vời để kiểm tra các yêu cầu đối với ứng dụng web của bạn từ bảng điều khiển:

$ pwd
/home/ubuntu
$ source env/bin/activate
$ python -m pip install httpie

Bạn có thể tạo một bí danh cho phép bạn gửi GETyêu cầu sử dụng httpievào ứng dụng của bạn:

$ # Send GET request and follow 30x Location redirects
$ alias GET='http --follow --timeout 6'

Bí danh này GETđến một httpgọi với một số cờ mặc định. Bây giờ bạn có thể sử dụng GET docs.python.orgđể xem tiêu đề và nội dung phản hồi từ trang chủ của tài liệu Python.

Trước khi khởi động máy chủ phát triển Django, bạn có thể kiểm tra dự án Django của mình để biết các sự cố tiềm ẩn:

$ cd django-gunicorn-nginx/
$ python manage.py check
System check identified no issues (0 silenced).

Nếu việc kiểm tra của bạn không xác định được bất kỳ vấn đề nào, thì hãy yêu cầu máy chủ ứng dụng tích hợp của Django bắt đầu lắng nghe trên localhost, sử dụng cổng mặc định là 8000:

$ # Listen on 127.0.0.1:8000 in the background
$ nohup python manage.py runserver &
$ jobs -l
[1]+ 43689 Running                 nohup python manage.py runserver &

Sử dụng nohup <command> &thi hành commandtrong nền để bạn có thể tiếp tục sử dụng trình bao của mình. Bạn có thể dùng jobs -lđể xem mã định danh quy trình (PID) , cho phép bạn đưa quy trình lên nền trước hoặc chấm dứt nó. nohupsẽ chuyển hướng đầu ra tiêu chuẩn (stdout) lỗi tiêu chuẩn (stderr) đến tệp nohup.out.

Django’s runserverlần lượt, sử dụng cú pháp sau:

$ python manage.py runserver [address:port]

Nếu bạn rời khỏi address:portđối số không xác định như đã làm ở trên, Django sẽ mặc định lắng nghe localhost:8000. Bạn cũng có thể sử dụng lsoflệnh để xác minh trực tiếp hơn rằng một pythonlệnh đã được gọi để nghe trên cổng 8000:

$ sudo lsof -n -P -i TCP:8000 -s TCP:LISTEN
COMMAND   PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
python  43689 ubuntu    4u  IPv4  45944      0t0  TCP 127.0.0.1:8000 (LISTEN)

Tại thời điểm này trong hướng dẫn, ứng dụng của bạn chỉ đang nghe trên localhost , đây là địa chỉ 127.0.0.1. Nó chưa thể truy cập được từ trình duyệt, nhưng bạn vẫn có thể cung cấp cho nó khách truy cập đầu tiên bằng cách gửi cho nó một GETyêu cầu từ dòng lệnh trong chính máy ảo:

$ GET :8000/myapp/
HTTP/1.1 200 OK
Content-Length: 182
Content-Type: text/html; charset=utf-8
Date: Sat, 25 Sep 2021 00:11:38 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.10
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <title>My secure app</title>
  </head>
  <body>
    <p>Now this is some sweet HTML!</p>
  </body>
</html>

Tiêu đề Server: WSGIServer/0.2 CPython/3.8.10mô tả phần mềm đã tạo ra phản hồi. Trong trường hợp này, đó là phiên bản 0.2 của WSGIServercùng với CPython 3,8.10 .

WSGIServerkhông gì khác hơn là một lớp Python được xác định bởi Django để triển khai giao thức Python WSGI. Điều này có nghĩa là nó tuân theo Giao diện Cổng vào Máy chủ Web (WSGI) , đây là một tiêu chuẩn xác định cách thức để phần mềm máy chủ các ứng dụng web tương tác.

Trong ví dụ của chúng tôi cho đến nay, django-gunicorn-nginx/dự án là ứng dụng web. Vì bạn đang cung cấp ứng dụng đang được phát triển nên thực tế không có máy chủ web riêng biệt. Django sử dụng simple_servermô-đun, thực hiện một máy chủ HTTP nhẹ và kết hợp khái niệm máy chủ web với máy chủ ứng dụng thành một lệnh, runserver.

Tiếp theo, bạn sẽ thấy cách bắt đầu giới thiệu ứng dụng của mình một cách toàn diện bằng cách liên kết nó với một miền trong thế giới thực.

Đưa trang web của bạn lên mạng với Django, Gunicorn và Nginx

Tại thời điểm này, trang web của bạn có thể truy cập cục bộ trên máy ảo của bạn. Nếu bạn muốn trang web của mình có thể truy cập được ở một URL giống như thật, bạn cần phải xác nhận một tên miền và gắn nó với máy chủ web. Điều này cũng cần thiết để bật HTTPS, vì một số tổ chức phát hành chứng chỉ sẽ không cấp chứng chỉ cho địa chỉ IP trống hoặc miền phụ mà bạn không sở hữu. Trong phần này, bạn sẽ thấy cách đăng ký và định cấu hình miền.

Đặt địa chỉ IP công cộng tĩnh

Thật lý tưởng nếu bạn có thể trỏ cấu hình miền của mình tới một địa chỉ IP công cộng được đảm bảo không thay đổi. Một thuộc tính phụ tối ưu của máy ảo đám mây là địa chỉ IP công cộng của chúng có thể thay đổi nếu phiên bản được đưa vào trạng thái dừng. Ngoài ra, nếu bạn cần thay thế máy ảo hiện có của mình bằng một phiên bản mới vì một lý do nào đó, thì sự thay đổi dẫn đến địa chỉ IP sẽ có vấn đề.

Giải pháp cho tình huống khó xử này là gắn địa chỉ IP tĩnh với ví dụ:

Làm theo tài liệu của nhà cung cấp đám mây của bạn để liên kết địa chỉ IP tĩnh với máy ảo đám mây của bạn. Trong môi trường AWS được sử dụng cho ví dụ trong hướng dẫn này, địa chỉ IP đàn hồi 50.19.125.152 được liên kết với phiên bản EC2.

Với một IP công cộng ổn định hơn trước máy ảo của bạn, bạn đã sẵn sàng liên kết với một miền.

Liên kết với một miền

Trong phần này, bạn sẽ hướng dẫn cách mua, thiết lập và liên kết tên miền với ứng dụng hiện có của bạn.

Những ví dụ này sử dụng Namecheap , nhưng vui lòng không coi đó là sự xác nhận rõ ràng. Có nhiều tùy chọn khác, chẳng hạn như domain.com , GoDaddy Google Domains . Về tính chất chia phần có liên quan, Namecheap đã trả chính xác $ 0 để được giới thiệu là công ty đăng ký tên miền được lựa chọn trong hướng dẫn này.

Đây là cách bạn có thể bắt đầu:

  1. Tạo tài khoản trên Namecheap , đảm bảo thiết lập xác thực hai yếu tố (2FA).
  2. Từ trang chủ, hãy bắt đầu tìm kiếm một tên miền phù hợp với túi tiền của bạn. Bạn sẽ thấy rằng giá có thể thay đổi đáng kể với cả miền cấp cao nhất (TLD) và tên máy chủ.
  3. Mua miền khi bạn hài lòng với một lựa chọn.

Hướng dẫn này sử dụng miền supersecure.codes, nhưng bạn sẽ có của riêng bạn.

Sau khi có miền của mình, bạn sẽ muốn bật tính năng WithheldForPrivacy , chính thức được gọi là WhoisGuard . Điều này sẽ che thông tin cá nhân của bạn khi ai đó điều hành whoistìm kiếm trên miền của bạn. Đây là cách để làm điều này:

  1. Chọn Tài khoản → Danh sách miền .
  2. Chọn Quản lý bên cạnh miền của bạn.
  3. Bật WithheldForPrivacy .

Tiếp theo, đã đến lúc thiết lập bảng ghi DNS cho trang web của bạn. Mỗi bản ghi DNS sẽ trở thành một hàng trong cơ sở dữ liệu cho trình duyệt biết địa chỉ IP cơ bản nào mà một tên miền đủ điều kiện (FQDN) trỏ tới. Trong trường hợp này, chúng tôi muốn supersecure.codesđể định tuyến đến 50.19.125.152, địa chỉ IPv4 công khai mà tại đó máy ảo có thể được truy cập:

  1. Chọn Tài khoản → Danh sách miền .
  2. Chọn Quản lý bên cạnh miền của bạn.
  3. Chọn DNS nâng cao .
  4. Trong Bản ghi Máy chủ , thêm hai Bản ghi A cho miền của bạn.

Thêm Bản ghi A như sau, thay thế 50.19.125.152với địa chỉ IPv4 công khai của phiên bản của bạn:

Loại hình Chủ nhà Giá trị TTL
Một bản ghi @ 50.19.125.152 Tự động
Một bản ghi www 50.19.125.152 Tự động

Bản ghi A cho phép bạn liên kết tên miền hoặc tên miền phụ với địa chỉ IPv4 của máy chủ web nơi bạn phân phối ứng dụng của mình. Ở trên, trường Giá trị phải sử dụng địa chỉ IPv4 công khai của phiên bản VM của bạn.

Bạn có thể thấy rằng có hai biến thể cho trường Máy chủ :

  1. Sử dụng @trỏ đến miền gốc , supersecure.codestrong trường hợp này.
  2. Sử dụng wwwcó nghĩa là www.supersecure.codessẽ chỉ đến cùng một nơi như chỉ supersecure.codes. Các wwwvề mặt kỹ thuật là một tên miền phụ thể đưa người dùng đến cùng một nơi với tên miền ngắn hơn supersecure.codes.

Khi bạn đã thiết lập bảng bản ghi máy chủ DNS, bạn sẽ cần đợi tối đa ba mươi phút để các tuyến có hiệu lực. Bây giờ bạn có thể giết cái hiện có runserverquá trình:

$ jobs -l
[1]+ 43689 Running                 nohup python manage.py runserver &
$ kill 43689
[1]+  Done                    nohup python manage.py runserver

Bạn có thể xác nhận rằng quá trình này đã kết thúc với pgrephoặc bằng cách kiểm tra lại các công việc đang hoạt động:

$ pgrep runserver  # Empty
$ jobs -l  # Empty or 'Done'
$ sudo lsof -n -P -i TCP:8000 -s TCP:LISTEN  # Empty
$ rm nohup.out

Với những điều này, bạn cũng cần phải điều chỉnh cài đặt Django, ALLOWED_HOSTS, là tập hợp các tên miền mà bạn cho phép ứng dụng Django của mình phân phát:

# project/settings.py
# Replace 'supersecure.codes' with your domain
ALLOWED_HOSTS = [".supersecure.codes"]

Dấu chấm ở đầu ( .) là một ký tự đại diện của miền phụ , cho phép cả hai www.supersecure.codessupersecure.codes. Giữ chặt danh sách này để ngăn chặn các cuộc tấn công tiêu đề máy chủ .

Now you can restart the WSGIServer with one slight change:

$ nohup python manage.py runserver '0.0.0.0:8000' &

Notice the address:port argument is now 0.0.0.0:8000, while none was previously specified:

  • Chỉ định không address:port implies serving the app on localhost:8000. This means that the application was only accessible from within the VM itself. You could talk to it by calling httpie from the same IP address, but you couldn’t reach your application from the outside world.
  • Chỉ định một address:portcủa '0.0.0.0:8000'làm cho máy chủ của bạn có thể xem được với thế giới bên ngoài, mặc dù vẫn ở cổng 8000 theo mặc định. Các 0.0.0.0là viết tắt của “liên kết với tất cả các địa chỉ IP mà máy tính này hỗ trợ.” Trong trường hợp máy ảo đám mây có sẵn với một bộ điều khiển giao diện mạng (NIC) được đặt tên eth0, sử dụng 0.0.0.0hoạt động như một dự phòng cho địa chỉ IPv4 công cộng của máy.

Tiếp theo, bật đầu ra từ nohup.outđể xem mọi nhật ký đến từ WSGIServer của Django:

$ tail -f nohup.out

Bây giờ cho thời điểm của sự thật. Đã đến lúc cung cấp cho trang web của bạn khách truy cập đầu tiên. Từ máy cá nhân của bạn, nhập URL sau vào trình duyệt web:

http://www.supersecure.codes:8000/myapp/

Thay thế tên miền trên bằng tên miền của riêng bạn. Bạn sẽ thấy trang phản hồi nhanh chóng trong tất cả sự vinh quang của nó:

Bây giờ đây là một số HTML ngọt ngào!

Bạn có thể truy cập URL này — nhưng không cho người khác — vì quy tắc bảo mật đến mà bạn đã tạo trước đó.

Bây giờ quay trở lại shell của máy ảo của bạn. Trong đầu ra liên tục của tail -f nohup.out, bạn sẽ thấy một cái gì đó giống như dòng này:

[<date>] "GET /myapp/ HTTP/1.1" 200 182

Xin chúc mừng, bạn vừa thực hiện bước quan trọng đầu tiên để lưu trữ trang web của riêng mình! Tuy nhiên, hãy tạm dừng ở đây và lưu ý một vài lỗi lớn được nhúng trong URL http://www.supersecure.codes:8000/myapp/:

  • Trang web chỉ được cung cấp qua HTTP . Nếu không bật HTTPS, trang web của bạn về cơ bản không an toàn nếu bạn muốn truyền bất kỳ dữ liệu nhạy cảm nào từ máy khách đến máy chủ hoặc ngược lại. Sử dụng HTTP có nghĩa là các yêu cầu và phản hồi được gửi ở dạng văn bản thuần túy. Bạn sẽ sớm khắc phục điều đó.
  • The URL uses the non-standard port 8000 versus the standard default HTTP port number 80. It’s unconventional and a bit of an eyesore, but you can’t use 80 yet. That’s because port 80 is privileged, and a non-root user can’t—and shouldn’t—bind to it. Later on, you’ll introduce a tool into the mix that allows your app to be available on port 80.

If you check in your browser, you’ll see your browser URL bar hinting at this. If you’re using Firefox, a red lock icon will appear indicating that the connection is over HTTP rather than HTTPS:

HTTP page emphasizing insecure icon

Going forward, you want to legitimize the operation. You could start serving over standard port 80 for HTTP. Better yet, start serving HTTPS (443) and redirect HTTP requests there. You’ll see how to progress through these steps soon.

Replacing WSGIServer With Gunicorn

Bạn có muốn bắt đầu chuyển ứng dụng của mình sang trạng thái sẵn sàng cho thế giới bên ngoài không? Nếu vậy, bạn nên thay thế WSGIServer tích hợp của Django, là máy chủ ứng dụng được sử dụng bởi manage.py runserver, with a separate dedicated application server. But wait a minute: WSGIServer seemed to be working just fine. Why replace it?

Để trả lời câu hỏi này, bạn có thể đọc tài liệu Django nói gì:

KHÔNG SỬ DỤNG MÁY CHỦ NÀY TRONG THIẾT LẬP SẢN XUẤT. Nó chưa trải qua kiểm tra bảo mật hoặc kiểm tra hiệu suất. (Và đó là cách nó sẽ tồn tại. Chúng tôi đang kinh doanh việc tạo ra các khung công tác Web, không phải máy chủ Web, vì vậy việc cải thiện máy chủ này để có thể xử lý môi trường sản xuất nằm ngoài phạm vi của Django.) ( Nguồn )

Django là một khung công tác web , không phải một máy chủ web và những người bảo trì nó muốn làm rõ ràng sự khác biệt đó. Trong phần này, bạn sẽ thay thế Django’s runserverchỉ huy với Gunicorn . Gunicorn trước hết là một máy chủ ứng dụng Python WSGI và là một máy chủ đã được thử nghiệm trong trận chiến tại đó:

  • Nó nhanh chóng, được tối ưu hóa và được thiết kế để sản xuất.
  • Nó cung cấp cho bạn khả năng kiểm soát chi tiết hơn đối với chính máy chủ ứng dụng.
  • Nó có bản ghi đầy đủ hơn và có thể định cấu hình.
  • Nó đã được thử nghiệm tốt , đặc biệt cho chức năng của nó như một máy chủ ứng dụng.

Bạn có thể cài đặt Gunicorn thông qua pipvào môi trường ảo của bạn:

$ pwd
/home/ubuntu
$ source env/bin/activate
$ python -m pip install 'gunicorn==20.1.*'

Tiếp theo, bạn cần thực hiện một số cấp độ cấu hình. Điều thú vị về tệp cấu hình Gunicorn là nó chỉ cần là mã Python hợp lệ, với các tên biến tương ứng với các đối số. Bạn có thể lưu trữ nhiều tệp cấu hình Gunicorn trong một thư mục con của dự án:

$ cd ~/django-gunicorn-nginx
$ mkdir -pv config/gunicorn/
mkdir: created directory 'config'
mkdir: created directory 'config/gunicorn/'

Tiếp theo, mở tệp cấu hình phát triển, config/gunicorn/dev.pyvà thêm những thứ sau:

"""Gunicorn *development* config file"""

# Django WSGI application path in pattern MODULE_NAME:VARIABLE_NAME
wsgi_app = "project.wsgi:application"
# The granularity of Error log outputs
loglevel = "debug"
# The number of worker processes for handling requests
workers = 2
# The socket to bind
bind = "0.0.0.0:8000"
# Restart workers when code changes (development only!)
reload = True
# Write access and error info to /var/log
accesslog = errorlog = "/var/log/gunicorn/dev.log"
# Redirect stdout/stderr to log file
capture_output = True
# PID file so you can easily fetch process ID
pidfile = "/var/run/gunicorn/dev.pid"
# Daemonize the Gunicorn process (detach & enter background)
daemon = True

Trước khi bắt đầu Gunicorn, bạn nên tạm dừng runserverquá trình. Sử dụng jobsđể tìm nó và killđể ngăn chặn nó:

$ jobs -l
[1]+ 26374 Running                 nohup python manage.py runserver &
$ kill 26374
[1]+  Done                    nohup python manage.py runserver

Tiếp theo, đảm bảo rằng các thư mục nhật ký và PID tồn tại cho các giá trị được đặt trong tệp cấu hình Gunicorn ở trên:

$ sudo mkdir -pv /var/{log,run}/gunicorn/
mkdir: created directory '/var/log/gunicorn/'
mkdir: created directory '/var/run/gunicorn/'
$ sudo chown -cR ubuntu:ubuntu /var/{log,run}/gunicorn/
changed ownership of '/var/log/gunicorn/' from root:root to ubuntu:ubuntu
changed ownership of '/var/run/gunicorn/' from root:root to ubuntu:ubuntu

Với các lệnh này, bạn đã đảm bảo rằng các thư mục PID và nhật ký cần thiết tồn tại cho Gunicorn và chúng có thể ghi được bởi ubuntungười sử dụng.

Với điều đó, bạn có thể bắt đầu Gunicorn bằng cách sử dụng -ccờ để trỏ đến tệp cấu hình từ gốc dự án của bạn:

$ pwd
/home/ubuntu/django-gunicorn-nginx
$ source .DJANGO_SECRET_KEY
$ gunicorn -c config/gunicorn/dev.py

Điều này chạy gunicorntrong nền với tệp cấu hình phát triển dev.pymà bạn đã chỉ định ở trên. Cũng giống như trước đây, bây giờ bạn có thể theo dõi tệp đầu ra để xem đầu ra được ghi lại bởi Gunicorn:

$ tail -f /var/log/gunicorn/dev.log
[2021-09-27 01:29:50 +0000] [49457] [INFO] Starting gunicorn 20.1.0
[2021-09-27 01:29:50 +0000] [49457] [DEBUG] Arbiter booted
[2021-09-27 01:29:50 +0000] [49457] [INFO] Listening at: http://0.0.0.0:8000 (49457)
[2021-09-27 01:29:50 +0000] [49457] [INFO] Using worker: sync
[2021-09-27 01:29:50 +0000] [49459] [INFO] Booting worker with pid: 49459
[2021-09-27 01:29:50 +0000] [49460] [INFO] Booting worker with pid: 49460
[2021-09-27 01:29:50 +0000] [49457] [DEBUG] 2 workers

Bây giờ hãy truy cập lại URL trang web của bạn trong trình duyệt. Bạn vẫn cần cổng 8000:

http://www.supersecure.codes:8000/myapp/

Kiểm tra lại thiết bị đầu cuối máy ảo của bạn. Bạn sẽ thấy một hoặc nhiều dòng như sau từ tệp nhật ký của Gunicorn:

67.xx.xx.xx - - [27/Sep/2021:01:30:46 +0000] "GET /myapp/ HTTP/1.1" 200 182

Những dòng này là nhật ký truy cập cho bạn biết về các yêu cầu đến:

Thành phần Nghĩa
67.xx.xx.xx Địa chỉ IP của người dùng
27/Sep/2021:01:30:46 +0000 Dấu thời gian của yêu cầu
GET Yêu cầu phương thức
/myapp/ Đường dẫn URL
HTTP/1.1 Giao thức
200 Mã trạng thái phản hồi
182 Độ dài nội dung phản hồi

Loại trừ ở trên cho ngắn gọn là tác nhân người dùng , tác nhân này cũng có thể hiển thị trong nhật ký của bạn. Đây là một ví dụ từ trình duyệt Firefox trên macOS:

Mozilla/5.0 (Macintosh; Intel Mac OS X ...) Gecko/20100101 Firefox/92.0

Với Gunicorn và lắng nghe, đã đến lúc giới thiệu một máy chủ web hợp pháp vào phương trình.

Kết hợp Nginx

Tại thời điểm này, bạn đã hoán đổi runserverchỉ huy ủng hộ gunicornlàm máy chủ ứng dụng. Có một người chơi nữa để thêm vào chuỗi yêu cầu: một máy chủ web như Nginx .

Chờ đã — bạn đã thêm Gunicorn! Tại sao bạn cần thêm một cái gì đó mới vào hình ảnh? Lý do là vì Nginx và Gunicorn là hai thứ khác nhau, họ cùng tồn tại và hoạt động như một đội.

Nginx tự định nghĩa là một máy chủ web hiệu suất cao và một máy chủ proxy ngược. Nó đáng để phá vỡ điều này vì nó giúp giải thích mối quan hệ của Nginx với Gunicorn và Django.

Thứ nhất, Nginx là một máy chủ web trong đó nó có thể phục vụ các tệp cho người dùng web hoặc máy khách. Tệp là tài liệu theo nghĩa đen: HTML, CSS, PNG, PDF — bạn đặt tên cho nó. Ngày xưa, trước khi các framework như Django ra đời, một trang web thường hoạt động về cơ bản như một chế độ xem trực tiếp vào một hệ thống tệp. Trong đường dẫn URL, dấu gạch chéo thể hiện các thư mục trên một phần giới hạn của hệ thống tệp của máy chủ mà bạn có thể yêu cầu xem.

Lưu ý sự khác biệt nhỏ trong thuật ngữ:

  • Django là một khuôn khổ web . Nó cho phép bạn xây dựng ứng dụng web cốt lõi cung cấp nội dung thực tế trên trang web. Nó xử lý kết xuất HTML, xác thực, quản trị và logic phụ trợ.
  • Gunicorn là một máy chủ ứng dụng . Nó dịch các yêu cầu HTTP thành một thứ mà Python có thể hiểu được. Gunicorn triển khai Giao diện Cổng vào Máy chủ Web (WSGI) , là giao diện tiêu chuẩn giữa phần mềm máy chủ web và các ứng dụng web.
  • Nginx là một máy chủ web . Đó là trình xử lý công khai, được gọi chính thức hơn là proxy ngược , cho các yêu cầu đến và mở rộng quy mô đến hàng nghìn kết nối đồng thời.

Một phần vai trò của Nginx như một máy chủ web là nó có thể phục vụ các tệp tĩnh hiệu quả hơn . Điều này có nghĩa là, đối với các yêu cầu về nội dung tĩnh như hình ảnh, bạn có thể loại bỏ người trung gian là Django và yêu cầu Nginx hiển thị các tệp trực tiếp. Chúng ta sẽ đến bước quan trọng này sau trong hướng dẫn.

Nginx cũng là một máy chủ proxy ngược ở chỗ nó nằm giữa thế giới bên ngoài và ứng dụng Gunicorn / Django của bạn. Tương tự như cách bạn có thể sử dụng proxy để thực hiện các yêu cầu gửi đi , bạn có thể sử dụng proxy như Nginx để nhận chúng:

Cấu hình hoàn thiện của Nginx và Gunicorn
Hình ảnh: Python thực

Để bắt đầu sử dụng Nginx, hãy cài đặt nó và xác minh phiên bản của nó:

$ sudo apt-get install -y 'nginx=1.18.*'
$ nginx -v  # Display version info
nginx version: nginx/1.18.0 (Ubuntu)

Sau đó, bạn nên thay đổi quy tắc cho phép đến mà bạn đã đặt cho cổng 8000 thành cổng 80. Thay thế quy tắc đến cho TCP:8000với những điều sau đây:

Loại hình Giao thức Phạm vi cổng Nguồn
HTTP TCP 80 my-laptop-ip-address/32

Các quy tắc khác, chẳng hạn như quy tắc truy cập SSH, sẽ không thay đổi.

Bây giờ, hãy bắt đầu nginxdịch vụ và xác nhận rằng trạng thái của nó là running:

$ sudo systemctl start nginx
$ sudo systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
   Loaded: loaded (/lib/systemd/system/nginx.service; enabled; ...
   Active: active (running) since Mon 2021-09-27 01:37:04 UTC; 2min 49s ago
...

Bây giờ bạn có thể yêu cầu một URL trông quen thuộc:

http://supersecure.codes/

Đó là một sự khác biệt lớn so với những gì bạn đã có trước đây. Bạn không cần cổng 8000 trong URL nữa. Thay vào đó, cổng mặc định là cổng 80, trông bình thường hơn rất nhiều:

Chào mừng đến nginx!

Đây là một tính năng thân thiện của Nginx. Nếu bạn khởi động Nginx với cấu hình 0, nó sẽ cung cấp một trang cho bạn cho biết rằng nó đang lắng nghe. Bây giờ hãy thử /myapptại URL sau:

http://supersecure.codes/myapp/

Nhớ thay supersecure.codesvới tên miền của riêng bạn.

Bạn sẽ thấy phản hồi 404 và điều đó không sao:

Trang Nginx 404

Điều này là do bạn đang yêu cầu /myappđường dẫn qua cổng 80, đó là nơi Nginx, chứ không phải Gunicorn, đang lắng nghe. Tại thời điểm này, bạn có thiết lập sau:

  • Nginx đang nghe trên cổng 80.
  • Gunicorn đang nghe riêng trên cổng 8000.

Không có kết nối hoặc ràng buộc nào giữa hai thứ này cho đến khi bạn chỉ định nó. Nginx không biết rằng Gunicorn và Django có một số HTML tuyệt vời mà họ muốn cả thế giới nhìn thấy. Đó là lý do tại sao nó trả về một 404 Not Foundphản ứng. Bạn chưa thiết lập nó cho proxy tới Gunicorn và Django:

Nginx đã ngắt kết nối khỏi Gunicorn
Hình ảnh: Python thực

Bạn cần cung cấp cho Nginx một số cấu hình cơ bản để yêu cầu nó định tuyến các yêu cầu đến Gunicorn, sau đó sẽ đưa chúng đến Django. Mở /etc/nginx/sites-available/supersecurevà thêm nội dung sau:

server_tokens               off;
access_log                  /var/log/nginx/supersecure.access.log;
error_log                   /var/log/nginx/supersecure.error.log;

# This configuration will be changed to redirect to HTTPS later
server {
  server_name               .supersecure.codes;
  listen                    80;
  location / {
    proxy_pass              http://localhost:8000;
    proxy_set_header        Host $host;
  }
}

Hãy nhớ rằng bạn cần phải thay thế supersecuretrong tên tệp bằng tên máy chủ của trang web của bạn và đảm bảo thay thế server_namegiá trị của .supersecure.codesvới miền của riêng bạn, có tiền tố là dấu chấm.

Tệp này là “Hello World” của cấu hình proxy ngược Nginx . Nó cho Nginx biết cách cư xử:

  • Nghe trên cổng 80 để biết các yêu cầu sử dụng máy chủ lưu trữ supersecure.codesvà các miền phụ của nó.
  • Chuyển những yêu cầu đó cho http://localhost:8000, đó là nơi Gunicorn đang lắng nghe.

Các proxy_set_headerlĩnh vực này là quan trọng. Nó đảm bảo rằng Nginx đi qua HostTiêu đề yêu cầu HTTP do người dùng cuối gửi tới Gunicorn và Django. Nginx nếu không sẽ sử dụng Host: localhosttheo mặc định, bỏ qua Hosttrường tiêu đề do trình duyệt của người dùng cuối gửi.

Bạn có thể xác thực tệp cấu hình của mình bằng cách sử dụng nginx configtest:

$ sudo service nginx configtest /etc/nginx/sites-available/supersecure
 * Testing nginx configuration                                  [ OK ]

Các [ OK ]đầu ra chỉ ra rằng tệp cấu hình hợp lệ và có thể được phân tích cú pháp.

Bây giờ bạn cần liên kết tệp này với sites-enabledthư mục, thay thế supersecurevới tên miền trang web của bạn:

$ cd /etc/nginx/sites-enabled
$ # Note: replace 'supersecure' with your domain
$ sudo ln -s ../sites-available/supersecure .
$ sudo systemctl restart nginx

Trước khi yêu cầu trang web của bạn với httpie, bạn sẽ cần thêm một quy tắc bảo mật đầu vào nữa. Thêm quy tắc gửi đến sau:

Loại hình Giao thức Phạm vi cổng Nguồn
HTTP TCP 80 vm-static-ip-address/32

Quy tắc bảo mật này cho phép lưu lượng HTTP gửi đến từ địa chỉ IP công cộng (đàn hồi) của chính máy ảo. Điều đó thoạt đầu có vẻ quá mức cần thiết, nhưng bạn cần phải làm điều đó vì các yêu cầu bây giờ sẽ được chuyển qua Internet công cộng, có nghĩa là quy tắc tự tham chiếu sử dụng ID nhóm bảo mật sẽ không còn đủ nữa.

Bây giờ nó đang sử dụng Nginx làm giao diện người dùng của máy chủ web, hãy gửi lại một yêu cầu đến trang web:

$ GET http://supersecure.codes/myapp/
HTTP/1.1 200 OK
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Mon, 27 Sep 2021 19:54:19 GMT
Referrer-Policy: same-origin
Server: nginx
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <title>My secure app</title>
  </head>
  <body>
    <p>Now this is some sweet HTML!</p>
  </body>
</html>

Bây giờ Nginx đang đứng trước Django và Gunicorn, có một vài kết quả thú vị ở đây:

  • Nginx bây giờ trả về Servertiêu đề như Server: nginx, chỉ ra rằng Nginx là máy chủ web front-end mới. Cài đặt server_tokensđến một giá trị của offyêu cầu Nginx không phát ra phiên bản chính xác của nó, chẳng hạn như nginx/x.y.z (Ubuntu). Từ góc độ bảo mật, điều đó sẽ tiết lộ thông tin không cần thiết.
  • Nginx sử dụng chunkedcho Transfer-Encodingtiêu đề thay vì quảng cáo Content-Length.
  • Nginx cũng yêu cầu tiếp tục mở kết nối mạng với Connection: keep-alive.

Tiếp theo, bạn sẽ tận dụng Nginx cho một trong những tính năng cốt lõi của nó: khả năng phân phát các tệp tĩnh một cách nhanh chóng và hiệu quả.

Cung cấp tệp tĩnh trực tiếp với Nginx

Bây giờ bạn có các yêu cầu ủy quyền Nginx đối với ứng dụng Django của bạn. Quan trọng hơn, bạn cũng có thể sử dụng Nginx để phân phát trực tiếp các tệp tĩnh . Nếu bạn có DEBUG = TrueTrong project/settings.py, sau đó Django sẽ hiển thị các tệp, nhưng điều này hoàn toàn không hiệu quả và có thể không an toàn . Thay vào đó, bạn có thể yêu cầu máy chủ web của mình hiển thị chúng trực tiếp.

Các ví dụ phổ biến về tệp tĩnh bao gồm JavaScript cục bộ, hình ảnh và CSS — bất kỳ thứ gì mà Django không thực sự cần thiết như một phần của phương trình để hiển thị động nội dung phản hồi.

Để bắt đầu, từ trong thư mục dự án của bạn, hãy tạo một nơi để lưu giữ và theo dõi các tệp tĩnh JavaScript đang được phát triển:

$ pwd
/home/ubuntu/django-gunicorn-nginx
$ mkdir -p static/js

Bây giờ hãy mở một tệp mới static/js/greenlight.jsvà thêm JavaScript sau:

// Enlarge the #changeme element in green when hovered over
(function () {
    "use strict";
    function enlarge() {
        document.getElementById("changeme").style.color = "green";
        document.getElementById("changeme").style.fontSize = "xx-large";
        return false;
    }
    document.getElementById("changeme").addEventListener("mouseover", enlarge);
}());

JavaScript này sẽ làm cho một khối văn bản nổi lên với phông chữ màu xanh lá cây lớn nếu nó được di chuột qua. Vâng, đó là một số công việc tiên tiến của giao diện người dùng!

Tiếp theo, thêm cấu hình sau vào project/settings.py, đang cập nhật STATIC_ROOTvới tên miền của bạn:

STATIC_URL = "/static/"
# Note: Replace 'supersecure.codes' with your domain
STATIC_ROOT = "/var/www/supersecure.codes/static"
STATICFILES_DIRS = [BASE_DIR / "static"]

Bạn đang nói với Django’s collectstaticlệnh nơi tìm kiếm và đặt các tệp tĩnh kết quả được tổng hợp từ nhiều ứng dụng Django, bao gồm các ứng dụng tích hợp sẵn của Django, chẳng hạn như admin.

Cuối cùng nhưng không kém phần quan trọng, hãy sửa đổi HTML trong myapp/templates/myapp/home.htmlđể bao gồm JavaScript mà bạn vừa tạo:

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <title>My secure app</title>
  </head>
  <body>
    <p><span id="changeme">Now this is some sweet HTML!</span></p>
    <script src="/static/js/greenlight.js"></script>
  </body>
</html>

Bằng cách bao gồm /static/js/greenlight.jsscript, <span id="changeme">phần tử sẽ có một trình nghe sự kiện gắn liền với nó.

Bước tiếp theo là tạo một đường dẫn thư mục chứa nội dung tĩnh trong dự án của bạn để Nginx phân phát:

$ sudo mkdir -pv /var/www/supersecure.codes/static/
mkdir: created directory '/var/www/supersecure.codes'
mkdir: created directory '/var/www/supersecure.codes/static/'
$ sudo chown -cR ubuntu:ubuntu /var/www/supersecure.codes/
changed ownership of '/var/www/supersecure.codes/static' ... to ubuntu:ubuntu
changed ownership of '/var/www/supersecure.codes/' ... to ubuntu:ubuntu

Bây giờ chạy collectstaticvới tư cách là người dùng không phải root của bạn từ trong thư mục dự án của bạn:

$ pwd
/home/ubuntu/django-gunicorn-nginx
$ python manage.py collectstatic
129 static files copied to '/var/www/supersecure.codes/static'.

Cuối cùng, thêm một locationbiến cho /staticTrong /etc/nginx/sites-available/supersecure, tệp cấu hình trang web của bạn cho Nginx:

server {
  location / {
    proxy_pass          http://localhost:8000;
    proxy_set_header    Host $host;
    proxy_set_header    X-Forwarded-Proto $scheme;
  }

  location /static {
    autoindex on;
    alias /var/www/supersecure.codes/static/;
  }
}

Hãy nhớ rằng miền của bạn có thể không supersecure.codes, vì vậy bạn sẽ cần tùy chỉnh các bước này để hoạt động cho dự án của riêng bạn.

Bây giờ bạn nên tắt DEBUGchế độ trong dự án của bạn trong project/settings.py:

# project/settings.py
DEBUG = False

Gunicorn sẽ chọn thay đổi này kể từ khi bạn chỉ định reload = TrueTrong config/gunicorn/dev.py.

Sau đó khởi động lại Nginx:

$ sudo systemctl restart nginx

Bây giờ, hãy làm mới lại trang trang web của bạn và di chuột qua văn bản trang:

Kết quả của việc phóng to JavaScript được gọi khi di chuột qua

Đây là bằng chứng rõ ràng rằng hàm JavaScript enlarge()đã khởi động. Để có được kết quả này, trình duyệt phải yêu cầu /static/js/greenlight.js. Chìa khóa ở đây là trình duyệt lấy tệp đó trực tiếp từ Nginx mà không cần Nginx yêu cầu Django cung cấp.

Lưu ý điều gì đó khác biệt về quy trình ở trên: không nơi nào bạn thêm một tuyến hoặc chế độ xem URL Django mới để phân phối tệp JavaScript. Đó là bởi vì, sau khi chạy collectstatic, Django không còn chịu trách nhiệm xác định cách ánh xạ URL tới một chế độ xem phức tạp và hiển thị chế độ xem đó. Nginx có thể chuyển tệp trực tiếp vào trình duyệt.

Trên thực tế, nếu bạn điều hướng đến miền của mình tương đương với https://supersecure.codes/static/js/, bạn sẽ thấy chế độ xem dạng cây hệ thống tệp truyền thống của /staticđược tạo bởi Nginx. Điều này có nghĩa là phân phối các tệp tĩnh nhanh hơn và hiệu quả hơn.

Tại thời điểm này, bạn đã có một nền tảng tuyệt vời cho một trang web có thể mở rộng bằng cách sử dụng Django, Gunicorn và Nginx. Một bước tiến lớn nữa là bật HTTPS cho trang web của bạn, điều mà bạn sẽ làm tiếp theo.

Chuẩn bị sản xuất trang web của bạn với HTTPS

Bạn có thể bảo mật từ mức tốt đến mức cao bằng một vài bước nữa, bao gồm bật HTTPS và thêm một bộ tiêu đề giúp trình duyệt web hoạt động với trang web của bạn theo cách an toàn hơn. Việc HTTPS làm tăng độ tin cậy của trang web của bạn và đó là điều cần thiết nếu trang web của bạn sử dụng xác thực hoặc trao đổi dữ liệu nhạy cảm với người dùng.

Bật HTTPS

Để cho phép khách truy cập trang web của bạn qua HTTPS, bạn sẽ cần chứng chỉ SSL / TLS nằm trên máy chủ web của bạn. Chứng chỉ được cấp bởi Tổ chức phát hành chứng chỉ (CA). Trong hướng dẫn này, bạn sẽ sử dụng CA miễn phí có tên Let’s Encrypt . Để thực sự cài đặt chứng chỉ, bạn có thể sử dụng ứng dụng Certbot , ứng dụng này cung cấp cho bạn một loạt lời nhắc từng bước hoàn toàn dễ dàng.

Trước khi bắt đầu với Certbot, bạn có thể yêu cầu Nginx tắt TLS phiên bản 1.0 và 1.1 thay vì phiên bản 1.2 và 1.3. TLS 1.0 là end-of-life (EOL), trong khi TLS 1.1 chứa một số lỗ hổng bảo mật đã được TLS 1.2 khắc phục. Để làm điều này, hãy mở tệp /etc/nginx/nginx.conf. Tìm dòng sau:

# File: /etc/nginx/nginx.conf
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

Thay thế nó bằng các triển khai gần đây hơn:

# File: /etc/nginx/nginx.conf
ssl_protocols TLSv1.2 TLSv1.3;

Bạn có thể dùng nginx -tđể xác nhận rằng Nginx của bạn hỗ trợ phiên bản 1.3:

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Bây giờ bạn đã sẵn sàng cài đặt và sử dụng Certbot. Trên Ubuntu Focal (20.04), bạn có thể sử dụng snapđể cài đặt Certbot:

$ sudo snap install --classic certbot
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot

của Certbot hướng dẫn hướng dẫn để xem các bước cài đặt cho các hệ điều hành và máy chủ web khác nhau.

Trước khi bạn có thể lấy và cài đặt chứng chỉ HTTPS với certbot, có một thay đổi khác mà bạn cần thực hiện đối với các quy tắc nhóm bảo mật của máy ảo. Vì Let’s Encrypt yêu cầu kết nối Internet cho mục đích xác thực, bạn sẽ cần thực hiện bước quan trọng là mở trang web của mình với Internet công cộng.

Sửa đổi các quy tắc bảo mật đầu vào của bạn để phù hợp với những điều sau:

Tài liệu tham khảo Loại hình Giao thức Phạm vi cổng Nguồn
1 HTTP TCP 80 0.0.0.0/0
2 Phong tục Tất cả các Tất cả các security-group-id
3 SSH TCP 22 my-laptop-ip-address/32

Thay đổi quan trọng ở đây là quy tắc đầu tiên, cho phép lưu lượng HTTP qua cổng 80 từ tất cả các nguồn. Bạn có thể xóa quy tắc đến cho TCP:80đã đưa địa chỉ IP công cộng của máy ảo của bạn vào danh sách cho phép vì điều đó hiện đã dư thừa. Hai quy tắc còn lại không thay đổi.

Sau đó, bạn có thể đưa ra một lệnh nữa, certbot, để cài đặt chứng chỉ:

$ sudo certbot --nginx --rsa-key-size 4096 --no-redirect
Saving debug log to /var/log/letsencrypt/letsencrypt.log
...

Điều này tạo ra một chứng chỉ có kích thước khóa RSA là 4096 byte. Các --no-redirecttùy chọn nói với certbotđể không tự động áp dụng cấu hình liên quan đến chuyển hướng HTTP tự động sang HTTPS. Với mục đích minh họa, bạn sẽ sớm thấy cách tự thêm cái này.

Bạn sẽ trải qua một loạt các bước thiết lập, hầu hết các bước này đều phải tự giải thích, chẳng hạn như nhập địa chỉ email của bạn. Khi được nhắc nhập tên miền của bạn, hãy nhập tên miền và wwwtên miền phụ được phân tách bằng dấu phẩy:

www.supersecure.codes,supersecure.codes

Khi bạn đã hoàn thành các bước, bạn sẽ thấy một thông báo thành công như sau:

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/supersecure.codes/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/supersecure.codes/privkey.pem
This certificate expires on 2021-12-26.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this
  certificate in the background.

Deploying certificate
Successfully deployed certificate for supersecure.codes
  to /etc/nginx/sites-enabled/supersecure
Successfully deployed certificate for www.supersecure.codes
  to /etc/nginx/sites-enabled/supersecure
Congratulations! You have successfully enabled HTTPS
  on https://supersecure.codes and https://www.supersecure.codes

nếu bạn catra tệp cấu hình tương đương với /etc/nginx/sites-available/supersecure, bạn sẽ thấy điều đó certbotđã tự động thêm một nhóm các dòng liên quan đến SSL:

# Nginx configuration: /etc/nginx/sites-available/supersecure
server {
  server_name               .supersecure.codes;
  listen                    80;
  location / {
    proxy_pass              http://localhost:8000;
    proxy_set_header        Host $host;
  }

  location /static {
    autoindex on;
    alias /var/www/supersecure.codes/static/;
  }

  listen 443 ssl;
  ssl_certificate /etc/letsencrypt/live/www.supersecure.codes/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/www.supersecure.codes/privkey.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

Đảm bảo rằng Nginx tiếp thu những thay đổi đó:

$ sudo systemctl reload nginx

Để truy cập trang web của bạn qua HTTPS, bạn cần bổ sung một quy tắc bảo mật cuối cùng. Bạn cần cho phép lưu lượng truy cập qua TCP:443, trong đó 443 là cổng mặc định cho HTTPS. Sửa đổi các quy tắc bảo mật đầu vào của bạn để phù hợp với những điều sau:

Tài liệu tham khảo Loại hình Giao thức Phạm vi cổng Nguồn
1 HTTPS TCP 443 0.0.0.0/0
2 HTTP TCP 80 0.0.0.0/0
2 Phong tục Tất cả các Tất cả các security-group-id
3 SSH TCP 22 my-laptop-ip-address/32

Mỗi quy tắc này có một mục đích cụ thể:

  1. Quy tắc 1 cho phép lưu lượng HTTPS qua cổng 443 từ tất cả các nguồn.
  2. Quy tắc 2 cho phép lưu lượng truy cập HTTP qua cổng 80 từ tất cả các nguồn.
  3. Quy tắc 3 cho phép lưu lượng truy cập vào từ các giao diện và phiên bản mạng được gán cho cùng một nhóm bảo mật, sử dụng ID nhóm bảo mật làm nguồn. Đây là một quy tắc có trong nhóm bảo mật AWS mặc định mà bạn nên gắn với phiên bản của mình.
  4. Quy tắc 4 cho phép bạn truy cập máy ảo của mình thông qua SSH từ máy tính cá nhân của bạn.

Bây giờ, điều hướng lại trang web của bạn trong một trình duyệt, nhưng có một điểm khác biệt chính. Còn hơn là http, chỉ định httpsnhư giao thức:

https://www.supersecure.codes/myapp/

Nếu tất cả đều ổn, bạn sẽ thấy một trong những kho báu tuyệt đẹp của cuộc sống, đó là trang web của bạn đang được phân phối qua HTTPS:

Kết nối với ứng dụng Django của bạn qua HTTPS

Nếu bạn đang sử dụng Firefox và nhấp vào biểu tượng ổ khóa, bạn có thể xem thông tin chi tiết hơn về chứng chỉ liên quan đến việc đảm bảo kết nối:

Bạn được kết nối an toàn với trang web này

Bạn đang tiến gần hơn một bước tới trang web an toàn. Tại thời điểm này, trang web vẫn có thể truy cập qua HTTP cũng như HTTPS. Điều đó tốt hơn trước đây, nhưng vẫn không phải là lý tưởng.

Chuyển hướng HTTP sang HTTPS

Trang web của bạn hiện có thể truy cập qua cả HTTP và HTTPS. Với HTTPS đang hoạt động, bạn có thể tắt HTTP — hoặc ít nhất là đến gần với nó trong thực tế. Bạn có thể thêm một số tính năng để tự động định tuyến bất kỳ khách truy cập nào cố gắng truy cập trang web của bạn qua HTTP đến phiên bản HTTPS. Chỉnh sửa tương đương của bạn với /etc/nginx/sites-available/supersecure:

# Nginx configuration: /etc/nginx/sites-available/supersecure
server {
  server_name               .supersecure.codes;
  listen                    80;
  return                    307 https://$host$request_uri;
}

server {
  location / {
    proxy_pass              http://localhost:8000;
    proxy_set_header        Host $host;
  }

  location /static {
    autoindex on;
    alias /var/www/supersecure.codes/static/;
  }

  listen 443 ssl;
  ssl_certificate /etc/letsencrypt/live/www.supersecure.codes/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/www.supersecure.codes/privkey.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

The added block tells the server to redirect the browser or client to the HTTPS version of any HTTP URL. You can verify that this configuration is valid:

$ sudo service nginx configtest /etc/nginx/sites-available/supersecure
 * Testing nginx configuration                                  [ OK ]

Then, tell nginx to reload the configuration:

$ sudo systemctl reload nginx

Sau đó, gửi một GETyêu cầu với --allgắn cờ cho URL HTTP của ứng dụng của bạn để hiển thị bất kỳ chuỗi chuyển hướng nào:

$ GET --all http://supersecure.codes/myapp/
HTTP/1.1 307 Temporary Redirect
Connection: keep-alive
Content-Length: 164
Content-Type: text/html
Date: Tue, 28 Sep 2021 02:16:30 GMT
Location: https://supersecure.codes/myapp/
Server: nginx

<html>
<head><title>307 Temporary Redirect</title></head>
<body bgcolor="white">
<center><h1>307 Temporary Redirect</h1></center>
<hr><center>nginx</center>
</body>
</html>

HTTP/1.1 200 OK
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Tue, 28 Sep 2021 02:16:30 GMT
Referrer-Policy: same-origin
Server: nginx
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <title>My secure app</title>
  </head>
  <body>
    <p><span id="changeme">Now this is some sweet HTML!</span></p>
    <script src="/static/js/greenlight.js"></script>
  </body>
</html>

Bạn có thể thấy rằng thực sự có hai phản hồi ở đây:

  1. Yêu cầu ban đầu nhận được mã trạng thái 307 chuyển hướng đến phiên bản HTTPS.
  2. Yêu cầu thứ hai được thực hiện với cùng một URI nhưng với lược đồ HTTPS chứ không phải HTTP. Lần này, nó nhận được nội dung trang mà nó đang tìm kiếm với 200 OKphản ứng.

Tiếp theo, bạn sẽ thấy cách vượt qua một bước ngoài cấu hình chuyển hướng bằng cách giúp các trình duyệt ghi nhớ lựa chọn đó.

Tiến thêm một bước với HSTS

Có một lỗ hổng nhỏ trong thiết lập chuyển hướng này khi được sử dụng riêng lẻ:

Khi người dùng nhập tên miền web theo cách thủ công (cung cấp tên miền không có tiền tố http: // hoặc https: //) hoặc theo liên kết http: // thuần túy, yêu cầu đầu tiên đến trang web được gửi không được mã hóa, sử dụng HTTP thuần túy.

Hầu hết các trang web được bảo mật ngay lập tức gửi lại một chuyển hướng để nâng cấp người dùng lên kết nối HTTPS, nhưng kẻ tấn công có vị trí tốt có thể kết hợp một cuộc tấn công ở giữa (MITM) để chặn yêu cầu HTTP ban đầu và có thể kiểm soát phiên của người dùng từ sau đó tiếp tục. ( Nguồn )

Để giảm bớt điều này, bạn có thể thêm chính sách HSTS để thông báo cho các trình duyệt thích HTTPS ngay cả khi người dùng cố gắng sử dụng HTTP. Dưới đây là sự khác biệt nhỏ giữa việc chỉ sử dụng chuyển hướng so với việc thêm tiêu đề HSTS cùng với nó:

  • Với chuyển hướng đơn giản từ HTTP sang HTTPS , máy chủ sẽ trả lời trình duyệt bằng cách nói, “Hãy thử lại lần nữa, nhưng với HTTPS.” Nếu trình duyệt thực hiện 1.000 yêu cầu HTTP, nó sẽ được thông báo 1.000 lần để thử lại với HTTPS.
  • Với tiêu đề HSTS , trình duyệt thực hiện công việc dự phòng là thay thế HTTP bằng HTTPS một cách hiệu quả sau yêu cầu đầu tiên. Không có chuyển hướng. Trong trường hợp thứ hai, bạn có thể coi trình duyệt đang nâng cấp kết nối. Khi người dùng yêu cầu trình duyệt của họ truy cập phiên bản HTTP của trang web của bạn, trình duyệt của họ sẽ trả lời cộc lốc, “Không, tôi đang đưa bạn đến phiên bản HTTPS.”

Để khắc phục điều này, bạn có thể yêu cầu Django đặt Strict-Transport-Securityđầu trang. Thêm những dòng này vào dự án của bạn settings.py:

# Add to project/settings.py
SECURE_HSTS_SECONDS = 30  # Unit is seconds; *USE A SMALL VALUE FOR TESTING!*
SECURE_HSTS_PRELOAD = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

Hãy lưu ý rằng SECURE_HSTS_SECONDSgiá trị tồn tại trong thời gian ngắn ở 30 giây. Đó là cố ý trong ví dụ này. Khi bạn chuyển sang sản xuất thực, bạn nên tăng giá trị này. Trang web Security Headers đề xuất giá trị tối thiểu là 2,592,000, tương đương với 30 ngày.

Khi bạn đã sẵn sàng tham gia, bạn sẽ cần thêm một dòng cấu hình Nginx nữa. Chỉnh sửa tương đương của bạn với /etc/nginx/sites-available/supersecuređể thêm một proxy_set_headerchỉ thị:

  location / {
    proxy_pass          http://localhost:8000;
    proxy_set_header    Host $host;
    proxy_set_header    X-Forwarded-Proto $scheme;
  }

Sau đó, yêu cầu Nginx tải lại cấu hình đã cập nhật:

$ sudo systemctl reload nginx

Hiệu ứng của điều này được thêm vào proxy_set_headerlà để Nginx gửi cho Django tiêu đề sau được bao gồm trong các yêu cầu trung gian ban đầu được gửi đến máy chủ web thông qua HTTPS trên cổng 443:

X-Forwarded-Proto: https

Điều này trực tiếp kết nối với SECURE_PROXY_SSL_HEADERgiá trị mà bạn đã thêm ở trên vào project/settings.py. Điều này là cần thiết vì Nginx thực sự gửi các yêu cầu HTTP đơn giản đến Gunicorn / Django, vì vậy Django không có cách nào khác để biết liệu yêu cầu ban đầu có phải là HTTPS hay không. Kể từ khi locationchặn từ tệp cấu hình Nginx ở trên dành cho cổng 443 (HTTPS), tất cả các yêu cầu đến qua cổng này sẽ cho Django biết rằng chúng thực sự là HTTPS.

Tài liệu Django giải thích điều này khá tốt:

Tuy nhiên, nếu ứng dụng Django của bạn có proxy, proxy có thể bị “nuốt” cho dù yêu cầu ban đầu có sử dụng HTTPS hay không. Nếu có kết nối không phải HTTPS giữa proxy và Django thì is_secure()sẽ luôn trở lại False—Còn đối với các yêu cầu được thực hiện qua HTTPS bởi người dùng cuối. Ngược lại, nếu có kết nối HTTPS giữa proxy và Django thì is_secure()sẽ luôn trở lại True—May cả đối với các yêu cầu được thực hiện ban đầu qua HTTP. ( Nguồn )

Làm thế nào bạn có thể kiểm tra xem tiêu đề này đang hoạt động? Đây là một cách thanh lịch cho phép bạn ở trong trình duyệt của mình:

  1. Trong trình duyệt của bạn, hãy mở các công cụ dành cho nhà phát triển. Điều hướng đến tab hiển thị hoạt động mạng. Trong Firefox, đây là Nhấp chuột phải → Kiểm tra phần tử → Mạng .
  2. Làm mới trang. Lúc đầu, bạn sẽ thấy một 307 Temporary Redirectphản hồi như một phần của chuỗi phản hồi. Đây là lần đầu tiên trình duyệt của bạn nhìn thấy Strict-Transport-Securityđầu trang.
  3. Thay đổi URL trong trình duyệt của bạn trở lại phiên bản HTTP và yêu cầu lại trang. Nếu bạn đang ở trong Chrome, bây giờ bạn sẽ thấy 307 Internal Redirect. Trong Firefox, bạn sẽ thấy một 200 OKphản hồi vì trình duyệt của bạn tự động chuyển thẳng đến một yêu cầu HTTPS ngay cả khi bạn cố gắng yêu cầu trình duyệt sử dụng HTTP. Mặc dù các trình duyệt hiển thị chúng khác nhau, nhưng cả hai phản hồi này đều cho thấy rằng trình duyệt đã thực hiện chuyển hướng tự động.

Nếu bạn đang theo dõi cùng với Firefox, bạn sẽ thấy một cái gì đó như sau:

200 phản hồi OK ngay lập tức với tiêu đề HSTS

Cuối cùng, bạn cũng có thể xác minh sự hiện diện của người đứng đầu bằng một yêu cầu từ bảng điều khiển:

$ GET -ph https://supersecure.codes/myapp/
...
Strict-Transport-Security: max-age=30; includeSubDomains; preload

Đây là bằng chứng cho thấy bạn đã thiết lập Strict-Transport-Securitytiêu đề sử dụng các giá trị tương ứng trong project/settings.py. Khi bạn đã sẵn sàng, bạn có thể tăng max-agenhưng hãy nhớ rằng điều này sẽ không thể đảo ngược yêu cầu trình duyệt nâng cấp HTTP trong khoảng thời gian đó.

Đặt Referrer-PolicyTiêu đề

Django 3.x cũng bổ sung khả năng điều khiển Referrer-Policyđầu trang. Bạn có thể chỉ định SECURE_REFERRER_POLICYTrong project/settings.py:

# Add to project/settings.py
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"

Cài đặt này hoạt động như thế nào? Khi bạn theo một liên kết từ trang A đến trang B , yêu cầu của bạn đến trang B chứa URL của trang A dưới tiêu đề Referer. Máy chủ đặt Referrer-Policytiêu đề mà bạn có thể đặt trong Django thông qua SECURE_REFERRER_POLICY, kiểm soát thời điểm và lượng thông tin được chuyển tiếp đến trang web mục tiêu. SECURE_REFERRER_POLICYcó thể nhận một số giá trị được công nhận mà bạn có thể đọc chi tiết trong tài liệu của Mozilla .

Ví dụ, nếu bạn sử dụng "strict-origin-when-cross-origin"và trang hiện tại của người dùng là https://example.com/page, các Referertiêu đề bị hạn chế theo những cách sau:

Trang đích RefererTiêu đề
https://example.com/otherpage https://example.com/page
https://mozilla.org https://example.com/
http://example.org (đích HTTP) [Không có]

Đây là những gì sẽ xảy ra theo từng trường hợp, giả sử trang của người dùng hiện tại là https://example.com/page:

  • Nếu người dùng theo một liên kết đến https://example.com/otherpage, Referersẽ bao gồm đường dẫn đầy đủ của trang hiện tại.
  • Nếu người dùng theo một liên kết đến miền riêng biệt của https://mozilla.org, Referersẽ loại trừ đường dẫn của trang hiện tại.
  • Nếu người dùng theo một liên kết đến http://example.orgvới một http://giao thức, Referersẽ trống.

Nếu bạn thêm dòng này vào project/settings.pyvà yêu cầu lại trang chủ ứng dụng của bạn, sau đó bạn sẽ thấy một người mới tham gia:

$ GET -ph https://supersecure.codes/myapp/  # -ph: Show response headers only
HTTP/1.1 200 OK
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Tue, 28 Sep 2021 02:31:36 GMT
Referrer-Policy: strict-origin-when-cross-origin
Server: nginx
Strict-Transport-Security: max-age=30; includeSubDomains; preload
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

Trong phần này, bạn đã thực hiện thêm một bước nữa để bảo vệ quyền riêng tư của người dùng. Tiếp theo, bạn sẽ thấy cách khóa lỗ hổng bảo mật trên trang web của mình trước các cuộc tấn công tập lệnh xuyên trang (XSS) và chèn dữ liệu.

Thêm một Content-Security-Policy(CSP) Tiêu đề

Một tiêu đề phản hồi HTTP quan trọng nữa mà bạn có thể thêm vào trang web của mình là Content-Security-Policy(CSP) tiêu đề, giúp ngăn chặn tạo tập lệnh giữa các trang web (XSS) và các cuộc tấn công đưa vào dữ liệu. Django nguyên bản không hỗ trợ điều này, nhưng bạn có thể cài đặt django-csp, một tiện ích mở rộng phần mềm trung gian nhỏ được phát triển bởi Mozilla:

$ python -m pip install django-csp

Để bật tiêu đề với giá trị mặc định của nó, hãy thêm dòng đơn này vào project/settings.pydưới cái hiện có MIDDLEWAREĐịnh nghĩa:

# project/settings.py
MIDDLEWARE += ["csp.middleware.CSPMiddleware"]

Làm thế nào bạn có thể đưa điều này vào thử nghiệm? Chà, bạn có thể đưa một liên kết vào một trong các trang HTML của mình và xem liệu trình duyệt có cho phép nó tải với phần còn lại của trang hay không.

Chỉnh sửa mẫu tại myapp/templates/myapp/home.htmlđể bao gồm liên kết đến Normalize.css , là tệp CSS giúp trình duyệt hiển thị tất cả các phần tử nhất quán hơn và phù hợp với các tiêu chuẩn hiện đại:

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <title>My secure app</title>
    <link rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/[email protected]/normalize.css"
    >
  </head>
  <body>
    <p><span id="changeme">Now this is some sweet HTML!</span></p>
    <script src="/static/js/greenlight.js"></script>
  </body>
</html>

Bây giờ, hãy yêu cầu trang trong trình duyệt có bật các công cụ dành cho nhà phát triển. Bạn sẽ thấy một lỗi như sau trong bảng điều khiển:

Cài đặt của trang đã chặn việc tải tài nguyên

Ồ, ồ. Bạn đang bỏ lỡ sức mạnh của chuẩn hóa vì trình duyệt của bạn không tải normalize.css. Đây là lý do tại sao nó không tải:

  • Của bạn project/settings.pybao gồm CSPMiddlewareở Django’s MIDDLEWARE. Bao gồm CSPMiddlewaređặt tiêu đề thành mặc định Content-Security-Policygiá trị, đó là default-src 'self', ở đâu 'self'nghĩa là tên miền riêng của trang web của bạn. Trong hướng dẫn này, đó là supersecure.codes.
  • Trình duyệt của bạn tuân theo quy tắc này và cấm cdn.jsdelivr.netkhỏi tải. CSP là một từ chối mặc định .

Bạn phải chọn tham gia và cho phép rõ ràng trình duyệt của khách hàng tải các liên kết nhất định được nhúng trong các phản hồi từ trang web của bạn. Để khắc phục điều này, hãy thêm cài đặt sau vào project/settings.py:

# project/settings.py
# Allow browsers to load normalize.css from cdn.jsdelivr.net
CSP_STYLE_SRC = ["'self'", "cdn.jsdelivr.net"]

Tiếp theo, hãy thử yêu cầu lại trang của trang web của bạn:

$ GET -ph https://supersecure.codes/myapp/
HTTP/1.1 200 OK
Connection: keep-alive
Content-Encoding: gzip
Content-Security-Policy: default-src 'self'; style-src 'self' cdn.jsdelivr.net
Content-Type: text/html; charset=utf-8
Date: Tue, 28 Sep 2021 02:37:19 GMT
Referrer-Policy: strict-origin-when-cross-origin
Server: nginx
Strict-Transport-Security: max-age=30; includeSubDomains; preload
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

Lưu ý rằng style-srcchỉ định 'self' cdn.jsdelivr.netnhư một phần của giá trị cho Content-Security-Policyđầu trang. Điều này có nghĩa là trình duyệt chỉ cho phép các biểu định kiểu từ hai miền:

  1. supersecure.codes ( 'self')
  2. cdn.jsdelivr.net

Các style-srcchỉ thị là một trong nhiều chỉ thị có thể là một phần của Content-Security-Policy. Có nhiều người khác , chẳng hạn như img-src, chỉ định các nguồn hình ảnh và biểu tượng yêu thích hợp lệ, và script-src, xác định các nguồn hợp lệ cho JavaScript .

Mỗi trong số này có một cài đặt tương ứng cho django-csp. Ví dụ, img-srcscript-srcđược thiết lập bởi CSP_IMG_SRCCSP_SCRIPT_SRC, tương ứng. Bạn có thể kiểm tra django-csptài liệu cho danh sách đầy đủ.

Đây là mẹo cuối cùng về tiêu đề CSP: Đặt nó sớm! Khi mọi thứ xảy ra sau này, sẽ dễ dàng hơn để xác định lý do, vì bạn có thể dễ dàng cô lập tính năng hoặc liên kết bạn đã thêm mà không tải vì bạn không có chỉ thị CSP tương ứng được cập nhật.

Các bước cuối cùng để triển khai sản xuất

Bây giờ, bạn sẽ thực hiện một số bước cuối cùng mà bạn có thể thực hiện khi bắt đầu triển khai ứng dụng của mình.

Trước tiên, hãy đảm bảo rằng bạn đã đặt DEBUG = Falsetrong dự án của bạn settings.pynếu bạn chưa làm như vậy. Điều này đảm bảo rằng thông tin gỡ lỗi phía máy chủ không bị rò rỉ trong trường hợp lỗi phía máy chủ 5xx.

Thứ hai, chỉnh sửa SECURE_HSTS_SECONDStrong dự án của bạn settings.pyđể tăng thời gian hết hạn cho Strict-Transport-Securitytiêu đề từ 30 giây đến 30 ngày được đề xuất, tương đương với 2,592,000 giây:

# Add to project/settings.py
SECURE_HSTS_SECONDS = 2_592_000  # 30 days

Tiếp theo, khởi động lại Gunicorn bằng tệp cấu hình sản xuất. Thêm các nội dung sau vào config/gunicorn/prod.py:

"""Gunicorn *production* config file"""

import multiprocessing

# Django WSGI application path in pattern MODULE_NAME:VARIABLE_NAME
wsgi_app = "project.wsgi:application"
# The number of worker processes for handling requests
workers = multiprocessing.cpu_count() * 2 + 1
# The socket to bind
bind = "0.0.0.0:8000"
# Write access and error info to /var/log
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
# Redirect stdout/stderr to log file
capture_output = True
# PID file so you can easily fetch process ID
pidfile = "/var/run/gunicorn/prod.pid"
# Daemonize the Gunicorn process (detach & enter background)
daemon = True

Tại đây, bạn đã thực hiện một số thay đổi:

  • Bạn đã tắt reloadtính năng được sử dụng trong phát triển.
  • Bạn đã đặt số lượng công nhân thành một hàm của số lượng CPU của máy ảo thay vì mã hóa nó.
  • Bạn đã cho phép loglevelmặc định thành "info"thay vì dài dòng hơn "debug".

Bây giờ bạn có thể dừng quy trình Gunicorn hiện tại và bắt đầu một quy trình mới, thay thế tệp cấu hình phát triển bằng tệp đối chiếu sản xuất của nó:

$ # Stop existing Gunicorn dev server if it is running
$ sudo killall gunicorn

$ # Restart Gunicorn with production config file
$ gunicorn -c config/gunicorn/prod.py

Sau khi thực hiện thay đổi này, bạn không cần phải khởi động lại Nginx vì nó chỉ chuyển các yêu cầu giống nhau address:hostvà không nên có bất kỳ thay đổi rõ ràng nào. Tuy nhiên, chạy Gunicorn với cài đặt theo định hướng sản xuất sẽ tốt hơn về lâu dài khi ứng dụng mở rộng quy mô.

Cuối cùng, hãy đảm bảo rằng bạn đã tạo đầy đủ tệp Nginx của mình. Đây là tệp đầy đủ, bao gồm tất cả các thành phần bạn đã thêm cho đến nay, cũng như một số giá trị bổ sung:

# File: /etc/nginx/sites-available/supersecure
# This file inherits from the http directive of /etc/nginx/nginx.conf

# Disable emitting nginx version in the "Server" response header field
server_tokens             off;

# Use site-specific access and error logs
access_log                /var/log/nginx/supersecure.access.log;
error_log                 /var/log/nginx/supersecure.error.log;

# Return 444 status code & close connection if no Host header present
server {
  listen                  80 default_server;
  return                  444;
}

# Redirect HTTP to HTTPS
server {
  server_name             .supersecure.codes;
  listen                  80;
  return                  307 https://$host$request_uri;
}

server {

  # Pass on requests to Gunicorn listening at http://localhost:8000
  location / {
    proxy_pass            http://localhost:8000;
    proxy_set_header      Host $host;
    proxy_set_header      X-Forwarded-Proto $scheme;
    proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_redirect        off;
  }

  # Serve static files directly
  location /static {
    autoindex             on;
    alias                 /var/www/supersecure.codes/static/;
  }

  listen 443 ssl;
  ssl_certificate /etc/letsencrypt/live/www.supersecure.codes/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/www.supersecure.codes/privkey.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

Như một bản cập nhật mới, các quy tắc bảo mật đầu vào được liên kết với máy ảo của bạn phải có một thiết lập nhất định:

Loại hình Giao thức Phạm vi cổng Nguồn
HTTPS TCP 443 0.0.0.0/0
HTTP TCP 80 0.0.0.0/0
Phong tục Tất cả các Tất cả các security-group-id
SSH TCP 22 my-laptop-ip-address/32

Kết hợp tất cả những điều đó lại với nhau, bộ quy tắc bảo mật AWS cuối cùng của bạn bao gồm bốn quy tắc đến và một quy tắc đi:

Bộ quy tắc bảo mật cuối cùng cho ứng dụng Django
Bộ quy tắc nhóm bảo mật cuối cùng

So sánh ở trên với bộ quy tắc bảo mật ban đầu của bạn. Lưu ý rằng bạn đã đánh mất quyền truy cập TCP:8000, nơi phiên bản phát triển của ứng dụng Django đã được phân phối và mở ra quyền truy cập Internet qua HTTP và HTTPS trên các cổng 80 và 443, tương ứng.

Trang web của bạn hiện đã sẵn sàng cho giờ chiếu:

Cấu hình hoàn thiện của Nginx và Gunicorn
Hình ảnh: Python thực

Bây giờ bạn đã đặt tất cả các thành phần lại với nhau, ứng dụng của bạn có thể truy cập thông qua Nginx qua HTTPS trên cổng 443. Các yêu cầu HTTP trên cổng 80 được chuyển hướng đến HTTPS. Bản thân các thành phần Django và Gunicorn không tiếp xúc với Internet công cộng mà nằm sau proxy ngược Nginx.

Kiểm tra bảo mật HTTPS của trang web của bạn

Trang web của bạn bây giờ an toàn hơn đáng kể so với khi bạn bắt đầu hướng dẫn này, nhưng đừng coi thường lời tôi. Có một số công cụ sẽ cung cấp cho bạn đánh giá khách quan về các tính năng liên quan đến bảo mật trên trang web của bạn, tập trung vào tiêu đề phản hồi và HTTPS.

Đầu tiên là Tiêu đề bảo mật , cung cấp xếp hạng cho chất lượng của tiêu đề phản hồi HTTP quay lại từ trang web của bạn. Nếu bạn đã theo dõi, trang web của bạn sẽ sẵn sàng đạt xếp hạng A hoặc tốt hơn ở đó.

Thứ hai là SSL Labs , sẽ thực hiện phân tích sâu về cấu hình máy chủ web của bạn vì nó liên quan đến SSL / TLS . Nhập tên miền của trang web của bạn và Phòng thí nghiệm SSL sẽ trả về điểm dựa trên sức mạnh của nhiều yếu tố liên quan đến SSL / TLS. Nếu bạn đã gọi certbotvới --rsa-key-size 4096và tắt TLS 1.0 và 1.1 thay vì 1.2 và 1.3, bạn sẽ được thiết lập phù hợp để nhận được xếp hạng A + từ SSL Labs.

Để kiểm tra, bạn cũng có thể yêu cầu URL HTTPS của trang web của mình từ dòng lệnh để xem tổng quan đầy đủ về các thay đổi mà bạn đã thêm trong suốt hướng dẫn này:

$ GET https://supersecure.codes/myapp/
HTTP/1.1 200 OK
Connection: keep-alive
Content-Encoding: gzip
Content-Security-Policy: style-src 'self' cdn.jsdelivr.net; default-src 'self'
Content-Type: text/html; charset=utf-8
Date: Tue, 28 Sep 2021 02:37:19 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
Strict-Transport-Security: max-age=2592000; includeSubDomains; preload
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <title>My secure app</title>
    <link rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/[email protected]/normalize.css"
    >
  </head>
  <body>
    <p><span id="changeme">Now this is some sweet HTML!</span></p>
    <script src="/static/js/greenlight.js"></script>
  </body>
</html>

Đó là một số HTML tuyệt vời, thực sự.

Sự kết luận

Nếu bạn đã làm theo hướng dẫn này, thì trang web của bạn đã đạt được những tiến bộ so với trước đây khi là một ứng dụng Django phát triển độc lập mới xuất hiện. Bạn đã thấy cách Django, Gunicorn và Nginx có thể kết hợp với nhau để giúp bạn phân phát trang web của mình một cách an toàn.

Trong hướng dẫn này, bạn đã học cách:

  • Đưa ứng dụng Django của bạn từ phát triển sang sản xuất
  • Lưu trữ ứng dụng của bạn trên một miền công cộng trong thế giới thực
  • Giới thiệu Gunicorn Nginx vào chuỗi yêu cầu và phản hồi
  • Làm việc với các tiêu đề HTTP để tăng cường bảo mật HTTPS cho trang web của bạn

Bây giờ bạn có một tập hợp các bước có thể tái tạo để triển khai ứng dụng web Django sẵn sàng sản xuất của mình.

Bạn có thể tải xuống dự án Django được sử dụng trong hướng dẫn này theo liên kết bên dưới:

Đọc thêm

Với bảo mật trang web, thực tế là bạn không bao giờ hoàn thành 100% con đường đến đó. Luôn có nhiều tính năng hơn mà bạn có thể thêm để bảo mật trang web của mình hơn nữa và cung cấp thông tin ghi nhật ký tốt hơn.

Kiểm tra các liên kết sau để biết các bước bổ sung mà bạn có thể tự thực hiện:

Leave a Reply