👏

ロードバランサー配下の Web サーバーへのクライアント認証

2021/03/07に公開

AWS 上に Elastic Load Balancing(ELB)+Elastic Compute Cloud(EC2) で構成する Web システムに対してアクセスを制御する方法はいくつかあるかと思います。
今回はクライアント証明書を保有しているクライアントからのアクセスのみを許可するクライアント認証の設定を行い、メモとして残します。

構成

ロードバランサーサービスである ELB と Web アプリケーションが動作する EC2 を使用するシンプルな構成です。
概要

私の認識の範囲では、現時点で ELB にはクライアント認証を行える機能がないため、今回は EC2 へnginx をインストールしここでクライアント認証させ、且つ Web アプリケーションとしても動作させます。 ELB には複数サービスが含まれますが、今回は Classic Load Balancer(CLB) と Network Load Balancer(NLB) を使用します。本来であれば、 CLB か NLB のどちらかの利用でいいですが、両方でためしてみます。
今回の構成

設定

ここでは、半分ぐらいは外部のサイトをご参考ください。(手抜き)
VPC や EC2 や Security Group などは作成済みで、 EC2 の OS は Amazon Linux2 で nginx がインストール済みであるとします。(更に手抜き)

EC2

nginx のインストールには以下を参考にさせていただきました。ありがとうございます。

サーバ証明書、クライアント証明書には自己証明書を使用しますが、作成するにあたって、以下のサイトを参考にさせていただきました。こちらもありがとうございます。

証明書は EC2 上の root ユーザーのホームディレクトリに作成しました。ファイル名は、上のリンク内をそのまま参考にしています。

# ls -l /root/keys
total 28
-rw-r--r-- 1 root root 1192 Mar  7 02:53 CLIENT_CERT-ca.crt
-rw-r--r-- 1 root root  997 Mar  4 08:46 CLIENT_CERT.csr
-rw-r--r-- 1 root root 1675 Mar  4 08:45 CLIENT_CERT.key
-rw-r--r-- 1 root root 2512 Mar  7 08:50 CLIENT_CERT.p12
-rw-r--r-- 1 root root 1192 Mar  7 02:53 SERVER_CERT.crt
-rw-r--r-- 1 root root  997 Mar  4 08:26 SERVER_CERT.csr
-rw-r--r-- 1 root root 1675 Mar  7 02:53 SERVER_CERT.key

次は、nginx の設定を追加し、証明書を設定します。まずは、上で作成した証明書の一部を /etc/nginx/keys ディレクトリへコピーします。コピーするディレクトリは nginx から参照できればどこでもいいです。

$ sudo mkdir /etc/nginx/keys
$ ls -l /etc/nginx/keys
total 12
-rw-r--r-- 1 root root 1192 Mar  4 08:46 CLIENT_CERT-ca.crt
-rw-r--r-- 1 root root 1192 Mar  4 08:28 SERVER_CERT.crt
-rw-r--r-- 1 root root 1675 Mar  4 08:24 SERVER_CERT.key

nginx の設定を追加します。

# cat /etc/nginx/conf.d/example.conf
server {
    listen       443 ssl;
    root         /usr/share/nginx/html;

    ssl_certificate "/etc/nginx/keys/SERVER_CERT.crt";
    ssl_certificate_key "/etc/nginx/keys/SERVER_CERT.key";

    ssl_client_certificate "/etc/nginx/keys/CLIENT_CERT-ca.crt";
    ssl_verify_client on;
}

CLB

ここで気をつけたのは、 CLB 作成時に指定するリスナー設定です。証明書自体は、 EC2 上にあるためロードバランサーではリクエストをスルーさせるようにします。
CLB 作成時のリスナー設定

NLB

こちらも CLB 同様に、リスナーとルーティングの設定で、リクエストをスルーさせました。Default action の Forwatd to には適当なターゲットグループを作成、指定します。
NLB 作成時のリスナーとルーティング

確認

リクエストを投げて確認する前に、構成図を確認します。作成した証明書などの配置場所なども記載してみました。
構成

各種ファイルについて、クライアント証明書はCLIENT_CERT-ca.crt、クライアント鍵はCLIENT_CERT.key、クライアント証明書、及び鍵(PKCS12フォーマット)はCLIENT_CERT.p12、サーバー証明書はSERVER_CERT.crt、サーバー鍵はSERVER_CERT.eyです。

CLB 経由で EC2 へリクエスト

まずは証明書を 指定しない ケースです。
クライアントで以下コマンドを実行すると、 HTTP ステータス の 400 が返ってきます。

$ curl -v -k -I https://<CLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com
* Rebuilt URL to: https://<CLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com/
*   Trying 54.248.204.14...
* TCP_NODELAY set
* Connected to <CLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com (54.248.204.14) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: C=JP; ST=Tokyo; L=Default City; O=Default Company Ltd; CN=Server
*  start date: Mar  4 08:28:18 2021 GMT
*  expire date: Mar  2 08:28:18 2031 GMT
*  issuer: C=JP; ST=Tokyo; L=Default City; O=Default Company Ltd; CN=Server
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
> HEAD / HTTP/1.1
> Host: <CLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 400 Bad Request
HTTP/1.1 400 Bad Request
< Server: nginx/1.18.0
Server: nginx/1.18.0
< Date: Sun, 07 Mar 2021 04:47:23 GMT
Date: Sun, 07 Mar 2021 04:47:23 GMT
< Content-Type: text/html
Content-Type: text/html
< Content-Length: 237
Content-Length: 237
< Connection: close
Connection: close

