Hướng Dẫn Bảo Mật Ứng Dụng Node.js Với Nginx, Let’s Encrypt và Docker Compose

Bảo Mật Ứng Dụng Node.js Với Nginx, Let's Encrypt và Docker Compose

Có nhiều cách để nâng cao tính linh hoạt và bảo mật cho ứng dụng Node.js của bạn. Một trong những giải pháp hiệu quả là sử dụng reverse proxy như Nginx, giúp cân bằng tải, lưu cache nội dung tĩnh và triển khai Transport Layer Security (TLS). Việc kích hoạt HTTPS trên máy chủ giúp mã hóa giao tiếp giữa người dùng và ứng dụng, đảm bảo tính bảo mật cho mọi dữ liệu truyền tải.

Khi triển khai reverse proxy với TLS/SSL trong môi trường container, quy trình sẽ khác so với việc làm việc trực tiếp trên hệ điều hành máy chủ. Chẳng hạn, khi bạn lấy chứng chỉ từ Let’s Encrypt cho một ứng dụng chạy trên máy chủ, bạn sẽ cài đặt phần mềm cần thiết trực tiếp trên máy chủ đó. Tuy nhiên, khi làm việc với container, bạn sẽ có một cách tiếp cận linh hoạt hơn. Với Docker Compose, bạn có thể tạo các container cho ứng dụng, máy chủ web và Certbot  công cụ giúp bạn lấy chứng chỉ. Việc sử dụng Docker Compose giúp tận dụng tính mô-đun và di động của container, mang lại lợi ích lớn trong việc triển khai và quản lý.

Để triển khai ứng dụng Node.js an toàn với Docker, bạn cần một nền tảng mạnh mẽ. VPS hosting cung cấp tài nguyên linh hoạt, giúp tối ưu hóa hiệu suất và bảo mật. Khám phá các giải pháp VPS hosting chất lượng tại DataOnline để đảm bảo ứng dụng của bạn luôn hoạt động ổn định.

Trong bài hướng dẫn này, bạn sẽ học cách triển khai một ứng dụng Node.js với reverse proxy Nginx sử dụng Docker Compose. Bạn sẽ lấy chứng chỉ TLS/SSL cho tên miền liên kết với ứng dụng và đảm bảo rằng ứng dụng của bạn đạt được điểm số bảo mật cao từ SSL Labs. Cuối cùng, bạn sẽ thiết lập một cron job để tự động gia hạn chứng chỉ, bảo vệ tên miền của bạn lâu dài.

Yêu Cầu

Để theo dõi hướng dẫn này, bạn cần:

  • Một máy chủ Ubuntu 18.04, một tài khoản không phải root có quyền sudo và một tường lửa hoạt động. Để biết cách thiết lập, hãy đọc hướng dẫn Thiết lập máy chủ ban đầu của DataOnline.

  • Docker và Docker Compose được cài đặt trên máy chủ của bạn. Để biết cách cài đặt Docker, hãy làm theo Bước 1 và 2 của Cách Cài Đặt và Sử Dụng Docker trên Ubuntu 18.04. Để cài đặt Compose, hãy làm theo Bước 1 của Cách Cài Đặt Docker Compose trên Ubuntu 18.04.

  • Một tên miền đã đăng ký. Hướng dẫn này sẽ sử dụng your_domain làm ví dụ. Bạn có thể lấy miễn phí tại Freenom hoặc sử dụng nhà đăng ký tên miền mà bạn ưa thích.

  • Cả hai bản ghi DNS sau đây được thiết lập cho máy chủ của bạn. Bạn có thể tham khảo phần giới thiệu về DNS của DataOnline nếu bạn đang sử dụng:

    • Một bản ghi A với your_domain trỏ tới địa chỉ IP công cộng của máy chủ.

    • Một bản ghi A với www.your_domain trỏ tới địa chỉ IP công cộng của máy chủ.

Khi mọi thứ đã sẵn sàng, bạn có thể bắt đầu với bước đầu tiên.

Bước 1 – Nhân bản và kiểm tra ứng dụng Node

Là bước đầu tiên, bạn sẽ nhân bản repository chứa mã nguồn ứng dụng Node, bao gồm cả Dockerfile dùng để build image ứng dụng với Docker Compose. Sau đó, bạn sẽ kiểm tra ứng dụng bằng cách build và chạy nó với lệnh docker run, chưa có reverse proxy hay SSL.

Trong thư mục home của tài khoản không phải root, clone repository nodejs-image-demo từ tài khoản GitHub của DataOnline Community. Repository này bao gồm mã nguồn từ hướng dẫn Cách Xây Dựng Ứng Dụng Node.js với Docker.

