Ugly Green Logo

4n6ir.com

GitHub Organization

Slack Workspace

24 July 2022

Route53 Public Host Zone Pattern

by John Lukach

It’s always DNS (Domain Name System), thus wanted to document a pattern for a Route53 Public Host Zone deployed in AWS (Amazon Web Service) with CDK (Cloud Deployment Kit) using the Python flavor.

The first thing is; first, query logging needs to be enabled, or it didn’t happen! To accomplish this requirement, I must provide a Cloud Watch (CW) Log Group with a Resource Policy with permissions for the Route53 service to create streams and put events for the logs.

account = Stack.of(self).account
region = Stack.of(self).region
        
policy_statement = _iam.PolicyStatement(
    principals = [
        _iam.ServicePrincipal('route53.amazonaws.com')
    ],
    actions = [
        'logs:CreateLogStream',
        'logs:PutLogEvents'
    ],
    resources=[
        'arn:aws:logs:'+region+':'+account+':log-group:*'
    ]
)

resourcepolicy = _logs.ResourcePolicy(
    self, 'resourcepolicy',
    policy_statements = [
        policy_statement
    ],
    resource_policy_name = 'AWSServiceRoleForRoute53'
)

logs = _logs.LogGroup(
    self, 'logs',
    log_group_name = '/aws/route53/fileblock.info',
    retention = _logs.RetentionDays.INFINITE,
    removal_policy = RemovalPolicy.DESTROY
)

hostzone = _route53.PublicHostedZone(
    self, 'hostzone', 
    zone_name = 'fileblock.info',
    comment = 'https://github.com/jblukach/fileblockinfo',
    query_logs_log_group_arn = logs.log_group_arn
)

I always add a Sender Policy Framework (SPF) record to all my domains, even if a Mail Exchanger (MX) record is not present. I do this to notify the Internet that no emails will be sent from this domain to help protect against spoofing.

 spf = _route53.TxtRecord(
    self, 'spf',
    zone = hostzone,
    values = ['v=spf1 -all'],
    ttl = Duration.minutes(300)
)

I also added a Domain-based Message Authentication Reporting and Conformance (DMARC) record without a Domain Keys Identified Mail (DKIM) record in strict mode to help defend the domain’s reputation.

dmarc = _route53.TxtRecord(
    self, 'dmarc',
    zone = hostzone,
    record_name = '_dmarc',
    values = ['v=DMARC1; p=reject; aspf=s; adkim=s;'],
    ttl = Duration.minutes(300)
)

Finally, I enable Domain Name System Security Extensions (DNSSEC) to add authentication and integrity to the resolution requests.

key = _kms.Key(
    self, 'key',
    key_spec = _kms.KeySpec.ECC_NIST_P256,
    key_usage = _kms.KeyUsage.SIGN_VERIFY,
    removal_policy = RemovalPolicy.DESTROY
)

key.add_alias('alias/fileblockinfo')

key_policy_one = _iam.PolicyStatement(
    effect = _iam.Effect(
        'ALLOW'
    ),
    actions = [
        'kms:DescribeKey',
        'kms:GetPublicKey',
        'kms:Sign'
    ],
    principals = [
        _iam.ServicePrincipal('dnssec-route53.amazonaws.com')
    ],
    resources = [
        '*'
    ]
)

key.add_to_resource_policy(
    statement = key_policy_one
)

key_policy_two = _iam.PolicyStatement(
    effect = _iam.Effect(
        'ALLOW'
    ),
    actions = [
        'kms:CreateGrant'
    ],
    principals = [
        _iam.ServicePrincipal('dnssec-route53.amazonaws.com')
    ],
    resources = [
        '*'
    ]
)

key_policy_two.add_condition('Bool', {'kms:GrantIsForAWSResource': 'true'})

key.add_to_resource_policy(
    statement = key_policy_two
)

ksk = _route53.CfnKeySigningKey(
    self, 'ksk',
    hosted_zone_id = hostzone.hosted_zone_id,
    key_management_service_arn = key.key_arn,
    name = 'fileblockinfo',
    status = 'ACTIVE'
)

dnssec = _route53.CfnDNSSEC(
    self, 'dnssec',
    hosted_zone_id = hostzone.hosted_zone_id
)
tags: AWS - Route53 - SPF - DMARC - DKIM - DNSSEC