驗證依賴項
使用發布在第三方儲存庫上的外部依賴項和外掛程式會使您的建置面臨風險。特別是,您需要了解哪些二進制檔案是通過傳遞方式引入的,以及它們是否合法。為了降低安全風險並避免將受損的依賴項整合到您的專案中,Gradle 支援依賴項驗證。
依賴項驗證本質上是一個不方便使用的功能。這意味著每當您要更新依賴項時,建置都可能失敗。這意味著合併分支將會更加困難,因為每個分支可能具有不同的依賴項。這意味著您可能會想將其關閉。
那麼,為什麼您應該費心呢?
依賴項驗證關乎您對所取得和交付內容的信任。
如果沒有依賴項驗證,攻擊者很容易破壞您的供應鏈。有很多真實世界的例子表明,通過新增惡意依賴項,工具會受到損害。依賴項驗證旨在保護您自己免受這些攻擊,方法是強制您確保包含在建置中的工件是您期望的工件。然而,它並非旨在阻止您包含易受攻擊的依賴項。
在安全性和便利性之間找到適當的平衡很困難,但 Gradle 將嘗試讓您選擇適合您的「正確級別」。
依賴項驗證包含兩個不同且互補的操作
-
校驗和驗證,允許斷言依賴項的完整性
-
簽章驗證,允許斷言依賴項的來源
Gradle 預設支援校驗和驗證和簽章驗證,但預設不執行任何依賴項驗證。本節將引導您正確配置依賴項驗證以滿足您的需求。
此功能可用於
-
偵測受損的依賴項
-
偵測受損的外掛程式
-
偵測本地依賴項快取中被竄改的依賴項
啟用依賴項驗證
驗證元數據檔案
目前,依賴項驗證元數據的唯一來源是此 XML 配置檔案。未來版本的 Gradle 可能會包含其他來源(例如通過外部服務)。 |
一旦發現依賴項驗證的配置檔案,就會自動啟用依賴項驗證。此配置檔案位於 $PROJECT_ROOT/gradle/verification-metadata.xml
。此檔案至少包含以下內容
<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
<configuration>
<verify-metadata>true</verify-metadata>
<verify-signatures>false</verify-signatures>
</configuration>
</verification-metadata>
-
在建置期間使用的工件檔案(例如 jar 檔案、zip 檔案等)
-
元數據工件(POM 檔案、Ivy 描述符、Gradle 模組元數據)
-
外掛程式(專案外掛程式和設定外掛程式)
-
使用進階依賴項解析 API 解析的工件
Gradle *不會*驗證變更中的依賴項(特別是 SNAPSHOT
依賴項)或本地產生的工件(通常是在建置本身期間產生的 jar 檔案),因為它們的校驗和和簽章本質上總是會變更。
使用如此最少的配置檔案,使用任何外部依賴項或外掛程式的專案都會立即開始失敗,因為它不包含任何要驗證的校驗和。
依賴項驗證的範圍
依賴項驗證配置是全域的:單個檔案用於配置整個建置的驗證。特別是,同一個檔案用於(子)專案和 buildSrc
。
如果使用包含的建置
-
目前建置的配置檔案用於驗證
-
因此,如果包含的建置本身使用驗證,則會忽略其配置,而優先使用目前的配置
-
這意味著包含建置的工作方式與升級依賴項類似:它可能需要您更新目前的驗證元數據
因此,入門的簡單方法是為現有建置產生最少的配置。
配置控制台輸出
預設情況下,如果依賴項驗證失敗,Gradle 將產生關於驗證失敗的簡短摘要以及包含關於失敗的完整資訊的 HTML 報告。如果您的環境阻止您閱讀此 HTML 報告檔案(例如,如果您在 CI 上執行建置,並且不容易獲取遠端工件),Gradle 提供了一種選擇加入詳細控制台報告的方法。為此,您需要在您的 gradle.properties
檔案中新增此 Gradle 屬性
org.gradle.dependency.verification.console=verbose
引導依賴項驗證
值得一提的是,雖然 Gradle 可以為您產生依賴項驗證檔案,但您應該始終檢查 Gradle 為您產生的內容,因為您的建置可能已經包含受損的依賴項,而您卻不知情。請參閱適當的校驗和驗證或簽章驗證章節以獲取更多資訊。
引導既可以用於從頭開始建立檔案,也可以用於使用新資訊更新現有檔案。因此,建議您在開始引導後始終使用相同的參數。
依賴項驗證檔案可以使用以下 CLI 指令產生
gradle --write-verification-metadata sha256 help
write-verification-metadata
標誌需要您想要產生的校驗和列表或簽章的 pgp
。
執行此命令列將導致 Gradle
-
解析所有可解析的配置,包括
-
來自根專案的配置
-
來自所有子專案的配置
-
來自
buildSrc
的配置 -
包含的建置配置
-
外掛程式使用的配置
-
-
下載在解析期間發現的所有工件
-
計算請求的校驗和,並可能根據您的要求驗證簽章
-
在建置結束時,產生將包含推斷的驗證元數據的配置檔案
因此,verification-metadata.xml
檔案將在後續建置中用於驗證依賴項。
有些依賴項 Gradle 無法通過這種方式發現。特別是,您會注意到上面的 CLI 使用了 help
任務。如果您未指定任何任務,Gradle 也會自動執行預設任務,並在建置結束時產生配置檔案。
不同之處在於,Gradle 可能會根據您執行的任務發現更多依賴項和工件。事實上,Gradle 無法自動發現分離的配置,這些配置基本上是作為任務執行內部實作細節解析的依賴項圖形:特別是,它們未聲明為任務的輸入,因為它們實際上取決於任務在執行時的配置。
一個好的開始方法是僅使用最簡單的任務 help
,它將盡可能多地發現,如果後續建置因驗證錯誤而失敗,您可以使用適當的任務重新執行產生以「發現」更多依賴項。
Gradle 不會驗證使用其自身 HTTP 用戶端的外掛程式的校驗和或簽章。只有使用 Gradle 提供的基礎架構來執行請求的外掛程式才會看到其請求得到驗證。
使用產生進行增量更新
Gradle 產生的驗證檔案對其所有內容都有嚴格的排序。它還使用來自現有狀態的資訊將變更限制在嚴格的最小值。
這意味著產生實際上是用於更新驗證檔案的便捷工具
-
Gradle 產生的校驗和條目將具有清晰的
origin
,以「Generated by Gradle」開頭,這是一個很好的指示,表明條目需要審查, -
手動新增的條目將立即被考慮在內,並在寫入檔案後出現在正確的位置,
-
檔案的標頭註解將被保留,即根 XML 節點之前的註解。這允許您擁有許可證標頭或關於要使用哪些任務和哪些參數來產生該檔案的說明。
憑藉上述優勢,通過簡單地再次產生檔案並審查變更,可以非常容易地處理新的依賴項或依賴項版本。
使用乾燥模式
預設情況下,引導是增量的,這意味著如果您多次運行它,資訊會新增到檔案中,特別是您可以依靠 VCS 來檢查差異。在某些情況下,您只想查看產生的驗證元數據檔案的外觀,而無需實際變更現有檔案或覆蓋它。
為此,您只需新增 --dry-run
gradle --write-verification-metadata sha256 help --dry-run
然後,將產生一個新檔案,稱為 verification-metadata.dryrun.xml
,而不是產生 verification-metadata.xml
檔案。
由於 --dry-run 不執行任務,因此速度會快得多,但它會遺漏在任務執行時發生的任何解析。 |
停用元數據驗證
預設情況下,Gradle 不僅會驗證工件(jar 檔案等),還會驗證與這些工件相關聯的元數據(通常是 POM 檔案)。驗證此項可確保最高級別的安全性:元數據檔案通常會告知將包含哪些傳遞依賴項,因此受損的元數據檔案可能會導致在圖形中引入不需要的依賴項。但是,由於所有工件都經過驗證,因此您通常很容易發現此類工件,因為它們會導致校驗和驗證失敗(校驗和將從驗證元數據中遺失)。由於元數據驗證可能會顯著增加配置檔案的大小,因此您可能需要停用元數據驗證。如果您了解這樣做的風險,請在配置檔案中將 <verify-metadata>
標誌設定為 false
<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
<configuration>
<verify-metadata>false</verify-metadata>
<verify-signatures>false</verify-signatures>
</configuration>
<!-- the rest of this file doesn't need to declare anything about metadata files -->
</verification-metadata>
驗證依賴項校驗和
校驗和驗證允許您確保工件的完整性。這是 Gradle 可以為您做的最簡單的事情,以確保您使用的工件未被竄改。
Gradle 支援 MD5、SHA1、SHA-256 和 SHA-512 校驗和。但是,如今只有 SHA-256 和 SHA-512 校驗和被認為是安全的。
為工件新增校驗和
外部組件由 GAV 坐標標識,然後每個工件由其檔案名稱標識。要聲明工件的校驗和,您需要在驗證元數據檔案中新增相應的章節。例如,聲明 Apache PDFBox 的校驗和。GAV 坐標為
-
群組
org.apache.pdfbox
-
名稱
pdfbox
-
版本
2.0.17
使用此依賴項將觸發下載 2 個不同的檔案
-
pdfbox-2.0.17.jar
,這是主要工件 -
pdfbox-2.0.17.pom
,這是與此工件相關聯的元數據檔案
因此,您需要聲明它們兩者的校驗和(除非您停用了元數據驗證)
<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
<configuration>
<verify-metadata>true</verify-metadata>
<verify-signatures>false</verify-signatures>
</configuration>
<components>
<component group="org.apache.pdfbox" name="pdfbox" version="2.0.17">
<artifact name="pdfbox-2.0.17.jar">
<sha512 value="7e11e54a21c395d461e59552e88b0de0ebaf1bf9d9bcacadf17b240d9bbc29bf6beb8e36896c186fe405d287f5d517b02c89381aa0fcc5e0aa5814e44f0ab331" origin="PDFBox Official site (https://pdfbox.apache.org/download.cgi)"/>
</artifact>
<artifact name="pdfbox-2.0.17.pom">
<sha512 value="82de436b38faf6121d8d2e71dda06e79296fc0f7bc7aba0766728c8d306fd1b0684b5379c18808ca724bf91707277eba81eb4fe19518e99e8f2a56459b79742f" origin="Generated by Gradle"/>
</artifact>
</component>
</components>
</verification-metadata>
從哪裡獲取校驗和?
一般來說,校驗和與工件一起發布在公共儲存庫上。但是,如果儲存庫中的依賴項受到損害,則其校驗和也可能受到損害,因此最好從不同的地方獲取校驗和,通常是函式庫本身的網站。
事實上,將工件的校驗和發布在與工件本身託管的伺服器不同的伺服器上是一種良好的安全措施:在儲存庫和官方網站上同時損害函式庫更加困難。
在上面的範例中,校驗和發布在 JAR 的網站上,但未發布在 POM 檔案上。這就是為什麼通常更容易讓 Gradle 產生校驗和並通過仔細審查產生的檔案來驗證的原因。
在此範例中,我們不僅可以檢查校驗和是否正確,還可以從官方網站上找到它,這就是為什麼我們將 sha512
元素上 origin
屬性的值從 Generated by Gradle
更改為 PDFBox Official site
。更改 origin
可以讓用戶了解您的建置有多值得信賴。
有趣的是,使用 pdfbox
將需要比這 2 個工件更多的東西,因為它還將引入傳遞依賴項。如果依賴項驗證檔案僅包含您使用的主要工件的校驗和,則建置將失敗,並顯示如下錯誤
Execution failed for task ':compileJava'. > Dependency verification failed for configuration ':compileClasspath': - On artifact commons-logging-1.2.jar (commons-logging:commons-logging:1.2) in repository 'MavenRepo': checksum is missing from verification metadata. - On artifact commons-logging-1.2.pom (commons-logging:commons-logging:1.2) in repository 'MavenRepo': checksum is missing from verification metadata.
這表示您的建置在執行 compileJava
時需要 commons-logging
,但是驗證檔案不包含足夠的資訊供 Gradle 驗證依賴項的完整性,這意味著您需要將所需的資訊新增到驗證元數據檔案中。
請參閱疑難排解依賴項驗證以獲取關於在此情況下應如何操作的更多見解。
驗證哪些校驗和?
如果依賴項驗證元數據檔案為一個依賴項聲明了多個校驗和,Gradle 將驗證所有這些校驗和,並且如果其中任何一個失敗,則建置將失敗。例如,以下配置將同時檢查 md5
和 sha1
校驗和
<component group="org.apache.pdfbox" name="pdfbox" version="2.0.17">
<artifact name="pdfbox-2.0.17.jar">
<md5 value="c713a8e252d0add65e9282b151adf6b4" origin="official site"/>
<sha1 value="b5c8dff799bd967c70ccae75e6972327ae640d35" origin="official site" reason="Additional check for this artifact"/>
</artifact>
</component>
您可能想要這樣做的原因有很多
-
官方網站不發布安全的校驗和(SHA-256、SHA-512),但發布了多個不安全的校驗和(MD5、SHA1)。雖然偽造 MD5 校驗和很容易,而偽造 SHA1 校驗和很難但有可能,但為同一個工件偽造它們兩者更難。
-
您可能想要將產生的校驗和新增到上面的列表中
-
當使用更安全的校驗和更新依賴項驗證檔案時,您不想意外刪除校驗和
驗證依賴項簽章
除了校驗和之外,Gradle 還支援簽章驗證。簽章用於評估依賴項的來源(它告訴誰簽署了工件,通常對應於誰生產了它)。
由於啟用簽章驗證通常意味著更高的安全級別,因此您可能想要用簽章驗證替換校驗和驗證。
簽章也可以像校驗和一樣用於評估依賴項的完整性。簽章是工件雜湊的簽章,而不是工件本身。這意味著,如果簽章是在不安全的雜湊(甚至是 SHA1)上完成的,那麼您就沒有正確評估檔案的完整性。因此,如果您同時關心兩者,則需要將簽章和校驗和都新增到您的驗證元數據中。 |
但是
-
Gradle 僅支援驗證發布在遠端儲存庫上的簽章,格式為 ASCII 裝甲 PGP 檔案
-
並非所有工件都隨附簽章發布
-
良好的簽章並不意味著簽署人是合法的
因此,簽章驗證通常將與校驗和驗證一起使用。
找到使用過期金鑰簽署的工件非常常見。這對於驗證不是問題:金鑰過期主要用於避免使用被盜的金鑰進行簽署。如果工件在過期之前簽署,則仍然有效。
啟用簽章驗證
由於驗證簽章更昂貴(無論是 I/O 還是 CPU 方面),並且更難以手動檢查,因此預設情況下未啟用它。
啟用它需要您變更 verification-metadata.xml
檔案中的配置選項
<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
<configuration>
<verify-signatures>true</verify-signatures>
</configuration>
</verification-metadata>
理解簽章驗證
一旦啟用簽章驗證,對於每個工件,Gradle 將會
-
嘗試下載對應的
.asc
檔案 -
如果它存在
-
自動下載執行簽章驗證所需的金鑰
-
使用下載的公鑰驗證工件
-
如果簽章驗證通過,則執行其他請求的校驗和驗證
-
-
如果它不存在,則回退到校驗和驗證
也就是說,如果啟用簽章驗證,Gradle 的驗證機制將比僅使用校驗和驗證強得多。特別是
-
如果工件使用多個金鑰簽署,則所有金鑰都必須通過驗證,否則建置將失敗
-
如果工件通過驗證,則也將檢查為工件配置的任何其他校驗和
但是,並不是因為工件通過簽章驗證就意味著您可以信任它:您需要信任金鑰。
實際上,這意味著您需要列出您信任的每個工件的金鑰,這可以通過新增 pgp
條目而不是 sha1
條目來完成,例如
<component group="com.github.javaparser" name="javaparser-core" version="3.6.11">
<artifact name="javaparser-core-3.6.11.jar">
<pgp value="8756c4f765c9ac3cb6b85d62379ce192d401ab61"/>
</artifact>
</component>
對於 目前,V4 金鑰指紋的長度為 160 位元(40 個字元)。我們接受更長的金鑰,以在引入更長的金鑰指紋的情況下實現未來證明。 在 |
這實際上意味著,如果 com.github.javaparser:javaparser-core:3.6.11
是使用金鑰 8756c4f765c9ac3cb6b85d62379ce192d401ab61
簽署的,則您信任它。
如果沒有這個,建置將失敗並顯示此錯誤
> Dependency verification failed for configuration ':compileClasspath': - On artifact javaparser-core-3.6.11.jar (com.github.javaparser:javaparser-core:3.6.11) in repository 'MavenRepo': Artifact was signed with key '8756c4f765c9ac3cb6b85d62379ce192d401ab61' (Bintray (by JFrog) <****>) and passed verification but the key isn't in your trusted keys list.
Gradle 在錯誤訊息中顯示的金鑰 ID 是它嘗試驗證的簽章檔案中找到的金鑰 ID。這並不意味著它一定是您應該信任的金鑰。特別是,如果簽章是正確的,但由惡意實體完成,Gradle 也不會告訴您。 |
全域信任金鑰
簽章驗證的優勢在於,它可以使依賴項驗證的配置更容易,而無需像僅進行校驗和驗證那樣顯式列出所有工件。事實上,同一個金鑰通常可以用於簽署多個工件。如果是這種情況,您可以將受信任的金鑰從工件級別移動到全域配置塊
<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
<configuration>
<verify-metadata>true</verify-metadata>
<verify-signatures>true</verify-signatures>
<trusted-keys>
<trusted-key id="8756c4f765c9ac3cb6b85d62379ce192d401ab61" group="com.github.javaparser"/>
</trusted-keys>
</configuration>
<components/>
</verification-metadata>
上面的配置意味著,對於屬於群組 com.github.javaparser
的任何工件,如果它使用 8756c4f765c9ac3cb6b85d62379ce192d401ab61
指紋簽署,我們就信任它。
trusted-key
元素的工作方式與trusted-artifact 元素類似
-
group
,要信任的工件的群組 -
name
,要信任的工件的名稱 -
version
,要信任的工件的版本 -
file
,要信任的工件檔案的名稱 -
regex
,一個布林值,表示是否需要將group
、name
、version
和file
屬性解釋為正則表達式(預設為false
)
全域信任金鑰時應謹慎。
嘗試將其限制在適當的群組或工件
-
有效的金鑰可能已用於簽署您信任的工件
A
-
稍後,金鑰被盜並用於簽署工件
B
這意味著您可以信任第一個工件的金鑰 A
,可能僅限於在金鑰被盜之前發布的版本,但不適用於 B
。
請記住,任何人在產生 PGP 金鑰時都可以放置任意名稱,因此永遠不要僅根據金鑰名稱來信任金鑰。驗證金鑰是否在官方網站上列出。例如,Apache 專案通常提供您可以信任的 KEYS.txt 檔案。
指定金鑰伺服器並忽略金鑰
Gradle 將自動下載驗證簽章所需的公鑰。為此,它使用眾所周知且受信任的金鑰伺服器列表(列表可能在 Gradle 版本之間變更,請參閱實作以了解預設使用哪些伺服器)。
您可以通過將它們新增到配置中來顯式設定您想要使用的金鑰伺服器列表
<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
<configuration>
<verify-metadata>true</verify-metadata>
<verify-signatures>true</verify-signatures>
<key-servers>
<key-server uri="hkp://my-key-server.org"/>
<key-server uri="https://my-other-key-server.org"/>
</key-servers>
</configuration>
</verification-metadata>
儘管如此,金鑰仍有可能不可用
-
因為它未發布到公鑰伺服器
-
因為它已遺失
在這種情況下,您可以在配置塊中忽略金鑰
<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
<configuration>
<verify-metadata>true</verify-metadata>
<verify-signatures>true</verify-signatures>
<ignored-keys>
<ignored-key id="abcdef1234567890" reason="Key is not available in any key server"/>
</ignored-keys>
</configuration>
</verification-metadata>
一旦金鑰被忽略,即使簽章檔案中提到了它,也不會將其用於驗證。但是,如果簽章無法使用至少另一個金鑰進行驗證,Gradle 將強制您提供校驗和。
如果 Gradle 在引導時無法下載金鑰,它會將其標記為已忽略。如果您可以找到金鑰但 Gradle 沒有找到,您可以手動將其新增到金鑰環檔案。 |
匯出金鑰以加快驗證速度
Gradle 自動下載所需的金鑰,但此操作可能非常緩慢,並且需要每個人都下載金鑰。為避免這種情況,Gradle 提供了使用包含所需公鑰的本地金鑰環檔案的功能。請注意,僅儲存和使用公鑰數據包和每個金鑰的單個用戶 ID。所有其他資訊(用戶屬性、簽章等)都從下載或匯出的金鑰中剝離。
Gradle 支援 2 種不同的金鑰環檔案格式:二進制格式(.gpg
檔案)和純文本格式(.keys
),也稱為 ASCII 裝甲格式。
每種格式都有優缺點:二進制格式更緊湊,可以直接通過 GPG 命令更新,但完全不透明(二進制)。相反,ASCII 裝甲格式是人類可讀的,可以通過手動輕鬆更新,並且由於可讀的差異,因此更容易進行代碼審查。
您可以通過新增 keyring-format
配置選項來配置將使用哪種檔案類型
<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
<configuration>
<verify-metadata>true</verify-metadata>
<verify-signatures>true</verify-signatures>
<keyring-format>armored</keyring-format>
</configuration>
</verification-metadata>
金鑰環格式的可用選項為 armored
和 binary
。
若沒有 keyring-format
,如果 gradle/verification-keyring.gpg
或 gradle/verification-keyring.keys
檔案存在,Gradle 會優先在這些檔案中搜尋金鑰。如果已經有 .gpg
檔案(二進位版本優先),則純文字檔案會被忽略。
您可以要求 Gradle 在啟動過程中,將用於驗證此建置的所有金鑰匯出到金鑰環
./gradlew --write-verification-metadata pgp,sha256 --export-keys
除非指定 keyring-format
,否則此命令將產生二進位版本和 ASCII 裝甲檔案。使用此選項選擇偏好的格式。您的專案應該只選擇一種格式。
將此檔案提交到 VCS 是個好主意(只要您信任您的 VCS)。如果您使用 git 並且使用二進位版本,請務必將此檔案視為二進位檔案,方法是將此行加入您的 .gitattributes
檔案
*.gpg binary
您也可以要求 Gradle 匯出所有受信任的金鑰,而無需更新驗證中繼資料檔案
./gradlew --export-keys
此命令不會報告驗證錯誤,只會匯出金鑰。 |
啟動和簽章驗證
簽章驗證啟動採取樂觀的觀點,認為簽章驗證就已足夠。因此,如果您也關心完整性,您必須先使用檢查總和驗證進行啟動,然後再使用簽章驗證。 |
與檢查總和的啟動類似,Gradle 提供了一個便利的方式,用於啟動已啟用簽章驗證的組態檔。為此,只需將 pgp
選項添加到要產生的驗證清單中。但是,由於可能存在驗證失敗、金鑰遺失或簽章檔案遺失的情況,您必須提供備用的檢查總和驗證演算法
./gradlew --write-verification-metadata pgp,sha256
這表示當出現問題時,Gradle 將驗證簽章並回退到 SHA-256 檢查總和。
啟動時,Gradle 執行樂觀驗證,因此假設建置環境健全。因此,它將會
-
在驗證通過後立即自動新增受信任的金鑰
-
對於無法從公鑰伺服器下載的金鑰,自動新增忽略的金鑰。請參閱此處,了解如何在需要時手動新增金鑰
-
自動為沒有簽章或忽略金鑰的工件產生檢查總和
如果由於某些原因,在產生過程中驗證失敗,Gradle 將自動產生一個忽略的金鑰條目,但會警告您必須絕對檢查發生了什麼事。
這種情況很常見,如本節所述:一個典型的情況是,相依性的 POM 檔案在不同的儲存庫中有所不同(通常以非實質性的方式)。
此外,Gradle 將嘗試自動將金鑰分組,並產生 trusted-keys
區塊,以盡可能縮減組態檔的大小。
強制僅使用本地金鑰環
本地金鑰環檔案(.gpg
或 .keys
)可用於避免在需要金鑰來驗證工件時,連線到金鑰伺服器。但是,本地金鑰環可能不包含金鑰,在這種情況下,Gradle 將使用金鑰伺服器來獲取遺失的金鑰。如果本地金鑰環檔案沒有定期更新(使用金鑰匯出),那麼例如,您的 CI 建置可能會過於頻繁地連線到金鑰伺服器(特別是如果您使用拋棄式容器進行建置)。
為了避免這種情況,Gradle 提供了完全禁止使用金鑰伺服器的功能:只會使用本地金鑰環檔案,如果此檔案中缺少金鑰,建置將會失敗。
若要啟用此模式,您需要在組態檔中停用金鑰伺服器
<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
<configuration>
<key-servers enabled="false"/>
...
</configuration>
...
</verification-metadata>
如果您要求 Gradle 產生驗證中繼資料檔案,並且現有的驗證中繼資料檔案將 enabled 設定為 false ,則此旗標將被忽略,以便下載可能遺失的金鑰。 |
停用驗證或使其寬鬆
相依性驗證可能很耗費資源,或者有時驗證可能會妨礙日常開發(例如,由於頻繁的相依性升級)。
或者,您可能希望在 CI 伺服器上啟用驗證,但在本地機器上不啟用。
Gradle 實際上提供了 3 種不同的驗證模式
-
strict
,這是預設模式。驗證會盡可能提早失敗,以避免在建置期間使用受損的相依性。 -
lenient
,即使存在驗證失敗,也會執行建置。驗證錯誤將在建置期間顯示,但不會導致建置失敗。 -
off
,當完全忽略驗證時。
所有這些模式都可以在 CLI 上使用 --dependency-verification
旗標啟動,例如
./gradlew --dependency-verification lenient build
或者,您可以在 CLI 上設定 org.gradle.dependency.verification
系統屬性
./gradlew -Dorg.gradle.dependency.verification=lenient build
或在 gradle.properties
檔案中設定
org.gradle.dependency.verification=lenient
僅針對某些組態停用相依性驗證
為了提供最強大的安全級別,相依性驗證是全域啟用的。這將確保,例如,您信任您使用的所有外掛程式。但是,外掛程式本身可能需要解析額外的相依性,要求使用者接受這些相依性沒有意義。為此,Gradle 提供了一個 API,允許在某些特定組態上停用相依性驗證。
如果您關心安全性,停用相依性驗證並不是一個好主意。此 API 主要存在於檢查相依性沒有意義的情況下。但是,為了安全起見,每當特定組態停用驗證時,Gradle 都會系統地列印警告。 |
例如,外掛程式可能想要檢查是否有較新版本的程式庫可用,並列出這些版本。在這種情況下,要求使用者放入較新版本的 POM 檔案的檢查總和是沒有意義的,因為根據定義,他們不知道這些檔案。因此,外掛程式可能需要獨立於相依性驗證組態執行其程式碼。
若要執行此操作,您需要呼叫 ResolutionStrategy#disableDependencyVerification
方法
configurations {
"myPluginClasspath" {
resolutionStrategy {
disableDependencyVerification()
}
}
}
configurations {
myPluginClasspath {
resolutionStrategy {
disableDependencyVerification()
}
}
}
也可以在分離的組態上停用驗證,如下列範例所示
tasks.register("checkDetachedDependencies") {
val detachedConf: FileCollection = configurations.detachedConfiguration(dependencies.create("org.apache.commons:commons-lang3:3.3.1")).apply {
resolutionStrategy.disableDependencyVerification()
}
doLast {
println(detachedConf.files)
}
}
tasks.register("checkDetachedDependencies") {
def detachedConf = configurations.detachedConfiguration(dependencies.create("org.apache.commons:commons-lang3:3.3.1"))
detachedConf.resolutionStrategy.disableDependencyVerification()
doLast {
println(detachedConf.files)
}
}
信任某些特定的工件
您可能想要比其他工件更信任某些工件。例如,認為在您公司內部產生並僅在您的內部儲存庫中找到的工件是安全的,但您想要檢查每個外部元件,這是合理的。
這是一種典型的公司政策。實際上,沒有任何東西可以阻止您的內部儲存庫被入侵,因此檢查您的內部工件也是一個好主意! |
為此,Gradle 提供了一種自動信任某些工件的方法。您可以透過將此行新增到您的組態來信任群組中的所有工件
<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
<configuration>
<trusted-artifacts>
<trust group="com.mycompany" reason="We trust mycompany artifacts"/>
</trusted-artifacts>
</configuration>
</verification-metadata>
這表示群組為 com.mycompany
的所有元件將自動被信任。受信任表示 Gradle 將不會執行任何驗證。
trust
元素接受以下屬性
-
group
,要信任的工件的群組 -
name
,要信任的工件的名稱 -
version
,要信任的工件的版本 -
file
,要信任的工件檔案的名稱 -
regex
,一個布林值,表示是否需要將group
、name
、version
和file
屬性解釋為正則表達式(預設為false
) -
reason
,一個可選的原因,說明為什麼信任符合條件的工件
在上面的範例中,這表示受信任的工件將是 com.mycompany
中的工件,而不是 com.mycompany.other
。若要信任 com.mycompany
和所有子群組中的所有工件,您可以使用
<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
<configuration>
<trusted-artifacts>
<trust group="^com[.]mycompany($|([.].*))" regex="true" reason="We trust all mycompany artifacts"/>
</trusted-artifacts>
</configuration>
</verification-metadata>
信任工件的多個檢查總和
在實際應用中,對於相同的工件,有不同的檢查總和是很常見的。這怎麼可能?儘管取得了進展,但開發人員通常還是會分別發佈到 Maven Central 和另一個儲存庫,使用不同的建置。一般來說,這不是問題,但有時這表示中繼資料檔案會有所不同(不同的時間戳記、額外的空白字元等)。再加上您的建置可能會使用多個儲存庫或儲存庫鏡像,這使得單一建置「看到」相同元件的不同中繼資料檔案的可能性非常高!一般來說,這不是惡意的(但您必須驗證工件實際上是正確的),因此 Gradle 允許您宣告額外的工件檢查總和。例如
<component group="org.apache" name="apache" version="13">
<artifact name="apache-13.pom">
<sha256 value="2fafa38abefe1b40283016f506ba9e844bfcf18713497284264166a5dbf4b95e">
<also-trust value="ff513db0361fd41237bef4784968bc15aae478d4ec0a9496f811072ccaf3841d"/>
</sha256>
</artifact>
</component>
您可以根據需要擁有任意數量的 also-trust
條目,但一般來說,您不應該超過 2 個。
略過 Javadocs 和來源碼
預設情況下,Gradle 將驗證所有下載的工件,包括 Javadocs 和來源碼。一般來說,這不是問題,但您可能會遇到 IDE 在匯入期間自動嘗試下載它們的問題:如果您沒有也為這些工件設定檢查總和,匯入將會失敗。
為了避免這種情況,您可以將 Gradle 設定為自動信任所有 javadocs/來源碼
<trusted-artifacts>
<trust file=".*-javadoc[.]jar" regex="true"/>
<trust file=".*-sources[.]jar" regex="true"/>
</trusted-artifacts>
手動將金鑰新增到金鑰環
將金鑰新增到 ASCII 裝甲金鑰環
新增的金鑰必須採用 ASCII 裝甲格式,並且可以簡單地新增到檔案的末尾。如果您已經以正確的格式下載了金鑰,您可以簡單地將其附加到檔案中。
或者,您可以透過發出以下命令來修改現有的 KEYS 檔案
$ gpg --no-default-keyring --keyring /tmp/keyring.gpg --recv-keys 8756c4f765c9ac3cb6b85d62379ce192d401ab61
gpg: keybox '/tmp/keyring.gpg' created
gpg: key 379CE192D401AB61: public key "Bintray (by JFrog) <****>" imported
gpg: Total number processed: 1
gpg: imported: 1
# Write its ASCII-armored version
$ gpg --keyring /tmp/keyring.gpg --export --armor 8756c4f765c9ac3cb6b85d62379ce192d401ab61 > gradle/verification-keyring.keys
完成後,請務必再次執行產生命令,以便 Gradle 處理金鑰。這將執行以下操作
-
將標準標頭新增到金鑰
-
使用 Gradle 自己的格式重寫金鑰,該格式將金鑰修剪到最簡
-
將金鑰移動到其排序的位置,保持檔案的可重現性
將金鑰新增到二進位金鑰環
您可以使用 GPG 將金鑰新增到二進位版本,例如發出以下命令(語法可能因您使用的工具而異)
$ gpg --no-default-keyring --keyring gradle/verification-keyring.gpg --recv-keys 8756c4f765c9ac3cb6b85d62379ce192d401ab61
gpg: keybox 'gradle/verification-keyring.gpg' created
gpg: key 379CE192D401AB61: public key "Bintray (by JFrog) <****>" imported
gpg: Total number processed: 1
gpg: imported: 1
$ gpg --no-default-keyring --keyring gradle/verification-keyring.gpg --recv-keys 6f538074ccebf35f28af9b066a0975f8b1127b83
gpg: key 0729A0AFF8999A87: public key "Kotlin Release <****>" imported
gpg: Total number processed: 1
gpg: imported: 1
處理驗證失敗
相依性驗證可能會以不同的方式失敗,本節說明您應該如何處理各種情況。
缺少驗證中繼資料
您可能遇到的最簡單的失敗是當驗證中繼資料從相依性驗證檔案中遺失時。例如,如果您使用檢查總和驗證,然後您更新相依性,並且引入了新版本的相依性(以及可能其傳遞相依性),就會發生這種情況。
Gradle 會告訴您遺失了哪些中繼資料
Execution failed for task ':compileJava'. > Dependency verification failed for configuration ':compileClasspath': - On artifact commons-logging-1.2.jar (commons-logging:commons-logging:1.2) in repository 'MavenRepo': checksum is missing from verification metadata.
-
遺失的模組群組是
commons-logging
,其工件名稱是commons-logging
,其版本是1.2
。對應的工件是commons-logging-1.2.jar
,因此您需要將以下條目新增到驗證檔案中
<component group="commons-logging" name="commons-logging" version="1.2">
<artifact name="commons-logging-1.2.jar">
<sha256 value="daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636" origin="official distribution"/>
</artifact>
</component>
或者,您可以使用啟動機制要求 Gradle 產生遺失的資訊:中繼資料檔案中的現有資訊將被保留,Gradle 只會新增遺失的驗證中繼資料。
檢查總和不正確
更棘手的問題是當實際的檢查總和驗證失敗時
Execution failed for task ':compileJava'. > Dependency verification failed for configuration ':compileClasspath': - On artifact commons-logging-1.2.jar (commons-logging:commons-logging:1.2) in repository 'MavenRepo': expected a 'sha256' checksum of '91f7a33096ea69bac2cbaf6d01feb934cac002c48d8c8cfa9c240b40f1ec21df' but was 'daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636'
這次,Gradle 會告訴您哪個相依性出現問題、預期的檢查總和(您在驗證中繼資料檔案中宣告的檢查總和)以及在驗證期間實際計算出的檢查總和。
這種失敗表示相依性可能已被入侵。在此階段,您必須執行手動驗證並檢查發生了什麼事。可能會發生幾件事
-
相依性在 Gradle 的本地相依性快取中被篡改。這通常是無害的:從快取中刪除該檔案,Gradle 將重新下載相依性。
-
相依性在多個來源中可用,但二進位檔案略有不同(額外的空白字元等)
-
請告知程式庫的維護者他們有這樣的問題
-
您可以使用
also-trust
來接受額外的檢查總和
-
-
相依性已被入侵
-
立即告知程式庫的維護者
-
通知受入侵程式庫的儲存庫維護者
-
請注意,受入侵程式庫的一種變體通常是名稱搶佔,即駭客會使用看起來合法但實際上相差一個字元的 GAV 座標,或是儲存庫陰影,即在您的建置中優先出現的惡意儲存庫中發佈具有官方 GAV 座標的相依性。
不受信任的簽章
如果您已啟用簽章驗證,Gradle 將執行簽章驗證,但不會自動信任它們
> Dependency verification failed for configuration ':compileClasspath': - On artifact javaparser-core-3.6.11.jar (com.github.javaparser:javaparser-core:3.6.11) in repository 'MavenRepo': Artifact was signed with key '379ce192d401ab61' (Bintray (by JFrog) <****>) and passed verification but the key isn't in your trusted keys list.
在這種情況下,這表示您需要自行檢查用於驗證的金鑰(以及簽章)是否可以信任,在這種情況下,請參閱文件的此章節,以了解如何宣告受信任的金鑰。
簽章驗證失敗
如果 Gradle 無法驗證簽章,您將需要採取行動並手動驗證工件,因為這可能表示相依性已受損。
如果發生這種情況,Gradle 將會失敗,並顯示
> Dependency verification failed for configuration ':compileClasspath': - On artifact javaparser-core-3.6.11.jar (com.github.javaparser:javaparser-core:3.6.11) in repository 'MavenRepo': Artifact was signed with key '379ce192d401ab61' (Bintray (by JFrog) <****>) but signature didn't match
有幾個選項
-
簽章一開始就錯誤,這在使用在不同儲存庫上發佈的相依性時經常發生。
-
簽章正確,但工件已被入侵(無論是在本地相依性快取中還是在遠端)
這裡正確的方法是前往相依性的官方網站,查看他們是否發佈了其工件的簽章。如果他們這樣做,請驗證 Gradle 下載的簽章是否與發佈的簽章相符。
如果您已檢查過相依性未受損,並且「僅」簽章錯誤,您應該宣告工件級金鑰排除
<components>
<component group="com.github.javaparser" name="javaparser-core" version="3.6.11">
<artifact name="javaparser-core-3.6.11.pom">
<ignored-keys>
<ignored-key id="379ce192d401ab61" reason="internal repo has corrupted POM"/>
</ignored-keys>
</artifact>
</component>
</components>
但是,如果您僅這樣做,Gradle 仍然會失敗,因為此工件的所有金鑰都將被忽略,並且您沒有提供檢查總和
<components>
<component group="com.github.javaparser" name="javaparser-core" version="3.6.11">
<artifact name="javaparser-core-3.6.11.pom">
<ignored-keys>
<ignored-key id="379ce192d401ab61" reason="internal repo has corrupted POM"/>
</ignored-keys>
<sha256 value="a2023504cfd611332177f96358b6f6db26e43d96e8ef4cff59b0f5a2bee3c1e1"/>
</artifact>
</component>
</components>
手動驗證相依性
您可能會遇到相依性驗證失敗(檢查總和驗證或簽章驗證),並且需要弄清楚相依性是否已受損。
在本節中,我們提供一個範例,說明如何手動檢查相依性是否已受損。
為此,我們將採用此範例失敗
> Dependency verification failed for configuration ':compileClasspath': - On artifact j2objc-annotations-1.1.jar (com.google.j2objc:j2objc-annotations:1.1) in repository 'MyCompany Mirror': Artifact was signed with key '29579f18fa8fd93b' but signature didn't match
此錯誤訊息為我們提供了有問題相依性的 GAV 座標,以及相依性從何處獲取的指示。在這裡,相依性來自 MyCompany Mirror
,這是我們建置中宣告的儲存庫。
因此,要做的第一件事是從鏡像手動下載工件及其簽章
$ curl https://my-company-mirror.com/repo/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar --output j2objc-annotations-1.1.jar $ curl https://my-company-mirror.com/repo/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar.asc --output j2objc-annotations-1.1.jar.asc
然後,我們可以使用錯誤訊息中提供的金鑰資訊在本地匯入金鑰
$ gpg --recv-keys B801E2F8EF035068EC1139CC29579F18FA8FD93B
並執行驗證
$ gpg --verify j2objc-annotations-1.1.jar.asc gpg: assuming signed data in 'j2objc-annotations-1.1.jar' gpg: Signature made Thu 19 Jan 2017 12:06:51 AM CET gpg: using RSA key 29579F18FA8FD93B gpg: BAD signature from "Tom Ball <****>" [unknown]
這告訴我們問題不在本地機器上:儲存庫已經包含錯誤的簽章。
下一步是透過下載 Maven Central 上的實際內容來執行相同的操作
$ curl https://my-company-mirror.com/repo/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar --output central-j2objc-annotations-1.1.jar $ curl https://my-company-mirror.com/repo/com/google/j2objc/j2objc-annotations/1/1/j2objc-annotations-1.1.jar.asc --output central-j2objc-annotations-1.1.jar.asc
現在我們可以再次檢查簽章
$ gpg --verify central-j2objc-annotations-1.1.jar.asc gpg: assuming signed data in 'central-j2objc-annotations-1.1.jar' gpg: Signature made Thu 19 Jan 2017 12:06:51 AM CET gpg: using RSA key 29579F18FA8FD93B gpg: Good signature from "Tom Ball <****>" [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: B801 E2F8 EF03 5068 EC11 39CC 2957 9F18 FA8F D93B
這表示相依性在 Maven Central 上是有效的。在此階段,我們已經知道問題出在鏡像中,它可能已被入侵,但我們需要驗證。
一個好主意是比較這 2 個工件,您可以使用像 diffoscope 這樣的工具來完成。
然後我們發現意圖不是惡意的,而是某種程度上建置已被較新版本覆蓋(Central 中的版本比我們儲存庫中的版本新)。
在這種情況下,您可以決定
-
忽略此工件的簽章,並信任不同的可能檢查總和(對於舊工件和新版本)
-
或清理您的鏡像,使其包含與 Maven Central 中相同的版本
值得注意的是,如果您選擇從您的儲存庫中刪除該版本,您也需要從本地 Gradle 快取中移除它。
錯誤訊息告訴您檔案的位置,這有助於執行此操作
> Dependency verification failed for configuration ':compileClasspath': - On artifact j2objc-annotations-1.1.jar (com.google.j2objc:j2objc-annotations:1.1) in repository 'MyCompany Mirror': Artifact was signed with key '29579f18fa8fd93b' but signature didn't match This can indicate that a dependency has been compromised. Please carefully verify the signatures and checksums. For your information here are the path to the files which failed verification: - $<<directory_layout.adoc#dir:gradle_user_home,GRADLE_USER_HOME>>/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/976d8d30bebc251db406f2bdb3eb01962b5685b3/j2objc-annotations-1.1.jar (signature: GRADLE_USER_HOME/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/82e922e14f57d522de465fd144ec26eb7da44501/j2objc-annotations-1.1.jar.asc) GRADLE_USER_HOME = /home/jiraya/.gradle
您可以安全地刪除工件檔案,因為 Gradle 會自動重新下載它
rm -rf ~/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1
清理驗證檔案
如果您什麼都不做,相依性驗證中繼資料會隨著時間的推移而增長,因為您新增新的相依性或變更版本:Gradle 不會自動從此檔案中移除未使用的條目。原因是 Gradle 無法預先知道在建置期間是否會實際使用相依性。
因此,新增相依性或變更相依性版本很容易導致檔案中出現更多條目,同時將不必要的條目留在那裡。
清理檔案的一個選項是將現有的 verification-metadata.xml
檔案移動到不同的位置,並使用 --dry-run
模式呼叫 Gradle:雖然不完美(它不會注意到僅在組態時解析的相依性),但它會產生一個新檔案,您可以將其與現有檔案進行比較。
我們需要移動現有檔案,因為啟動模式和 dry-run 模式都是增量的:它們從現有的中繼資料驗證檔案中複製資訊(特別是受信任的金鑰)。