Clone repository vào một thư mục – ví dụ sử dụng tên node_project (bạn có thể đặt tên khác nếu thích):

git clone https://github.com/do-community/nodejs-image-demo.git node_project

Chuyển vào thư mục node_project:

cd node_project

Trong thư mục này, có một Dockerfile chứa hướng dẫn để build ứng dụng Node sử dụng image node:10 và nội dung của thư mục dự án hiện tại. Bạn có thể xem nội dung Dockerfile với lệnh:

cat Dockerfile
Output
FROM node:10-alpine

RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app

WORKDIR /home/node/app

COPY package*.json ./

USER node

RUN npm install

COPY --chown=node:node . .

EXPOSE 8080

CMD [ "node", "app.js" ]

Các hướng dẫn này xây dựng một image Node bằng cách sao chép mã nguồn dự án từ thư mục hiện tại vào container và cài đặt các phụ thuộc bằng npm install. Chúng cũng tận dụng tính năng cache và phân lớp image của Docker bằng cách tách riêng việc sao chép package.jsonpackage-lock.json  trước khi sao chép phần còn lại của mã nguồn ứng dụng. Cuối cùng, các hướng dẫn này quy định rằng container sẽ chạy dưới quyền của người dùng node (không phải root), với các quyền phù hợp được thiết lập cho mã nguồn ứng dụng và thư mục node_modules.

Để kiểm tra ứng dụng mà không cần SSL, bạn có thể build và gán tag cho image bằng lệnh docker build cùng với tham số -t. Ví dụ dưới đây đặt tên cho image là node-demo, nhưng bạn có thể đặt tên khác tùy thích:

docker build -t node-demo .

Sau khi quá trình build hoàn tất, bạn có thể liệt kê các image bằng:

docker images
Output
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
node-demo           latest              23961524051d        7 seconds ago       73MB
node                10-alpine           8a752d5af4ce        3 weeks ago         70.7MB

Tiếp theo, tạo container với lệnh docker run. Ba tham số được sử dụng:

  • -p: Công khai cổng trên container và ánh xạ nó tới cổng trên máy chủ. Ví dụ này dùng cổng 80 trên máy chủ.

  • -d: Chạy container dưới nền.

  • –name: Đặt tên dễ nhớ cho container.

Chạy lệnh sau để tạo container:

docker run --name node-demo -p 80:8080 -d node-demo

Kiểm tra các container đang chạy với:

docker ps
Output
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
4133b72391da        node-demo           "node app.js"       17 seconds ago      Up 16 seconds       0.0.0.0:80->8080/tcp   node-demo

Bây giờ, bạn có thể truy cập tên miền của bạn để kiểm tra thiết lập http://your_domain (Hãy thay your_domain bằng tên miền của bạn.)
Ứng dụng sẽ hiển thị trang landing mặc định.

Thiet ke chua co ten 9

Bây giờ, sau khi đã kiểm tra ứng dụng, bạn có thể dừng container và xóa các image. Sử dụng lệnh docker ps để lấy CONTAINER ID của bạn:

docker ps
Output
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
4133b72391da        node-demo           "node app.js"       17 seconds ago      Up 16 seconds       0.0.0.0:80->8080/tcp   node-demo

Dừng container bằng lệnh docker stop. Hãy chắc chắn thay thế CONTAINER ID trong ví dụ này bằng CONTAINER ID thực tế của ứng dụng của bạn:

docker stop 4133b72391da

Bây giờ, bạn có thể xóa container đã dừng và toàn bộ các image, bao gồm cả các image không sử dụng và image bị treo (dangling images), bằng cách sử dụng lệnh docker system prune với tham số -a:

docker system prune -a

Nhấn y khi được nhắc trong output để xác nhận rằng bạn muốn xóa container đã dừng và các image. Lưu ý rằng lệnh này cũng sẽ xóa bộ nhớ đệm của quá trình build.

Sau khi kiểm tra image của ứng dụng thành công, bạn có thể tiếp tục xây dựng phần còn lại của thiết lập bằng Docker Compose.

Bước 2 – Xác định cấu hình máy chủ Web

Với Dockerfile của ứng dụng đã có, bạn sẽ tạo một file cấu hình cho container Nginx. Bạn có thể bắt đầu với một cấu hình tối giản bao gồm tên miền, thư mục gốc (document root), thông tin proxy và một location block để định tuyến yêu cầu của Certbot tới thư mục .well-known – nơi Certbot sẽ đặt file tạm thời xác thực rằng DNS của tên miền trỏ về máy chủ.

Trước tiên, tạo một thư mục trong thư mục dự án hiện tại (node_project) để chứa file cấu hình:

mkdir nginx-conf

Tạo và mở file với nano (hoặc editor ưa thích):

