- Mock
- 测试
【免费下载链接】moto
A library that allows you to easily mock out tests based on AWS infrastructure.
导读
本文以 Moto 仓库中 acm-pca 服务文档 为核心,系统讲解如何在本地开发与测试中完整模拟 AWS Private Certificate Authority(ACM Private CA / acm-pca)服务:从创建私有 CA、获取 CSR、签发根证书与终端实体证书,到吊销证书、管理标签与资源策略的完整生命周期。读完本文,你将掌握该服务在 Moto 中已实现的全部 API 用法、未实现参数的真实行为边界,以及其基于cryptography库的底层实现原理,可直接用 boto3 在测试中复现真实的 PKI 工作流。
一、服务概述:Moto 如何模拟 acm-pca
Moto 的 acm-pca 模拟位于moto/acmpca/目录,遵循 Moto 标准的「Backend(模型层)— Response(协议层)— URL(路由层)」三段式架构:
- models.py:定义
ACMPCABackend与CertificateAuthority模型,持有全部状态与业务逻辑; - responses.py:解析 boto3 发来的 JSON 请求体,调用 Backend 方法并序列化响应;
- urls.py:注册
https?://acm-pca\.(.+)\.amazonaws\.com端点,将请求分发给ACMPCAResponse.dispatch,同时在 backend_index.py 中完成服务路由注册。
与真实 AWS 一样,Moto 的 CA 状态机也遵循「先创建(PENDING_CERTIFICATE)→ 签发并导入证书(ACTIVE)→ 可更新(DISABLED)→ 可删除(DELETED)」的流转路径。在测试中,只需为 boto3 客户端加上@mock_aws装饰器,即可让boto3.client("acm-pca")的所有请求落到本地模拟实现上。
二、功能覆盖总览
依据 docs/docs/services/acm-pca.rst 的实现清单,目前共实现 15 个 API,未实现 5 个,具体如下:
| 状态 | API | 说明 |
|---|---|---|
| ✅ 已实现 | create_certificate_authority | 创建私有 CA(部分参数未实现,见下文) |
| ❌ 未实现 | create_certificate_authority_audit_report | — |
| ❌ 未实现 | create_permission | — |
| ✅ 已实现 | delete_certificate_authority | 将 CA 状态置为DELETED |
| ❌ 未实现 | delete_permission | — |
| ✅ 已实现 | delete_policy | 删除附加到私有 CA 的资源策略 |
| ✅ 已实现 | describe_certificate_authority | 返回 CA 详情 |
| ❌ 未实现 | describe_certificate_authority_audit_report | — |
| ✅ 已实现 | get_certificate | 获取已签发证书与证书链 |
| ✅ 已实现 | get_certificate_authority_certificate | 获取 CA 自身的证书与链 |
| ✅ 已实现 | get_certificate_authority_csr | 获取 CA 的 CSR |
| ✅ 已实现 | get_policy | 检索附加到私有 CA 的资源策略 |
| ✅ 已实现 | import_certificate_authority_certificate | 导入 CA 证书使其激活 |
| ✅ 已实现 | issue_certificate | 签发证书(部分参数未实现,见下文) |
| ✅ 已实现 | list_certificate_authorities | 列出私有 CA(支持分页与ResourceOwner过滤) |
| ❌ 未实现 | list_permissions | — |
| ✅ 已实现 | list_tags | 列出标签(分页未实现) |
| ✅ 已实现 | put_policy | 附加资源策略到私有 CA |
| ❌ 未实现 | restore_certificate_authority | — |
| ✅ 已实现 | revoke_certificate | 吊销证书 |
| ✅ 已实现 | tag_certificate_authority/untag_certificate_authority | 增删标签 |
文档同时明确标注了两处参数缺口:create_certificate_authority未实现IdempotencyToken、KeyStorageSecurityStandard、UsageMode三个参数;issue_certificate未实现ApiPassthrough、SigningAlgorithm、Validity、ValidityNotBefore、IdempotencyToken五个参数,且证书的部分字段将采用默认值而非完全取自 CSR。后文将逐一说明这些缺口的实际影响。
三、创建私有 CA:create_certificate_authority
创建 CA 是全部 PKI 流程的起点。以下代码直接取自仓库测试 tests/test_acmpca/test_acmpca.py,可原样运行:
import boto3 from moto import mock_aws @mock_aws def test_create_certificate_authority(): client = boto3.client("acm-pca", region_name="eu-west-1") resp = client.create_certificate_authority( CertificateAuthorityConfiguration={ "KeyAlgorithm": "RSA_4096", "SigningAlgorithm": "SHA512WITHRSA", "Subject": {"CommonName": "yscb41lw.test"}, }, CertificateAuthorityType="SUBORDINATE", IdempotencyToken="terraform-20221125230308947400000001", ) print(resp["CertificateAuthorityArn"]) # 输出形如:arn:aws:acm-pca:eu-west-1:123456789012:certificate-authority/<uuid>3.1 核心参数说明
CertificateAuthorityConfiguration(必填):含KeyAlgorithm(如RSA_4096)、SigningAlgorithm(如SHA512WITHRSA)与Subject。Subject支持CommonName、Country、State、Organization、OrganizationalUnit等字段,模型层在 models.py 的issuer属性中按固定顺序映射为 X.509 名称属性。CertificateAuthorityType(必填):ROOT或SUBORDINATE。RevocationConfiguration(可选):吊销配置,详见下文 CRL 配置一节。Tags(可选):创建时即打标签,backend 会同步到内部TaggingService(见 models.py)。
3.2 未实现参数的真实行为
文档明确声明IdempotencyToken、KeyStorageSecurityStandard、UsageMode尚未实现。从 models.py 的签名可以看出,backend 的create_certificate_authority只接收配置、吊销配置、类型、安全标准与标签五个参数——IdempotencyToken被 boto3 侧直接忽略,重复请求会创建新的 CA 而非幂等复用;UsageMode在模型中被固定为"SHORT_LIVED_CERTIFICATE"(models.py);而KeyStorageSecurityStandard参数虽然会被接收并透传,但即便不传,模型也会默认赋值为"FIPS_140_2_LEVEL_3_OR_HIGHER"(models.py),测试test_describe_certificate_authority验证了这一默认值。
3.3 创建后的内部状态
每次创建时,模型会同步执行以下初始化(见 models.py):
- 生成一个随机 UUID 作为 CA 的
id,并组装出标准 ARN:arn:{partition}:acm-pca:{region}:{account_id}:certificate-authority/{id}; - 用
cryptography生成RSA-2048 私钥(rsa.generate_private_key(public_exponent=65537, key_size=2048)),并以随机口令做 PEM 加密存储,私钥字节以BestAvailableEncryption方式持久化,保证 backend 可被 pickle 序列化(测试test_acmpca_backend_is_serialisable专门验证了这一点); - 状态初始为
PENDING_CERTIFICATE,记录created_at时间戳; - 吊销配置默认值为
{"CrlConfiguration": {"Enabled": False}}。
四、CA 生命周期:获取 CSR 并激活 CA
4.1 获取 CSR:get_certificate_authority_csr
创建后 CA 处于PENDING_CERTIFICATE,需要先取出 CSR 自签根证书才能激活:
csr = client.get_certificate_authority_csr(CertificateAuthorityArn=ca_arn)["Csr"] # csr 为 PEM 格式的字符串,可直接交给 openssl 或 cryptography 解析从 models.py 看,CSR 由x509.CertificateSigningRequestBuilder生成,主体使用 CA 的issuer名称(即创建时传入的Subject),并附带 critical 的BasicConstraints(ca=True, path_length=None)扩展,用 SHA256 签名。测试test_get_certificate_authority_csr验证了 CSR 中CommonName、Country、State、Organization、OrganizationalUnit各字段与创建参数一致。
4.2 签发根证书并导入激活
激活 CA 的标准流程分三步:先issue_certificate用根 CA 模板签发自身证书,再get_certificate取回证书,最后import_certificate_authority_certificate导入:
# 1) 签发根证书 cert_arn = client.issue_certificate( CertificateAuthorityArn=ca_arn, Csr=csr, SigningAlgorithm="SHA256WITHRSA", TemplateArn="arn:aws:acm-pca:::template/RootCACertificate/V1", Validity={"Type": "YEARS", "Value": 10}, )["CertificateArn"] # 2) 取回证书 PEM ca_cert = client.get_certificate( CertificateAuthorityArn=ca_arn, CertificateArn=cert_arn )["Certificate"] # 3) 导入证书,CA 状态变为 ACTIVE client.import_certificate_authority_certificate( CertificateAuthorityArn=ca_arn, Certificate=ca_cert )import_certificate_authority_certificate的实现(models.py)会先尝试解析 PEM 证书,若解析失败则抛出MalformedCertificateAuthorityException(测试用"invalid certificate pem"验证);成功后写入证书字节、可选证书链,并将状态置为ACTIVE、刷新updated_at。导入后,describe_certificate_authority返回的NotBefore/NotAfter字段也会随证书填充(见 models.py)。
4.3 状态约束
注意get_certificate_authority_certificate(获取 CA 自身证书)要求 CA 状态为ACTIVE,否则抛出InvalidStateException(models.py);测试test_get_certificate_authority_certificate验证了在PENDING_CERTIFICATE状态下调用会返回错误码InvalidStateException及对应错误消息。而get_certificate_authority_csr则不受状态限制,随时可调用。
五、签发终端实体证书:issue_certificate
CA 激活后即可对外签发证书。核心实现位于 models.py 的issue_certificate与 models.py 的_x509_extensions:
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) ee_csr = create_csr(private_key, "US", "WA", "BezosCorp", "bezoscorp.com") # 自行生成终端实体 CSR ee_cert_arn = client.issue_certificate( CertificateAuthorityArn=ca_arn, Csr=ee_csr, SigningAlgorithm="SHA256WITHRSA", Validity={"Type": "YEARS", "Value": 1}, )["CertificateArn"]5.1 模板(TemplateArn)驱动的扩展逻辑
实现区分两类模板,并据此注入不同的 X.509 扩展:
arn:aws:acm-pca:::template/RootCACertificate/V1:注入 critical 的BasicConstraints(ca=True, path_length=None)、KeyUsage(crl_sign, key_cert_sign, digital_signature)以及SubjectKeyIdentifier;arn:aws:acm-pca:::template/EndEntityCertificate/V1(或未传 TemplateArn):注入BasicConstraints(ca=False)、AuthorityKeyIdentifier(取自已导入的 CA 公钥)、SubjectKeyIdentifier、KeyUsage(digital_signature, key_encipherment)以及含SERVER_AUTH/CLIENT_AUTH的ExtendedKeyUsage。
此外,CSR 中的 Subject Alternative Name(SAN)扩展会被原样透传到新证书(models.py)。测试test_end_entity_certificate_issuance验证了 SAN 中的 DNS、邮箱(RFC822)、OtherName 与 URI 均被保留。
5.2 签发产物的内部行为
- 新证书用 SHA512 签名,有效期固定为自当前时间起 365 天(models.py),即文档所述「证书部分字段使用默认值而非 CSR」的具体体现——
Validity、SigningAlgorithm等参数虽可传入但未生效; - 证书序列号由
x509.random_serial_number()随机生成; - 签发的证书以
certificate-authority/{ca_id}/certificate/{cert_id}形式的 ARN 存储,其中cert_id为去除连字符的 UUID; - 根 CA 证书(RootCACertificate 模板签发)不带证书链,而终端实体证书会附带 CA 证书作为链(models.py)。测试
test_root_certificate_has_no_chain与test_end_entity_certificate_includes_chain分别验证了两种行为。
5.3 签名算法默认值说明
由于SigningAlgorithm参数未实现,所有证书的签名算法由代码固定:CSR 生成用 SHA256,证书签发用 SHA512。如果你在测试中断言签名算法,需要注意这一点。
六、检索证书:get_certificate 与 get_certificate_authority_certificate
两个 API 用途不同:
get_certificate:检索issue_certificate签发的证书(含链)。响应中根证书不包含CertificateChain字段,终端实体证书则返回 CA 证书作为链(responses.py)。如果证书已被吊销,该方法会抛出RequestInProgressException(详见下节)。get_certificate_authority_certificate:检索 CA 自身的证书与链,要求状态为ACTIVE。响应中的CertificateChain会尝试 Base64 解码,解码失败则按原始字符串返回(responses.py)。
测试 tests/test_acmpca/test_acmpca.py 还验证了证书链必须是合法 PEM 且可被cryptography正常加载,保证返回内容具备真实的 X.509 可验证性——在test_end_entity_certificate_issuance中,Moto 签发的终端实体证书甚至能通过cryptography.x509.verification的证书链验证(信任存储中放入 CA 证书,验证出长度为 2 的信任链)。
七、吊销证书:revoke_certificate
# 从证书 PEM 中解析序列号(大写十六进制,按字节加冒号分隔) cert_obj = x509.load_pem_x509_certificate(test_cert.encode("utf-8")) serial_number = format(cert_obj.serial_number, "X") serial_number = ":".join( serial_number[i : i + 2] for i in range(0, len(serial_number), 2) ) client.revoke_certificate( CertificateAuthorityArn=ca_arn, CertificateSerial=serial_number, RevocationReason="KEY_COMPROMISE", )吊销的底层行为(models.py):
- 仅允许对
ACTIVE状态的 CA 执行吊销,否则抛InvalidStateException; - 吊销记录以证书序列号为键、以
{"revocation_reason": ..., "revocation_time": ...}为值存入 CA 模型; - 之后调用
get_certificate时,models.py 会从证书中反解序列号并比对吊销表,命中即抛RequestInProgressException,消息形如"The certificate has been revoked with reason: KEY_COMPROMISE"。
测试test_revoke_certificate完整覆盖了「吊销前可取回 → 吊销后 get_certificate 报错」的闭环,并直接通过 backend 状态断言了吊销原因与吊销时间均已记录。
八、查询与管理 CA
8.1 describe_certificate_authority
返回 CA 的完整 JSON 视图(models.py),包含:Arn、OwnerAccount、CertificateAuthorityConfiguration、Type、RevocationConfiguration、CreatedAt、Status、UsageMode、KeyStorageSecurityStandard,以及状态变化后的LastStateChangeAt和导入证书后的NotBefore/NotAfter。对不存在的 ARN 调用会抛ResourceNotFoundException(测试test_describe_unknown_certificate_authority验证)。
8.2 list_certificate_authorities
支持分页与ResourceOwner过滤(models.py):
- 分页模型定义于
PAGINATION_MODEL(models.py),MaxResults默认上限为 100,响应携带NextToken;测试验证了MaxResults=1时返回单条记录与分页令牌; ResourceOwner取值SELF(默认,仅本账号)或OTHER_ACCOUNTS(仅其他账号),通过比对account_id过滤;- 结果按
created_at倒序排列。
8.3 update_certificate_authority 与 delete_certificate_authority
update_certificate_authority可更新Status(如DISABLED)与RevocationConfiguration,并刷新LastStateChangeAt(测试验证更新后Status变为DISABLED且出现LastStateChangeAt);delete_certificate_authority将状态置为DELETED(软删除语义,与真实 AWS 的删除后保留期行为方向一致)。
九、CRL 吊销配置的校验细节
RevocationConfiguration.CrlConfiguration中有一个值得注意的S3ObjectAcl字段,行为如下(models.py):
- 未显式传入时,默认值为
"PUBLIC_READ"; - 仅接受
"PUBLIC_READ"与"BUCKET_OWNER_FULL_CONTROL"两个取值,其他值抛出InvalidS3ObjectAclInCrlConfiguration,错误消息中会列出合法取值(exceptions.py)。
测试test_update_certificate_authority完整覆盖了默认值填充、显式传值覆盖与非法值报错三种场景,是排查吊销配置问题时的最佳参考。
十、标签管理:tag / untag / list_tags
三个 API 均基于 Moto 通用的TaggingService(models.py):
client.tag_certificate_authority( CertificateAuthorityArn=ca_arn, Tags=[{"Key": "t1", "Value": "v1"}, {"Key": "t2", "Value": "v2"}], ) resp = client.list_tags(CertificateAuthorityArn=ca_arn) # resp["Tags"] == [{"Key": "t1", "Value": "v1"}, ...] client.untag_certificate_authority( CertificateAuthorityArn=ca_arn, Tags=[{"Key": "t1", "Value": "v1"}] )按文档标注,list_tags的分页尚未实现;tag_certificate_authority与untag_certificate_authority则直接透传至 tagger,行为与 AWS 一致(测试test_tag_certificate_authority、test_untag_certificate_authority验证了增删效果)。
十一、资源策略:put_policy / get_policy / delete_policy
三个策略 API 是文档中明确给出语义说明的功能:
put_policy:将 JSON 字符串形式的资源策略附加到私有 CA,要求 CA 状态为ACTIVE(否则抛InvalidStateException);get_policy:检索策略;若从未设置过策略,抛ResourceNotFoundException;delete_policy:清除策略。
测试test_policy_operations给出了完整用例:先激活 CA,再put_policy写入包含"Action": ["acm-pca:IssueCertificate"]的策略 JSON,get_policy原样取回,delete_policy后再次get_policy报ResourceNotFoundException。策略以字符串形式存于CertificateAuthority.policy字段(models.py),不做语法校验。
十二、底层实现架构速览
了解请求如何流转,有助于排查边界行为。一次 boto3 调用的完整链路是:
- 请求命中 urls.py 注册的
acm-pca.{region}.amazonaws.com端点; - responses.py 的
ACMPCAResponse按 action 分发到对应处理方法,将请求体json.loads后提取参数(例如create_certificate_authority读取CertificateAuthorityConfiguration、RevocationConfiguration、CertificateAuthorityType、KeyStorageSecurityStandard、Tags); - 调用 models.py 中
ACMPCABackend的对应方法(通过acmpca_backends[current_account][region]获取区域隔离的 backend 实例),执行状态变更; - 结果序列化为 JSON 返回。
值得强调的是区域隔离:acmpca_backends = BackendDict(ACMPCABackend, "acm-pca")(models.py)意味着不同 region 的 CA 相互独立,而 ARN 中的 partition 会根据 region 正确推导(如us-gov-east-1→aws-us-gov,eusc-de-east-1→aws-eusc,测试对此做了参数化验证)。
十三、测试验证与常见坑位
仓库的 tests/test_acmpca/test_acmpca.py 是功能行为的最佳规格说明书,覆盖了创建、描述、CSR、签发、导入、吊销、策略、标签、分页、证书链等全部已实现 API。实践中的常见注意事项:
get_certificate_authority_certificate与put_policy都要求 CA 处于ACTIVE,测试前务必先走完「签发根证书 → 导入」两步;- 签发证书时如果不传
TemplateArn,默认按终端实体证书模板处理(含ca=False与服务器/客户端认证扩展); Validity与SigningAlgorithm参数暂未生效,证书有效期固定为 365 天、签名算法固定为 SHA512,若断言这些字段需要以实际实现为准;- 吊销后
get_certificate抛出的异常类型是RequestInProgressException,而非常见的ResourceNotFoundException,断言错误码时注意区分; - backend 是可 pickle 序列化的(私钥以加密字节存储),可在 LocalStack 等下游项目中安全持久化。
结语
Moto 的 acm-pca 实现已经覆盖了私有 CA 生命周期中最核心的 15 个 API,足以支撑基于 boto3 的 PKI 相关测试(证书签发、链验证、吊销、标签与策略管理)。在编写测试时,建议以 docs/docs/services/acm-pca.rst 的实现清单为功能边界,以 tests/test_acmpca/test_acmpca.py 为行为参照,并注意本文点明的若干未实现参数与默认值细节,即可写出与真实 AWS 行为高度对齐的可靠测试。
- Mock
- 测试
【免费下载链接】moto
A library that allows you to easily mock out tests based on AWS infrastructure.
相关推荐
使用 AWS CLI 安全删除 ACM Private CA:`aws acm-pca delete-certificate-authority` 实战指南
使用 AWS CLI 安全删除 ACM Private CA: aws acm pca delete certificate authority 实战指南 导读
开发工具云原生运维AWS CLI acm-pca import-certificate-authority-certificate 实战指南:导入 CA 证书激活私有 CA
AWS CLI acm pca import certificate authority certificate 实战指南:导入 CA 证书激活私有 CA 导读
开发工具云原生运维AWS CLI `acm-pca revoke-certificate` 实战指南:私有证书吊销全流程解析
AWS CLI acm pca revoke certificate 实战指南:私有证书吊销全流程解析 导读 在 AWS Private Certificate
开发工具云原生运维
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考