Networking & Content Delivery

Implementing fine-grained Amazon Route 53 access using AWS IAM condition keys (Part 1)

Users implement multi-account strategies to support multiple teams to deploy workloads. This post is for Amazon Web Services (AWS) administrators and network engineers who need to manage DNS permissions across multiple teams with shared Amazon Route 53 private hosted zone or public hosted zones. There may be situations where multiple teams shared the same hosted zones, and access control is necessary to either allow or deny the update of one or more DNS records within the shared hosted zones. One example is multiple teams operating in a shared subnet with only one shared hosted zone, where each team is delegating the responsibility to manage a specific DNS suffix within the shared hosted zone. Another example is a sandbox shared hosted zone for user to experiment with, and the users are denied management of DNS records for some shared services within the sandbox shared hosted zone.

This post discusses a scalable solution for fine-grained access to Route 53 hosted zones, private or public, to grant conditional access to update subset of DNS records in a shared hosted zone using AWS Identity and Access Management (IAM) condition keys and AWS principal tags. This post guides you through an example of how to grant fine-grained access within the same AWS account for IAM users.

Solution overview

We assume you have familiarity with Route 53 hosted zones for DNS management, IAM policy and permissions for access management, and creating key-value pairs as custom attribute with tags for IAM users. This solution uses the functionalities of IAM condition element and Route53 IAM actions, resources, and conditions keys for fine-grained access of Route 53 hosted zones. IAM policy condition is an optional element in a policy statement that specifies the circumstances under which the policy grants or denies permissions. This solution streamlines access management by allowing you to create fine-grained permissions based on user attributes and reducing the permissions to align with least-privilege principles.

The following diagram shows the fine-grained Route 53 access architecture. The diagram shows how IAM users (left) are assigned a custom tag key “dnsrecords” with values of their permitted DNS updates. When users attempt to modify DNS records in the Route 53 hosted zone (right), the IAM policy (center) evaluates the route53:ChangeResourceRecordSets action and compares the DNS record name update against the user tag value using the condition key route53:ChangeResourceRecordSetsNormalizedRecordNames to grant access.

Diagram shows IAM users on the left with assign custom tag key “dnsrecords” with values of their permitted DNS updates. When users attempt to modify DNS records in the Route 53 hosted zone on the right, the IAM policy in the center evaluates the route53:ChangeResourceRecordSets action and compares the DNS record name update against the user tag value using the condition key route53:ChangeResourceRecordSetsNormalizedRecordNames to grant access.

Figure 1: Fine-grained Route 53 access using IAM condition keys

The following four permission policies show the use of the four condition operators to grant permissions to DNS records. The Route 53 condition key route53:ChangeResourceRecordSetsNormalizedRecordNames controls the DNS record names in the request of route53:ChangeResourceRecordSets action by matching the ${aws:PrincipalTag/{custom-attribute}} key to grant permission to manage specific DNS records. The ${aws:PrincipalTag/{custom-attribute}} condition key specifies the tag value attached to IAM user.

Allow access to specific DNS record using StringEquals

Use the ForAllValues:StringEquals operator when you want to grant permission to manage a specific DNS record name. This approach is ideal when you want to limit users to manage a precisely defined record without allowing access to other records in shared hosted zone. The following permission policy, replacing the {custom-attribute} and {Route53HostedZoneID} with your custom tag attribute and hosted zone ID respectively, allows the update of one DNS record.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": "arn:aws:route53:::hostedzone/{Route53HostedZoneID}",
            "Condition": {
                "ForAllValues:StringEquals": {
                    "route53:ChangeResourceRecordSetsNormalizedRecordNames": [
                        "${aws:PrincipalTag/{custom-attribute}}"
                    ]
                }
            }
        }
    ]
}

This policy and a principal tag value of “svc1.example.com” mean that a user can only manage the DNS record “svc1.example.com”. The same user is denied permission for other records, such as the following:

  • Create new record “api.svc1.example.com
  • Update existing record “marketing.example.com
  • Delete existing record “svc2.example.com

Deny access to specific DNS record using StringNotEquals

Use the ForAllValues:StringNotEquals operator when you want to deny permission to manage a specific DNS record. This approach is ideal when you want to protect a specific DNS record while users can create any DNS record’s shared hosted zone. The following permission policy, replacing the {custom-attribute} and {Route53HostedZoneID} with your custom tag attribute and hosted zone ID respectively, denies the update of one DNS record.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": "arn:aws:route53:::hostedzone/{Route53HostedZoneID}",
            "Condition": {
                "ForAllValues:StringNotEquals": {
                    "route53:ChangeResourceRecordSetsNormalizedRecordNames": [
                        "${aws:PrincipalTag/{custom-attribute}}"
                    ]
                }
            }
        }
    ]
}