nano nginx-conf/nginx.conf

Thêm khối server sau để proxy các yêu cầu người dùng đến container Node và định tuyến yêu cầu của Certbot tới thư mục .well-known:

server {
        listen 80;
        listen [::]:80;

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;

        server_name your_domain www.your_domain;

        location / {
                proxy_pass http://nodejs:8080;
        }

        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }
}

Khối server này sẽ cho phép bạn khởi chạy container Nginx như một reverse proxy, giúp chuyển tiếp các yêu cầu đến container ứng dụng Node của bạn. Đồng thời, nó cũng cho phép bạn sử dụng plugin webroot của Certbot để lấy chứng chỉ SSL cho tên miền của bạn. Plugin này dựa trên phương thức xác thực HTTP-01, tức là sử dụng một yêu cầu HTTP để xác minh rằng Certbot có thể truy cập tài nguyên từ một máy chủ phản hồi với tên miền nhất định.

Sau khi chỉnh sửa xong, hãy lưu và đóng file. Nếu bạn sử dụng nano, bạn có thể làm điều này bằng cách nhấn CTRL + X, sau đó nhấn Y, rồi ENTER để xác nhận lưu thay đổi.Vui lòng tham khảo bài viết về Hiểu về thuật toán lựa chọn Server Block và Location Block trong Nginx.

Sau khi đã cấu hình xong máy chủ web, bạn có thể tiếp tục tạo file docker-compose.yml, file này sẽ giúp bạn định nghĩa các service của ứng dụng và thiết lập container Certbot để lấy chứng chỉ SSL.

Bước 3 – Tạo File Docker Compose

File docker-compose.yml sẽ định nghĩa các service của bạn, bao gồm ứng dụng Node và máy chủ web. File này sẽ chỉ định các thông tin như volumes đặt tên (quan trọng để chia sẻ chứng chỉ SSL giữa các container), cũng như thông tin về mạng và cổng. Ngoài ra, bạn cũng có thể chỉ định các lệnh cần chạy khi các container được tạo.

Tạo và mở file docker-compose.yml trong thư mục dự án hiện tại:

nano docker-compose.yml

Đầu tiên, định nghĩa service cho ứng dụng:

version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped

Định nghĩa service nodejs bao gồm các thành phần sau:

  • build: Xác định các tùy chọn cấu hình, bao gồm context dockerfile, được áp dụng khi Compose build image của ứng dụng. Nếu bạn muốn sử dụng một image có sẵn từ một registry như Docker Hub, bạn có thể sử dụng lệnh image thay thế, kèm theo thông tin về tên người dùng, repository và tag của image.

  • context: Xác định ngữ cảnh build cho image ứng dụng. Trong trường hợp này, nó là thư mục dự án hiện tại, được biểu diễn bằng .

  • dockerfile: Chỉ định Dockerfile mà Compose sẽ sử dụng để build image – chính là Dockerfile đã được xem xét ở Bước 1.

  • image, container_name: Gán tên cho image và container.

  • restart: Xác định chính sách khởi động lại. Mặc định là no, nhưng trong ví dụ này, container được thiết lập để restart unless-stopped (tự động khởi động lại trừ khi bị dừng thủ công).

Lưu ý rằng bạn không sử dụng bind mounts với service này, vì thiết lập hiện tại tập trung vào triển khai (deployment) thay vì phát triển (development). Để tìm hiểu thêm, bạn có thể đọc tài liệu của Docker về bind mountsvolumes.

Để cho phép giao tiếp giữa container ứng dụng và container máy chủ web, hãy thêm một mạng bridge có tên app-network ngay sau phần định nghĩa restart:

services:
  nodejs:
...
    networks:
      - app-network

Một mạng bridge do người dùng định nghĩa như thế này cho phép các container trên cùng một Docker daemon host có thể giao tiếp với nhau. Điều này giúp tối ưu hóa lưu lượng truy cập và giao tiếp trong ứng dụng của bạn, vì nó mở tất cả các cổng giữa các container trên cùng một mạng bridge, nhưng không mở bất kỳ cổng nào ra bên ngoài.

Nhờ đó, bạn có thể chủ động lựa chọn chỉ mở các cổng cần thiết để cung cấp dịch vụ frontend của mình, thay vì để lộ toàn bộ hệ thống.

Tiếp theo, định nghĩa service webserver:

...
 webserver:
    image: nginx:mainline-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
    depends_on:
      - nodejs
    networks:
      - app-network

