SSHログインを二段階認証（ワンタイムパスワード）したお話。

#SSHログインを二段階認証化したお話。

※本記事により、これらの情報を運用した結果について、筆者はいかなる責任も負いません。本番環境で試される前に必ずご自身で検証されることをお勧めします。


#### はじめに
最近、複数人でRaspberry Piを使ってIotをするようになった。
ただ、置いている場所の都合上、スタッフ以外の一般人からもアクセスできるAPに接続しなければならない環境にある。
SSHにログインする際、いつも不安がつきまとう。鍵ファイルが漏れたら終わりだ。
そんな不安を少しでも和らげようと、SSHを二段階認証にしてみることに。
しかし、いざ始めようにも、情報が少ない。
そんな中で、最近、DjangoとLINE Notifyを使った二段階認証を作成した事例を見かけた。
これを参考に、SSHログインに応用できないか考えてみた。

まず、以下のようなモデルを作る必要がある。
<img width="306" alt=" 6.png" src="https://qiita-image-store.s3.amazonaws.com/0/110060/f849b7bc-48eb-f160-5b54-210bd97be2b4.png">

上記モデルは、想定人数が何百人の話になっている。

では、今回のモデルについて考えてみる。
・SSHにログインするユーザは10人未満である。
　　・管理ページや管理サイトを作る必要がない。

・二段階認証は強制にしたい。

・Rasbianのユーザはrootとpiの二つのみ。
　　・このアカウントに誰がログインしたかという情報の通知。
　　・接続元の情報（IPアドレスなど）をログイン完了後に通知。

今回↑上記を元に二段階認証を構築したいと思う。

#### LINE Notifyを使う。
二段階認証をする際、メールでアクセスコードを送信して認証をさせるシステムが一般的だと思う。
しかし、今時、うちうちで使用するシステムにメールを使うことはほとんどないのではないだろうか。

そこで、普段から使っているコミュニケーションツールである、LINEやSlackにアクセスコードを送信する方法にしたいと思う。
また、LINE Notifyを使った二段階認証が実際に大学で使われている事例があるので、問題ないと思われる。

個人的にはSlackを使いたかったのだが、LINEしかみない人がいるので、やはり、同様にLINE Notifyを用いることにした。

##### ・LINE Notifyにアクセス
まずは、LINE Notifyにアクセスしてみた。↓
https://notify-bot.line.me/ja/
<img width="1149" alt=" 3.png" src="https://qiita-image-store.s3.amazonaws.com/0/110060/184915f0-64ef-5b92-18d9-e68f47c05da9.png">

ログインする。

<img width="710" alt=" 4.png" src="https://qiita-image-store.s3.amazonaws.com/0/110060/b94e86df-a8cf-ebac-4b5c-75edaaec3d6e.png">

ログインできたら、My pageへ移動。

<img width="626" alt=" 5.png" src="https://qiita-image-store.s3.amazonaws.com/0/110060/60f351c3-e268-81da-5dc7-7db7fdd8801a.png">

そして、Generate tokenをクリック。

<img width="949" alt=" 2.png" src="https://qiita-image-store.s3.amazonaws.com/0/110060/ee024821-06e1-f5bd-3f5d-f3c3eab7b661.png">

Access tokenを取得できたら、閉じる前にtokenをきちんとコピーしておくこと。
これでLINE Notifyを使う準備はできた。

bashで使うには、curlなどを用いてやれば良い。[^5]


```sh:LineNotify

$ curl -X POST -H "Authorization: Bearer ここにアクセストークン" -F "message=${code}" https://notify-api.line.me/api/notify

```

#### bashログイン時にスクリプトを実行したい。

次に考えなければならないのは、どのタイミングでスクリプトを実行させるかという問題である。
SSHログインができれば即時実行できるようにしたい。
これを可能にしてくれるのが、.profileである。[^9]

2019/01/30追記〜
プロファイルスキップ

```terminal:terminal
$ ssh -t user@host bash --noprofile
```

プロファイルのスキップは簡単である事をご指摘いただいた。
上記コマンドで簡単にプロファイルをスキップすることができるようだ。[^11]


