返回文章列表

Python套件儲存函式庫建置與安全管理

本文介紹如何使用 pypiserver 建立私有 Python 套件儲存函式庫,以及如何確保套件的安全性。文章涵蓋了套件完整性檢查、發行版簽章、Wheel 格式的運用、建立 Wheel 發行版、從 Pipfile.lock 建立

Web 開發 資安

在 Python 開發中,管理內部套件和確保其安全性至關重要。本文將引導你使用 pypiserver 建立私有套件儲存函式庫,並探討如何運用 Pipenv、Wheel 格式和雜湊校驗等機制來強化套件的安全性。同時,我們也會討論在架設私有索引伺服器時,永續性、保密性和完整性三大支柱的重要性,並提供實用的操作步驟和程式碼範例,協助你更好地管理和保護你的 Python 套件。

建立私人套件儲存函式庫:使用pypiserver管理內部Python套件

在開發Python專案時,管理內部套件是一個常見的需求。雖然PyPI(Python Package Index)是公開的Python套件儲存函式庫,但對於私人專案或企業內部使用,我們需要建立一個私人套件儲存函式庫來存放和管理我們的套件。

為什麼需要私人套件儲存函式庫?

對於開源專案來說,PyPI是一個理想的釋出平台,因為它允許任何人使用和分享你的套件。然而,對於私人專案或企業內部使用的套件,將其釋出到PyPI上並不合適。這時候,我們就需要一個私人套件儲存函式庫來存放和管理這些套件。

替代PyPI的私人套件儲存函式庫方案

有多個開源專案提供了與PyPI類別似的功能,可以用來建立私人套件儲存函式庫。其中兩個流行的選擇是Warehouse和pypiserver。Warehouse是PyPI官方網站後端的開源實作,而pypiserver則是另一個獨立的實作。

Warehouse與pypiserver的比較

  • Warehouse和pypiserver都提供了基本的套件瀏覽和下載功能。
  • 兩者都支援使用twine工具上傳套件。
  • 在安全性方面,Warehouse實作了根據專案的存取控制,而pypiserver則採用了較為簡單的扁平許可權架構。

對於商業環境或小型團隊來說,pypiserver的簡單許可權架構可能更為合適,因為它允許團隊成員在不需要複雜許可權設定的情況下上傳和管理套件。

建立pypiserver

以下是在Raspberry Pi 4B上建立pypiserver的步驟:

  1. 建立新的使用者帳號

rpi> sudo adduser indexserver

    
    為了將pypiserver與系統的其他部分隔離,建立一個新的使用者帳號是個好主意。

2.  **安裝必要的工具**

    ```bash
rpi> sudo apt install apache2-utils
安裝`htpasswd`工具,用於設定驗證資訊。
  1. 切換到新建立的使用者帳號

rpi> sudo -iu indexserver

    
    使用新建立的使用者帳號進行後續操作。

4.  **安裝pipenv並設定環境**

    ```bash
rpi> pip install --user pipenv
rpi> echo "export PATH=/home/indexserver/.local/bin:$PATH" >> ~/.bashrc
rpi> source ~/.bashrc
安裝pipenv並將其新增至PATH環境變數。
  1. 建立pypiserver環境

rpi> mkdir indexserver rpi> mkdir packages rpi> cd indexserver rpi> pipenv install pypiserver passlib>=1.6

    
    建立新的目錄並在其中建立pypiserver環境。

6.  **設定驗證資訊**

    ```bash
rpi> htpasswd -c htaccess your_desired_username
使用`htpasswd`工具建立驗證檔案。
  1. 設定systemd服務

    建立一個systemd檔案(例如/lib/systemd/system/indexserver.service),內容如下:

[Unit] Description=Custom Index Server for Python distributions After=multi-user.target

[Service] Type=idle User=indexserver WorkingDirectory=/home/indexserver/indexserver ExecStart=/home/indexserver/.local/bin/pipenv run pypi-server -p 8080 -P htaccess ../packages

[Install] WantedBy=multi-user.target

    
    這樣就可以在系統啟動時自動啟動pypiserver服務。

8.  **啟用並啟動服務**

    ```bash
$ sudo systemctl enable indexserver
$ sudo service indexserver start
啟用並啟動pypiserver服務。

使用pypiserver管理內部套件

設定好pypiserver後,你就可以使用twine工具上傳你的內部套件至私人儲存函式庫。確保在上傳時提供正確的使用者憑證以透過驗證。

上傳套件至pypiserver

使用twine上傳套件的指令如下:

twine upload --repository-url http://rpi4:8080/ dist/*

