owned this note
owned this note
Published
Linked with GitHub
# 《NGINX 源碼建構》練習 參考解答
說明《NGINX 源碼建構》練習的最佳實務解法
<https://hackmd.io/@brlin/nginx-build-from-source-reference-answer>
## 內容大綱
[TOC]
## 智慧財產授權條款
除另外標註之內容外本作品的內容以[《Creative Commons 姓名標示-相同方式分享》授權條款第 4.0 國際版](https://creativecommons.org/licenses/by-sa/4.0/deed.zh_TW)或其任意更近期版本釋出供大眾*於授權範圍內*自由使用
請使用 `林博仁(Buo-ren, Lin) <https://brlin.me>` 作為滿足「姓名標示」授權要求的表彰內容
如有任何問題或其他要求請聯繫 <buo.ren.lin+legal@gmail.com>
## 目標
* 下載 NGINX 當前最新版本軟體的{來源程式碼|source code},將其建構並安裝到可以正常運作的狀態
* 必須支持下列功能:
+ HTTPS 服務支援(支援之 SSL/TLS 版本不限)
## <ruby>前備條件<rp>(</rp><rt>prerequisites</rt><rp>)</rp></ruby>
以下說明完成本練習所需要的前備條件:
* 您的練習環境電腦須有安裝下列軟體:
* Docker(桌面或命令列界面版本)
* 您須有基本的 Linux 命令列界面操作經驗
* 您須有基本的 Docker 容器操作經驗
## 重現環境
以下說明應該能重現本筆記之結果的環境細節:
### 作業系統
Ubuntu 23.10 AMD64(透過 Ubuntu 22.04 {容器|container})
### Docker
Docker 容器的執行環境:
```
$ docker --version
Docker version 24.0.5, build ced0996
```
### NGINX
1.25.3
## 操作流程
以下說明完成本練習的相關流程:
### 詳閱 NGINX 官方的源碼安裝說明文件
大部分{開放來源碼|open-source}軟體的專案官方的文件通常會有一些源碼建構軟體的重要資訊,所以事先詳閱是強烈建議的
於搜尋引擎查得並訪問 [nginx.org NGINX 開源版官網](https://nginx.org/)(非 [nginx.com 商業版官網](https://www.nginx.com/)),您可以於右側導覽欄找到官方說明文件的頁面(紅框處):
![NGINX 官方文件頁面在官網首頁導覽列的位置示意圖](https://hackmd.io/_uploads/rkgfn6cIKT.png "NGINX 官方文件頁面在官網首頁導覽列的位置(紅框處)")
就可以找到可能跟源碼建構相關的說明文件(紅框處):
![NGINX 官方文件頁面跟源碼建構可能有關的官方文件示意圖](https://hackmd.io/_uploads/HkCzfs8tp.png "NGINX 官方文件頁面跟源碼建構可能有關的官方文件(紅框處)")
主要有關的說明文件應該是這個:[Building nginx from Sources](https://nginx.org/en/docs/configure.html),之後遇到問題時可以回來確認是不是某個眉角沒有注意到
### 創建並啟動一 Ubuntu 22.04 Docker 容器作為練習環境
為確保重現環境的一致性且避免練習期間的操作影響到主端系統,我們建立一個 Ubuntu 22.04 的 Docker 容器並在其內進行後續的練習操作。*以 root 身份*執行下列命令自 ubuntu:22.04 {容器映像|container image}創建一個新的容器:
```bash
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
```
:::info
**附註:**
* `array=(...)` 為 Bash 的{索引式陣列|indexed array}語法,如上範例命令為例使用此語法可以以 Bash 註解的形式為命令參數新增說明文字
* `"${array[@]}"` 為 Bash 陣列的其中一種{參數展開|parameter expansion}語法,會將陣列中的元素以空白字元為分隔插入於展開處,您可以在展開命令前面加上 echo 命令(如 `echo "${array[@]}"`)以預覽展開結果
* 如您的使用者為 `docker` 使用者群組的成員則可以以一般使用者身份運行前述 `docker run` 命令(允許使用者直接訪問 Docker 服務[有潛在的安全風險](https://docs.docker.com/engine/security/#docker-daemon-attack-surface),應**僅開放給完全信任的使用者**使用)
* 如您位於僅能透過一 HTTP(S) 正向代理服務器才能連到網際網路的網路環境(如企業內部網路或是使用 [TetherFi](https://github.com/pyamsoft/tetherfi) 應用分享手機的行動網路給電腦),您可以在執行 `docker run` 命令前額外執行下列命令將本地的 HTTP(S) 正向代理服務設定(須已設定相關環境變數)繼承給 Docker 容器使用:
+ `docker_run_opts+=(--env http_proxy)`
+ `docker_run_opts+=(--env https_proxy)`
+ `docker_run_opts+=(--env no_proxy)`
:::
執行命令後您應可以取得容器內 Bash {殼層|shell}互動式操作環境的{命令提示文字|command prompt}:
```bash
$ sudo docker run "${docker_run_opts[@]}" ubuntu:22.04
root@nginx-source-build:/#
```
### 改用本地下載速度較快的 Ubuntu <ruby>軟體庫<rp>(</rp><rt>software repository</rt><rp>)</rp></ruby> <ruby>鏡像站<rp>(</rp><rt>mirror site</rt><rp>)</rp></ruby>
因為 Ubuntu 的 Docker 容器預設使用(應該是在英國跟美國的)archive.ubuntu.com 軟體庫服務於台灣本地下載軟體的速度較慢,我們可以改用本地的軟體庫鏡像服務來減少軟體下載所需時間
以 root 身份於文字終端執行下列命令已將軟體庫地址替換為台灣本地鏡像站的地址:
```bash
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 <ruby>軟體包索引<rp>(</rp><rt>package index</rt><rp>)</rp></ruby>資料
因為 APT 軟體來源的設定已經變更了,我們需要重新建立本地的 APT 軟體包索引資料以獲取當前軟體庫的軟體包清單與下載地址等相關資訊
*以 root 身份*於文字終端執行下列命令以建立/重建本地的 APT 軟體包索引資料:
```bash
apt update
```
如果設定軟體庫鏡像站的步驟順利完成的話,您應該可於命令輸出中找到自 tw.archive.ubuntu.com 伺服器下載資料的進度報告訊息:
```command
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 的軟體<ruby>來源程式碼<rp>(</rp><rt>source code</rt><rp>)</rp></ruby>
到 [NGINX 開源版官方網站的下載頁](https://nginx.org/en/download.html)可以找到當前最新版本(推定為指{開發主線|mainline}版)的 NGINX 來源程式碼{封存檔|archive}的下載地址:
![NGINX 軟體下載頁中的 1.25.3 當前開發主線版本的源碼包下載連結示意圖](https://hackmd.io/_uploads/Bk3kG3iF6.png "NGINX 軟體下載頁中的 1.25.3 當前開發主線(mainline)釋出版本的源碼包下載連結(紅框處)")
下載頁中紅框的 [nginx-1.25.3 超連結](https://nginx.org/download/nginx-1.25.3.tar.gz)為 NGINX 的 1.25.3 版開發主線版本的來源程式碼封存檔的下載連結
:::info
**附註:** NGINX 的{開發主線|mainline}版與{穩定|stable}版的差異:
* 相對於穩定版使用偶數的{次版本|minor version}版號,開發主線版使用奇數的次版本版號
* 每隔 4 到 6 週{開發主線分支|mainline branch}會釋出包含新功能與修正的新的開發主線版本
* 每年 NGINX 專案維護者會將當前的開發主線分支內容{叉分|fork}為新的{穩定版分支|stable branch}進行維護(前一個的穩定版分支則{不再被建議使用|deprecated}),該分支只會於需要時自開發主線分支{揀選|cherry-pick}/{向前移植|backport}重大問題的修正
詳細資訊參閱 [Introducing NGINX 1.10 and 1.11 - NGINX 文章的 Explaining NGINX’s Version Numbering 章節](https://www.nginx.com/blog/nginx-1-10-1-11-released/#Explaining-NGINX%E2%80%99s-Version-Numbering)
:::
由於 GNU+Linux 作業系統可以使用 [curl](https://curl.se/) 等 Web 客戶端軟體直接於文字終端下載 NGINX 的軟體來源碼包,因 Ubuntu 22.04 官方容器映像預設不提供 curl 軟體的原因我們需要*以 root 身份*執行下列命令手動進行安裝:
```bash
apt install curl
```
然後執行下列命令下載 1.25.3 版的 NGINX 的來源程式碼封存檔:
```bash
curl_opts=(
# 跟隨 301/302 轉址(如果有)
--location
# 使用下載網址 URL 末端片段作為下載資源的檔案名稱
--remote-name
)
curl "${curl_opts[@]}" https://nginx.org/download/nginx-1.25.3.tar.gz
```
### 驗證 NGINX 軟體來源碼封存檔的<ruby>資料完整性<rp>(</rp><rt>data integrity</rt><rp>)</rp></ruby>
為避免 NGINX 開源版官方網站的 NGINX 軟體來源碼封存檔遭到惡意竄改造成安裝 NGINX 的主機資料被竊取或被當作 DDoS 攻擊的肉雞所以必須要驗證其資料完整性,首先需要確認該軟體有提供哪種驗證資料完整性的方案
查看 [NGINX 開源版官方網站下載頁](https://nginx.org/en/download.html)可以發現 [nginx-1.25.3 超連結](https://nginx.org/download/nginx-1.25.3.tar.gz) 右邊的 [pgp 超連結](https://nginx.org/download/nginx-1.25.3.tar.gz.asc)(紅框處)為透過 [Pretty Good Privacy(PGP)](https://hackmd.io/JXK5LS2uTAu4BDZvlJ2EkQ) 驗證 NGINX 1.25.3 軟體來源碼封存檔資料完整性的{數位簽名檔案|signature file}:
![NGINX 軟體下載頁中的 1.25.3 當前開發主線(mainline)釋出版本的源碼包的 PGP 數位簽名文件下載連結示意圖](https://hackmd.io/_uploads/SJgZ4nita.png "NGINX 軟體下載頁中的 1.25.3 當前開發主線(mainline)釋出版本的源碼包的 PGP 數位簽名文件下載連結(紅框處)")
執行下列命令下載 1.25.3 版 NGINX 來源程式碼封存檔驗證資料完整性用的 PGP 數位簽名檔:
```bash
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)](https://gnupg.org/),*以 root 身份*執行下列命令以進行安裝:
```bash
apt install gnupg
```
接下來可以試著執行下列命令以進行 NGINX 軟體來源碼封存檔的資料完整性驗證:
```bash
gpg_opts=(
# 驗證經數位簽名之文件的資料完整性
--verify nginx-1.25.3.tar.gz.asc nginx-1.25.3.tar.gz
)
gpg "${gpg_opts[@]}"
```
如果命令輸出下列錯誤訊息代表您的 GnuPG 鑰匙圈尚未匯入{簽發|issue}這個數位簽名的人的公鑰:
```plaintext
$ 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」搜尋關鍵字](https://duckduckgo.com/?q=nginx+pgp+verify&t=newext&atb=v399-1&ia=web)可以在 [NGINX 開源版官方網站](https://nginx.org/en/pgp_keys.html)找到[應該是 k.pavlov@f5.com 對應的 Konstantin Pavlov 這個專案維護者的 PGP 公鑰](https://nginx.org/keys/thresh.key):
![NGINX 開源版官方網站 PGP public keys 網頁中的 Konstantin Pavlov 專案維護者的 PGP 公鑰下載連結示意圖](https://hackmd.io/_uploads/SJtQM6itT.png "NGINX 開源版官方網站 PGP public keys 網頁中的 Konstantin Pavlov 專案維護者的 PGP 公鑰下載連結(紅框處)")
執行下列命令下載 [Konstantin Pavlov 專案維護者的 PGP 公鑰](https://nginx.org/keys/thresh.key):
```bash
curl_opts=(
# 跟隨 301/302 轉址(如果有)
--location
# 使用下載網址 URL 末端片段作為下載資源的檔案名稱
--remote-name
)
curl "${curl_opts[@]}" https://nginx.org/keys/thresh.key
```
然後執行下列命令將 Konstantin Pavlov 專案維護者的 PGP 公鑰匯入自己的 PGP 鑰匙圈中:
```bash
gpg --import thresh.key
```
命令執行順利的話應該可以看到下列輸出:
```plaintext
$ 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
```
:::info
**附註:** 命令輸出中的下列警告訊息為正常現象(PGP 信任網未建立):
* 10 signatures not checked due to missing keys
* no ultimately trusted keys found
:::
:::info
**附註:** 您另可透過第三方 PGP {金鑰服務器|keyserver}來獲取該簽發者的簽名公鑰:
```plaintext
$ 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
```
:::warning
**警告:** 使用此方式驗證 NGINX 來源程式碼封存檔的資料完整性有下列問題:
* 因為 [1.25.3 版 NGINX 軟體的來源碼封存檔](https://nginx.org/download/nginx-1.25.3.tar.gz)、[其數位簽名文件](https://nginx.org/download/nginx-1.25.3.tar.gz.asc)以及 [Konstantin Pavlov 專案維護人員的 PGP 公鑰](https://nginx.org/keys/thresh.key)疑似是託管在同一個 Web 伺服器上,有可能**同時被攻擊者竄改**以致資料完整性檢查無法檢查出問題
* 如同前命令輸出的警告訊息所稱,我們無法確認網站上的 [Konstantin Pavlov 專案維護人員的 PGP 公鑰](https://nginx.org/keys/thresh.key)*一定*是由 Konstantin Pavlov 這個人所發布的
故於資安實務上這種作法我們並無法 100% 確認這個檔案一定是沒問題的,如何有效避免這些問題非本文章的說明範疇,請搜尋 PGP web of trust 關鍵字以自行了解相關資訊
:::
### 解開 NGINX 的軟體來源碼封存檔
執行下列命令以解開 NGINX 的軟體來源碼封存檔:
```bash
tar_opts=(
# 選擇解開封存檔 tar 操作模式
--extract
# 指定要解開的封存檔路徑
--file nginx-1.25.3.tar.gz
)
tar "${tar_opts[@]}"
```
您應可於{當前作業目錄|current working directory}找到解開的 nginx-1.25.3 軟體來源碼目錄:
```plaintext
$ 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
```
### 配置 NGINX 軟體的軟體建構細節
據前面一開始提到的 [Building nginx from Sources](https://nginx.org/en/docs/configure.html) 官網說明文件所稱,NGINX(如同大多數開放來源碼軟體)使用一個叫做 `configure` 的軟體建構配置程式進行軟體的軟體建構細節配置,*一般來說*會進行下列功能:
* 偵測不同作業系統與處理器平台的特性(如 32/64 位元、是否支持特定指令集與應用程式界面(API)等)
* 設定(*通常*是於{建構時期|build-time}於二進位程式中寫死的){軟體安裝前綴路徑|installation prefix}與各種資源(如{可執行檔案|executable file}、{程式庫檔案|library file}、{軟體配置檔案|configuration file}、{運行紀錄檔|log file}等等)的安裝與存取路徑
* 偵測系統中是否存在{必要|mandatory}或{選用|optional}之,與軟體相容的{依賴組件|dependency}及其版本(如果依賴組件未安裝於系統預設路徑的話,得由使用者指定其路徑)
* 決定軟體於{建構時期|build-time}要編入哪些{功能|feature}(如果沒有手動指定的話,可能會根據系統中依賴軟體的有無來決定是否編入)
* 要使用哪一個軟體開發{工具鏈|toolchain}程式(如{預處理器|preprocessor}、{編譯器|compiler}、{連結器|linker}等)來進行軟體建構
* 其他使用者自定義的細節(如軟體的可執行檔是否要有特定的檔案名稱{前綴|prefix}等)
最後軟體建構配置程式會生成用於實際進行軟體建構的程序供我們於後續步驟使用
:::info
**附註:** 觀察 NGINX 來源碼目錄中的內容您或許會注意到 NGINX 並不是使用常規的{軟體建構系統|software build system}(GNU Autotools/CMake/Maven)而是使用專案專有之模組化的 Shell 腳本來進行軟體建構的配置與建構程式生成
:::
我們先執行下列命令切換{作業目錄|working directory}到 NGINX 1.25.3 軟體來源碼的目錄中,方便後續要進行的操作:
```bash
cd nginx-1.25.3
```
我們可以於前述之[上游專案的說明文件](https://nginx.org/en/docs/configure.html)或是 `configure` 程式印出的的幫助訊息查看可以 NGINX 軟體建構配置的項目,因為上游專案的文件不一定跟我們使用的版本一致的關係所以這邊我們以 configure 程式的輸出作為主要的建構參考:
```plaintext
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` {分頁器|pager} 沒辦法自由查看上一頁的內容的關係,我們可*以 root 身份*執行下列命令改裝功能比較強大的 `less` 分頁器:
```bash
apt install less
```
然後就可以執行下列命令,用 Bash {殼層操作界面|shell} 的{輸入/輸出資料流重導向|Input/Output redirection}功能來一頁一頁瀏覽 `./configure --help` 命令的輸出:
```bash
./configure --help | less
```
![執行 `./configure --help | less` 命令後的終端機畫面示意圖](https://hackmd.io/_uploads/BkzvmCiYp.png "執行 `./configure --help | less` 命令後的終端機畫面")
您可以於 `less` 分頁器中使用 `/` 按鍵搜尋特定的{關鍵字|keyword}快速查找要啟用的功能,自 `./configure --help` 命令的輸出我們可以知道要啟用 HTTPS(SSL/TLS) 支援需要於 `configure` 程式的執行命令中新增 `--with-http_ssl_module` 命令選項:
![自 `configure` 程式的幫助訊息中找到啟用 HTTPS 支援的命令選項的示意圖](https://hackmd.io/_uploads/SJdi40jta.png "自 `configure` 程式的幫助訊息中找到啟用 HTTPS 支援的命令選項")
接下來我們就可以[{迭代地|iteratively}](https://dictionary.cambridge.org/dictionary/english/iteratively)執行下列命令來配置 NGINX 的軟體建構,並根據其錯誤或警告訊息來準備建構所需要的環境:
```bash
./configure --with-http_ssl_module
```
首先 NGINX 軟體建構配置程式抱怨找不到 C 語言程式{編譯器|compiler}:
```plaintext
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)](https://gcc.gnu.org/) 與 [Clang](https://clang.llvm.org/),我們*以 root 身份*安裝 GCC:
```bash
apt install gcc
```
:::info
**附註:** 您也可以執行下列命令搜尋 Ubuntu 軟體庫提供之,可能提供 C 語言程式編譯器的軟體包:
```bash
apt search 'C compiler' | less
```
注意不是每個 C 語言程式編譯器(的各個版本)都支持 NGINX 軟體軟體建構所需要的編譯器特性
:::
然後再一次執行 NGINX 的軟體建構配置程式,這一次它抱怨找不到 PCRE 程式庫:
```plaintext
$ ./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 {軟體包管理器|package manager}會將軟體的不同{組成元件|component} *分開打包成獨立的軟體包* 以節省系統的儲存空間使用,於源碼建構時軟體的依賴組件我們主要會安裝它的「{軟體開發用檔案|development files}(包含但不限於 C/C++ 語言程式的{標頭檔案|header file}、用於{靜態連結|static linking}的{靜態程式庫|static library}檔案與{軟體開發者用的參考文件|developer reference}等等)」的軟體包,此類軟體包通常會使用 `-dev` 的軟體包名{後綴|suffix}(如果是程式庫則通常會有 `lib` 的軟體包名{前綴|prefix}),我們可以執行下列命令將吻合此{名稱式樣|name pattern}的軟體包通通找出來:
```bash
apt_search_opts=(
# 只比對軟體包名稱而非軟體描述文字
--names-only
)
apt search "${apt_search_opts[@]}" '^libpcre.*-dev$'
```
:::info
**附註:**
* `^libpcre.*-dev$` 為 POSIX 風格的正規表達式語法(參閱 apt-cache(8) 與 regex(7) 的 manpage 使用手冊頁面)
* RedHat 系 GNU/Linux 作業系統散布版中軟體開發用檔案的軟體包取名慣例為 `-devel` 後綴
:::
於 `apt search` 命令的輸出我們可以發現 Ubuntu 軟體庫有同時提供*第一版*與*第二版*的 PCRE 程式庫:
```plaintext
$ 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
```
:::info
**附註:** 對您沒看錯,同 libpcre3-dev 軟體包的文字描述所稱,實際上該軟體包為**第一版而非第三版的 PCRE 程式庫**(翻桌)
:::
因自 NGINX 軟體建構配置程序的輸出可以判斷 NGINX 有相容第二版的 PCRE 程式庫,這邊選擇*以 root 身份*執行下列命令安裝第二版 PCRE 程式庫的軟體開發用文件:
```bash
apt install libpcre2-dev
```
:::info
**附註:** NGINX 於 1.21.5 版(2021/12/28)起才新增了第二版 PCRE 程式庫的支援,如建構舊版 NGINX 則須安裝第一版 PCRE 程式庫的軟體開發用檔案
:::
然後再一次執行 NGINX 的軟體建構配置程式,這一次它抱怨找不到 OpenSSL 程式庫的安裝:
```plaintext
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.
```
:::info
**附註:** 因為 NGINX 需要的是 OpenSSL {軟體建構時期|build-time}用的檔案而非{執行時期|runtime}用的檔案,所以就算您的系統明顯有安裝基於 OpenSSL 程式庫的軟體 NGINX 的軟體安裝配置程序仍然會提示未安裝 OpenSSL 程式庫。具體會使用的檔案可以於軟體來源碼目錄中執行下列命令確認:
```bash
grep_opts=(
# 不區分大小寫
--ignore-case
# 遞迴地搜尋指定目錄下的所有檔案
--recursive
)
grep "${grep_opts[@]}" openssl auto/
```
:::
使用先前 PCRE 程式庫的經驗,我們可以執行下列命令搜尋提供 OpenSSL 程式庫的軟體包:
```bash
apt_search_opts=(
# 只比對軟體包名稱而非軟體描述文字
--names-only
)
apt search "${apt_search_opts[@]}" 'libopenssl.*-dev'
```
……但是什麼都找不到呢:
```plaintext
$ apt search "${apt_search_opts[@]}" 'libopenssl.*-dev'
Sorting... Done
Full Text Search... Done
```
我們把搜尋條件再調鬆一點,現在終於有結果了但是好像都不是我們要的:
```plaintext
$ 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 軟體開發用檔案的軟體包:
```bash
$ 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 {小腳本|scriptlet}中的依賴軟體檢查邏輯有嘗試載入一個叫做 `openssl/ssl.h` 的 C 程式語言標頭檔案:
![NGINX 軟體建構配置程序的 OpenSSL 程式庫安裝偵測邏輯片段示意圖](https://hackmd.io/_uploads/r1D55YTKp.png "NGINX 軟體建構配置程序的 OpenSSL 程式庫安裝偵測邏輯片段")
我們可以使用 `apt-file` 工具來查詢所有有提供這個檔案路徑式樣的軟體包,首先先*以 root 身份*執行下列命令安裝軟體:
```bash
apt install apt-file
```
然後執行下列命令建立 apt-file 軟體的本地{快取資料|cache}
```bash
apt-file update
```
最後再執行下列命令搜尋所有提供 `openssl/ssl.h` 路徑結尾之檔案的軟體包:
```bash
apt_file_search_opts=(
# 使用 Perl 正規表達式(而非純文字)搜尋
--regexp
)
apt-file search "${apt_file_search_opts[@]}" 'openssl/ssl\.h$'
```
```output
$ 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 開發用檔案
:::info
**附註:** 以下提供幾種同樣可以找到正確的提供 OpenSSL 軟體開發用檔案軟體包(libssl-dev)的可能作法:
* 編輯 APT 軟體包管理器的軟體來源清單啟用{來源碼軟體包|source package}的軟體來源(deb-src),然後使用 `apt showsrc` 命令查詢 `openssl` 這個來源碼軟體包到底會建構出哪些看起來是提供軟體開發用檔案的{二進位軟體包|binary package}
```plaintext!
# 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) {工具組|toolkit}」的「{加密學相關的|cryptographic} {工具程式|utility}」,那有沒有軟體包有提供「Secure Sockets Layer(SSL) 工具組」的「軟體開發用檔案」呢?直接用 `Secure Sockets Layer toolkit` 當作搜尋關鍵字找找看就可以發現 libssl-dev 應該是我們需要的軟體包:
```plaintext
$ 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` 結尾的提供軟體開發用檔案的軟體包:
```plaintext
$ 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 軟體包管理器的軟體來源清單啟用{來源碼軟體包|source package}的軟體來源(deb-src),然後使用 `apt showsrc` 命令查詢 NGINX 等確定有使用 OpenSSL 程式庫的軟體的{建構依賴軟體|build dependencies}的軟體包有哪個看起來像是跟 OpenSSL 程式庫比較有關的,提供軟體開發用檔案的軟體包
```plaintext!
# 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](https://google.com)/[StackOverflow](https://stackoverflow.com)/[AskUbuntu](https://askubuntu.com)/[ChatGPT](https://chat.openai.com)/[Google Gemini](https://gemini.google.com) 問也可能可以找到解答
:::
*以 root 身份* 執行下列命令補上依賴軟體的安裝:
```bash
apt install libssl-dev
```
然後再一次執行 NGINX 的軟體建構配置程式,現在它抱怨缺少 zlib 程式庫:
```plaintext
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 這個軟體包沒錯:
```plaintext
$ 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
```
:::info
**附註:** 跟 OpenSSL 程式庫一樣,提供 zlib 程式庫軟體開發用檔案的軟體包一樣*不太遵守 Debian 系 GNU/Linux 作業系統的軟體開發用檔案軟體包的命名慣例*,這些軟體就只能多花點時間確認了(攤手)
:::
*以 root 身份* 執行下列命令補上依賴軟體的安裝:
```bash
apt install zlib1g-dev
```
然後再一次執行 NGINX 的軟體建構配置程式,出現下列輸出代表 NGINX 的軟體建構已經順利完成配置了:
```plaintext
$ ./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 安裝的:
* OpenSSL 支援有啟用
* 軟體安裝前綴目錄位於 /usr/local/nginx(一般來說應該是要安裝到 /usr/local,此為 NGINX 軟體的特例)
* 可執行(二進制)檔安裝目錄位於 _軟體安裝前綴目錄_/sbin 目錄(第 12 行)
* 服務配置前綴目錄位於 _軟體安裝前綴目錄_/conf 目錄(一般來說應該要是 _軟體安裝前綴目錄_/etc/_服務識別名稱_ 這個目錄,此為 NGINX 軟體的特例)
* 主服務{配置檔案|config file}位於 _服務配置前綴目錄_/nginx.conf
* 服務主{進程識別編號檔|process id(PID) file}位於 _軟體安裝前綴目錄_/logs/nginx.pid(一般來說應該要安裝到 _軟體安裝前綴目錄_/run/_服務識別名稱_.pid,此為 NGINX 軟體的特例)
* 服務的{錯誤運行紀錄檔|error log}位於 _軟體安裝前綴目錄_/logs/error.log(一般來說應該要安裝到 _軟體安裝前綴目錄_/var/log/_服務識別名稱_/error.log 目錄下,此為 NGINX 軟體的特例)
* 服務的預設{站台訪問紀錄擋|access log}位於 _安裝前綴目錄_/logs/access.log(一般來說應該要安裝到 _軟體安裝前綴目錄_/var/log/_服務識別名稱_/access.log,此為 NGINX 軟體的特例)
且 NGINX 軟體來源碼目錄中出現可用於建構 NGINX 軟體的 Makefile 檔案:
```plaintext
nginx-1.25.3 $ ls
CHANGES LICENSE README conf contrib man src
CHANGES.ru Makefile auto configure html objs
```
### 建構 NGINX 軟體
因為 NGINX 軟體建構配置程式產生了 Makefile 文件,我們可以推定該軟體使用的{軟體建構自動化工具|build-automation utility}為 [GNU Make](https://www.gnu.org/software/make/)。*以 root 身份* 執行下列命令進行安裝:
```bash
apt install make
```
然後我們可以執行下列命令進行 NGINX 軟體的軟體建構:
```bash
make_opts=(
# 開啟平行建構(parallel build)功能,將將平行運行的 sub-make 工作數量限制在
# 主機的中央處理器總處理器執行緒數以減少軟體建構所需時間
--jobs="$(nproc)"
)
make "${make_opts[@]}"
```
如果看到類似這樣的輸出且命令的{結束狀態代碼|exit status code}為零則代表軟體建構順利結束:
```plaintext
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
```
:::info
**附註:**
* 您可以在 make 命令前加上 time 命令以測量軟體建構的所需時間:
```bash
$ 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 目標|phony make target}來清理所有先前 Make 產生的的建構(中間)產物:
```bash
make clean
```
注意此操作也會將 Makefile 文件移除,需要重新執行軟體建構配置程序來重新生成那個檔案
:::
### 安裝 NGINX 軟體
*以 root 身份*執行 Makefile 提供的 `install` 偽 make 目標即可將建構好的 NGINX 軟體安裝到系統中:
```bash
make install
```
:::info
**附註:** 因為一般使用者沒有權限在 /usr/local 系統目錄寫入檔案,所以命令才需要*以 root 身份*執行,如果於 NGINX 軟體建構配置時期有指定將軟體安裝到一般使用者可寫入的目錄中是不需要這樣做的
:::
### 驗證 NGINX 安裝
使用 less 純文字資料分頁器審閱 _服務配置前綴目錄_ 中的 nginx.conf 服務主配置檔可以發現 NGINX 服務預設會在 80 通訊埠號提供一個服務 _軟體安裝前綴目錄_/html 目錄下網頁的靜態 HTTP 服務:
```nginx
...stripped...
http {
...stripped...
server {
listen 80;
server_name localhost;
...stripped...
location / {
root html;
index index.html index.htm;
}
}
}
```
接下來您可以*以 root 身份*執行下列命令將 NGINX 服務啟動並作為{幕後服務|daemon}運行:
```bash
/usr/local/nginx/sbin/nginx
```
```
$ pgrep --list-full nginx
26776 nginx: master process /usr/local/nginx/sbin/nginx
26777 nginx: worker process
```
:::info
**附註:**
* 因為下列原因 NGINX 服務主程式才需要*以 root 身份*執行:
+ 一般使用者沒有權限運行監聽 <1024 監聽埠號的服務(除非服務主程式有被賦予 `CAP_NET_BIND_SERVICE` {Linux 能力|capability})
+ 一般使用者沒有權限寫入位於系統目錄中的 NGINX 進程識別編號檔目錄以及運行紀錄目錄
* NGINX 為*多進程*的運作模型,一個服務實體會創建一個{主進程|master process}(負責管理工人進程等)跟至少一個的{工人進程|worker process}(負責實際處理服務請求)
:::
使用 curl HTTP 客戶端軟體訪問{本地主機|localhost}(於此情境指 Docker 容器自己)的 80 HTTP 服務常規通訊埠應該可以獲得類似下面的 NGINX 範例站台網頁內容:
```output
$ 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 服務常規通訊埠應該也可以看到相同頁面:
![使用 Mozilla Firefox Web 瀏覽器訪問容器中的 NGINX HTTP 服務站台示意圖](https://hackmd.io/_uploads/ryX1TO0tT.png "使用 Mozilla Firefox Web 瀏覽器訪問容器中的 NGINX HTTP 服務站台")
接下來我們來測試看看我們安裝的 NGINX 是否支援 HTTPS 服務,查看 NGINX 服務的主配置檔可以看到 NGINX 有一個預設註解掉的 HTTPS 站台範例配置:
```nginx
...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 編輯器軟體](https://www.nano-editor.org/):
```bash
apt install nano
```
然後我們可以*以 root 身份*執行下列命令編輯 NGINX 的服務主配置檔將該 HTTPS 服務配置取消註解:
```bash
nano /usr/local/nginx/conf/nginx.conf
```
:::info
**附註:** GNU nano 編輯器會在操作界面的下側列舉可使用的常見操作,各操作前面的 {插入記號|caret}-_字元_ 序列代表該操作需要以 Ctrl+_字元_ 的組合鍵觸發:
![GNU nano 的操作提示面板示意圖](https://hackmd.io/_uploads/B1z6QdkcT.png "GNU nano 的操作提示面板")
例如內容編輯完成後可以按下 Ctrl+X 組合鍵存檔結束編輯
:::
留意該站台配置會參照一個叫做 cert.pem 的 X.509 證書文件以及一個叫做 cert.key 的 RSA 私鑰文件:
```nginx
...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 配置的正確性:
```bash
/usr/local/nginx/sbin/nginx -t
```
:::info
**附註:** 需要*以 root 身份*執行的原因是因為 NGINX 安裝到系統目錄中以一般使用者身份執行會有部份資源存取不了的問題,如果 NGINX 是安裝到使用者自己的目錄下就沒這個問題
:::
會發現 NGINX 抱怨無法存取到 cert.pem 這個 HTTPS 服務會使用到的 X.509 證書文件:
```output
# /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),我們可以執行下列命令{自行簽發|self-sign}一個給 localhost 站台使用的 X.509 證書來用:
```bash
# 因為私鑰洩漏有資訊安全疑慮所以將預設的群組(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 服務配置目錄:
```bash
# 通用的 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 配置的正確性應該就能過了:
```output
# /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 的服務配置:
```bash
/usr/local/nginx/sbin/nginx -s reload
```
:::info
**附註:** 此命令實際的作用為自先前提到的 NGINX 服務{主進程|master process}{進程識別編號檔|pid file}讀取 NGINX 服務主進程的{進程識別編號|process ID(PID)},然後對該進程發送 SIGHUP 訊號,更多資訊可以參閱 [Controlling nginx](https://nginx.org/en/docs/control.html) 這個官方文件
:::
順利的話您應可以執行下列命令訪問到 NGINX 的 HTTPS 站台:
```bash
curl_opts=(
# 略過 SSL/TLS 憑證驗證,非由公認信任之憑證簽發機構(certificate authority(CA))
# 簽發的憑證是沒辦法過驗證的
--insecure
)
curl "${curl_opts[@]}" https://localhost
```
於宿主機使用 Web 瀏覽器(本文以 Mozilla Firefox Web 瀏覽器應用軟體為例)訪問本地主機的 <https://localhost> 頁面則會先跳出 SSL/TLS 驗證警告畫面(同前所述因為憑證是我們自己簽發的不被 Web 瀏覽器/作業系統信任:
![Mozilla Firefox Web 瀏覽器的「本網站可能有安全性風險」警告畫面截圖](https://hackmd.io/_uploads/SJg5Bgxca.png "Mozilla Firefox 瀏覽器的「本網站可能有安全性風險」警告畫面")
點擊下方的「進階…」按鈕再點擊「接受風險並繼續」按鈕就可以讓 Web 瀏覽器記住此安全例外設定:
![Mozilla Firefox Web 瀏覽器的「本網站可能有安全性風險」警告畫面設定安全例外操作示意圖](https://hackmd.io/_uploads/BkfH4eec6.png "Mozilla Firefox Web 瀏覽器的「本網站可能有安全性風險」警告畫面設定安全例外操作示意圖")
接下來應該就可以看到同一個 NGINX 範例站台頁面:
![使用 Mozilla Firefox Web 瀏覽器訪問容器中的 NGINX HTTPS 服務站台示意圖](https://hackmd.io/_uploads/HJ4YLll96.png "使用 Mozilla Firefox Web 瀏覽器訪問容器中的 NGINX HTTPS 服務站台")
至此我們就可以確定我們的建構的 NGINX 服務是正常運作且有滿足我們所有要求的
## 操作演示
以下提供實際進行操作的終端機畫面錄影,僅供參考:
[![asciinema 操作過程錄影](https://asciinema.org/a/633486.svg "asciinema 操作過程錄影")](https://asciinema.org/a/633486)
## 參考資料
以下說明進行本練習所需要的相關參考資料:
* [nginx: download](https://nginx.org/en/download.html)
NGINX 的軟體下載頁面
* [Introducing NGINX 1.10 and 1.11 - NGINX](https://www.nginx.com/blog/nginx-1-10-1-11-released/)
說明 NGINX 軟體{開發主線|mainline}版本與{穩定|stable}版本的差異
* [nginx documentation](https://nginx.org/en/docs/)
NGINX 的官方說明文件索引頁
* [Building nginx from Sources](https://nginx.org/en/docs/configure.html)
說明如何自源碼建構 NGINX
* [Ubuntu Manpage: apt-get - APT package handling utility -- command-line interface - DESCRIPTION - `update` sub command](https://manpages.ubuntu.com/manpages/jammy/man8/apt-get.8.html)
說明 `apt-get update` 命令的用途
* [Repositories/Ubuntu - Community Help Wiki](https://help.ubuntu.com/community/Repositories/Ubuntu)
說明 Ubuntu 軟體庫的基本概念
* [Docker Interactive Shell Options | ChatGPT](https://chat.openai.com/share/b4200cce-1080-4b34-a178-3ddd850987b1)
說明 `docker run`、`docker exec` 等命令之 `--interactive`、`--tty` 命令選項的用途與於啟動 Bash 互動式{殼層|shell}操作界面情境的目的
* [NGINX Versions: Mainline vs. Stable | ChatGPT](https://chat.openai.com/share/11ba41be-84cc-40fa-b875-8abb69c003fa)
說明 NGINX 開發主線版與穩定版的差異
* [Commands to select the type of operation - Invoking GPG - GnuPG manual - Using the GNU Privacy Guard](https://www.gnupg.org/documentation/manuals/gnupg/Operational-GPG-Commands.html)
說明 `--verify` `gpg` 命令選項的用途與支援的引數類型
* [Importing a public key | Exchanging keys | Getting Started | The GNU Privacy Handbook](https://www.gnupg.org/gph/en/manual.html#AEN216)
說明如何匯入第三方的公鑰至 GnuPG 鑰匙圈
* [Detached signatures | Making and verifying signatures | Getting Started | The GNU Privacy Handbook](https://www.gnupg.org/gph/en/manual.html#AEN161)
說明如何驗證檔案的分離式 PGP 簽名
* [Digital signatures | Concepts | The GNU Privacy Handbook](https://www.gnupg.org/gph/en/manual.html#:~:text=Digital%20signatures)
說明數位簽名的基本概念
* [Distributing keys](https://www.gnupg.org/gph/en/manual.html#:~:text=Distributing%20keys)
說明如何透過 PGP keyserver 獲取他人的 PGP 公鑰
* [How to Verifiy Nginx Source Tarball with GPG on Ubuntu Server](https://forum.nginx.org/read.php?11,279096)
說明如何(理想上)正確地驗證 NGINX 軟體來源碼封存檔的完整性與真實性
* [ITERATIVELY | English meaning - Cambridge Dictionary](https://dictionary.cambridge.org/dictionary/english/iteratively)
說明 iteratively 英文單字的意思
* [GCC, the GNU Compiler Collection - GNU Project](https://gcc.gnu.org/)
說明 GCC 軟體的相關資訊
* [Clang C Language Family Frontend for LLVM](https://clang.llvm.org/)
說明 Clang 軟體的相關資訊
* [PLEASE TAKE NOTE — pcre(3) — libpcre3-dev — Debian testing — Debian Manpages](https://manpages.debian.org/testing/libpcre3-dev/pcre.3.en.html#PLEASE_TAKE_NOTE)
說明 pcre3 軟體包與 pcre2 軟體包的差異
* [libpcre3 軟體包的 Debian 維護者 README 文件](file:///usr/share/doc/libpcre3/README.Debian)
說明 pcre3 軟體包與 pcre2 軟體包的差異
* [PID Files: Managing Processes | ChatGPT](https://chat.openai.com/share/e0055a1a-d4a2-44c1-ab06-4a71941ad637)
[Process Identification File | Google Bard](https://bard.google.com/share/5d311af111c9)
說明進程識別編號檔的用途
* [Make (software) - Wikipedia](https://en.wikipedia.org/wiki/Make_(software))
說明 GNU Make 軟體的分類
* [Build automation - Wikipedia](https://en.wikipedia.org/wiki/Build_automation)
說明軟體建構自動化的基本概念
* [PARALLEL MAKE AND THE JOBSERVER — make(1) — make-guile — Debian bookworm — Debian Manpages](https://manpages.debian.org/bookworm/make-guile/make.1.en.html#PARALLEL_MAKE_AND_THE_JOBSERVER)
說明 GNU Make 平行建構功能的使用方式與原理
* [Rule Introduction (GNU make)](https://www.gnu.org/software/make/manual/html_node/Rule-Introduction.html)
說明 make {目標|target}的定義
* [Phony Targets (GNU make)](https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html)
說明{偽 make 目標|phony make target}的定義與用途
* [capabilities(7) — manpages — Debian bookworm — Debian Manpages](https://manpages.debian.org/bookworm/manpages/capabilities.7.en.html)
說明 {Linux 能力|capabilities}的基本概念與 `CAP_NET_BIND_SERVICE` 這個 Linux 能力的效果
* [docker run --publish vs --expose | Google Bard](https://bard.google.com/share/e3158c90a1e3)
[Docker Run Options: Networking | ChatGPT](https://chat.openai.com/share/ce09998c-8a2c-4dc0-bc9f-f237f6ebbf0d)
說明 `docker run` 命令的 `--publish` 跟 `--expose` 命令選項之間的差異
* [插入記號 - 維基百科,自由的百科全書](https://zh.wikipedia.org/zh-tw/%E6%8F%92%E5%85%A5%E8%A8%98%E8%99%9F)
說明 caret 字元的用途與台灣中文翻譯
* [openssl-genrsa(1ssl) — openssl — Debian bookworm — Debian Manpages](https://manpages.debian.org/bookworm/openssl/openssl-genrsa.1ssl.en.html)
說明 `openssl genrsa` 命令的用途與相關命令選項
* [openssl-req(1ssl) — openssl — Debian bookworm — Debian Manpages](https://manpages.debian.org/bookworm/openssl/openssl-req.1ssl.en.html)
說明 `openssl req` 命令的用途與相關命令選項
* [openssl-x509(1ssl) — openssl — Debian bookworm — Debian Manpages](https://manpages.debian.org/bookworm/openssl/openssl-x509.1ssl.en.html)
說明 `openssl x509` 命令的用途與相關命令選項
<style>
/* 調大旁註文字的字元大小 */
rt{
font-size: 10pt;
}
</style>