Docker, Nginx, NodeJs, SSL #3

Kittinut Pramhan
5 min readMay 28, 2018

--

วันนี้คือหัวใจหลักของงานแล้วครับก็คือการทำ SSL ขอ CA จาก let’s encrypt
จากบทความครั้งก่อน

ผมได้ทำการลงทุนสมัครโดเมนที่ถูกที่สุดของ NameCheap ประมาณ 40 บาทต่อปี (ถูกจริงๆ) http://kittinut.asia < ลิ้งนี้

ณ จุดๆ นี้ผมจะพูดถึงเรื่องของการทำ CA ไว้บางหากท่านใดไม่มีความรู้เรื่อง Web security ผมจะขออธิบายไว้ หากใครมีความรู้แล้วข้ามหัวข้อข้างล่างไปเลยนะครับ

Certificate authority

สิ่งนี้คือการปกกันการส่งข้อมูลของเว็ปไซด์ที่เราสร้าง โดยจะมีการเข้ารหัสข้อมูลที่เราส่ง โดยอยู่ในรูปของ Private Key และ Public Key โดยเราซึ่งเป็นผู้ให้บริการเซิฟเวอร์ จะมี Private Key ที่ใช้ในการถอดรหัสข้อความ โดย ผู้ส่ง ก็จะมี Public key ของเรา จะนำไปเขารหัสกับข้อความที่เขาส่ง แล้วเรา ผู้ให้บริการเซิฟ จะใช้ private key ในการถอดรหัส คำถาม??!!!??? จะรู้ได้อย่างไรว่า Public Key ที่ผู้ส่งได้เป็นของเราจริงๆ นั้นก็คือ

มีการยืนยันจากผู้ให้บริการ Certificate authority เช่น let’s encrypt เป็นต้น โดยเราจะส่งข้อมูลนั้น ๆ ผ่านทาง port 433 เอ้ย 443 (ที่เล่นเนี้ยผมเขียน port ใน docker-compsoe ผิด นั่งหาอยู่สองวันกว่าจะเเก้ อิอิ ผิดเป็นครู)

เริ่มต้นทำ SSL

ถึงขั้นตอนนี้ทุกคนต้องแน่ใจเเล้วว่า Domain name เรา online และเราต้องทำการผู้ Domain เราเข้ากับ Default.conf โดยตรงตัวหน้าแก้เป็น Domain name ของเรา หลังจากนั้นให้เราทำการ push ขึ้น github

//default.conf
server {
listen 80;
server_name kittinut.asia www.kittinut.asia;

#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}

ในDigital Ocean นั้นให้เราทำการผูก Domain name ไปที่เมนูด้านมุมขวาสีเขียวเเล้วเลือกที่ Domain/DNS แล้วทำตามรูปข้างล่างเลย

เราจะมี domain name คือ kittinut.asia และ CNAME www.kittinut.asia ประมาณนี้แล้วเราเอา

ในขั้นตอนนี้ผมไม่ขอพูดเยอะนะครับเพราะต้องมีสกิลในระดับนึงเเล้ว

Access to Server

หลังจากนั้นให้ git pull และทำการ สั่ง ตามด้วยคำสั่งรัน Docker

$cd DockerProject
DockerProject $ git pull
DockerProject $ docker-compose up -d

เราก็จะได้เซิฟเวอร์ที่มีโดเมนของเราเป็นที่เรียบร้อยย

Port 80 to 443

ตอนนี้เซิฟเวอร์เราทำงานที่ port 80 อยู่นั้นเองโดย nginx ถูกสั่งมาว่าอย่างนั้น เอาละครับตอนนี้เราจะต้องทำ SSLให้ไปอยู่ที่ port 443 แต่เราต้องทำการขอเลข private key และ public key ก่อน ซึ่งก่อนจะขอต้องมี domain name ก่อนนะ

การของ่ายมาก ขั้นแรก แก้ไข default.conf ใน DockerProject

server {
listen 80;
server_name kittinut.asia www.kittinut.asia;

#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
location ~ /.well-known/acme-challenge {
allow all;
root /usr/share/nginx/html;
}


location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location = /50x.html {
root /usr/share/nginx/html;
}


}

หลังจากนั้นก็เข้าไปใน server ตามด้วย git pull && docker-compose up -d และพิมพ์คำสั่งตามนี้

$ sudo docker run -it --rm \
-v /root/DockerProject/etc/letsencrypt:/etc/letsencrypt \
-v /root/DockerProject/var/lib/letsencrypt:/var/lib/letsencrypt \
-v /root/DockerProject/www/example.com:/data/letsencrypt \
-v "/root/DockerProject/var/log/letsencrypt:/var/log/letsencrypt"\
certbot/certbot \
certonly --webroot\
--email knightza94@gmail.com --agree-tos --no-eff-email \
--webroot-path=/data/letsencrypt \
-d kittinut.asia -d www.kittinut.asia

docker certbot จะทำงานให้เราในการตรวจสอบว่า domain name ของคุณมีอยู่จริงไหมที่ http-01 :80 นั้นแหละ ถ้ามีก็จะส่งมาเก็บโดยเรา mapped volumes หรือ mount ไว้ใน /root/DockerProject/etc/letsencrypt
เมื่อทำเสร็จเเล้วจะได้message แบบนี้ลงบนserver

อัพเดทโครงสร้างโปรเจคบน เซิฟเวอร์

DockerProject
|-etc
|-letsencrypt
|-live //เราสนใจอันนี้
|-kittinut.asia
|-fullchain.pem
|-privkey.pem
|-nginx
|-default.conf
|-www
|-example.com
|-index.html
|- docker-compose.yml

ตอนนี้โปรเจคเราเริ่มจะเยอะขึ้นเเล้วนะครับ

เอาหล่ะครับมาถึงขั้นนี้เราจะทำการแก้ไข docker-compose.yml ใหม่ดังนี้นะคับ

version: '3.1'
services:
nginx:
image:
nginx:1.13.12-alpine
container_name: nginx
ports:
- "80:80"
- "443:443" //เปิด ports 443
volumes:
- ./nginx/:/etc/nginx/conf.d/
- ./www/example.com/:/usr/share/nginx/html
- ./etc/letsencrypt/live/kittinut.asia/fullchain.pem:/etc/letsencrypt/live/kittinut.asia/fullchain.pem
- ./etc/letsencrypt/live/kittinut.asia/privkey.pem:/etc/letsencrypt/live/kittinut.asia/privkey.pem
- ./etc/letsencrypt/dh-param/dhparam-2048.pem:/etc/ssl/certs/dhparam-2048.pem

ในบรรทัดข้างบน ทุกท่านอาจจะงงว่าทำไมผมถึง mapped volumes โดยตรงนั้นเป็นเพราะว่า มันบังคับ แต่เราต้องทำการ certbot ก่อนไม่งั้นมันจะ สร้าง directory มาให้เราเอง นะ อย่าลืม

และ default.conf จะต้องเพิ่มดังนี้ แล้ว push ขึ้น github

server {
listen 80;
listen [::]:80;
server_name kittinut.com www.kittinut.asia;

location ^~ /.well-known/acme-challenge {
root /usr/share/nginx/html;
default_type text/plain;
allow all;
}

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

#https://kittinut.asia
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name kittinut.asia;

server_tokens off;

ssl_certificate /etc/letsencrypt/live/kittinut.asia/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/kittinut.asia/privkey.pem;

ssl_buffer_size 8k;

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

ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
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;

# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8;

location ^~ /.well-known/acme-challenge {
root /usr/share/nginx/html;
default_type text/plain;
allow all;
}

return 301 https://www.kittinut.asia$request_uri;
}

#https://www.kittinut.asia
server {
server_name www.kittinut.asia;
listen 443 ssl http2;
listen [::]:443 ssl http2;

server_tokens off;

ssl on;

ssl_buffer_size 8k;
ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;

ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
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;

# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4;

ssl_certificate /etc/letsencrypt/live/kittinut.asia/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/kittinut.asia/privkey.pem;

location ^~ /.well-known/acme-challenge {
root /usr/share/nginx/html;
default_type text/plain;
allow all;
}

root /usr/share/nginx/html;
index index.html;
}

ก่อนที่เราจะ pull github ลงมาเราต้องทำการสร้าง 2048 bit DH Param file บนเซิฟเวอร์เรา

$ sudo openssl dhparam -out /root/DockerProject/etc/letsencrypt/dh-param/dhparam-2048.pem  2048

หลังจากเรา generation 2048 bit DH Param file ให้นำ default.conf และ docker-compose.yml ที่เมื่อกี้ pull ลงมาจากนั้นใช้คำสั่ง

$docker-compose up -d 
$docker ps //เพื่อตรวจสอบว่า nginx เราทำงานปกติไหม

เอาหล่ะครับตอนนี้ ถ้าไม่เข้าใจ ให้ย้อนกลับไปไหมหรือทักมาถามผมก็ได้นะอิอิ

ทำการทดสอบ Browser

เมื่อเราเข้าไปยัง kittinut.asia มันจะถูก redirect ไปยัง https://kittinut.asia ตามคำสั่งที่อยู่ใน default.conf บรรทัดที่เขียนว่า

location / {
rewrite ^ https://$host$request_uri? permanent;
}
และreturn 301 https://www.kittinut.asia$request_uri;

ลองไปดูกันนะครับ

https แล้วว

ทดสอบ SSL

เราจะทำการทดสอบผ่านทาง SSL labs และผลที่ได้คือ

ตีบวก SSL

//default.conf
#https://www.kittinut.asia
server {
# ....

location / {
#security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
#CSP
add_header Content-Security-Policy "frame-src 'self'; default-src 'self'; script-src 'self' 'unsafe-inline' https://maxcdn.bootstrapcdn.com https://ajax.googleapis.com; img-src 'self'; style-src 'self' https://maxcdn.bootstrapcdn.com; font-src 'self' data: https://maxcdn.bootstrapcdn.com; form-action 'self'; upgrade-insecure-requests;" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

# ....
}

ท่อนนี้จะเป็นการเพิ่มการป้องกันอีกชั้นนึงให้กับเว็ปไซด์ของเรา และทำการนำขึ้น server พร้อมกับรัน docker-compose up -d อีกครั้ง และนำไปทดสอบ

ถึงตอนสรุปผล

การทำ SSL docker nginx นั้นทางผู้ทำต้องมีประการณ์ (mistake) ที่ผมใช้คำว่า mistake เพราะว่า ถ้าเราไม่เรียนรู้จากข้อผิดพลาด เราก็จะไม่มีประสบการณ์ ความซับซ้อนของ Docker คือการทำ mapped volumes และ การ conf default.conf nginx ที่ต้องมีประสบการณ์ แต่เราสามารถหาได้ในเว็ปไซด์อยู่แล้ว

Reference :

บทถัดไปผมจะสอนเรื่องการ ทำ nodejs บน ตัวที่เราสร้างนี้

หากผิดพลาดประการใดต้องขออภัยมา ณ ที่นี้ด้วย และหากมีข้อสงสัยสามารถติดต่อได้จ้า

--

--