Bastionホストは、インターネットのような外部のネットワークから、プライベートネットワークへのアクセスを提供するためのサーバです。Bastionホストは潜在的な攻撃に晒されるため、突破される機会を最小化しておかければいけません。例えば、Basitionホストを、外部ネットワークから Amazon Virtual Private Cloud (VPC)内のプライベートサブネット内のLinuxインスタンスに対してSSHコネクションを許可するリスクを緩和するために利用できます。
この記事では、BastionホストにLinuxインスタンスとの間で確立された全てのSSHセッションを記録させる方法についてご紹介します。SSHセッションを記録すれば、監査が可能になり、規制要件を順守する手助けとなります。
ソリューションアーキテクチャ
このセクションでは、ソリューションのアーキテクチャをお見せし、どうやってSSHセッションを記録するためのBastionホストを構成できるかを説明します。この記事の後段で、このソリューションをどのように実装し、テストするかについてのインストラクションを提供します。
Amazon VPCによって、ユーザーが定義した仮想プライベートネットワーク上にAWSのリソースを起動することができます。Bastionホストは一般的にAmazon VPCのパブリックサブネット内のAmazon EC2インスタンスで稼働します。Linuxインスタンスは、パブリックアクセスのできないサブネットに配置されており、Bastionホストが稼働しているEC2インスタンスに定義されたセキュリティグループからのSSHアクセスが許可されたセキュリティグループが設定されています。次の図に示すように、BastionホストのユーザーはLinuxインスタンスに接続するためにBastionホストに接続します。
ユーザーは自身の要件に合うようにこのアーキテクチャを適用できます。例えば、Bastionホストを別のAmazon VPCに配置し、2つのAmazon VPCの間をVPC Peeringで接続した構成をとることも可能です。重要なのはLinuxインスタンスに対するSSHトラフィックの唯一のソースがBastionホストであることです。
SSHセッションを記録するためのこのブログ記事のソリューションはBastionホストのみで稼働し、Linuxインスタンスには特別な設定は必要ありません。インスタンス起動時にAmazon Linuxインスタンス上でrootユーザーとしてコマンドを実行することでソリューションを構築することが出来ます。
注: ネットワークセキュリティにとってクリティカルポイントとなるので、Bastionホストを強固にすることがベストプラクティスです。ハードニングには必要のないアプリケーションやサービスを無効にしたり、ネットワークスタックをチューニングしたりすることも含まれます。この記事では、ハードニングの詳細 については議論しません。
クライアントがAmazon Linuxインスタンスにアクセスする際、SSHサーバである
OpenSSHのデフォルトの挙動では、インタラクティブなシェルが起動します。その代わりに、ここでは、
scriptコマンドの中にインタラクティブシェルをラップするカスタムスクリプトを実行するようにOpenSSHを設定します。そうすることで、
scriptコマンドがキーボード入力や
vimのようなフルスクリーンアプリケーションも含めてターミナルに表示されるもの全てを記録します。
script replayコマンドを利用して結果ログからセッションをリプレイすることも可能です。このブログ記事後半の “ソリューションをテストする” セクションのステップ・バイ・ステップの例を参照下さい。
ローカルコンピューターとLinuxインスタンスの間に直接接続を作成でき、このソリューションをバイパスできるような幾つかのSSHフィーチャーを意図的にblockしていることに注意して下さい。
# ログファイル用フォルダの作成
mkdir /var/log/bastion
# フォルダとその中身にアクセスできるのはec2-userだけにする
chown ec2-user:ec2-user /var/log/bastion
chmod -R 770 /var/log/bastion
setfacl -Rdm other:0 /var/log/bastion
# OpenSSHログイン時にカスタムスクリプトを実行させる
echo -e "\nForceCommand /usr/bin/bastion/shell " >> /etc/ssh⁄sshd_config
# Bastionホストユーザーがソリューションを回避できるSSHフィーチャーをブロック
awk '!/AllowTcpForwarding/' /etc/ssh⁄sshd_config> temp && mv temp /etc/ssh⁄sshd_config
awk '!/X11Forwarding/' /etc/ssh⁄sshd_config> temp && mv temp /etc/ssh⁄sshd_config
echo "AllowTcpForwarding no" >> /etc/ssh⁄sshd_config
echo "X11Forwarding no" >> /etc/ssh⁄sshd_config
mkdir /usr/bin/bastion
cat > /usr/bin/bastion/shell << 'EOF'
# SSHクライアントがコマンドを定義していなかったかチェック
if [[ -z $SSH_ORIGINAL_COMMAND ]]; then
# ログファイルのフォーマットは/var/log/bastion/YYYY-MM-DD_HH-MM-SS_user
LOG_FILE="`date --date="today" "+%Y-%m-%d_%H-%M-%S"`_`whoami`"
LOG_DIR="/var/log/bastion/"
# ウェルカムメッセージの表示
echo ""
echo "NOTE: This SSH session will be recorded"
echo "AUDIT KEY: $LOG_FILE"
echo ""
# ログファイル名のサフィックスにランダムな文字列を追加(あとで説明)
SUFFIX=`mktemp -u _XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`
# SSHセッション記録のためインタラクティブシェルを"script"にラップ
script -qf --timing=$LOG_DIR$LOG_FILE$SUFFIX.time $LOG_DIR$LOG_FILE$SUFFIX.data --command=/bin/bash
else
# "script"プログラムはいくつかのコマンド(e.g. bash, nc)で回避可能
# このため、ユーザーがこれらのコマンドを入力するのを意図的に防ぐ
echo "This bastion supports interactive sessions only. Do not supply a command"
exit 1
fi
EOF
# カスタムスクリプトを実行可能にする
chmod a+x /usr/bin/bastion/shell
# Bastionホストユーザーはファイル名を知っていれば"script"を使って
# ログファイルの上書きおよび改竄が可能。
# ファイル名をわかりにくくする幾つかの手段を取る:
# 1. ログファイル名にランダムなサフィックスを付与
# 2. Bastionホストユーザーのログファイルが格納されている領域の一覧表示を禁止
# これは"script"のグループオーナーを変更しGIDをセットすることで実現
chown root:ec2-user /usr/bin/script
chmod g+s /usr/bin/script
# 3. Bastionホストユーザーが他のユーザーのプロセスを参照することを禁止
# ログファイル名はコマンドの実行パラメータの一つであるため
mount -o remount,rw,hidepid=2 /proc
awk '!/proc/' /etc/fstab > temp && mv temp /etc/fstab
echo "proc /proc proc defaults,hidepid=2 0 0" >> /etc/fstab
# 設定ファイルの変更を反映するためSSHサービスを再起動
service sshd restart
上記のコマンドはOpenSSHにログイン時にカスタムスクリプトを実行させます。このコマンドはSSHセッションを
/var/log/bastionフォルダの中のログファイルに記録します。耐久性を高めるため、ログファイルは一定の間隔で
aws s3 cpコマンドを使用してAmazon S3 バケットにコピーされます。これは次のようになります。
cat > /usr/bin/bastion/sync_s3 << 'EOF'
# ログファイルをSSEを有効にしてS3へコピー
# 成功したら古いログファイルを削除
LOG_DIR="/var/log/bastion/"
aws s3 cp $LOG_DIR s3://bucket-name/logs/ --sse --region region --recursive && find $LOG_DIR* -mtime +1 -exec rm {} \;
EOF
chmod 700 /usr/bin/bastion/sync_s3
この時点で、OpenSSHはSSHセッションを記録するよう構成され、ログファイルはS3に保管されます。SSHを使用してLinuxインスタンス上で実行されたアクションの実行元を特定するために、Bastionホストのユーザーは、Bastionホスト上で個人ユーザーアカウントを持ち、個人のSSHキーペアでログインします。各ユーザーアカウントは、Bastionホストのユーザーがこのソリューションを無効化したり破ったり出来ないよう最低限必要な権限を与えられます。
ユーザーアカウントの管理を容易にするため、BastionホストユーザーそれぞれのSSH公開鍵はS3バケットにアップロードされます。一定の間隔で、Bastionホストはこのバケットの中から公開鍵を取得します。新たにユーザーアカウントを作成する際には、SSH公開鍵をBastionホストにコピーし、そのユーザーがキーペアでログインできるようにします。例えば、BastionホストがJohnの公開鍵である
john.pubというファイルがバケットの中に見つかれば、ユーザーアカウント
johnを作成し、公開鍵を
/home/john/.ssh/authorized_keysにコピーします。S3バケットから公開鍵が削除されれば、Bastionホストは関連するユーザーアカウントを削除します。個人アカウントの作成および削除は
/var/log/bastion/users_changelog.txtにロギングされます。
次のコマンドは個人アカウントを管理するためのシェルスクリプトを作成し、5分毎にそのスクリプトを実行するcronジョブをスケジューリングします。
# Bastionホストユーザーは、個人のSSHキーペアでBasitionホストにログインしなければならない
# 公開鍵は次の命名規則でS3に保管される: "username.pub".
# このスクリプトは公開鍵を取得し、必要に応じてローカルユーザーの作成あるいは削除を行い、
# 公開鍵を/home/username/.ssh/authorized_keysにコピーする
cat > /usr/bin/bastion/sync_users << 'EOF'
# ユーザーの変更ログ
LOG_FILE="/var/log/bastion/users_changelog.txt"
# この関数は公開鍵のファイル名からユーザー名を返す
# Example: public-keys/sshuser.pub => sshuser
get_user_name () {
echo "$1" | sed -e 's/.*\///g' | sed -e 's/\.pub//g'
}
# S3バケット上で利用可能な公開鍵それぞれに実行
aws s3api list-objects --bucket bucket-name --prefix public-keys/ --region region --output text --query 'Contents[?Size>`0`].Key' | sed -e 'y/\t/\n/' > ~/keys_retrieved_from_s3
while read line; do
USER_NAME="`get_user_name "$line"`"
# ユーザー名がアルファベットであることを確認
if [[ "$USER_NAME" =~ ^[a-z][-a-z0-9]*$ ]]; then
# 既存でなければアカウントを作成
cut -d: -f1 /etc⁄passwd | grep -qx $USER_NAME
if [ $? -eq 1 ]; then
/usr/sbin/adduser $USER_NAME && \
mkdir -m 700 /home/$USER_NAME/.ssh && \
chown $USER_NAME:$USER_NAME /home/$USER_NAME/.ssh && \
echo "$line" >> ~/keys_installed && \
echo "`date --date="today" "+%Y-%m-%d %H-%M-%S"`: Creating user account for $USER_NAME ($line)" >> $LOG_FILE
fi
# ユーザーアカウントが作成されたら公開鍵をS3からコピー
if [ -f ~/keys_installed ]; then
grep -qx "$line" ~/keys_installed
if [ $? -eq 0 ]; then
aws s3 cp s3://bucket-name/$line /home/$USER_NAME/.ssh/authorized_keys --region region
chmod 600 /home/$USER_NAME/.ssh/authorized_keys
chown $USER_NAME:$USER_NAME /home/$USER_NAME/.ssh/authorized_keys
fi
fi
fi
done < ~/keys_retrieved_from_s3
# S3から公開鍵が削除されたユーザーを削除
if [ -f ~/keys_installed ]; then
sort -uo ~/keys_installed ~/keys_installed
sort -uo ~/keys_retrieved_from_s3 ~/keys_retrieved_from_s3
comm -13 ~/keys_retrieved_from_s3 ~/keys_installed | sed "s/\t//g" > ~/keys_to_remove
while read line; do
USER_NAME="`get_user_name "$line"`"
echo "`date --date="today" "+%Y-%m-%d %H-%M-%S"`: Removing user account for $USER_NAME ($line)" >> $LOG_FILE
/usr/sbin/userdel -r -f $USER_NAME
done < ~/keys_to_remove
comm -3 ~/keys_installed ~/keys_to_remove | sed "s/\t//g" > ~/tmp && mv ~/tmp ~/keys_installed
fi
EOF
chmod 700 /usr/bin/bastion/sync_users
cat > ~/mycron << EOF
*/5 * * * * /usr/bin/bastion/sync_s3
*/5 * * * * /usr/bin/bastion/sync_users
0 0 * * * yum -y update --security
EOF
crontab ~/mycron
rm ~/mycron
Bastionホストが稼働するインスタンスに関連付けられたキーペアを配布する時には十分注意して下さい。そのキーペアを利用すれば、 root または ec2-user ログイン可能であり、このソリューションにダメージを与えたり改竄したりすることができるからです。パッチ適用等のルートアクセスが必要なオペレーションはスクリプト化し、自動化することができるので、インスタンスをキーペア無しで立ち上げることも考慮すべきです。
また、S3バケットのパーミッションはバケットあるいはIAMポリシーを使って制限しなければいけません。例えば、ログファイルはコンプライアンスチームにのみ読み込み可能とし、SSH公開鍵はDevOpsチームによって管理させる事ができます。
ソリューションを実装する
ソリューションのアーキテクチャをご理解いただいたら、あなたのAWSのアカウントにこのブログのソリューションを実装するために、このセクションのインストラクションに従って下さい。
まず最初に、2つのキーペアを作成してください。最初キーペアはBationホストに関連付けるものです。2つ目のキーペアはプライベートサブネットに起動するAmazon Linuxインスタンスに関連付けるもので、BationホストユーザーのSSHキーペアとして利用されます。
手動でキーペアを作成するには以下の手順を実施します:
- Amazon EC2 console を開いて、ナビゲーションバーからリージョンを選択します。
- 左側からキーペアを選択します
- キーペアをクリックします
- キーペア名に bastion とタイプし作成をクリックします。ブラウザが秘密鍵を bastion.pem としてダウンロードします。
- sshuserという名前で別の鍵を作成するためにステップ3とステップ4を繰り返します。
次に、必要なリソースをプロビジョニングするためにAWS CloudFormation を利用します。CloudFormationコンソールを開いて、テンプレートからCloudFormationスタックを作成するために、Create a Stack をクリックしてください。Click Next をクリックして、BastionKeyPair に bastion InstanceKeyPairに sshuser と入力後、後続のオンスクリーンインストラクションに従って下さい。
CloudFormationは次のリソースを作成します:
- インターネットゲートウェイがアタッチされた1つのAmazon VPC
- VPC内に新規のルートテーブルによってパブリックアクセスが可能な1つのパブリックサブネット
- VPC内にインターネットアクセスを持たない1つのプライベートサブネット
- 1つのS3バケット。ログファイルは logs というフォルダに保管され、とSSH公開鍵は public-keys というフォルダに保管される
- 2つのセキュリティグループ。1つ目はインターネットからのSSHトラフィックを許可し、2つ目は最初のセキュリティグループからのSSHトラフィックを許可する
- EC2インスタンスにS3へのログファイルのアップロードとSSH公開鍵の読み取りを許可する1つのIAMロール
- パブリックサブネット上で、IAMロールがアタッチされ、ソリューションを構成するためのユーザーデータスクリプトが入力されているBastionホスト用の1つのAmazon Linuxインスタンス
- ライベートサブネット上のAmazon Linuxインスタンス
スタックの作成が完了したら(10分ほどかかります)、CloudFormationコンソールのOutputsタブをクリックし、S3バケット名、BastionホストのパブリックIPアドレス、LinuxインスタンスのプライベートIPアドレスを記録して下さい。
キーペア sshuser のSSH公開鍵をS3バケットにアップロードすれば、Bastionホストが新しいユーザーアカウントを作成します:
- 公開鍵を取得し、 sshuser.pub という名前でローカルに保管します (キーペアのパブリックキーを取得する (Linux) あるいは キーペアのパブリックキーを取得する(Windows)を参照)
- S3 コンソール を開いて、バケットリストの中からバケット名をクリックします
- public-keys という名前の新しいフォルダを作成し(フォルダの作成を参照)、SSH公開鍵 sshuser.pub をこのフォルダにアップロードします(Amazon S3 へのオブジェクトのアップロードを参照)
- 数分待つと、バケットの中に、ユーザーアカウントの作成および削除に関連するイベントが記録される、新しいファイルが格納された新しい logs フォルダを確認できます。
ソリューションをテストする
キーペアsshuserはBastionホストへのBastionホストユーザーとしてのログインと、プライベートサブネットのLinuxインスタンスへの特権をもった ec2-user としてのログインを提供することを覚えているかもしれません。ですので、ソリューションをテストするには、Bastionホストに秘密鍵を保管すること無くBastionホストからLinuxインスタンスへ接続するためにSSHエージェントフォワーディングを利用します。SSHエージェントフォワーディングについては、このブログ記事を参照して下さい。
最初に、SSH秘密鍵をフォワードするために-A の引数をつけて sshuser としてBastionホストにログインします。
chmod 600 [path to sshuser.pem]
ssh-add [path to sshuser.pem]
ssh -A sshuser@[public IP of the bastion host] –i [path to sshuser.pem]
次のスクリーンショットに示すように、SSHセッションが記録されるというウェルカムメッセージが表示されます。
AUDIT KEYの値を書き留めてください。その後Linuxインスタンスに接続し、コマンドを幾つか実行してSSHセッションをクローズしてください。
ssh ec2-user@[private IP of the Linux instance]
[commands of your choice that will be recorded]
exit
exit
ここで、たった今記録されたSSHセッションをリプレイします。それぞれのセッションは2つのログファイルを持ちます。1つはターミナルに表示されたデータを含んでおり、もう一つは、現実的なタイピングとアウトプットのディレイを持ったリプレイを可能にするタイミングデータを含んでいます。簡単のために、Bastionホストに ec2-user として接続してログファイルのローカルコピーからリプレイします。
注: 通常の環境では、2つの理由からBastionホストでリプレイをしないで下さい。まず最初に、特権ユーザーアカウントであるec2-user の利用は厳格に避けるべきであるということを思い出して下さい、2つ目は、Bastionホストは、S3バケットのログフォルダに対する読み取り権限を持たず、1日前より古いログはBastionホストから削除されるためです。その代わりに、S3バケットに対してログファイルをダウンロードしてリプレイするための充分な権限を持つ他のLinuxインスタンスを利用して下さい。
ssh ec2-user@[public IP of the bastion host] –i [path to bastion.pem]
export LOGFILE=`ls /var/log/bastion/[audit key]*.data | cut -d. -f1`
scriptreplay --timing=$LOGFILE.time $LOGFILE.data
これで、作成したリソースをクリーンアップする為にCloudFormationスタックを削除できます。CloudFormationによって削除される前にS3バケットを空にしておく必要が有ることに注意して下さい。 (バケットを空にするを参照)
結論
Bastionホストは、SSHを利用してプライベートネットワークにセキュアなアクセスを提供するネットワークセキュリティのスタンダードな要素です。このブログ記事ではこのBastionホストにSSHセッションを記録させる方法をお伝えしました。この記録はプレイバック可能で、監査目的に利用可能です。
コメントのある方はコメント欄に、質問があるかたは、Amazon VPC forumに投稿下さい。
- Nicolas (翻訳はSA布目が担当しました。原文はこちら)