Ubuntu 20.04 + Apache 2.4 で「特定 IP は通す、それ以外はクライアント証明書認証」

# 概要

Ubuntu 20.04 + Apache 2.4 が導入されたサーバで「特定 IP は無条件で通す、それ以外は各ユーザにクライアント証明書を配布しそれを利用して認証」という運用が必要になり、諸所の文献を参考にさせて頂きながら実施した作業のウォークスルーを示します。

# 環境

```sh
# cat /etc/*release
PRETTY_NAME="Ubuntu 20.04.2 LTS"
# apache2 -v
Server version: Apache/2.4.53 (Ubuntu)
Server built:   2022-03-14T15:01:34
# openssl version
OpenSSL 1.1.1f  31 Mar 2020
```

# 証明書追加設定ファイル用意

ルート証明書・クライアント証明書（・サーバ証明書）で設定ファイルをまるごと別物として用意する例が多く見られますが、以下のような追加設定ファイルを用意して読み込ませる方法のほうがなんとなく好きで使っています。

```sh
# vim /etc/ssl/san.txt.ca # ファイル作成
subjectAltName = DNS:***.jp, IP:***.***.***.***
keyUsage = cRLSign, keyCertSign

# vim /etc/ssl/san.txt.client # ファイル作成
subjectAltName = DNS:***.jp, IP:***.***.***.***
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
```

# オレオレルート証明書生成

認証局用の秘密鍵を作る (genrsa)
→ 認証局へ送信する署名リクエストを生成 (req)
→ 自分で署名して証明書完成、認証局成立 (x509)

有効期限は十分長く 3650 日 `-days 3650` としています。

```sh
# openssl genrsa -out /etc/ssl/private/ca.key 2048
# openssl req -new -key /etc/ssl/private/ca.key -out /etc/ssl/ca.csr
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:***
Locality Name (eg, city) []:***
Organization Name (eg, company) [Internet Widgits Pty Ltd]:***
Organizational Unit Name (eg, section) []:***
Common Name (e.g. server FQDN or YOUR name) []:***
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

# openssl x509 -req -days 3650 -extfile /etc/ssl/san.txt.ca -in /etc/ssl/ca.csr -signkey /etc/ssl/private/ca.key -out /etc/ssl/ca.crt
Signature ok
subject=C = JP, ST = ***, L = ***, O = ***, OU = ***, CN = ***
Getting Private key
```

# クライアント証明書生成

クライアント証明書用の秘密鍵を作る (genrsa)
→ 認証局へ送信する署名リクエストを生成 (req)
→ オレオレ認証局に署名してもらって証明書完成 (ca)
→ ブラウザに読み込ませるための形式に変換 (pkcs12)

```sh
# openssl genrsa -out /etc/ssl/private/client.key 2048
# openssl req -new -key /etc/ssl/private/client.key -out /etc/ssl/client.csr
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:***
Locality Name (eg, city) []:***
Organization Name (eg, company) [Internet Widgits Pty Ltd]:***
Organizational Unit Name (eg, section) []:***
Common Name (e.g. server FQDN or YOUR name) []:***
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

 # openssl ca 実施時にこれらのファイル・ディレクトリがないとエラーになる
 # 証明書失効手続きに必要らしいが今回は一時的にファイルを用意するだけ
# mkdir -p ./demoCA/newcerts
# touch ./demoCA/index.txt
# echo 00 > ./demoCA/serial
# openssl ca -days 3650 -extfile /etc/ssl/san.txt.client -keyfile /etc/ssl/private/ca.key -cert /etc/ssl/ca.crt -in /etc/ssl/client.csr -out /etc/ssl/certs/client.crt
Using configuration from /usr/lib/ssl/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 0 (0x0)
        Validity
            Not Before: Apr  1 06:59:53 2022 GMT
            Not After : Mar 29 06:59:53 2032 GMT
        Subject:
            countryName               = JP
            stateOrProvinceName       = ***
            organizationName          = ***
            organizationalUnitName    = ***
            commonName                = ***
        X509v3 extensions:
            X509v3 Subject Alternative Name:
                DNS:***.jp, IP Address:***.***.***.***
            X509v3 Key Usage:
                Digital Signature, Non Repudiation, Key Encipherment
Certificate is to be certified until Mar 29 06:59:53 2032 GMT (3650 days)
Sign the certificate? [y/n]:y

1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

# openssl pkcs12 -export -in /etc/ssl/certs/client.crt -inkey /etc/ssl/private/client.key -out /etc/ssl/certs/client.p12 -name "***"
Enter Export Password: # ここの値がクライアント証明書インポート時のパスワードになる
Verifying - Enter Export Password:
```

完成した `/etc/ssl/certs/client.p12` を持ち出してユーザに配布します。

# Apache の設定

以下の設定を追記します。

```sh
# vim /etc/apache2/sites-available/default-ssl.conf
<LocationMatch ".*">
        ServerName ***.jp
        SSLCACertificateFile /etc/ssl/ca.crt # クライアント証明書が紐付けされたオレオレルート証明書
        SSLRenegBufferSize 10000000 # 413 Request Entity Too Large 対策
        SSLRequireSSL
        SSLVerifyClient optional
        SSLVerifyDepth 10
        <RequireAny>
                Require ip ***.***.***.***                     # 無条件で通す IP
                Require local                                  # 自分自身のアクセスも通す
                Require expr %{SSL_CLIENT_VERIFY} == 'SUCCESS' # それ以外はクライアント証明書認証成功で通す
        </RequireAny>
</LocationMatch>

# a2enmod rewrite
# a2enmod ssl
# a2ensite default-ssl
# systemctl restart apache2
```

指定した IP 以外からのアクセスでクライアント証明書を要求され、予めインポートした `/etc/ssl/certs/client.p12` を提示して認証に成功したら完了です。