Một số thiết lập của service nodejs vẫn được giữ nguyên, nhưng có một số thay đổi quan trọng như sau:

  • image: Chỉ định rằng Compose sẽ kéo image Nginx mới nhất dựa trên Alpine từ Docker Hub. Để tìm hiểu thêm về Alpine images, bạn có thể tham khảo Bước 3 của hướng dẫn Cách Xây Dựng Ứng Dụng Node.js với Docker.

  • ports: Mở cổng 80, cho phép cấu hình Nginx mà bạn đã thiết lập hoạt động chính xác.

Ngoài ra, một số volumes được đặt tên và bind mounts cũng được xác định:

  • web-root:/var/www/html: Thêm tài nguyên tĩnh của trang web vào một volume có tên web-root, sau đó ánh xạ nó đến thư mục /var/www/html trên container.

  • ./nginx-conf:/etc/nginx/conf.d: Bind mount thư mục cấu hình Nginx trên máy chủ host vào thư mục tương ứng trên container. Điều này giúp đảm bảo rằng bất kỳ thay đổi nào bạn thực hiện trên host cũng sẽ được phản ánh ngay trong container.

  • certbot-etc:/etc/letsencrypt: Mount thư mục chứa chứng chỉ và khóa Let’s Encrypt cho tên miền của bạn vào thư mục tương ứng trên container.

  • certbot-var:/var/lib/letsencrypt: Mount thư mục làm việc mặc định của Let’s Encrypt vào thư mục tương ứng trên container.

Các cấu hình này giúp đảm bảo rằng chứng chỉ SSL được lưu trữ chính xác và bất kỳ cập nhật nào đối với cấu hình Nginx hoặc chứng chỉ cũng sẽ tự động có hiệu lực mà không cần build lại container.

Tiếp theo, định nghĩa service cho Certbot:

...
  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@your_domain --agree-tos --no-eff-email --staging -d your_domain  -d www.your_domain

Định nghĩa này yêu cầu Compose kéo image certbot/certbot từ Docker Hub. Nó cũng sử dụng các volumes được đặt tên để chia sẻ tài nguyên với container Nginx, bao gồm:

  • certbot-etc: Chứa chứng chỉ và khóa của tên miền.

  • certbot-var: Chứa thư mục làm việc của Let’s Encrypt.

  • web-root: Chứa mã nguồn ứng dụng.

Ngoài ra, bạn cũng sử dụng depends_on để đảm bảo rằng container certbot chỉ được khởi chạy sau khi service webserver đang chạy.

Tùy chọn command chỉ định lệnh sẽ chạy khi container được khởi động. Trong đó, nó bao gồm subcommand certonly với các tham số sau:

  • –webroot: Yêu cầu Certbot sử dụng plugin webroot để đặt các file xác thực vào thư mục webroot.

  • –webroot-path: Xác định đường dẫn đến thư mục webroot.

  • –email: Địa chỉ email của bạn để đăng ký và khôi phục tài khoản.

  • –agree-tos: Xác nhận rằng bạn đồng ý với Thỏa thuận Người đăng ký ACME.

  • –no-eff-email: Chỉ định rằng bạn không muốn chia sẻ email với Electronic Frontier Foundation (EFF). Nếu bạn muốn chia sẻ, có thể bỏ qua tùy chọn này.

  • –staging: Yêu cầu Certbot sử dụng môi trường thử nghiệm của Let’s Encrypt để lấy chứng chỉ test. Tùy chọn này giúp bạn kiểm tra cấu hình mà không bị giới hạn số lượng yêu cầu chứng chỉ trong một khoảng thời gian. Để biết thêm thông tin, hãy tham khảo tài liệu về giới hạn yêu cầu (rate limits) của Let’s Encrypt.

  • -d: Xác định danh sách tên miền sẽ áp dụng chứng chỉ. Trong trường hợp này, bạn sử dụng your_domain và www.your_domain. Hãy thay thế bằng tên miền thực tế của bạn.

Thêm định nghĩa volumenetwork. Hãy chắc chắn thay thế username bằng tài khoản người dùng không phải root của bạn.

Cuối cùng, thêm định nghĩa cho volumes và network:

...
volumes:
  certbot-etc:
  certbot-var:
  web-root:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/views/
      o: bind

networks:
  app-network:
    driver: bridge

Các volumes được đặt tên của bạn bao gồm chứng chỉ Certbot, thư mục làm việc của Certbot, và volume chứa các tài nguyên tĩnh của trang web, web-root.

Trong hầu hết các trường hợp, driver mặc định cho Docker volumes là local driver, và trên Linux, nó chấp nhận các tùy chọn tương tự như lệnh mount. Nhờ đó, bạn có thể chỉ định một danh sách các tùy chọn driver với driver_opts, cho phép mount thư mục views trên máy chủ (host) – thư mục này chứa các tài nguyên tĩnh của ứng dụng, vào volume khi container chạy. Nội dung thư mục này sau đó có thể được chia sẻ giữa các container.

