說明《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 容器的執行環境:
$ docker --version
Docker version 24.0.5, build ced0996
1.25.3
以下說明完成本練習的相關流程:
大部分開放來源碼軟體的專案官方的文件通常會有一些源碼建構軟體的重要資訊,所以事先詳閱是強烈建議的
於搜尋引擎查得並訪問 nginx.org NGINX 開源版官網(非 nginx.com 商業版官網),您可以於右側導覽欄找到官方說明文件的頁面(紅框處):
就可以找到可能跟源碼建構相關的說明文件(紅框處):
主要有關的說明文件應該是這個:Building nginx from Sources,之後遇到問題時可以回來確認是不是某個眉角沒有注意到
為確保重現環境的一致性且避免練習期間的操作影響到主端系統,我們建立一個 Ubuntu 22.04 的 Docker 容器並在其內進行後續的練習操作。以 root 身份執行下列命令自 ubuntu:22.04 容器映像創建一個新的容器:
docker_run_opts=(
# 離開容器的作業階段時自動摧毀容器,節省磁碟空間使用
--rm
# 啟用標準輸入裝置(stdin)支援(使 bash shell
# 互動式模式(interactive mode)得以正常運行)
--interactive
# 啟用偽 Teletype(TTY) 終端機模擬功能(使 bash shell
# 互動式模式得以正常運行)
--tty
# 設定人類可讀的 Docker 容器名稱
# (於 `docker ps`、`docker container list` 命令中呈現)
--name nginx-source-build
# 設定容器內的主機名稱(於 Bash 提示字串呈現)
--hostname nginx-source-build
# 將容器內的 HTTP 跟 HTTPS 標準通訊埠發布到宿主機的回送(loopback)網路界面上,方便測試 NGINX 服務功能
--publish 127.0.0.1:80:80/tcp
--publish 127.0.0.1:443:443/tcp
# 規避 Ubuntu Docker 容器無法正確輸入與顯示中文字元的問題
--env LANG=C.UTF-8
)
docker run "${docker_run_opts[@]}" 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 殼層互動式操作環境的命令提示文字:
$ sudo docker run "${docker_run_opts[@]}" ubuntu:22.04
root@nginx-source-build:/#
因為 Ubuntu 的 Docker 容器預設使用(應該是在英國跟美國的)archive.ubuntu.com 軟體庫服務於台灣本地下載軟體的速度較慢,我們可以改用本地的軟體庫鏡像服務來減少軟體下載所需時間
以 root 身份於文字終端執行下列命令已將軟體庫地址替換為台灣本地鏡像站的地址:
sed_opts=(
# 直接修改文件而非將修改後的結果輸出到標準輸出裝置(stdout)
--in-place
# 要套用的 sed 表達式(將每行跟 `//archive\.u` 正規表達式(regular expression)
# 式樣(pattern)比對(match)到的字串替換為 `//tw.archive.u` 字串)
--expression='s@//archive\.u@//tw.archive.u@'
)
sed "${sed_opts[@]}" /etc/apt/sources.list
因為 APT 軟體來源的設定已經變更了,我們需要重新建立本地的 APT 軟體包索引資料以獲取當前軟體庫的軟體包清單與下載地址等相關資訊
以 root 身份於文字終端執行下列命令以建立/重建本地的 APT 軟體包索引資料:
apt update
如果設定軟體庫鏡像站的步驟順利完成的話,您應該可於命令輸出中找到自 tw.archive.ubuntu.com 伺服器下載資料的進度報告訊息:
root@43e688c2defb:/# apt update
Get:1 http://tw.archive.ubuntu.com/ubuntu jammy InRelease [270 kB]
Get:2 http://tw.archive.ubuntu.com/ubuntu jammy-updates InRelease [119 kB]
Get:3 http://tw.archive.ubuntu.com/ubuntu jammy-backports InRelease [109 kB]
Get:4 http://tw.archive.ubuntu.com/ubuntu jammy/universe amd64 Packages [17.5 MB]
...stripped...
Fetched 29.1 MB in 4s (8156 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
7 packages can be upgraded. Run 'apt list --upgradable' to see them.
到 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 身份執行下列命令手動進行安裝:
apt install curl
然後執行下列命令下載 1.25.3 版的 NGINX 的來源程式碼封存檔:
curl_opts=(
# 跟隨 301/302 轉址(如果有)
--location
# 使用下載網址 URL 末端片段作為下載資源的檔案名稱
--remote-name
)
curl "${curl_opts[@]}" https://nginx.org/download/nginx-1.25.3.tar.gz
為避免 NGINX 開源版官方網站的 NGINX 軟體來源碼封存檔遭到惡意竄改造成安裝 NGINX 的主機資料被竊取或被當作 DDoS 攻擊的肉雞所以必須要驗證其資料完整性,首先需要確認該軟體有提供哪種驗證資料完整性的方案
查看 NGINX 開源版官方網站下載頁可以發現 nginx-1.25.3 超連結 右邊的 pgp 超連結(紅框處)為透過 Pretty Good Privacy(PGP) 驗證 NGINX 1.25.3 軟體來源碼封存檔資料完整性的數位簽名檔案:
執行下列命令下載 1.25.3 版 NGINX 來源程式碼封存檔驗證資料完整性用的 PGP 數位簽名檔:
curl_opts=(
# 跟隨 301/302 轉址(如果有)
--location
# 使用下載網址 URL 末端片段作為下載資源的檔案名稱
--remote-name
)
curl "${curl_opts[@]}" https://nginx.org/download/nginx-1.25.3.tar.gz.asc
目前 GNU/Linux 作業系統主流的 PGP 操作軟體為 GNU Privacy Guard(GnuPG),以 root 身份執行下列命令以進行安裝:
apt install gnupg
接下來可以試著執行下列命令以進行 NGINX 軟體來源碼封存檔的資料完整性驗證:
gpg_opts=(
# 驗證經數位簽名之文件的資料完整性
--verify nginx-1.25.3.tar.gz.asc nginx-1.25.3.tar.gz
)
gpg "${gpg_opts[@]}"
如果命令輸出下列錯誤訊息代表您的 GnuPG 鑰匙圈尚未匯入簽發這個數位簽名的人的公鑰:
$ gpg --verify nginx-1.25.3.tar.gz.asc nginx-1.25.3.tar.gz
gpg: directory '/root/.gnupg' created
gpg: keybox '/root/.gnupg/pubring.kbx' created
gpg: Signature made Tue Oct 24 15:42:51 2023 UTC
gpg: using RSA key 13C82A63B603576156E30A4EA0EA981B66B0D967
gpg: issuer "k.pavlov@f5.com"
gpg: Can't check signature: No public key
您可以透過「nginx pgp verify」搜尋關鍵字可以在 NGINX 開源版官方網站找到應該是 k.pavlov@f5.com 對應的 Konstantin Pavlov 這個專案維護者的 PGP 公鑰:
執行下列命令下載 Konstantin Pavlov 專案維護者的 PGP 公鑰:
curl_opts=(
# 跟隨 301/302 轉址(如果有)
--location
# 使用下載網址 URL 末端片段作為下載資源的檔案名稱
--remote-name
)
curl "${curl_opts[@]}" https://nginx.org/keys/thresh.key
然後執行下列命令將 Konstantin Pavlov 專案維護者的 PGP 公鑰匯入自己的 PGP 鑰匙圈中:
gpg --import thresh.key
命令執行順利的話應該可以看到下列輸出:
$ gpg --import thresh.key
gpg: key A0EA981B66B0D967: 10 signatures not checked due to missing keys
gpg: /root/.gnupg/trustdb.gpg: trustdb created
gpg: key A0EA981B66B0D967: public key "Konstantin Pavlov <thresh@nginx.com>" imported
gpg: Total number processed: 1
gpg: imported: 1
gpg: no ultimately trusted keys found
附註: 命令輸出中的下列警告訊息為正常現象(PGP 信任網未建立):
附註: 您另可透過第三方 PGP 金鑰服務器來獲取該簽發者的簽名公鑰:
$ gpg --keyserver keyserver.ubuntu.com --recv-keys 13C82A63B603576156E30A4EA0EA981B66B0D967
gpg: /root/.gnupg/trustdb.gpg: trustdb created
gpg: key A0EA981B66B0D967: public key "Konstantin Pavlov <thresh@nginx.com>" imported
gpg: Total number processed: 1
gpg: imported: 1
如果公鑰匯入順利且 NGINX 軟體來源碼封存檔未被竄改的話再次驗證應該可以順利驗證成功(下列輸出第五行的「Good signature」關鍵字):
$ gpg --verify nginx-1.25.3.tar.gz.asc nginx-1.25.3.tar.gz
gpg: Signature made Tue Oct 24 15:42:51 2023 UTC
gpg: using RSA key 13C82A63B603576156E30A4EA0EA981B66B0D967
gpg: issuer "k.pavlov@f5.com"
gpg: Good signature from "Konstantin Pavlov <thresh@nginx.com>" [unknown]
gpg: aka "Konstantin Pavlov <k.pavlov@f5.com>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 13C8 2A63 B603 5761 56E3 0A4E A0EA 981B 66B0 D967
警告: 使用此方式驗證 NGINX 來源程式碼封存檔的資料完整性有下列問題:
故於資安實務上這種作法我們並無法 100% 確認這個檔案一定是沒問題的,如何有效避免這些問題非本文章的說明範疇,請搜尋 PGP web of trust 關鍵字以自行了解相關資訊
執行下列命令以解開 NGINX 的軟體來源碼封存檔:
tar_opts=(
# 選擇解開封存檔 tar 操作模式
--extract
# 指定要解開的封存檔路徑
--file nginx-1.25.3.tar.gz
)
tar "${tar_opts[@]}"
您應可於當前作業目錄找到解開的 nginx-1.25.3 軟體來源碼目錄:
$ ls nginx-*
nginx-1.25.3.tar.gz nginx-1.25.3.tar.gz.asc
nginx-1.25.3:
CHANGES LICENSE auto configure html src
CHANGES.ru README conf contrib man
據前面一開始提到的 Building nginx from Sources 官網說明文件所稱,NGINX(如同大多數開放來源碼軟體)使用一個叫做 configure
的軟體建構配置程式進行軟體的軟體建構細節配置,一般來說會進行下列功能:
最後軟體建構配置程式會生成用於實際進行軟體建構的程序供我們於後續步驟使用
附註: 觀察 NGINX 來源碼目錄中的內容您或許會注意到 NGINX 並不是使用常規的軟體建構系統(GNU Autotools/CMake/Maven)而是使用專案專有之模組化的 Shell 腳本來進行軟體建構的配置與建構程式生成
我們先執行下列命令切換作業目錄到 NGINX 1.25.3 軟體來源碼的目錄中,方便後續要進行的操作:
cd nginx-1.25.3
我們可以於前述之上游專案的說明文件或是 configure
程式印出的的幫助訊息查看可以 NGINX 軟體建構配置的項目,因為上游專案的文件不一定跟我們使用的版本一致的關係所以這邊我們以 configure 程式的輸出作為主要的建構參考:
nginx-1.25.3 $ ./configure --help
--help print this message
--prefix=PATH set installation prefix
--sbin-path=PATH set nginx binary pathname
...stripped...
……NGINX 的軟體建構配置程式的輸出實在是太長了,因為 Ubuntu 22.04 Docker 容器映像中預設提供的 more
分頁器 沒辦法自由查看上一頁的內容的關係,我們可以 root 身份執行下列命令改裝功能比較強大的 less
分頁器:
apt install less
然後就可以執行下列命令,用 Bash 殼層操作界面 的輸入/輸出資料流重導向功能來一頁一頁瀏覽 ./configure --help
命令的輸出:
./configure --help | less
您可以於 less
分頁器中使用 /
按鍵搜尋特定的關鍵字快速查找要啟用的功能,自 ./configure --help
命令的輸出我們可以知道要啟用 HTTPS(SSL/TLS) 支援需要於 configure
程式的執行命令中新增 --with-http_ssl_module
命令選項:
接下來我們就可以迭代地執行下列命令來配置 NGINX 的軟體建構,並根據其錯誤或警告訊息來準備建構所需要的環境:
./configure --with-http_ssl_module
首先 NGINX 軟體建構配置程式抱怨找不到 C 語言程式編譯器:
nginx-1.25.3 $ ./configure --with-http_ssl_module
checking for OS
+ Linux 6.1.0-1026-oem x86_64
checking for C compiler ... not found
./configure: error: C compiler cc is not found
於 GNU/Linux 系統中常見的提供 C 語言程式編譯器的軟體為 GNU Compiler Collection(GCC) 與 Clang,我們以 root 身份安裝 GCC:
apt install gcc
附註: 您也可以執行下列命令搜尋 Ubuntu 軟體庫提供之,可能提供 C 語言程式編譯器的軟體包:
apt search 'C compiler' | less
注意不是每個 C 語言程式編譯器(的各個版本)都支持 NGINX 軟體軟體建構所需要的編譯器特性
然後再一次執行 NGINX 的軟體建構配置程式,這一次它抱怨找不到 PCRE 程式庫:
$ ./configure --with-http_ssl_module
...stripped...
checking for PCRE2 library ... not found
checking for PCRE library ... not found
checking for PCRE library in /usr/local/ ... not found
checking for PCRE library in /usr/include/pcre/ ... not found
checking for PCRE library in /usr/pkg/ ... not found
checking for PCRE library in /opt/local/ ... not found
./configure: error: the HTTP rewrite module requires the PCRE library.
You can either disable the module by using --without-http_rewrite_module
option, or install the PCRE library into the system, or build the PCRE library
statically from the source with nginx by using --with-pcre=<path> option.
Debian 系 GNU/Linux 作業系統的 APT 軟體包管理器會將軟體的不同組成元件 分開打包成獨立的軟體包 以節省系統的儲存空間使用,於源碼建構時軟體的依賴組件我們主要會安裝它的「軟體開發用檔案(包含但不限於 C/C++ 語言程式的標頭檔案、用於靜態連結的靜態程式庫檔案與軟體開發者用的參考文件等等)」的軟體包,此類軟體包通常會使用 -dev
的軟體包名後綴(如果是程式庫則通常會有 lib
的軟體包名前綴),我們可以執行下列命令將吻合此名稱式樣的軟體包通通找出來:
apt_search_opts=(
# 只比對軟體包名稱而非軟體描述文字
--names-only
)
apt search "${apt_search_opts[@]}" '^libpcre.*-dev$'
附註:
^libpcre.*-dev$
為 POSIX 風格的正規表達式語法(參閱 apt-cache(8) 與 regex(7) 的 manpage 使用手冊頁面)-devel
後綴於 apt search
命令的輸出我們可以發現 Ubuntu 軟體庫有同時提供第一版與第二版的 PCRE 程式庫:
$ apt search --names-only '^libpcre.*-dev'
Sorting... Done
Full Text Search... Done
...stripped...
libpcre2-dev/jammy-updates,jammy-security 10.39-3ubuntu0.1 amd64
New Perl Compatible Regular Expression Library - development files
libpcre3-dev/jammy-updates,jammy-security 2:8.39-13ubuntu0.22.04.1 amd64
Old Perl 5 Compatible Regular Expression Library - development files
附註: 對您沒看錯,同 libpcre3-dev 軟體包的文字描述所稱,實際上該軟體包為第一版而非第三版的 PCRE 程式庫(翻桌)
因自 NGINX 軟體建構配置程序的輸出可以判斷 NGINX 有相容第二版的 PCRE 程式庫,這邊選擇以 root 身份執行下列命令安裝第二版 PCRE 程式庫的軟體開發用文件:
apt install libpcre2-dev
附註: NGINX 於 1.21.5 版(2021/12/28)起才新增了第二版 PCRE 程式庫的支援,如建構舊版 NGINX 則須安裝第一版 PCRE 程式庫的軟體開發用檔案
然後再一次執行 NGINX 的軟體建構配置程式,這一次它抱怨找不到 OpenSSL 程式庫的安裝:
nginx-1.25.3 $ ./configure --with-http_ssl_module
...stripped...
checking for PCRE2 library ... found
checking for OpenSSL library ... not found
checking for OpenSSL library in /usr/local/ ... not found
checking for OpenSSL library in /usr/pkg/ ... not found
checking for OpenSSL library in /opt/local/ ... not found
./configure: error: SSL modules require the OpenSSL library.
You can either do not enable the modules, or install the OpenSSL library
into the system, or build the OpenSSL library statically from the source
with nginx by using --with-openssl=<path> option.
附註: 因為 NGINX 需要的是 OpenSSL 軟體建構時期用的檔案而非執行時期用的檔案,所以就算您的系統明顯有安裝基於 OpenSSL 程式庫的軟體 NGINX 的軟體安裝配置程序仍然會提示未安裝 OpenSSL 程式庫。具體會使用的檔案可以於軟體來源碼目錄中執行下列命令確認:
grep_opts=(
# 不區分大小寫
--ignore-case
# 遞迴地搜尋指定目錄下的所有檔案
--recursive
)
grep "${grep_opts[@]}" openssl auto/
使用先前 PCRE 程式庫的經驗,我們可以執行下列命令搜尋提供 OpenSSL 程式庫的軟體包:
apt_search_opts=(
# 只比對軟體包名稱而非軟體描述文字
--names-only
)
apt search "${apt_search_opts[@]}" 'libopenssl.*-dev'
……但是什麼都找不到呢:
$ apt search "${apt_search_opts[@]}" 'libopenssl.*-dev'
Sorting... Done
Full Text Search... Done
我們把搜尋條件再調鬆一點,現在終於有結果了但是好像都不是我們要的:
$ apt search "${apt_search_opts[@]}" 'openssl.*-dev'
Sorting... Done
Full Text Search... Done
golang-github-mendersoftware-openssl-dev/jammy 1.1.0-2ubuntu2 all
OpenSSL bindings for Go.
libcurl4-openssl-dev/jammy-updates,jammy-security 7.81.0-1ubuntu1.15 amd64
development files and documentation for libcurl (OpenSSL flavour)
libghc-hsopenssl-dev/jammy 0.11.4.18-1ubuntu1 amd64
partial OpenSSL binding for Haskell
libghc-hsopenssl-x509-system-dev/jammy 0.1.0.3-5build4 amd64
use system's native CA certificate store with HsOpenSSL
...stripped...
……我們把搜尋條件再調鬆一點,這次我們找到了跟 OpenSSL 同名的 openssl
軟體包,但是還是沒有找到看起來有提供 OpenSSL 軟體開發用檔案的軟體包:
$ apt search "${apt_search_opts[@]}" openssl
Sorting... Done
Full Text Search... Done
...stripped...
openssl/jammy-updates,jammy-security,now 3.0.2-0ubuntu1.12 amd64 [installed,automatic]
Secure Sockets Layer toolkit - cryptographic utility
...stripped...
看來我們的這個作法已經走到了一個死胡同了,我們試試看能不能從其他地方找到有價值的線索。如果您有使用前面附註提到之 grep
命令查詢跟 OpenSSL 程式庫有關的軟體建構配置邏輯的話您應該會注意到在 auto/lib/openssl/conf shell 小腳本中的依賴軟體檢查邏輯有嘗試載入一個叫做 openssl/ssl.h
的 C 程式語言標頭檔案:
我們可以使用 apt-file
工具來查詢所有有提供這個檔案路徑式樣的軟體包,首先先以 root 身份執行下列命令安裝軟體:
apt install apt-file
然後執行下列命令建立 apt-file 軟體的本地快取資料
apt-file update
最後再執行下列命令搜尋所有提供 openssl/ssl.h
路徑結尾之檔案的軟體包:
apt_file_search_opts=(
# 使用 Perl 正規表達式(而非純文字)搜尋
--regexp
)
apt-file search "${apt_file_search_opts[@]}" 'openssl/ssl\.h$'
$ apt-file search "${apt_file_search_opts[@]}" 'openssl/ssl\.h$'
android-libboringssl-dev: /usr/include/android/openssl/ssl.h
libnode-dev: /usr/include/node/openssl/ssl.h
libssl-dev: /usr/include/openssl/ssl.h
libwolfssl-dev: /usr/include/cyassl/openssl/ssl.h
libwolfssl-dev: /usr/include/wolfssl/openssl/ssl.h
python3-pycparser: /usr/share/python3-pycparser/fake_libc_include/openssl/ssl.h
從查詢結果中的軟體包名稱跟提供的檔案路徑複雜度可推測應該是 libssl-dev 這個軟體包提供我們需要的 OpenSSL 開發用檔案
附註: 以下提供幾種同樣可以找到正確的提供 OpenSSL 軟體開發用檔案軟體包(libssl-dev)的可能作法:
編輯 APT 軟體包管理器的軟體來源清單啟用來源碼軟體包的軟體來源(deb-src),然後使用 apt showsrc
命令查詢 openssl
這個來源碼軟體包到底會建構出哪些看起來是提供軟體開發用檔案的二進位軟體包
# sed \
--in-place \
--expression='s/^# deb-src/deb-src/' \
/etc/apt/sources.list
# apt update
...stripped...
$ apt showsrc openssl
Package: openssl
Format: 3.0 (quilt)
Binary: openssl, libssl3, libcrypto3-udeb, libssl3-udeb, libssl-dev, libssl-doc
...stripped...
前面有提過「APT 軟體包管理器會將軟體的不同組成元件 分開打包成獨立的軟體包」這點,觀察 openssl 軟體包的文字描述可以得知此軟體包提供「Secure Sockets Layer(SSL) 工具組」的「加密學相關的 工具程式」,那有沒有軟體包有提供「Secure Sockets Layer(SSL) 工具組」的「軟體開發用檔案」呢?直接用 Secure Sockets Layer toolkit
當作搜尋關鍵字找找看就可以發現 libssl-dev 應該是我們需要的軟體包:
$ apt search 'Secure Sockets Layer toolkit'
Sorting... Done
Full Text Search... Done
libssl-dev/jammy-updates,jammy-security 3.0.2-0ubuntu1.12 amd64
Secure Sockets Layer toolkit - development files(軟體開發用檔案)
libssl-doc/jammy-updates,jammy-security 3.0.2-0ubuntu1.12 all
Secure Sockets Layer toolkit - development documentation(軟體開發用說明文件)
libssl3/jammy-updates,jammy-security,now 3.0.2-0ubuntu1.12 amd64 [installed]
Secure Sockets Layer toolkit - shared libraries(共享程式庫)
openssl/jammy-updates,jammy-security,now 3.0.2-0ubuntu1.12 amd64 [installed,automatic]
Secure Sockets Layer toolkit - cryptographic utility(加密學相關的工具程式)
使用 ldd "$(which openssl)"
命令查看 openssl
程式的會於執行時期載入的可能跟 OpenSSL 有關的共享程式庫名稱(libssl)(版本號去掉),然後用 apt search --names-only libssl
命令尋找該程式庫名稱開頭 -dev
結尾的提供軟體開發用檔案的軟體包:
$ ldd "$(which openssl)"
linux-vdso.so.1 (0x00007ffcade07000)
libssl.so.3 => /lib/x86_64-linux-gnu/libssl.so.3 (0x00007f02c13cd000)
libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f02c0e00000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f02c0a00000)
/lib64/ld-linux-x86-64.so.2 (0x00007f02c156d000)
$ apt search --names-only '^libssl.*-dev$'
Sorting... Done
Full Text Search... Done
libssl-dev/jammy-updates,jammy-security 3.0.2-0ubuntu1.12 amd64
Secure Sockets Layer toolkit - development files
...stripped...
編輯 APT 軟體包管理器的軟體來源清單啟用來源碼軟體包的軟體來源(deb-src),然後使用 apt showsrc
命令查詢 NGINX 等確定有使用 OpenSSL 程式庫的軟體的建構依賴軟體的軟體包有哪個看起來像是跟 OpenSSL 程式庫比較有關的,提供軟體開發用檔案的軟體包
# sed \
--in-place \
--expression='s/^# deb-src/deb-src/' \
/etc/apt/sources.list
# apt update
...stripped...
$ apt showsrc nginx
Package: nginx
...stripped...
Build-Depends: debhelper-compat (= 13), dpkg-dev (>= 1.15.5), libexpat-dev, libgd-dev, libgeoip-dev, libhiredis-dev, libmaxminddb-dev, libmhash-dev, libpam0g-dev, libpcre3-dev, libperl-dev, libssl-dev, libxslt1-dev, po-debconf, quilt, zlib1g-dev
...stripped...
當然傳統的直接找 Google/StackOverflow/AskUbuntu/ChatGPT/Google Gemini 問也可能可以找到解答
以 root 身份 執行下列命令補上依賴軟體的安裝:
apt install libssl-dev
然後再一次執行 NGINX 的軟體建構配置程式,現在它抱怨缺少 zlib 程式庫:
nginx-1.25.3 $ ./configure --with-http_ssl_module
...stripped...
checking for OpenSSL library ... found
checking for zlib library ... not found
./configure: error: the HTTP gzip module requires the zlib library.
You can either disable the module by using --without-http_gzip_module
option, or install the zlib library into the system, or build the zlib library
statically from the source with nginx by using --with-zlib=<path> option.
用同樣方式去尋找提供 zlib 程式庫的軟體開發用檔案軟體包,推定應該是 zlib1g-dev 這個軟體包沒錯:
$ apt search --names-only '^libzlib.*-dev$'
Sorting... Done
Full Text Search... Done
$ apt search --names-only 'zlib.*-dev$'
Sorting... Done
Full Text Search... Done
...stripped...
zlib1g-dev/jammy-updates,jammy-security 1:1.2.11.dfsg-2ubuntu9.2 amd64
compression library - development
附註: 跟 OpenSSL 程式庫一樣,提供 zlib 程式庫軟體開發用檔案的軟體包一樣不太遵守 Debian 系 GNU/Linux 作業系統的軟體開發用檔案軟體包的命名慣例,這些軟體就只能多花點時間確認了(攤手)
以 root 身份 執行下列命令補上依賴軟體的安裝:
apt install zlib1g-dev
然後再一次執行 NGINX 的軟體建構配置程式,出現下列輸出代表 NGINX 的軟體建構已經順利完成配置了:
$ ./configure --with-http_ssl_module
...stripped...
creating objs/Makefile
Configuration summary
+ using system PCRE2 library
+ using system OpenSSL library
+ using system zlib library
nginx path prefix: "/usr/local/nginx"
nginx binary file: "/usr/local/nginx/sbin/nginx"
nginx modules path: "/usr/local/nginx/modules"
nginx configuration prefix: "/usr/local/nginx/conf"
nginx configuration file: "/usr/local/nginx/conf/nginx.conf"
nginx pid file: "/usr/local/nginx/logs/nginx.pid"
nginx error log file: "/usr/local/nginx/logs/error.log"
nginx http access log file: "/usr/local/nginx/logs/access.log"
...stripped...
留意 NGINX 安裝的:
且 NGINX 軟體來源碼目錄中出現可用於建構 NGINX 軟體的 Makefile 檔案:
nginx-1.25.3 $ ls
CHANGES LICENSE README conf contrib man src
CHANGES.ru Makefile auto configure html objs
因為 NGINX 軟體建構配置程式產生了 Makefile 文件,我們可以推定該軟體使用的軟體建構自動化工具為 GNU Make。以 root 身份 執行下列命令進行安裝:
apt install make
然後我們可以執行下列命令進行 NGINX 軟體的軟體建構:
make_opts=(
# 開啟平行建構(parallel build)功能,將將平行運行的 sub-make 工作數量限制在
# 主機的中央處理器總處理器執行緒數以減少軟體建構所需時間
--jobs="$(nproc)"
)
make "${make_opts[@]}"
如果看到類似這樣的輸出且命令的結束狀態代碼為零則代表軟體建構順利結束:
nginx-1.25.3 $ make "${make_opts[@]}"
make -f objs/Makefile
make[1]: Entering directory '/nginx-1.25.3'
cc -c -pipe -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g -I src/core -I src/event -I src/event/modules -I src/event/quic -I src/os/unix -I objs \
-o objs/src/core/nginx.o \
src/core/nginx.c
...stripped...
objs/src/http/modules/ngx_http_upstream_zone_module.o \
objs/ngx_modules.o \
-lcrypt -lpcre2-8 -lssl -lcrypto -lz \
-Wl,-E
make[1]: Leaving directory '/nginx-1.25.3'
nginx-1.25.3 $ echo $?
0
附註:
您可以在 make 命令前加上 time 命令以測量軟體建構的所需時間:
$ time make "${make_opts[@]}"
...stripped...
make[1]: Leaving directory '/nginx-1.25.3'
real 0m2.841s
user 0m31.412s
sys 0m6.163s
這邊的軟體建構所需參考時間為 2.841 秒,沒有開啟平行建構的所需時間則為 15.772 秒(Framework 13 AMD 7040 系列(Ryzen 7 7840U)(總執行緒數:16)、使用 performance 性能層級),隨著要建構的軟體的大小規模增長軟體建構的所需時間也會隨之增加,超過半小時甚至是長達一整天或更久也是有可能的所以能開啟平行建構的話建議開啟
有些情況下會發生平行建構報錯但是單執行緒建構可以正常完成的狀況,這通常是軟體的軟體建構自動化實現有缺陷所造成
GNU Make 預設會跳過已經完成且源碼檔案未異動的軟體建構工作項目,如果要進行重新建構您需要執行 clean
這個偽 make 目標來清理所有先前 Make 產生的的建構(中間)產物:
make clean
注意此操作也會將 Makefile 文件移除,需要重新執行軟體建構配置程序來重新生成那個檔案
以 root 身份執行 Makefile 提供的 install
偽 make 目標即可將建構好的 NGINX 軟體安裝到系統中:
make install
附註: 因為一般使用者沒有權限在 /usr/local 系統目錄寫入檔案,所以命令才需要以 root 身份執行,如果於 NGINX 軟體建構配置時期有指定將軟體安裝到一般使用者可寫入的目錄中是不需要這樣做的
使用 less 純文字資料分頁器審閱 服務配置前綴目錄 中的 nginx.conf 服務主配置檔可以發現 NGINX 服務預設會在 80 通訊埠號提供一個服務 軟體安裝前綴目錄/html 目錄下網頁的靜態 HTTP 服務:
...stripped...
http {
...stripped...
server {
listen 80;
server_name localhost;
...stripped...
location / {
root html;
index index.html index.htm;
}
}
}
接下來您可以以 root 身份執行下列命令將 NGINX 服務啟動並作為幕後服務運行:
/usr/local/nginx/sbin/nginx
$ pgrep --list-full nginx
26776 nginx: master process /usr/local/nginx/sbin/nginx
26777 nginx: worker process
附註:
CAP_NET_BIND_SERVICE
Linux 能力)使用 curl HTTP 客戶端軟體訪問本地主機(於此情境指 Docker 容器自己)的 80 HTTP 服務常規通訊埠應該可以獲得類似下面的 NGINX 範例站台網頁內容:
$ curl http://localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
於宿主機使用 Web 瀏覽器訪問本地主機的的 80 HTTP 服務常規通訊埠應該也可以看到相同頁面:
接下來我們來測試看看我們安裝的 NGINX 是否支援 HTTPS 服務,查看 NGINX 服務的主配置檔可以看到 NGINX 有一個預設註解掉的 HTTPS 站台範例配置:
...stripped...
http {
...stripped...
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
由於 Ubuntu 22.04 的 Docker 容器映像並無提供任何的純文字文件編輯器,我們以 root 身份執行下列命令安裝使用上最簡單的 GNU nano 編輯器軟體:
apt install nano
然後我們可以以 root 身份執行下列命令編輯 NGINX 的服務主配置檔將該 HTTPS 服務配置取消註解:
nano /usr/local/nginx/conf/nginx.conf
附註: GNU nano 編輯器會在操作界面的下側列舉可使用的常見操作,各操作前面的 插入記號-字元 序列代表該操作需要以 Ctrl+字元 的組合鍵觸發:
例如內容編輯完成後可以按下 Ctrl+X 組合鍵存檔結束編輯
留意該站台配置會參照一個叫做 cert.pem 的 X.509 證書文件以及一個叫做 cert.key 的 RSA 私鑰文件:
...stripped...
http {
...stripped...
# HTTPS server
#
server {
listen 443 ssl;
server_name localhost;
ssl_certificate cert.pem;
ssl_certificate_key cert.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
root html;
index index.html index.htm;
}
}
}
因為我們修改了配置,我們以 root 身份執行下列命令檢查 NGINX 配置的正確性:
/usr/local/nginx/sbin/nginx -t
附註: 需要以 root 身份執行的原因是因為 NGINX 安裝到系統目錄中以一般使用者身份執行會有部份資源存取不了的問題,如果 NGINX 是安裝到使用者自己的目錄下就沒這個問題
會發現 NGINX 抱怨無法存取到 cert.pem 這個 HTTPS 服務會使用到的 X.509 證書文件:
# /usr/local/nginx/sbin/nginx -t
nginx: [emerg] cannot load certificate "/usr/local/nginx/conf/cert.pem": BIO_new_file() failed (SSL: error:80000002:system library::No such file or directory:calling fopen(/usr/local/nginx/conf/cert.pem, r) error:10000080:BIO routines::no such file)
使用 ls
命令查看會發現 NGINX 配置目錄下並沒有該 SSL/TLS 證書文件(cert.pem)跟私鑰文件(cert.key),我們可以執行下列命令自行簽發一個給 localhost 站台使用的 X.509 證書來用:
# 因為私鑰洩漏有資訊安全疑慮所以將預設的群組(group)跟其他人(other)
# 的 Unix 檔案訪問權限設定遮罩掉
umask 077
# 產生一個 2048 位元大小的 RSA 私鑰
openssl genrsa 2048 > cert.key
# 使用前面產生的 RSA 私鑰產生一個給「localhost」這個 Common Name 實體
# 使用的 PKCS#10 憑證簽發請求(CSR)
openssl_req_opts=(
# 產生新的憑證簽發請求
-new
# 指定要使用的私鑰文件
-key cert.key
# 設定新的憑證簽發請求的主體名稱(subject name)
-subj '/CN=localhost'
)
openssl req "${openssl_req_opts[@]}" > cert.csr
# 使用前面產生的 PKCS#10 憑證簽發請求自行簽發一個新 X.509 憑證
openssl_x509_opts=(
# 指定輸入文件為一憑證簽發請求
-req
# 指定輸入的憑證簽發請求文件
-in cert.csr
# 指定要簽發的 X.509 憑證的效期為一個月(30 天)
-days 30
# 指定用於簽發 X.509 憑證使用的憑證簽發者(certificate issuer)私鑰
# 因為是自簽憑證我們使用同一個私鑰文件即可
-signkey cert.key
# 指定要簽發的 X.509 憑證文件路徑
-out cert.pem
)
openssl x509 "${openssl_x509_opts[@]}"
然後以 root 身份執行下列命令將產生出來的證書跟私鑰文件安裝到 NGINX 服務配置目錄:
# 通用的 install 命令選項
install_opts_common=(
# 設定擁有者為 root
--owner root
# 設定擁有群組為 root
--group root
# 顯示冗餘輸出
--verbose
)
# 安裝 X.509 證書專用的 install 命令選項
install_opts_cert=(
"${install_opts_common[@]}"
# 設定 Unix 檔案訪問權限為 rw-r--r--
--mode 644
)
# 安裝 RSA 私鑰文件專用的 install 命令選項
install_opts_key=(
"${install_opts_common[@]}"
# 設定 Unix 檔案訪問權限為 rw-------
--mode 600
)
install "${install_opts_cert[@]}" cert.pem /usr/local/nginx/conf/cert.pem
install "${install_opts_key[@]}" cert.key /usr/local/nginx/conf/cert.key
再次檢查 NGINX 配置的正確性應該就能過了:
# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
然後以 root 身份執行下列命令重新載入 NGINX 的服務配置:
/usr/local/nginx/sbin/nginx -s reload
附註: 此命令實際的作用為自先前提到的 NGINX 服務主進程進程識別編號檔讀取 NGINX 服務主進程的進程識別編號,然後對該進程發送 SIGHUP 訊號,更多資訊可以參閱 Controlling nginx 這個官方文件
順利的話您應可以執行下列命令訪問到 NGINX 的 HTTPS 站台:
curl_opts=(
# 略過 SSL/TLS 憑證驗證,非由公認信任之憑證簽發機構(certificate authority(CA))
# 簽發的憑證是沒辦法過驗證的
--insecure
)
curl "${curl_opts[@]}" https://localhost
於宿主機使用 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
命令的用途與相關命令選項