This policy and a principal tag value of “svc1.example.com” mean that a user is denied access to manage the DNS record “svc1.example.com”. The user would be allowed to update other DNS records, such as the following:

  • Create new record “api.svc1.example.com
  • Update existing record “marketing.example.com
  • Delete existing record “svc2.example.com

Allow access to DNS records ending with pattern using StringLike

Use the ForAllValues:StringLike operator with wildcard “*” and period “.” in front of the custom attribute to grant permission to manage DNS records in a domain suffix. This approach is ideal when you want to limit users to manage a domain suffix without allowing access to other records in shared hosted zone. The following permission policy, replacing the {custom-attribute} and {Route53HostedZoneID} with your custom tag attribute and hosted zone ID respectively, allows you to update the DNS records in a domain suffix.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": "arn:aws:route53:::hostedzone/{Route53HostedZoneID}",
            "Condition": {
                "ForAllValues:StringLike": {
                    "route53:ChangeResourceRecordSetsNormalizedRecordNames": [
                        "*.${aws:PrincipalTag/{custom-attribute}}"
                    ]
                }
            }
        }
    ]
}

This policy and a principal tag value of “svc1.example.com” mean that a user would be able to manage any DNS records in the “.svc1.example.com” suffix, such as the following:

  • Create new record “api.svc1.example.com
  • Update existing record “dev.svc1.example.com
  • Delete existing record “prod.svc1.example.com

However, the same user would be denied permission for other records, such as the following:

  • Create new record “svc1.example.com
  • Update existing record “marketing.example.com
  • Delete existing record “api.svc2.example.com

Deny access to DNS records ending with pattern using StringNotLike

Use the ForAllValues:StringNotLike operator with wildcard “*” and period “.” in front of the custom attribute to deny the update of DNS records in the domain suffix. This approach is ideal when you want to protect the specific DNS suffix in shared environments while users are allowed to create any DNS records in the shared hosted zone. The following permission policy, replacing the {custom-attribute} and {Route53HostedZoneID} with your custom tag attribute and hosted zone ID respectively, denies the update of DNS records ending with a specific pattern.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": "arn:aws:route53:::hostedzone/{Route53HostedZoneID}",
            "Condition": {
                "ForAllValues:StringNotLike": {
                    "route53:ChangeResourceRecordSetsNormalizedRecordNames": [
                        "*.${aws:PrincipalTag/{custom-attribute}}"
                    ]
                }
            }
        }
    ]
}

This policy and a principal tag value of “svc1.example.com” mean that a user would be denied access to manage DNS records in the “.svc1.example.com” suffix, such as the following:

  • Create a new record “api.svc1.example.com
  • Update an existing record “dev.svc1.example.com
  • Delete an existing record “prod.svc1.example.com

However, the same user is granted permission for other records, such as the following:

  • Create a new record “svc1.example.com
  • Update an existing record “marketing.example.com
  • Delete an existing record “api.svc2.example.com

Solution implementation

You create a solution that allows users to manage only DNS records that match their assigned tag values. Following these steps allows you to configure IAM user tags and policies that enable fine-grained access control to your Route 53 hosted zone. When completed, users can only manage DNS records that match the value in their custom tag.

The example uses the value in the “dnsrecords” custom attribute to grant conditional access to update the DNS records ending with the “.svc1.example.com” domain suffix in the shared hosted zone “example.com” using one permission policy in the same account.

Prerequisites

Before proceeding, you need the following:

  • Hosted zone for domain “example.com
  • IAM user in the same AWS account as the hosted zone
  • Log in to the AWS account of the hosted zone with permissions to update IAM users and permissions

Step 1: Create principal tag

Using the steps in tag IAM users, create the attribute/tag “dnsrecords” with the value of “svc1.example.com” for the IAM users in the AWS Management Console.

You can use the following AWS Command Line Interface (AWS CLI) to create the “dnsrecord” key with the “svc1.example.com” value, replacing <USER_NAME> with your IAM user:

aws iam tag-user --user-name <USER_NAME> --tags '{"Key": "dnsrecord", "Value": "svc1.example.com"}'

Step 2: Create the permission policy

Use the steps in the User Guide Define custom IAM permissions with customer managed policies to create the IAM permission policy using the following JSON policy file for fine-grained access of DNS records in the console. There is a wildcard “*” and period “.” in front of the ${aws:PrincipalTag/dnsrecords} to enable the string match. The value of Z1R8UBAEXAMPLE is the private hosted zone ID, and you must replace it for your hosted zone.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": "arn:aws:route53:::hostedzone/Z1R8UBAEXAMPLE",
            "Condition": {
                "ForAllValues:StringLike": {
                    "route53:ChangeResourceRecordSetsNormalizedRecordNames": [
                        "*.${aws:PrincipalTag/dnsrecords}"
                    ]
                }
            }
        }
    ]
}

