Active Directory環境下でVPCエンドポイントを利用する
しばたです。
本記事ではActive Directory環境下でVPCエンドポイント(インターフェイスエンドポイント)を利用する際に気を付けるべきことについて説明します。
VPCエンドポイントをおさらい
はじめにVPCエンドポイントについて軽く触れておきます。
上記ユーザーガイドではVPCエンドポイントを次の様に説明しています。
VPC エンドポイントでは、PrivateLink を使用する AWS サービスや VPC エンドポイントサービスに VPC をプライベートに接続できます。
インターネットゲートウェイ、NAT デバイス、VPN 接続、または AWS Direct Connect 接続は必要ありません。
VPC のインスタンスは、サービスのリソースと通信するためにパブリック IP アドレスを必要としません。
VPC と他のサービス間のトラフィックは、Amazon ネットワークを離れません。
そしてAWS PrivateLinkの概要では
ネットワークトラフィックを AWS ネットワーク内に限定することで、AWS でホストされるサービスに簡単かつセキュアにアクセスします。
とあります。
なんともわかる様な、わからない様な説明です...
ここに記載されていない大前提として幾つかのAWSサービスは利用に際しインターネットへのアクセスを必要とします。
例えばAWS Systems Manager(SSM)であればOSにインストールされるAgentプログラムはインターネット経由でAWSの基盤と通信しSSMの各種機能を利用可能とします。
このため通常はインターネットゲートウェイの無いプライベートに閉じたVPCネットワークだとSSMの機能を利用することができません。
ここでプライベートに閉じたネットワークでもAWSのサービスを利用するための機能がAWS PrivateLinkやそれを含むVPCエンドポイントになります。
2つのエンドポイント
歴史的経緯によりVPCエンドポイントには2種類の方式があります。
こちらについては以下の記事に詳しく記載されていますのでまずはご覧ください。
ゲートウェイエンドポイント
ゲートウェイエンドポイントはVPCのルーティングテーブルにAWS内部からアクセス可能なルートを追加することでプライベートなネットワークからAWSのサービスにアクセスするための方式となります。
(上図はS3に対するルーティングテーブルが追加された例)
こちらは
- Amazon S3
- DynamoDB
が対象サービスとなります。
インターフェイスエンドポイント (AWS PrivateLinkを使用)
インターフェイスエンドポイントは各AWSサービスのエンドポイントを肩代わりするプライベートなIPを持つElastic Network Interface(ENI)をVPC内に作り、VPC内DNS(Amazon DNS)の名前解決でENIのIPにアクセスさせる方式となります。
例えばSSMの場合ssm.ap-northeast-1.amazonaws.com
というホスト(エンドポイント)にアクセスしますが、通常VPCエンドポイントの設定が無い場合、
PS C:\> nslookup ssm.ap-northeast-1.amazonaws.com
サーバー: ip-172-18-0-2.ap-northeast-1.compute.internal
Address: 172.18.0.2
権限のない回答:
名前: ssm.ap-northeast-1.amazonaws.com
Address: 54.240.225.181
の様にグローバルなIPアドレスを返します。
これがVPCエンドポイントを設定したVPC内では
PS C:\> nslookup ssm.ap-northeast-1.amazonaws.com
サーバー: ip-172-18-0-2.ap-northeast-1.compute.internal
Address: 172.18.0.2
権限のない回答:
名前: ssm.ap-northeast-1.amazonaws.com
Addresses: 172.18.1.62
172.18.2.83
の様にプライベートなIPアドレスを返す様になります。(この例では172.18.1.62
と172.18.2.83
が作成したエンドポイント)
こちらは以下のサービスが対象です。
- Amazon API Gateway
- Amazon CloudWatch
- Amazon CloudWatch Events
- Amazon CloudWatch Logs
- AWS CodeBuild
- Amazon EC2 API
- Elastic Load Balancing API
- AWS Key Management Service
- Amazon Kinesis Data Streams
- Amazon SageMaker ランタイム
- AWS Secrets Manager
- AWS Service Catalog
- Amazon SNS
- AWS Systems Manager
- 他の AWS アカウントによってホストされるエンドポイントサービス
- サポートされる AWS Marketplace パートナーサービス
Active DirectoryとVPCエンドポイント
ここまでの説明でVPCエンドポイントがどの様なものでどう実装されているか大枠はつかめたかと思います。
ここでAWS環境下でActive Directoryを利用する場合を考えると、Active Directoryはその名前解決に独自にDNSを必要としますのでドメインに参加するEC2インスタンスのDNS設定はActive Directoryが使用するDNSサーバーに切り替えてやる必要があります。
当然ですがインターフェイスエンドポイントの名前解決の切り替えはActive Directoryが使用するDNSサーバーでは行われません。
何の対処もしない場合ドメインに参加した時点でVPCエンドポイント(インターフェイスエンドポイント)はアクセス不能になります。
テスト環境
ここで簡単な環境で確かめてみます。
以前紹介したVPN環境に対してSSM用のVPCエンドポイントを構成した環境を用意しました。
SSM用のVPCエンドポイントは以下の4つ必要ですのでサブネット毎に4つのENIが必要になります。
- com.amazonaws.[region].ssm
- com.amazonaws.[region].ec2messages
- com.amazonaws.[region].ec2
- com.amazonaws.[region].ssmmessages
加えてSSMでは大抵の場合S3も利用しますのでS3用のエンドポイントも作成しています。(ただし今回は使用しません)
ざっくり以下のCloudFormationテンプレートで作成しています。
CloudFormationテンプレート
AWSTemplateFormatVersion: 2010-09-09
Parameters:
SystemName:
Description: "System name of each resource names."
Type: String
Default: "devio"
EnvironmentName:
Description: "Environment name of each resource names."
Type: String
Default: "test"
AZ1:
Description: "AZ1"
Type: AWS::EC2::AvailabilityZone::Name
Default: "ap-northeast-1a"
AZ2:
Description: "AZ2"
Type: AWS::EC2::AvailabilityZone::Name
Default: "ap-northeast-1c"
Resources:
# VPC endpoint for SSM
# https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/sysman-setting-up-vpc.html
SSMEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId:
Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-vpc"
SubnetIds:
- Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-1"
- Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-2"
VpcEndpointType: Interface
ServiceName:
Fn::Sub: "com.amazonaws.${AWS::Region}.ssm"
PrivateDnsEnabled: True
SecurityGroupIds:
- Ref: SecurityGroupSSMEndpoint
EC2MessageEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId:
Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-vpc"
SubnetIds:
- Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-1"
- Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-2"
VpcEndpointType: Interface
ServiceName:
Fn::Sub: "com.amazonaws.${AWS::Region}.ec2messages"
PrivateDnsEnabled: True
SecurityGroupIds:
- Ref: SecurityGroupSSMEndpoint
EC2Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId:
Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-vpc"
SubnetIds:
- Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-1"
- Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-2"
VpcEndpointType: Interface
ServiceName:
Fn::Sub: "com.amazonaws.${AWS::Region}.ec2"
PrivateDnsEnabled: True
SecurityGroupIds:
- Ref: SecurityGroupSSMEndpoint
SSMMessageEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId:
Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-vpc"
SubnetIds:
- Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-1"
- Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-2"
VpcEndpointType: Interface
ServiceName:
Fn::Sub: "com.amazonaws.${AWS::Region}.ssmmessages"
PrivateDnsEnabled: True
SecurityGroupIds:
- Ref: SecurityGroupSSMEndpoint
S3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId:
Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-vpc"
RouteTableIds:
- Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-private-rtb"
VpcEndpointType: Gateway
ServiceName:
Fn::Sub: "com.amazonaws.${AWS::Region}.s3"
# Security groups
SecurityGroupSSMEndpoint:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName:
Fn::Sub: "${SystemName}-${EnvironmentName}-ssmendpoint-sg"
GroupDescription:
Fn::Sub: "${SystemName}-${EnvironmentName}-ssmendpoint-sg"
VpcId:
Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-vpc"
SecurityGroupIngress:
- IpProtocol: -1
CidrIp: 172.18.0.0/16
Tags:
- Key: Name
Value:
Fn::Sub: "${SystemName}-${EnvironmentName}-ssmendpoint-sg"
EC2インスタンスは適当なスペックで作成したWindows Server 2019です。
今回はIPアドレス172.18.1.33
が割り当てられました。
ドメイン参加前
ドメイン参加前は下図の様にプライベートなネットワークでもSSMの機能を利用することができ、
SSMのエンドポイントもこのとおりです。
ドメイン参加後
手順は端折りますがここから確認用インスタンスをドメインcorp.shibata.tech
に参加させると以下の様にSSMの機能が利用できなくなり、
SSMのエンドポイントもパブリックなIPアドレスを返してしまいます。
(SSMセッションを張れないのでRDPで接続して確認)
Active Directory環境下でVPCエンドポイントを利用する
やっとですがここから本題に入ります。
VPCエンドポイント(インターフェイスエンドポイント)を利用できなくなったのはActive Directoryを利用することでDNSサーバーが切り替わってしまったのが直接的な原因です。
問題を解決するにはどうにかしてActive Directoryが利用するDNSサーバーからVPCエンドポイントのホスト名に対してプライベートなIPアドレスを返す様にしてやれば良いわけです。
対処法としてはActive Directoryが利用するDNSサーバーがどこにあるかによって2パターン存在します。
1. Active Directoryが利用するDNSサーバーがVPC内にある場合
こちらは今回の例とは合致しないのですが、AWSでActive Directoryを利用するに際してVPC内にドメインコントローラー(およびDNSサーバー)を配置することは多いと思います。
この場合は割とシンプルに問題を解決することが可能です。
Amazon DNSはVPC内部からであればDNSの問い合わせに回答します。
このためActive Directory側DNSのフォーワーダーにAmazon DNSを指定してやればVPCエンドポイントに対してプライベートなIPアドレスを返す様になります。
既存の設定を極力変えたくない場合は条件付きフォーワーダーを設定してやればよいでしょう。
AWS側でMicrosoft ADを利用している場合の例ですが、以下の記事が参考になります。
2. Active Directoryが利用するDNSサーバーがVPC外にある場合
こちらが今回の例に合致し、Active Directoryが利用するDNSサーバーがVPC外(オンプレ側)にあります。
この場合Amazon DNSはActive Directoryが利用するDNSサーバーからの問い合わせに回答しませんので、単純にフォーワーダーの設定をしても意味はありません。
(Amazon DNSに対しVPC外から条件付きフォーワーダーを設定しようとしてもタイムアウトになる)
このためVPC内にRoute 53 Resolverの様なDNSの問い合わせをフォーワードするサービスを設定する必要があります。
問い合わせをフォーワードできれば何でも良いので自前でVPC内にDNSサーバーを立てても構いませんが、個人的には絶対やりたくないので本記事ではRoute 53 Resolverを試します。
Route 53 Resolverを構成する
Route 53 Resolverにはインバウンド(オンプレ→VPC)
とアウトバウンド(VPC→オンプレ)
の二方向のエンドポイントがありますが、今回必要なのはインバウンドのエンドポイントです。
下図の様に各サブネットにオンプレからの通信を受けるエンドポイントを作成します。
今回は以下のCloudFormationテンプレートでRoute 53 Resolver(とそれに必要なセキュリテグループ)を構成しました。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
SystemName:
Description: "System name of each resource names."
Type: String
Default: "devio"
EnvironmentName:
Description: "Environment name of each resource names."
Type: String
Default: "test"
Resources:
# Security groups
# オンプレ → VPCの通信用
SecurityGroupR53ResolverInbound:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName:
Fn::Sub: "${SystemName}-${EnvironmentName}-r53resolver-inbound-sg"
GroupDescription:
Fn::Sub: "${SystemName}-${EnvironmentName}-r53resolver-inbound-sg"
VpcId:
Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-vpc"
SecurityGroupIngress:
- IpProtocol: tcp
CidrIp: 192.168.0.0/16
FromPort: 53
ToPort: 53
- IpProtocol: udp
CidrIp: 192.168.0.0/16
FromPort: 53
ToPort: 53
Tags:
- Key: Name
Value:
Fn::Sub: "${SystemName}-${EnvironmentName}-r53resolver-inbound-sg"
# ResolverEndpoint
# * Direction を INBOUND
# * IpAddresses を2つのサブネットから自動取得
ResolverEndpointInbound:
Type: AWS::Route53Resolver::ResolverEndpoint
Properties:
Direction: INBOUND
IpAddresses:
- SubnetId:
Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-1"
- SubnetId:
Fn::ImportValue:
Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-2"
Name:
Fn::Sub: "${SystemName}-${EnvironmentName}-r53resolver-inbound"
SecurityGroupIds:
- Ref: SecurityGroupR53ResolverInbound
結果は下図の様にIPアドレス172.18.1.132
、172.18.2.29
を持つインバウンドエンドポイントが作成されました。
あとはActive Directoryが利用するDNSサーバーのフォーワーダーにこのエンドポイントを指定してやるだけです。
今度はタイムアウトせずにきちんと設定されました。
(今回はamazonaws.com
に対して条件付きフォーワーダーを設定しましたが、AWSサービスの種類によって設定すべき内容は若干変わります)
これでドメインに参加したEC2インスタンスからVPCエンドポイントに対して名前解決を試みるとプライベートなIPアドレスを返す様になり、
ドメインに参加しつつSSMの機能も利用できる様になります。
(状況によってはSSM Agentの再起動も必要です)