請將http://rpi4:8080/替換為你的pypiserver實際位址。

從pypiserver安裝套件

要從pypiserver安裝套件,可以使用pip工具,並指定--index-url引數指向你的pypiserver:

pip install --index-url http://rpi4:8080/simple/ your-package-name

同樣地,請將http://rpi4:8080/simple/替換為你的pypiserver實際位址。

架設私有索引伺服器的三大支柱:永續性、保密性與完整性

在自行架設索引伺服器(index server)時,必須考慮多個關鍵因素以確保系統的穩定性和安全性。以下將探討永續性(Durability)、保密性(Confidentiality)以及完整性(Integrity)這三大支柱的重要性及其實作方法。

永續性:確保環境的一致性

當執行自己的索引伺服器時,必須考慮到基礎設施發生災難性硬體故障的可能性。由於散佈包(distributions)並未儲存在版本控制系統中,儘管其原始碼的版本應該被標記以便於未來存取,從相同的標籤重新生成散佈包可能會產生具有不同校驗和(check hashes)的檔案。因此,確保相同的檔案始終可用對於重建軟體的舊版本至關重要。

Pipenv 自動記錄了最後一次鎖定(lock)時所有可用散佈包的雜湊值(hashes),因此,只要未來能夠取得相同的檔案,就可以重建相同的環境。

備份策略

  1. 儲存散佈包的重要性:儲存在索引伺服器上的散佈包檔案應被視為與主要原始碼樹(source tree)同等重要,需要進行備份。
  2. 使用 wget 建立部分映象:可以使用 wget 這類別工具為所依賴的套件建立 PyPI 的部分映象。

提取依賴套件清單

  • 使用 pipenv lock -rpipenv lock -r --dev 命令可以輸出依賴套件的清單,包括所選擇的版本和適用條件。
  • 或者,使用 jq 工具從 Pipenv.lock 檔案中提取資料,例如:jq ".default + .develop | keys" Pipfile.lock 可以提取主要和開發依賴清單中的套件名稱。

保密性:保護私有套件

在執行私有索引伺服器時,通常會有不想公開的套件。這些可能是商業基礎上開發的閉源套件,或者是特定用途的工具,甚至是分叉的開源套件。保密性是指確保未經授權的人員無法存取索引伺服器上儲存的散佈包。

風險評估與對策

  1. 風險評估:根據公司的風險承受能力和潛在威脅評估保密措施。
  2. 使用現有安全功能:對於大多數公司,使用 pypiserver 或 Apache、Nginx 等網頁伺服器的安全功能就足夠了。
  3. 私有網路:在私有網路中執行索引伺服器可以提高安全性。
  4. 結合網路安全與認證系統:使用公司控制的網路結合傳統的認證系統可以提供額外的保護。

生產佈署的存取

生產佈署通常需要存取索引伺服器,以便自動下載和安裝應用程式碼。因此,保密措施也需要考慮到生產環境的需求。

完整性:確保散佈包未被篡改

完整性是指能夠確定散佈包未被惡意第三方更改。這通常透過記錄散佈包的雜湊值來實作,Pipenv 在鎖定環境時會記錄這些雜湊值。

實踐完整性檢查

  1. 雜湊值驗證:使用 Pipenv 鎖定機制來驗證散佈包的完整性。
  2. 安全儲存雜湊值:確保雜湊值的安全儲存,以防止被篡改。

綜上所述,架設私有索引伺服器需要綜合考慮永續性、保密性和完整性。根據具體需求和風險評估,選擇適當的措施來確保索引伺服器的安全性與可靠性。

軟體套件安裝與安全性考量

在軟體開發過程中,確保所使用的套件安全性是至關重要的。Python 的套件管理工具 Pipenv 在這方面提供了多種機制來保障安裝的套件是安全且可信的。

套件完整性檢查

當使用 Pipenv 安裝套件時,它會檢查下載的檔案是否與預期的雜湊值相符。這種檢查可以防止下載過程中檔案被篡改。Pipenv 會比較下載檔案的雜湊值與在 Pipfile.lock 中記錄的雜湊值。如果兩者不符,Pipenv 將拒絕安裝該套件。

內容解密:

此機制確保了套件在傳輸過程中的安全性,防止了中間人攻擊等安全威脅。開發者應該確保 Pipfile.lock 檔案的安全,以避免雜湊值被篡改。

發行版簽章

除了雜湊值檢查外,PyPI 還支援發行版簽章。這意味著開發者可以上傳他們的套件簽章,以證明該套件是由特定的可信方上傳的。然而,這需要假設 PyPI 伺服器是可信的,並且只有授權人員才能上傳套件。

