注目の記事

踏み台サーバ経由のSSHセッションを記録する方法

2017.03.06

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

こんにちは。大阪の市田です。
今回は、下記のブログの内容を元に、踏み台サーバ経由のSSHセッションを記録する方法をご紹介します。

How to Record SSH Sessions Established Through a Bastion Host | AWS Security Blog

尚、踏み台サーバはAmazon Linuxを想定しています。

ポイント

この記事のポイントは下記です。

  • OpenSSHの設定の修正
  • scriptコマンドの利用
  • 踏み台サーバユーザの権限制限
  • ログファイルのS3保管
  • S3による踏み台サーバユーザの自動管理
  • SSHのエージェントフォワード利用
  • CloudFormationで環境構築

それでは順に説明していきたいと思います。

構成

想定の構成は下記の通りです。

diagram-20170306

ログファイルのディレクトリ作成

まずは、踏み台サーバにログの保存ディレクトリを作成し、アクセス制限を行います。

# 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コマンドによる記録は、bashncなどで回避することができてしまいます。これを防止する為に$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

作成するリージョンを確認します。

01-createstack

Stack nameは適宜変更して下さい。先程作成した2つのキーペアをそれぞれパラメータで指定します。

02-cfndetail

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タブで確認できます。

03-CFn-Outputs

動作確認

公開鍵の取得

それでは、実際に動作確認してみます。 まず、プライベートサブネットにあるサーバの公開鍵を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バケットにデータがあると削除できませんので、バケットを空にしておくことに注意して下さい。

最後に

この内容は監査等にも利用できるかと思いますので、うまく組み合わせて頂ければと思います。

以上です。