Passer au contenu

DNSSEC on Route 53 for Cross-Account Delegated Zones

0

Background

I have an AWS account layout that looks like this:

Enter image description here

More specifically, the DNS account is where rootdomain.com is registered and its nameservers live there as well.

For the various environment accounts, I have created a Route 53 hosted zone and delegation set therein, and in the DNS account, I simply have NS records that point to the name servers in the various accounts, delegating the entire subdomain to each account.

I'm managing all of this in Terraform, so in the DNS account, my code looks like this:

workspaces/account/dns/
variable prod_name_servers {
  type = list(string)
}

resource aws_route53_zone root {
  # ...
}

# forward prod.rootdomain.com to the production account
resource aws_route53_record prod_ns {
  name = "prod.rootdomain.com"
  type = "NS"
  ttl = 60
  records = var.prod_name_servers
  zone_id = aws_route53_zone.root.id
}

In the prod account, my code looks like this:

workspaces/account/prod
resource aws_route53_zone prod {
   # ...
}

resource aws_route53_delegation_set prod {}

output prod_name_servers {
  value = aws_route53_delegation_set.prod.name_servers
}

This is a fairly straightforward way to delegate zones across accounts without setting up cross-account permissions.

Problem

I'm looking to set up DNSSEC for all zones, including those in the environment accounts (read: not in the DNS account).

Following the examples from the Terraform AWS provider docs, it seems necessary to declare an aws_kms_key for signing records, an aws_route53_key_signing_key for the KSK, and aws_route53_hosted_zone_dnssec to bind things together:

workspaces/account/dns/
# define key policy
data aws_iam_policy_document key_policy {
  statement {
    sid = "AllowR53DNSSECSigning"
    effect = "Allow"
    actions = [
      "kms:DescribeKey",
      "kms:GetPublicKey",
      "kms:Sign",
      "kms:Verify",
    ]
    resources = ["*"]
    principals {
      type = "Service"
      identifiers = ["dnssec-route53.amazonaws.com"]
    }
  }

  statement {
    sid = "AllowIAMRootUserPerms"
    effect = "Allow"
    actions = ["kms:*"]
    resources =["*"]
    principals {
      type = "AWS"
      identifiers = ["arn:aws:iam::${var.account_id}:root"]
    }
  }
}

resource aws_kms_key default {
  customer_master_key_spec = "ECC_NIST_P256"
  deletion_window_in_days = 7
  key_usage = "SIGN_VERIFY"
  policy = data.aws_iam_policy_document.key_policy.json
}

resource aws_route53_key_signing_key root {
  hosted_zone_id = aws_route53_zone.root.id
  key_management_service_arn = aws_kms_key.default.arn
  name = "rootdomain.com"
}

resource aws_route53_hosted_zone_dnssec root {
  hosted_zone_id = aws_route53_zone.root.id
  depends_on = [aws_route53_key_signing_key.root]
}

So with this code, it appears that this will cause Route 53 to enable DNSSEC for rootdomain.com and all records within that zone.

Here's the issue: since I'm delegating prod.rootdomain.com to another account, how do I set up signing for that account?

It appears that I need to create a DS record for prod.rootdomain.com within the DNS account and fill this data with the KMS key information in the production account, but since KMS is rotating keys regularly, how do I keep the DS record updated?

demandé il y a 6 mois178 vues
1 réponse
0

I was able to get it working and I'll detail how I did for posterity.

Background

Once again, our setup looks like this:

  • in main account we have an aws_route53_zone and aws_route53_delegation_set for rootdomain.com and we have updated our registrar (in this case Route 53) with the name servers so that resolution works over public internet.
  • in child account we have an aws_route53_zone and aws_route53_delegation_set for child.rootdomain.com
  • in main account we have a NS child.rootdomain.com pointing to the child delegation set, so DNS queries will be resolved there.

And we can consider that all of this works because it does.

Step 1: Root Domain

The first step is to get DNSSEC validating on rootdomain.com, so we'll be operating in the main account.

First, we'll set up the Route 53 resources for the zone:

resource aws_route53_key_signing_key default {
  name = "rootdomain.com"  # can be any name, is name of key
  hosted_zone_id = aws_route53_zone.default.id
  key_management_service_arn = aws_kms_key.zsk.arn
}

resource aws_route53_hosted_zone_dnssec default {
  hosted_zone_id = aws_route53_key_signing_key.default.hosted_zone_id
  signing_status = "SIGNING"  # or "NOT_SIGNING"
}

