電通総研 テックブログ

電通総研が運営する技術ブログ

DNSの概要から掴むAWSにおけるDNSSEC実装方法 (Terraform)

はじめに

金融IT本部 2年目の坂江 克斗です。
業務にてDNSSECを使用する機会があり、とても面白い内容だったのでまとめてみました。
初学者の視点で疑問に感じる部分も含め、基本的な概念から丁寧に解説できればと思います。
(概要説明が不要な方は、Terraformによる実装の章のみご参照ください)
本記事は電通総研 Advent Calendar 2025 5日目の記事です。

DNSの概要

DNSとは

DNS(Domain Name System)は、インターネット上で人間が読みやすい名前(ドメイン名)とIPアドレスを対応付ける仕組みです。
コンピュータは通信の際に使用するIPアドレス(数値やビット形式)の方が便利ですが、人間にとっては「example.com」のような文字列(ドメイン名)の方が読みやすく便利です。
DNSは、こうした人間に分かりやすいドメイン名から機械が扱うIPアドレスに変換(名前解決)する役割を担っています。
なお、ドメイン名を省略せずに完全な形で記載したものをFQDN(Fully Qualified Domain Name) と呼びます。

名前解決の流れ (DNSサーバとリゾルバ、レジストラの関係)

全てのドメイン名と対応するIPアドレスを一つのサーバで管理するのは、データ量や処理速度の面から現実的ではありません。
そのためDNSでは、「.」で区切られた各ドメインごとに専用のサーバ(後述する権威サーバ)が分散して管理を行っています。

各権威サーバが独立して情報を持つだけでは、名前解決をする際に直接目的の権威サーバにアクセスする必要があり、全ての権威サーバのアドレスを把握しておかなければならず、現実的ではありません。
そのため、親ドメインの権威サーバが子ドメインの権威サーバのアドレスを持つ、つまりあるドメイン(例:com)に「○○.」を加えたドメイン(例:sample.com)の権威サーバの場所を持つことで、親から子を辿り目的の権威サーバまでアクセスする仕組みが使用されています。

ドメインから子ドメインに向かって根が張っているようなこの仕組みにより、どんなに長いドメイン名に対しても目的の権威サーバまで効率的にたどり着ける構成になっています。


上記の内容を現実のリソースに合わせて具体的に示します。
ドメイン解決で登場するリソース名とその関係は、以下の図に示す形になります。

権威サーバに聞きまわって解決するのはフルサービスリゾルバ(再帰リゾルバとも呼ばれます)が担当していることが分かります。
特に、権威サーバの中でも最初にアクセスする「.」ドメインを管理するサーバをルートサーバと呼び、ルートサーバがレコードとして管理する「.com」や「.jp」等をTLDトップレベルドメイン と呼びます。

また、先述した権威サーバの宛先情報を持つのがNSレコードであり、ドメイン名とIPアドレスを紐付けるのがA/AAAAレコード(IPv4だとA、IPv6だとAAAA)となります。

NSレコードの補足 NSレコードにはドメイン名が値として設定されます。そのため、子ドメインにアクセスする場合は、親ドメインに登録されたNSレコードを取得した後、NSレコードに設定されていたドメイン名をさらに名前解決する必要があります。 もし、そのドメイン名が子ドメインと同じゾーンに属している場合、再度親ドメインから解決する循環参照に陥ってしまいます。この問題を防ぐために、親ドメイン側にNSレコードとともにGlueレコード (権威サーバのドメインに対応するA/AAAAレコード)を設定する必要があります。


ルートサーバやTLD用の権威サーバは特に名前解決の根幹となるサーバであり、ゾーン情報が適切に管理される必要があります。
現在はICANN (The Internet Corporation for Assigned Names and Numbers) によりルートサーバを管理、各TLDの権威サーバ・ゾーン情報の管理をレジストリが担当し、消費者への仲介をレジストラが担当しています。

細かい話になりましたが、ここではドメインのレコードを管理・応答するのが権威サーバ、権威サーバが管理する論理的なドメイン単位の管理領域をゾーン(AWSではホストゾーン)、名前解決を頑張るのがフルサービスリゾルバとだけ覚えていただければ十分です。

DNSSECの概要

通常のDNS脆弱性

