View a markdown version of this page

版本存留期的閘道套件 - CodeArtifact

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

版本存留期的閘道套件

重要

版本存留期閘道無法保護已通過隔離時段的現有版本中發現的漏洞。將 CodeArtifact 與 Amazon Inspector 搭配使用,以漏洞掃描已建立的套件。

當套件版本發佈至公有登錄檔時,登錄檔會記錄發佈時間戳記。CodeArtifact 會在從外部連線快取套件時保留這些原始上游時間戳記。您可以在 CI/CD 管道中讀取這些時間戳記,並拒絕過早發佈的版本。

大多數惡意套件都會在發佈後數小時內偵測到並移除。3 到 7 天的隔離時段會封鎖大多數供應鏈攻擊,包括相依性混淆、維護者帳戶接管和錯別字。

注意

本主題中的範例會查詢 CodeArtifact 儲存庫 URL,這是套件管理員用來下載套件的相同端點。所有請求都需要身分驗證。在 Authorization: Bearer標頭中傳遞有效的 CodeArtifact 授權字符。若要取得權杖,請執行 aws codeartifact get-authorization-token(請參閱本主題稍後的 CI/CD 範例,了解使用 --domain和 的完整調用--domain-owner)。

請勿使用 傳回publishedTime的欄位aws codeartifact describe-package-version進行年齡調整。此欄位會在擷取時間填入。對於未擷取原始上游發佈時間的記錄 (包括引入每個成品時間戳記擷取之前擷取的套件,以及某些擷取路徑),它會無提示地回到記錄的上次更新時間。API 回應不會指出您正在讀取的兩個值中,因此您無法將其視為上游發佈日期。請改用本主題中描述的每個格式欄位。

每個格式的詳細資訊不同,但每種格式的閘道模式都相同:讀取每個已解析版本的上游發佈時間戳記、將其與隔離截止值 (目前時間減去您選擇的時段) 進行比較,如果任何版本比截止值更新,則組建會失敗。如果您使用多個套件格式,您可以使用如下所示的格式特定欄位套用相同的模式。

npm

CodeArtifact 會傳回 npm 封裝time欄位中每個版本的原始 npmjs.org 發佈時間戳記。許多套件管理員和安全工具會以原生方式讀取此欄位。這些選項包括 Yarn 4 npmMinimalAgeGate選項、Renovate minimumReleaseAge設定和 pnpm minimumReleaseAge設定,所有這些都適用於 CodeArtifact,無需修改。

要求:

curl -H "Authorization: Bearer $CODEARTIFACT_AUTH_TOKEN" \ https://my-domain-111122223333.d.codeartifact.us-east-1.amazonaws.com/npm/my-repo/axios

回應 (縮寫):

{ "name": "axios", "time": { "1.6.0": "2023-10-26T21:15:55.685Z", "1.6.5": "2024-01-05T19:52:15.051Z", "1.7.0": "2024-05-19T20:25:03.615Z", "1.7.1": "2024-05-20T13:32:52.757Z", "1.7.2": "2024-05-21T16:58:04.163Z" } }

金鑰欄位:time["<version>"]— 值是來自 npmjs.org 的原始發佈日期。例如, time["1.7.2"]會傳回 2024-05-21T16:58:04.163Z,這是發佈該版本時 npmjs.org 記錄的確切時間戳記。若要在存留期時進入閘道,請將此值與目前時間減去隔離時段進行比較。

Maven

對於 Maven 成品,CodeArtifact 會在您下載檔案 (JAR、POM 或其他成品) 時,傳回 Last-Modified HTTP 回應標頭中的原始 Maven Central 發佈時間戳記。

要求:

curl -I -H "Authorization: Bearer $CODEARTIFACT_AUTH_TOKEN" \ https://my-domain-111122223333.d.codeartifact.us-east-1.amazonaws.com/maven/my-repo/\ org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.pom

回應標頭:

HTTP/2 200 content-type: application/xml Last-Modified: Fri, 26 Feb 2021 20:40:52 GMT content-length: 22672

金鑰欄位:Last-Modified標頭 — 這是 Maven Central 的原始發佈日期。將其與您的隔離截止值進行比較,以判斷成品是否太新。

