Chuyên mục
Tip - Trick

Cập nhật IP động cho tên miền qua CloudFlare để truy cập homelab tại nhà

Có hai cách để chúng ta có thể truy cập homelab từ bên ngoài:

  • Truy cập trực tiếp bằng Public IP của modem
  • Truy cập qua tên miền được cấu hình trỏ đến Public IP.

Đa số mọi người sẽ chọn cách thứ hai vì nó đơn giản, dễ nhớ hơn so với chuỗi dãy số IP xxx.xxx.xxx.xxx. Tuy nhiên nếu mạng ở nhà sử dụng IP động (đa số trường hợp), tên miền cần phải được tự động cập nhật mỗi khi Public IP thay đổi để tránh kết nối bị lỗi.

Hiện tại mình đang dùng 1 bash script trên Raspberry Pi để tự động cập nhật IP động cho tên miền qua dịch vụ CloudFlare. Nhờ vậy tên miền luôn được trỏ đến IP mới nhất được cấp cho modem mạng ở nhà.

Dưới đây là hướng dẫn chi tiết cách thiết lập

Yêu cầu

  • Đã có sẵn tên miền và đã có tài khoản Cloudflare
  • Tên miền đã được chuyển về CloudFlare quản lý
  • Máy tính / máy ảo chạy Linux (Ubuntu / Arch / CentOS / …) hoặc Raspberry Pi (đời nào cũng được)

Cài đặt Cloudflare.sh script

Bạn cần truy cập SSH vào máy tính để thiết lập script.

Tạo file cloudflare.sh) trong thư mục $HOME

cd ~
mkdir cloudflare
cd cloudflare
nano cloudflare.sh

Nhập vào nội dung sau

#!/bin/bash

# Forked from benkulbertis/cloudflare-update-record.sh
# CHANGE THESE

# API Token (Recommended)                                                                      #####
auth_token="xxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Domain and DNS record for synchronization
zone_identifier="f1nd7h3fuck1n6z0n31d3n71f13r4l50" # Can be found in the "Overview" tab of your domain
record_name="ipv4.example.org"                     # Which record you want to be synced

# DO NOT CHANGE LINES BELOW

# SCRIPT START
echo -e "Check Initiated"

# Check for current external network IP
ip=$(curl -s4 <https://icanhazip.com/>)
if [[ ! -z "${ip}" ]]; then
  echo -e "  > Fetched current external network IP: ${ip}"
else
  >&2 echo -e "Network error, cannot fetch external network IP."
fi

# The execution of update
if [[ ! -z "${auth_token}" ]]; then
  header_auth_paramheader=( -H '"Authorization: Bearer '${auth_token}'"' )
else
  header_auth_paramheader=( -H '"X-Auth-Email: '${auth_email}'"' -H '"X-Auth-Key: '${auth_key}'"' )
fi

# Seek for the record
seek_current_dns_value_cmd=( curl -s -X GET '"<https://api.cloudflare.com/client/v4/zones/'${zone_identifier}'/dns_records?name='${record_name}'&type=A>"' "${header_auth_paramheader[@]}" -H '"Content-Type: application/json"' )
record=`eval ${seek_current_dns_value_cmd[@]}`

# Can't do anything without the record
if [[ -z "${record}" ]]; then
  >&2 echo -e "Network error, cannot fetch DNS record."
  exit 1
elif [[ "${record}" == *'"count":0'* ]]; then
  >&2 echo -e "Record does not exist, perhaps create one first?"
  exit 1
fi

# Set the record identifier from result
record_identifier=`echo "${record}" | sed 's/.*"id":"//;s/".*//'`

# Set existing IP address from the fetched record
old_ip=`echo "${record}" | sed 's/.*"content":"//;s/".*//'`
echo -e "  > Fetched current DNS record value   : ${old_ip}"

# Compare if they're the same
if [ "${ip}" == "${old_ip}" ]; then
  echo -e "Update for A record '${record_name} (${record_identifier})' cancelled.\\n  Reason: IP has not changed."
  exit 0
else
  echo -e "  > Different IP addresses detected, synchronizing..."
fi

# The secret sause for executing the update
json_data_v4="'"'{"id":"'${zone_identifier}'","type":"A","proxied":true,"name":"'${record_name}'","content":"'${ip}'","ttl":120}'"'"
update_cmd=( curl -s -X PUT '"<https://api.cloudflare.com/client/v4/zones/'${zone_identifier}'/dns_records/'${record_identifier}'>"' "${header_auth_paramheader[@]}" -H '"Content-Type: application/json"' )

# Execution result
update=`eval ${update_cmd[@]} --data $json_data_v4`

# The moment of truth
case "$update" in
*'"success":true'*)
  echo -e "Update for A record '${record_name} (${record_identifier})' succeeded.\\n  - Old value: ${old_ip}\\n  + New value: ${ip}";;
