クライアント証明書を発行しWebのメニューを制御する方法

Security

はじめに

このドキュメントでは、特定の物理的な場所(例:本社ビル5階の機密エリア)のPCにのみクライアント証明書を配布し、そのPCからアクセスしたユーザーにだけWebアプリケーション上で特別なメニューを表示する方法を解説します。

他のフロアのPCなど、証明書を持っていないユーザーも通常メニューは利用できる、というアクセス制御を実現します。

今回は、WindowsPCのChromeブラウザでNginxのWebサーバーにアクセスする例で考えます。Nginxはブラウザから渡されるクライアント証明書の有無と証明書に埋め込まれた識別情報(部署名や場所など)を判別し、その結果をDjangoのバックエンドアプリケーションに渡すことで、5階の機密エリアのPCだけは特別なWebアプリメニューの表示をできるようにします。

この記事の内容
  • OpenSSLによるクライト証明書の作成方法・PCへの登録方法を説明します。
  • Webサーバーのクライアント証明書処理方法・アプリケーション側の情報取得方法を説明します。

ステップ1: OpenSSLによる認証局(CA)と各種証明書の作成 (Windows編)

証明書の作成は Windows環境 で行います。コマンドは コマンドプロンプト (cmd.exe) での実行を想定しています。

0. 【準備】WindowsへのOpenSSLのインストール

Windowsには標準でOpenSSLがインストールされていません。まず、別途OpenSSLをインストールする必要があります。ここでは、Win64 OpenSSL v3.5.0 Light を使用することを想定します。

  1. OpenSSL for Windows の公式サイトなど、信頼できるソースから Win64 OpenSSL v3.5.0 Light のインストーラーをダウンロードします。
  2. インストーラーの指示に従い、OpenSSLをインストールします。筆者は全てデフォルトの設定でインストールしました。
  3. インストール時またはインストール後に、OpenSSLの実行ファイルがあるディレクトリ(例: C:\Program Files\OpenSSL-Win64\bin)に対して環境変数 Path を設定してください。これにより、コマンドプロンプトのどこからでも openssl コマンドが実行できるようになります。

1. CA (Certificate Authorities: 認証局) の作成

作業用のディレクトリを作成し、そこで作業を行います。

まず、CAの秘密鍵と自己署名証明書(ルート証明書)を作成します。 CAの秘密鍵は -aes256 オプションでパスワード保護するのが安全ですが、今回は手順を簡略化するため、このオプションを省略して実行します。

:: CAの秘密鍵を生成 (パスワード保護なし)
openssl genrsa -out ca.key 4096

:: CAの自己署名証明書を生成(20年間有効)
openssl req -new -x509 -days 7300 -key ca.key -out ca.crt -subj "/C=JP/ST=Tokyo/L=Shinjuku/O=My Company/CN=My Company Root CA"

openssl genrsa コマンド RSA形式の秘密鍵を生成するためのコマンドです。

コマンド引数の説明 (openssl genrsa)

引数説明
-aes256(今回は省略) 生成する秘密鍵をAES-256方式で暗号化します。これにより、鍵を使用する際にパスワードが求められ、セキュリティが向上します。
-out ca.key生成される秘密鍵の出力先ファイル名を指定します。
4096秘密鍵のビット長(鍵の強度)を4096ビットに設定します。

openssl req コマンド 証明書署名要求 (CSR: Certificate Signing Request) の作成と処理を行うためのコマンドです。-x509 オプションを付けることで、CSRを経由せずに自己署名証明書を直接生成することもできます。

コマンド引数の説明 (openssl req)

引数説明
-new新しい証明書署名要求 (CSR) または証明書を生成します。
-x509CSRではなく、自己署名の証明書を直接生成します。
-days 7300証明書の有効期間を7300日(20年)に設定しています。
注意:
独自で作る証明書はWindowsUpdateで最新版が追加されるものでもありませんので、筆者なら自分が退職まで持つ長さにします。プライベート証明書ですから。
-key ca.key署名に使用する秘密鍵ファイルを指定します。
-out ca.crt生成される証明書の出力先ファイル名を指定します。
-subj "..."証明書のサブジェクト情報を対話形式ではなく直接指定します。

