Feature: ACM-PCA (#5712)
This commit is contained in:
parent
22539585e7
commit
672c95384a
@ -16,6 +16,7 @@ def lazy_load(module_name, element, boto3_name=None, backend=None):
|
|||||||
|
|
||||||
|
|
||||||
mock_acm = lazy_load(".acm", "mock_acm")
|
mock_acm = lazy_load(".acm", "mock_acm")
|
||||||
|
mock_acmpca = lazy_load(".acmpca", "mock_acmpca", boto3_name="acm-pca")
|
||||||
mock_amp = lazy_load(".amp", "mock_amp")
|
mock_amp = lazy_load(".amp", "mock_amp")
|
||||||
mock_apigateway = lazy_load(".apigateway", "mock_apigateway")
|
mock_apigateway = lazy_load(".apigateway", "mock_apigateway")
|
||||||
mock_apigatewayv2 = lazy_load(".apigatewayv2", "mock_apigatewayv2")
|
mock_apigatewayv2 = lazy_load(".apigatewayv2", "mock_apigatewayv2")
|
||||||
@ -78,6 +79,9 @@ mock_emr = lazy_load(".emr", "mock_emr")
|
|||||||
mock_emrcontainers = lazy_load(
|
mock_emrcontainers = lazy_load(
|
||||||
".emrcontainers", "mock_emrcontainers", boto3_name="emr-containers"
|
".emrcontainers", "mock_emrcontainers", boto3_name="emr-containers"
|
||||||
)
|
)
|
||||||
|
mock_emrserverless = lazy_load(
|
||||||
|
".emrserverless", "mock_emrserverless", boto3_name="emr-serverless"
|
||||||
|
)
|
||||||
mock_es = lazy_load(".es", "mock_es")
|
mock_es = lazy_load(".es", "mock_es")
|
||||||
mock_events = lazy_load(".events", "mock_events")
|
mock_events = lazy_load(".events", "mock_events")
|
||||||
mock_firehose = lazy_load(".firehose", "mock_firehose")
|
mock_firehose = lazy_load(".firehose", "mock_firehose")
|
||||||
@ -163,9 +167,6 @@ mock_xray = lazy_load(".xray", "mock_xray")
|
|||||||
mock_xray_client = lazy_load(".xray", "mock_xray_client")
|
mock_xray_client = lazy_load(".xray", "mock_xray_client")
|
||||||
mock_wafv2 = lazy_load(".wafv2", "mock_wafv2")
|
mock_wafv2 = lazy_load(".wafv2", "mock_wafv2")
|
||||||
mock_textract = lazy_load(".textract", "mock_textract")
|
mock_textract = lazy_load(".textract", "mock_textract")
|
||||||
mock_emrserverless = lazy_load(
|
|
||||||
".emrserverless", "mock_emrserverless", boto3_name="emr-serverless"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MockAll(ContextDecorator):
|
class MockAll(ContextDecorator):
|
||||||
|
5
moto/acmpca/__init__.py
Normal file
5
moto/acmpca/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
"""acmpca module initialization; sets value for base decorator."""
|
||||||
|
from .models import acmpca_backends
|
||||||
|
from ..core.models import base_decorator
|
||||||
|
|
||||||
|
mock_acmpca = base_decorator(acmpca_backends)
|
7
moto/acmpca/exceptions.py
Normal file
7
moto/acmpca/exceptions.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
"""Exceptions raised by the acmpca service."""
|
||||||
|
from moto.core.exceptions import JsonRESTError
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceNotFoundException(JsonRESTError):
|
||||||
|
def __init__(self, arn: str):
|
||||||
|
super().__init__("ResourceNotFoundException", f"Resource {arn} not found")
|
305
moto/acmpca/models.py
Normal file
305
moto/acmpca/models.py
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
"""ACMPCABackend class with methods for supported APIs."""
|
||||||
|
import base64
|
||||||
|
from .exceptions import ResourceNotFoundException
|
||||||
|
from moto.core import BaseBackend, BackendDict, BaseModel
|
||||||
|
from moto.core.utils import unix_time
|
||||||
|
from moto.moto_api._internal import mock_random
|
||||||
|
from moto.utilities.tagging_service import TaggingService
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import cryptography.x509
|
||||||
|
from cryptography.x509 import NameOID, load_pem_x509_certificate, Certificate
|
||||||
|
import cryptography.hazmat.primitives.asymmetric.rsa
|
||||||
|
from cryptography.hazmat.primitives import serialization, hashes
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
class CertificateAuthority(BaseModel):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
region: str,
|
||||||
|
account_id: str,
|
||||||
|
certificate_authority_configuration: Dict[str, Any],
|
||||||
|
certificate_authority_type: str,
|
||||||
|
revocation_configuration: Dict[str, Any],
|
||||||
|
):
|
||||||
|
self.id = mock_random.uuid4()
|
||||||
|
self.arn = (
|
||||||
|
f"arn:aws:acm-pca:{region}:{account_id}:certificate-authority/{self.id}"
|
||||||
|
)
|
||||||
|
self.account_id = account_id
|
||||||
|
self.region_name = region
|
||||||
|
self.certificate_authority_configuration = certificate_authority_configuration
|
||||||
|
self.certificate_authority_type = certificate_authority_type
|
||||||
|
self.revocation_configuration: Dict[str, Any] = {
|
||||||
|
"CrlConfiguration": {"Enabled": False}
|
||||||
|
}
|
||||||
|
self.set_revocation_configuration(revocation_configuration)
|
||||||
|
self.created_at = unix_time()
|
||||||
|
self.updated_at: Optional[float] = None
|
||||||
|
self.status = "PENDING_CERTIFICATE"
|
||||||
|
|
||||||
|
common_name = self.certificate_authority_configuration.get("Subject", {}).get(
|
||||||
|
"CommonName", "Moto.org"
|
||||||
|
)
|
||||||
|
self.key = cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key(
|
||||||
|
public_exponent=65537, key_size=2048
|
||||||
|
)
|
||||||
|
self.password = str(mock_random.uuid4()).encode("utf-8")
|
||||||
|
self.private_bytes = self.key.private_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||||
|
encryption_algorithm=serialization.BestAvailableEncryption(self.password),
|
||||||
|
)
|
||||||
|
self.certificate: Optional[Certificate] = None
|
||||||
|
self.certificate_chain: Optional[bytes] = None
|
||||||
|
self.csr = self.generate_csr(common_name)
|
||||||
|
|
||||||
|
self.issued_certificates: Dict[str, bytes] = dict()
|
||||||
|
|
||||||
|
def generate_cert(self, common_name: str, subject: cryptography.x509.Name) -> bytes:
|
||||||
|
issuer = cryptography.x509.Name(
|
||||||
|
[ # C = US, O = Amazon, OU = Server CA 1B, CN = Amazon
|
||||||
|
cryptography.x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
|
||||||
|
cryptography.x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Amazon"),
|
||||||
|
cryptography.x509.NameAttribute(
|
||||||
|
NameOID.ORGANIZATIONAL_UNIT_NAME, "Server CA 1B"
|
||||||
|
),
|
||||||
|
cryptography.x509.NameAttribute(NameOID.COMMON_NAME, common_name),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
cert = (
|
||||||
|
cryptography.x509.CertificateBuilder()
|
||||||
|
.subject_name(subject)
|
||||||
|
.issuer_name(issuer)
|
||||||
|
.public_key(self.key.public_key())
|
||||||
|
.serial_number(cryptography.x509.random_serial_number())
|
||||||
|
.not_valid_before(datetime.datetime.utcnow())
|
||||||
|
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=365))
|
||||||
|
.sign(self.key, hashes.SHA512(), default_backend())
|
||||||
|
)
|
||||||
|
|
||||||
|
return cert.public_bytes(serialization.Encoding.PEM)
|
||||||
|
|
||||||
|
def generate_csr(self, common_name: str) -> bytes:
|
||||||
|
csr = (
|
||||||
|
cryptography.x509.CertificateSigningRequestBuilder()
|
||||||
|
.subject_name(
|
||||||
|
cryptography.x509.Name(
|
||||||
|
[
|
||||||
|
cryptography.x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
|
||||||
|
cryptography.x509.NameAttribute(
|
||||||
|
NameOID.STATE_OR_PROVINCE_NAME, "California"
|
||||||
|
),
|
||||||
|
cryptography.x509.NameAttribute(
|
||||||
|
NameOID.LOCALITY_NAME, "San Francisco"
|
||||||
|
),
|
||||||
|
cryptography.x509.NameAttribute(
|
||||||
|
NameOID.ORGANIZATION_NAME, "My Company"
|
||||||
|
),
|
||||||
|
cryptography.x509.NameAttribute(
|
||||||
|
NameOID.COMMON_NAME, common_name
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.sign(self.key, hashes.SHA256())
|
||||||
|
)
|
||||||
|
return csr.public_bytes(serialization.Encoding.PEM)
|
||||||
|
|
||||||
|
def issue_certificate(self, csr_bytes: bytes) -> str:
|
||||||
|
cert = cryptography.x509.load_pem_x509_csr(base64.b64decode(csr_bytes))
|
||||||
|
new_cert = self.generate_cert(common_name="", subject=cert.subject)
|
||||||
|
cert_id = str(mock_random.uuid4()).replace("-", "")
|
||||||
|
cert_arn = f"arn:aws:acm-pca:{self.region_name}:{self.account_id}:certificate-authority/{self.id}/certificate/{cert_id}"
|
||||||
|
self.issued_certificates[cert_arn] = new_cert
|
||||||
|
return cert_arn
|
||||||
|
|
||||||
|
def get_certificate(self, certificate_arn: str) -> bytes:
|
||||||
|
return self.issued_certificates[certificate_arn]
|
||||||
|
|
||||||
|
def set_revocation_configuration(
|
||||||
|
self, revocation_configuration: Optional[Dict[str, Any]]
|
||||||
|
) -> None:
|
||||||
|
if revocation_configuration is not None:
|
||||||
|
self.revocation_configuration = revocation_configuration
|
||||||
|
if "CrlConfiguration" in self.revocation_configuration:
|
||||||
|
if (
|
||||||
|
"S3ObjectAcl"
|
||||||
|
not in self.revocation_configuration["CrlConfiguration"]
|
||||||
|
):
|
||||||
|
self.revocation_configuration["CrlConfiguration"][
|
||||||
|
"S3ObjectAcl"
|
||||||
|
] = "PUBLIC_READ"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def certificate_bytes(self) -> bytes:
|
||||||
|
if self.certificate:
|
||||||
|
return self.certificate.public_bytes(serialization.Encoding.PEM)
|
||||||
|
return b""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def not_valid_after(self) -> Optional[float]:
|
||||||
|
if self.certificate is None:
|
||||||
|
return None
|
||||||
|
return unix_time(self.certificate.not_valid_after)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def not_valid_before(self) -> Optional[float]:
|
||||||
|
if self.certificate is None:
|
||||||
|
return None
|
||||||
|
return unix_time(self.certificate.not_valid_before)
|
||||||
|
|
||||||
|
def import_certificate_authority_certificate(
|
||||||
|
self, certificate: bytes, certificate_chain: Optional[bytes]
|
||||||
|
) -> None:
|
||||||
|
self.certificate = load_pem_x509_certificate(certificate)
|
||||||
|
self.certificate_chain = certificate_chain
|
||||||
|
self.status = "ACTIVE"
|
||||||
|
self.updated_at = unix_time()
|
||||||
|
|
||||||
|
def to_json(self) -> Dict[str, Any]:
|
||||||
|
dct = {
|
||||||
|
"Arn": self.arn,
|
||||||
|
"OwnerAccount": self.account_id,
|
||||||
|
"CertificateAuthorityConfiguration": self.certificate_authority_configuration,
|
||||||
|
"Type": self.certificate_authority_type,
|
||||||
|
"RevocationConfiguration": self.revocation_configuration,
|
||||||
|
"CreatedAt": self.created_at,
|
||||||
|
"Status": self.status,
|
||||||
|
}
|
||||||
|
if self.updated_at:
|
||||||
|
dct["LastStateChangeAt"] = self.updated_at
|
||||||
|
if self.certificate:
|
||||||
|
dct.update(
|
||||||
|
{
|
||||||
|
"NotBefore": self.not_valid_before,
|
||||||
|
"NotAfter": self.not_valid_after,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return dct
|
||||||
|
|
||||||
|
|
||||||
|
class ACMPCABackend(BaseBackend):
|
||||||
|
"""Implementation of ACMPCA APIs."""
|
||||||
|
|
||||||
|
def __init__(self, region_name: str, account_id: str):
|
||||||
|
super().__init__(region_name, account_id)
|
||||||
|
self.certificate_authorities: Dict[str, CertificateAuthority] = dict()
|
||||||
|
self.tagger = TaggingService()
|
||||||
|
|
||||||
|
def create_certificate_authority(
|
||||||
|
self,
|
||||||
|
certificate_authority_configuration: Dict[str, Any],
|
||||||
|
revocation_configuration: Dict[str, Any],
|
||||||
|
certificate_authority_type: str,
|
||||||
|
tags: List[Dict[str, str]],
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
The following parameters are not yet implemented: IdempotencyToken, KeyStorageSecurityStandard, UsageMode
|
||||||
|
"""
|
||||||
|
authority = CertificateAuthority(
|
||||||
|
region=self.region_name,
|
||||||
|
account_id=self.account_id,
|
||||||
|
certificate_authority_configuration=certificate_authority_configuration,
|
||||||
|
certificate_authority_type=certificate_authority_type,
|
||||||
|
revocation_configuration=revocation_configuration,
|
||||||
|
)
|
||||||
|
self.certificate_authorities[authority.arn] = authority
|
||||||
|
if tags:
|
||||||
|
self.tagger.tag_resource(authority.arn, tags)
|
||||||
|
return authority.arn
|
||||||
|
|
||||||
|
def describe_certificate_authority(
|
||||||
|
self, certificate_authority_arn: str
|
||||||
|
) -> CertificateAuthority:
|
||||||
|
if certificate_authority_arn not in self.certificate_authorities:
|
||||||
|
raise ResourceNotFoundException(certificate_authority_arn)
|
||||||
|
return self.certificate_authorities[certificate_authority_arn]
|
||||||
|
|
||||||
|
def get_certificate_authority_certificate(
|
||||||
|
self, certificate_authority_arn: str
|
||||||
|
) -> Tuple[bytes, Optional[bytes]]:
|
||||||
|
ca = self.describe_certificate_authority(certificate_authority_arn)
|
||||||
|
return ca.certificate_bytes, ca.certificate_chain
|
||||||
|
|
||||||
|
def get_certificate_authority_csr(self, certificate_authority_arn: str) -> bytes:
|
||||||
|
ca = self.describe_certificate_authority(certificate_authority_arn)
|
||||||
|
return ca.csr
|
||||||
|
|
||||||
|
def list_tags(
|
||||||
|
self, certificate_authority_arn: str
|
||||||
|
) -> Dict[str, List[Dict[str, str]]]:
|
||||||
|
"""
|
||||||
|
Pagination is not yet implemented
|
||||||
|
"""
|
||||||
|
return self.tagger.list_tags_for_resource(certificate_authority_arn)
|
||||||
|
|
||||||
|
def update_certificate_authority(
|
||||||
|
self,
|
||||||
|
certificate_authority_arn: str,
|
||||||
|
revocation_configuration: Dict[str, Any],
|
||||||
|
status: str,
|
||||||
|
) -> None:
|
||||||
|
ca = self.describe_certificate_authority(certificate_authority_arn)
|
||||||
|
if status is not None:
|
||||||
|
ca.status = status
|
||||||
|
ca.set_revocation_configuration(revocation_configuration)
|
||||||
|
ca.updated_at = unix_time()
|
||||||
|
|
||||||
|
def delete_certificate_authority(self, certificate_authority_arn: str) -> None:
|
||||||
|
ca = self.describe_certificate_authority(certificate_authority_arn)
|
||||||
|
ca.status = "DELETED"
|
||||||
|
|
||||||
|
def issue_certificate(self, certificate_authority_arn: str, csr: bytes) -> str:
|
||||||
|
"""
|
||||||
|
The following parameters are not yet implemented: ApiPassthrough, SigningAlgorithm, TemplateArn, Validity, ValidityNotBefore, IdempotencyToken
|
||||||
|
Some fields of the resulting certificate will have default values, instead of using the CSR
|
||||||
|
"""
|
||||||
|
ca = self.describe_certificate_authority(certificate_authority_arn)
|
||||||
|
certificate_arn = ca.issue_certificate(csr)
|
||||||
|
return certificate_arn
|
||||||
|
|
||||||
|
def get_certificate(
|
||||||
|
self, certificate_authority_arn: str, certificate_arn: str
|
||||||
|
) -> Tuple[bytes, Optional[str]]:
|
||||||
|
"""
|
||||||
|
The CertificateChain will always return None for now
|
||||||
|
"""
|
||||||
|
ca = self.describe_certificate_authority(certificate_authority_arn)
|
||||||
|
certificate = ca.get_certificate(certificate_arn)
|
||||||
|
certificate_chain = None
|
||||||
|
return certificate, certificate_chain
|
||||||
|
|
||||||
|
def import_certificate_authority_certificate(
|
||||||
|
self,
|
||||||
|
certificate_authority_arn: str,
|
||||||
|
certificate: bytes,
|
||||||
|
certificate_chain: Optional[bytes],
|
||||||
|
) -> None:
|
||||||
|
ca = self.describe_certificate_authority(certificate_authority_arn)
|
||||||
|
ca.import_certificate_authority_certificate(certificate, certificate_chain)
|
||||||
|
|
||||||
|
def revoke_certificate(
|
||||||
|
self,
|
||||||
|
certificate_authority_arn: str,
|
||||||
|
certificate_serial: str,
|
||||||
|
revocation_reason: str,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
This is currently a NO-OP
|
||||||
|
"""
|
||||||
|
|
||||||
|
def tag_certificate_authority(
|
||||||
|
self, certificate_authority_arn: str, tags: List[Dict[str, str]]
|
||||||
|
) -> None:
|
||||||
|
self.tagger.tag_resource(certificate_authority_arn, tags)
|
||||||
|
|
||||||
|
def untag_certificate_authority(
|
||||||
|
self, certificate_authority_arn: str, tags: List[Dict[str, str]]
|
||||||
|
) -> None:
|
||||||
|
self.tagger.untag_resource_using_tags(certificate_authority_arn, tags)
|
||||||
|
|
||||||
|
|
||||||
|
acmpca_backends = BackendDict(ACMPCABackend, "acm-pca")
|
164
moto/acmpca/responses.py
Normal file
164
moto/acmpca/responses.py
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
"""Handles incoming acmpca requests, invokes methods, returns responses."""
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
|
||||||
|
from moto.core.responses import BaseResponse
|
||||||
|
from .models import acmpca_backends, ACMPCABackend
|
||||||
|
|
||||||
|
|
||||||
|
class ACMPCAResponse(BaseResponse):
|
||||||
|
"""Handler for ACMPCA requests and responses."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__(service_name="acmpca")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def acmpca_backend(self) -> ACMPCABackend:
|
||||||
|
"""Return backend instance specific for this region."""
|
||||||
|
return acmpca_backends[self.current_account][self.region]
|
||||||
|
|
||||||
|
def create_certificate_authority(self) -> str:
|
||||||
|
params = json.loads(self.body)
|
||||||
|
certificate_authority_configuration = params.get(
|
||||||
|
"CertificateAuthorityConfiguration"
|
||||||
|
)
|
||||||
|
revocation_configuration = params.get("RevocationConfiguration")
|
||||||
|
certificate_authority_type = params.get("CertificateAuthorityType")
|
||||||
|
tags = params.get("Tags")
|
||||||
|
certificate_authority_arn = self.acmpca_backend.create_certificate_authority(
|
||||||
|
certificate_authority_configuration=certificate_authority_configuration,
|
||||||
|
revocation_configuration=revocation_configuration,
|
||||||
|
certificate_authority_type=certificate_authority_type,
|
||||||
|
tags=tags,
|
||||||
|
)
|
||||||
|
return json.dumps(dict(CertificateAuthorityArn=certificate_authority_arn))
|
||||||
|
|
||||||
|
def describe_certificate_authority(self) -> str:
|
||||||
|
params = json.loads(self.body)
|
||||||
|
certificate_authority_arn = params.get("CertificateAuthorityArn")
|
||||||
|
certificate_authority = self.acmpca_backend.describe_certificate_authority(
|
||||||
|
certificate_authority_arn=certificate_authority_arn,
|
||||||
|
)
|
||||||
|
return json.dumps(dict(CertificateAuthority=certificate_authority.to_json()))
|
||||||
|
|
||||||
|
def get_certificate_authority_certificate(self) -> str:
|
||||||
|
params = json.loads(self.body)
|
||||||
|
certificate_authority_arn = params.get("CertificateAuthorityArn")
|
||||||
|
(
|
||||||
|
certificate,
|
||||||
|
certificate_chain,
|
||||||
|
) = self.acmpca_backend.get_certificate_authority_certificate(
|
||||||
|
certificate_authority_arn=certificate_authority_arn,
|
||||||
|
)
|
||||||
|
return json.dumps(
|
||||||
|
dict(
|
||||||
|
Certificate=certificate.decode("utf-8"),
|
||||||
|
CertificateChain=certificate_chain,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_certificate_authority_csr(self) -> str:
|
||||||
|
params = json.loads(self.body)
|
||||||
|
certificate_authority_arn = params.get("CertificateAuthorityArn")
|
||||||
|
csr = self.acmpca_backend.get_certificate_authority_csr(
|
||||||
|
certificate_authority_arn=certificate_authority_arn,
|
||||||
|
)
|
||||||
|
return json.dumps(dict(Csr=csr.decode("utf-8")))
|
||||||
|
|
||||||
|
def list_tags(self) -> str:
|
||||||
|
params = json.loads(self.body)
|
||||||
|
certificate_authority_arn = params.get("CertificateAuthorityArn")
|
||||||
|
tags = self.acmpca_backend.list_tags(
|
||||||
|
certificate_authority_arn=certificate_authority_arn
|
||||||
|
)
|
||||||
|
return json.dumps(tags)
|
||||||
|
|
||||||
|
def update_certificate_authority(self) -> str:
|
||||||
|
params = json.loads(self.body)
|
||||||
|
certificate_authority_arn = params.get("CertificateAuthorityArn")
|
||||||
|
revocation_configuration = params.get("RevocationConfiguration")
|
||||||
|
status = params.get("Status")
|
||||||
|
self.acmpca_backend.update_certificate_authority(
|
||||||
|
certificate_authority_arn=certificate_authority_arn,
|
||||||
|
revocation_configuration=revocation_configuration,
|
||||||
|
status=status,
|
||||||
|
)
|
||||||
|
return "{}"
|
||||||
|
|
||||||
|
def delete_certificate_authority(self) -> str:
|
||||||
|
params = json.loads(self.body)
|
||||||
|
certificate_authority_arn = params.get("CertificateAuthorityArn")
|
||||||
|
self.acmpca_backend.delete_certificate_authority(
|
||||||
|
certificate_authority_arn=certificate_authority_arn
|
||||||
|
)
|
||||||
|
return "{}"
|
||||||
|
|
||||||
|
def issue_certificate(self) -> str:
|
||||||
|
params = json.loads(self.body)
|
||||||
|
certificate_authority_arn = params.get("CertificateAuthorityArn")
|
||||||
|
csr = params.get("Csr").encode("utf-8")
|
||||||
|
certificate_arn = self.acmpca_backend.issue_certificate(
|
||||||
|
certificate_authority_arn=certificate_authority_arn,
|
||||||
|
csr=csr,
|
||||||
|
)
|
||||||
|
return json.dumps(dict(CertificateArn=certificate_arn))
|
||||||
|
|
||||||
|
def get_certificate(self) -> str:
|
||||||
|
params = json.loads(self.body)
|
||||||
|
certificate_authority_arn = params.get("CertificateAuthorityArn")
|
||||||
|
certificate_arn = params.get("CertificateArn")
|
||||||
|
certificate, certificate_chain = self.acmpca_backend.get_certificate(
|
||||||
|
certificate_authority_arn=certificate_authority_arn,
|
||||||
|
certificate_arn=certificate_arn,
|
||||||
|
)
|
||||||
|
return json.dumps(
|
||||||
|
dict(
|
||||||
|
Certificate=certificate.decode("utf-8"),
|
||||||
|
CertificateChain=certificate_chain,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def import_certificate_authority_certificate(self) -> str:
|
||||||
|
params = json.loads(self.body)
|
||||||
|
certificate_authority_arn = params.get("CertificateAuthorityArn")
|
||||||
|
certificate = params.get("Certificate")
|
||||||
|
certificate_bytes = base64.b64decode(certificate)
|
||||||
|
certificate_chain = params.get("CertificateChain")
|
||||||
|
self.acmpca_backend.import_certificate_authority_certificate(
|
||||||
|
certificate_authority_arn=certificate_authority_arn,
|
||||||
|
certificate=certificate_bytes,
|
||||||
|
certificate_chain=certificate_chain,
|
||||||
|
)
|
||||||
|
return "{}"
|
||||||
|
|
||||||
|
def revoke_certificate(self) -> str:
|
||||||
|
params = json.loads(self.body)
|
||||||
|
certificate_authority_arn = params.get("CertificateAuthorityArn")
|
||||||
|
certificate_serial = params.get("CertificateSerial")
|
||||||
|
revocation_reason = params.get("RevocationReason")
|
||||||
|
self.acmpca_backend.revoke_certificate(
|
||||||
|
certificate_authority_arn=certificate_authority_arn,
|
||||||
|
certificate_serial=certificate_serial,
|
||||||
|
revocation_reason=revocation_reason,
|
||||||
|
)
|
||||||
|
return "{}"
|
||||||
|
|
||||||
|
def tag_certificate_authority(self) -> str:
|
||||||
|
params = json.loads(self.body)
|
||||||
|
certificate_authority_arn = params.get("CertificateAuthorityArn")
|
||||||
|
tags = params.get("Tags")
|
||||||
|
self.acmpca_backend.tag_certificate_authority(
|
||||||
|
certificate_authority_arn=certificate_authority_arn,
|
||||||
|
tags=tags,
|
||||||
|
)
|
||||||
|
return "{}"
|
||||||
|
|
||||||
|
def untag_certificate_authority(self) -> str:
|
||||||
|
params = json.loads(self.body)
|
||||||
|
certificate_authority_arn = params.get("CertificateAuthorityArn")
|
||||||
|
tags = params.get("Tags")
|
||||||
|
self.acmpca_backend.untag_certificate_authority(
|
||||||
|
certificate_authority_arn=certificate_authority_arn,
|
||||||
|
tags=tags,
|
||||||
|
)
|
||||||
|
return "{}"
|
11
moto/acmpca/urls.py
Normal file
11
moto/acmpca/urls.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
"""acmpca base URL and path."""
|
||||||
|
from .responses import ACMPCAResponse
|
||||||
|
|
||||||
|
url_bases = [
|
||||||
|
r"https?://acm-pca\.(.+)\.amazonaws\.com",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
url_paths = {
|
||||||
|
"{0}/$": ACMPCAResponse.dispatch,
|
||||||
|
}
|
@ -3,6 +3,7 @@ import re
|
|||||||
|
|
||||||
backend_url_patterns = [
|
backend_url_patterns = [
|
||||||
("acm", re.compile("https?://acm\\.(.+)\\.amazonaws\\.com")),
|
("acm", re.compile("https?://acm\\.(.+)\\.amazonaws\\.com")),
|
||||||
|
("acm-pca", re.compile("https?://acm-pca\\.(.+)\\.amazonaws\\.com")),
|
||||||
("amp", re.compile("https?://aps\\.(.+)\\.amazonaws\\.com")),
|
("amp", re.compile("https?://aps\\.(.+)\\.amazonaws\\.com")),
|
||||||
("apigateway", re.compile("https?://apigateway\\.(.+)\\.amazonaws.com")),
|
("apigateway", re.compile("https?://apigateway\\.(.+)\\.amazonaws.com")),
|
||||||
(
|
(
|
||||||
|
@ -149,12 +149,12 @@ def append_mock_to_init_py(service):
|
|||||||
with open(path, encoding="utf-8") as fhandle:
|
with open(path, encoding="utf-8") as fhandle:
|
||||||
lines = [_.replace("\n", "") for _ in fhandle.readlines()]
|
lines = [_.replace("\n", "") for _ in fhandle.readlines()]
|
||||||
|
|
||||||
if any(_ for _ in lines if re.match(f"^mock_{service}.*lazy_load(.*)$", _)):
|
escaped_service = get_escaped_service(service)
|
||||||
|
if any(_ for _ in lines if _.startswith(f"^mock_{escaped_service} = lazy_load")):
|
||||||
return
|
return
|
||||||
filtered_lines = [_ for _ in lines if re.match("^mock_.*lazy_load(.*)$", _)]
|
filtered_lines = [_ for _ in lines if re.match("^mock_.*lazy_load(.*)$", _)]
|
||||||
last_import_line_index = lines.index(filtered_lines[-1])
|
last_import_line_index = lines.index(filtered_lines[-1])
|
||||||
|
|
||||||
escaped_service = get_escaped_service(service)
|
|
||||||
new_line = (
|
new_line = (
|
||||||
f"mock_{escaped_service} = lazy_load("
|
f"mock_{escaped_service} = lazy_load("
|
||||||
f'".{escaped_service}", "mock_{escaped_service}", boto3_name="{service}")'
|
f'".{escaped_service}", "mock_{escaped_service}", boto3_name="{service}")'
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"""{{ service_class }}Backend class with methods for supported APIs."""
|
"""{{ service_class }}Backend class with methods for supported APIs."""
|
||||||
|
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BackendDict, BaseModel
|
||||||
from moto.core.utils import BackendDict
|
|
||||||
|
|
||||||
|
|
||||||
class {{ service_class }}Backend(BaseBackend):
|
class {{ service_class }}Backend(BaseBackend):
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
acm:
|
acm:
|
||||||
- TestAccACMCertificateDataSource
|
- TestAccACMCertificateDataSource
|
||||||
|
acmpca:
|
||||||
|
- TestAccACMPCACertificateAuthority_
|
||||||
|
- TestAccACMPCACertificateAuthorityDataSource
|
||||||
|
- TestAccACMPCACertificateAuthorityCertificate
|
||||||
|
- TestAccACMPCACertificateDataSource
|
||||||
amp:
|
amp:
|
||||||
- TestAccAMPWorkspace
|
- TestAccAMPWorkspace
|
||||||
- TestAccAMPRuleGroupNamespace
|
- TestAccAMPRuleGroupNamespace
|
||||||
|
0
tests/test_acmpca/__init__.py
Normal file
0
tests/test_acmpca/__init__.py
Normal file
360
tests/test_acmpca/test_acmpca.py
Normal file
360
tests/test_acmpca/test_acmpca.py
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
"""Unit tests for acmpca-supported APIs."""
|
||||||
|
import boto3
|
||||||
|
import pytest
|
||||||
|
import sure # noqa # pylint: disable=unused-import
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
|
from moto import mock_acmpca
|
||||||
|
from moto.core import DEFAULT_ACCOUNT_ID
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import cryptography.x509
|
||||||
|
from cryptography.x509 import NameOID
|
||||||
|
import cryptography.hazmat.primitives.asymmetric.rsa
|
||||||
|
from cryptography.hazmat.primitives import serialization, hashes
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
|
||||||
|
# See our Development Tips on writing tests for hints on how to write good tests:
|
||||||
|
# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
|
||||||
|
|
||||||
|
|
||||||
|
@mock_acmpca
|
||||||
|
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",
|
||||||
|
)
|
||||||
|
|
||||||
|
resp.should.have.key("CertificateAuthorityArn").match(
|
||||||
|
f"^arn:aws:acm-pca:eu-west-1:{DEFAULT_ACCOUNT_ID}:certificate-authority/"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_acmpca
|
||||||
|
def test_describe_certificate_authority():
|
||||||
|
client = boto3.client("acm-pca", region_name="ap-southeast-1")
|
||||||
|
ca_arn = client.create_certificate_authority(
|
||||||
|
CertificateAuthorityConfiguration={
|
||||||
|
"KeyAlgorithm": "RSA_4096",
|
||||||
|
"SigningAlgorithm": "SHA512WITHRSA",
|
||||||
|
"Subject": {"CommonName": "yscb41lw.test"},
|
||||||
|
},
|
||||||
|
CertificateAuthorityType="SUBORDINATE",
|
||||||
|
IdempotencyToken="terraform-20221125230308947400000001",
|
||||||
|
)["CertificateAuthorityArn"]
|
||||||
|
ca = client.describe_certificate_authority(CertificateAuthorityArn=ca_arn)[
|
||||||
|
"CertificateAuthority"
|
||||||
|
]
|
||||||
|
|
||||||
|
ca.should.have.key("Arn").equals(ca_arn)
|
||||||
|
ca.should.have.key("OwnerAccount").equals(DEFAULT_ACCOUNT_ID)
|
||||||
|
ca.should.have.key("CreatedAt")
|
||||||
|
ca.should.have.key("Type").equals("SUBORDINATE")
|
||||||
|
ca.should.have.key("Status").equals("PENDING_CERTIFICATE")
|
||||||
|
ca.should.have.key("CertificateAuthorityConfiguration").equals(
|
||||||
|
{
|
||||||
|
"KeyAlgorithm": "RSA_4096",
|
||||||
|
"SigningAlgorithm": "SHA512WITHRSA",
|
||||||
|
"Subject": {"CommonName": "yscb41lw.test"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_acmpca
|
||||||
|
def test_describe_unknown_certificate_authority():
|
||||||
|
client = boto3.client("acm-pca", region_name="ap-southeast-1")
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.describe_certificate_authority(CertificateAuthorityArn="unknown")
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ResourceNotFoundException")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_acmpca
|
||||||
|
def test_get_certificate_authority_certificate():
|
||||||
|
client = boto3.client("acm-pca", region_name="ap-southeast-1")
|
||||||
|
ca_arn = client.create_certificate_authority(
|
||||||
|
CertificateAuthorityConfiguration={
|
||||||
|
"KeyAlgorithm": "RSA_4096",
|
||||||
|
"SigningAlgorithm": "SHA512WITHRSA",
|
||||||
|
"Subject": {"CommonName": "yscb41lw.test"},
|
||||||
|
},
|
||||||
|
CertificateAuthorityType="SUBORDINATE",
|
||||||
|
IdempotencyToken="terraform-20221125230308947400000001",
|
||||||
|
)["CertificateAuthorityArn"]
|
||||||
|
|
||||||
|
resp = client.get_certificate_authority_certificate(CertificateAuthorityArn=ca_arn)
|
||||||
|
|
||||||
|
# Certificate is empty for now, until we call import_certificate_authority_certificate
|
||||||
|
resp.should.have.key("Certificate").equals("")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_acmpca
|
||||||
|
def test_get_certificate_authority_csr():
|
||||||
|
client = boto3.client("acm-pca", region_name="us-east-2")
|
||||||
|
ca_arn = client.create_certificate_authority(
|
||||||
|
CertificateAuthorityConfiguration={
|
||||||
|
"KeyAlgorithm": "RSA_4096",
|
||||||
|
"SigningAlgorithm": "SHA512WITHRSA",
|
||||||
|
"Subject": {"CommonName": "yscb41lw.test"},
|
||||||
|
},
|
||||||
|
CertificateAuthorityType="SUBORDINATE",
|
||||||
|
IdempotencyToken="terraform-20221125230308947400000001",
|
||||||
|
)["CertificateAuthorityArn"]
|
||||||
|
|
||||||
|
resp = client.get_certificate_authority_csr(CertificateAuthorityArn=ca_arn)
|
||||||
|
|
||||||
|
resp.should.have.key("Csr")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_acmpca
|
||||||
|
def test_list_tags_when_ca_has_no_tags():
|
||||||
|
client = boto3.client("acm-pca", region_name="us-east-2")
|
||||||
|
ca_arn = client.create_certificate_authority(
|
||||||
|
CertificateAuthorityConfiguration={
|
||||||
|
"KeyAlgorithm": "RSA_4096",
|
||||||
|
"SigningAlgorithm": "SHA512WITHRSA",
|
||||||
|
"Subject": {"CommonName": "yscb41lw.test"},
|
||||||
|
},
|
||||||
|
CertificateAuthorityType="SUBORDINATE",
|
||||||
|
IdempotencyToken="terraform-20221125230308947400000001",
|
||||||
|
)["CertificateAuthorityArn"]
|
||||||
|
|
||||||
|
resp = client.list_tags(CertificateAuthorityArn=ca_arn)
|
||||||
|
resp.should.have.key("Tags").equals([])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_acmpca
|
||||||
|
def test_list_tags():
|
||||||
|
client = boto3.client("acm-pca", region_name="us-east-2")
|
||||||
|
ca_arn = client.create_certificate_authority(
|
||||||
|
CertificateAuthorityConfiguration={
|
||||||
|
"KeyAlgorithm": "RSA_4096",
|
||||||
|
"SigningAlgorithm": "SHA512WITHRSA",
|
||||||
|
"Subject": {"CommonName": "yscb41lw.test"},
|
||||||
|
},
|
||||||
|
CertificateAuthorityType="SUBORDINATE",
|
||||||
|
IdempotencyToken="terraform-20221125230308947400000001",
|
||||||
|
Tags=[{"Key": "t1", "Value": "v1"}, {"Key": "t2", "Value": "v2"}],
|
||||||
|
)["CertificateAuthorityArn"]
|
||||||
|
|
||||||
|
resp = client.list_tags(CertificateAuthorityArn=ca_arn)
|
||||||
|
resp.should.have.key("Tags").equals(
|
||||||
|
[{"Key": "t1", "Value": "v1"}, {"Key": "t2", "Value": "v2"}]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_acmpca
|
||||||
|
def test_update_certificate_authority():
|
||||||
|
client = boto3.client("acm-pca", region_name="eu-west-1")
|
||||||
|
ca_arn = client.create_certificate_authority(
|
||||||
|
CertificateAuthorityConfiguration={
|
||||||
|
"KeyAlgorithm": "RSA_4096",
|
||||||
|
"SigningAlgorithm": "SHA512WITHRSA",
|
||||||
|
"Subject": {"CommonName": "yscb41lw.test"},
|
||||||
|
},
|
||||||
|
CertificateAuthorityType="SUBORDINATE",
|
||||||
|
)["CertificateAuthorityArn"]
|
||||||
|
|
||||||
|
client.update_certificate_authority(
|
||||||
|
CertificateAuthorityArn=ca_arn,
|
||||||
|
Status="DISABLED",
|
||||||
|
)
|
||||||
|
|
||||||
|
ca = client.describe_certificate_authority(CertificateAuthorityArn=ca_arn)[
|
||||||
|
"CertificateAuthority"
|
||||||
|
]
|
||||||
|
ca.should.have.key("Status").equals("DISABLED")
|
||||||
|
ca.should.have.key("LastStateChangeAt")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_acmpca
|
||||||
|
def test_delete_certificate_authority():
|
||||||
|
client = boto3.client("acm-pca", region_name="ap-southeast-1")
|
||||||
|
ca_arn = client.create_certificate_authority(
|
||||||
|
CertificateAuthorityConfiguration={
|
||||||
|
"KeyAlgorithm": "RSA_4096",
|
||||||
|
"SigningAlgorithm": "SHA512WITHRSA",
|
||||||
|
"Subject": {"CommonName": "yscb41lw.test"},
|
||||||
|
},
|
||||||
|
CertificateAuthorityType="SUBORDINATE",
|
||||||
|
)["CertificateAuthorityArn"]
|
||||||
|
|
||||||
|
client.delete_certificate_authority(CertificateAuthorityArn=ca_arn)
|
||||||
|
|
||||||
|
ca = client.describe_certificate_authority(CertificateAuthorityArn=ca_arn)[
|
||||||
|
"CertificateAuthority"
|
||||||
|
]
|
||||||
|
ca.should.have.key("Status").equals("DELETED")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_acmpca
|
||||||
|
def test_issue_certificate():
|
||||||
|
client = boto3.client("acm-pca", region_name="ap-southeast-1")
|
||||||
|
ca_arn = client.create_certificate_authority(
|
||||||
|
CertificateAuthorityConfiguration={
|
||||||
|
"KeyAlgorithm": "RSA_4096",
|
||||||
|
"SigningAlgorithm": "SHA512WITHRSA",
|
||||||
|
"Subject": {"CommonName": "t8fzth32.test"},
|
||||||
|
},
|
||||||
|
CertificateAuthorityType="ROOT",
|
||||||
|
)["CertificateAuthorityArn"]
|
||||||
|
|
||||||
|
csr = client.get_certificate_authority_csr(CertificateAuthorityArn=ca_arn)["Csr"]
|
||||||
|
|
||||||
|
resp = client.issue_certificate(
|
||||||
|
CertificateAuthorityArn=ca_arn,
|
||||||
|
Csr=csr,
|
||||||
|
SigningAlgorithm="SHA512WITHRSA",
|
||||||
|
Validity={"Type": "YEARS", "Value": 10},
|
||||||
|
)
|
||||||
|
|
||||||
|
resp.should.have.key("CertificateArn")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_acmpca
|
||||||
|
def test_get_certificate():
|
||||||
|
client = boto3.client("acm-pca", region_name="us-east-2")
|
||||||
|
ca_arn = client.create_certificate_authority(
|
||||||
|
CertificateAuthorityConfiguration={
|
||||||
|
"KeyAlgorithm": "RSA_4096",
|
||||||
|
"SigningAlgorithm": "SHA512WITHRSA",
|
||||||
|
"Subject": {"CommonName": "t8fzth32.test"},
|
||||||
|
},
|
||||||
|
CertificateAuthorityType="ROOT",
|
||||||
|
)["CertificateAuthorityArn"]
|
||||||
|
|
||||||
|
csr = client.get_certificate_authority_csr(CertificateAuthorityArn=ca_arn)["Csr"]
|
||||||
|
|
||||||
|
certificate_arn = client.issue_certificate(
|
||||||
|
CertificateAuthorityArn=ca_arn,
|
||||||
|
Csr=csr,
|
||||||
|
SigningAlgorithm="SHA512WITHRSA",
|
||||||
|
Validity={"Type": "YEARS", "Value": 10},
|
||||||
|
)["CertificateArn"]
|
||||||
|
|
||||||
|
resp = client.get_certificate(
|
||||||
|
CertificateAuthorityArn=ca_arn, CertificateArn=certificate_arn
|
||||||
|
)
|
||||||
|
resp.should.have.key("Certificate")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_acmpca
|
||||||
|
def test_import_certificate_authority_certificate():
|
||||||
|
client = boto3.client("acm-pca", region_name="eu-west-1")
|
||||||
|
ca_arn = client.create_certificate_authority(
|
||||||
|
CertificateAuthorityConfiguration={
|
||||||
|
"KeyAlgorithm": "RSA_4096",
|
||||||
|
"SigningAlgorithm": "SHA512WITHRSA",
|
||||||
|
"Subject": {"CommonName": "yscb41lw.test"},
|
||||||
|
},
|
||||||
|
CertificateAuthorityType="SUBORDINATE",
|
||||||
|
)["CertificateAuthorityArn"]
|
||||||
|
|
||||||
|
cert = create_cert()
|
||||||
|
|
||||||
|
client.import_certificate_authority_certificate(
|
||||||
|
CertificateAuthorityArn=ca_arn,
|
||||||
|
Certificate=cert,
|
||||||
|
)
|
||||||
|
|
||||||
|
ca = client.describe_certificate_authority(CertificateAuthorityArn=ca_arn)[
|
||||||
|
"CertificateAuthority"
|
||||||
|
]
|
||||||
|
ca.should.have.key("Status").equals("ACTIVE")
|
||||||
|
ca.should.have.key("NotBefore")
|
||||||
|
ca.should.have.key("NotAfter")
|
||||||
|
|
||||||
|
resp = client.get_certificate_authority_certificate(CertificateAuthorityArn=ca_arn)
|
||||||
|
resp.should.have.key("Certificate").match("^-----BEGIN CERTIFICATE-----")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_acmpca
|
||||||
|
def test_tag_certificate_authority():
|
||||||
|
client = boto3.client("acm-pca", region_name="eu-west-1")
|
||||||
|
ca_arn = client.create_certificate_authority(
|
||||||
|
CertificateAuthorityConfiguration={
|
||||||
|
"KeyAlgorithm": "RSA_4096",
|
||||||
|
"SigningAlgorithm": "SHA512WITHRSA",
|
||||||
|
"Subject": {"CommonName": "yscb41lw.test"},
|
||||||
|
},
|
||||||
|
CertificateAuthorityType="SUBORDINATE",
|
||||||
|
)["CertificateAuthorityArn"]
|
||||||
|
|
||||||
|
client.tag_certificate_authority(
|
||||||
|
CertificateAuthorityArn=ca_arn,
|
||||||
|
Tags=[{"Key": "t1", "Value": "v1"}, {"Key": "t2", "Value": "v2"}],
|
||||||
|
)
|
||||||
|
|
||||||
|
resp = client.list_tags(CertificateAuthorityArn=ca_arn)
|
||||||
|
resp.should.have.key("Tags").equals(
|
||||||
|
[{"Key": "t1", "Value": "v1"}, {"Key": "t2", "Value": "v2"}]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_acmpca
|
||||||
|
def test_untag_certificate_authority():
|
||||||
|
client = boto3.client("acm-pca", region_name="eu-west-1")
|
||||||
|
ca_arn = client.create_certificate_authority(
|
||||||
|
CertificateAuthorityConfiguration={
|
||||||
|
"KeyAlgorithm": "RSA_4096",
|
||||||
|
"SigningAlgorithm": "SHA512WITHRSA",
|
||||||
|
"Subject": {"CommonName": "yscb41lw.test"},
|
||||||
|
},
|
||||||
|
CertificateAuthorityType="SUBORDINATE",
|
||||||
|
)["CertificateAuthorityArn"]
|
||||||
|
|
||||||
|
client.tag_certificate_authority(
|
||||||
|
CertificateAuthorityArn=ca_arn,
|
||||||
|
Tags=[{"Key": "t1", "Value": "v1"}, {"Key": "t2", "Value": "v2"}],
|
||||||
|
)
|
||||||
|
|
||||||
|
client.untag_certificate_authority(
|
||||||
|
CertificateAuthorityArn=ca_arn, Tags=[{"Key": "t1", "Value": "v1"}]
|
||||||
|
)
|
||||||
|
|
||||||
|
resp = client.list_tags(CertificateAuthorityArn=ca_arn)
|
||||||
|
resp.should.have.key("Tags").equals([{"Key": "t2", "Value": "v2"}])
|
||||||
|
|
||||||
|
|
||||||
|
def create_cert():
|
||||||
|
serial_number = cryptography.x509.random_serial_number()
|
||||||
|
subject = cryptography.x509.Name(
|
||||||
|
[
|
||||||
|
cryptography.x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
|
||||||
|
cryptography.x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "CA"),
|
||||||
|
cryptography.x509.NameAttribute(NameOID.LOCALITY_NAME, "Test Francisco"),
|
||||||
|
cryptography.x509.NameAttribute(NameOID.ORGANIZATION_NAME, "TestCompany"),
|
||||||
|
cryptography.x509.NameAttribute(NameOID.COMMON_NAME, "testcert.io"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
issuer = cryptography.x509.Name(
|
||||||
|
[ # C = US, O = Amazon, OU = Server CA 1B, CN = Amazon
|
||||||
|
cryptography.x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
|
||||||
|
cryptography.x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Amazon"),
|
||||||
|
cryptography.x509.NameAttribute(
|
||||||
|
NameOID.ORGANIZATIONAL_UNIT_NAME, "Server CA 1B"
|
||||||
|
),
|
||||||
|
cryptography.x509.NameAttribute(NameOID.COMMON_NAME, "TestCert"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
key = cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key(
|
||||||
|
public_exponent=65537, key_size=2048
|
||||||
|
)
|
||||||
|
cert = (
|
||||||
|
cryptography.x509.CertificateBuilder()
|
||||||
|
.subject_name(subject)
|
||||||
|
.issuer_name(issuer)
|
||||||
|
.public_key(key.public_key())
|
||||||
|
.serial_number(serial_number)
|
||||||
|
.not_valid_before(datetime.datetime.utcnow() - datetime.timedelta(days=10))
|
||||||
|
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=10))
|
||||||
|
.sign(key, hashes.SHA512(), default_backend())
|
||||||
|
)
|
||||||
|
|
||||||
|
return cert.public_bytes(serialization.Encoding.PEM)
|
Loading…
Reference in New Issue
Block a user