schlarpc / overengineered-cloudfront-s3-static-website

The objectively correct static website host with S3 + CloudFront
5 stars 0 forks source link

Add automatic ACM creation/verification #1

Closed schlarpc closed 5 years ago

schlarpc commented 5 years ago

Can trigger a Lambda based on a CWE rule:

{
    "detail-type": [
        "AWS API Call via CloudTrail"
    ],
    "detail": {
        "eventSource": [
            "acm.amazonaws.com"
        ]
    }
}

Event contents:

{
    "version": "0",
    "id": "f2ea2738-07d0-ecff-cbaa-ddcf050d3ba4",
    "detail-type": "AWS API Call via CloudTrail",
    "source": "aws.acm",
    "account": "902119125327",
    "time": "2019-02-05T18:35:32Z",
    "region": "us-west-2",
    "resources": [],
    "detail": {
        "eventVersion": "1.06",
        "userIdentity": ...,
        "eventTime": "2019-02-05T18:35:32Z",
        "eventSource": "acm.amazonaws.com",
        "eventName": "RequestCertificate",
        "awsRegion": "us-west-2",
        "sourceIPAddress": "205.251.233.176",
        "userAgent": "Coral/Netty4",
        "requestParameters": {
            "validationMethod": "DNS",
            "subjectAlternativeNames": [
                "snack.foo"
            ],
            "domainName": "snicker.foo",
            "idempotencyToken": "2199609609141f2dae"
        },
        "responseElements": {
            "certificateArn": "arn:aws:acm:us-west-2:902119125327:certificate/4f687013-03c3-4a05-8e70-c985da0c2df7"
        },
        "requestID": "d1511daf-2974-11e9-89bf-49b2b85f8c66",
        "eventID": "b12f4ae1-4a8e-48a5-80d9-2fe2dcb49138",
        "readOnly": false,
        "eventType": "AwsApiCall",
        "managementEvent": true,
        "recipientAccountId": "902119125327"
    }
}

So the idea is to match on details.eventSource == "acm.amazonaws.com", details.eventName == "RequestCertificate", and details.requestParameters.validationMethod == "DNS". Call DescribeCertificate on details.responseElements.certificateArn to get the necessary DNS CNAMEs from Certificate.DomainValidationOptions[].ResourceRecord, and put them into Route 53. Once ACM verifies the DNS records, the CloudFormation stack should automatically resume creation.

schlarpc commented 5 years ago

More specific event pattern:

{
    "detail-type": [
        "AWS API Call via CloudTrail"
    ],
    "detail": {
        "eventSource": [
            "acm.amazonaws.com"
        ],
        "eventName": [
            "RequestCertificate"
        ],
        "requestParameters": {
            "validationMethod": [
                "DNS"
            ]
        }
    }
}

To only capture the relevant certificate in the validation Lambda, you probably also want to add the relevant domainName to the event pattern.

schlarpc commented 5 years ago

Better event matching idea: Include a tag in the AWS::CertificateManager::Certificate resource set to a value of AWS::StackId and only match on that event.

CFN would look like:

{
    "Resources": {
        "Certificate": {
            "Type": "AWS::CertificateManager::Certificate",
            "Properties": {
                "DomainName": "bingus.com",
                "ValidationMethod": "DNS",
                "Tags": [
                    {
                        "Key": "AUTOVALIDATE_CLOUDFORMATION_STACK_ID",
                        "Value": {"Ref": "AWS::StackId"}
                    }
                ]
            }
        }
    }
}

Pattern looks like:

{
    "detail-type": [
        "AWS API Call via CloudTrail"
    ],
    "detail": {
        "eventSource": [
            "acm.amazonaws.com"
        ],
        "eventName": [
            "AddTagsToCertificate"
        ],
        "requestParameters": {
            "tags": {
                "key": [
                    "AUTOVALIDATE_CLOUDFORMATION_STACK_ID"
                ],
                "value": [
                    "arn:aws:cloudformation:us-west-2:123456789012:stack/teststack/51af3dc0-da77-11e4-872e-1234567db123"
                ]
            }
        }
    }
}

And you can pluck the certificate ARN off the event at detail.requestParameters.certificateArn:


{
    "version": "0",
    "id": "c7546309-81ea-28ab-ed84-8f45b546d36b",
    "detail-type": "AWS API Call via CloudTrail",
    "source": "aws.acm",
    "account": "902119125327",
    "time": "2019-02-05T19:50:03Z",
    "region": "us-west-2",
    "resources": [],
    "detail": {
        "eventVersion": "1.06",
        "userIdentity": {},
        "eventTime": "2019-02-05T19:50:03Z",
        "eventSource": "acm.amazonaws.com",
        "eventName": "AddTagsToCertificate",
        "awsRegion": "us-west-2",
        "sourceIPAddress": "cloudformation.amazonaws.com",
        "userAgent": "cloudformation.amazonaws.com",
        "requestParameters": {
            "tags": [
                {
                    "value": "arn:aws:cloudformation:us-west-2:123456789012:stack/teststack/51af3dc0-da77-11e4-872e-1234567db123",
                    "key": "AUTOVALIDATE_CLOUDFORMATION_STACK_ID"
                }
            ],
            "certificateArn": "arn:aws:acm:us-west-2:902119125327:certificate/0d585908-f763-451e-ad9f-1dfab0fcc113"
        },
        "responseElements": null,
        "requestID": "3a9e9f89-297f-11e9-85c3-418efeef2042",
        "eventID": "93228a56-41e7-46ea-8c16-681e2e258b75",
        "readOnly": false,
        "eventType": "AwsApiCall",
        "managementEvent": true,
        "recipientAccountId": "902119125327"
    }
}
schlarpc commented 5 years ago

Resolved in 2785cf259