SREチームの藤原です。
サービスを運用しているとデータ分析や他システムとのデータ連携などで、DBへの読み取り専用アクセスが必要になる場合は多々あると思います。 本エントリでは、AWS PrivateLinkを活用することで、セキュアなクロスアカウントかつ、クロスVPCなアクセスを実現できるのでその方法を解説します。
構成のイメージ
本エントリでは、2つのアカウントの間でのクロスアカウント & クロスVPCアクセスを前提とします。 アカウントAがアクセスする側、アカウントBがアクセスされる側とします。
PrivateLink設定前のアカウントBは図1のような構成です。

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

ここではアカウントAからアカウントBへの具体的なアクセス方法については示していません。 PrivateLinkを使った接続をする前にVPCピアリングや、Transit Gatewayなどのサービスを使ったVPC間接続と今回の要件における課題について述べたのち、PrivateLinkを使った接続方法について解説します。
VPCピアリングやTransit Gatewayを使ったVPC間接続とその課題
クロスアカウント、クロスVPCでのリソースアクセスを考えると、昔からAWSを使っている人であれば、VPCピアリング(VPCピア機能)とAWS Transit Gatewayの活用が浮かぶと思います。
今回これでも要件は達成できるのですが、注意点が2つほど存在します。
ひとつ目がVPCネットワークのCIDRが重複している場合はVPCピアリングもTransit Gatewayも使えません。 もうひとつがセキュリティ的な課題です。 VPCピアリングやTransit Gatewayでの接続はIP層でネットワークを直接繋げるような形になります。 このような場合、意図しないリソースへのアクセスを防ぐためには、セキュリティグループなどの設定を細かく設定、管理しなければいけません。
今回の事例では、CIDRの重複は発生していませんが、意図しないリソースへのアクセスの可能性を排除する目的で、PrivateLinkを活用することとしました。
PrivateLinkを使ったセキュアなVPC間接続
PrivateLinkを活用した場合のVPC間の接続は次の図3のようになります。 VPCエンドポイントを介してアカウントA、BそれぞれのVPC間を接続し、Aurora MySQLのリーダーエンドポイントのみへの到達性を確保します。

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

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_definitionのdns_resourceブロックで接続先となるリソースのドメインを指定します。
Aurora MySQLのリーダーエンドポイントのFQDNをここで指定することにより、PrivateLinkの接続先をリーダーエンドポイントのみに限定できます。
次に、アカウントAからVPCエンドポイントを通じて接続できるようにする必要があります。 このためにAWS Resource Access Manager(以降、AWS RAM)を利用して、Resource ConfigurationをアカウントAに共有します。
RAMで共有するリソースをTerraformで定義する場合は、aws_ram_resource_share、aws_ram_principal_association、aws_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。

アカウント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_endpointのsubnet_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を第一候補として考えても良いでしょう。