<
* Closing connection 0
* TLSv1.2 (OUT), TLS alert, Client hello (1):

次は証明書を 指定する ケースです。
クライアントで以下コマンドを実行すると、 HTTP ステータス の 200 が返ってきて、正常に動作をしました。

$ curl -v -k -I --key CLIENT_CERT.key --cert CLIENT_CERT-ca.crt https://<CLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com
* Rebuilt URL to: https://<CLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com/
*   Trying 54.248.204.14...
* TCP_NODELAY set
* Connected to <CLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com (54.248.204.14) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS handshake, CERT verify (15):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: C=JP; ST=Tokyo; L=Default City; O=Default Company Ltd; CN=Server
*  start date: Mar  4 08:28:18 2021 GMT
*  expire date: Mar  2 08:28:18 2031 GMT
*  issuer: C=JP; ST=Tokyo; L=Default City; O=Default Company Ltd; CN=Server
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
> HEAD / HTTP/1.1
> Host: <CLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Server: nginx/1.18.0
Server: nginx/1.18.0
< Date: Sun, 07 Mar 2021 04:47:47 GMT
Date: Sun, 07 Mar 2021 04:47:47 GMT
< Content-Type: text/html
Content-Type: text/html
< Content-Length: 3520
Content-Length: 3520
< Last-Modified: Fri, 06 Nov 2020 22:27:51 GMT
Last-Modified: Fri, 06 Nov 2020 22:27:51 GMT
< Connection: keep-alive
Connection: keep-alive
< ETag: "5fa5cde7-dc0"
ETag: "5fa5cde7-dc0"
< Accept-Ranges: bytes
Accept-Ranges: bytes

<
* Connection #0 to host <CLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com left intact

NLB 経由で EC2 へリクエスト

証明書を 指定しない ケースです。 CLB 同様に、 400 が返ってきています。

$ curl -v -k -I https://<NLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com
* Rebuilt URL to: https://<NLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com/
*   Trying 54.238.125.11...
* TCP_NODELAY set
* Connected to <NLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com (54.238.125.11) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: C=JP; ST=Tokyo; L=Default City; O=Default Company Ltd; CN=Server
*  start date: Mar  4 08:28:18 2021 GMT
*  expire date: Mar  2 08:28:18 2031 GMT
*  issuer: C=JP; ST=Tokyo; L=Default City; O=Default Company Ltd; CN=Server
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
> HEAD / HTTP/1.1
> Host: <NLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 400 Bad Request
HTTP/1.1 400 Bad Request
< Server: nginx/1.18.0
Server: nginx/1.18.0
< Date: Sun, 07 Mar 2021 06:07:33 GMT
Date: Sun, 07 Mar 2021 06:07:33 GMT
< Content-Type: text/html
Content-Type: text/html
< Content-Length: 237
Content-Length: 237
< Connection: close
Connection: close

<
* Closing connection 0
* TLSv1.2 (OUT), TLS alert, Client hello (1):

次は証明書を 指定する ケースです。こちらも CLB 同様に 200 が返ってきて、正常に動作するのを確認できます。

$ curl -v -k -I --key CLIENT_CERT.key --cert CLIENT_CERT-ca.crt  https://<NLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com
* Rebuilt URL to: https://<NLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com/
*   Trying 54.238.125.11...
* TCP_NODELAY set
* Connected to <NLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com (54.238.125.11) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS handshake, CERT verify (15):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: C=JP; ST=Tokyo; L=Default City; O=Default Company Ltd; CN=Server
*  start date: Mar  4 08:28:18 2021 GMT
*  expire date: Mar  2 08:28:18 2031 GMT
*  issuer: C=JP; ST=Tokyo; L=Default City; O=Default Company Ltd; CN=Server
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
> HEAD / HTTP/1.1
> Host: <NLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Server: nginx/1.18.0
Server: nginx/1.18.0
< Date: Sun, 07 Mar 2021 06:06:51 GMT
Date: Sun, 07 Mar 2021 06:06:51 GMT
< Content-Type: text/html
Content-Type: text/html
< Content-Length: 3520
Content-Length: 3520
< Last-Modified: Fri, 06 Nov 2020 22:27:51 GMT
Last-Modified: Fri, 06 Nov 2020 22:27:51 GMT
< Connection: keep-alive
Connection: keep-alive
< ETag: "5fa5cde7-dc0"
ETag: "5fa5cde7-dc0"
< Accept-Ranges: bytes
Accept-Ranges: bytes

<
* Connection #0 to host <NLB 名>-<数字>.ap-northeast-1.elb.amazonaws.com left intact

以上、「ロードバランサー配下の Web サーバーへのクライアント認証設定」をメモがてらに残しました。そのうち、クライアント認証機能が ELB に追加されて、このメモが無駄になりますように!

Discussion