【AWS】Internal Port Concentrator パターン(仮)でプライベートサブネットへのアクセスを考える

AWS

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、植木和樹です。本日はAWS VPC環境において、グローバルIPを持たないサーバへインターネットからアクセスする手段について取り上げます。

本日の課題

VPCでサブネットを作る際には、大きく2つのサブネットを作成するかと思います。一つはInternetGatewayを持ち、EIPを割り当てることでインターネットとの通信を行うサーバを配置する「パブリックサブネット」、もう一つはNATサーバを介してのみインターネットと通信することができる「プライベート(プロテクテッド)サブネット」です。

20130706_blog_IPC01

プライベートサブネットに配置するサーバには「インターネットから接続する必要がない」サーバ、例えばバッチ処理しか行わないサーバや、ELBの配下に入るWebサーバなどがあります。

ただ「インターネットから接続する必要がない」というのは、あくまで提供しているサービスを利用する立場からみた話で、運用が始まると「開発目的でMySQLにつなぎたい」とか「動作確認のためELB配下の特定のWebサーバにだけアクセスしたい」といった開発や保守担当者からの要望がでてくるかと思います。

今回はそういった要望をどのように実現するかを考えてみました。

NATサーバを使った「Internal Port Concentrator」CDPパターン

NATサーバはプライベートサブネットにあるサーバが、VPC外のサービス(NTPやyumリポジトリ、S3バケットも)へアクセスするために使用する「内→外」方向の通信のために使用しています。Amazonが提供しているAMIから起動したNATサーバの場合、NAT機能はiptablesで実現されています。

このNATサーバ(iptables)に「内→外」だけじゃなく、「外→内」の通信も仲介(ポートマッピングによる転送)をしてもらえれば、インターネットから特定サーバの特定ポートへのアクセスができそうですね。「VPCプライベートサブネット内にあるサーバのポートをNATサーバに集約する」ことから「Internal Port Concentrator パターン」と名付けてみました。

20130706_blog_IPC02

名前の由来は、複数のシリアルポートをまとめTCP/IP経由でのアクセスを可能にする「ターミナル・コンセントレータ(ターミナル・サーバ)」というハードウェアからヒントをいただいてます。

VPC環境を構築する

まずは試験環境となるVPCを構築しましょう。VPC環境の構築を一からやるのは大変なのでCloudFormationを使います。

マネージメントコンソールにログインが済んだ状態でAWSが提供しているCloudFormationテンプレート集のページにアクセスし、"multi-tier-web-app-in-vpc.template"をページ内検索します。見つかったらページ右側にある"Launch Stack"ボタンをクリックするとCloudFormationの "Create Stack" 画面が開きますので画面の指示に従って(パラメータとしてkeypair名が必要です)スタックを作成しましょう。これでVPCやサブネット、NATサーバやいくつかのELBとEC2インスタンスが作成されます。

130706-0004

このCloudFormationではサブネットを単一AZにしか作らないので、マネージメントコンソールを使って、別AZにもパブリックとプライベート計2つのサブネットを作っておきましょう。

次にRDSも作成します。先ほど同様CloudFormationテンプレート集のページから"VPC_RDS_DB_Instance.template"を使ってスタックを作成します。なおこのテンプレートはVpcID と RDSを配置するSubnetID(カンマ区切りで複数指定必須)がパラメータとして必要です。事前にご自身の環境のIDをメモしておきましょう。

130706-0003

NATサーバにポートマッピングを設定する

NATサーバはVPC構築で作成されたEC2インスタンスをそのまま使用します。NATサーバにsshでログインしてポートマッピングを設定していきます。

NAT機能はOS起動処理の最後に/etc/rc.localからシェルスクリプトを呼び出すことで行われています。

/etc/rc.local

#!/bin/sh
#
# This script will be executed *after* all the other init scripts.
# You can put your own initialization stuff in here if you don't
# want to do the full Sys V style init stuff.

touch /var/lock/subsys/local

# Configure PAT
/usr/local/sbin/configure-pat.sh

シェルスクリプトの最後で実行している/usr/local/sbin/configure-pat.shが実際にNATを実現しているシェルスクリプトです。ざっくり何をしているか見てみましょう。

  • ifconfig で eth0 のMACアドレスを調べる
  • インスタンスメタデータとMACアドレスからVPCのIPアドレスレンジ(10.0.0.0/16)を入手する
  • カーネルパラメータ net.ipv4.ip_forward に 1 を設定してIPv4のフォワードを有効にする
  • カーネルパラメータ net.ipv4.ICMP redirect に 0 設定しICMP リダイレクトメッセージをプライベートサブネットのホストへ送信しないようにする
  • iptablesでVPCのアドレスレンジからの通信にはNAT変換を行う

【参考】Linux Advanced Routing & Traffic Control HOWTO 

/usr/local/sbin/configure-pat.sh

#!/bin/bash

# Configure the instance to run as a Port Address Translator (PAT) to provide
# Internet connectivity to private instances.
#

set -x
echo "Determining the MAC address on eth0"
ETH0_MAC=`/sbin/ifconfig  | /bin/grep eth0 | awk '{print tolower($5)}' | grep '^[0-9a-f]\{2\}\(:[0-9a-f]\{2\}\)\{5\}$'`
if [ $? -ne 0 ] ; then
   echo "Unable to determine MAC address on eth0" | logger -t "ec2"
   exit 1
fi
echo "Found MAC: ${ETH0_MAC} on eth0" | logger -t "ec2"

VPC_CIDR_URI="http://169.254.169.254/latest/meta-data/network/interfaces/macs/${ETH0_MAC}/vpc-ipv4-cidr-block"
echo "Metadata location for vpc ipv4 range: ${VPC_CIDR_URI}" | logger -t "ec2"

VPC_CIDR_RANGE=`curl --retry 3 --retry-delay 0 --silent --fail ${VPC_CIDR_URI}`
if [ $? -ne 0 ] ; then
   echo "Unable to retrive VPC CIDR range from meta-data. Using 0.0.0.0/0 instead. PAT may not function correctly" | logger -t "ec2"
   VPC_CIDR_RANGE="0.0.0.0/0"
else
   echo "Retrived the VPC CIDR range: ${VPC_CIDR_RANGE} from meta-data" |logger -t "ec2"
fi

echo 1 >  /proc/sys/net/ipv4/ip_forward && \
   echo 0 >  /proc/sys/net/ipv4/conf/eth0/send_redirects && \
   /sbin/iptables -t nat -A POSTROUTING -o eth0 -s ${VPC_CIDR_RANGE} -j MASQUERADE

if [ $? -ne 0 ] ; then
   echo "Configuration of PAT failed" | logger -t "ec2"
   exit 0
fi

echo "Configuration of PAT complete" |logger -t "ec2"
exit 0

今回設定しようとしているポートマッピングの対象は数が多いのでシェルスクリプトに直書きせず、/etc/sysconfig/iptablesで管理したいと思います。そこでいったん現在の設定を/etc/sysconfig/iptablesに保存してから、シェルを少々修正しNAT(MASQUERADE)している処理を削ってしまいましょう。

# service iptables save
# cat /etc/sysconfig/iptables
# Generated by iptables-save v1.4.18 on Sat Jul  6 09:37:51 2013
*nat
:PREROUTING ACCEPT [1:64]
:INPUT ACCEPT [1:64]
:OUTPUT ACCEPT [19:1466]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 10.0.0.0/16 -o eth0 -j MASQUERADE
COMMIT
# Completed on Sat Jul  6 09:37:51 2013
# vi /usr/local/sbin/configure-pat.sh
echo 1 >  /proc/sys/net/ipv4/ip_forward && \
  echo 0 >  /proc/sys/net/ipv4/conf/eth0/send_redirects

インターネットからプライベートサーバへ通信させたいポートは次の3つにしました。システム構築後に自社開発部門からWebサーバやMySQLサーバにログインして、アプリケーションの動作検証や障害原因の調査をすることを想定しています。

  • インターネットからプライベートサブネットにあるEC2(Webサーバ)へのhttpアクセス
  • インターネットからプライベートサブネットにあるEC2(Webサーバ)へのsshアクセス
  • インターネットからプライベートサブネットにあるRDS(MySQLサーバ)へのmysqlアクセス

NATサーバの特定のポートにアクセスした時に、プライベートサブネットにあるサーバの、特定ポートにフォワードされるようにします。対応付けは以下のようにしました。

アクセスするIPアドレス アクセスするポート フォワード先IPアドレス フォワード先ポート
NATサーバのEIP 50001 WebサーバのプライベートIP http(tcp/80)
NATサーバのEIP 50002 WebサーバのプライベートIP ssh(tcp/22)
NATサーバのEIP 50003 RDS(MySQL) EndPointのプライベートIP mysql(tcp/3306)

iptablesでは以下のコマンドで設定することができます。NATサーバのアドレスはEIPアドレスでなくプライベートアドレス(10.0.0.155)で指定しているという点に注意してください。また--to-destinationにはホスト名が指定できないため、事前にdigコマンドでRDS EndPointのIPアドレスを調べておきます。

# iptables -t nat -A PREROUTING -p tcp -d 10.0.0.155 --dport 50001 -j DNAT --to-destination 10.0.1.24:80
# iptables -t nat -A PREROUTING -p tcp -d 10.0.0.155 --dport 50002 -j DNAT --to-destination 10.0.1.24:22
# iptables -t nat -A PREROUTING -p tcp -d 10.0.0.155 --dport 50003 -j DNAT --to-destination 10.0.11.147:3306

うまく登録されたか確認してみましょう。

# iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DNAT       tcp  --  anywhere             10.0.0.155          tcp dpt:50001 to:10.0.1.24:80
DNAT       tcp  --  anywhere             10.0.0.155          tcp dpt:50002 to:10.0.1.24:22
DNAT       tcp  --  anywhere             10.0.0.155          tcp dpt:50003 to:10.0.11.147:3306

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  10.0.0.0/16          anywhere

問題ないようなので、次回 iptables サービス起動時にこのルールが読み込まれるようsaveしておきます。

# service iptables save
# chkconfig --list iptables
iptables       	0:off	1:off	2:on	3:on	4:on	5:on	6:off

接続確認をする前にセキュリティグループを変更しておきましょう(Inbound/Outboundとも)。なお接続元は実際のクライアントのIPアドレスでなく、NATサーバに対して許可している点に注意してください。

接続元IPアドレス または セキュリティグループ 接続先セキュリティグループ 接続先ポート
インターネット(自社または開発委託業者のグローバルIPアドレス) NATサーバ tcp/50001〜50003
NATサーバ Webサーバ http(tcp/80)
NATサーバ Webサーバ ssh(tcp/22)
NATサーバ RDS(MySQL) mysql(tcp/3306)

接続確認する

HTTP(tcp/50001 -> tcp/80)

ブラウザでNATサーバの50001番ポートにアクセスします(http://54.249.XXX.XXX:50001/)。正しく設定できていればWebサーバのページが表示されます。

130706-0005

SSH(tcp/50002 -> tcp/22)

ローカルPCからNATサーバの50002番ポートにsshでアクセスします。正常にWebサーバにログインできコマンドプロンプトが表示されれば成功です。

$ ssh -i ~/.ssh/my_keypair.pem ec2-user@54.249.XXX.XXX -p 50002

MySQL(tcp/50003 -> tcp/3306)

mysqlコマンドでNATサーバの50003番ポートに接続します。CloudFormationで作った場合の初期ユーザとパスワードは admin/admin です。ログインできれば成功です。

$ mysql -u admin -p -h 54.249.XXX.XXX -P 50003
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 54
Server version: 5.5.31-log Source distribution

Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

Internal Port Concentrator 使用時に注意する点

ポートマッピングを使ってインターネットからプライベートサブネットのサーバに接続することができました。あとは必要な数だけマッピングをコマンドで追加しservice iptables saveすれば良いでしょう。ただ本運用にのせるには、まだいくつか課題が残っています。

NATサーバにアクセスできる外部IPアドレスを極力しぼること

本来であればプライベートサブネットのサーバにはインターネットからのアクセスが行われないはずです。そこに「穴」を開けています。セキュリティを少しでも向上させるためにも接続元は制限しましょう。具体的にはNATサーバのセキュリティグループのInboud(50001-50003)へアクセスできるIPアドレスを限定すれば良いでしょう。

AutoScalingなどによるプライベートIPアドレス変更の反映

ELB配下のWebサーバはAutoScaling構成にすることが多いと思います。そうするとEC2インスタンスが入れ替わる(増減する)たびにマッピング情報をメンテナンスしなければいけません。aws-apitoolsなどを使ってこの辺は自動化を検討したいところですね。

RDSフェイルオーバーの考慮

Multi-AZのRDSの場合、フェイルオーバーするとIPアドレスが変わります。iptablesにはEndPointでなくIPアドレスを直接指定しるため、フェイルオーバーした場合は改めてEndPointのIPアドレスを調べてiptablesを設定しなおす、といった運用の手間が発生します。ここも自動化しておきたいですね。

NATのポートと実際のポートの管理

正直NATサーバの50001番ポートがWebサーバの80番ポートに繋がっている・・・というのは直感的ではありません。月日が経つにつれ忘れ去られてしまわないように、環境定義書等のドキュメントにはちゃんと記述しておきましょう。そして運用ドキュメントは正しく情物一致しているか定期的に棚卸しましょう!

まとめ

今回はVPC構築後、後々になってメンテナンス用通信経路が必要になった場合、というものを想定し解決策を検討してみました。

いままでClassic-EC2環境では、PublicDNSが自動的に割り当てられていたために、インターネットからAWS内へのメンテナンス用アクセスというものはそれほど気にしなくても良い状況でした。しかしVPCが登場し次々に新しい機能が実装されていく昨今、今後はVPC導入を検討されるお客様が増えてくるかと思います。そして環境設計の際はどうしても外部向けサービス提供が視点になってしまうため、メンテナンス用通信経路が見落とされがちです。

本来であれば構築の段階から開発・保守運用に必要なプライベートサブネットへの安全なアクセスというものを考え、必要に応じてVPNを用意する(

Backnetパターン)など検討しておくべきでしょう。しかしスケジュールや費用面からそうもいかないのが現実です。その際にNATサーバを利用した「Internal Port Concentrator」案は追加コストも抑えた選択肢として検討いただけるのではないでしょうか。