DNSによって、ユーザはドメインから情報 (IPアドレス等) を取得します。
しかし、セキュリティ攻撃によってレコード情報が改ざんされると、例えばドメイン解決により取得したIPアドレスを使用した場合に悪意のあるWebサイト(フィッシングサイト等)にアクセスさせられる可能性もあります。
改ざんが発生するセキュリティ攻撃の例としては以下の図に示す、DNSハイジャックによるゾーン情報の改ざん、DNSキャッシュポイズニング、中間者攻撃(の一例)等があげられます。

では、レコード情報が改ざんされていることをどのように検出すればよいでしょうか?


今回紹介するDNSSEC (Domain Name System Security Extensions) は、まさにこの問題を解決するための仕組みになります。
以下はJPRSによるDNSSECの説明となります。

DNS応答に電子署名を追加し、問い合わせ側で検証することでDNSの攻撃耐性を向上させる、セキュリティ拡張機能です。DNSSECは、受け取ったDNSレコードの出自(送信元で登録したデータであること)・完全性(データの欠落や改ざんのないこと)を問い合わせ側で検証できるようにするための機能をDNSに追加します。

ざっくりしたイメージとしては、親から子への信頼の連鎖(信頼チェーン)を構成し、改ざんされたとしても信頼が崩れた箇所で改ざんを検知できるものとなっています。

ただし、以下に示す前提の上でDNSSECがセキュリティを担保している点に注意してください。

  • クライアントから正しいフルサービスリゾルバへ接続していること
  • フルサービスリゾルバから正しいルートサーバへ接続していること
  • ルートサーバが正しい情報を持っていること

次節から具体的に説明します。

DNSSECで登場するリソース

おさえるべきリソースは、2種類の鍵と3種類のレコードとなります。
リソースの一覧表および設定フローのイメージ図を以下に示します。

種類 名称 親/子 役割
ZSK (Zone Signing Key) ゾーン内のリソースレコードに署名するための非対称鍵(公開鍵・秘密鍵のペア)
KSK (Key Signing Key) ZSKの公開鍵自体を署名するための非対称鍵
レコード RRSIG (Resource Record Signature) リソースレコードセット(同じレコード名、Typeでまとめた塊)毎に、ZSKの秘密鍵により作成した署名 を持つレコード。署名とともに署名元のレコード、鍵の情報も持つ。
DNSKEY ゾーンの公開鍵(ZSKとKSKの公開鍵) を持つレコード。レコードのFlagが256のものがZSK、257のものがKSK
DS (Delegation Signer) 子ゾーンのKSKハッシュ値を持つレコード。信頼チェーンの肝

ZSKとKSKの補足 DNSKEYレコード(ZSK) をそのままDSレコードとの紐付けに使用すれば、KSKが必要ないと思われます。しかし、実際にはセキュリティとメンテナンス性の観点から役割が分かれています。 RFC 4641を参考に、私はZSK・KSKの推奨方針を以下のように解釈しています。

・ 署名(暗号化)の管理観点で鍵をこまめにローテーションすべきである。しかし、ローテーション毎に親ゾーンのDSレコード更新が発生すると運用コストが高くなる。
・ そのため、ZSKとKSKの2つの鍵ペアを用い、ZSKを頻繁にローテーションし、KSKを長期間使用する方針が推奨される。これにより、親ゾーンのDSレコード更新頻度を下げる。
・ KSKはZSKの署名に使用されるのみであり、大量のデータ署名には使用しない。そのため、強度の高い鍵(より大きなビット長のキーマテリアル)を採用できる。
・ KSKの使用頻度の低さから、ZSKよりも安全な場所(HSM)に保管できる。

DNSSECの署名検証フロー

前節で示した設定フローイメージを基に考えると、aa.comのAレコードを解決する際の署名検証は以下のような流れで行われます。

  1. 親ゾーンのDSレコードと子ゾーンのDNSKEYレコード(KSK)を検証し、子ゾーンのKSKの正当性を確認します。
  2. 子ゾーンのKSKを使用して、子ゾーンのDNSKEYレコード(ZSK)とRRSIGレコードを検証し、子ゾーンのZSKの正当性を確認します。
  3. 子ゾーンのZSKを使用して、子ゾーンのAレコードとRRSIGレコードを検証し、子ゾーンのAレコードの正当性を確認します。


上記は1組の親子ゾーンに対しての検証フローになりますが、ルートゾーンからの検証も同様の仕組みで行われます。
ただし、ルートゾーンのKSK、ZSK、DSレコードは信頼できる前提となっています。