サブジェクト情報の内訳: C: 国, ST: 都道府県, L: 市区町村, O: 組織名, CN: コモンネーム(共通名)

CN(コモンネーム)は、プライベートCAである事がわかる名前にしてください。

これで ca.key (CA秘密鍵) と ca.crt (CA証明書) が作成されます。ca.key は厳重に管理してください。

ca.crtファイルをクリックすると表示される
詳細タブの発行者とサブジェクト(件名)は同じ値になる

2. Webサーバー証明書の前提条件

この手順では、Webサーバー(Nginx)には既に適切なサーバー証明書が設定されていることを前提とします。

サーバー証明書は、クライアント証明書を利用する上で不可欠です。主な役割は以下の通りです。

  • 通信の暗号化: ブラウザとサーバー間の通信を暗号化(HTTPS化)し、盗聴や改ざんを防ぎます。
  • サーバーの身元保証: ブラウザが接続しようとしているサーバーが、なりすましではない本物のサーバーであることを保証します。

サーバー証明書がない場合、通信は暗号化されず、中間者攻撃などのセキュリティリスクに晒されます。クライアント証明書の認証も、この暗号化された安全な通信路の上で行われるため、サーバー証明書は必須となります。なお、サーバー証明書の準備方法は本記事では触れませんので、ご了承ください。

3. 5階の機密エリア用クライアント証明書の作成

アクセス元を識別するための情報をクライアント証明書に埋め込みます。今回は、OU (Organizational Unit Name) フィールドにエリア情報 Floor5-SecureArea を記述します。

:: 5階の機密エリア用クライアントの秘密鍵を生成
openssl genrsa -out client-floor5-secure.key 2048

:: 5階の機密エリア用クライアントの署名要求(CSR)を生成
openssl req -new -key client-floor5-secure.key -out client-floor5-secure.csr -subj "/C=JP/ST=Tokyo/O=My Company/OU=Floor5-SecureArea/CN=User Secure Area"

:: CAでクライアント証明書に署名
openssl x509 -req -in client-floor5-secure.csr -out client-floor5-secure.crt -days 7300 -CA ca.crt -CAkey ca.key -CAcreateserial

openssl x509 コマンド 証明書署名要求(CSR)に対して、指定したCAの鍵で署名し、新しい証明書を発行するためのコマンドです。

コマンド引数の説明 (openssl x509)

引数説明
-req入力ファイルがCSRであることを示します。
-in ...入力するCSRファイルを指定します (client-floor5-secure.csr)。
-out ...出力する証明書ファイルを指定します (client-floor5-secure.crt)。
-days 7300発行する証明書の有効期間を7300日に設定しています。
注意:
実際の運用では、セキュリティリスクを低減するため、より短い期間で証明書を定期的に更新するこを推奨します。とか良く言いますが、筆者は今回の社内向け用途なら長い有効期限にするべきと考えます。更新作業が最も高リスクなので、短い有効期間は総合的にセキュリティーを押し下げます。
-CA ca.crt署名に使用するCAの証明書ファイルを指定します。
-CAkey ca.key署名に使用するCAの秘密鍵ファイルを指定します。
-CAcreateserialシリアル番号ファイルを自動で作成・管理します。これにより、発行する証明書に一意のシリアル番号が割り当てられます。

サブジェクト情報の内訳: OU は Organizational Unit Name の略で、部門名や部署名などを指定します。今回はここにエリア情報 Floor5-SecureArea を設定しています。

client-floor5.crtをクリックすると表示される
詳細タブ:発行者には署名したCAの情報が入る

4. クライアント証明書のパッケージ化 (.p12)

ブラウザにインポートしやすくするため、クライアント証明書と秘密鍵をPKCS#12形式(.p12)にまとめます。

:: 5階の機密エリア用
openssl pkcs12 -export -out client-floor5-secure.p12 -inkey client-floor5-secure.key -in client-floor5-secure.crt -certfile ca.crt

実行すると、Export Password の入力を求められます。ここで設定したパスワードは、後ほどクライアントPCでこの証明書をインポートする際に必要になります。何も入力せずにEnterキーを押下し続けると、パスワード無しでファイルが作成されます。

