🐥

Cloudflare Keyless SSLの実装手順

2023/08/26に公開

先日Cloudflareで利用可能なTLS用電子証明書をまとめる記事を上げたところ、同僚より"Keyless SSLもありますよ"と指摘がありました。
あ!確かに!!!と。自分の不明を恥じるとともに(持つべきものは優秀な同僚)、時間を見つけて検証していましたが、設定項目が多岐にわたりいろいろ時間がかかってしまいました。
Cloudfareのドキュメントは基本よくできているのですが、知識が足りていない場合、説明不足だったりするケースもありはまりどころも多かったので以下に手順をまとめます。

ところで・・・SSLっていつまでSSLっていうんですかね?もうSSLプロトコルを商用で使うケースは許されておらず全てTLS (殆どは1.1以降)に移行しているのですが、まだSSLという表記は多用されているようです。この辺りの正しい使い分けの基準をご存じの方は教えて頂けると嬉しいです。

Keyless SSLとは

やってみる前にKeyless SSLについて整理します。同僚のブログ良くまとまっていますが、簡単に言うとCloudfareにSSL用証明書の秘密鍵を保存せずにTLS処理を行わせるものです。

秘密鍵はユーザーが管理する専用サーバ(鍵サーバと言います)に存在しており、CloudfareのエッジがTLS negotiationリクエストを受けた際にその鍵サーバの鍵を用いてTLS処理を行います。この場合のメリットですが、Cloudflareから万が一証明書が漏洩したとしても、存在しているのは公開鍵および電子証明書のみで秘密鍵が漏洩することはなくなります。Cloudflareから情報漏洩することは考えたくはありませんし、あるとはおもいませんが、セキュリティに100%は存在しないのも事実です。
また、このモードはさらにPKCS#11経由で、鍵サーバと連携するHSM(タンタンパー性を持つHardware Security Module:このデバイスに格納されているデータはFIPS140-2 Level3規格により非常に強固な対攻撃性を持つことが立証されている)に秘密鍵が格納されます。

この際鍵サーバへの通信は、インターネット側にポートを開放する必要の内容、インバウンド通信設定が不要なCloudflare Tunnelが使われます。Tunnelの記事についてはこちらです。

さっそくやってみる

ではやってみます。おそらく今回の手順は今までの記事の中で最長になるかもしれません。
簡単なStepは以下の通りです。
1.オリジンと鍵サーバの準備
2.オリジンにCNAME設定
3.(鍵サーバ) Cloudlfare Tunnelの設定
4.(鍵サーバ) Let's Encryptで証明書生成
5.Cloudflareへのアップロード(公開鍵のみ)
6.(鍵サーバ) Gokeylessモジュールの設定と起動

オリジンと鍵サーバの準備

ではまず2つのサーバを立てます。それぞれ以下の名前にします。
オリジン:target
鍵サーバ:key
Amazon EC2 Amazon Linux 2023を使います。targetはSSHに加えてHTTP/HTTPSのインバウンドを開けておきます。keyはSSHのみを空けておきます。
以下のコマンドでtargetにnginxをインストールします。

sudo dnf install nginx
sudo service nginx start

ブラウザからアクセスできることを確認します。この時点ではHTTP通信です。
http://ec2-52-197-199-143.ap-northeast-1.compute.amazonaws.com/

オリジンにCNAME設定

ここはいつものCloudflareの設定です。

この時点では以下の電子証明書でTLS通信が行われています。

(鍵サーバ) Cloudflare Tunnelの設定

マネージメントコンソール左ペインからAccessをクリックします。

Launch Zero Trustを押します。

再度左ペインからAccessTunnelをクリックします。

Create a tunnelを押します。

適当な名前を付けSave tunnelを押します。

Red Hat 64bitを押します。

Amazon Linux 2023はRedHatじゃないですが、細かいことは気にしない。
注意点:ちなみにKeyless SSLは正式にはamd64armしかCPUアーキテクチャをサポートしていないですが、試したところIntel CPUでも問題なく動作しましたのでそれで作業を進めます。
下のようなコマンドが表示されているのでそれをkeyのEC2で実行します。

curl -L --output cloudflared.rpm https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-x86_64.rpm && 
sudo yum localinstall -y cloudflared.rpm && 
sudo cloudflared service install <account ifo>

<account info>は皆さん毎に環境が変わるので注意してください。
Nextを押します。
Private Networksをクリックします。

PublicとPrivateの簡単な違いはこちらに記載しています。
鍵サーバのPrivateIPを入れSave tunnelを押します。
注意:PrivateIPだとCloudflare側から見えないためPublicIPを設定しがちですが注意してください。TunnelはEC2上のAgentがCloudflareEdgeにアクセスします。この際、自分のAgentが認識しているIPアドレスがセットされている必要があります。EC2では実体のIPアドレスと外から見えるPublicIPは別物であり、OSの中身からはPrivateIPしか取れないことに注意してください。

