說明《NGINX 源碼建構》練習的最佳實務解法
https://hackmd.io/@brlin/nginx-build-from-source-reference-answer
除另外標註之內容外本作品的內容以《Creative Commons 姓名標示-相同方式分享》授權條款第 4.0 國際版或其任意更近期版本釋出供大眾於授權範圍內自由使用
請使用 林博仁(Buo-ren, Lin) <https://brlin.me>
作為滿足「姓名標示」授權要求的表彰內容
如有任何問題或其他要求請聯繫 buo.ren.lin+legal@gmail.com
以下說明完成本練習所需要的前備條件:
以下說明應該能重現本筆記之結果的環境細節:
Ubuntu 23.10 AMD64(透過 Ubuntu 22.04 容器)
Docker 容器的執行環境:
1.25.3
以下說明完成本練習的相關流程:
大部分開放來源碼軟體的專案官方的文件通常會有一些源碼建構軟體的重要資訊,所以事先詳閱是強烈建議的
於搜尋引擎查得並訪問 nginx.org NGINX 開源版官網(非 nginx.com 商業版官網),您可以於右側導覽欄找到官方說明文件的頁面(紅框處):
就可以找到可能跟源碼建構相關的說明文件(紅框處):
主要有關的說明文件應該是這個:Building nginx from Sources,之後遇到問題時可以回來確認是不是某個眉角沒有注意到
為確保重現環境的一致性且避免練習期間的操作影響到主端系統,我們建立一個 Ubuntu 22.04 的 Docker 容器並在其內進行後續的練習操作。以 root 身份執行下列命令自 ubuntu:22.04 容器映像創建一個新的容器:
附註:
array=(...)
為 Bash 的索引式陣列語法,如上範例命令為例使用此語法可以以 Bash 註解的形式為命令參數新增說明文字"${array[@]}"
為 Bash 陣列的其中一種參數展開語法,會將陣列中的元素以空白字元為分隔插入於展開處,您可以在展開命令前面加上 echo 命令(如 echo "${array[@]}"
)以預覽展開結果docker
使用者群組的成員則可以以一般使用者身份運行前述 docker run
命令(允許使用者直接訪問 Docker 服務有潛在的安全風險,應僅開放給完全信任的使用者使用)docker run
命令前額外執行下列命令將本地的 HTTP(S) 正向代理服務設定(須已設定相關環境變數)繼承給 Docker 容器使用:
docker_run_opts+=(--env http_proxy)
docker_run_opts+=(--env https_proxy)
docker_run_opts+=(--env no_proxy)
執行命令後您應可以取得容器內 Bash 殼層互動式操作環境的命令提示文字:
因為 Ubuntu 的 Docker 容器預設使用(應該是在英國跟美國的)archive.ubuntu.com 軟體庫服務於台灣本地下載軟體的速度較慢,我們可以改用本地的軟體庫鏡像服務來減少軟體下載所需時間
以 root 身份於文字終端執行下列命令已將軟體庫地址替換為台灣本地鏡像站的地址:
因為 APT 軟體來源的設定已經變更了,我們需要重新建立本地的 APT 軟體包索引資料以獲取當前軟體庫的軟體包清單與下載地址等相關資訊
以 root 身份於文字終端執行下列命令以建立/重建本地的 APT 軟體包索引資料:
如果設定軟體庫鏡像站的步驟順利完成的話,您應該可於命令輸出中找到自 tw.archive.ubuntu.com 伺服器下載資料的進度報告訊息:
到 NGINX 開源版官方網站的下載頁可以找到當前最新版本(推定為指開發主線版)的 NGINX 來源程式碼封存檔的下載地址:
下載頁中紅框的 nginx-1.25.3 超連結為 NGINX 的 1.25.3 版開發主線版本的來源程式碼封存檔的下載連結
附註: NGINX 的開發主線版與穩定版的差異:
詳細資訊參閱 Introducing NGINX 1.10 and 1.11 - NGINX 文章的 Explaining NGINX’s Version Numbering 章節
由於 GNU+Linux 作業系統可以使用 curl 等 Web 客戶端軟體直接於文字終端下載 NGINX 的軟體來源碼包,因 Ubuntu 22.04 官方容器映像預設不提供 curl 軟體的原因我們需要以 root 身份執行下列命令手動進行安裝:
然後執行下列命令下載 1.25.3 版的 NGINX 的來源程式碼封存檔:
為避免 NGINX 開源版官方網站的 NGINX 軟體來源碼封存檔遭到惡意竄改造成安裝 NGINX 的主機資料被竊取或被當作 DDoS 攻擊的肉雞所以必須要驗證其資料完整性,首先需要確認該軟體有提供哪種驗證資料完整性的方案
查看 NGINX 開源版官方網站下載頁可以發現 nginx-1.25.3 超連結 右邊的 pgp 超連結(紅框處)為透過 Pretty Good Privacy(PGP) 驗證 NGINX 1.25.3 軟體來源碼封存檔資料完整性的數位簽名檔案:
執行下列命令下載 1.25.3 版 NGINX 來源程式碼封存檔驗證資料完整性用的 PGP 數位簽名檔:
目前 GNU/Linux 作業系統主流的 PGP 操作軟體為 GNU Privacy Guard(GnuPG),以 root 身份執行下列命令以進行安裝:
接下來可以試著執行下列命令以進行 NGINX 軟體來源碼封存檔的資料完整性驗證:
如果命令輸出下列錯誤訊息代表您的 GnuPG 鑰匙圈尚未匯入簽發這個數位簽名的人的公鑰:
您可以透過「nginx pgp verify」搜尋關鍵字可以在 NGINX 開源版官方網站找到應該是 k.pavlov@f5.com 對應的 Konstantin Pavlov 這個專案維護者的 PGP 公鑰:
執行下列命令下載 Konstantin Pavlov 專案維護者的 PGP 公鑰:
然後執行下列命令將 Konstantin Pavlov 專案維護者的 PGP 公鑰匯入自己的 PGP 鑰匙圈中:
命令執行順利的話應該可以看到下列輸出:
附註: 命令輸出中的下列警告訊息為正常現象(PGP 信任網未建立):
附註: 您另可透過第三方 PGP 金鑰服務器來獲取該簽發者的簽名公鑰:
如果公鑰匯入順利且 NGINX 軟體來源碼封存檔未被竄改的話再次驗證應該可以順利驗證成功(下列輸出第五行的「Good signature」關鍵字):
警告: 使用此方式驗證 NGINX 來源程式碼封存檔的資料完整性有下列問題:
故於資安實務上這種作法我們並無法 100% 確認這個檔案一定是沒問題的,如何有效避免這些問題非本文章的說明範疇,請搜尋 PGP web of trust 關鍵字以自行了解相關資訊
執行下列命令以解開 NGINX 的軟體來源碼封存檔:
您應可於當前作業目錄找到解開的 nginx-1.25.3 軟體來源碼目錄:
據前面一開始提到的 Building nginx from Sources 官網說明文件所稱,NGINX(如同大多數開放來源碼軟體)使用一個叫做 configure
的軟體建構配置程式進行軟體的軟體建構細節配置,一般來說會進行下列功能:
最後軟體建構配置程式會生成用於實際進行軟體建構的程序供我們於後續步驟使用
附註: 觀察 NGINX 來源碼目錄中的內容您或許會注意到 NGINX 並不是使用常規的軟體建構系統(GNU Autotools/CMake/Maven)而是使用專案專有之模組化的 Shell 腳本來進行軟體建構的配置與建構程式生成
我們先執行下列命令切換作業目錄到 NGINX 1.25.3 軟體來源碼的目錄中,方便後續要進行的操作:
我們可以於前述之上游專案的說明文件或是 configure
程式印出的的幫助訊息查看可以 NGINX 軟體建構配置的項目,因為上游專案的文件不一定跟我們使用的版本一致的關係所以這邊我們以 configure 程式的輸出作為主要的建構參考:
……NGINX 的軟體建構配置程式的輸出實在是太長了,因為 Ubuntu 22.04 Docker 容器映像中預設提供的 more
分頁器 沒辦法自由查看上一頁的內容的關係,我們可以 root 身份執行下列命令改裝功能比較強大的 less
分頁器:
然後就可以執行下列命令,用 Bash 殼層操作界面 的輸入/輸出資料流重導向功能來一頁一頁瀏覽 ./configure --help
命令的輸出:
您可以於 less
分頁器中使用 /
按鍵搜尋特定的關鍵字快速查找要啟用的功能,自 ./configure --help
命令的輸出我們可以知道要啟用 HTTPS(SSL/TLS) 支援需要於 configure
程式的執行命令中新增 --with-http_ssl_module
命令選項:
接下來我們就可以迭代地執行下列命令來配置 NGINX 的軟體建構,並根據其錯誤或警告訊息來準備建構所需要的環境:
首先 NGINX 軟體建構配置程式抱怨找不到 C 語言程式編譯器:
於 GNU/Linux 系統中常見的提供 C 語言程式編譯器的軟體為 GNU Compiler Collection(GCC) 與 Clang,我們以 root 身份安裝 GCC:
附註: 您也可以執行下列命令搜尋 Ubuntu 軟體庫提供之,可能提供 C 語言程式編譯器的軟體包:
注意不是每個 C 語言程式編譯器(的各個版本)都支持 NGINX 軟體軟體建構所需要的編譯器特性
然後再一次執行 NGINX 的軟體建構配置程式,這一次它抱怨找不到 PCRE 程式庫:
Debian 系 GNU/Linux 作業系統的 APT 軟體包管理器會將軟體的不同組成元件 分開打包成獨立的軟體包 以節省系統的儲存空間使用,於源碼建構時軟體的依賴組件我們主要會安裝它的「軟體開發用檔案(包含但不限於 C/C++ 語言程式的標頭檔案、用於靜態連結的靜態程式庫檔案與軟體開發者用的參考文件等等)」的軟體包,此類軟體包通常會使用 -dev
的軟體包名後綴(如果是程式庫則通常會有 lib
的軟體包名前綴),我們可以執行下列命令將吻合此名稱式樣的軟體包通通找出來:
附註:
^libpcre.*-dev$
為 POSIX 風格的正規表達式語法(參閱 apt-cache(8) 與 regex(7) 的 manpage 使用手冊頁面)-devel
後綴於 apt search
命令的輸出我們可以發現 Ubuntu 軟體庫有同時提供第一版與第二版的 PCRE 程式庫:
附註: 對您沒看錯,同 libpcre3-dev 軟體包的文字描述所稱,實際上該軟體包為第一版而非第三版的 PCRE 程式庫(翻桌)
因自 NGINX 軟體建構配置程序的輸出可以判斷 NGINX 有相容第二版的 PCRE 程式庫,這邊選擇以 root 身份執行下列命令安裝第二版 PCRE 程式庫的軟體開發用文件:
附註: NGINX 於 1.21.5 版(2021/12/28)起才新增了第二版 PCRE 程式庫的支援,如建構舊版 NGINX 則須安裝第一版 PCRE 程式庫的軟體開發用檔案
然後再一次執行 NGINX 的軟體建構配置程式,這一次它抱怨找不到 OpenSSL 程式庫的安裝:
附註: 因為 NGINX 需要的是 OpenSSL 軟體建構時期用的檔案而非執行時期用的檔案,所以就算您的系統明顯有安裝基於 OpenSSL 程式庫的軟體 NGINX 的軟體安裝配置程序仍然會提示未安裝 OpenSSL 程式庫。具體會使用的檔案可以於軟體來源碼目錄中執行下列命令確認:
使用先前 PCRE 程式庫的經驗,我們可以執行下列命令搜尋提供 OpenSSL 程式庫的軟體包:
……但是什麼都找不到呢:
我們把搜尋條件再調鬆一點,現在終於有結果了但是好像都不是我們要的:
……我們把搜尋條件再調鬆一點,這次我們找到了跟 OpenSSL 同名的 openssl
軟體包,但是還是沒有找到看起來有提供 OpenSSL 軟體開發用檔案的軟體包:
看來我們的這個作法已經走到了一個死胡同了,我們試試看能不能從其他地方找到有價值的線索。如果您有使用前面附註提到之 grep
命令查詢跟 OpenSSL 程式庫有關的軟體建構配置邏輯的話您應該會注意到在 auto/lib/openssl/conf shell 小腳本中的依賴軟體檢查邏輯有嘗試載入一個叫做 openssl/ssl.h
的 C 程式語言標頭檔案:
我們可以使用 apt-file
工具來查詢所有有提供這個檔案路徑式樣的軟體包,首先先以 root 身份執行下列命令安裝軟體:
然後執行下列命令建立 apt-file 軟體的本地快取資料
最後再執行下列命令搜尋所有提供 openssl/ssl.h
路徑結尾之檔案的軟體包:
從查詢結果中的軟體包名稱跟提供的檔案路徑複雜度可推測應該是 libssl-dev 這個軟體包提供我們需要的 OpenSSL 開發用檔案
附註: 以下提供幾種同樣可以找到正確的提供 OpenSSL 軟體開發用檔案軟體包(libssl-dev)的可能作法:
編輯 APT 軟體包管理器的軟體來源清單啟用來源碼軟體包的軟體來源(deb-src),然後使用 apt showsrc
命令查詢 openssl
這個來源碼軟體包到底會建構出哪些看起來是提供軟體開發用檔案的二進位軟體包
前面有提過「APT 軟體包管理器會將軟體的不同組成元件 分開打包成獨立的軟體包」這點,觀察 openssl 軟體包的文字描述可以得知此軟體包提供「Secure Sockets Layer(SSL) 工具組」的「加密學相關的 工具程式」,那有沒有軟體包有提供「Secure Sockets Layer(SSL) 工具組」的「軟體開發用檔案」呢?直接用 Secure Sockets Layer toolkit
當作搜尋關鍵字找找看就可以發現 libssl-dev 應該是我們需要的軟體包:
使用 ldd "$(which openssl)"
命令查看 openssl
程式的會於執行時期載入的可能跟 OpenSSL 有關的共享程式庫名稱(libssl)(版本號去掉),然後用 apt search --names-only libssl
命令尋找該程式庫名稱開頭 -dev
結尾的提供軟體開發用檔案的軟體包:
編輯 APT 軟體包管理器的軟體來源清單啟用來源碼軟體包的軟體來源(deb-src),然後使用 apt showsrc
命令查詢 NGINX 等確定有使用 OpenSSL 程式庫的軟體的建構依賴軟體的軟體包有哪個看起來像是跟 OpenSSL 程式庫比較有關的,提供軟體開發用檔案的軟體包
當然傳統的直接找 Google/StackOverflow/AskUbuntu/ChatGPT/Google Gemini 問也可能可以找到解答
以 root 身份 執行下列命令補上依賴軟體的安裝:
然後再一次執行 NGINX 的軟體建構配置程式,現在它抱怨缺少 zlib 程式庫:
用同樣方式去尋找提供 zlib 程式庫的軟體開發用檔案軟體包,推定應該是 zlib1g-dev 這個軟體包沒錯:
附註: 跟 OpenSSL 程式庫一樣,提供 zlib 程式庫軟體開發用檔案的軟體包一樣不太遵守 Debian 系 GNU/Linux 作業系統的軟體開發用檔案軟體包的命名慣例,這些軟體就只能多花點時間確認了(攤手)
以 root 身份 執行下列命令補上依賴軟體的安裝:
然後再一次執行 NGINX 的軟體建構配置程式,出現下列輸出代表 NGINX 的軟體建構已經順利完成配置了:
留意 NGINX 安裝的:
且 NGINX 軟體來源碼目錄中出現可用於建構 NGINX 軟體的 Makefile 檔案:
因為 NGINX 軟體建構配置程式產生了 Makefile 文件,我們可以推定該軟體使用的軟體建構自動化工具為 GNU Make。以 root 身份 執行下列命令進行安裝:
然後我們可以執行下列命令進行 NGINX 軟體的軟體建構:
如果看到類似這樣的輸出且命令的結束狀態代碼為零則代表軟體建構順利結束:
附註:
您可以在 make 命令前加上 time 命令以測量軟體建構的所需時間:
這邊的軟體建構所需參考時間為 2.841 秒,沒有開啟平行建構的所需時間則為 15.772 秒(Framework 13 AMD 7040 系列(Ryzen 7 7840U)(總執行緒數:16)、使用 performance 性能層級),隨著要建構的軟體的大小規模增長軟體建構的所需時間也會隨之增加,超過半小時甚至是長達一整天或更久也是有可能的所以能開啟平行建構的話建議開啟
有些情況下會發生平行建構報錯但是單執行緒建構可以正常完成的狀況,這通常是軟體的軟體建構自動化實現有缺陷所造成
GNU Make 預設會跳過已經完成且源碼檔案未異動的軟體建構工作項目,如果要進行重新建構您需要執行 clean
這個偽 make 目標來清理所有先前 Make 產生的的建構(中間)產物:
注意此操作也會將 Makefile 文件移除,需要重新執行軟體建構配置程序來重新生成那個檔案
以 root 身份執行 Makefile 提供的 install
偽 make 目標即可將建構好的 NGINX 軟體安裝到系統中:
附註: 因為一般使用者沒有權限在 /usr/local 系統目錄寫入檔案,所以命令才需要以 root 身份執行,如果於 NGINX 軟體建構配置時期有指定將軟體安裝到一般使用者可寫入的目錄中是不需要這樣做的
使用 less 純文字資料分頁器審閱 服務配置前綴目錄 中的 nginx.conf 服務主配置檔可以發現 NGINX 服務預設會在 80 通訊埠號提供一個服務 軟體安裝前綴目錄/html 目錄下網頁的靜態 HTTP 服務:
接下來您可以以 root 身份執行下列命令將 NGINX 服務啟動並作為幕後服務運行:
附註:
CAP_NET_BIND_SERVICE
Linux 能力)使用 curl HTTP 客戶端軟體訪問本地主機(於此情境指 Docker 容器自己)的 80 HTTP 服務常規通訊埠應該可以獲得類似下面的 NGINX 範例站台網頁內容:
於宿主機使用 Web 瀏覽器訪問本地主機的的 80 HTTP 服務常規通訊埠應該也可以看到相同頁面:
接下來我們來測試看看我們安裝的 NGINX 是否支援 HTTPS 服務,查看 NGINX 服務的主配置檔可以看到 NGINX 有一個預設註解掉的 HTTPS 站台範例配置:
由於 Ubuntu 22.04 的 Docker 容器映像並無提供任何的純文字文件編輯器,我們以 root 身份執行下列命令安裝使用上最簡單的 GNU nano 編輯器軟體:
然後我們可以以 root 身份執行下列命令編輯 NGINX 的服務主配置檔將該 HTTPS 服務配置取消註解:
附註: GNU nano 編輯器會在操作界面的下側列舉可使用的常見操作,各操作前面的 插入記號-字元 序列代表該操作需要以 Ctrl+字元 的組合鍵觸發:
例如內容編輯完成後可以按下 Ctrl+X 組合鍵存檔結束編輯
留意該站台配置會參照一個叫做 cert.pem 的 X.509 證書文件以及一個叫做 cert.key 的 RSA 私鑰文件:
因為我們修改了配置,我們以 root 身份執行下列命令檢查 NGINX 配置的正確性:
附註: 需要以 root 身份執行的原因是因為 NGINX 安裝到系統目錄中以一般使用者身份執行會有部份資源存取不了的問題,如果 NGINX 是安裝到使用者自己的目錄下就沒這個問題
會發現 NGINX 抱怨無法存取到 cert.pem 這個 HTTPS 服務會使用到的 X.509 證書文件:
使用 ls
命令查看會發現 NGINX 配置目錄下並沒有該 SSL/TLS 證書文件(cert.pem)跟私鑰文件(cert.key),我們可以執行下列命令自行簽發一個給 localhost 站台使用的 X.509 證書來用:
然後以 root 身份執行下列命令將產生出來的證書跟私鑰文件安裝到 NGINX 服務配置目錄:
再次檢查 NGINX 配置的正確性應該就能過了:
然後以 root 身份執行下列命令重新載入 NGINX 的服務配置:
附註: 此命令實際的作用為自先前提到的 NGINX 服務主進程進程識別編號檔讀取 NGINX 服務主進程的進程識別編號,然後對該進程發送 SIGHUP 訊號,更多資訊可以參閱 Controlling nginx 這個官方文件
順利的話您應可以執行下列命令訪問到 NGINX 的 HTTPS 站台:
於宿主機使用 Web 瀏覽器(本文以 Mozilla Firefox Web 瀏覽器應用軟體為例)訪問本地主機的 https://localhost 頁面則會先跳出 SSL/TLS 驗證警告畫面(同前所述因為憑證是我們自己簽發的不被 Web 瀏覽器/作業系統信任:
點擊下方的「進階…」按鈕再點擊「接受風險並繼續」按鈕就可以讓 Web 瀏覽器記住此安全例外設定:
接下來應該就可以看到同一個 NGINX 範例站台頁面:
至此我們就可以確定我們的建構的 NGINX 服務是正常運作且有滿足我們所有要求的
以下提供實際進行操作的終端機畫面錄影,僅供參考:
以下說明進行本練習所需要的相關參考資料:
update
sub commandapt-get update
命令的用途docker run
、docker exec
等命令之 --interactive
、--tty
命令選項的用途與於啟動 Bash 互動式殼層操作界面情境的目的--verify
gpg
命令選項的用途與支援的引數類型CAP_NET_BIND_SERVICE
這個 Linux 能力的效果docker run
命令的 --publish
跟 --expose
命令選項之間的差異openssl genrsa
命令的用途與相關命令選項openssl req
命令的用途與相關命令選項openssl x509
命令的用途與相關命令選項