この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは。大阪の市田です。
今回は、下記のブログの内容を元に、踏み台サーバ経由のSSHセッションを記録する方法をご紹介します。
How to Record SSH Sessions Established Through a Bastion Host | AWS Security Blog
尚、踏み台サーバはAmazon Linuxを想定しています。
ポイント
この記事のポイントは下記です。
- OpenSSHの設定の修正
- scriptコマンドの利用
- 踏み台サーバユーザの権限制限
- ログファイルのS3保管
- S3による踏み台サーバユーザの自動管理
- SSHのエージェントフォワード利用
- CloudFormationで環境構築
それでは順に説明していきたいと思います。
構成
想定の構成は下記の通りです。
ログファイルのディレクトリ作成
まずは、踏み台サーバにログの保存ディレクトリを作成し、アクセス制限を行います。
# mkdir /var/log/bastion
# chown ec2-user:ec2-user /var/log/bastion
# chmod -R 770 /var/log/bastion
# setfacl -Rdm other:0 /var/log/bastion
OpenSSHの設定
SSHログイン時に/usr/bin/bastion/shell
というスクリプトを実行するようにします。スクリプトの内容は後で記載します。
# echo -e "\nForceCommand /usr/bin/bastion/shell " >> /etc/ssh⁄sshd_config
次に、今回の設定を回避できないように一部の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
ログイン時のスクリプト作成
先程の/usr/bin/bastion/shell
を作成します。まずはディレクトリを作成します。
# mkdir /usr/bin/bastion
下記の内容で/usr/bin/bastion/shell
を作成します。
# Check that the SSH client did not supply a command
if [[ -z $SSH_ORIGINAL_COMMAND ]]; then
# The format of log files is /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/"
# Print a welcome message
echo ""
echo "NOTE: This SSH session will be recorded"
echo "AUDIT KEY: $LOG_FILE"
echo ""
# I suffix the log file name with a random string. I explain why later on.
SUFFIX=`mktemp -u _XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`
# Wrap an interactive shell into "script" to record the SSH session
script -qf --timing=$LOG_DIR$LOG_FILE$SUFFIX.time $LOG_DIR$LOG_FILE$SUFFIX.data --command=/bin/bash
else
# The "script" program could be circumvented with some commands (e.g. bash, nc).
# Therefore, I intentionally prevent users from supplying commands.
echo "This bastion supports interactive sessions only. Do not supply a command"
exit 1
fi
このスクリプトは、環境変数$SSH_ORIGINAL_COMMAND
の有無によって処理が変わります。
もしこの環境変数がセットされていなければ、ログイン時にscriptコマンドの中にインタラクティブシェルがラップされます。
環境変数$SSH_ORIGINAL_COMMAND
がセットされている場合は、下記メッセージが出力されてログイン出来ません。例えば、ec2-userの公開鍵(~/.ssh/authorized_keys)を下記のように編集します。
command="grep $SSH_ORIGINAL_COMMAND /var/log/cloud-init.log" ssh-rsa AAA (以下略)
この状態で、ローカルPCからコマンドを実行してみます。
$ ssh -i [path to bastion.pem] ec2-user@[public IP of the bastion host] "GET"
This bastion supports interactive sessions only. Do not supply a command
インタラクティブなセッションしか受け付けないというメッセージが出て、期待するコマンドを実行出来ませんでした。
上記スクリプトのコメントにも記載されていますが、scriptコマンドによる記録は、bash
やnc
などで回避することができてしまいます。これを防止する為に$SSH_ORIGINAL_COMMAND
のチェックを行っているという訳です。
作成できたら実行権限を付けておきます。
# chmod a+x /usr/bin/bastion/shell
その他に、踏み台サーバ上で他ユーザのプロセスが見えないようにするといった制限を設定します。
# chown root:ec2-user /usr/bin/script
# chmod g+s /usr/bin/script
# 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
ログファイルのS3保管
上記のスクリプトにある通り、ログイン後のSSHセッションのログファイルは/var/log/bastion
ディレクトリに保存されます。また、このログはS3バケットに定期的にコピーしておくようにします。
下記のような内容のスクリプト(/usr/bin/bastion/sync_s3
)を作成します。
# Copy log files to S3 with server-side encryption enabled.
# Then, if successful, delete log files that are older than a day.
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 {} \;
実行権限を付けます。
# chmod 700 /usr/bin/bastion/sync_s3
S3による踏み台サーバユーザの自動管理
この記事では、踏み台サーバのアカウント管理にS3を利用する方法を紹介しています。仕組みの概要は次の通りです。
- 踏み台サーバから接続するサーバのキーペアを作成(秘密鍵のダウンロード)
- 秘密鍵から公開鍵を作成
- アカウント毎の公開鍵をS3にアップロード
- 踏み台サーバがS3上の公開鍵に対するユーザを自動作成
- S3上の公開鍵が削除されると踏み台サーバ上のユーザも削除
- 踏み台サーバはcronでS3バケットを定期チェック
踏み台サーバのユーザを作成、削除したログは下記のような内容で/var/log/bastion/users_changelog.txt
に保存されます。
2017-03-06 07-55-02: Creating user account for sshuser (public-keys/sshuser.pub)
2017-03-06 09-00-03: Removing user account for sshuser (public-keys/sshuser.pub)
それでは、S3を定期的にチェックしてアカウントを管理するスクリプト(/usr/bin/bastion/sync_users
)を作成します。
# Bastion host users should log in to the bastion host with
# their personal SSH key pair. The public keys are stored on
# S3 with the following naming convention: "username.pub". This
# script retrieves the public keys, creates or deletes local user
# accounts as needed, and copies the public key to
# /home/username/.ssh/authorized_keys
cat > /usr/bin/bastion/sync_users << 'EOF'
# The file will log user changes
LOG_FILE="/var/log/bastion/users_changelog.txt"
# The function returns the user name from the public key file name.
# Example: public-keys/sshuser.pub => sshuser
get_user_name () {
echo "$1" | sed -e 's/.*\///g' | sed -e 's/\.pub//g'
}
# For each public key available in the S3 bucket
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"`"
# Make sure the user name is alphanumeric
if [[ "$USER_NAME" =~ ^[a-z][-a-z0-9]*$ ]]; then
# Create a user account if it does not already exist
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
# Copy the public key from S3, if a user account was created
# from this key
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
# Remove user accounts whose public key was deleted from 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
実行権限を付けます。
chmod 700 /usr/bin/bastion/sync_users
cronに登録します。
# crontab -e
# 下記の内容で登録します
*/5 * * * * /usr/bin/bastion/sync_s3
*/5 * * * * /usr/bin/bastion/sync_users
0 0 * * * yum -y update --security
尚、鍵ファイルの取扱いには十分に注意するようにしましょう。S3バケットへのアクセスもバケットポリシーやIAMポリシーを使って、IP制限や部門毎に権限を分けるなど、適切に設定することが望ましいです。
CloudFormationでテスト環境を構築
これまで説明した構成はCloudFormationですぐに試すことができます。 その前に、キーペアを2つ作成しておきます。秘密鍵は忘れずダウンロードしましょう。
- bastion
- 踏み台サーバ用のキーペアです。
- sshuser
- 踏み台からアクセスするプライベートサブネットに置くサーバのキーペアです。
CloudFormationのスタック作成は下記リンクから可能です。
Create a Stack - Record SSH Sessions Bastion Host
作成するリージョンを確認します。
Stack nameは適宜変更して下さい。先程作成した2つのキーペアをそれぞれパラメータで指定します。
CloudFormationで作成されるもの
- インターネットゲートウェイがアタッチされたVPC
- パブリックサブネット
- プライベートサブネット(インターネットアクセス不可)
- sshbastionrecording-bucket-xxxxxxxxxxというS3バケット
- ログは
logs
フォルダに保存されます。 - アカウント管理用の公開鍵は
public-keys
フォルダに保存されますが、これは手動で作成します。
- ログは
- 2つのセキュリティグループ
- 1つは踏み台サーバ用で、インターネットからのSSHアクセスを許可します
- 0.0.0.0/0で公開されるので、適宜修正して下さい。
- 2つ目は踏み台サーバ用のセキュリティグループからのSSHアクセスを許可します。
- S3に対するいくつかの権限を持ったIAMロール
- 踏み台サーバにひも付きます。
- 踏み台サーバと踏み台サーバからアクセスされるプライベート上のサーバ
- いずれもAmazon Linuxです。
作成されるS3バケット名や各サーバのIPアドレスは、CloudFormationのOutputタブで確認できます。
動作確認
公開鍵の取得
それでは、実際に動作確認してみます。
まず、プライベートサブネットにあるサーバの公開鍵をS3にアップロードします。
とはいえ、手元にはsshuser.pem
という秘密鍵しか無いので、秘密鍵から公開鍵を取得します。
最初に秘密鍵のパーミッションを修正します。
$ chmod 400 sshuser.pem
秘密鍵があるPC上でssh-keygen
コマンドで公開鍵を取得します。
$ ssh-keygen -y
秘密鍵の入力を求められるので、秘密鍵のパスを入力します。
Enter file in which the key is (/Users/xxxxxx/.ssh/id_rsa):[path to sshuser.pem]
下記のように公開鍵の内容が返ってきますので、公開鍵ファイル(sshuser.pub
)として保存します。
ssh-rsa AAA(以下略)
参考: Amazon EC2 のキーペア - Amazon Elastic Compute Cloud
公開鍵のアップロード
CloudFormationで作成されたS3バケットに対して、手動でpublic-keys
というフォルダを作成します。そのフォルダにsshuser.pub
を保存します。
数分待てば、踏み台サーバのcronによりsshuser
アカウントが作成されます。逆に保存したpubファイルを削除すると、踏み台サーバからも該当ユーザが削除されます。
踏み台サーバにログイン
それでは踏み台サーバにログインします。しかし、プライベートサブネットにあるサーバの秘密鍵を踏み台サーバに置きたくありませんので、SSHエージェントフォワーディングを使います。
$ chmod 400 [path to sshuser.pem]
$ ssh-add [path to sshuser.pem]
Identity added: [path to sshuser.pem] ([path to sshuser.pem])
# 確認(記憶した鍵の一覧表示)
$ ssh-add -l
2048 SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx [path to sshuser.pem] (RSA)
それでは踏み台サーバにログインします。ログインすると「AUDIT KEY」 が表示されます。これが該当のSSHセッションを記録しているIDになります。
$ ssh -A sshuser@[public IP of the bastion host] -i [path to sshuser.pem]
NOTE: This SSH session will be recorded
AUDIT KEY: 2017-03-03_17-16-39_sshuser
踏み台サーバにログインできたら、そこからプライベートサブネットにあるサーバにログインします。ログインする時は秘密鍵の指定なくログイン可能です。
$ ssh ec2-user@[private IP of the Linux instance]
ログアウトした後は、安全の為に記憶した鍵情報は削除しておきます。-D
オプションで記憶したものを全て削除できます。
$ ssh-add -D
SSHセッションのリプレイ
踏み台サーバでは/usr/bin/bastion/sync_s3
により、scriptコマンドによるログが1日でサーバから削除されます。その為、リプレイする場合は通常、該当するファイルをS3からダウンロードしてリプレイするようにします。
例えば、S3のReadOnlyのIAM権限を持ったEC2インスタンスで再生します。(IAMの権限は必要最小限にします。)
リプレイ(再生)
まずはS3から対象のファイルをダウンロードします。今回はAWS CLIを使っています。「AUDIT KEY」は調査したい日時のものを指定して下さい。
$ aws s3 cp --profile ichida-s3-readonly s3://sshbastionrecording-bucket-xxxxxxxxxx/logs/ . --recursive --exclude "*" --include "[AUDIT KEY]*"
$ export LOGFILE=`ls [AUDIT KEY]*.data | cut -d. -f1`
ファイルを指定して再生します。
$ scriptreplay --timing=$LOGFILE.time $LOGFILE.data
CloudFormationスタックの削除
CloudFormationで作成した各リソースは、スタックの削除ですべて消せますが、S3バケットにデータがあると削除できませんので、バケットを空にしておくことに注意して下さい。
最後に
この内容は監査等にも利用できるかと思いますので、うまく組み合わせて頂ければと思います。
以上です。