この.profileはホームディレクトリにある。
このファイルの先頭にスクリプトを加えてあげれば良い。

#### Ctrl + C 　や　Ctrl + Z　で回避される問題。

ここで、問題になるのが、Ctrl + C　や　Ctrl + Z　で強制的に認証処理を終了することが出来てしまう。
この問題を回避するために、trapというものを使う。
trap コマンドは送出されたシグナルを捕捉し、あらかじめ指定されていた処理を実行するコマンドである。[^2]
このtrapコマンドを応用して、[^3]

```sh:trap
trap 'exit' SIGINT
trap -- SIGINT

```
上記を加えてあげれば、 Ctrl + C や Ctrl + Z　でSIGINTを補足した時、exitコマンドを実行してくれる。

#### アクセスコードの生成

ランダムにアクセスコードを生成して、認証に使いたい。
そこで、opensslを使ってランダムにコードを生成させたい。[^4][^8]
四桁程度にしたかったので、以下のようにパイプ処理した。

```sh:openssl
$ openssl rand -base64 12 | fold -w 4 | head -1
```

#### 誰がログインしたか知りたい
次にユーザ登録をどうするかの問題だが、管理サイトを作らないで、ユーザを管理者が登録する形にしたいと思う。
login_user.txtといったように適当にファイルを作って、一行ごとにログインできるユーザを追加してあげれば良い。

```txt:login_user.txt
name1
name2
name3
name4
```
このファイルを一行ずつ比較するようにした。[^6][^7]

```sh:login_user_process

__uname() {
read uname
  cat login_user.txt | while read user
  do
    if [ "$uname" = "$user" ]; then
        userstatus="t"
        echo $userstatus
        break
    else
        userstatus="f"
    fi
  done

linesend=`curl -X POST -H "Authorization: Bearer ここにLineNotifyアクセストークン" -F "message=${uname}からのアクセス。" https://notify-api.line.me/api/notify > /dev/null 2>&1`


  return 0
}
main() {
echo please input user name:
ustatus=`__uname`
if [ "$ustatus" = "t" ]; then
    echo ok.
else
    echo false.
    exit
fi
}

main $@
```

#### アクセスコード送信、比較
次に、アクセスコードの送信と比較。

・opensslで四桁のアクセスコード生成。
・LINE Notifyでアクセスコードを送信。
・アクセスコード入力。
・認証完了。

↑と言う流れになる。

```sh:ac_code
ac_code=`openssl rand -base64 12 | fold -w 4 | head -1`
curl -X POST -H "Authorization: Bearer ここにLineNotifyアクセストークン" -F "message=${ac_code}" https://notify-api.line.me/api/notify > /dev/null 2>&1
echo "\n"

echo please input token:
read ans
if [ $ans = $ac_code ]; then
	echo OK,Passed.
else
	echo false.
	exit
fi

```

また、アクセスコードを入力した際、四桁のコードが見えるのはよくない。
アクセスコードの入力を非表示にする。[^10]

```sh:stty_process
stty -echo
echo please input token:
read ans
stty echo
```

#### 接続元IPアドレス、ポート番号の通知

最後に接続元IPアドレス、ポート番号の通知に関して。
環境変数の$SSH_CONNECTIONで上記の情報を参照できるのでそれを利用して、Line Notifyへ通知させる。

```sh:ip_port
echo Please input AccessCode:
read ans
if [ "$ans" = "$ac_code" ]; then
    echo complete.
    curl -X POST -H "Authorization: Bearer ${line_token}" -F "message=正常ログインしました。" https://notify-api.line.me/api/notify > /dev/null 2>&1
    curl -X POST -H "Authorization: Bearer ${line_token}" -F "message=(${SSH_CONNECTION})${uname}:接続情報" https://notify-api.line.me/api/notify > /dev/null 2>&1