注意

內的 <lastUpdated> 欄位maven-metadata.xml反映 CodeArtifact 上次重新整理其快取的時間,而不是上游發佈日期。請勿使用maven-metadata.xml時間戳記進行年齡調整。

NuGet

對於 NuGet 套件,CodeArtifact 會在 V3 註冊回應的 published 欄位中傳回原始 nuget.org 發佈時間戳記。

要求:

curl -H "Authorization: Bearer $CODEARTIFACT_AUTH_TOKEN" \ https://my-domain-111122223333.d.codeartifact.us-east-1.amazonaws.com/nuget/my-repo/\ v3/registration4/newtonsoft.json/index.json

回應 (已縮寫,顯示一個版本):

{ "items": [{ "items": [{ "catalogEntry": { "id": "Newtonsoft.Json", "version": "13.0.3", "published": "2023-03-08T07:42:54.647+00:00", "listed": true } }] }] }

金鑰欄位:catalogEntry.published— 這是來自 nuget.org 的原始發佈日期。例如, "2023-03-08T07:42:54.647+00:00"表示 Newtonsoft.Json 13.0.3 已於 2023 年 3 月 8 日發佈。

Cargo (Rust)

對於從 crates.io 提取的貨運箱,CodeArtifact 會在 crates.io V1 API versions回應中保留原始發佈時間戳記。查詢 V1 API 端點以取得木箱,以讀取每個版本的created_at時間戳記。

要求:

curl -H "Authorization: Bearer $CODEARTIFACT_AUTH_TOKEN" \ https://my-domain-111122223333.d.codeartifact.us-east-1.amazonaws.com/cargo/my-repo/\ api/v1/crates/serde/versions

金鑰欄位:V1 versions回應中的每個版本created_at時間戳記是原始 crates.io 發佈日期。

注意

Cargo 使用稀疏索引通訊協定 (<prefix>/<crate>) 進行正常相依性解析。稀疏索引會傳回features每個版本的 versyankedcksum和 ,但不包含發佈時間戳記。若要對存留期的貨物相依性進行閘道,請在管道中的單獨步驟中查詢上面顯示的 V1 API。標準 cargo buildcargo update命令不會呼叫 V1 API。

PyPI

對於 Python 套件,CodeArtifact 支援 PEP 691 (JSON Simple API) 和 PEP 700,其中包含每個分佈檔案的 upload-time 欄位。

要求:

curl -H "Authorization: Bearer $CODEARTIFACT_AUTH_TOKEN" \ -H "Accept: application/vnd.pypi.simple.v1+json" \ https://my-domain-111122223333.d.codeartifact.us-east-1.amazonaws.com/pypi/my-repo/simple/requests/

回應 (縮寫):

{ "files": [{ "filename": "requests-2.31.0-py3-none-any.whl", "upload-time": "2023-05-22T15:12:42.313790Z", "size": 62574, "url": "../../packages/requests/2.31.0/requests-2.31.0-py3-none-any.whl" }] }

金鑰欄位:upload-time— 這是 PyPI 的原始發佈日期。等工具原生uv支援此項目:

# Only install versions published before a specific date uv pip install --exclude-newer 2026-05-01T00:00:00Z -r requirements.txt
注意

PEP 691 JSON API 需要 Accept: application/vnd.pypi.simple.v1+json標頭。預設 HTML Simple API 不包含時間戳記。

對於引入每個成品上傳時間擷取之前擷取的分發檔案 (舊版記錄), upload-time 欄位可能沒有 中的項目files[]。您的門控邏輯應將遺失視為無法驗證upload-time的版本,並根據您的政策決定是否允許或封鎖它。

範例:在 CI/CD 中閘道 npm 套件

下列 Python 指令碼會讀取package-lock.json檔案、查詢 CodeArtifact 以取得每個相依性的發佈時間戳記,並在隔離視窗中發佈任何版本時以非零狀態結束。

