0
0
Lập trình
TT

Từ 404|1003 đến Khóa Xanh: Sửa Proxy SMS Rust trên GCP với Caddy

Đăng vào 2 tuần trước

• 8 phút đọc

Giới thiệu

Trong bài viết này, tôi sẽ hướng dẫn bạn cách khắc phục lỗi khi sử dụng proxy SMS viết bằng Rust trên Google Cloud Platform (GCP). Bạn có thể đã gặp phải các lỗi như 404 Not Found hay Cloudflare 1003 khi gọi API từ địa chỉ IP thay vì tên miền. Bằng cách sử dụng Caddy, chúng ta sẽ thiết lập một endpoint HTTPS an toàn cho ứng dụng của bạn.

Tóm tắt

TL;DR: Tôi đã gọi proxy SMS Rust của mình trên một VM GCP qua IP:8080, gây ra lỗi Cloudflare 1003 và cảnh báo từ trình duyệt. Tôi đã ánh xạ một hostname (example-proxy.example.com) tới VM, mở cổng 80/443, sử dụng Caddy trước ứng dụng Rust của mình, và để nó tự động phát hành chứng chỉ Let’s Encrypt. Kết quả là một endpoint HTTPS sạch sẽ và một biến môi trường dễ dàng cho Cloudflare Worker: SMS_API_URL=https://example-proxy.example.com/sms/send.

Tại sao lại viết bài này?

Tôi đã xây dựng một ứng dụng Rust để proxy các yêu cầu SMS. Nó chạy tốt tại http://203.0.113.45:8080, nhưng khi tôi tích hợp nó với Cloudflare Worker và frontend, tôi bắt đầu thấy:

  • 404 Not Found (từ nguồn gốc của tôi trên các đường dẫn chưa được định nghĩa)
  • Cloudflare 1003 (khi yêu cầu được thực hiện bằng IP thay vì hostname)

Bài viết này sẽ ghi lại những lỗi này thực sự có nghĩa là gì, cách mà stack web nhìn nhận yêu cầu của bạn, và các lệnh/cấu hình chính xác mà tôi đã sử dụng để khắc phục - cho sử dụng sản xuất thực tế.

Kiến trúc hệ thống (trước & sau)

Trước

  • Rust SMS proxy (Axum/Actix/Hyper) → lắng nghe trên 0.0.0.0:8080
  • Các yêu cầu từ Cloudflare Worker / trình duyệt đôi khi sử dụng IP:8080 trực tiếp
  • Không có HTTPS trên nguồn → nội dung hỗn hợp và các hạn chế proxy

Sau

  • Tên miền DNS: example-proxy.example.comA record → 203.0.113.45
  • Caddy trên VM lắng nghe trên 80/443, kết thúc TLS với Let’s Encrypt
  • Caddy reverse_proxy tới Rust tại 127.0.0.1:8080
  • Biến môi trường Worker: SMS_API_URL=https://example-proxy.example.com/sms/send

Các triệu chứng và ý nghĩa thực sự của chúng

  • 404 Not Found: Nguồn gốc (ứng dụng Rust của bạn hoặc máy chủ upstream) không có đường dẫn cho yêu cầu bạn đã thực hiện (ví dụ: /health). Đây không phải là lỗi của Cloudflare; đó là lỗi của ứng dụng/router của bạn.

  • Cloudflare 1003: Cloudflare không cho phép bạn truy cập mạng của nó bằng IP thô hoặc với tiêu đề Host không khớp. Nếu mã của bạn fetch() http://203.0.113.45:8080/... qua một vùng CF, hãy mong đợi điều này. Cloudflare muốn một tên miền mà vùng biết đến.

  • Nội dung hỗn hợp / Vấn đề TLS: Nếu frontend của bạn là HTTPS nhưng API của bạn là HTTP, trình duyệt có thể chặn hoặc cảnh báo. Bạn muốn HTTPS → HTTPS.

Từ vựng chính (định nghĩa nhanh, thực tiễn)

  • Hostname / FQDN: Một tên dễ đọc cho con người (ví dụ: example-proxy.example.com) ánh xạ tới một IP qua DNS.
  • A record: Bản ghi DNS ánh xạ một hostname tới một địa chỉ IPv4.
  • Reverse proxy: Một máy chủ công khai chuyển tiếp các yêu cầu đến backend riêng tư (ứng dụng Rust của chúng tôi).
  • TLS (SSL): Mã hóa cho HTTP. Cung cấp cho bạn https:// và khóa xanh của trình duyệt.
  • Let’s Encrypt: Một CA miễn phí phát hành chứng chỉ tự động. Caddy sẽ xử lý điều này cho bạn.
  • SNI: Tiện ích mở rộng TLS cho phép một IP phục vụ nhiều hostname bằng cách thông báo cho máy chủ cert nào sẽ sử dụng.
  • HTTP-01 challenge: Kiểm tra Let’s Encrypt chứng minh bạn kiểm soát một tên miền bằng cách trả lời một yêu cầu trên cổng 80.
  • Origin: Máy chủ ứng dụng thực tế của bạn (ở đây: Rust trên 127.0.0.1:8080).
  • Edge: Điểm vào công cộng (ở đây: Caddy trên 80/443; đôi khi là Cloudflare hoặc một bộ cân bằng tải).
  • CORS: Chính sách bảo mật của trình duyệt cho các yêu cầu cross-origin. Nếu một Cloudflare Worker gọi máy chủ nguồn của bạn từ máy chủ tới máy chủ, bạn thường không cần CORS trên nguồn.
  • Project ID (GCP): Chuỗi định danh cho dự án Google Cloud của bạn (không phải số dự án số).

Cách sửa lỗi - các bước chính xác (dễ sao chép/dán)

1) Chỉ định một hostname cho VM của bạn

Tôi đã sử dụng DuckDNS để tạo example-proxy.example.com trỏ tới 203.0.113.45.

Kiểm tra nhanh:

Copy
dig +short example-proxy.example.com
# Mong đợi: 203.0.113.45

2) Mở cổng 80 và 443 trong Google Cloud

Chúng ta cần 80 cho Let’s Encrypt (HTTP-01) và 443 cho lưu lượng HTTPS thực tế.

Copy
gcloud compute firewall-rules create allow-http --allow tcp:80 --description="Cho phép HTTP"
gcloud compute firewall-rules create allow-https --allow tcp:443 --description="Cho phép HTTPS"

Nếu gcloud báo lỗi về dự án của bạn, hãy thiết lập trước:

Copy

gcloud config set project YOUR_PROJECT_ID

Copy

3) Cài đặt Caddy

Copy
sudo apt update
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy

4) Cấu hình Caddy làm reverse proxy tới Rust

Tạo /etc/caddy/Caddyfile:

Copy
example-proxy.example.com {
    reverse_proxy 127.0.0.1:8080
}

Tải lại Caddy và kiểm tra trạng thái:

Copy
sudo systemctl reload caddy
sudo systemctl status caddy --no-pager

Điều gì xảy ra ở đây: Caddy tự động lấy chứng chỉ Let’s Encrypt cho example-proxy.example.com (sử dụng HTTP-01 challenge trên cổng 80), sau đó bắt đầu phục vụ HTTPS trên cổng 443 với chứng chỉ đó.

5) Liên kết ứng dụng Rust của bạn với localhost

Giữ cổng 8080 riêng tư phía sau Caddy:

Copy
./your_proxy_binary --host 127.0.0.1 --port 8080

Nếu bạn đã mở cổng 8080 ra internet trước đó, bạn có thể xóa/vô hiệu hóa quy tắc tường lửa đó để đảm bảo an toàn.

6) Kiểm tra như một chuyên gia

Nếu bạn có một đường dẫn sức khỏe:

Copy
curl -I https://example-proxy.example.com/health

Nếu không, hãy truy cập endpoint thực của bạn:

Copy
curl -i -X POST \
  -H "content-type: application/json" \
  -d '{"to":"+821012345678","text":"hello"}' \
  https://example-proxy.example.com/sms/send

Bạn sẽ thấy một phản hồi HTTPS sạch từ dịch vụ Rust của bạn thông qua Caddy.

7) Chỉ định môi trường Cloudflare Worker / backend tới hostname

Trong mã:

Copy
const endpoint = env.SMS_API_URL || "https://example-proxy.example.com/sms/send";

Trong wrangler.toml:

Copy
[vars]
SMS_API_URL = "https://example-proxy.example.com/sms/send"

Triển khai Worker/Pages của bạn và bạn đã hoàn tất.

Tại sao điều này hoạt động (giải thích dòng yêu cầu)

  1. DNS phân giải example-proxy.example.com → 203.0.113.45.
  2. Trình duyệt (hoặc Worker) kết nối tới cổng 443 và nói qua SNI: “Tôi là example-proxy.example.com.”
  3. Caddy trình bày chứng chỉ Let’s Encrypt hợp lệ cho tên miền đó.
  4. Caddy reverse_proxy yêu cầu tới http://127.0.0.1:8080 (ứng dụng Rust của bạn).
  5. Phản hồi được truyền trở lại qua kết nối TLS an toàn tới client.

Kết quả: một khóa xanh đáng tin cậy, không có nội dung hỗn hợp, không có Cloudflare 1003, và một URL ổn định duy nhất cho ứng dụng của bạn.

Khắc phục sự cố & Câu hỏi thường gặp

Q: Caddy không lấy được chứng chỉ.

  • Đảm bảo example-proxy.example.com phân giải tới 203.0.113.45.
  • Đảm bảo các cổng 80/443 được mở và có thể truy cập từ internet.
  • Kiểm tra nhật ký: journalctl -u caddy -n 100 --no-pager.
  • Xác nhận không có gì khác đang lắng nghe trên cổng 80.

Q: Tôi vẫn nhận được 404 trên /health.

  • Đó là router Rust của bạn, không phải Caddy. Hãy thêm một đường dẫn hoặc kiểm tra /sms/send.

Q: Tôi có cần CORS không?

  • Nếu trình duyệt gọi trực tiếp nguồn gốc Rust của bạn, có. Nếu Worker gọi máy chủ nguồn từ máy chủ tới máy chủ, bạn thường không cần CORS trên nguồn.

Q: Tôi có thể sử dụng Google HTTPS Load Balancer thay vì không?

  • Có. Nó quản lý TLS và mở rộng tốt, nhưng thiết lập nặng hơn và có thể tăng chi phí. Caddy là một lựa chọn tốt cho một VM duy nhất.

Q: Tôi có thể tránh mở cổng với Cloudflare Tunnel không?

  • Có. Một đường hầm ánh xạ một tên miền công khai tới 127.0.0.1:8080 mà không cần mở cổng. Đây cũng là một lựa chọn tốt.

Bảo mật & tăng cường sản xuất

  • Liên kết Rust với 127.0.0.1 để chỉ Caddy có thể truy cập.
  • Đóng cổng 8080 từ internet nếu bạn đã mở trước đó.
  • Sử dụng biến môi trường cho các endpoint và bí mật.
  • Giới hạn tỷ lệ / xác thực cho /sms/send để ngăn chặn lạm dụng.
  • Giám sát nhật ký (journalctl, nhật ký ứng dụng) và đặt cảnh báo.
  • Sao lưu & cập nhật: giữ Caddy và các gói hệ điều hành được cập nhật.

Thêm: Đường dẫn sức khỏe tối giản trong Rust

Axum

rust Copy
use axum::{routing::{get, post}, Router};
use std::net::SocketAddr;

async fn health() -> &'static str { "ok" }
async fn send_sms() { /* xử lý của bạn */ }

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/health", get(health))
        .route("/sms/send", post(send_sms));

    let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
    axum::Server::bind(&addr).serve(app.into_make_service()).await.unwrap();
}

Actix-web

rust Copy
use actix_web::{get, post, App, HttpServer, Responder, HttpResponse};

#[get("/health")]
async fn health() -> impl Responder { HttpResponse::Ok().body("ok") }

async fn send_sms() -> impl Responder { HttpResponse::Ok().finish() }

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(health).route("/sms/send", actix_web::web::post().to(send_sms)))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

Danh sách kiểm tra cuối cùng

  • [ ] DNS: example-proxy.example.com → 203.0.113.45
  • [ ] Tường lửa GCP: 80/443 được cho phép
  • [ ] Caddy đã cài đặt và reverse_proxy 127.0.0.1:8080
  • [ ] Rust được liên kết với 127.0.0.1:8080
  • [ ] curl -I https://example-proxy.example.com/… hoạt động
  • [ ] Biến môi trường Worker: SMS_API_URL=https://example-proxy.example.com/sms/send

Những điều cần nhớ

  • Đừng gọi backend của bạn bằng IP thô—sử dụng một hostname.
  • Đặt một reverse proxy (Caddy/Nginx) ở rìa để xử lý TLS.
  • Giữ ứng dụng của bạn riêng tư trên localhost; chỉ mở 80/443.
  • Ưu tiên các endpoint dựa trên biến môi trường để triển khai sạch sẽ.

Bây giờ proxy SMS Rust của bạn đã sẵn sàng cho sản xuất với một URL HTTPS thích hợp mà frontend và Workers của bạn có thể tin tưởng.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào