Closed16

Nginxでクライアント認証したい

tmsc4zhttmsc4zht

環境

uname -a
# Linux tmsc4zht-dev 5.4.0-1042-gcp #45-Ubuntu SMP Tue Apr 13 01:44:53 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
nginx -v
# nginx version: nginx/1.18.0 (Ubuntu)

Nginxでクライアント認証したい。
サーバー側のCAはLet's Encryptで作った鍵じゃだめなのかな。
まずはよく紹介されている方法で動くか確認しよう。
PKI基盤から作らなきゃいけないのか。


PKIはPublic Key Infrastructureの略だからPKI基盤って言うと意味が被る…

tmsc4zhttmsc4zht

デフォルトの?CA設定は /usr/lib/ssl にある。
これとは別にPKI用のディレクトリを作成したほうが良さそう。
とりあえず /etc/opt/pki を作成する。

後のコマンドは以下を参考した。
ただしクライアント証明書は /etc/opt/pki/client に作成した。

https://qiita.com/bashaway/items/ac5ece9618a613f37ce5

tmsc4zhttmsc4zht

ブラウザからSSH接続してpfxをダウンロードしようとしたができない。原因はわからない。
GCPのCloud Storage経由でやることにする。

今は以下の条件が無料枠

  • 5 GB-月の Standard Storage
  • 5,000 回のクラス A オペレーション(1 か月あたり)
  • 50,000 回のクラス B オペレーション(1 か月あたり)
  • 1 GB の北米から全リージョン宛ての下り(外向き)ネットワーク(1 か月あたり、中国とオーストラリアを除く)
  • 無料枠を利用できるのは、us-east1、us-west1、us-central1 のみです。使用量の計算は、これらのリージョンすべての合計が集計されます。

computeがus-westにあるのでそれに合わせてバケットを作った。

次のコマンドでクライアント証明書をバケットに置く。
gsutil cp ./user_crt.pfx gs://{backet_name}

403エラーが出た。権限が足りてないので追加。

https://zenn.dev/noriko3/articles/e7a234788b4314

だめだった。なんで?
→キャッシュのせいでした。多分。

tmsc4zhttmsc4zht

gcloud auth list で表示されるGCEにのアカウントをIAM管理から確認して、GCSオブジェクト作成のロールをつけたらアップロードできるようになった…
正しいやり方か不明…

Cloud API アクセス スコープではR/W許可を無効にしても動いた。
まあもとのサービスアカウントで許可しているから当たり前か。
これがうまく設定できていないようだ

ともかくクライアント認証を手元に持ってくることはできた。

→キャッシュのせいでした。多分。
この設定は不要になったので戻しました。

tmsc4zhttmsc4zht

nginxをclient証明を要求する設定にする。

server {
        listen 443 ssl default_server;
       # 略
        ssl_verify_client on;
        ssl_client_certificate /etc/opt/pki/RootCA/RootCA_crt.pem;
}

クライアント証明書がチェーン形式じゃなかったので、こちらが証明書を提供できず。
400エラーになるようだ。

チェーン形式でPKCS#12形式のファイルに纏め直す。

openssl pkcs12 -export -in user_crt.pem -inkey user_key.pem -certfile ../RootCA/RootCA_crt.pem -out user_crt.pfx

作り直して、またCloud Storageにアップロードしようとしたらまた403エラー
どういうこと。

tmsc4zhttmsc4zht

https://stackoverflow.com/questions/27275063/gsutil-copy-returning-accessdeniedexception-403-insufficient-permission-from

/ .gsutil に認証情報のキャッシュが残ってるとうまく動かないことがあるらしい。
もういちどnoriko3さんの設定をしてから該当のフォルダを削除したらアップロードできるようになった。

つまり

  1. noriko3さんの設定で動くようにはなるはずだった
  2. 認証情報のキャッシュが働き接続できない状態が維持されていた
  3. IAMで設定してる間に前のキャッシュが消え、新たに認証情報を作り直したためアップロードできるようになった
  4. IAMのせいだと思い1の設定を戻した
  5. しばらくはキャッシュで動いたが、そのうち消えて動かなくなった
  6. もう一度1の設定をして、キャッシュを消したので新たに認証情報を作り直しアップロードできるようになった

ということ?
IAMの設定を消して、キャッシュを消してアップロードを試す。
動いた。

そんな…

tmsc4zhttmsc4zht

Let's Encryptで取得した証明書と秘密鍵で署名してみる。

cd /etc/opt/pki/client
openssl genrsa -des3 -out user.key 4096
openssl req -new -key user.key -out user.csr
openssl x509 -req -days 365 -in user.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out user.crt