內容解密:

簽章機制增加了額外的一層安全性,但它需要開發者和使用者都信任 PyPI 伺服器及其驗證上傳者的機制。

Wheel 格式與安裝安全性

使用 Wheel 格式可以避免在安裝過程中執行任意程式碼。Wheel 是一種預先建構的套件格式,可以直接安裝而無需編譯。

pipenv install wheel
pipenv run pip wheel packagename

內容解密:

  • 第一步是安裝 wheel 套件,以支援將其他套件轉換為 Wheel 格式。
  • 第二步是使用 pip wheel 命令將指定的套件轉換為 Wheel 格式。這樣可以確保該套件在安裝時不會執行任何可能有害的程式碼。

建立 Wheel 發行版

對於已經存在的套件,可以使用 pip wheel 命令將其轉換為 Wheel 格式。這需要在一個乾淨的 Pipenv 環境中進行,以避免汙染其他環境。

cd ~
mkdir wheelbuilding
cd wheelbuilding
pipenv install
pipenv run pip wheel packagename

內容解密:

  • 建立一個新的目錄 wheelbuilding 用於建立 Wheel 發行版。
  • 在該目錄中建立一個新的 Pipenv 環境,並安裝必要的工具。
  • 使用 pip wheel 命令建立指定套件的 Wheel 發行版。

從 Pipfile.lock 建立 requirements.txt

可以使用 pipenv lock -r 命令從 Pipfile.lock 建立 requirements.txt 檔案,但這種方法會丟失雜湊資訊。為了保留雜湊資訊,可以直接解析 Pipfile.lock 檔案。

pipfile lock -r > requirements.txt

內容解密:

此命令將 Pipfile.lock 中的依賴資訊輸出到 requirements.txt 檔案中,但不會包含雜湊資訊。開發者應該直接使用 Pipfile.lock 以保留完整性檢查資訊。

練習:擷取更好的 requirements.txt

由於 pipenv lock -r 不會輸出雜湊資訊,開發者可以嘗試直接解析 Pipfile.lock 以取得更完整的依賴資訊。

# 解析 Pipfile.lock 的範例程式碼
import json

with open('Pipfile.lock') as f:
    lock_data = json.load(f)

# 處理 lock_data 中的依賴資訊

內容解密:

此範例展示瞭如何讀取 Pipfile.lock 檔案並解析其中的 JSON 資料。開發者可以根據需要進一步處理這些資料,以產生包含雜湊資訊的依賴列表。

封裝指令碼與安裝設定

在前面的章節中,我們已經能夠建立乾淨安裝的發行版本,但命令列工具的呼叫方式又再次發生了變化。我們曾經使用 python sensors.pypython src/apd/sensors/sensors.pypython -m apd.sensors.sensors 來呼叫指令碼。這些方法都不太理想,變化的原因是我們的設定中缺乏足夠的間接層。

我們希望使用者能夠像執行其他二進位制可執行檔案一樣執行我們的指令碼。Python 提供了 console_scripts 功能來實作這一點。當安裝的發行版本中包含 console_scripts 後設資料欄位時,這些指令碼會在安裝位置的二進位制目錄中建立為可執行檔案。

使用 entrypoints 安裝控制檯指令碼

例如,在第一章中,我們在全域性環境中安裝了 pipenv。這將 Python 程式碼安裝到了 C:\Users\micro\AppData\Roaming\Python\Python38\site-packages\pipenv\__init__.py(在典型的 Windows 機器上)。當在命令列中呼叫 pipenv 時,實際執行的是 C:\Users\micro\AppData\Roaming\Python\Python38\Scripts\pipenv.exe。這個檔案是一個真實的可執行檔案,但它不是獨立的;它只是包裝了 Python 呼叫,真正的程式碼並沒有編譯到可執行檔案中。

檢視 Pipenvsetup.py,我們可以看到如下程式碼:

entry_points={
    "console_scripts": [
        "pipenv=pipenv:cli",
        "pipenv-resolver=pipenv.resolver:main",
    ]
},

這部分程式碼宣告瞭兩個應該被包裝成可直接執行的可執行檔案的 Python 可呼叫物件。格式是先寫可執行檔案的名稱,然後是 =,再然後是對應的可呼叫物件的參照。這是一個點分隔的模組名稱,後面跟著一個冒號,然後是該模組中的可呼叫物件的名稱。

將 show_sensors 新增為控制檯指令碼

