MNTSQ Techブログ

リーガルテック・カンパニー「MNTSQ(モンテスキュー)」のTechブログです。

PrivateLinkを使ってクロスアカウント & クロスVPCでセキュアなデータベースアクセスを実現する

MNTSQ Tech Blog TOP > 記事一覧 > PrivateLinkを使ってクロスアカウント & クロスVPCでセキュアなデータベースアクセスを実現する

SREチームの藤原です。

サービスを運用しているとデータ分析や他システムとのデータ連携などで、DBへの読み取り専用アクセスが必要になる場合は多々あると思います。 本エントリでは、AWS PrivateLinkを活用することで、セキュアなクロスアカウントかつ、クロスVPCなアクセスを実現できるのでその方法を解説します。

aws.amazon.com

構成のイメージ

本エントリでは、2つのアカウントの間でのクロスアカウント & クロスVPCアクセスを前提とします。 アカウントAがアクセスする側、アカウントBがアクセスされる側とします。

PrivateLink設定前のアカウントBは図1のような構成です。

図1. アカウントB(アクセスされる側)の構成

この環境に対して、他のAWSアカウント(この図ではアカウントA)のVPCからアクセスしたいケースを想定しています。 具体的には図2のような構成を想定しています。

図2. アカウントAからアカウントBのRDSへのアクセスイメージ

ここではアカウントAからアカウントBへの具体的なアクセス方法については示していません。 PrivateLinkを使った接続をする前にVPCピアリングや、Transit Gatewayなどのサービスを使ったVPC間接続と今回の要件における課題について述べたのち、PrivateLinkを使った接続方法について解説します。

VPCピアリングやTransit Gatewayを使ったVPC間接続とその課題

クロスアカウント、クロスVPCでのリソースアクセスを考えると、昔からAWSを使っている人であれば、VPCピアリング(VPCピア機能)とAWS Transit Gatewayの活用が浮かぶと思います。

docs.aws.amazon.com

aws.amazon.com

今回これでも要件は達成できるのですが、注意点が2つほど存在します。

ひとつ目がVPCネットワークのCIDRが重複している場合はVPCピアリングもTransit Gatewayも使えません。 もうひとつがセキュリティ的な課題です。 VPCピアリングやTransit Gatewayでの接続はIP層でネットワークを直接繋げるような形になります。 このような場合、意図しないリソースへのアクセスを防ぐためには、セキュリティグループなどの設定を細かく設定、管理しなければいけません。

今回の事例では、CIDRの重複は発生していませんが、意図しないリソースへのアクセスの可能性を排除する目的で、PrivateLinkを活用することとしました。

PrivateLinkを使ったセキュアなVPC間接続

PrivateLinkを活用した場合のVPC間の接続は次の図3のようになります。 VPCエンドポイントを介してアカウントA、BそれぞれのVPC間を接続し、Aurora MySQLのリーダーエンドポイントのみへの到達性を確保します。

図3. PrivateLinkを使った接続

以降では、まずアカウントB側の準備を行い、その後でアカウントA側の準備を進めていきます。 なお、Terraformでの定義例を示していますが、アカウントAとBのリソースはそれぞれ異なるstateファイルで管理していることを前提としています

アカウントB(アクセスされる側)でのリソースの準備

まず、アカウントB側でResource GatewayとResource Configurationsを作成します(図4)。

図4. Resource ConfigurationとResource Gateway

AWS VPC Latticeにおいて、Resource Gatewayに対応するTerraformのリソースはaws_vpclattice_resource_gatewayです。

このリソースはTerraformを利用する場合、次のように定義します。

resource "aws_vpclattice_resource_gateway" "gateway" {
  name               = Resource Gatewaysの名前
  vpc_id             = Resource Gatewayを設置するVPCのID
  subnet_ids         = [Resource Gatewayを設置するサブネットIDのリスト]
  security_group_ids = [Resource Gatewayに設定するセキュリティグループIDのリスト]
}

Resource Configurationに対応するTerraformのリソースはaws_vpclattice_resource_configurationです。

resource "aws_vpclattice_resource_configuration" "configuration" {
  name                        = Resource Configurationの名前
  resource_gateway_identifier = aws_vpclattice_resource_gateway.gateway.id

  port_ranges = ["3306"]
  protocol    = "TCP"

  resource_configuration_definition {
    dns_resource {
      domain_name     = Aurora MySQLのリーダーエンドポイントのFQDN
      ip_address_type = "IPV4"
    }
  }
}

resource_gateway_identifierパラメータで接続先となるResource GatewayのIDを指定します。 resource_configuration_definitiondns_resourceブロックで接続先となるリソースのドメインを指定します。 Aurora MySQLのリーダーエンドポイントのFQDNをここで指定することにより、PrivateLinkの接続先をリーダーエンドポイントのみに限定できます

次に、アカウントAからVPCエンドポイントを通じて接続できるようにする必要があります。 このためにAWS Resource Access Manager(以降、AWS RAM)を利用して、Resource ConfigurationをアカウントAに共有します

aws.amazon.com