openssl pkcs12 コマンド

コマンド引数の説明 (openssl pkcs12)

引数説明
-exportPKCS#12ファイルを作成するモードを指定します。
-out ...出力する.p12ファイル名を指定します。
-inkey ...パッケージに含める秘密鍵ファイルを指定します。
-in ...パッケージに含める証明書ファイルを指定します。
-certfile ...証明書チェーンを構成するCA証明書(ルート証明書)ファイルを指定します。

ステップ2: Nginxの設定

Nginxがクライアント証明書を検証し、その情報をバックエンドに渡すように設定します。

作成した証明書ファイル (ca.crt) をNginxの設定ディレクトリ(例: /etc/nginx/ssl/)に配置してください。

# /etc/nginx/sites-available/myapp.example.com

server {
    listen 443 ssl;
    server_name myapp.example.com;

    # --- Webサーバー証明書(既存のものを設定) ---
    ssl_certificate /path/to/your/server.crt;
    ssl_certificate_key /path/to/your/server.key;
    
    # --- クライアント認証用の設定追加 ---
    ssl_client_certificate /etc/nginx/ssl/ca.crt;
    ssl_verify_client optional;
    ssl_verify_depth 2;
    # --- クライアント認証用の設定追加ここまで ---

    location / {
        proxy_pass http://localhost:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        # --- クライアント認証用の設定追加 ---
        proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
        proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
        # --- クライアント認証用の設定追加ここまで ---
    }
}

Nginx設定ディレクティブの説明

クライアント認証の設定 (server ブロック内)

ディレクティブ説明
ssl_client_certificateクライアント証明書を検証するために使用するCA(認証局)の証明書ファイルを指定します。ここに指定されたCAによって署名されたクライアント証明書のみが信頼されます。
ssl_verify_clientクライアント証明書の検証方法を指定します。 ・on: 証明書の提示を必須とします。提示されない場合はアクセスを拒否します。 ・optional: 証明書の提示を任意とします。提示されれば検証し、されなければそのまま許可します。今回はこちらを使用します。 ・off: 検証を行いません(デフォルト)。
ssl_verify_depth証明書チェーンをどこまで遡って検証するかを指定します。2に設定すると、クライアント証明書とその署名者であるCA証明書の2階層を検証します。

バックエンドへ渡すHTTPヘッダーの設定 (location ブロック内)

ディレクティブ説明
proxy_set_header X-SSL-Client-Verify $ssl_client_verify;クライアント証明書の検証結果 (SUCCESS, FAILED:reason, NONE) を、X-SSL-Client-Verifyという名前のヘッダーに格納してバックエンドに渡します。
proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;検証に成功したクライアント証明書の所有者情報(Subject DN)を、X-SSL-Client-DNヘッダーに格納してバックエンドに渡します。アプリケーション側ではこのヘッダーの値を見て表示を切り替えます。

ステップ3: Webアプリケーションの実装例 (Django)

バックエンドのDjangoアプリケーションでは、Nginxから渡されるHTTPヘッダーを request.META から読み取り、表示を切り替えます。

1. ビュー (views.py)

Nginxから渡された X-SSL-Client-DN ヘッダーの値に特定の文字列が含まれているかをチェックし、表示を分岐させます。

# myapp/views.py
from django.shortcuts import render

def index(request):
    # Nginxから渡されたヘッダーを取得
    client_verify = request.META.get('HTTP_X_SSL_CLIENT_VERIFY', 'Unknown')
    client_dn = request.META.get('HTTP_X_SSL_CLIENT_DN', '証明書なし')

    # アプリケーション側で、クライアント証明書の情報(DN)に
    # 'OU=Floor5-SecureArea' が含まれているかを直接判断する
    show_special_menu = ', OU=Floor5-SecureArea,' in client_dn

    context = {
        'show_special_menu': show_special_menu,
        'client_verify': client_verify,
        'client_dn': client_dn,
    }
    return render(request, 'myapp/index.html', context)

2. テンプレート (index.html)

