AWS EC2へのSSHを便利にするツールのご紹介

Adways Advent Calendar 2020 19 日目の記事です。


 

こんにちは。インフラの天津です。

今年のアドベントカレンダーも最後となりました。

みなさんにとって今年はどんな年だったでしょうか。

COVID-19により様々なことが起きた年でしたが、皆さんにとって良い年であったことと、来年も良い年であることをを願ってやみません。

さて、今日はEC2インスタンスへのSSHを行う際に便利なツールをご紹介します。
EC2を使用されている方に何かしらお役に立てれば幸いです。

モチベーション

以前から、AWSのEC2インスタンスがAuto Scallingなどで動的に変化したり、IPアドレスを固定していない場合に都度SSH先のIPアドレスを調べることが面倒と感じていました。

そこで一覧からSSHしたいインスタンスを選択してログインできたら便利だなと思い、ツールを作って活用しています。

ツール紹介

スクリプト

bashで作成しています。a-peco.sh と名付けています。

a-peco.sh

#!/usr/bin/env bash

function _usage() {
  cat << EOF
usage: a-peco.sh [-doj] [-i identity_file_path] [-u ssh_user] [-p aws_profile_name] 
  options:
    -o: only display instances(format by column)
    -j: only display instances(json)
    -i: specify identy file (default: ~/.ssh/id_rsa)
    -u: specify user (default: ec2-user)
    -p: specify AWS profile
    -d: debug ssh command
EOF
  exit
}

function _format_for_json() {
  echo "${1}" | \
    jq -r '
    [
      .Reservations[]|.Instances[] | 
      {
        "name": [ .Tags[] | select(.Key == "Name").Value ][],
        "role": [ .Tags[] | select(.Key == "role").Value ][],
        "State": .State.Name,
        PrivateIpAddress,
        PublicIpAddress,
        InstanceId
      }
    ]'
}

function _format_for_peco() {
  echo "${1}" | \
    jq -r '.Reservations[]|.Instances[] | 
      [
        [ .Tags[] | select(.Key == "Name").Value ][] // "----",
        [ .Tags[] | select(.Key == "role").Value ][] // "----",
        .State.Name,
        .PrivateIpAddress // "----",
        .PublicIpAddress // "----",
        .InstanceId // "----"
      ] |
      @tsv' |
    sort -k 3f,3 -k 5fr,5 -k 2,2rf | \
    column -t -s "$(printf '\t')"
}

# default values
USER=ec2-user
PRIVATE_KEY=~/.ssh/id_rsa

while getopts "dojp:i:u:" OPT; do
  case $OPT in
    i) PRIVATE_KEY="$OPTARG" ;;
    u) USER="$OPTARG";;
    p) PROFILE="$OPTARG" ;;
    o) DISP_ONLY=1 ;;
    j) DISP_JSON=1 ;;
    d) DEBUG=1 ;;
    *) _usage;;
  esac
done

# PROFILEの設定
if [[ ${PROFILE} ]]; then
  export AWS_PROFILE=${PROFILE}
  export AWS_DEFAULT_PROFILE=$AWS_PROFILE
fi

# インスタンス情報の取得
ec2_describe_response=$(aws ec2 describe-instances)
test -z "${ec2_describe_response}" && echo "No instance described" && exit

# -o なら 表示して終わり
if [[ -n ${DISP_ONLY} ]]; then
  if [[ -n ${DISP_JSON} ]]; then
    _format_for_json "${ec2_describe_response}" 
    exit
  else
    _format_for_peco "${ec2_describe_response}" 
    exit
  fi
fi

# SSH
## インスタンスの選択
eval "SELECTED_INSTANCE_ARRAY=( $( _format_for_peco "${ec2_describe_response}" | peco ))"
test -z "${SELECTED_INSTANCE_ARRAY}" && echo "No instance selected" && exit

## SSH
### コマンド作成
ssh_command="ssh -i ${PRIVATE_KEY} ${USER}@${SELECTED_INSTANCE_ARRAY[3]}"
echo "ssh command => ${ssh_command}"
test "${DEBUG}" && exit  # デバッグモード時はここで終了

### 実行
eval "${ssh_command}"

スクリプトの解説

実装ですが、bashスクリプトでaws cliとjq, pecoを使用しています。

処理内容は次のとおりです。

  • aws cliでインスタンス情報を取得
  • 必要な情報をjqで取得
  • sortとcolumnで整形
  • pecoにて選択
  • 選択されたターゲットにsshを実行

上記のスクリプトでは下記情報を取得していますが、お使いの環境に合わせて取得する項目は変えていただければと思います。

特にtagは変更が必要かと思います。

  • インスタンスID
  • プライベートIP
  • グローバルIP
  • tag:Name
  • tag:role
  • 状態

また、踏み台サーバ情報は持たせていないため、~/.ssh/configで設定済みであることを前提としています。

下記はVPC IPアドレス帯が10.0.0.0/16、踏み台サーバが1.2.3.4の場合の設定例です。

# step
Host 1.2.3.4
  StrictHostKeyChecking no
  User ec2-user
  IdentityFile ~/.ssh/ec2.pem

Host 10.0.*
  StrictHostKeyChecking no
  ProxyJump 1.2.3.4
  User ec2-user
  IdentityFile ~/.ssh/ec2.pem

インストール方法など

/usr/local/bin など、PATHが通った場所に配置し、実行権を付与して使用します。

aws cliやjq, pecoのインストールについては割愛しますが、Macであればbrewでインストール可能です。

利用方法

下記のオプションを使用して実行します。