RAMで共有するリソースをTerraformで定義する場合は、aws_ram_resource_shareaws_ram_principal_associationaws_ram_resource_associationの3つのリソースを利用します。

resource "aws_ram_resource_share" "resource_configuration_share" {
  name                      = リソース共有の名前
}

resource "aws_ram_principal_association" "resource_configuration_share" {
  resource_share_arn = aws_ram_resource_share.resource_configuration_share.arn
  principal          = リソースの共有先となるAWSアカウントID(アカウントAのアカウントID)
}

resource "aws_ram_resource_association" "resource_configuration_share" {
  resource_arn       = aws_vpclattice_resource_configuration.configuration.arn
  resource_share_arn = aws_ram_resource_share.resource_configuration_share.arn
}

aws_ram_resource_shareはリソース共有そのものを定義します。aws_ram_principal_associationでは、リソースを共有する先のアカウントなどを指定します。

今回はアカウントAとリソースを共有したいので、aws_ram_principal_associationにはアカウントAのAWSアカウントIDを指定します。

最後にaws_ram_resource_associationでは、具体的にaws_ram_resource_shareで共有したいAWSリソースを指定します。 上記の例では、Resource Configurationを共有対象としています。

これにより、アカウントB側のResource ConfigurationがアカウントA側で利用する準備ができました。

アカウントA(アクセスする側)でのリソースの準備

アカウントAでは、アカウントBからAWS RAMを通じて共有されたResource Configurationを受け入れる必要があります。AWS管理コンソールのAWS RAM画面からリソース共有を受け入れます1

図3. PrivateLinkを使った接続(再掲)

アカウントA側では、Resource Configurationに紐づいたVPCエンドポイントを定義する必要があります。

まず、VPCエンドポイントに紐づける共有リソースの情報を取得するため、aws_ram_resource_shareのDataソースを使用します。

data "aws_ram_resource_share" "rds_resource_config" {
  name           = リソース共有の名前 # アカウントBで共有されたリソースの名前
  resource_owner = "OTHER-ACCOUNTS" # 他アカウントから共有されたIPアドレス
}

nameでリソース共有の名前を指定、resource_ownerにOTHER-ACCOUNTSを指定することで、アカウントBから共有されたAWSリソース(今回の場合はResource Configuration)を取得できます。

次に、取得したaws_ram_resource_shareのリソースARNをVPCエンドポイントに関連付けます。 VPCエンドポイントに対応するリソースはaws_vpc_endpointです。

resource "aws_vpc_endpoint" "rds_endpoint" {
  vpc_endpoint_type          = "Resource"
  vpc_id                     = VPCエンドポイントを作成するVPCのID
  subnet_ids                 = [VPCエンドポイントを作成するVPCのプライベートサブネットIDのリスト]
  resource_configuration_arn = data.aws_ram_resource_share.rds_resource_config.resource_arns[0]
}

これにより、アカウントAのプライベートサブネットにVPCエンドポイントが作成され、サブネットに応じたプライベートIPアドレスが割り当てられます。 この状態でも機能しますし、アカウントBのAurora MySQLにアクセスできますが、IPアドレスベースよりもホスト名ベースでアクセスする方が管理しやすいため、IPアドレスFQDNを関連付けましょう。

Route53のプライベートホストゾーンを準備し、そのプライベートホストゾーン内でAレコードを定義します。

aws_vpc_endpoint.rds_endpointsubnet_configurationからVPCエンドポイントのIPアドレスを取得できるので、Terraformの局所変数rds_endpoint_ip_addresses)を使ってIPアドレスのリストを取得します。

locals {  
  rds_endpoint_ip_addresses = sort([
    for address in aws_vpc_endpoint.rds_endpoint.subnet_configuration :
    address.ipv4
  ])
}


resource "aws_route53_record" "production_rds_endpoint_dns" {
  zone_id =  プライベートホストゾーンのID
  name    = VPCエンドポイントに紐づけたいFQDN
  type    = "A"
  ttl     = 60
  records = local.rds_endpoint_ip_addresses
}

これにより、アカウントAのプライベートサブネットからアカウントBのAurora MySQLのリーダーエンドポイントへセキュアにアクセスできるようになりました。実際の接続コマンドは以下のような形になります。

mysql -u ユーザー名 -p -h VPCエンドポイントのFQDN

これにより、アカウント横断かつVPC横断でAurora MySQLのリーダーエンドポイントへのセキュアなアクセスを実現できました。最終的な構成のイメージとしては図3のとおりになります。

まとめ

クロスアカウント、クロスVPCでのリソースアクセスに際してPrivateLinkの活用する場合の例について解説しました。VPCピアリングや、Transit Gatewayを使う方法もありますが、ネットワーク観点やセキュリティ観点での考慮事項(ネットワークのCIDRや、セキュリティグループなど)を減らせるため、今後はPrivateLinkを第一候補として考えても良いでしょう。


  1. aws_ram_resource_share_accepterリソースを定義して受け入れることも可能ですが、一時的に必要なリソースであるため、AWS管理コンソールから手動で受け入れています)