クライアント証明書選択ダイアログが出ない。

よく考えたら、nginxのCAの設定も書き換えなきゃいけないのかもしれない。
この署名チェーンのルート証明書をダウンロードしてssl_client_certificateのパスに設定。

クライアント証明書選択ダイアログが出ない。

このサイトにはssl_verify_depathも必要との記載がある。

https://www.nslabs.jp/pki-client-certification-with-nginx.rhtml

ssl_verify_depth 4;

クライアント証明書選択ダイアログが出ない。

どういう条件でクライアント証明書選択ダイアログが出るのかよくわからない。
→わかった(次)


原理的はできるようだが。
そもそもプライベートCA使ったほうがいいよとの記載があり、ガイドのリンクもある。

https://community.letsencrypt.org/t/can-i-create-client-certificates-for-a-received-letsencrypt-certificate/78627

It’s much more better to generate a private CA (and intermediate CA as well as client certificate) to use with authentication.
For now, the best (and complete) guide to this is
https://jamielinux.com/docs/openssl-certificate-authority/

正統?にこちらの選択をとる。

tmsc4zhttmsc4zht

https://jamielinux.com/docs/openssl-certificate-authority/introduction.html

こちらに記載の通りにすすめる。

変更点が少しある。
以下の値をoptionalにした。
これらは別になくていい。

[policy_strict]
stateOrProvinceName     = optional
organizationName        = optional

参考
https://l-w-i.net/t/openssl/conf_001.txt

このセクションではCAが発行する証明書のポリシーを記述する。
CAは依頼者からCSR(証明書要求)を受け取って証明書を発行するわけだが、全てのCSRに対して証明書を発行するべきではない。
全く知らない人から来たCSRを本人の身元を確認もせずに発行された証明書は信用度があまりないからである。
そこで、CAではどのようなCSR(証明書要求)に対して証明書を発行するかを決めた「ポリシー」を設定する。
CSRのSubjectの部分がどのような場合に証明書を発行するかについて以下のようにポリシーを設定できる。

  • match
    • CAの内容と一致しなければならない
  • optional
    • 無くてもよい
  • supplied
    • なければならない

基本的にはコマンドどおりに行えばOK。
Sign server and client certificatesではコマンド記述がサーバー用のものになっているのでクライアント用として少し書き換える必要がある。

ここはextensionsusr_certにするとよい。

openssl ca -config intermediate/openssl.cnf \
      -extensions server_cert -days 375 -notext -md sha256 \
      -in intermediate/csr/www.example.com.csr.pem \
      -out intermediate/certs/www.example.com.cert.pem

ファイル名やCNもサーバー用になっているので適当に変える。
ここではCNは(サーバー名) client、ファイル名はclientとした。

こうなる。

openssl ca -config intermediate/openssl.cnf \
      -extensions usr_cert -days 375 -notext -md sha256 \
      -in intermediate/csr/client.csr.pem \
      -out intermediate/certs/client.cert.pem

PKCS#12でクライアント証明書・鍵ペアを作成

cd /etc/opt/pki/ca/intermediate/certs
openssl pkcs12 -export -in client.cert.pem -inkey ../private/client.key.pem -certfile ca-chain.cert.pem -out ../private/client.cert.pfx
tmsc4zhttmsc4zht

https://www.nslabs.jp/pki-client-certification-with-nginx.rhtml

よく読み直したら以下の記述があった。

ssl_verify_client on の場合, ssl_client_certificate ディレクティブが必須。ここで指定されたファイルの証明書 (複数可) がクライアントに送られ、クライアント側 (Webブラウザ) で, クライアント証明書の候補を表示する。

なるほど。そういうことか…

ssl_client_certificatessl_trusted_certificateを混同していた。

つまり今の状態ではこう設定する。

ssl_client_certificate /etc/opt/pki/ca/intermediate/certs/intermediate.cert.pem;
ssl_trusted_certificate /etc/opt/pki/ca/certs/ca.cert.pem;

動いた!

ちなみにssl_trusted_certificateをコメントアウトするとまた400エラーになった。
やはり正統な証明書として認められないようだ。

どの発行機関の証明書を要求するかと、送られてきた証明書を信用するかどうかは別の話だよな。そりゃそうだ。

tmsc4zhttmsc4zht

技術的には可能と言われているところをできれば、考え方があっていたということになると思う。
Let's Encryptで取得した証明書によってクライアント証明書を署名しよう。

まず中間証明書用のディレクトリとかをつくる。