else
    echo false.
    curl -X POST -H "Authorization: Bearer ${line_token}" -F "message=アクセスコードが一致しません。" https://notify-api.line.me/api/notify > /dev/null 2>&1
    curl -X POST -H "Authorization: Bearer ${line_token}" -F "message=(${SSH_CONNECTION})${uname}:拒否元接続情報" https://notify-api.line.me/api/notify > /dev/null 2>&1
    exit
fi
```

最終的に完成した.profileが以下。

```sh:.profile
#!/bin/sh
#<>は必要ない
__uname() {
line_token="<ここにLineNotifyアクセストークン>"
read uname
  cat login_user.txt | while read user
  do
    if [ "$uname" = "$user" ]; then
        userstatus="t"
        echo $userstatus
        break
    else
        userstatus="f"
    fi
  done
linesend=`curl -X POST -H "Authorization: Bearer ${line_token}" -F "message=${uname}からのアクセス。" https://notify-api.line.me/api/notify > /dev/null 2>&1`
return 0
}
main() {
line_token="<ここにLineNotifyアクセストークン>"
echo -n Please input UserName:
ustatus=`__uname`
if [ "$ustatus" = "t" ]; then
    :
else
    echo False.
    exit
fi
ac_code=`openssl rand -base64 12 | fold -w 4 | head -1`
curl -X POST -H "Authorization: Bearer ${line_token}" -F "message=${ac_code}" https://notify-api.line.me/api/notify > /dev/null 2>&1
echo -n Please input AccessCode:
stty -echo
read ans
stty echo
if [ "$ans" = "$ac_code" ]; then
    echo Complete.Welcome to Raspberry Pi.
    curl -X POST -H "Authorization: Bearer ${line_token}" -F "message=正常ログインしました。" https://notify-api.line.me/api/notify > /dev/null 2>&1
    curl -X POST -H "Authorization: Bearer ${line_token}" -F "message=(${SSH_CONNECTION})${uname}接続情報" https://notify-api.line.me/api/notify > /dev/null 2>&1
else
    echo False.
    curl -X POST -H "Authorization: Bearer ${line_token}" -F "message=アクセスコードが一致しません。" https://notify-api.line.me/api/notify
    curl -X POST -H "Authorization: Bearer ${line_token}" -F "message=(${SSH_CONNECTION})${uname}:拒否元接続情報" https://notify-api.line.me/api/notify > /dev/null 2>&1
    exit
fi
ac_code="cleared."
ans="cleared."
line_token="cleared."
}
trap 'exit' SIGINT
main $@
trap -- SIGINT
（以下略）
```

2019/01/30追記

```bash:プロファイルスキップ
$ ssh -t user@host bash --noprofile
```
プロファイルのスキップは簡単である事をご指摘いただいた。
上記コマンドで簡単にプロファイルをスキップすることができるようだ。[^11]
現在PAMを使って再構築中。

2019/02/17追記

pam_execを使ってみた。

PAMとは？
IBM developerWorksによると、[^12]  

***

>* 認証 (auth) モジュールは、ユーザーの認証や、クレデンシャルの設定と破棄に使われます。  
>* アカウント管理 (account) モジュールは、アクセスや、アカウントとクレデンシャルの有効期限切れ、パスワードの制限やルールなどに関連するアクションを実行します。  
>* セッション管理 (session) モジュールは、セッションの初期化と終了のために使われます。  
>* パスワード管理 (password) モジュールは、パスワードの変更と更新に関連するアクションを実行します。  

***

上記のことができる。

同じく、モジュールには以下のようなものがある。[^12]

***
>* pam_access は /etc/security/access.conf の中に事前定義されたルールに基づいて、ログイン名とドメイン名を使ってログ・デーモン・スタイルのログイン・アクセス制御を行います。
* pam_cracklib はパスワード・ルールに基づいてパスワードのチェックを行います。
* pam_env は /etc/security/pam_env_conf の環境変数の設定 (set) と設定解除 (unset) を行います。
* pam_debug は PAM をデバッグします。
* pam_deny は PAM モジュールをロックアウトします。
* pam_echo はメッセージを出力します。
* pam_exec は外部コマンドを実行します。
* pam_ftp は匿名アクセスのためのモジュールです。
* pam_localuser を利用するためにはユーザーが /etc/passwd のリストに載っている必要があります。
* pam_unix は /etc/passwd による従来のパスワード認証を行います。
* その他にも、pam_userdb、pam_warn、pam_xauth など多くのモジュールがあります。

***

pam_execを使って、ログイン完了後に通知などを出すことができることが分かった。
ただし、これだけでは物足りなかった。

この情報を頼りに色々と調べていった。
次にSSHにはChallengeResponseAuthenticationというものがあるという事を知った。[^13]
SSH ChallengeResponseAuthenticationにはワンタイムパスワードの利用も想定されているとの事であった。[^14]
だから、これを使って二段階認証をLINENotifyを用いてできないか模索を始めた。

ChallengeResponseAuthenticationとGoogle Authenticator[^15]を組み合わせて使っている事例を多く発見した。

今回はGoogle Authenticatorを使う事なく、ChallengeResponseAuthenticationとLINENotifyまた、PAMなどを用いてLINEグループへワンタイムパスワードを送信して、二段階認証を実現したい。
というのも、Google Authenticatorを使うにはアプリケーションをインストールしてもらわなければならないので、今回のLINE Notifyを使って、ユーザが手軽に二段階認証できるという達成目標に反している。

ただ、Google Authenticatorを使わない方法はどこを探しても見つからず、まして、LINE Notifyを使った事例はだいぶ探したのだが見つけられなかったので、新規性という面ではこのお話も少しは参考になるかもしれない。

まず、PasswordAuthenticationとChallengeResponseAuthenticationの違いについて見ていかなければならない。
結論を言えば[^16]

* PasswordAuthentication: RFC 4252 で定義されている “Password Authentication Method (password)” を有効にする。

* ChallengeResponseAuthentication: RFC 4256 で定義されている “Generic Message Exchange Authentication (keyboard-interactive)” 認証を有効にする。

ということだが、もう少し詳しく見れば[^16]

* Password Authentication Method (password) では、クライアントがユーザ名とパスワードをサーバに送り、サーバは成功したか失敗したかを返す。

* Generic Method Exchange Authentication (keyboard-interactive) では、まずクライアントがサーバにユーザ名を送る(パスワードは送らない)。それに対してサーバがクライアントに instruction や prompt 等を送り、クライアントが応答する。認証に必要な回数だけ、サーバはクライアントとやり取りすることが出来る。

との事である。

つまり簡単にまとめてみると、
## Password Authentication Method
クライアント（ユーザ）がユーザ名、パスワードをセットでサーバ送信。
↓
その値によって判断する。

***

## Generic Method Exchange Authentication
クライアント（ユーザ）がサーバにユーザ名送信。
↓ ←----------------------------------　　　　　　　　　　　　　　　　　　　　　
サーバがクライアント（ユーザ）にその他の認証要素を必要な回数だけLISTEN。
↓  ----------------------------------↑
複数の認証要素によって判断する。

*** 

という事である。

ちなみにこれを有効にするのは簡単で、sshd_configの設定を書き換えるだけで良い。
今回は公開鍵認証とチャレンジレスポンス認証を使いたい。

```sh:sshd_config