enX0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9001
        inet 172.31.36.23  netmask 255.255.240.0  broadcast 172.31.47.255
        inet6 fe80::424:daff:fed9:ec9f  prefixlen 64  scopeid 0x20<link>
        ether 06:24:da:d9:ec:9f  txqueuelen 1000  (Ethernet)
        RX packets 29028  bytes 37481422 (35.7 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 9891  bytes 811084 (792.0 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 12  bytes 1020 (1020.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 12  bytes 1020 (1020.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

以下のようにTunnelが作成されステータスがHEALTHYになれば成功です。
DOWNになる場合以下のコマンドを試してみて下さい。

sudo systemctl restart cloudflared/

INVALIDになる場合、おそらくセットしたPrivateIPが間違っています。

(鍵サーバ) Let's Encryptで証明書生成

では次に鍵サーバでSSL証明書を作成します。先日別の記事を上げましたがcertbotを使います。acme.shを使いたい場合は、同僚の記事に手順があります。

まずは以下を実行します。

sudo dnf install -y augeas-libs
sudo python3 -m venv /opt/certbot/
sudo /opt/certbot/bin/pip install --upgrade pip
sudo /opt/certbot/bin/pip install certbot
cd /opt/certbot/bin/
sudo ./certbot certonly --manual -d target.harunobukameda.labrat.online --preferred-challenges dns

target.harunobukameda.labrat.onlineは勿論皆さんの環境に合わせてください。
そうするとメールアドレスや許諾への同意が求められますのでそれに従い入力します。
以下が表示されるのでこの内容に従いDNSエントリを作成します。

Please deploy a DNS TXT record under the name:

_acme-challenge.target.harunobukameda.labrat.online.

with the following value:
<何かのハッシュ的な値>

その内容に従いDNSでTXTレコードを作ります。

DNSの詳しい説明は本記事からは割愛しますが作業すべきゾーンはharunobukameda.labrat.onlineです。
その後Enterを押すと証明書が無事発行されます。

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/target.harunobukameda.labrat.online/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/target.harunobukameda.labrat.online/privkey.pem

Cloudflareへのアップロード(公開鍵のみ)####

次に作成された証明書の公開鍵+電子証明書のみをCloudflareへアップロードします。
アップロードにはTunnelのIDが必要になりますのでまずは以下のコマンドを実行します。

export EMAIL=harunobukameda@gmail.com
export APIKEY=<secret>
export ACCOUNT_ID=<secret>

% curl -sX GET "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/teamnet/routes" \
     -H "X-Auth-Email: $EMAIL" \
     -H "X-Auth-Key: $APIKEY" \
     -H "Content-Type: application/json" | jq '.result[] | select (.tunnel_name == "key")'

API KEYはマネージメントコンソールのhttps://dash.cloudflare.com/profile/api-tokens から取得可能です。
AccountIDは各ドメインをクリックしてOverview画面の右下にあります。

表示された"virtual_network_id"をメモっておきます。

左ペインからSSL/TLSEdge Certificateをクリックします。

Upload Keyless SSL Certificateをクリックします。
注意点:この機能は有償アドオンであり、普通の環境では上位Enterpriseプランでも利用できません。検証などで必要な方は@kameoncloudまでご連絡ください。
以下の通り入力します。
Key server label:適当な名前
Key server hostname:鍵サーバのプライベートIP
Private IP:上記同様
Virtual Network ID:上で取得した値
SSL Certificate:生成された電子証明書の値
この例でいうと/etc/letsencrypt/live/target.harunobukameda.labrat.online/fullchain.pem
このファイルには複数の
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
で囲まれたBASE64ブロックがありますが、必要なのは一番最初のブロックのみです。
Upload Keyless SSL Certificateを押します。


暫く待つとデプロイが完了します。

(鍵サーバ) Gokeylessモジュールの設定と起動

この時点で
https://target.harunobukameda.labrat.online/
にアクセスするとエラーとなります。Cloudflare側はすでにKeyless SSL用電子証明書を用いてTLS処理を行おうとしますが、まだ秘密鍵へのアクセスが確立されていないためです。Tunnelの設定だけでは通信経路が確立されたのみで、秘密鍵へのアクセスが確立されていません。このためgokeylessというモジュールを使います。
以下のコマンドを実行します。

sudo dnf install dnf-plugins-core && dnf clean all
sudo dnf config-manager --add-repo https://pkg.cloudflare.com/gokeyless.repo
sudo dnf install gokeyless

/etc/keyless/gokeyless.yamlの中身を修正します。
hostname: PrivateIPアドレス
zone_id: CloudflareのZoneID
(各ドメインをクリックしたOverview画面の右側)
origin_ca_api_key: ここの情報

先ほど生成した秘密鍵を/etc/keyless/keys/に移動させ拡張子を.keyに変更します。

sudo cp /etc/letsencrypt/live/target.harunobukameda.labrat.online/privkey.pem /etc/keyless/keys/privkey.key

以下のような形で権限を修正します。

ls -l /etc/keyless/keys
-r-------- 1 keyless keyless 1675 Nov 18 16:44 privkey.key

そして

sudo service gokeyless start

gokeylessを起動しサイトにアクセスします。
作成したLet’s EncryptでTLSセッションが確立していることがわかります。

この鍵サーバは複数作っておくことで冗長性を担保することが可能です。この場合EdgeからBGPのダイナミックルーティングで一番Network的に近しい場所の鍵サーバが優先的に使われます。

Discussion