顯示具有 check 標籤的文章。 顯示所有文章
顯示具有 check 標籤的文章。 顯示所有文章

2024年11月19日 星期二

Linux 開發筆記 - OpenLDAP StartTLS 憑證驗證與故障排除 @ Ubuntu 20.04


負責 DevOps 同事休假,當初 LDAP 架設沒涉入,用 AI 輔助來練一下功,盡量讓同事好好休假不用 oncall 解題,就把這過程筆記一下。

現在有 AI 輔助服務,做事的心態變了不少,包括:
  1. 面對不是自己從頭到尾參與的計劃時,該怎樣建立自己了解系統思維或是達成想做的任務
  2. AI 的答案也不一定是對的,該如何驗證(如同管理或跨團隊合作上,請夥伴做事,但不一定做對,該怎樣驗正)
事件起因是一些小型內部服務,因 LDAP 憑證失效而無法完成登入(已登入不受影響),我的追法:
  1. 請 AI 給我指令或程式,檢驗快速檢驗 ldap 連線過程,著重在憑證檢查,例如檢查 https 憑證最常就是靠 openssl 指令或是 browser 瀏覽檢視憑證
  2. 有時 AI 會回的很搞剛,開始回寫 code 的 python 解法,這時因為自己有經驗 openssl 指令就能搞定,所以不斷提醒 AI 直接給指令解
  3. 對於 OpenLDAP 不熟的我,開始追問 AI 憑證相關的方向,才順勢了解 StartTLS ,才知道有 ldapsearch, slaptest 指令可用
  4. 對於管理線上服務踩過雷,知道不能隨便 service restart,多問一下 AI 該怎樣先檢驗設定檔,避免機器從 running -> stop -> 設定有誤 -> 服務停止營運
使用 openssl 指令檢查 SSL 憑證,假設網域是 ldap-dmz.changyy.org

```
$ openssl s_client -connect ldap-dmz.changyy.org:443 
...
---
SSL handshake has read xxxx bytes and written xxx bytes
Verification: OK
---
...
```

使用 openssl 指令檢查 LDAP + StartTLS 憑證,假設網域是 ldap-dmz.changyy.org

```
$ openssl s_client -connect ldap-dmz.changyy.org:389 -starttls ldap
...
---
SSL handshake has read xxxx bytes and written xxx bytes
Verification error: certificate has expired
---
...
```

得知 LDAP + StartTLS 使用流程中的憑證過期了,且此例剛好 https 憑證已正常工作,僅 LDAP + StartTLS 的部分失敗,所以很輕鬆的可以把 https 憑證複製一份到 ldap 使用即可。先追蹤設定檔位置:

```
$ sudo cat /etc/ldap/slapd.d/cn\=config.ldif | grep -i TLS
olcTLSCACertificateFile: /etc/ssl/ldap/wildcard.*.ca-bundle
olcTLSCertificateFile: /etc/ssl/ldap/wildcard.*.crt
olcTLSCertificateKeyFile: /etc/ssl/ldap/wildcard.*.key
$ sudo tree -L 1 /etc/ssl/
/etc/ssl/
├── certs
├── ldap
├── nginx
├── openssl.cnf
└── private
```

速速解:

```
$ sudo cp -r /etc/ssl/ldap /etc/ssl/ldap-bak
$ sudo cp /etc/ssl/nginx/wildcard.* /etc/ssl/ldap/
```

檢驗設定檔和重啟服務:

```
$ sudo slaptest
config file testing succeeded
$ sudo service slapd restart
```

再次驗證:

```
$ openssl s_client -connect ldap-dmz.changyy.org:389 -starttls ldap
...
---
SSL handshake has read xxxx bytes and written xxx bytes
Verification: OK
---
...
```

此外,追蹤 LDAP 和使用他的服務時,意外發現不同的服務整合過程,也順手筆記一下,例如 Apache2 設定檔:

僅 LDAP 非加密:

<Location />
    Order allow,deny
    Allow from all
    AuthType basic
    AuthName "service.changyy.org"
    AuthBasicProvider ldap
    AuthLDAPUrl "ldap://ldap-dmz.changyy.org/dc=changyy,dc=org?uid"
    Require valid-user
</Location>

LDAP + StartTLS:

<Location />
    Order allow,deny
    Allow from all
    AuthType basic
    AuthName "service.changyy.org"
    AuthBasicProvider ldap
    AuthLDAPUrl "ldap://ldap-dmz.changyy.org/dc=changyy,dc=org?uid" TLS
    Require valid-user
</Location>

對應的其他服務也會有類似架構,舉例來說 Mantis 也有 mantisbt.org/docs/master/en-US/Admin_Guide/html/admin.config.auth.ldap.html

預設是啟用 LDAP + StartTLS

//
// Determines whether the connection will attempt an opportunistic upgrade to a TLS connection (STARTTLS).
//
// Defaults to ON.
//
// $g_ldap_use_starttls = ON

需要測試 LDAP 健康情況時,可以把它關閉

// $g_ldap_use_starttls = OFF

補充檢查 ldap service 健康情況時,最常是直接使用 ldapsearch 指令:

```
$ sudo apt install ldap-utils
$ ldapsearch -x -H 'ldap://ldap-dmz.changyy.org' -b 'dc=changyy,dc=org'
...
# search result
search: #
result: 0 Success
...
# numResponses: ###
# numEntries: ###
```

測試 LDAP + StartTLS:

```
$ ldapsearch -x -H 'ldap://ldap-dmz.changyy.org' -b 'dc=changyy,dc=org' -Z
...
ldap_start_tls: Connect error (-11)
additional info: (unknown error code)
ldap_result: Can't contact LDAP server (-1)
```

測試 LDAP + StartTLS 需要更多 debug 資訊:

```
$ ldapsearch -x -H 'ldap://ldap-dmz.changyy.org' -b 'dc=changyy,dc=org' -Z -d 1
...
TLS: peer cert untrusted or revoked (0x402)
TLS: can't connect: (unknown error code).
...
ldap_start_tls: Connect error (-11)
...
```

用 PHP Code 嘗試建立連線,先確認 php.ini 套件:

```
php -i | grep ldap
/etc/php/7.4/cli/conf.d/20-ldap.ini,
Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtmp, rtsp, scp, sftp, smb, smbs, smtp, smtps, telnet, tftp
ldap
...
```

運行:

```
$ cat /tmp/t.php 
<?php
$ldapconn = ldap_connect("ldap://ldap-dmz.changyy.org");
if ($ldapconn) {
    ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0);
    // 關閉 TLS
    putenv('LDAPTLS_REQCERT=never');
    
    $bind = ldap_bind($ldapconn);
    if ($bind) {
        echo "LDAP bind successful...\n";
    } else {
        echo "LDAP bind failed...\n";
    }
}
$ php /tmp/t.php 
LDAP bind successful...
```

收工!

2022年8月25日 星期四

C 開發筆記 - 使用 libwebsockets 製作簡易的 WebSocket Client

已經有用過 Golang 開發小工具,以及其他現成工具如 websocat 等,做 WebSocket Server 的測試。這次回過頭來再仔細看看 libwebsockets 這套,寫一點 C 語言。

程式架構不會太難,比較難的反而是一些小東西,像是跟 ws.ptt.cc 連線時,PTT 會做 Request Header(Origin欄位) 檢查,當作簡單的管控,然而 libwebsockets 則是要設定  LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN 才能不加料 XD

#include <include/libwebsockets/lws-context-vhost.h>

For backwards-compatibility reasons, by default lws prepends "http://" to the origin you give in the client connection info struct. If you give this flag when you create the context, only the string you give in the client connect info for .origin (if any) will be used directly.

大概在這種地方耗掉半小時以上。另外,還有做 wss 連線時,也須設定 LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT 等等,以及如果想要略過 wss 的 SSL 憑證檢查,有滿多設定項目可以試試:LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK | LCCSCF_ALLOW_INSECURE;

其他則是依照 libwebsockets 的架構,要設計連線時的 callback 處理機制,算是體驗了一下。未來就多一個 C 語言工具可以做 WebSocket Server 連線測試。


常見的錯誤訊息:
  • E: SSL_new failed: error:00000063:lib(0)::reason(99)
  • W: [wsicli|0|WS/h1/default/ws.ptt.cc]: lws_client_ws_upgrade: got bad HTTP response '403'
  • E: lws_ssl_client_bio_create: Unable to get hostname
執行 logs:

% make
gcc -g -Wall main.c `pkg-config libwebsockets --libs --cflags`

% ./a.out   
Usage> ./a.out url
./a.out ws://localhost:8000/
./a.out wss://ws.ptt.cc/bbs app://cmd
./a.out wss://ws.ptt.cc/bbs https://term.ptt.cc

% ./a.out wss://ws.ptt.cc/bbs https://term.ptt.cc
[2022/08/25 20:45:29:8530] N: lws_create_context: LWS: 4.3.2-no_hash, NET CLI SRV H1 H2 WS ConMon IPV6-on
[2022/08/25 20:45:29:8539] N: __lws_lc_tag:  ++ [wsi|0|pipe] (1)
[2022/08/25 20:45:29:8964] N: __lws_lc_tag:  ++ [vh|0|default||-1] (1)
wsCallback - LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS
[INFO] libwebsockets version: 4.3.2
[INFO] lws_parse_uri - Server: ws.ptt.cc:443
[INFO] lws_parse_uri - URLScheme: wss
[INFO] lws_parse_uri - URLPath: bbs
[INFO] clientConnectInfo.addres: ws.ptt.cc
[INFO] clientConnectInfo.port: 443
[INFO] clientConnectInfo.path: /bbs
[INFO] clientConnectInfo.host: ws.ptt.cc
[INFO] clientConnectInfo.origin: https://term.ptt.cc
[INFO] clientConnectInfo.ssl_connection: 109
[2022/08/25 20:45:29:9789] N: __lws_lc_tag:  ++ [wsicli|0|WS/h1/default/ws.ptt.cc] (1)
call lws_client_connect_via_info done, webSocket is NULL(NO)
noop
[2022/08/25 20:45:30:0229] N: lws_gate_accepts: on = 0
[2022/08/25 20:45:30:0575] N: lws_gate_accepts: on = 0
wsCallback - LWS_CALLBACK_CLIENT_ESTABLISHED
wsCallback - LWS_CALLBACK_CLIENT_WRITEABLE
wsCallback - LWS_CALLBACK_CLIENT_RECEIVE
RECEIVER: HTTP/1.1 200 OK

     ??      PTT                ?P   ??        ?P  ??      ???i?i?i?i?i?i??
             140.112.172.11 ?P     ???i???i??            ???i?i?i?i?i
  ?z?w?{     ?????~?{        ???d?i?i???i??   ?P   ???i?i?i?i?i??  ?P
  ?x?V?|?{   ptt.cc            ???i?i?i?i?i???i??    ???i?i?i?i?i
  ?x?V  ?x                   ???i?i?i???i?i?i????  ???i?i?i?i?i??  ??  ?P
?w?}    ?x?z?w?w?{  ?P       ????      ?i?i?i?i?????i?i?i?i    ?P
        ?|?t  ?V?x      ?P           ???i?i?i?i???i?i?i??            ?P
                ?x?z?w?w?w?{         ?i?i?i?i?i?h?h?g?g?f?f?e?e?d?c?b
            ?z?w?r?}?V?V  ?| ???i?i?i?i?i?h?h?g?g?f?f?e?e?d?d?c?c?b
            ?x?V?V           ???i?i
wsCallback - LWS_CALLBACK_CLIENT_RECEIVE
RECEIVER:       ?i?h?h?g?g?f?f?e?e?d?c?b

wsCallback - LWS_CALLBACK_CLIENT_RECEIVE
RECEIVER:            ?f?d?d?e?g?f?e         ?e?d?g??     ???âg?d?e       ????            
               ?g?h  ?h         ?e?d         ?b      ???c?g?d                   
?п?J?N???A?ΥH guest ???[?A?ΥH new ???U:               
^C   ?b?c?e?f?e?g?h?g?f?d    ?n  ??   ??  ???n?l?? ??  ???? ?l?k  ?h            

2015年11月18日 星期三

Ansible 筆記 - 找尋本機端資源並部署至遠端機器,包含複雜 shell 指令、資源尋找錯誤處理、hosts 之間變數傳遞等

這個需求其實還滿簡單實現的,但為了想要達成更仔細的錯誤偵測,後來越改越複雜 XD 順便筆記一下。完整的使用情境:每次部署遠端機器前,本地端會有一個 package 產出(rpm),透過 find 指令找到最新產出的 package 後,再部署到遠端機器(其實也可以把架設 yum server 來搞定啦 XD)

最簡單的方式,使用 vars 紀錄:

- hosts: YourTargetServers

  vars:
    rpm_dir: "/data/rpm/production/"
    rpm_name_prefix: "packagename*"
    rpm_find_command: "find {{ rpm_dir }} -name '{{ rpm_name_prefix }}' -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d ' ' "
    rpm_path: "{{ lookup('pipe', 'rpm_find_command') }}"

  tasks:
    - name: find rpm prefix name
      debug: msg="{{rpm_name_prefix}}"

    - name: find rpm command
      debug: msg="{{rpm_find_command}}"

    - name: find rpm path
      debug: msg="{{rpm_path}}"


這樣算是收工了,但是 rpm_path 若沒找到時,卻無法有洽當的錯誤偵測 Orz

所以第一次再改成這招:

- hosts: YourTargetServers

  vars:
    rpm_dir: "/data/rpm/production/"
    rpm_name_prefix: "packagename*"
    rpm_find_command: "find {{ rpm_dir }} -name '{{ rpm_name_prefix }}' -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d ' ' "

  tasks:
    - name: find rpm prefix name
      debug: msg="{{rpm_name_prefix}}"

    - name: find rpm command
      debug: msg="{{rpm_find_command}}"

    - name: find rpm path
      local_action: command /bin/bash -c "{{ rpm_find_command }}"
      sudo: False
      failed_when: " '' != rpm_path_result.stderr"

    - name: get rpm path
      debug: msg="{{ rpm_path_result.stdout }}"


但這樣仍有個缺點,那就是若有 roles 等一堆工作,會變成那堆工作做完才會跑 tasks,這時就會希望先偵測本地端的資料在跑 roles 工作,於是乎又改成:

- hosts: loclahost

  vars:
    rpm_dir: "/data/rpm/production/"
    rpm_name_prefix: "packagename*"
    rpm_find_command: "find {{ rpm_dir }} -name '{{ rpm_name_prefix }}' -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d ' ' "

  tasks:
    - name: find rpm prefix name
      debug: msg="{{rpm_name_prefix}}"

    - name: find rpm command
      debug: msg="{{rpm_find_command}}"

    - name: find rpm path
      local_action: command /bin/bash -c "{{ rpm_find_command }}"
      failed_when: " '' != rpm_path_result.stderr"

    - name: get rpm path
      debug: msg="{{ rpm_path_result.stdout }}"

- hosts: YourTargetServers

  vars:
    rpm_path: "{{ hostvars['localhost']['rpm_path_result']['stdout'] }}"

  tasks:
    - name: find rpm path
      debug: msg="{{rpm_path}}"


如此一來,就可以先確保在 localhost 把資源準備齊了,再進行遠端部署動作,也包含偵錯處理啦。

2015年8月29日 星期六

Node.js 筆記 - 使用 Bloom Filter 實作大量 URL Checker/Filter (SPAM)

最近想要針對 UGC 資料進行過濾,首先要面對的就是 SPAM/18+ 資料,接著,沒想到聽到 Bloom Filter 字眼!這個學生時代就碰過的東西,竟然會被提及,就那把玩一下吧!對於 Bloom Filter 的用法需要留意一下,本身設計是用時間省空間的架構,並且有誤判的機率,只要用得恰當都還好啦。

有點久沒摸 node.js 了,就挑他來試試,立馬找到有人已經實作好了 XD https://www.npmjs.com/package/bloomfilter 我只好接著使用啦 :P

接著不知不覺就蹦出一個 url-spam-checker 了 :P 有需要想把玩的可以試試:

$ npm install url-spam-checker
$ vim test.js
var
sys = require('sys'),
path = require('path'),
url_spam_checker = require('url-spam-checker')
;

url_spam_checker.createServer([
function handle_resource(callback){
var ret = [];
ret.push('yahoo.com');
ret.push('google.com.tw');
callback(null, ret);
}
], 8000, sys.log, sys.log);
$ node test.js


就會在 8000 port 起了 HTTP server ,接著可以用 http://localhost:8000/?url=http://tw.yahoo.com 的方式測試,若存在會回傳 HTTP 200 OK,不存在則是 HTTP 404 NOT FOUND。

當要驗證的 URL = http://tw.yahoo.com 也會被判定 200 OK,因為這邊是檢驗 domain level 的,不是 hostname 而已,同理在 URL = http://yahoo.com.tw 則是 404 NOT FOUND。而上述 handle_resource 可以自行填寫,例如回傳個20萬筆也是 ok 的。

這邊在使用 Bloom Filter 時,其 bit space =  Pattern 數量 x 256,這是因為我設定 hash function 有 8 組,所以 256 = 2^8  囉。

有興趣可以參考這邊:https://www.npmjs.com/package/url-spam-checker

2015年4月28日 星期二

AWS 筆記 - 關於 Load Balancers 之 Health Check 處理心得

若是 web server,通常 Health check 都是定期去請求 /index.html 是否正常,以此來判斷服務是否正常。而檢查 /index.html 有很多含義,例如當 server response time 超過 10 秒,也能標記為不正常等。

最近碰到一個案例,是在處理外包案時,發現對 / 或 /index.html 時,一直不正常,但用瀏覽器去看又覺得無恙,例如用 wget -d 也行,最後發現用 telnet 看到問題 XD

$ telnet server_ip 80
GET / 或 GET /index.html


接著看到一些 PHP 噴 error message。

發現,外包在一些設定檔內,透過判斷 $_SERVER['HTTP_HOST'] 是不是他們家的 server ,以此決定開啟 debug mode。

<?php
if ($_SERVER['HTTP_HOST'] == 'xxx.yyy.com') {
    $is_test_server = true;
} else {
    $is_test_server = false;
}


這件事大概只能透過 telnet 等不送 HTTP header 要求時,才會發現的 bug。這個 bug 還滿好解的,例如加上一些 isset($_SERVER['HTTP_HOST']) 等,但有一些 PHP framework 使用上會要求提供網址位置,例如:

<?php
$protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
$config['base_url'] = $protocol . '://' . $_SERVER['HTTP_HOST'] . '/';


這時又只能再爆一次。若有心解決,就繼續改 code ,無心解決的話,可以把一些 error message 都關掉,或是把 Health check 改成 TCP 80 模式吧 XD

2014年7月3日 星期四

[Linux] 使用 wget 測試 Remote Resource Availability @ Ubuntu 14.04

wget 有 --spider 模式,挺方便的 :P

$ wget --spider http://www.google.com/favicon.ico
Spider mode enabled. Check if remote file exists.
Resolving www.google.com (www.google.com)... 173.194.33.148, 173.194.33.144, 173.194.33.145, ...
Connecting to www.google.com (www.google.com)|173.194.33.148|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [image/x-icon]
Remote file exists.

2014年3月20日 星期四

iOS 開發筆記 - 在 Compiler Time 使用 __has_include 偵測 Framework 是否存在

__has_include Framework

以 FacebookSDK 來說,之前一直都用 github.com/facebook/facebook-ios-sdk 來使用,最近發現 source tree 預設有一些 bug 無法在 Xcode 5.1 編譯成功,自行修又容易碰到維護問題,於是就跑去下載 Facebook 官方打包好的 facebook-ios-sdk-current.pkg 來用了。

之前用 Facebook source code 來編譯時,在 header file 的使用:

#import "Facebook.h"

然而,現在改用 FacebookSDK 時,則該使用

#import <FacebookSDK/FacebookSDK.h>

因此想要在 Compiler Time 來確認,因此來增加彈性。

所幸有找到類似堪用的方式(Include File Checking Macros):

#if defined(__has_include)
#if __has_include("FacebookSDK/FacebookSDK.h")
#import <FacebookSDK/FacebookSDK.h>
#else
#import "Facebook.h"
#endif
#endif


此外,在 Project 的 Build Settings,就可以這樣通用設定:
  • Framework Search Path: /path/sdk/FacebookSDK
  • Library Search Paths: ${SRCROOT}
  • User Header Search Path: /path/sdk/facebook-ios-sdk
其中 /path/sdk/FacebookSDK 指的是 facebook-ios-sdk-current.pkg 安裝位置(只要把pkg裡的 FacebookSDK.framework 拖進去會自動設定好),而 Library Search Paths 和 User Header Search Path 的設定則是為了採用 source tree 方式。