#!/usr/bin/env python3 """ gate_package_age.py - Block npm packages published too recently. Reads package-lock.json, queries CodeArtifact for each dependency's publish timestamp, and rejects versions newer than QUARANTINE_HOURS. Environment variables: CODEARTIFACT_AUTH_TOKEN - from `aws codeartifact get-authorization-token` CODEARTIFACT_NPM_ENDPOINT - from `aws codeartifact get-repository-endpoint` QUARANTINE_HOURS - minimum version age in hours (default: 72) """ import json, os, sys, urllib.request from datetime import datetime, timezone, timedelta def get_publish_time(endpoint, token, package, version): """Fetch the publish timestamp for a specific version from CodeArtifact.""" url = f"{endpoint.rstrip('/')}/{package}" req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"}) data = json.loads(urllib.request.urlopen(req, timeout=30).read()) time_str = data.get("time", {}).get(version) if not time_str: return None return datetime.fromisoformat(time_str.replace("Z", "+00:00")) token = os.environ["CODEARTIFACT_AUTH_TOKEN"] endpoint = os.environ["CODEARTIFACT_NPM_ENDPOINT"] quarantine_hours = int(os.environ.get("QUARANTINE_HOURS", "72")) cutoff = datetime.now(timezone.utc) - timedelta(hours=quarantine_hours) with open("package-lock.json") as f: lockfile = json.load(f) blocked = [] checked = 0 for path, info in lockfile.get("packages", {}).items(): if not path.startswith("node_modules/"): continue name = path.replace("node_modules/", "", 1) version = info.get("version") if not name or not version: continue checked += 1 pub_time = get_publish_time(endpoint, token, name, version) if pub_time and pub_time > cutoff: age_h = (datetime.now(timezone.utc) - pub_time).total_seconds() / 3600 blocked.append(f" {name}@{version} (age: {age_h:.0f}h, published {pub_time.isoformat()})") if blocked: print(f"BLOCKED: {len(blocked)} package(s) newer than {quarantine_hours} hours:") print("\n".join(blocked)) sys.exit(1) print(f"PASSED: all {checked} packages older than {quarantine_hours} hours.")

CI/CD 整合:

# Step 1: Authenticate export CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token \ --domain my-domain --domain-owner 111122223333 \ --query authorizationToken --output text) export CODEARTIFACT_NPM_ENDPOINT=$(aws codeartifact get-repository-endpoint \ --domain my-domain --domain-owner 111122223333 \ --repository my-repo --format npm \ --query repositoryEndpoint --output text) # Step 2: Set quarantine window (72 hours = 3 days) export QUARANTINE_HOURS=72 # Step 3: Generate lockfile without installing npm install --package-lock-only # Step 4: Gate on age — fails if any dependency is too new python3 gate_package_age.py # Step 5: If gate passes, install npm ci

套件遭到封鎖時的輸出範例:

BLOCKED: 1 package(s) newer than 72 hours: evil-package@1.0.0 (age: 2h, published 2026-06-03T08:30:00+00:00)

所有套件通過時的範例輸出:

PASSED: all 847 packages older than 72 hours.

已緩解的攻擊

攻擊類型 運作方式 年齡調控如何有所幫助
相依性混淆 攻擊者會將與內部套件同名的高版本套件發佈至公有登錄檔。 惡意版本是全新的。被隔離時段封鎖。
帳戶接管 攻擊者入侵維護者的登入資料並發佈惡意更新 (例如,事件串流)。 保留新版本。社群在隔離期間偵測並還原它。
Typosquatting 攻擊者會發佈名稱類似於熱門套件的套件 (例如,lodashs而非 lodash)。 所有版本的 typosquat 套件都是新的。每個版本都會遭到封鎖。
Star-jacking 攻擊者將惡意軟體注入其控制之合法套件的修補程式版本中。 惡意修補程式是新發佈的版本。封鎖,直到超過隔離時段為止。

支援的格式摘要

格式 在何處讀取發佈時間戳記 金鑰欄位 狀態
npm 套件回應 (GET /<package>) time["<version>"] Available
Maven 成品下載時的 HTTP 回應標頭 Last-Modified Available
NuGet V3 註冊索引 catalogEntry.published Available
貨物 V1 API 版本回應 created_at Available
PyPI PEP 691 JSON Simple API upload-time Available