ラズパイwebサーバのHTTPS化 (Nginx, Let's encrypt, Fail2ban)

# はじめに
Rasbperry piを個人的なアプリのwebサーバのテストとして使っていましたが，セキュリティの面からHTTPSにしたいと思っていました．
グローバルIP直接たたいて運用していたので，ドメイン取得も含めてHTTPS化までのログを載せておきます．
ラズパイとは書いていますがラズパイ要素ないので悪しからず．

## こういうことします

- ドメイン取得，ネーム設定
- Let's Encrypt(Certbot)でSSL証明書導入 → NginxでHTTPS
- Fail2banで不正アクセス対処 (とりあえずBasic認証だけ)

## 環境

- Raspberry Pi 3
- Raspbian 8.0 (jessie)
- サーバはルータのプライベートネットワーク内で固定IPアドレス
- 必要なポートだけルータでポート変換 (今回は80,443)

# 手順
## 1. ドメイン取得・DNS設定
今回 "お名前.com" でドメイン取得しました → https://www.onamae.com
ちなみに費用は「取得料金+年ごとの更新費用」なのですが，安いと銘打っているのは取得料金なので，見た目的にも ".net" ".com" でいいんじゃないでしょうか..

DNSの設定はログイン後，[DNS]→[ドメインのDNS関連機能設定]から．対象ドメインを選び，[DNSレコードの設定] ページへ．

## 2. Let's Encrypt
オレオレ証明書を使えば無料でSSL化できますが，Chromeなどでは「安全ではありません」と出るデメリットが．
ちゃんとした証明書は結構な値段するのですが，最近はLet's Encryptという無料で取得できる証明書サービスがあり，こちらを使ってみましょう

**2020/4月更新**
Let's Encryptから「ACMEv1はサポートしない」旨の通知があり，Certbot対応のため確認すると，Certbotのサイトも更新され，インストールもだいぶ手軽になっていました．
https://certbot.eff.org/

Certbotのサイトに従い，使うwebサーバとOSを選べばどういうコマンドでインストールするか表示されます．

(1) 前のバージョン削除

```sh
$ sudo apt-get remove certbot
```

(2) インストール

```sh
$ wget https://dl.eff.org/certbot-auto
$ sudo mv certbot-auto /usr/local/bin/certbot-auto
$ sudo chown root /usr/local/bin/certbot-auto
$ sudo chmod 0755 /usr/local/bin/certbot-auto
```

(3) 証明書取得

```sh
$ sudo /usr/local/bin/certbot-auto --nginx
```

(4) 証明書の自動更新
Let's Encryptでは無料で証明書を使える代わりに有効期限が3ヶ月となっています．
certbotではrenewコマンドがあり，証明書の有効期限が迫っている場合のみ自動的に更新してくれます．
また，`--post-hook`コマンドでrenew後の処理を追加できますが，nginxをreloadすることで証明書更新しています．
以下のコマンドをrootのcrontabに追加(sudo crontab -e)[^1]

[^1]: PATHが通っていないことで更新が実行されず，ハマりました．PATHを指定するか，フルパスで記述すれば問題なし．

```sh
0 2 * * 0 /usr/local/bin/certbot-auto renew --post-hook "systemctl reload nginx"
```

更新のログは`/var/log/letencrypt/letencrypt.log`として保存されます．

## 3. Fail2ban
HTTPで簡易セキュリティといえばBasic認証ですが，(そもそも暗号化されていないということは置いておいて)総当たり攻撃されるなど危険があります．
Fail2banは認証エラーなどのログから動的にIPフィルタ項目を追加してくれます．
(ちなみにiptablesにフィルタを登録しますが，Cent OSなどのようにサービスや設定ファイルがありません．それでも一応動いてはいます)

参考: http://kamilslab.com/2016/12/11/how-to-install-fail2ban-on-the-raspberry-pi/

(1) インストールします

```sh
$ sudo apt-get install fail2ban
$ sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
```
Fail2banでは設定ファイルjail.confを直接編集せず，jail.localを作成して編集することが推奨されています．

(2) nginxのBasic認証の監視を有効化します
参考: https://blog.jicoman.info/2014/03/fail2ban_nginx-http-auth/

```sh:/etc/fail2ban/jail.local
[nginx-http-auth]
# trueに変更する
enabled = true
filter  = nginx-http-auth
# sendmailアクションは任意で
action  = iptables-multiport[name=nginx-http-auth,port="80,443"]
          sendmail[name=nginx-http-auth, dest=xxx@xxx sender=xxx@yyy]
# nginxのエラーログすべてを対象にする
logpath = /var/log/nginx/*error*.log
bantime = 86400
maxretry = 5
```

bantimeで締め出す時間を，maxretryで何回エラーしたらbanするかを設定．

どういう内容のエラーならbanするかは`filter.d/nginx-http-auth.conf`で変更します．今回は，一度テストしてみるとひっかからないパターンがあることに気づいたため，以下のように変更．

```sh:/etc/fail2ban/filter.d/nginx-http-auth.conf
# 旧
failregex = ^ \[error\] \d+#\d+: \*\d+ user "\S+":? (password mismatch|was not found in ".*"), client: <HOST>, server: \S+, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"\s*$
# 新: referrer部分があると旧ではヒットせず
failregex = ^ \[error\] \d+#\d+: \*\d+ user "\S+":? (password mismatch|was not found in ".*"), client: <HOST>, server: \S+, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(, referrer: "\S+")?\s*
```

設定したら再起動

```
$ sudo systemctl restart fail2ban
```

(3) 動作確認
bantimeを短くしてローカルからBasic認証でパスワードを何回か間違えてbanされるか試してみましょう．
検知されているかは以下のコマンドから

```
$ sudo fail2ban-client status
Status
|- Number of jail:    1
`- Jail list:        nginx-http-auth
$ sudo fail2ban-client status nginx-http-auth
Status for the jail: nginx-http-auth
|- filter
|  |- File list:    /var/log/nginx/error.log 
|  |- Currently failed:    0
|  `- Total failed:    3
`- action
   |- Currently banned:    1
   |  `- IP list:    xxx.xxx.xx.xx 
   `- Total banned:    1
```


## 後記
certbotのstandalone以外にwebrootもhttpチャレンジタイプなので，webrootを使えばwebサーバを停止させずにできるかもしれません．

あとは，Basic認証じゃなくログイン機構やCookie，セッション維持を試してみたいんですが.. 

## 履歴
2020/4/5 certbotのインストール 更新
2018/11/05 作成

### 過去ログ(certbotインストール部分)

参考: https://qiita.com/pollenjp/items/a6551dbbfdfb56991307

Raspbian 8でも基本はDebian 8同様の方法で行いました．
(1) Jessie backports リポジトリを有効化

```sh:/etc/apt/sources.list
deb http://ftp.debian.org/debian jessie-backports main
```
(2) certbotインストール

```
$ sudo apt-get install certbot python-certbot-nginx -t jessie-backports
```
(3) certbotセットアップ
certbotをnginxプラグインでそのままセットアップすると以下のようなエラーになりました．

```
$ sudo certbot --nginx
(略)
Performing the following challenges:
Client with the currently selected authenticator does not support any combination of challenges that will satisfy the CA.
```
調べるとnginxプラグインで使うTLS-SNI検証に問題があり2018年1月以降は無効化されているとのこと([参考](https://rms-digicert.ne.jp/digital-certificate-news/update-regarding-acme-tls-sni))．
今回はstandalone認証で行いました．その場合ポート80にバインドさせるため，以下のとおり既存のwebサーバは停止させる必要があります．

```
$ sudo systemctl stop nginx
$ sudo certbot --authenticator standalone --installer nginx
(略)
Which names would you like to activate HTTPS for?
 → HTTPS化させるドメインを選択．出てこない場合，先にnginxの方でドメインに対応させる必要がある？
(略)
Please choose whether HTTPS access is required or optional.
-------------------------------------------------------------------------------
1: Easy - Allow both HTTP and HTTPS access to these sites
2: Secure - Make all requests redirect to secure HTTPS access
-------------------------------------------------------------------------------
 → HTTP接続したら自動的にHTTPSにリダイレクトさせるか．今回は2で設定してみました
(略)
```
完了したらnginxの設定ファイルに以下の内容が追記されていることと思います

```sh:/etc/nginx/sites-available/default
listen 443 ssl; # managed by Certbot (以下略)
ssl_certificate /etc/letsencrypt/live/(ドメイン名)/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/(ドメイン名)/privkey.pem;
ssl_session_cache shared:le_nginx_SSL:1m;
ssl_session_timeout 1440m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "... (色々書いてある)"

if ($scheme != "https") {
     return 301 https://$host$request_uri;
}

```
問題なさそうならnginxを起動．
私はここでポートが他に使われているとエラーがでました．

```sh
$ sudo systemctl start nginx
nginx: [emerg] still could not bind()
$ netstat -ant
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN
```

80ポートを使っているプロセスを調べて停止させたら起動できました

```sh
$ sudo lsif -i:80
COMMAND   PID     USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
nginx   xxxxx     root   10u  IPv4 50143522      0t0  TCP *:http (LISTEN)
$ sudo kill xxxxx ←PID
```