PubkeyAuthentication yes

#~Omission~

MaxSessions 2

#~Omission~

ChallengeResponseAuthentication yes
PasswordAuthentication no

#~Omission~

UsePAM yes

#~Omission~

AuthenticationMethods publickey,keyboard-interactive #Last line

```

```bash:terminal

$ sudo service sshd restart
```

>## UsePAMとの関係[^15]
>UsePAM が yes になっている時、PasswordAuthentication も ChallengeResponseAuthentication も PAMを使って認証します。

>UsePAM が no の時、PasswordAuthentication は /etc/shadow を読んで認証するはずですが、

>error: Could not get shadow information for fugahoge

>と言って認証に失敗してますね…

>ちなみに、ChallengeResponseAuthentication は PAM を使って認証するという前提の実装になっているので、UsePAM が no だとこれまた認証に失敗します。

>UsePAM を no にする必要はないと思いますが、念の為。

***

上記から、UsePAMもyesに設定する。

次にワンタイムパスワードの発行ですが、私は暗号のエキスパートではないので、ライブラリを用いたい。
OATH規格があり、Debian系のOSにはlibpam-oathというのがあるのでそれを利用する。[^13][^17]

```bash:terminal
$ sudo apt install libpam-oath oathtool
```

ここでPAMについておさらいだが、Linuxで認証、認可時 (*.so) を再利用し、認証 (auth)、アカウント管理 (account)、セッション管理 (session)、パスワード管理(password)ができる。[^12][^13]

上記から、authの設定を/etc/pam.d/sshdに追加する。

```bash:/etc/pam.d/sshd
# PAM configuration for the Secure Shell service