Để biết thêm chi tiết về nội dung của thư mục views, vui lòng tham khảo Bước 2 của hướng dẫn Cách Xây Dựng Ứng Dụng Node.js với Docker.

Dưới đây là file docker-compose.yml hoàn chỉnh:

version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped
    networks:
      - app-network

  webserver:
    image: nginx:mainline-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
    depends_on:
      - nodejs
    networks:
      - app-network

  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@your_domain --agree-tos --no-eff-email --staging -d your_domain  -d www.your_domain 

volumes:
  certbot-etc:
  certbot-var:
  web-root:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/views/
      o: bind

networks:
  app-network:
    driver: bridge

Với các định nghĩa service đã được thiết lập, bạn đã sẵn sàng khởi động các container và kiểm tra yêu cầu chứng chỉ của mình.

Bước 4 – Lấy chứng chỉ SSL và thông tin xác thực

Bạn có thể khởi động các container bằng lệnh docker-compose up. Lệnh này sẽ tạo và chạy các container cũng như các service theo thứ tự mà bạn đã chỉ định. Khi các yêu cầu chứng chỉ cho tên miền của bạn thành công, các chứng chỉ sẽ được mount vào thư mục /etc/letsencrypt/live trên container webserver.

Tạo các service bằng lệnh docker-compose up với tham số -d, giúp chạy các container nodejswebserver ở chế độ nền:

docker-compose up -d

Output sẽ xác nhận các service đã được tạo:

Output
Creating nodejs ... done
Creating webserver ... done
Creating certbot   ... done

Sử dụng lệnh:

docker-compose ps

Nếu mọi thứ diễn ra thành công, các service nodejswebserver sẽ ở trạng thái Up, và container certbot sẽ thoát với mã trạng thái 0, cho biết quá trình thực thi đã hoàn tất mà không gặp lỗi.

Output
  Name                 Command               State          Ports
------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0
nodejs      node app.js                      Up       8080/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:80->80/tcp

Nếu trạng thái của nodejs và webserver là “Up” và certbot có exit code 0, nghĩa là yêu cầu chứng chỉ đã thành công. Để kiểm tra các chứng chỉ đã được mount vào container webserver, chạy:

docker-compose logs certbot

Bây giờ, bạn có thể kiểm tra xem chứng chỉ SSL đã được mount vào container webserver hay chưa bằng cách sử dụng lệnh:

docker-compose exec webserver ls -la /etc/letsencrypt/live

Output sẽ hiển thị thư mục chứa chứng chỉ, ví dụ:

Output
total 16
drwx------ 3 root root 4096 Dec 23 16:48 .
drwxr-xr-x 9 root root 4096 Dec 23 16:48 ..
-rw-r--r-- 1 root root  740 Dec 23 16:48 README
drwxr-xr-x 2 root root 4096 Dec 23 16:48 your_domain

Khi biết yêu cầu chứng chỉ thành công, bạn có thể chỉnh sửa định nghĩa service certbot trong docker-compose.yml để bỏ tham số –staging và thay thế bằng –force-renewal nhằm yêu cầu chứng chỉ mới với cùng tên miền.

Mở file docker-compose.yml:

nano docker-compose.yml

Tìm phần định nghĩa service certbot và thay đổi lệnh thành:

...
  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@your_domain --agree-tos --no-eff-email --force-renewal -d your_domain -d www.your_domain
...

Sau khi chỉnh sửa xong, hãy lưu và thoát file. Bây giờ, bạn có thể chạy lệnh docker-compose up để tạo lại container certbot cùng với các volumes liên quan.

Bằng cách thêm tùy chọn --no-deps, bạn đang yêu cầu Compose bỏ qua việc khởi động lại service webserver, vì nó đã đang chạy:

Sau đó, chạy lệnh để tái tạo container certbot mà không khởi động lại service webserver:
docker-compose up --force-recreate --no-deps certbot

Output sẽ xác nhận yêu cầu chứng chỉ thành công.

Output
Recreating certbot ... done
Attaching to certbot
certbot      | Account registered.
certbot      | Renewing an existing certificate for your_domain and www.your_domain
certbot      |
certbot      | Successfully received certificate.
certbot      | Certificate is saved at: /etc/letsencrypt/live/your_domain/fullchain.pem
certbot      | Key is saved at:         /etc/letsencrypt/live/your_domain                               phd.com/privkey.pem
certbot      | This certificate expires on 2022-11-03.
certbot      | These files will be updated when the certificate renews.
certbot      | NEXT STEPS:
certbot      | - The certificate will need to be renewed before it expires. Cert                               bot can automatically renew the certificate in the background, but you may need                                to take steps to enable that functionality. See https://certbot.org/renewal-setu                               p for instructions.
certbot      | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot      |
certbot      | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -                                - - - - - - -
certbot      | If you like Certbot, please consider supporting our work by:
certbot      |  * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/do                               nate
certbot      |  * Donating to EFF:                    https://eff.org/donate-le
certbot      | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -                                - - - - - - -
certbot exited with code 0

