MNTSQ Techブログ

「MNTSQ(モンテスキュー)」のTechブログです。

スプリットビュー DNS 実践: VPC 内で動く OpenSearch ドメインのカスタムエンドポイント用 SSL 証明書を ACM で管理する

MNTSQ Tech Blog TOP > 記事一覧 > スプリットビュー DNS 実践: VPC 内で動く OpenSearch ドメインのカスタムエンドポイント用 SSL 証明書を ACM で管理する

はじめに

小ネタです。記事タイトルが長いのですが、これは本稿の内容を1行で説明したものになります。

そして OpenSearch に限らない一般的な話題としては

repost.aws

という優れた情報が既にあります。本稿では Terraform コードの例示や背景についての解説を与えることで、付加価値を与えんとするものになります。

課題感

OpenSearchAWS 上のマネージドサービスとして扱う場合、OpenSearch ドメインを外部からのアクセスが可能なものとして構築するか、VPC 内に閉じたものとして構築するかの2択があります*1。後者を選ぶ場合 OpenSearch ドメインへのアクセスは対象の VPC 内からのみアクセスが可能になります

また OpenSearch ドメインには作成後標準で払い出されるエンドポイントとは別にカスタムエンドポイントを設定することができます。これは apex を含む任意ドメインを利用者の所望のものにすることができるものになります*2

さて OpenSearch は通常 HTTPS による通信が強制されており、これは標準のエンドポイント / カスタムエンドポイント 両方で該当します。標準のエンドポイントについては AWS がよしなに SSL 証明書を設定してくれますが、カスタムエンドポイントについてはユーザ側で SSL 証明書を用意しなくてはなりません。

手軽にこのあたりをやるには ACMSSL 証明書を払い出そうという話になり、より手軽にやろうとすればカスタムエンドポイント設定値としてのドメインの存在確認を DNS 検証でやる*3 という段取りになるでしょう。

ただし ACM による DNS 検証は インターネット経由で名前解決が可能な DNS レコード に対してのみ対応可能です。VPC に閉じる構成とした OpenSearch ドメインに対し設定するカスタムエンドポイントで使うドメインをインターネットから名前解決可能とするメリットは無いでしょうから、これも VPC 内からのみ名前解決が可能な Route 53 の private hosted zone で設定することになります*4

つまり カスタムエンドポイントとして使用するドメインはインターネットから名前解決不可 になり、これで何が困るかというと ACM による DNS 検証が不可 となります。

いままでの議論をまとめると以下のようになります。

課題感

スプリットビュー DNS

これを解決するには本稿題名に掲げたスプリットビュー DNS*5が有効でしょう。AWS においては Route 53 に関するドキュメントのうち こちら にあるとおり、Route 53 において private / public の両 hosted zone を組み合わせることで実現が可能です。すなわち

  1. example.com という public な hosted zone を Route 53 に追加する
  2. internal.example.com という private な hosted zone を Route 53 に追加する

これだけです。opensearch.internal.example.com というドメインVPC 内に閉じた OpenSearch ドメインのカスタムエンドポイントとして使用することを考えると

  1. opensearch というレコードで OpenSearch エンドポイントを向く CNAME レコードを private 側の Route 53 hosted zone(internal.example.com に対応)へ設定
  2. opensearch.internal.example.com というドメインに対し ACM 上で SSL 証明書発行
  3. 2. の結果得られた DNS 検証用レコードを public 側の Route 53 hosted zone(opensearch.internal.example.com に対応)へ設定
  4. ここまでに得られた ACMSSL 証明書の ARN とドメイン名とを OpenSearch へカスタムエンドポイントとして設定

という手筈を踏めば、ACMDNS 検証が可能な OpenSearch カスタムドメイン向けの SSL 証明書が手に入り、かつ VPC 内に限って OpenSearch ドメインにてカスタムドメインを使っての通信が可能、という状態にもってゆくことができます*6

opensearch.internal.example.comOpenSearch カスタムエンドポイントを使う図

サンプルコード

上記構想を AWS マネジメントコンソールでやるのは少々骨です。実際に使用している Terraform コードに適宜修正を加えたサンプルコードを例示します

variable "hostzone" {
  type = object({
    external = string
    internal = string
  })
  default = {
    external = "example.com"
    internal = "internal.example.com"
  }
}

variable "custom_endpoint" {
  type     = string
  nullable = true
  default  = "opensearch.internal.example.com"
}

resource "aws_vpc" "main" {
  # 詳細略
}

/*
  Route 53 関連
  各 hosted zone 定義と OpenSearch カスタムエンドポイントとして使うドメインに対応する DNS レコードの設定を行う
 */
resource "aws_route53_zone" "external" {
  name = var.hostzone.external
}

resource "aws_route53_zone" "internal_split_dns" {
  name = var.hostzone.internal
  vpc {
    vpc_id = aws_vpc.main.id
  }
}

resource "aws_route53_record" "opensearch_custom_endpoint" {
  # 本稿の例では常時 true だが var.custom_endpoint が無い場合はレコード作成不要なので条件分岐できるようにしておく
  for_each = (
    var.custom_endpoint != null
    ? { "main" = {} }
    : {}
  )

  zone_id = aws_route53_zone.internal_split_dns.zone_id

  /*
   var.custom_endpoint の apex ドメインは aws_route53_zone.internal_split_dns で指される Route 53 ゾーンに一致する必要がある
   この理屈から当該 apex zone に設定すべき OpenSearch ドメイン用 DNS レコードの値は var.custom_endpoint から apex zone 名を差っ引けば得られる
   */
  name = replace(var.custom_endpoint, aws_route53_zone.internal_split_dns.name, "")

  type = "CNAME"
  ttl  = 60
  records = [
    aws_opensearch_domain.main.endpoint,
  ]
}

/*
  ここから ACM 関連
   証明書払い出しと DNS 検証のための各種設定までを行う
 */
resource "aws_acm_certificate" "internal_split_dns" {
  domain_name       = var.custom_endpoint
  validation_method = "DNS"
}

resource "aws_route53_record" "internal_split_dns" {
  for_each = {
    for dvo in aws_acm_certificate.internal_split_dns.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }
  zone_id         = aws_route53_zone.external.id
  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 600 # 適当に変更可
  type            = each.value.type
}

resource "aws_acm_certificate_validation" "internal_split_dns" {
  certificate_arn         = aws_acm_certificate.internal_split_dns.arn
  validation_record_fqdns = [for record in aws_route53_record.internal_split_dns : record.fqdn]
  timeouts {
    create = "15m"
  }
}

resource "aws_opensearch_domain" "main" {

  /*
    カスタムエンドポイント関連以外の詳細は省略
    実際に OpenSearch ドメインを構築するには他にも必須項目がある
    See: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/opensearch_domain
   */

  domain_endpoint_options {
    enforce_https = true

    custom_endpoint_enabled         = var.custom_endpoint == null ? false : true
    custom_endpoint                 = var.custom_endpoint
    custom_endpoint_certificate_arn = aws_acm_certificate.internal_split_dns.arn
  }

  encrypt_at_rest {
    enabled = true
  }

  node_to_node_encryption {
    enabled = true
  }
}

おわりに

簡単ではありますが OpenSearch 構成の tips について解説する項目となりました。カスタムエンドポイントを使わない場合、OpenSearch のデフォルトでは

https://search-<ドメイン名>-<ランダム文字列>.<リージョン>.es.amazonaws.com

のようになり*7、かつ VPC 内に閉じる場合ではより長く

https://vpc-search-<ドメイン名>-<ランダム文字列>.<リージョン>.es.amazonaws.com

となります。これが本稿の例でいえば

https://opensearch.internal.example.com

として扱えるようになり、シンプルなエンドポイントで OpenSearch を使うことが可能になります。

用途その他の事情で OpenSearch デフォルトのものではないエンドポイントを設定したくなり、またその対象となる OpenSearch ドメインVPC 内からのみ利用可能なものであった場合に、本稿の内容がお役に立てば幸いです。だいぶニッチな話題ではありますが、そういった需要は決してゼロではないと考えています。

MNTSQ 株式会社 SRE 秋本

参考

文中で示した引用以外のものとして、本稿の話題に取り組む前の調査時に 【Route53】スプリットビューDNSの名前解決順序を整理してみた | DevelopersIO を拝読しました。

*1:https://docs.aws.amazon.com/opensearch-service/latest/developerguide/vpc.html

*2:https://docs.aws.amazon.com/opensearch-service/latest/developerguide/customendpoint.html

*3:https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html

*4:OpenSearch / ACM / VPC とお膳立てを AWS 前提として進めてきたので、いきなり DNS 関係も Route 53 を使うこととしてもさして矛盾はしないと思っています

*5:本稿を書く上でこの定義が気になったので調べたところ https://datatracker.ietf.org/doc/html/rfc8499https://datatracker.ietf.org/doc/html/rfc6950/ が有用そうでした。本稿を読む上でこの内容を理解する必要は全くありません。これはほぼ私的なメモです

*6:直下の図中 xN の表記は https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html に拠ります

*7:https://docs.aws.amazon.com/opensearch-service/latest/developerguide/createupdatedomains.html