# Standard Un*x authentication.
@include common-auth

auth required pam_oath.so usersfile=/etc/users.oath window=30 # Add this line!

# Disallow non-root logins when /etc/nologin exists.
account    required     pam_nologin.so
```

また、OATH用にユーザファイルに情報を加えなければならないので、/etc/users.oathとしている。

上述しているが、opensslを使った[^4][^8]
ここでは30桁にしたかったので、以下のようにパイプ処理した。

```sh:openssl(yourcode)
$ openssl rand -base64 100 | md5sum | fold -w 30 | head -1
<yourcode>
```

```bash:name
$ hostname
<name>
```

/etc/users.oath に記述

パーミッションも設定する事。

```bash:terminal
$ sudo chmod go-rw /etc/users.oath
```

```bash:/etc/users.oath
HOTP/T30/6  <name>    -   <yourcode>
```
TOTPは時間ベース認証。
ツールで時間ベース認証の種を生成。

```bash:oathtool
$ oathtool --totp -v <yourcode>
Hex secret: ~Omission~
Base32 secret: ~Omission~
```
Base32 secretをgoogle-authenticatorでは「キー」として使う。[^13][^14]
今回はgoogle-authenticatorを使わないので、以下のように直接生成する。

```bash:terminal
$ oathtool --totp <yourcode>
```

この段階で、

```bash:terminal
$ sudo service ssh restart
```

新規にプロンプトを立ち上げて、でログインをしてみると、以下のようになる。

<img width="278" alt=" 2.png" src="https://qiita-image-store.s3.amazonaws.com/0/110060/44df289a-5679-e5d5-713a-d613bfa5375d.png">

ここで気をつけなければならないのが、このままログアウトしてしまうと、時間ベース認証で、送信する部分を作っていないので、ログインできなくなってしまう。
私は、ログインできなくなってしまい、再度、仮想環境を立ち上げなければならなくなってしまった。
上図はexitを打ってしまった様子だ。
くれぐれも本番環境で試さず、仮想環境で作り込んでからのデプロイをオススメする。

次に、送信部分だが、今回、SSHログイン前にサーバ側がアクセスを検知してワンタイムパスワードを飛ばさねばならない。
これに関しては、ほとんど情報がなくて苦労した。
SSHログイン後に通知するのであれば、前述のprofileを使ったり、sshrcを使ったり、pam_execを用いれば良いのだが、これでは、ワンタイムパスワードを送る前に、認証を突破せねばならないので、無理であった。

いくつか、実験をして、swatchを使ったり、ループで送りっぱなしにしたり、数多くの失敗を繰り返したがここでは割愛する。

最終的には、SSHアクセスがあったかどうか、SSHのポート（だいたい22番ポートであろうか）を監視する常駐型プログラムを作らねばならなかった。

試行錯誤していたが、結局まとめてShellScriptにすることができた。

```bash:LINENotifyOATH.sh
#!/bin/sh
#<>は必要ない
ST=0
while :
do
var=`date "+%S"`
sshnode=`lsof -i:22 | grep ssh | awk '{print $9}' | wc -l`
if [ $var -gt 30 ]; then
    rt=`expr 60 - $var`
else
    rt=`expr 30 - $var`
fi
code=`oathtool --totp <yourcode>`
if [ $sshnode = 0 ]; then
    echo none.
    ST=0