Khi chứng chỉ đã được thiết lập, bạn có thể tiếp tục chỉnh sửa cấu hình Nginx để kích hoạt SSL.

Bước 5 – Chỉnh sửa cấu hình máy chủ Web và định nghĩa Service

Để kích hoạt SSL trong cấu hình Nginx, bạn cần thêm chuyển hướng HTTP sang HTTPS và chỉ định vị trí của chứng chỉ SSL, khóa và các thiết lập bảo mật (bao gồm cả Diffie-Hellman để hỗ trợ Perfect Forward Secrecy).

Trước tiên, dừng service webserver:

docker-compose stop webserver

Tạo một thư mục cho Diffie-Hellman key:

mkdir dhparam

Sinh khóa Diffie-Hellman với lệnh:

sudo openssl dhparam -out /home/sammy/node_project/dhparam/dhparam-2048.pem 2048

Sau đó, xóa file cấu hình Nginx cũ:

rm nginx-conf/nginx.conf

Tạo lại file cấu hình mới:

nano nginx-conf/nginx.conf

Thêm mã sau để chuyển hướng HTTP sang HTTPS và thêm thông tin SSL, giao thức, các tham số bảo mật. Hãy thay your_domain bằng tên miền của bạn:

server {
        listen 80;
        listen [::]:80;
        server_name your_domain www.your_domain;

        location ~ /.well-known/acme-challenge {
          allow all;
          root /var/www/html;
        }

        location / {
                rewrite ^ https://$host$request_uri? permanent;
        }
}

server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name your_domain www.your_domain;

        server_tokens off;

        ssl_certificate /etc/letsencrypt/live/your_domain/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/your_domain/privkey.pem;

        ssl_buffer_size 8k;

        ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;

        ssl_protocols TLSv1.2;
        ssl_prefer_server_ciphers on;

        ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

        ssl_ecdh_curve secp384r1;
        ssl_session_tickets off;

        ssl_stapling on;
        ssl_stapling_verify on;
        resolver 8.8.8.8;

        location / {
                try_files $uri @nodejs;
        }

        location @nodejs {
                proxy_pass http://nodejs:8080;
                add_header X-Frame-Options "SAMEORIGIN" always;
                add_header X-XSS-Protection "1; mode=block" always;
                add_header X-Content-Type-Options "nosniff" always;
                add_header Referrer-Policy "no-referrer-when-downgrade" always;
                add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
                #add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
                # enable strict transport security only if you understand the implications
        }

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;
}

Khối server HTTP xác định webroot cho các yêu cầu gia hạn chứng chỉ của Certbot tại thư mục .well-known/acme-challenge. Nó cũng bao gồm chỉ thị rewrite, giúp chuyển hướng tất cả các yêu cầu HTTP đến thư mục gốc sang HTTPS. Khối server HTTPS kích hoạt SSL và HTTP/2. Để tìm hiểu thêm về cách HTTP/2 cải thiện giao thức HTTP và lợi ích của nó đối với hiệu suất trang web, bạn có thể tham khảo bài viết Cách Cấu Hình Nginx với Hỗ Trợ HTTP/2 trên Ubuntu 18.04.

Khối này cũng chứa một loạt các tùy chọn bảo mật để đảm bảo rằng bạn đang sử dụng các giao thức SSL và bộ mã hóa (ciphers) mới nhất, đồng thời kích hoạt OCSP Stapling.

OCSP Stapling giúp máy chủ cung cấp phản hồi có dấu thời gian từ tổ chức cấp chứng chỉ (CA) ngay trong quá trình bắt tay TLS ban đầu, giúp tăng tốc độ xác thực.Khối này cũng xác định chứng chỉ SSL, khóa riêng (private key) và thông tin Diffie-Hellman.

Cuối cùng, bạn đã di chuyển thông tin proxy_pass vào khối này, bao gồm:

Một location block với chỉ thị try_files, giúp ánh xạ các yêu cầu đến container ứng dụng Node.js.Một location block riêng biệt cho alias này, bao gồm các tiêu đề bảo mật (security headers) giúp bạn đạt xếp hạng A trên các bài kiểm tra bảo mật như SSL LabsSecurity Headers.Các headers bảo mật được bao gồm là: X-Frame-Options,X-Content-Type-Options,Referrer-Policy,Content-Security-Policy,X-XSS-Protection,Header HTTP Strict Transport Security (HSTS) đã bị ghi chú (comment) – chỉ kích hoạt nếu bạn hiểu rõ tác động của nó và đã đánh giá kỹ về chức năng preload của nó.