{# templates/myapp/index.html #}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>My Web App</title>
</head>
<body>
    <h2>ようこそ</h2>
    <p>証明書検証結果: <code>{{ client_verify }}</code></p>
    <p>あなたの証明書情報: <code>{{ client_dn }}</code></p>

    <h1>通常メニュー</h1>
    <ul>
        <li>ホーム</li>
        <li>製品情報</li>
        <li>お問い合わせ</li>
    </ul>

    {% if show_special_menu %}
        <hr>
        <h1 style="color: red;">★特別メニュー (5階機密エリア限定)★</h1>
        <ul>
            <li>内部監査レポート</li>
            <li>開発者向けドキュメント</li>
        </ul>
    {% endif %}
</body>
</html>

今回のクライアント証明書が入ったPCの場合は、{{ client_verify }}には「SUCCESS」が、{{ client_dn }} には「CN=User Five,OU=Floor5,O=My Company,ST=Tokyo,C=JP」が表示されます。

ステップ4: クライアントPCへの証明書のインポートと削除

インポート手順と運用に関する重要な注意点

1. インストール先ストアの選択

証明書をインポートする際、保存場所として「現在のユーザー」と「ローカルコンピューター」の2つを選択できます。この仕組みを正しく機能させるには、「現在のユーザー」へのインストールが必要です。

  • 現在のユーザー (推奨):
    • メリット: ChromeやEdgeなどの主要なブラウザは、基本的にこのストアから証明書を読み込みます。このため、設定が簡単で確実に動作します。
    • デメリット/注意点: 企業のActive Directory環境で「移動プロファイル」などの設定が有効な場合、ユーザーが別のPCにログインすると、この証明書も一緒に”移動”してしまいます。その結果、5階の機密エリア以外のPCからも特別メニューにアクセスできてしまい、「PCを固定する」という目的が達成できません。
  • ローカルコンピューター:
    • メリット: 証明書をPC自体に紐付けるため、移動プロファイルの影響を受けません。
    • デメリット/注意点: 多くのブラウザは、デフォルトでこのストアを自動的に探しに行きません。そのため、ここにインストールしてもブラウザが証明書を認識できず、認証が機能しないケースがほとんどです。

2. アカウントの運用 (推奨)

上記「現在のユーザー」ストアのデメリットを回避し、証明書を5階の機密エリアのPCに実質的に固定するための最も確実な方法は、PCのアカウント運用を工夫することです。

  • 5階の機密エリアのPCでは、他のフロアのPCでは使用しない専用のアカウントで運用することを強く推奨します。
    • ローカルアカウント: PCに固有のアカウントを作成して使用する。(スタンドアロンアカウント)
    • 専用ドメインアカウント: Active Directoryで、5階の機密エリアのPCにしかログインできないように制限された専用のドメインアカウントを用意する。

この運用により、「特定のユーザー」が「特定のPC」でログインした場合にのみ証明書が利用される状況を作り出し、セキュリティを担保します。

3. インポート実行

  1. 5階の機密エリアのPCにのみ client-floor5-secure.p12 を配布し、上記で推奨した専用アカウントでログインします。
  2. .p12 ファイルをダブルクリックし、ウィザードに従ってインポートします。保存場所は「現在のユーザー」を選択し、ファイル作成時に設定したパスワードを入力します。
  3. 証明書のインストール後、ブラウザを完全に終了し、再起動します。 これにより、ブラウザが新しくインストールされた証明書を認識し、サーバーへのアクセス時に利用できるようになります。

アクセス結果:

  • 5階の機密エリアのPCからアクセス: ブラウザが証明書の選択を求めてきます。User Secure Area の証明書を選択すると、特別メニューが表示されます
  • その他のPCからアクセス: 証明書がないため、何も求められず、そのまま通常メニューが表示されます

削除手順

不要になった証明書は、Windowsの管理コンソールから削除できます。

  1. Windowsキー + R を押して「ファイル名を指定して実行」ダイアログを開きます。
  2. certlm.msc と入力して実行します。これにより、「ローカル コンピューター」の証明書ストアが開きます。
  3. 左側のペインで 個人 -> 証明書 を選択します。
  4. 右側のペインに表示された一覧から、削除したい証明書(例: User Secure Area)を右クリックし、「削除」を選択します。

注意: 証明書のインポート時に「現在のユーザー」を選択してインストールした場合、証明書は certmgr.msc (ユーザー アカウントの証明書マネージャー) に格納されます。certlm.mscで見つからない場合は、certmgr.msc を実行して同様の操作を行ってください。

補足: Cloudflare環境での対策

Cloudflareのようなリバースプロキシ(CDN)を経由する場合、クライアントとオリジンサーバー(Nginx)間のTLS通信がプロキシによって一度終端されるため、オリジンサーバーはクライアント証明書を直接受け取ることができません。 これには、主に2つの対策が考えられます。

対策1: Cloudflare mTLSを利用する(Enterpriseプラン)

Cloudflareの**mTLS(相互TLS)**機能を利用します。これはクライアント証明書をCloudflareのエッジで検証し、その情報をHTTPヘッダーに格納してオリジンサーバーに転送するものです。(※この機能は通常Enterpriseプランが必要です)

  1. Cloudflareの管理画面で、作成した認証局の証明書 (ca.crt) をアップロードします。
  2. 対象のホスト名に対してmTLSを有効化し、証明書の検証を必須または任意に設定します。
  3. Nginx側ではクライアント認証設定が不要になり、アプリケーション側ではCloudflareが転送する専用のHTTPヘッダー(例: Cf-Tls-Client-Auth-Cert-Subject-Dn)を読み取るように修正します。

対策2: CDNをバイパスする専用ドメインを用意する(推奨)

より現実的で一般的な方法は、クライアント証明書を必要とするユーザー向けに、CDNを経由しないダイレクトアクセス用のドメイン(サブドメイン)を用意することです。

構成の概要

  • 一般ユーザー向け: www.myapp.example.com → Cloudflare経由 → Nginx(クライアント認証なし)
  • 特別ユーザー向け: direct.myapp.example.com → Nginx(クライアント認証あり)

手順

  1. DNS設定:
    • www.myapp.example.com: CNAMEレコードなどでCloudflareに向けます。(通常の設定)
    • direct.myapp.example.com: Aレコードで、オリジンサーバー(Nginx)のIPアドレスを直接指定します。このとき、Cloudflareのプロキシ設定(オレンジ色の雲マーク)を**必ずオフ(DNS Only)**にします。
  2. Nginxの設定: ドメインごとにserverブロックを分けます。
    /etc/nginx/sites-available/myapp.conf
    --- 一般ユーザー向け (Cloudflare経由) ---
    server {
    listen 443 ssl;
    server_name www.myapp.example.com;
    ssl_certificate /path/to/your/server.crt; ssl_certificate_key /path/to/your/server.key; # クライアント認証は行わない # CloudflareのIPからのみ許可する設定を推奨 # include /etc/nginx/cloudflare_ips.conf; location / { proxy_pass http://localhost:8000; # ... (通常のproxy_set_header) }
    }
    --- 特別ユーザー向け (ダイレクトアクセス) ---
    server {
    listen 443 ssl;
    server_name direct.myapp.example.com;
    ssl_certificate /path/to/your/server.crt; # 同じサーバー証明書でOK ssl_certificate_key /path/to/your/server.key; # こちらではクライアント認証を有効にする ssl_client_certificate /etc/nginx/ssl/ca.crt; ssl_verify_client optional; # もしくは on ssl_verify_depth 2; location / { proxy_pass http://localhost:8000; # ... (通常のproxy_set_header) proxy_set_header X-SSL-Client-Verify $ssl_client_verify; proxy_set_header X-SSL-Client-DN $ssl_client_s_dn; }
    }

  3. 運用: 特別なメニューが必要なユーザーには https://direct.myapp.example.com のURLを案内します。一般ユーザーは引き続き https://www.myapp.example.com を利用します。アプリケーション側のロジック(views.py)は、X-SSL-Client-DNヘッダーの有無で判断するため、変更の必要はありません。

この方法により、高価なCDNのプランに依存することなく、柔軟なアクセス制御を実現できます。

タイトルとURLをコピーしました