Static ip and port with certs points to a website

# devops

這邊記錄一下研究過程,可能沒那麼像是部落格文章。。

static ip -> vm instance -> serve via caddy -> proxy or local static files 實驗更加穩定的做法 multiple static ip -> tcp load balancer(不要選擇直通型的我們需要proxy protocol) -> vm instances group -> serve via caddy -> proxy or local static files

不知道下面有些直接貼 AI 產生的 markdown 會不會被認爲這是AI文章XD

Research nginx, caddy to serve static ip address and port

有鑑於 cloudfalre 的https只有幾個port 還有 google http load balancer 只能限定於 443 port,必須要研究其他方式。

經過各種嘗試後,從結論上來講,確定 let's encrypt 是無法產生免費的憑證給 raw ip 用的,至於自己購買的付費憑證(因爲憑證等級比較高:Organization Validated (OV) certificate)跟實驗 zerossl 是可以免費使用,但是zerossl免費一個帳號只能3個憑證。

以下是記錄實驗單台VM設定過程,主要是兩大路線:

  1. caddy 內建自動產生跟更新憑證,然後背後導過去我們frontend server

    systemd 跟一些設定就不多談了,這個是爲了維持service auto failover。 確定 caddy 可以設定 tls zerossl 但是!! 經過一連串實驗,鬼打牆遇到很多問題,最後主要卡點是這個 issue: https://github.com/caddyserver/certmagic/issues/148 最根本的原因是 https://pkg.go.dev/github.com/caddyserver/certmagic#SubjectQualifiesForPublicCert caddy本身因爲一些考量本來就不想支援 raw ip certs! 所以確定caddy這條路應該是被封殺了。

  1. nginx + zerossl 官方自己寫的工具 https://github.com/zerossl/zerossl-bot ,途中也是一直鬼打牆無法成功,最後看了其他地方官方文檔有提到,開始陸續不支援 acme client的方式,這個bot的方式就是走這個模式,zerossl似乎已經不支援了,所以後來自己手動在 https://app.zerossl.com/dashboard 一步一步來,建立憑證,還有需要設定相關 nginx conf 設定,大概類系下面這樣

    server {
        listen 80;
        server_name 34.92.142.234;
        location /.well-known/pki-validation/ {
            root /tmp;
            allow all;
        }
    }
    

    put challenge files under /tmp/.well-known/pki-validation/

    combine the .crt Files https://help.zerossl.com/hc/en-us/articles/360058295894-Installing-SSL-Certificate-on-NGINX

    上面是爲了要驗證的過程~ 需要有相關endpoints,就是走 http challenge 的方式

    
    map $proxy_protocol_server_addr $ssl_cert {
    34.92.142.234 /etc/ssl/certificate.crt; default /etc/ssl/certificate-2.crt;
    }
    map $proxy_protocol_server_addr $ssl_key {
    34.92.142.234 /etc/ssl/private.key; default /etc/ssl/private-2.key;
    }
    
    server {
    
        listen               8300 ssl proxy_protocol;
    
    
        ssl_certificate      $ssl_cert;
        ssl_certificate_key  $ssl_key;
    
        ssl_protocols TLSv1.2 TLSv1.3;
    
        access_log   /var/log/nginx/nginx.access.log;
        error_log    /var/log/nginx/nginx.error.log;
    
        location /debug-proxy {
            return 200 "proxy_protocol_server_addr: $proxy_protocol_server_addr\n";
        }
    
        location     / {
        proxy_pass http://34.107.143.218:80; # Replace with your target IP address
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        }
    
    }
    
    server {
    
        listen               8302 ssl proxy_protocol;
    
    
        ssl_certificate      $ssl_cert;
        ssl_certificate_key  $ssl_key;
    
        ssl_protocols TLSv1.2 TLSv1.3;
    
        access_log   /var/log/nginx/nginx.access.log;
        error_log    /var/log/nginx/nginx.error.log;
    
        location /debug-proxy {
            return 200 "proxy_protocol_server_addr: $proxy_protocol_server_addr\n";
        }
    
        location     / {
        proxy_pass http://34.107.143.218:80; # Replace with your target IP address
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        }
    
    }
    

    產生完憑證再另外設定 nginx conf ,最後就可以了 https://34.92.142.234:8300 不過目前這個方式是單台VM的設定,如果要比較穩定的方式,就要走 tcp load balancer + vm instance group 的方式。

更加穩定的做法

需要另外設定 google tcp load balancer -> vm instances group -> vm (serve nginx),看文檔應該是可以達到自定義 ip:port的連接方式,實驗過程中,下面是用 AI 整理對話成 markdown

    # NGINX Configuration Behind a Google Cloud TCP Load Balancer: A Summary

    This document summarizes our discussion about the correct and incorrect ways to configure an NGINX server that receives traffic from a Google Cloud TCP Load Balancer.

    ## Part 1: Using the Wrong NGINX Module (`http` for TCP)

**Question:** What happens if I use a Google TCP Load Balancer and point it to an NGINX instance configured with the standard `http` module instead of the `stream` module?

**Answer:** This configuration will fail for actual client traffic.

* **How it Fails:** A TCP Load Balancer operates at Layer 4, forwarding raw TCP packets. NGINX's `http` module operates at Layer 7 and expects a well-formed HTTP request (e.g., `GET / HTTP/1.1`). When the `http` module receives raw TCP data that isn't valid HTTP, it will result in errors like a `400 Bad Request`, connection timeouts, or connection resets.
* **Health Checks:** The setup might appear "healthy" if the TCP Load Balancer uses a basic TCP connection check, as the `http` module will accept the initial connection. However, the service will not be functional.
* **Conclusion:** This is an incorrect setup. The `http` module should only be used for HTTP/HTTPS traffic.

    ---

    ## Part 2: The Correct Module and a Follow-up Question

**Question:** Can the NGINX `stream` module receive a TCP request and then proxy it to another server as a *new HTTP request*?

**Answer:** No, not directly.

* **`stream` Module's Role:** The `stream` module is designed for Layer 4 TCP/UDP proxying. Its `proxy_pass` directive forwards the byte stream as-is. It has no awareness of the HTTP protocol and cannot create HTTP requests (methods, headers, etc.).
* **The Right Tool for the Job:** To receive an HTTP request and proxy it as a new HTTP request, you must use the `http` module. This is the standard function of an NGINX reverse proxy.
* **For Protocol Translation:** If you truly need to receive raw TCP and convert it into an HTTP request (e.g., from an IoT device), you need an intermediate "translator" application. The flow would be: `Client -> NGINX (stream) -> Your App (e.g., Python/Node.js) -> NGINX (http) or Final Server`.

    ---

    ## Part 3: The "Browser to TCP LB to NGINX Stream" Scenario

**Question:** Can a browser load a static page from a flow like: `Browser -> Google TCP LB -> NGINX (stream) -> Frontend Web Server`?

**Answer:** Surprisingly, yes, this will *technically* work, but it is a **deeply flawed architecture**.

* **Why it Works:** A browser's HTTP request is just text data sent over a TCP connection. The TCP LB and the NGINX `stream` module will pass these TCP packets along transparently to the frontend server, which will correctly interpret the HTTP request and send a response back along the same path.
* **Why It's a Bad Idea:**
        1.  **You Lose the Client IP Address:** The biggest issue. Your frontend server will only see the IP of the NGINX proxy, not the real user. This breaks logging, security, and analytics.
        2.  **No HTTP-Aware Features:** You lose all the power of NGINX's `http` module: no URL-based routing (`location`), no header modification, no virtual hosts, and no caching.
        3.  **Ineffective Health Checks:** A simple TCP health check can pass even if the web server is returning `500` errors.

    ---

    ## Part 4: The Advanced Solution: The "Loopback Proxy" with PROXY Protocol

**Question:** To solve the missing header problem, can we have NGINX `stream` receive the public traffic and proxy it to an `http` block on the same server (`localhost`)?

**Answer:** Yes! This is a clever and powerful technique, but it requires using the **PROXY protocol** to prevent losing the client IP.

    1.  **The Flaw:** Without the PROXY protocol, the `http` block would see the connection coming from `127.0.0.1` and would still not know the real client IP.
    2.  **The Solution:** The PROXY protocol allows the `stream` module to prepend a small header containing the original client IP information before passing it to the `http` module.
    3.  **Implementation:**
* In the `stream` block, enable sending the protocol header: `proxy_protocol on;`.
* In the `http` block, enable receiving it on the listen directive: `listen 127.0.0.1:8080 proxy_protocol;`.
* Configure the `http` block to trust and use this information: `set_real_ip_from 127.0.0.1;` and `real_ip_header proxy_protocol;`.

    ### Example `nginx.conf` for the Loopback Proxy
    ```nginx
    stream {
        server {
            # Receives traffic from the TCP LB
            listen 80;

            # Add the PROXY protocol header
            proxy_protocol on;

            # Forward to the local http module
            proxy_pass 127.0.0.1:8080;
        }
    }

    http {
        # Trust the PROXY protocol info from our own machine
        set_real_ip_from 127.0.0.1;
        real_ip_header proxy_protocol;

        server {
            # Listen locally and expect the PROXY protocol header
            listen 127.0.0.1:8080 proxy_protocol;

            location / {
                # Now the client IP is correctly preserved
                proxy_set_header X-Forwarded-For $proxy_protocol_addr;
                proxy_set_header Host $host;
                proxy_pass http://<ip_of_frontend_server>;
            }
        }
    }

接著基於好奇心,我用 ai 整理對話產生一個動態式報告,不得不說還真是有模有樣~

See the Pen Untitled by 葉景新 (@fvdxjauc-the-vuer) on CodePen.

不過目前AI果然還是很會唬爛,這個做法是我自己直接去讀 GCP 跟 nginx 官網得出的想法,只是最後拿去跟 AI 討論叫他寫出一些相關檔案~ 只能說 AI 現在還是處於只要你問的事情,是相對冷門一點的,還是很容易拿到一堆唬爛的回答,最可怕的是有時候寫得還一副很像對的,有了AI後反而更累於檢查到底AI產的東西是否正確。。。 (對於現在大家很愛講的 vibe coding,我沒有太大的感受,畢竟他們那些vibe coding大部分都是簡單場景,而且也幾乎都是跟前端刻畫面相關,AI 對於後端滿多時候還是只能處理重複性跟邏輯相對不復雜且單純的場景,不過這些或許隨著AI繼續發展,model遲早會變得聰明到可以應付? 確實從 chatgpt 4.1後,明顯有不少場景也可以應用了!

但是總歸一句,AI確實讓生產力提升,但是我覺得可怕的是很多人會誤會效率提升很大,我指的是他可能會看到別人舉例某些事情提升很多,就會莫名覺得其他事情或者一些類似的事情會不會也是可以提升很大~

If you like my content,

feel free to buy me a coffee

Enjoy crafting new things

Never stop learning.

Life is the sum of your daily habits.

Find things that you enjoy and please

Doit.

Feel free to connect with me.

Created by potrace 1.16, written by Peter Selinger 2001-2019

© Jing 2024. All rights reserved.