大量のサーバーに鍵登録した時に自動接続確認した話

# 事の始まり
扱っている事業のサーバーに大量に鍵登録した。ただサーバーログインする環境がバラバラで、踏み台サーバーを踏まなきゃいけないサーバーがわからないので、とりあえず、弊社の踏み台から入れるものだけを調べて、残ったサーバーのログインできない方法を各個撃破していきました。(ほとんどは、弊社の踏み台で入れるということだったので。)
　
今日はそんな大量鍵登録したサーバーの接続確認を１つ１つしていくのが大変なので、自動で多段sshして、接続確認できるスクリプトを書いたお話です。

## サーバーのホストネームだけ書かれた一列のテキストを用意する

縦一列に並べるのがミソです。シェルスクリプトのfor文は文字列を１つ１つ処理していくので、ホストネームをひとつ書いたら改行して次のホストネームを書くようにしてください。

## .ssh/configのファイルを編集する

.ssh/config


ForwardAgent yes
LogLevel ERROR

Host 踏み台サーバー名
Hostname IPアドレス or 紐付いているアドレス名
User ユーザー名

Host 多段sshしたいサーバー名
  ProxyCommand ssh -W %h:%p 踏み台サーバー名
  HostName 多段sshしたいサーバ名
  User ユーザー名

Host 多段sshしたいサーバー名
  ProxyCommand ssh -W %h:%p 踏み台サーバー名
  HostName 多段sshしたいサーバ名
  User ユーザー名
　　　　　　　　　　　　　　　　　　 ・
　　　　　　　　　　　　　　　　　　　　・
　　　　　　　　　　　　　　　　　　　　・

まず上記のように、踏み台サーバーをconfigファイルに記述し、その後に多段sshしたいサーバーに対してProxyCommandを用いて、代理でsshコマンドを入力してもらいます。
sshコマンドにつけているオプションは、

**-W**
クライアント上の標準入出力が、セキュアなチャネルを介してポート上のホストに転送されるように要求します

**%h:%p**　
踏み台にするサーバーのポート番号に置換します

です。

この記述の仕方をコピペして、鍵登録をしたサーバーをconfigファイルに　書きまくっていきます。

## 自動ssh確認スクリプトを作る。
　たった9行と思う事なかれ。ここに結構な学びの要素が詰まっています。

```ssh_analysis.ssh

#!/bin/zsh

for row in $(cat server.txt);
do
  ssh -oStrictHostKeyChecking=no $row "touch $USER" > /dev/null 2>&1
  if [ $? -ne 0 ] ; then
    echo $row
  fi
done;

```

**for文**

シェルスクリプトのfor文は、文字列処理を行います。

```
for 変数　in 文字列;
do
　処理内容
done
```

構文はこんななのですが、今回は、文字列ではなく、catコマンドでテキストの中身を取りに行かせたいわけです。

ここで登場するのが

**$()**

この子を書いてあげる事で、このカッコのコマンドが実行された結果が文字列としてfor文の引数として、変数(今回はrow)に入るという流れになります。

次に、doの中に処理内容を書いていきます。まずやってほしいのは、sshコマンドなので、sshコマンドを書きます。またsshに長いオプションが付いています。一個ずつ見ていきます。

**-oStrictHostKeyChecking=no**

こいつは、ssh接続する際のフィンガープリント確認の警告文を出さないようにしてくれます。

**$row**
rowは変数名ですが、forで１つずつcatで標準出力されているホスト名がsshコマンドの中に入ってきます

**"touch $USER"**
ダブルクォーテーションとシングルクォーテーションにはシェルスクリプトでは違いがあり、どちらも文字列で扱ってくれるのですが、式展開もできる能力も備えているのが、ダブルクォーテーションです。今回は、sshコマンドの第2引数として、空のUSERのtouchコマンドの式展開をしています。第2引数にコマンドを置いてあげると、**sshした先のサーバーで、そのコマンドを実行し、ローカルのターミナル上で標準出力することができます。**これを利用して、スクリプトを実行して自動sshした時に、向こうのリモートシェルのコンソール上に入ってそこでスクリプトの実行が止まってしまうことを防いでいます。←個人的にここすごい応用みを感じる部分です。

**> /dev/null 2>&1**
これで、さっきのtouch $USEで出てくる文字列内容を/dev/nullといUNIXのブラックホールに>を使ってリダイレクトさせています。また、この1や２というのは、出力内容を示しており、３つ存在する。

UNIXの出力3種

- 0: 標準入力
- 1: 標準出力
- 2: 標準エラー出力

今回の**2>&1**は、エラー標準出力を標準出力にリダイレクトさせていて、ブラックホールのnullに入れているということになります。
なぜsshコマンドにこれらのオプションをつけなければならないかと言いますと、一旦sshしたらさっさとそこから抜け出て、次のsshに自動で行ってほしいわけです。なので、sshした時に、入った先のサーバーのコマンドラインに入る前に抜け出る必要があります。だから、sshでログインした段階でわちゃっと出てくる標準出力やログインできなかった時のエラー標準出力をまとめてnullにぶち込んで消してやっているわけです。

最後に
**if文**
この構文は、

```
if コマンド ; then
処理内容
fi
```

です。for文と違い、条件式のところは　**コマンドでなければならない**です。
シェルスクリプトのif文は、終了ステータスの真偽値、0がtrueで、1がfalseを示します。

今回は、sshした時の**終了ステータス**が成功時の0か失敗時の1かが出力さるので、それを条件式としたいです。しかし、こいつをコマンドでどーやって表すんじゃいっていう話です。ifは条件式をコマンドでしか取れません。そこで登場するのが、

**[ ]**
これはテストコマンドと呼ばれる、**コマンドの終了ステータスをこのテストコマンド自体の終了ステータスの真偽値で判断する** if文御用達のコマンドです。
今回の場合、sshをした終了ステータスがどうかを判断し、テストコマンドとしての終了ステータスを出しています。

中に書かれている**$?**は、終了ステータスを表します。それが、テストコマンドのオプションである**-ne**で終了ステータスに真、つまり0がない状況だった場合、echoでその時の変数を標準出力するというものです。

いかがでしたでしょうか？これによって、自動でログインできないサーバーを検知します。作業効率がたった9行で楽になるなんて素晴らしいですね。

## 余談

最初はこんな風に、真の場合、は**:**で何もしない。真でなければechoを返すという書き方をしていました。本文に載せたやつはリファクタリングしたものです。コロンは何もしないということを示せるんですね。

```
if [ $? = 0 ] ; then
    :
  else    
    echo $row
  fi
```