NginxでLuaを用いず認証画面を作成してみる

家のRaspberry PiにMotionとMuninを設定していますが、通常通り外部公開すると、
URLさえ知っていれば（最悪アタックされれば）誰でも見れてしまいます。
（一応ポートは変えてますが…）
Basic認証だと固定のID、パスワードとなってしまうため、TOTPを用いた認証画面を作成します。
## 目標
* サードパーティモジュール（ngx_lua）は使わない  
　 ~~なぜなら自分がよく分かってないから~~
* TOTPを使って、なるべくセキュリティを上げる
* 仮に1度バレても、後で見れなくなるようにする
* せっかくなので、それなりのログイン画面風にする
* Raspberry Piのメモリの少なさを意識する

## Nginxの設定
まず、Nginx側でクッキーがない場合、認証画面にリダイレクトするようにします。
以下のように設定すると「authid」という名前で「hogehogehogehogehogehoge2」という値が
クッキーに設定されていないと、index.htmlに遷移します。

``` hoge.conf
...
# motion設定 {{{
	location /motion {
		if ($cookie_authid != "hogehogehogehogehogehoge2") {
			rewrite ^(.*)$ https://${ドメイン}/index.html;
			break;
		}
		proxy_pass http://127.0.0.1:${motionのポート};
	}
# }}}
...
 
```

これでだけです。
## TOTPの設定
TOTPに関しては、ここで説明するよりも他の方がより詳しく説明してくれているので、割愛します。
自分がやったことだけ列挙しておきます。

### 1. Javaで認証コード作成
　せっかくRFCにコードもあったので、お得意のJavaでやってみました。
　GoogleのAPI（GoogleAuthenticator）を使うと簡単に生成してくれます。

``` Java
GoogleAuthenticator gAuth = new GoogleAuthenticator();
System.out.println(gAuth.createCredentials());

```

### 2. QRコード作成
　Google認証システムアプリであれば、生データを登録することも可能ですが、
　せっかくなのでQRコードを作成して読み込ませました。
#### 　URLの作成
　　otpauth://totp/\${userName}?secret=\${上記で生成したコード}&issuer=\${発行者名}
　　これを元に、GoogleにQRコードを作成してくれるURLに変換します。
　　https://chart.googleapis.com/chart?\${URLエンコードした上記URL}
#### 　QRコード読み込み
　　上記のURLで表示されたQRコードを、バーコードリーダーなどで読み込みます。
　　自分のアプリがGoogleと並んで一覧に出てくるのはなかなか…
## 認証用API作成
最近Spark（Apacheじゃない方）がお気に入りなので、そちらで作成しました。
Sparkはtomcatを使わなくてもWebサーバーが立ち上げられるので（Playもそうですね）
パワーのないRaspberry Piにはよいんじゃないかと勝手に思ってます。
なお、 **クッキーに設定する値は別ファイル**に設定します。
（理由は後ほど出てきます。）
APIではクッキーの値をファイルから読み込み、認証が通ればクッキーを発行し、遷移するURLを返します。
「error」に認証真偽を、「comment」にエラー内容（または遷移するURL）を設定しJSONで返します。

## 認証画面作成
### html
認証画面はこんな感じのフツーのhtmlです。
![認証画面](https://qiita-image-store.s3.amazonaws.com/0/90107/e6d42737-b6d0-7df1-c858-b9764fd3a8c2.png "認証画面")
ちなみにこれは「ログイン画面 テンプレート」でググって拾ってきたhtmlです。。。
ユーザーIDも設定して、気持ちセキュリティを上げた気にしておきます。
### jQuery
APIは上記のようにRaspberry Pi上に作ったので、jQueryでajax通信します。

``` api-ajax.js
$(function(){
    $("#submit").click(function() {
        $("#inputError").slideUp();
        $("#submit").prop("disabled", true);

        var user = $("#user").val();
        var id = $("#id").val();
        var message = null;

        $.ajax({
            url: "https://${Raspberry PiのURL}/auth",
            type:'POST',
            dataType: 'json',
            data : {"user" : user, "id" : id}
        }).done(function(data) {
            if (data.error) {
                $("#inputError").text(data.comment);
                $("#inputError").slideDown();
            } else {
                window.location.href = data.comment;
            }
            $("#submit").prop("disabled", false);
        }).fail(function(data) {
            $("#inputError").text("サーバー通信でエラーが発生(´･ω･`)");
            $("#inputError").slideDown();
            $("#submit").prop("disabled", false);
        });

    });
});
```
特に変わったことはしてない（ハズ）です。
slideDown()は2つも書かないでcompleteに出せばよかったかな？
## クッキーの値を定期的に変えるcronを作成
クッキーに設定する値を別ファイルした理由です。
MotionやMuninは定期的にリロードされているため、1回で見れる時間を限定します。

まずは書き換えるためのシェル

``` cookie_change.sh
#!/bin/bash

# 現在の値を取得
nowValue=`cat /home/pi/cookie`
# 新しい値を生成
newValue=`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 25 | head -n 1 | sort | uniq`

# ファイルの書き換え（www.confとcookieの2つ）
sed -i -e "s/$nowValue/$newValue/g" /etc/nginx/conf.d/hoge.conf
sed -i -e "s/$nowValue/$newValue/g" /home/pi/cookie

# 設定反映のため、nginxを再起動
/etc/init.d/nginx restart
```

「Nginx再起動させんの？！」という声も聞こえてきそうなシェルですが、まぁ自宅の自分用なので…

これを10分おきにcronで回します。

``` crontab(su)
*/10 * * * * /home/pi/cookie_change.sh
```

「sudo crontab -e」で登録しないとhoge.confが書き換わりません。

# これで完成！
いや〜、ガンバッタ(笑)
## 他にも
我が家のRaspberry Piは、Slackの送信内容に応じてエアコンを操作してくれます。
（これはQiitaに投稿されている内容を丸パクリしました。。。）
あと、Slackが無料版につき10,000メッセージまでらしいので、Outgoing Webhooksを使って
GoogleSpreadSheetに保存してくれたりしています。
（メッセージの更新は通知されないので、間違えて送ったら間違えっぱなしですが…）
こちらもJava(Spark)ではありますが、いつか記事にしたいと思います。
## 最後に…
ツッコミどころも多々ありそうですが、自分なりによくできたと思ったので投稿してみました。
今回初めて記事をちゃんと書いてみましたが、なかなか大変ですね。。。
いつも参考にさせて頂いてる方々にあらためて感謝です！