elif [ $sshnode = 1 ] && [ $ST = 0 ]; then
    echo ok1.
    TOKEN=<LINE Notify access token.>
    MSG="node1:${code}(残り:${rt}秒)"
    curl -X POST -H "Authorization: Bearer ${TOKEN}" -F "message=${MSG}" https://notify-api.line.me/api/notify
    ST=1
elif [ $sshnode = 2 ] && [ $ST = 1 ]; then
    echo ok2.
    TOKEN=<LINE Notify access token.>
    MSG="node2:${code}(残り:${rt}秒)"
    curl -X POST -H "Authorization: Bearer ${TOKEN}" -F "message=${MSG}" https://notify-api.line.me/api/notify
    ST=0
else
    :
fi
done
```
上記を用いてサーバに常駐させれば良い。


普通にデーモン化するなら以下の要領で、

```bash:デーモン化
$ sudo mv LINENotifyOATH.sh /etc/init.d 
$ sudo chmod 755 /etc/init.d/LINENotifyOATH.sh
$ cd /etc/init.d
$ ln ../init.d/LINENotifyOATH.sh S10LINENotifyOATH.sh
```
以上でOSを再起動させると LINENotifyOATH.sh がデーモンとして動いているはずである。

ちなみに、本体を解説すると、

```bash
$ sshnode=`lsof -i:<port for example 22> | grep ssh | awk '{print $9}' | wc -l`
```
でSSHで接続要求している台数をみることができる。
この数値がゼロであれば要求なし、ということで、ワンタイムパスワードを送信しないようにできる。
また、上述しているが、SSHセッションを二つまでにしたいので二つ以上の要求があってもワンタイムパスワードを送信しないようにしている。
今回は同一のRaspbianのPiユーザを共有して使うので台数の情報しか必要ないが、書き方によっては、どのユーザに接続要求しているのかということも検知することができる。


また、今回採用しているTOTPの場合、カウンタがUnixtimeであるが、しかし、これをそのまま使うとワンタイムパスワードが毎秒変化し、使い物にならないとのこと。
それで、ワンタイムパスワードが切り替わる間隔としてTime-Stepを設定し、UnixtimeをTime-Stepで割った値をカウンタとします。
messagetime = Unixtime / Time-Step
これにより、タイムステップが30なら、30秒間は同じワンタイムパスワードが生成されることになる。[^19]
>※ OATHの仕様ではタイムステップとして30秒が推奨されています。[^19]

ちょうど時刻がXX:XX:30、XX:XX:XX:00になると表示が切り替わるので、その点を考慮して、以下のように残り時間も送信することができる。

```bash:RT
var=`date "+%S"`
if [ $var -gt 30 ]; then
    rt=`expr 60 - $var`
else
    rt=`expr 30 - $var`
fi
```

あとは、ログイン後にpam_execやsshrcなどを使って上述のコードを実行させてあげれば良い。
やはり、普段から使っているLINEに通知がくると大変便利である。

<img width="665" alt=" 3.png" src="https://qiita-image-store.s3.amazonaws.com/0/110060/68edc5aa-98a3-8014-c678-5c9fdc3b2ee9.png">

<img width="565" alt=" 1.png" src="https://qiita-image-store.s3.amazonaws.com/0/110060/7d824ec2-ff2e-a6ac-4559-3d5e983b4215.png">


# GoogleAuthenticatorも併用可能
2019/02/18追記

LINEを使った二段階認証もできたが、GoogleAuthenticatorを使った二段階認証もどちらも使うことができるので、より多くのユーザに合わせることができる。

今までの作業に加えてあとやることは簡単で、先ほど生成した、oathtoolのBase32 secretのコードを使えばいいだけのことである。

```bash:$
$ oathtool --totp -v <yourcode>
Hex secret: ~Omission~
Base32 secret: ~Omission~
```
これで表示されたBase32 secretキーを手入力でGoogleAuthenticatorに読み込めば完了。
<img width="375" alt="IMG_4425.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/110060/1d6c0b44-820f-10fa-80e1-2ce1cc7ee86a.jpeg">

またQRコードを生成したいのであれば、以下の手順が必要[^22] [^20]
まず、以下のツールが必要なのでインストールする。

```bash:QRコードのツール
$ sudo apt install qrencode
```

TOTPのURIは以下のようになっていて、先ほどのBASE32キーを用いる。[^21]

```bash:TOTP-URI形式
otpauth://totp/<user>@<servicename>?secret=<secret_base32>
```

具体的には以下のようになる。

```bash:$
$qrencode -t ansi "otpauth://totp/pi@raspi?secret=<your base32 key>"
```

2019/03/06追記
LINE Notifyをcurlで使うと「Couldn't resolve host」エラーが出ていた。
このため、プログラムが中断されてしまっていた。

```bash:nohup.out
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     
0curl: (6) Could not resolve host: notify-api.line.me