The aws_route53_key_signing_key is, unsurprisingly, the key-signing key (ZSK), and it is bound to a KMS key which will serve as the zone-signing key (ZSK). The aws_route53_hosted_zone_dnssec resource adds DNSSEC records to the hosted zone and enables record signing.

Next, we'll create the KMS zone-signing key, but we'll delay on the key policy for now:

resource aws_kms_key zsk {
  customer_master_key_spec = "ECC_NIST_P256"
  key_usage = "SIGN_VERIFY"
  deletion_window_in_days = 7
  policy = data.aws_iam_policy_document.kms_key_policy.json
}

And now, we'll get into the KMS key policy:

data aws_iam_policy_document kms_key_policy {
  statement {
    sid = "AllowR53DNSSECSignVerify"
    effect = "Allow"

    actions = [
      "kms:DescribeKey",
      "kms:GetPublicKey",
      "kms:Sign",
      "kms:Verify",
    ]

    resources = ["*"]

    principals {
      type = "Service"
      identifiers = ["dnssec-route53.amazonaws.com"]
    }
  }

  statement {
    sid = "AllowR53DNSSECCreateGrants"
    effect = "Allow"
    actions = ["kms:CreateGrant"]
    resources = ["*"]

    principals {
      type = "Service"
      identifiers = ["dnssec-route53.amazonaws.com"]
    }

    condition {
      test = "Bool"
      variable = "kms:GrantIsForAWSResource"
      values = ["true"]
    }
  }

  statement {
    sid = "AllowEditKeyPolicy"
    effect = "Allow"
    actions = ["kms:*"]
    resources = ["*"]

    principals {
      type = "AWS"
      identifiers = ["arn:aws:iam::${var.account_id}:root"]
    }
  }
}

The first two statements grant the Route 53 DNSSEC service to sign/verify/create grants for the zone-signing key. The final statement is required to grant an IAM principal the ability to edit the key policy. You can replace the principals.identifiers with a list of IAM principals you'd like to be able to edit the key policy. At minimum, there must be one principal here and the default policy uses the root user like I did above.

DNSSEC with the TLD Registrar

The next step depends on who your registrar is.

If your registrar is not Route 53, you'll need to get the value of aws_route53_key_signing_key.default.ds_record, and you'll need to provide your registrar with this value.

If your registrar is Route 53, you can use this resource instead:

resource aws_route53domains_delegation_signer_record default {
  domain_name = "rootdomain.com"

  signing_attributes {
    algorithm = aws_route53_key_signing_key.default.signing_algorithm_type
    flags = aws_route53_key_signing_key.default.flag
    public_key = aws_route53_key_signing_key.default.public_key
  }
}

Once this is done, congratulations, your root domain should now be signing/validating DNSSEC! You can use delv @1.1.1.1 rootdomain.com to see that your domain is fully validated.

Step 2: Child Domain

For the child domain in the child account, follow all steps above with the exception of the last one, the aws_route53domains_delegation_signer_record or whatever you need to do for your TLD registar. We are no longer interacting with the TLD, we need to interact with our parent zone.

Create the KMS key, the KSK key, and the DNSSEC hosted zone resources in the child account and then proceed.

The final step to get your child domain validated is to add a DS record on the rootdomain.com zone, so we will switch back to the main account and the rootdomain.com zone.

All you need to do is add a record of type DS named child.rootdomain.com, and provide it with the DS record value from the KSK in the child account:

resource aws_route53_record child_ds {
  name = "child.rootdomain.com"
  type = "DS"
  ttl = 60
  records = [var.child_zone_ds_value]

  zone_id = aws_route53_zone.root.id
}

Once this is done, everything should work! When DNSSEC validation occurs, the TLD will be asked for the DS key for your domain, that will resolve to your root domain's KSK, we'll then be able to hop into your rootdomain.com zone, we'll get a signed NS and DS record for child.rootdomain.com with the DS record holding the public key digest of the child KSK, and then finally we'll validate the child domain and everything will be green.

You can plug your child zone into Verisign's DNSSEC Debugger and validate the whole chain down to your child zone.

The best part for me personally is that to get this set up does not involve doing weird things like cross-account permissions. It works mostly the same as just regular NS delegation with the caveat of needing to propagate the child zone's DS record up into the parent.

I sincerely hope this helps someone out there in the future.

répondu il y a 6 mois

Vous n'êtes pas connecté. Se connecter pour publier une réponse.

Une bonne réponse répond clairement à la question, contient des commentaires constructifs et encourage le développement professionnel de la personne qui pose la question.