このDSレコードによる署名検証の連鎖こそがDNSSECの信頼チェーンの肝であり、DNSSECで一番面白いと思うポイントになります。
以上がDNSSECの概要となります。

(補足)DNSSECが確認できるサイト

DNSSECが適用されているかどうかは、digコマンドでレコードを確認していくことでも可能ですが、DNSVizがオススメです。

  1. 確認したいドメイン名を入力し、以下に示すAnalyzeタブにてAnalyzeボタンを押下します。
    ※明示的にAnalyzeボタンを押下しない限り、情報が更新されないことに注意

  2. 処理が終わった後に、Continueボタンを押下すると、DNSSECの設定状況が図でわかりやすく表示されます。

AWSにおけるDNSSECの実装方法 (Terraform)

前提

Terraformの実装

小規模なのでlocalで構成します。基本設定は以下になります。

terraform {
  required_version = "~> 1.14.0"
  required_providers {
    aws = {
      version = "6.23.0"
      source  = "hashicorp/aws"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}
provider "aws" {
  alias  = "global"
  region = "us-east-1"
}


locals {
  domain    = "ks-sample.com"
  subdomain = "subdomain.${local.domain}"
}

data "aws_caller_identity" "current" {}

ホストゾーンの定義をします。Route53の購入ドメインに対しては、自動でホストゾーンが作成されているためdata sourceを使用して取得します。
また、ホストゾーンを作成するだけでは、親ドメイン側にサブドメインの権威サーバの宛先情報(NSレコード)が存在せずサブドメインの名前解決ができないため、明示的に親ホストゾーンにサブドメインのNSレコードを追加します(サブドメイン委譲)。

# Hosted zone
data "aws_route53_zone" "registered_domain" {
  name = local.domain
}
resource "aws_route53_zone" "subdomain" {
  name = local.subdomain
}
resource "aws_route53_record" "subdomain-ns" {
  zone_id = data.aws_route53_zone.registered_domain.zone_id
  name    = local.subdomain
  type    = "NS"
  ttl     = "30"
  records = aws_route53_zone.subdomain.name_servers
}

KMSカスタマー管理キーを定義します。DNSSECで使用するカスタマー管理キーは、以下の4つの設定が必須となります。

  • バージニア北部(us-east-1)で作成
  • 署名用の非対称鍵:SIGN_VERIFY
  • 鍵の形式:ECC_NIST_P256
    • 内部的には署名アルゴリズムECDSA_SHA_256を使用。RFC 8624を参照すると、RSA系に比べて短い署名長かつより高い暗号強度を持つことからECDSA系が推奨されています。
  • Route53サービスがKMSキーにアクセスするためのキーポリシー
# KMS key
resource "aws_kms_key" "kms_key_global" {
  provider                 = aws.global
  description              = "Key for DNSSEC"
  key_usage                = "SIGN_VERIFY"
  customer_master_key_spec = "ECC_NIST_P256"
  enable_key_rotation      = false
  policy = jsonencode({
    Statement = [
      {
        Action = [
          "kms:DescribeKey",
          "kms:GetPublicKey",
          "kms:Sign",
        ],
        Effect = "Allow"
        Principal = {
          Service = "dnssec-route53.amazonaws.com"
        }
        Sid      = "Allow Route 53 DNSSEC Service",
        Resource = "*"
        Condition = {
          StringEquals = {
            "aws:SourceAccount" = data.aws_caller_identity.current.account_id
          }
          ArnLike = {
            "aws:SourceArn" = "arn:aws:route53:::hostedzone/*"
          }
        }
      },
      {
        Action = "kms:CreateGrant",
        Effect = "Allow"
        Principal = {
          Service = "dnssec-route53.amazonaws.com"
        }
        Sid      = "Allow Route 53 DNSSEC Service to CreateGrant",
        Resource = "*"
        Condition = {
          Bool = {
            "kms:GrantIsForAWSResource" = "true"
          }
        }
      },
      {
        Action = "kms:*"
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
        }
        Resource = "*"
        Sid      = "Enable IAM User Permissions"
      },
    ]
    Version = "2012-10-17"
  })
}

KSKの作成、DNSSECの有効化(内部的にDNSKEYレコードやRRSIGレコードの作成・署名設定を行う処理をトリガーするものと考えられます)を定義します。
DNSSECの有効化時にKSKが作成済みになっている必要があるため、hosted_zone_id = aws_route53_key_signing_key.registered_domain.hosted_zone_idのように設定して暗黙的に依存関係を定義します。

# KSK : https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_key_signing_key
resource "aws_route53_key_signing_key" "registered_domain" {
  provider                   = aws.global
  hosted_zone_id             = data.aws_route53_zone.registered_domain.id
  key_management_service_arn = aws_kms_key.kms_key_global.arn
  name                       = "ksk-${replace(local.domain, ".", "-")}"
}
resource "aws_route53_key_signing_key" "subdomain" {
  provider                   = aws.global
  hosted_zone_id             = aws_route53_zone.subdomain.id
  key_management_service_arn = aws_kms_key.kms_key_global.arn
  name                       = "ksk-${replace(local.subdomain, ".", "-")}"
}

# DNSSEC
resource "aws_route53_hosted_zone_dnssec" "registered_domain" {
  hosted_zone_id = aws_route53_key_signing_key.registered_domain.hosted_zone_id
}
resource "aws_route53_hosted_zone_dnssec" "subdomain" {
  hosted_zone_id = aws_route53_key_signing_key.subdomain.hosted_zone_id
}

最後にDSレコードの定義をします。
購入ドメインの場合は親ドメインTLDとなるため、専用のaws_route53domains_delegation_signer_recordを使用してDSレコードの作成を行います。一方、サブドメイン側は親ドメインがRoute 53ホストゾーンの管理内のため、DSレコードを直接作成する形になります。
また、depends_on で定義している依存関係は、以下の状態を満たしたうえでDSレコードを作成するためのものです。

  • 対象ホストゾーンで DNSSEC が有効化されていること
  • NS レコードが作成され、サブドメインの名前解決が可能な状態になっていること
  • 親ゾーンから順番に DNSSEC の信頼チェーンを繋げていく(DSレコードを作成する)こと
# DS Record
# Registered domain TLD専用:https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53domains_delegation_signer_record
resource "aws_route53domains_delegation_signer_record" "registered_domain" {
  depends_on = [
    aws_route53_hosted_zone_dnssec.registered_domain
  ]
  domain_name = local.domain
  signing_attributes {
    algorithm  = aws_route53_key_signing_key.registered_domain.signing_algorithm_type
    flags      = aws_route53_key_signing_key.registered_domain.flag
    public_key = aws_route53_key_signing_key.registered_domain.public_key
  }
}
# Subdomain
resource "aws_route53_record" "subdomain-ds" {
  depends_on = [
    aws_route53_hosted_zone_dnssec.subdomain,
    aws_route53domains_delegation_signer_record.registered_domain,
    aws_route53_record.subdomain-ns
  ]
  zone_id = data.aws_route53_zone.registered_domain.zone_id
  name    = local.subdomain
  type    = "DS"
  ttl     = "30"
  records = [aws_route53_key_signing_key.subdomain.ds_record]
}

Applyの結果

terraform applyを実行すると、エラーなく数分で完了しました。

PS C:/path> terraform apply

(中略)

Plan: 9 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

(中略)

Apply complete! Resources: 9 added, 0 changed, 0 destroyed.

Apply直後にDNSVizを確認すると、正常にDNSSECが設定されていることが確認できます。(黄色のWARNマークに関しては、AAAAレコードに関する注意であり今回は関係ありません)

Destroyの結果

terraform destroyを実行すると、同様にエラーなく数分で完了しました。

PS C:\path> terraform destroy

(中略)

Plan: 0 to add, 0 to change, 9 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

(中略)

aws_route53_zone.subdomain: Destruction complete after 33s

Destroy complete! Resources: 9 destroyed.

destroy直後にks-sample.comに対してDNSVizを確認すると、DNSSEC設定が削除されていることが確認できます。
NSEC3レコードは「ある名前やタイプのレコードが存在しない」ことを証明するための特殊なレコードで、今回は親ゾーン側でDSレコードが存在しないことを示しています。

おわりに

DNSSECに関連するレコードだけを見ていると一見とても難しく感じますが、今回のように図に落としてみると、署名検証の流れがシンプルかつ美しく整理されていることが分かります。
また、実装において依存関係に迷う部分もありましたが、現在はAWSを使用することでDNSSECを簡単に設定できますので、セキュリティ要件にある方は是非参考にしていただければと思います。

私たちは一緒に働いてくれる仲間を募集しています!

電通総研 キャリア採用サイト 電通総研 新卒採用サイト

執筆:@sakae.katsuto
レビュー:Ishizawa Kento (@kent)
Shodoで執筆されました