```

/etc/resolv.confのDNS設定によって発生していた模様。
この場合は以下のようにすれば良いとのこと。[^18]

```bash:/etc/resolv.conf
nameserver 8.8.8.8
```

2019/03/13追記
上記のコード(LINENotifyOATH.sh)ではポートマッピングをしているサーバーからのssh接続要求しか見てくれないことが分かった。
ssh接続要求の検知には、

```bash:$
$ lsof | grep sshd | awk '{print $3}' | grep sshd | wc -l
```

でsshdの動きを見てあげる必要がある。
以下のように書き直した。

```bash:LINENotifyOATH.sh

#!/bin/sh
#<>は必要ない
ST=0
while :
do
var=`date "+%S"`
sshnode=`lsof | grep sshd | awk '{print $3}' | grep sshd | wc -l`
if [ $var -gt 30 ]; then
    rt=`expr 60 - $var`
else
    rt=`expr 30 - $var`
fi
code=`oathtool --totp <yourcode>`
if [ $sshnode = 0 ]; then
    echo none.
    ST=0
elif [ $sshnode = 4 ] && [ $ST = 0 ]; then
    echo ok1.
    TOKEN=<LINE Notify access token.>
    MSG="node1:${code}(残り:${rt}秒)"
    curl -X POST -H "Authorization: Bearer ${TOKEN}" -F "message=${MSG}" https://notify-api.line.me/api/notify
    ST=1
elif [ $sshnode = 8 ] && [ $ST = 1 ]; then
    echo ok2.
    TOKEN=<LINE Notify access token.>
    MSG="node2:${code}(残り:${rt}秒)"
    curl -X POST -H "Authorization: Bearer ${TOKEN}" -F "message=${MSG}" https://notify-api.line.me/api/notify
    ST=0
else
    :
fi
done

```

2020/05/24追記
常駐化に関して、最近はinit.dは古くて、Systemdが推奨されているようなので、これに書き換えることにした。

```
$ sudo vim /lib/systemd/system/noti.service
```

/lib/systemd/systemにファイルnoti.serviceを作成。

```noti.service
[Unit]
Description = noti

[Service]
ExecStart=/bin/bash /home/pi/noti.sh
Restart=always
Type=simple

[Install]
WantedBy=multi-user.target

```
また、任意の場所に実行ファイルを置く。→　/bin/bash /home/pi/noti.sh

```bash:noti.sh
#!/bin/sh
#<>は必要ない
while :
do
var=`date "+%S"`
sshnode=`lsof | grep sshd | awk '{print $3}' | grep sshd | wc -l`

if [ $var -gt 30 ]; then
    rt=`expr 60 - $var`
else
    rt=`expr 30 - $var`
fi

if [ $sshnode = 0 ]; then
    ST=0
    :
elif [ $sshnode = 43 ] && [ $ST = 0 ]; then
    code=`/usr/bin/oathtool --totp $(openssl rsautl -decrypt -inkey /home/pi/.ssh/id_rsa -in /home/pi/.ssh/code.rsa)`
    TOKEN="<your token>"
    MSG="${code}(残り:${rt}秒)"
    curl -X POST -H "Authorization: Bearer ${TOKEN}" -F "message=${MSG}" https://notify-api.line.me/api/notify
    ST=1
    :