usage: a-peco.sh [-doj] [-i identity_file_path] [-u ssh_user] [-p aws_profile_name] 
  options:
    -o: only display instances(format by column)
    -j: only display instances(json)
    -i: specify identy file (default: ~/.ssh/id_rsa)
    -u: specify user (default: ec2-user)
    -p: specify AWS profile
    -d: debug ssh command

選択肢を表示してsshする

a-peco.sh -i <秘密鍵のパス> -u <sshユーザ> -p <awsプロファイル名>

選択肢を表示してsshコマンドを表示する(デバッグモード)

a-peco.sh -d -i <秘密鍵のパス> -u <sshユーザ> -p <awsプロファイル名>

インスタンス一覧を表示(columnコマンド整形済み)

a-peco.sh -p <awsプロファイル名>  -o

インスタンス一覧を表示(json)

a-peco.sh -p <awsプロファイル名>  -oj

.ssh/config不要ver.

~/.ssh/configに設定することが面倒と感じる方もいらっしゃるかも知れません。
その場合は、踏み台サーバを自動取得し、proxycommandを生成することで~/.ssh/configの設定を不要にすることができます。

具体的な例ですが、下記はtag:roleにstepという識別子を与えた場合のスクリプトです。

鍵情報とユーザは踏み台とssh先で共通であること、踏み台が1台であることを想定しています。
(踏み台が2台以上になる場合は.ssh/configを記載するほうが良いでしょう)

a-peco2.sh

#!/usr/bin/env bash

function _usage() {
  cat << EOF
usage: a-peco.sh [-doj] [-i identity_file_path] [-u ssh_user] [-p aws_profile_name] 
  options:
    -o: only display instances(format by column)
    -j: only display instances(json)
    -i: specify identy file (default: ~/.ssh/id_rsa)
    -u: specify user (default: ec2-user)
    -p: specify AWS profile
    -d: debug ssh command
EOF
  exit
}

function _format_for_json() {
  echo "${1}" | \
    jq -r '
    [
      .Reservations[]|.Instances[] | 
      {
        "name": [ .Tags[] | select(.Key == "Name").Value ][],
        "role": [ .Tags[] | select(.Key == "role").Value ][],
        "State": .State.Name,
        PrivateIpAddress,
        PublicIpAddress,
        InstanceId
      }
    ]'
}

function _format_for_peco() {
  echo "${1}" | \
    jq -r '.Reservations[]|.Instances[] | 
      [
        [ .Tags[] | select(.Key == "Name").Value ][] // "----",
        [ .Tags[] | select(.Key == "role").Value ][] // "----",
        .State.Name,
        .PrivateIpAddress // "----",
        .PublicIpAddress // "----",
        .InstanceId // "----"
      ] |
      @tsv' |
    sort -k 3f,3 -k 5fr,5 -k 2,2rf | \
    column -t -s "$(printf '\t')"
}

# default values
USER=ec2-user
PRIVATE_KEY=~/.ssh/id_rsa

while getopts "dojp:i:u:" OPT; do
  case $OPT in
    i) PRIVATE_KEY="$OPTARG" ;;
    u) USER="$OPTARG";;
    p) PROFILE="$OPTARG" ;;
    o) DISP_ONLY=1 ;;
    j) DISP_JSON=1 ;;
    d) DEBUG=1 ;;
    *) _usage;;
  esac
done

# PROFILEの設定
if [[ ${PROFILE} ]]; then
  export AWS_PROFILE=${PROFILE}
  export AWS_DEFAULT_PROFILE=$AWS_PROFILE
fi

# インスタンス情報の取得
ec2_describe_response=$(aws ec2 describe-instances)
test -z "${ec2_describe_response}" && echo "No instance described" && exit

# -o なら 表示して終わり
if [[ -n ${DISP_ONLY} ]]; then
  if [[ -n ${DISP_JSON} ]]; then
    _format_for_json "${ec2_describe_response}" 
    exit
  else
    _format_for_peco "${ec2_describe_response}" 
    exit
  fi
fi

# 踏み台サーバの取得
step_server=$(echo "${ec2_describe_response}" | \
  jq -r '.Reservations[].Instances[]|
    select(.Tags[]|select(.Key == "role").Value == "step")|
    .PublicIpAddress' 2> /dev/null \
)
test -z "${step_server}" && echo "No step server is defined" && exit

# SSH
## インスタンスの選択
eval "SELECTED_INSTANCE_ARRAY=( $( _format_for_peco "${ec2_describe_response}" | peco ))"
test -z "${SELECTED_INSTANCE_ARRAY}" && echo "No instance selected" && exit

## SSH
### コマンド作成
proxy_command=('"' ssh -i "${PRIVATE_KEY}" "${USER}@${step_server}" -W %h:%p '"')
ssh_command="ssh -o ProxyCommand=${proxy_command[*]} -i ${PRIVATE_KEY} ${USER}@${SELECTED_INSTANCE_ARRAY[3]}"
echo "ssh command => ${ssh_command}"
test "${DEBUG}" && exit  # デバッグモード時はここで終了

### 実行
eval "${ssh_command}"

応用

今回のツールはssh用だけではなく、簡易的なインスタンスの確認ツールとしても使用できます。

また、-o, -jオプションにて選択したインスタンスの情報をtsvやjsonで出力することもサポートしていますので、出力結果をもとに一括で何らかの作業を実施するなども可能です。

まとめ

本ツールを利用することで、手軽にインスタンスの一覧化とsshすることが可能になりました。

現在ではEC2へのログインはSession Managerなどssh以外の機能もありますが、sshを使用している方も多いと思いますので参考になればと思います。

それでは皆様、来年もAdwaysエンジニアブログをよろしくお願いします。

Merry X'mas & Happy New Year!