mkdir /etc/opt/pki/{ドメイン名}
cd /etc/opt/pki/{ドメイン名}
mkdir certs crl csr newcerts private
chmod 700 private
touch index.txt
echo 1000 > serial

Let's Encryptで証明書を取得したときに作った鍵とその証明書のリンクを作る・

ln -s (既存の鍵) private/intermediate.key.pem
ls -s (既存の証明書) certs/intermediate.crt.pem
# ls -s (既存の証明書チェーン)   certs/ca-chain.cert.pem

中間証明書用のopenssl.cnfをつくる。
https://jamielinux.com/docs/openssl-certificate-authority/appendix/intermediate-configuration-file.html

vi openssl.cnf

クライアント用の秘密鍵を作る。

openssl genrsa -aes256 \
      -out private/client.key.pem 2048
chmod 400 private/client.key.pem

署名要求をつくる。

 openssl req -config openssl.cnf \
      -key private/client.key.pem \
      -new -sha256 -out csr/client.csr.pem

署名する。とりあえず期限は60日にする。(Let's Encryptで署名した証明書の期限が90日なのでそれより短いものにした)

openssl ca -config openssl.cnf \
      -extensions usr_cert -days 60 -notext -md sha256 \
      -in csr/client.csr.pem \
      -out certs/client.cert.pem

ちゃんと出来てるか確認。

openssl x509 -noout -text \
      -in certs/client.cert.pem

PKCS#12に変換する。

cd private
openssl pkcs12 -export -in ../certs/client.cert.pem -inkey client.key.pem -certfile ../certs/intermediate.crt.pem -out client.cert.pfx

クライアント証明書をインストールしたらnginxの設定を変える。

ssl_client_certificate /etc/opt/pki/{ドメイン名}/certs/intermediate.cert.pem;
ssl_trusted_certificate /etc/opt/pki/{ドメイン名}/certs/intermediate.cert.pem;
 ssl_verify_depth 10;

とりあえずクライアント証明書の選択画面と選択はできるようになった。

が認証が通らないぞ。

tmsc4zhttmsc4zht

https://docs.nginx.com/nginx/admin-guide/monitoring/debugging/

デバッグログを出力する。(最初からしておけば…)

To enable writing the debugging log to a file:
Make sure your NGINX is configured with the --with-debug configuration option. Run the command and check if the output contains the --with-debug line:

$ nginx -V 2>&1 | grep -- '--with-debug'

Open NGINX configuration file:

$ sudo vi /etc/nginx/nginx.conf

Find the error_log directive which is by default located in the main context, and change the logging level to debug. If necessary, change the path to the log file:

error_log  /var/log/nginx/error.log debug;

Save the configuration and exit the configuration file.

tmsc4zhttmsc4zht

設定を読み込み、アクセスし、エラーログを見るとこんな記載が。

 *9 client SSL certificate verify error: (21:unable to verify the first certificate) while reading client request headers, client: ***.***.***.***, server: {domain}, request: "GET /favicon.ico HTTP/1.1", host: {domain}, referrer: {domain}

どうやら最初の証明書のverifyができないとの記載。なら最初の署名をssl_certificateにルート証明書を指定すればいいのか?

エラー文面でググる。

https://thercb.org/331183-how-to-get-nginx-to-RCQKOO

スウェーデン語の情報があった。どうやらチェイン証明書にする必要がありそう。

ルート証明書を含むチェーン証明書にしたが同じエラーメッセージとなった。

tmsc4zhttmsc4zht
# openssl verify -CAfile intermediate.cert.pem certs/client.cert.pem
error 20 at 0 depth lookup:unable to get local issuer certificate

そもそも証明書として正しいか不安になってきた。

tmsc4zhttmsc4zht

https://superuser.com/questions/904859/why-cant-i-verify-this-certificate-chain

Also seems the in ca extension you need set basicConstraints = CA:true otherwise I still encounter openssl verify report error.

多分これだと思う

前に作成したもののオプションを変えて同じエラーが出ればそれが原因と言えそうだ。

https://qiita.com/3244/items/2a2a2dc6cd1e2e35beb9

Let's Encryptで得られた証明書はCA用ではないので、そのあたりでエラーが出てそうだ。

https://shammerism.hatenadiary.com/entry/20130802/p1

証明書チェーンの中でpathlenが0のCA証明書の次はサーバー証明書じゃないといけない。この設定は知っておいた方がよさそうだ。

Let's Encryptの pathlen は0だった。なるほどね...

つまりできないということだろう。

これで終わりとする。

このスクラップは2021/05/10にクローズされました