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(有关使用--domain和的完整调用,请参阅本主题后面的 CI/CD 示例--domain-owner)。

请勿使用返回的publishedTime字段aws codeartifact describe-package-version进行年龄限制。此字段是在摄取时填充的。对于未捕获原始上游发布时间的记录(包括在引入每工件时间戳捕获之前摄取的包以及某些摄取路径),它会静默地回退到记录的上次更新时间。API 响应并未指明您正在读取两个值中的哪一个,因此您不能依赖它作为上游发布日期。请改用本主题中描述的每种格式的字段。

每种格式的详细信息不同,但是每种格式的门控模式都是一样的:读取每个已解析版本的上游发布时间戳,将其与隔离截止时间(当前时间减去您选择的窗口)进行比较,如果任何版本比截止时间更新,则构建失败。如果您使用多种包裹格式,则在格式特定的字段中应用相同的模式,如下所示。

npm

CodeArtifact 返回 npm packument 字段中每个版本的原始 npmjs.org 发布时间戳。time许多软件包管理器和安全工具都是以本地方式读取此字段。其中包括 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 工件,当您下载文件(JAR、POM 或其他工件)时,会在 Last-Modified HTTP 响应标头中 CodeArtifact 返回原始的 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 日发布。

货物(生锈)

对于从 crates.io 中提取的货箱,在 crates.io V1 API 响应中 CodeArtifact 保留原始发布时间戳。versions查询 V1 API 端点以获取用于读取每个created_at版本的时间戳的 crate。

请求:

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为每个版本返回verscksumyanked、、和,但它不包含发布时间戳。要控制 Cargo 对年龄的依赖性,请在管道的单独步骤中查询上面显示的 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 简单 API 不包含时间戳。

对于在引入按工件上传时间捕获之前提取的分发文件(旧记录),中的条目中可能没有该upload-time字段。files[]您的门禁逻辑应upload-time将缺失视为无法验证的版本,并根据您的政策决定是允许还是屏蔽该版本。

示例:对 npm 包进行门控 CI/CD

以下 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.

攻击得到缓解

攻击类型 工作原理 年龄门控有何帮助
依赖关系混乱 攻击者将与内部软件包同名的高版本软件包发布到公共注册表。 恶意版本是全新的。被隔离窗口阻止。
账户接管 攻击者泄露维护者的凭据并发布恶意更新(例如,事件流)。 新版本已保存。社区会在隔离期内对其进行检测并恢复。
域名仿名 攻击者发布了一个名称与流行软件包相似的软件包(例如,lodashs而不是lodash)。 typosquat 软件包的所有版本都是新的。每个版本都被屏蔽。
Star-jacking 攻击者将恶意软件注入他们控制的合法软件包的补丁版本中。 恶意补丁是新发布的版本。在超过隔离期限之前一直处于封锁状态。

支持的格式摘要

Format 在哪里阅读发布时间戳 关键字段 Status
npm 包裹回复 () GET /<package> time["<version>"] 可用
Maven 下载工件时的 HTTP 响应标头 Last-Modified 可用
NuGet V3 注册索引 catalogEntry.published 可用
货物 V1 API 版本响应 created_at 可用
PyPI PEP 691 JSON 简单 API upload-time 可用