Sau khi chỉnh sửa xong, hãy lưu và đóng file.

Trước khi tạo lại service webserver, bạn cần bổ sung một số thông tin vào định nghĩa service trong file docker-compose.yml, bao gồm:

Thông tin cổng liên quan đến HTTPS và Định nghĩa volume cho Diffie-Hellman

Tiếp theo, mở file docker-compose.yml để cập nhật định nghĩa service webserver. Thêm ánh xạ cổng cho HTTPS và định nghĩa volume cho Diffie-Hellman key:

nano docker-compose.yml

Trong phần webserver của file docker-compose.yml, thay đổi như sau:

...
 webserver:
    image: nginx:latest
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - dhparam:/etc/ssl/certs
    depends_on:
      - nodejs
    networks:
      - app-network

Sau đó, thêm volume cho dhparam vào cuối file docker-compose.yml:

...
volumes:
  ...
  webroot:
  ...
  dhparam:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/dhparam/
      o: bind

Lưu và đóng file, sau đó tái tạo service webserver:

docker-compose up -d --force-recreate --no-deps webserver

Kiểm tra trạng thái các service với:

docker-compose ps
Output
  Name                 Command               State                     Ports
----------------------------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0
nodejs      node app.js                      Up       8080/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp

Cuối cùng, bạn có thể truy cập tên miền của mình để đảm bảo mọi thứ hoạt động như mong đợi. Mở trình duyệt và điều hướng đến: https://your_domain. Hãy thay your_domain bằng tên miền thực tế của bạn.

Thiet ke chua co ten 9

Một biểu tượng ổ khóa sẽ xuất hiện trên chỉ báo bảo mật của trình duyệt. Nếu muốn, bạn có thể truy cập trang kiểm tra máy chủ SSL Labs hoặc trang kiểm tra Security Headers để đánh giá mức độ bảo mật của trang web. Các tùy chọn cấu hình hiện tại sẽ giúp trang web của bạn đạt xếp hạng A trên bài kiểm tra SSL Labs. Để đạt xếp hạng A trên bài kiểm tra Security Headers, bạn cần bỏ ghi chú (uncomment) dòng Strict Transport Security (HSTS) trong file:

…
location @nodejs {
                proxy_pass http://nodejs:8080;
                add_header X-Frame-Options "SAMEORIGIN" always;
                add_header X-XSS-Protection "1; mode=block" always;
                add_header X-Content-Type-Options "nosniff" always;
                add_header Referrer-Policy "no-referrer-when-downgrade" always;
                add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
                add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
                # enable strict transport security only if you understand the implications
        }
…

Một lần nữa, chỉ kích hoạt tùy chọn này nếu bạn hiểu rõ tác động của nó và đã đánh giá kỹ về chức năng preload của HSTS.

Bước 6 – Gia hạn chứng chỉ

Chứng chỉ Let’s Encrypt có thời hạn 90 ngày. Bạn có thể thiết lập một quá trình tự động gia hạn để tránh chứng chỉ hết hạn. Một cách là tạo một cron job sử dụng script để gia hạn chứng chỉ và tải lại cấu hình Nginx.

Tạo một script có tên ssl_renew.sh trong thư mục dự án:

nano ssl_renew.sh

Thêm đoạn mã sau vào script, đoạn mã này sẽ gia hạn chứng chỉ và tải lại cấu hình Nginx:

#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --ansi never"
DOCKER="/usr/bin/docker"

cd /home/sammy/node_project/
$COMPOSE run certbot renew --dry-run && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

Script này trước tiên gán binary docker-compose vào một biến có tên COMPOSE và chỉ định tùy chọn --no-ansi, giúp chạy các lệnh docker-compose mà không hiển thị các ký tự điều khiển ANSI. Sau đó, nó thực hiện tương tự với binary docker. Cuối cùng, script chuyển đến thư mục ~/node_project và chạy các lệnh docker-compose sau:

  • docker-compose run:

    • Lệnh này sẽ khởi chạy một container certbot và ghi đè lệnh mặc định được định nghĩa trong service certbot.

    • Thay vì sử dụng subcommand certonly, nó sử dụng subcommand renew, giúp gia hạn các chứng chỉ sắp hết hạn.

    • Ngoài ra, nó cũng bao gồm tùy chọn --dry-run để kiểm tra script mà không thực sự thực thi quá trình gia hạn.

  • docker-compose kill:

    • Lệnh này sẽ gửi tín hiệu SIGHUP đến container webserver, yêu cầu tải lại cấu hình Nginx mà không cần khởi động lại container.

  • Sau đó, script chạy lệnh docker system prune để xóa tất cả các container và image không sử dụng, giúp giải phóng dung lượng ổ đĩa.