else
    :
fi
done
```

あとはsystemdへ登録して完了。

```bash:$
$ sudo systemctl enable noti.service
$ sudo systemctl start noti.service
```
動かない場合は、以下のコマンドでステータスを見ることができる。

```bash:$
$ sudo systemctl status noti.service
```

また、

```sh:code
code=`/usr/bin/oathtool --totp <your code>`
```

このcodeに関して、平文codeをそのまま置いておくのはあまり良くないので、気休め程度だが ssh用のrsa秘密鍵と、opensslを使って改善しておく。[^23]

```bash:code
code=`/usr/bin/oathtool --totp $(openssl rsautl -decrypt -inkey /home/pi/.ssh/id_rsa -in /home/pi/.ssh/code.rsa)`
```


[^2]: 「シグナルと trap コマンド」 https://shellscript.sunone.me/signal_and_trap.html
[^3]: 「Ctrl+Cとkill -SIGINTの違いからLinuxプロセスグループを理解する」http://equj65.net/tech/linuxprocessgroup/
[^4]: 「opensslコマンドの使い方」　https://qiita.com/hana_shin/items/6d9de0847a06d8ee95cc
[^5]: 「LINE Notify API Document」https://notify-bot.line.me/doc/en/
[^6]: 「ファイルを一行ずつ処理する方法」http://sweng.web.fc2.com/ja/program/bash/read-each-line.html
[^7]: 「bashのwhileループ内の変数をループ外で使う」http://iwsttty.hatenablog.com/entry/2015/01/31/183525
[^8]: 「OpenSSL」https://wiki.gsi.de/foswiki/bin/view/Linux/OpenSSL
[^9]: 「.bashrc .bash_profile .profile の読み込み順」http://blog.jojo.jp/?eid=1013034
[^10]: 「シェルでパスワードとかの入力を非表示にする。」http://d.hatena.ne.jp/slayer845/20100210/1265734761
[^11]: 「Can I ssh into my account without invoking .profile?」https://askubuntu.com/questions/63741/can-i-ssh-into-my-account-without-invoking-profile
[^12]: 「PAM を理解し、構成する」 https://www.ibm.com/developerworks/jp/linux/library/l-pam/index.html
[^13]: 「小ネタ: 2段階認証でssh」 https://qiita.com/amedama/items/c0ae17a5bc33657e492d
[^14]: 「[Linux] SSHの認証でワンタイムパスワードを使う（導入編）」 http://muramasa64.fprog.org/diary/?date=20140912
[^15]: 「google-authenticator」 https://itunes.apple.com/jp/app/google-authenticator/id388497605?mt=8
[^16]: 「OpenSSH の ChallengeResponseAuthentication と PasswordAuthentication」 http://lovepeers.org/2014/06/14/openssh-challengeresponseauthentication/
[^17]: 「Two-factor time based (TOTP) SSH authentication with pam_oath and Google Authenticator」 http://spod.cx/blog/two-factor-ssh-auth-with-pam_oath-google-authenticator.shtml
[^18]: 『curlで「Couldn't resolve host」エラーが出た場合の対処』https://qiita.com/bz0/items/7d4bac34c6cdada59b94
[^19]: 「OATHによるワンタイムパスワードの仕様 - 3」https://yamatamemo.blogspot.com/2011/06/oath-3.html
[^20]: 「TOTPのURIを生成し、QRにエンコードとデコードしてから、ワンタイムパスワードを計算する」 https://blog.monophile.net/posts/20171111_passlib_totp.html
[^21]: 「google-authenticator Key Uri Format」 https://github.com/google/google-authenticator/wiki/Key-Uri-Format
[^22]: 「２段階認証を実装するための記事まとめ（PHP＋認証アプリ）」 https://blog.apar.jp/program/13282/
[^23]: 「シェルスクリプトの平文パスワードをセキュアにする方法」 http://auewe.hatenablog.com/entry/2014/04/14/213319


※本記事により、これらの情報を運用した結果について、筆者はいかなる責任も負いません。本番環境で試される前に必ずご自身で検証されることをお勧めします。