From 0bbe1f1717747eea0fed34877a198ae4d4c0cc11 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Tue, 19 Dec 2023 22:04:21 -0100 Subject: [PATCH] ACM: describe_certificate() should return a DomainValidationOption for each SAN (#7144) --- .github/workflows/build.yml | 4 ++ .../workflows/tests_terraform_examples.yml | 33 +++++++++++++++ .gitignore | 2 + moto/acm/models.py | 37 +++++++++------- other_langs/terraform/acm/acm.tf | 42 +++++++++++++++++++ other_langs/terraform/acm/providers.tf | 15 +++++++ tests/test_acm/test_acm.py | 9 +++- 7 files changed, 126 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/tests_terraform_examples.yml create mode 100644 other_langs/terraform/acm/acm.tf create mode 100644 other_langs/terraform/acm/providers.tf diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5a83285ab..eefb42076 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,6 +82,10 @@ jobs: needs: lint uses: ./.github/workflows/tests_sdk_ruby.yml + terraformexamplestest: + needs: lint + uses: ./.github/workflows/tests_terraform_examples.yml + test: needs: [lint] if: "!contains(github.event.pull_request.labels.*.name, 'java')" diff --git a/.github/workflows/tests_terraform_examples.yml b/.github/workflows/tests_terraform_examples.yml new file mode 100644 index 000000000..46fa804d2 --- /dev/null +++ b/.github/workflows/tests_terraform_examples.yml @@ -0,0 +1,33 @@ +# Small, self contained Terraform examples +# Scripts should be placed in: +# other_langs/terraform/service + +name: Terraform Examples +on: [workflow_call] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + service: ["acm"] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python 3.8 + uses: actions/setup-python@v5 + with: + python-version: "3.8" + - name: Start MotoServer + run: | + pip install build + python -m build + docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.10-slim /moto/scripts/ci_moto_server.sh & + python scripts/ci_wait_for_server.py + - name: Run tests + run: | + mkdir ~/.aws && touch ~/.aws/credentials && echo -e "[default]\naws_access_key_id = test\naws_secret_access_key = test" > ~/.aws/credentials + cd other_langs/terraform/${{ matrix.service }} && terraform init && terraform apply --auto-approve diff --git a/.gitignore b/.gitignore index 690cc0cff..e34bcfd32 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ other_langs/tests_java/target other_langs/tests_dotnet/ExampleTestProject/bin other_langs/tests_dotnet/ExampleTestProject/obj other_langs/tests_ruby/Gemfile.lock +other_langs/terraform/*/.terraform* +other_langs/terraform/*/terraform* diff --git a/moto/acm/models.py b/moto/acm/models.py index 522061436..8d7bcf4c8 100644 --- a/moto/acm/models.py +++ b/moto/acm/models.py @@ -337,25 +337,32 @@ class CertBundle(BaseModel): "ExtendedKeyUsages": [], "RenewalEligibility": "INELIGIBLE", "Options": {"CertificateTransparencyLoggingPreference": "ENABLED"}, - "DomainValidationOptions": [{"DomainName": self.common_name}], } } + domain_names = set(sans + [self.common_name]) + validation_options = [] + if self.status == "PENDING_VALIDATION": - result["Certificate"]["DomainValidationOptions"][0][ - "ValidationDomain" - ] = self.common_name - result["Certificate"]["DomainValidationOptions"][0][ - "ValidationStatus" - ] = self.status - result["Certificate"]["DomainValidationOptions"][0]["ResourceRecord"] = { - "Name": f"_d930b28be6c5927595552b219965053e.{self.common_name}.", - "Type": "CNAME", - "Value": "_c9edd76ee4a0e2a74388032f3861cc50.ykybfrwcxw.acm-validations.aws.", - } - result["Certificate"]["DomainValidationOptions"][0][ - "ValidationMethod" - ] = "DNS" + for san in domain_names: + resource_record = { + "Name": f"_d930b28be6c5927595552b219965053e.{san}.", + "Type": "CNAME", + "Value": "_c9edd76ee4a0e2a74388032f3861cc50.ykybfrwcxw.acm-validations.aws.", + } + validation_options.append( + { + "DomainName": san, + "ValidationDomain": san, + "ValidationStatus": self.status, + "ValidationMethod": "DNS", + "ResourceRecord": resource_record, + } + ) + else: + validation_options = [{"DomainName": name} for name in domain_names] + result["Certificate"]["DomainValidationOptions"] = validation_options + if self.type == "IMPORTED": result["Certificate"]["ImportedAt"] = datetime_to_epoch(self.created_at) else: diff --git a/other_langs/terraform/acm/acm.tf b/other_langs/terraform/acm/acm.tf new file mode 100644 index 000000000..1e43932f6 --- /dev/null +++ b/other_langs/terraform/acm/acm.tf @@ -0,0 +1,42 @@ +locals { + domain_name = "test.co" +} + +resource "aws_route53_zone" "test" { + name = local.domain_name +} + +resource "aws_acm_certificate" "certificate" { + domain_name = local.domain_name + validation_method = "DNS" + + subject_alternative_names = ["*.${local.domain_name}"] + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_route53_record" "certificate_validation" { + for_each = { + for dvo in aws_acm_certificate.certificate.domain_validation_options : dvo.domain_name => { + name = dvo.resource_record_name + record = dvo.resource_record_value + type = dvo.resource_record_type + } + } + + allow_overwrite = true + name = each.value.name + records = [each.value.record] + ttl = 60 + type = each.value.type + zone_id = aws_route53_zone.test.zone_id +} + +resource "aws_acm_certificate_validation" "validation" { + certificate_arn = aws_acm_certificate.certificate.arn + validation_record_fqdns = [ + for record in aws_route53_record.certificate_validation : record.fqdn + ] +} \ No newline at end of file diff --git a/other_langs/terraform/acm/providers.tf b/other_langs/terraform/acm/providers.tf new file mode 100644 index 000000000..3b900e069 --- /dev/null +++ b/other_langs/terraform/acm/providers.tf @@ -0,0 +1,15 @@ +provider "aws" { + region = "us-east-1" + s3_use_path_style = true + skip_credentials_validation = true + skip_metadata_api_check = true + skip_requesting_account_id = true + + endpoints { + acm = "http://localhost:5000" + route53 = "http://localhost:5000" + } + + access_key = "my-access-key" + secret_key = "my-secret-key" +} \ No newline at end of file diff --git a/tests/test_acm/test_acm.py b/tests/test_acm/test_acm.py index 14db902b6..d0c46d20c 100644 --- a/tests/test_acm/test_acm.py +++ b/tests/test_acm/test_acm.py @@ -385,7 +385,7 @@ def test_request_certificate(): @mock_acm -def test_request_certificate_with_tags(): +def test_request_certificate_with_optional_arguments(): client = boto3.client("acm", region_name="eu-central-1") token = str(uuid.uuid4()) @@ -402,6 +402,13 @@ def test_request_certificate_with_tags(): assert "CertificateArn" in resp arn_1 = resp["CertificateArn"] + cert = client.describe_certificate(CertificateArn=arn_1)["Certificate"] + assert len(cert["SubjectAlternativeNames"]) == 3 + assert len(cert["DomainValidationOptions"]) == 3 + assert {option["DomainName"] for option in cert["DomainValidationOptions"]} == set( + cert["SubjectAlternativeNames"] + ) + resp = client.list_tags_for_certificate(CertificateArn=arn_1) tags = {item["Key"]: item.get("Value", "__NONE__") for item in resp["Tags"]} assert len(tags) == 2