Lưu file và làm cho nó có thể thực thi:

chmod +x ssl_renew.sh

Tiếp theo, mở file crontab của root để chạy script gia hạn theo khoảng thời gian nhất định:

sudo crontab -e

Nếu lần đầu tiên chỉnh sửa, chọn trình soạn thảo (ví dụ: nano). Sau đó, thêm dòng sau vào cuối file:

crontab
no crontab for root - using an empty one
Select an editor.  To change later, run 'select-editor'.
  1. /bin/ed
  2. /bin/nano        <---- easiest
  3. /usr/bin/vim.basic
  4. /usr/bin/vim.tiny
Choose 1-4 [2]: 
...

Sau khi chỉnh sửa xong, hãy đóng file và làm cho nó có thể thực thi bằng lệnh:

chmod +x ssl_renew.sh

Tiếp theo, mở file crontab của root để thiết lập lịch chạy tự động cho script gia hạn chứng chỉ:

sudo crontab -e

Nếu đây là lần đầu tiên bạn chỉnh sửa file này, hệ thống sẽ yêu cầu bạn chọn một trình soạn thảo:

crontab
no crontab for root - using an empty one
Select an editor.  To change later, run 'select-editor'.
  1. /bin/ed
  2. /bin/nano        <---- easiest
  3. /usr/bin/vim.basic
  4. /usr/bin/vim.tiny
Choose 1-4 [2]: 
...

Thiết lập cron job để kiểm tra gia hạn chứng chỉ mỗi 5 phút

Ở cuối file, thêm dòng sau:

crontab
...
*/5 * * * * /home/sammy/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1

Dòng này sẽ thiết lập công việc chạy mỗi 5 phút, giúp bạn kiểm tra xem quá trình gia hạn chứng chỉ có hoạt động đúng hay không. Ngoài ra, nó cũng tạo một file log cron.log để ghi lại các kết quả của cron job.

Kiểm tra xem gia hạn chứng chỉ có thành công không

Sau 5 phút, kiểm tra file log bằng lệnh:

tail -f /var/log/cron.log

Sau vài khoảnh khắc, nếu quá trình gia hạn giả lập thành công, bạn sẽ thấy output tương tự như sau:

Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/your_domain/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Killing webserver ... done
Output
…
Congratulations, all simulated renewals succeeded: 
  /etc/letsencrypt/live/your_domain/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Killing webserver ... 
Killing webserver ... done
Deleted Containers:
00cad94050985261e5b377de43e314b30ad0a6a724189753a9a23ec76488fd78

Total reclaimed space: 824.5kB

Để thoát khỏi chế độ theo dõi log, nhấn CTRL + C.

Thiết lập cron job để chạy hàng ngày vào buổi trưa

Bây giờ, bạn có thể chỉnh sửa lại file crontab để đặt lịch chạy hàng ngày. Nếu muốn chạy script vào 12 giờ trưa mỗi ngày, hãy thay đổi dòng cuối cùng thành:

crontab
...
0 12 * * * /home/sammy/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1

Bạn cũng có thể xóa tùy chọn --dry-run khỏi script ssl_renew.sh để thực hiện quá trình gia hạn thực tế.

#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"

cd /home/sammy/node_project/
$COMPOSE run certbot renew && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

Cron job của bạn sẽ đảm bảo rằng chứng chỉ Let’s Encrypt không bị hết hạn bằng cách tự động gia hạn khi đủ điều kiện.

Bạn cũng có thể thiết lập log rotation bằng tiện ích Logrotate để xoay vòng và nén các file log, giúp quản lý dung lượng lưu trữ hiệu quả hơn.

Kết luận

Chúc mừng! Bạn đã thành công trong việc thiết lập và chạy một ứng dụng Node.js với reverse proxy Nginx trong môi trường container. Bạn cũng đã bảo mật ứng dụng của mình bằng chứng chỉ SSL cho tên miền và thiết lập một cron job tự động gia hạn chứng chỉ, giúp duy trì bảo mật lâu dài cho ứng dụng.

Khi cấu hình Nginx và Let’s Encrypt, một VPS đáng tin cậy là yếu tố then chốt. Mua VPS giá rẻ tại DataOnline để tiết kiệm chi phí mà vẫn đảm bảo tốc độ, bảo mật, và khả năng mở rộng cho ứng dụng Node.js của bạn.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *