2022年7月26日 星期二

[NAS] Private IP / Custom Domain / Wildcard SSL / HTTPS 憑證定期更新與設定 @ DS216play / DSM 7.1



之前在 2018 年已經寫過一篇:[NAS] Private IP Server 與 Let's Encrypt Wildcard SSL 憑證 / HTTPS 服務,那時是採用 Let's Encrypt Wildcard SSL 憑證,主因是只有 Private IP 的 NAS Server ,無法讓憑證服務連進來驗證 IP 與 Domain 的關係,因此主要都是靠 DNS Record (TXT Record) 的驗證方式。

然而,忘了是何時開始,原本設定好的 acme.sh 跟連續動作 script 失效好一陣子,後來才發現在 2021.08.01 起,預設已經改成 ZeroSSL 方案,所以要處理創帳號的部分:

$ ~/.acme.sh/acme.sh --upgrade
$ ~/.acme.sh/acme.sh --register-account -m YOUR_EMAIL
[Sun May 22 13:26:39 CST 2022] No EAB credentials found for ZeroSSL, let's get one
[Sun May 22 13:26:41 CST 2022] Registering account: https://acme.zerossl.com/v2/DV90
[Sun May 22 13:26:55 CST 2022] Registered
[Sun May 22 13:26:55 CST 2022] ACCOUNT_THUMBPRINT='XXXXXXXXXXXXXXXXXXXXXXX'

運行:

$ CF_Key=YOUR_KEY CF_Email=YOUR_EMAIL ~/.acme.sh/acme.sh --issue --dns dns_cf -d *.YOUR_DOMAIN

如此,在 ~/.acme.sh\*.YOUR_DOMAIN/ 中,會有:
  • ~/.acme.sh\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.cer
  • ~/.acme.sh\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.key
  • ~/.acme.sh\*.YOUR_DOMAIN/ca.cer
  • ~/.acme.sh\*.YOUR_DOMAIN/fullchain.cer
接著把這些 copy 出來,直接到 NAS DSM7.1 網頁介面匯入

控制台 -> 安全性 -> 憑證 -> 新增 -> 匯入憑證 ->

私鑰: Your cert key - \*.YOUR_DOMAIN.key
憑證:  Your cert - \*.YOUR_DOMAIN.cer
中繼憑證: ca.cer

控制台 -> 安全性 -> 憑證 -> 設定 -> 系統預設 -> 更換成剛剛上傳的憑證(如 *.YOUR_DOMAIN)

 

如此,再去系統 /usr/syno/etc/www/certificate/system_default/cert.conf 查一下資訊,可以看到寫死的憑證位置,未來就可以靠手段去覆蓋掉。

取代掉 ssl_certificate 欄位,如 /usr/syno/etc/www/certificate/system_default/XXX-SC-XXX.pem;

$ cat ~/.acme.sh/\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.cer ~/.acme.sh/\*.YOUR_DOMAIN/ca.cer > ~/.acme.sh/\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.fullchain.pem
$ sudo cp ~/.acme.sh/\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.fullchain.pem /usr/syno/etc/www/certificate/system_default/XXX-SC-XXX.pem;

取代掉 ssl_certificate_key,如 /usr/syno/etc/www/certificate/system_default/XXX-SCK-XXX.pem;

$ openssl pkcs8 -topk8 -inform pem -in ~/.acme.sh/\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.key -outform pem -nocrypt -out ~/.acme.sh/\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.key.pem
$ sudo cp ~/.acme.sh/\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.key.pem /usr/syno/etc/www/certificate/system_default/XXX-SCK-XXX.pem;

測試與重啟 Nginx

$ sudo nginx -t && sudo synopkg restart --service nginx

上述的任務都可以擠在一個 script 中,再靠 NAS Web UI 設定定期跑,而 NAS 只需在 Web UI 上設定好一次使用指定的憑證資訊後,後續就可以靠系統底層不斷覆蓋重啟 nginx 方式來處理,像是有需要的話,也可以靠 gawk 去動態取出 ssl_certificate_key 和 ssl_certificate 的位置

ssl_certificate_path=`sudo grep "ssl_certificate " /usr/syno/etc/www/certificate/system_default/cert.conf | gawk -F';' '{print $1}' | gawk -F' ' '{print $2}'`

ssl_certificate_key_path=`sudo grep "ssl_certificate_key " /usr/syno/etc/www/certificate/system_default/cert.conf | gawk -F';' '{print $1}' | gawk -F' ' '{print $2}'` 

echo "ssl_certificate_path: $ssl_certificate_path"

echo "ssl_certificate_key_path: $ssl_certificate_key_path"


ref: 

2022年7月20日 星期三

PHP 開發筆記 - 使用 Google Analytic (GA4) Measurement Protocol

使用 GA Measurement Protocol 大概也有超過五年了 XD 最近一直收到通報,說舊版 GA Measurement Protocol 即將在 2023.07.01 不在接收資料處理,簡稱看不到報表。

建立一個 GA4 專案後,發現要在使用 Measurement Protocol 的難度有稍微增加。包括測試也有點卡卡 XD 最近就記錄一下筆記:
首先,想要模擬個 page_view 時,才發現官方文件沒有多提,後來大概是組出這樣:

{
    "client_id":"00000000",
    "non_personalized_ads":true,
    "events":[
        {
            "name":"page_view",
            "params":{
                "page_title":"hello",
                "page_location":"blog.changyy.org"
            }
        }
    ]
}

而 PHP Code:

function _GA4_($api_secret, $measurement_id, $deviceId, $deviceIP, $events = array()) {
    $query_string = array(
        'measurement_id' => $measurement_id,
        'api_secret' => $api_secret,
    );  
    if (!empty($deviceIP)) {
        // https://support.google.com/analytics/answer/9143382
        // https://github.com/dataunlocker/save-analytics-from-content-blockers/issues/25
        $query_string['uip'] = $deviceIP;
        $query_string['_uip'] = $deviceIP;
    }   
    $url = 'https://www.google-analytics.com/mp/collect?'.http_build_query($query_string);
    //$url = 'https://www.google-analytics.com/debug/mp/collect?'.http_build_query($query_string);

    //  post without libcurl
    // https://ga-dev-tools.web.app/ga4/event-builder/
    $payload = array(
        'client_id' => $deviceId,
        'non_personalized_ads' => true,
        //'timestamp_micros' => "".floor(microtime(true) * 1000)."000",
        'events' => $events,
    );

    //if (isset($_SERVER) && isset($_SERVER['COUNTRY_CODE']) && !empty($_SERVER['COUNTRY_CODE']) ) {
    //        if (!isset($payload['user_properties']))
    //                $payload['user_properties'] = array();
    //        $payload['user_properties']['country_code'] = array(
    //                'value' => $_SERVER['COUNTRY_CODE'],
    //        );
    //}

    $POST_DATA = json_encode($payload);
    $options = array(
        'http' => array(
            'header' => implode("\r\n", array(
                "Content-Type: application/json",
                //"Content-Length: ".strlen($POST_DATA),
            )), 
            'method' => 'POST',
            'content' => $POST_DATA,
        )   
    );  
    $context  = @stream_context_create($options);
    $result = @file_get_contents($url, false, $context);
    //echo "result:[$result]\n$POST_DATA\n".print_r($options)."\n";exit;
}

如此,就可以用以下完成事件回報了:

_GA4_($api_secret, $measurement_id, $deviceId, $deviceIP, array(
    array(
        'name' => 'page_view',
        'params' => array(
            'page_title' => 'Hello',
            'page_location' => 'https://blog.changyy.org/',
        ),
    )
);

而使用 https://www.google-analytics.com/debug/mp/collect 驗證時,有錯誤可以在 http response body 看到資料,例如:

{
  "validationMessages": [ {
    "description": "Unable to parse Measurement Protocol JSON payload. : invalid value Cannot bind a list to map for field 'user_properties'. for type Map",
    "validationCode": "VALUE_INVALID"
  } ]
}

2022年7月7日 星期四

Node.js 開發筆記 - 模擬 Web Browser 環境,執行前端 JS code / library

最近協助同事處理一個棘手的事,把一段需要算力的 JS Code 從本地端改成雲端運行,首要碰到的就是 node.js 預設環境並沒有很適合,如果有 JS library 十分依賴 Web Browser 環境時,那段 js code 透過 eval 運行時,會出現初始化錯誤。

原本土炮零碎地拼湊出 window, document, location, hostname 等把環境弄的堪用,後來還是先找一下,找到 browser-env 這套件,非常不錯!套上去後,所有土炮破壞架構美感的東西都可以去掉了。

如此,如果要將算力很高的任務拋去雲端計算時,且不幸依賴很多常見的 Web browser 套件邏輯時,就可以如此封裝成一隻 api ,供人把算力高的任務拋去高速運算的機器處理即可。

註:此例用 eval 是個危險的用法,只適合運行信任的程式碼,亂給使用者填寫會有高資安風險的。


範例:
  • 故意在 node.js 植入前端 jQuery library - https://code.jquery.com/jquery-3.6.0.slim.min.js (實務上真的有需求時,是要找 node.js 套件)
  • 在 node.js 透過 eval 啟用整段程式碼時,缺少 web browser 環境時,會噴錯誤訊息:TypeError: Cannot read properties of undefined (reading 'createElement')

2022年6月24日 星期五

Go開發筆記 - 使用 net/http 處理 URL Redirect 和 Cookie 之 Service Unit Test 需求

最近用 Golang 撰寫 Service Unit Test 時,練了一下 net/http 要如何處理 HTTP Response Header Location 的處理。主要是在使用 net/http 的 http.Client 時,可以加設定 CheckRedirect 的機制來阻擋 URL Redirect 的行為,藉以檢驗 URL Redirect 是否正確。

//httpClient := http.DefaultClient
httpClient := &http.Client{}

httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}

if jar, err := cookiejar.New(nil); err == nil {
httpClient.Jar = jar
}

如此,做 Service Unit Test 時,在發送 HTTP GET request or HTTP HEAD request 時,可以停下來,看看要跳轉到哪裡,以此判斷跳轉機制是否正常。

接著,碰到了另一個問題: 在 follow URL Redirect 後,似乎跳轉過程中產生的 Cookie 都沒有被記錄下來?例如最後的 response.Cookies() 輸出,的確只列出最後的 Response Cookies 資料。這時想要驗證中間的 Cookie 資訊,則需要瀏覽 httpClient.Jar 資料:

for _, c := range(httpClient.Jar.Cookies(res.Request.URL)) {
fmt.Println(c)
}

如此就可以搞定 URL Redirect 驗證,以及全部過程的產生的 Cookie 檢查!當然,若要更細膩地去追蹤每次 URL Redirect 時,產生的 Response Cookies 資料,可以透過 httpClient.CheckRedirect 那處理:

httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
for _, c := range (req.Response.Cookies()) {
fmt.Println(c)
}
}

參考資料:
  • https://pkg.go.dev/net/http#Client
  • https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/net/http/client.go
  • https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/net/http/client.go;l=493
  • https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/net/http/client.go;l=811
  • https://pkg.go.dev/net/http/cookiejar#Jar.Cookies
  • https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/net/http/cookiejar/jar.go

2022年6月21日 星期二

VIM 設定檔(vimrc)切換方式 @ macOS 12.4

由於 coding style 的規劃,需要調整 vim editor 的行為:

set tabstop=4
set shiftwidth=4
set expandtab

因此,設計一個 vim_env.sh 來做切換啟用:

% vim --version
...
   system vimrc file: "$VIM/vimrc"
     user vimrc file: "$HOME/.vimrc"
 2nd user vimrc file: "~/.vim/vimrc"
      user exrc file: "$HOME/.exrc"
       defaults file: "$VIMRUNTIME/defaults.vim"
  fall-back for $VIM: "/usr/share/vim"

% cat project_vimrc 
try
    source $VIM/vimrc 
catch
    echo "\$VIM/vimrc not found"
endtry

set tabstop=4
set shiftwidth=4
set expandtab

% cat vim_env.sh
#!/bin/bash

SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )";
alias vim='vim -u $SCRIPT_DIR/project_vimrc'
echo "Update vim environment (using 'type vim' to show raw command) "

% type vim
vim is an alias for vim -u $SCRIPT_DIR/project_vimrc

接著,就靠 alias 來啟用專案 vimrc