*)
  >&2 echo -e "Update for A record '${record_name} (${record_identifier})' failed.\\nDUMPING RESULTS:\\n${update}"
  exit 1;;
esac

Bạn cần phải điền thông tin vào các dòng sau:

  • auth_token: Cloudflare API Token
  • zone_identifier: Zone ID
  • record_name: Tên miền

Tạo Cloudflare API Token

Truy cập vào trang Cloudflare API Token để tạo Token mới

Bấm vào Create Token
Chọn Use Template ở mục Edit Zone DNS
Kéo xuống mục Zone Resources, chọn tên miền bạn muốn sử dụng để cập nhật IP sau đó bấm Continue to Summary
Bấm Create Token
Token đã tạo xong

Bạn cần lưu lại Token vừa mới tạo để lưu vào script đã tạo. Token này sẽ không hiện ra lại trên Cloudflare vì lý do bảo mật. Nếu bạn quên lưu, cần phải xoá Token và tạo lại cái mới.

Tìm thông tin Zone ID

Từ trang chủ Cloudflare, bạn bấm vào tên miền muốn sử dụng để cập nhật IP. Sau đó kéo xuống dưới sẽ thấy mục Zone ID. Lưu lại thông số này để điền vào script

Lưu lại thông tin Zone ID

Tạo A Record cho tên miền

Ví dụ mình muốn sử dụng tên miền alibaba.thuanbui.me để truy cập đến homelab ở nhà. Bấm vào Add record và tạo một A record mới.

  • Name: điền vào alibaba
  • IPv4: điền tạm 1.1.1.1, thông số này sẽ được tự động cập nhật về IP của modem sau khi chạy script.
Tạo A Record cho subdomain alibaba.thuanbui.me

Cập nhật script

Thông tin cần thiết đã có đầy đủ, mình cập nhật lại vào file cloudflare.sh và lưu lại

#!/bin/bash

# Forked from benkulbertis/cloudflare-update-record.sh
# CHANGE THESE

# API Token (Recommended)                                                                  #####
auth_token="LrAB--xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Domain and DNS record for synchronization
zone_identifier="48bxxxxxxxxxxxxxxxxxxxx60" # Can be found in the "Overview" tab of your domain
record_name="alibaba.thuanbui.me"                     # Which record you want to be synced

# DO NOT CHANGE LINES BELOW

# SCRIPT START
echo -e "Check Initiated"

# Check for current external network IP
ip=$(curl -s4 <https://icanhazip.com/>)
if [[ ! -z "${ip}" ]]; then
  echo -e "  > Fetched current external network IP: ${ip}"
else
  >&2 echo -e "Network error, cannot fetch external network IP."
fi

# The execution of update
if [[ ! -z "${auth_token}" ]]; then
  header_auth_paramheader=( -H '"Authorization: Bearer '${auth_token}'"' )
else
  header_auth_paramheader=( -H '"X-Auth-Email: '${auth_email}'"' -H '"X-Auth-Key: '${auth_key}'"' )
fi

# Seek for the record
seek_current_dns_value_cmd=( curl -s -X GET '"<https://api.cloudflare.com/client/v4/zones/'${zone_identifier}'/dns_records?name='${record_name}'&type=A>"' "${header_auth_paramheader[@]}" -H '"Content-Type: application/json"' )
record=`eval ${seek_current_dns_value_cmd[@]}`

# Can't do anything without the record
if [[ -z "${record}" ]]; then
  >&2 echo -e "Network error, cannot fetch DNS record."
  exit 1
elif [[ "${record}" == *'"count":0'* ]]; then
  >&2 echo -e "Record does not exist, perhaps create one first?"
  exit 1
fi

# Set the record identifier from result
record_identifier=`echo "${record}" | sed 's/.*"id":"//;s/".*//'`

# Set existing IP address from the fetched record
old_ip=`echo "${record}" | sed 's/.*"content":"//;s/".*//'`
echo -e "  > Fetched current DNS record value   : ${old_ip}"

# Compare if they're the same
if [ "${ip}" == "${old_ip}" ]; then
  echo -e "Update for A record '${record_name} (${record_identifier})' cancelled.\\n  Reason: IP has not changed."
  exit 0
else
  echo -e "  > Different IP addresses detected, synchronizing..."
fi

# The secret sause for executing the update
json_data_v4="'"'{"id":"'${zone_identifier}'","type":"A","proxied":true,"name":"'${record_name}'","content":"'${ip}'","ttl":120}'"'"
update_cmd=( curl -s -X PUT '"<https://api.cloudflare.com/client/v4/zones/'${zone_identifier}'/dns_records/'${record_identifier}'>"' "${header_auth_paramheader[@]}" -H '"Content-Type: application/json"' )

# Execution result
update=`eval ${update_cmd[@]} --data $json_data_v4`

# The moment of truth
case "$update" in
*'"success":true'*)
  echo -e "Update for A record '${record_name} (${record_identifier})' succeeded.\\n  - Old value: ${old_ip}\\n  + New value: ${ip}";;
*)
  >&2 echo -e "Update for A record '${record_name} (${record_identifier})' failed.\\nDUMPING RESULTS:\\n${update}"
  exit 1;;
esac

Mặc định, script này sẽ kích hoạt chế độ proxy trên Cloudflare nhằm mục đích ẩn IP của modem, giảm nguy cơ bị tấn công vào mạng nhà. Nếu vì lý do nào đó không muốn ẩn IP của mình thì có thể thay đổi thông số "proxied":true thành "proxied":false

Chạy thử nghiệm

Thiết lập quyền thực thi cho file cloudflare.sh

chmod +X cloudflare.sh

Chạy thử xem nào

./cloudflare.sh
[email protected]:~/cloudflare $ ./cloudflare.sh
bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)
Check Initiated
  > Fetched current external network IP: 27.xx.xx.xx
  > Fetched current DNS record value   : 1.1.1.1
  > Different IP addresses detected, synchronizing...
Update for A record 'alibaba.thuanbui.me (48bxxxxxxxxxxxxxxxxxxxx60)' succeeded.
  - Old value: 1.1.1.1
  + New value: 27.xx.xx.xx

Quay lại Cloudflare, tên miền alibaba.thuanbui.me, mình thấy đã được tự động cập nhật với IP mới. Ngon lành!

Thiết lập cron tự động

Việc cuối cùng cần làm là thiết lập cron để script tự động chạy 24/7. Sau đó, không cần bận tâm đến nó nữa.

crontab -e

Mình chỉnh cho script tự động chạy sau mỗi 10 phút bằng cách thêm dòng này vào dưới cùng

*/10 * * * * /bin/bash /home/thuanbui/cloudflare/cloudflare.sh

Chú ý nhớ thay /home/thuanbui/cloudflare/cloudflare.sh thành đường dẫn đến nơi lưu file cloudflare.sh của bạn.

Trả lời