You can use the following CLI command to create the permission policy, replacing <POLICY_NAME> with name for policy and <DOCUMENT_FILE> with the location of the preceding JSON policy document:

aws iam create-policy –policy-name <POLICY_NAME> --policy-document <DOCUMENT_FILE>

Step 3: Attached the permission policy to the IAM user or group

Attach the permission created in Step 2 to the IAM user with the custom tag in Step 1 or the group that the user is in to enable the fine-grained hosted zone access control for the user. Use the steps in the User Guide Changing permission for an IAM user to attach permission to the user, or use the steps in the User Guide Attach a policy to an IAM user group to attach permission to a group in the console.

You can use the following CLI command to attach permission policy to an IAM user, replacing <POLICY_ARN> from the policy created in Step 2 and <USER_NAME> with the IAM user created in Step 1:

aws iam attach-user-policy --policy-arn <POLICY_ARN> --user-name <USER_NAME>

You can use the following CLI command to attach permission policy to an IAM group, replacing <POLICY_ARN> from the policy created in Step 2 and <GROUP_NAME> with the group containing the IAM user created in Step 1:

aws iam attach-user-policy --policy-arn <POLICY_ARN> --group-name <GROUP_NAME>

Step 4: Verified DNS update permissions

The IAM user now has permission to managed DNS records in the “.svc1.example.com” domain suffix in the shared hosted zone “example.com”. You can verify the permission is working as expected by using following the steps in the Developer Guide Route 53 console to edit DNS records or the Developer Guide change-resource-record-sets CLI commands.

For example, using the following CLI command with the JSON configuration file to create the DNS record “dev.svc1.example.com” can succeed.

aws route53 change-resource-record-sets --hosted-zone-id Z1R8UBAEXAMPLE --change-batch file://svc1-create.json
{
    "Comment": "configuration to create dev.svc1.example.com record",
    "Changes": [
        {
            "Action": "CREATE",
            "ResourceRecordSet": {
                "Name": "dev.svc1.example.com",
                "Type": "A",
                "TTL": 300,
                "ResourceRecords": [
                    {
                        "Value": "10.1.1.1"
                    }
                ]
            }
        }
    ]
}

Using the following CLI command with the JSON configuration file to create the DNS record “dev.svc2.example.com” failed.

aws route53 change-resource-record-sets --hosted-zone-id Z1R8UBAEXAMPLE --change-batch file://svc2-create.json
{
    "Comment": "configuration to create dev.svc2.example.com record",
    "Changes": [
        {
            "Action": "CREATE",
            "ResourceRecordSet": {
                "Name": "dev.svc2.example.com",
                "Type": "A",
                "TTL": 300,
                "ResourceRecords": [
                    {
                        "Value": "10.1.1.1"
                    }
               ]
            }
        }
    ]
}

Considerations

Although the route53:ChangeResourceRecordSetsNormalizedRecordNames condition is a multi-valued key and can match on multiple values using comma “,” to separate the value, the tag value cannot contain commas “,” to separate multiple values. Therefore, the custom attribute can only contain one DNS record value to work with a permission policy. If there is a need for access control to multiple DNS record values, then you must use multiple custom attributes to work with the corresponding permission policy.

More IAM policy conditions for Route 53 are available for more access controls on DNS records. The route53:ChangeResourceRecordSetsActions condition key can limited actions to CREATE, UPDATE, and DELETE. The route53:ChangeResourceRecordSetsRecordTypes condition key can limit actions on DNS record types.

As you manage your IAM access, consider the IAM quotas and character limitations for users, groups, and policies.

If your permissions are not working as expected, then the troubleshoot IAM Policies documentation includes useful guides for common issues.

For environments not using shared hosted zones, where each team manages private hosted zones for their services, Route 53 Profiles is the recommended best practice for centralized management of DNS configurations for Amazon Virtual Private Clouds (Amazon VPCs) and accounts. The post Using Amazon Route 53 Profiles for scalable multi-account AWS environments reviews the architecture.

Conclusion

In this post, we reviewed a scalable solution of using AWS IAM conditional keys and AWS principal tags for fine-grained access control of shared Amazon Route 53 hosted zones for multiple teams to managed subsets of DNS records in a shared hosted zone. The review included an example of implementing conditional access of a hosted zone within the same account. In the next two posts, we review cross-account and federated user fine-grain access with IAM conditional keys and AWS principal tags.

About the author

Daniel Yu Headshot

Daniel Yu

Daniel Yu is a Senior Technical Account Manager who partners with Enterprise Support users to optimize their cloud transformation initiatives. With expertise in networking and security infrastructure, he specializes in delivering strategic guidance on AWS architectural design and operational excellence.