我們希望將 apd.sensors.sensors 中的 show_sensors 可呼叫物件作為命令列指令碼提供。因此,我們需要在 setup.cfg 檔案中新增以下內容:

[options.entry_points]
console_scripts =
    sensors = apd.sensors.sensors:show_sensors

這些可執行檔案只在安裝時建立,因此我們需要重新執行安裝過程,以便處理這個新的指令碼。由於我們是以可編輯模式安裝的,大多數更改會立即生效,但對於 console_scripts 的更改則需要重新安裝。

執行 pipenv install 後,我們現在可以透過 pipenv run sensors 來執行指令碼。我們已經接近完成第一個版本的軟體;只剩下檔案檔案了。

檔案檔案:README、DEVELOP 和 CHANGES

編寫這些檔案的重要性不言而喻。當開始一個新專案時,手頭有足夠的檔案是非常寶貴的。最佳實踐會隨著時間而變化,對不再常見的工具的使用知識也會逐漸淡忘。此外,讓其他開發者能夠以最小的麻煩開始工作在某個軟體上是非常重要的。

有時候,開始一個新專案的最困難的部分是理解開發者所遵循的模式。我們需要為 apd.sensors 包編寫一個 README 檔案,解釋這個包是什麼,如何安裝它,以及如何使用它。這個檔案將是使用者接觸該專案的第一個東西。

編寫 README 檔案

README 檔案應該包含以下內容:

  • 軟體包的介紹
  • 安裝
  • 使用

這些資訊對於使用者能夠順利開始使用該軟體包至關重要。

生成 wheel 檔案並上傳到自定義索引伺服器

在前面的章節中,我們生成了一個 requirements.txt 檔案,並使用 pip wheel 命令生成了 wheel 檔案。這些 wheel 檔案被儲存在 wheels/ 目錄中,準備被上傳到自定義索引伺服器。

cd ~/wheelbuilding
pipenv run pip wheel -r requirements.txt -w wheels

提取附加資料並儲存到 requirements.txt

如果生成的 requirements.txt 檔案沒有包含雜湊校驗,可以編寫一個小的 Python 指令碼來提取這些附加資料並儲存到檔案中。

import subprocess

def generate_requirements_with_hash():
    # 使用 pip freeze 生成 requirements.txt
    result = subprocess.run(["pip", "freeze"], stdout=subprocess.PIPE)
    requirements = result.stdout.decode('utf-8').splitlines()

    # 新增雜湊校驗
    requirements_with_hash = []
    for req in requirements:
        package_name = req.split('==')[0]
        result = subprocess.run(["pip", "hash", package_name], stdout=subprocess.PIPE)
        hash_value = result.stdout.decode('utf-8').strip()
        requirements_with_hash.append(f"{req} --hash={hash_value}")

    # 儲存到 requirements.txt
    with open("requirements.txt", "w") as f:
        f.write('\n'.join(requirements_with_hash))

if __name__ == "__main__":
    generate_requirements_with_hash()

詳細解說:

  1. 匯入必要的模組:指令碼首先匯入了 subprocess 模組,用於執行系統命令。
  2. 定義函式:定義了一個名為 generate_requirements_with_hash 的函式,用於生成包含雜湊校驗的 requirements.txt 檔案。
  3. 生成原始 requirements.txt:使用 pip freeze 命令生成初始的 requirements.txt 內容。
  4. 新增雜湊校驗:遍歷每個包,使用 pip hash 命令計算其雜湊值,並將結果新增到包名稱後面。
  5. 儲存結果:將最終結果寫入到 requirements.txt 檔案中。

PiWheels 流程簡介

PiWheels 自動下載 PyPI 上可用的每個發行版本,將其轉換為 wheel,並使其在他們的替代索引伺服器上可用。這個過程與我們前面所做的工作類別似,但 PiWheels 的流程更為複雜,因為他們有自定義的 wheel 構建過程,以生成可能適用於許多不同 Raspberry Pi 主機的檔案。對於只使用 Python 程式碼的分發版本來說,轉換為 wheel 格式非常容易,但也可以新增編譯元件,這需要適當的函式庫和工具。

好處:

  • sysv_ipcpsutil 這樣的包原本需要在每個 Raspberry Pi 安裝目標上進行長度的構建步驟,但現在安裝速度大大加快。
  • 一般來說,如果一個包有適用於目標環境的 wheel 可用,那麼就不再需要在生產伺服器上安裝編譯器和構建鏈。在非生產伺服器上提前進行任何編譯對於許多系統管理員來說是一個非常有吸引力的好處。