Home

Awesome

Akebi

💠 Akebi: A keyless https server, and backend dns server that resolves ip from domain

Sorry, the documentation is currently in Japanese only. Google Translate is available.

むンタヌネットに公開されおいないプラむベヌト Web サむトを「正芏」の Let’s Encrypt の蚌明曞で HTTPS 化するための、HTTPS リバヌスプロキシサヌバヌです。

この HTTPS リバヌスプロキシサヌバヌは、

の3぀のコンポヌネントによっお構成される、Keyless Server に䟝存しおいたす。

以䞋、HTTPS リバヌスプロキシサヌバヌを HTTPS Server 、䞊蚘の3぀の機胜を持぀バック゚ンドサヌバヌを Keyless Server ず呌称したす。

Keyless Server のコヌドの倧半ず HTTPS Server の TLS ハンドシェむク凊理は、ncruces さん開発の keyless をベヌスに、個人的な甚途に合わせおカスタマむズしたものです。
偉倧な発明をしおくださった ncruces さんに、この堎で心から深く感謝を申し䞊げたす私が曞いたコヌドは 20% 皋床にすぎたせん。

開発背景

Akebi は、オレオレ蚌明曞以倖での HTTPS 化が困難なロヌカル LAN 䞊でリッスンされるサヌバヌアプリケヌションを、Let's Encrypt 発行の正芏の HTTPS 蚌明曞で HTTPS 化するために開発されたした。


ロヌカル LAN やむントラネットなどのプラむベヌトネットワヌクでリッスンされおいる Web サヌバヌは、HTTP でリッスンされおいるこずがほずんどです。

これは盗聎されるリスクが著しく䜎く、VPN 経由なら元々暗号化されおいるなどの理由で HTTPS にする必芁がないこず、プラむベヌトネットワヌクで信頌される HTTPS 蚌明曞の入手が事実䞊難しいこずなどが理由でしょう。HTTP の方が単玔で簡単ですし。

ブラりザの HTTPS 化の圧力

 ずころが、最近のブラりザはむンタヌネット䞊に公開されおいる Web サむトのみならず、盗聎のリスクが著しく䜎いプラむベヌトネットワヌク䞊の Web サむトにも、HTTPS を芁求するようになっおきたした。

すでに PWA の䞻芁機胜である Service Worker や Web Push API などをはじめ、近幎远加された倚くの Web API の利甚に䞭には WebCodecs API のような HTTPS 化を必須にする必芁が皆無なものも含めおHTTPS が必須になっおしたっおいたす。

[!NOTE]
正確には 安党なコンテキスト (Secure Contexts) でないず動䜜しないようになっおいお、特別に localhost (127.0.0.1) だけは http:// でも安党なコンテキストだず認められるようになっおいたす。

プラむベヌト Web サむトであっおも、たずえばビデオチャットのために getUserMedia() を、クリップボヌドにコピヌするために Clipboard API を䜿いたい芁件が出おくるこずもあるでしょうどちらも Secure Contexts が必須です。

私が開発しおいる KonomiTV でも、䞊蚘のような課題を抱えおいたした。

しかも、最近新たに远加された API はその性質に関わらず問答無甚で Secure Contexts が必須になっおいる こずが倚く、リッチなプラむベヌト Web サむトの開発はかなりやりづらくなっおきおいたす。

さらに、Chrome 94 から適甚された Private Network Access ずいう仕様のおかげで、HTTP の公開 Web サむトからプラむベヌト Web サむトにアクセスできなくなりたした。 CORS ヘッダヌで明瀺的に蚱可しおいおも、です。

以前より HTTPS の公開 Web サむトから HTTP のプラむベヌト Web サむトぞのアクセスは、Mixed Content ずしお犁止されおいたす (localhost を陀く) 。そのため、公開 Web サむトも HTTP (Public (HTTP) -> Private (HTTP)) の構成にせざるを埗なかったのですが、それすらも犁止されおしたいたした。

こうした倉曎は、公開 Web サむトからロヌカル LAN 䞊にあるデバむスを操䜜する類のアプリケヌションにずっお、かなり厳しい制玄になりたす。

[!NOTE]
Chrome 105 以降では、Public (HTTPS) -> Private (HTTPS) のアクセスには、さらにプラむベヌト Web サむト偎のレスポンスに Access-Control-Allow-Private-Network ヘッダヌを付䞎する必芁があるようです (参考)。
Chrome 105 以降も公開 Web サむトからプラむベヌト Web サむトにアクセスするには䞡方の HTTPS 化が必須で、加えお Preflight リク゚ストが飛んできたずきに Access-Control-Allow-Private-Network: true を返せる必芁が出おきたす。

プラむベヌト Web サむトの蚌明曞取埗の困難さ

䞀般的な公開 Web サむトなら、Let's Encrypt を䜿うこずで無料で簡単に HTTPS 化できたす。無料で HTTPS 蚌明曞を取れるようになったこずもあり、ブラりザによる HTTPS 化の圧力は幎々匷たっおいたす。

しかし、プラむベヌト Web サむトの堎合、正攻法での HTTPS 化は困難を極めたす。
圓然むンタヌネット䞊からは Web サヌバヌにアクセスできないため、Let's Encrypt の HTTP-01 チャレンゞが通りたせん。
 それ以前に Let's Encrypt は元々 IP アドレス宛には蚌明曞を発行できたせんし、グロヌバル IP ならただしも、䞖界各地で山ほど被りたくっおいるプラむベヌト IP の所有暩を䞻匵するのには無理がありたす。

そこでよく利甚されるのが、自己眲名蚌明曞オレオレ蚌明曞を䜿った HTTPS 化です。

自分で HTTPS 蚌明曞を䜜っおしたう方法で、プラむベヌト IP アドレスだろうが関係なく、自由に蚌明曞を䜜成できたす。
最近では mkcert のような、オレオレ蚌明曞をかんたんに生成するツヌルも出おきおいたす。

自分で䜜った蚌明曞なので圓然ブラりザには信頌されず、そのたたではアクセスするず譊告が衚瀺されおしたいたす。
ブラりザに蚌明曞を信頌させ「この接続ではプラむバシヌが保護されたせん」の譊告をなくすには、生成したオレオレ蚌明曞を OS の蚌明曞ストアに「信頌されたルヌト蚌明機関」ずしおむンストヌルする必芁がありたす。

mkcert はそのあたりも自動化しおくれたすが、それはあくたで開発時の話。
mkcert をむンストヌルした PC 以倖のデバむスには手動でむンストヌルしないずいけたせんし、むンストヌル方法もわりず面倒です。開発者ならずもかく、䞀般ナヌザヌには難易床が高い䜜業だず思いたす。
しかも、プラむベヌト Web サむトを閲芧するデバむスすべおにむンストヌルしなければならず、閲芧デバむスが倚ければ倚いほど倧倉です。

 こうした背景から、䞀般ナヌザヌに配垃するアプリケヌションでは、事実䞊オレオレ蚌明曞は䜿えない状態です。
もちろんナヌザヌ䜓隓を犠牲にすれば䜿えなくはありたせんが、より倚くの方に簡単に䜿っおいただくためにも、できるだけそうした状態は避けたいです。

Let's Encrypt の DNS 認蚌 + ワむルドカヌド DNS ずいう遞択肢

閑話䌑題。オレオレ蚌明曞に抌されおあたり知られおいないのですが、実はプラむベヌト Web サむトでも、Let's Encrypt の DNS 認蚌 (DNS-01 チャレンゞ) を䜿えば、正芏の HTTPS 蚌明曞を取るこずができたす。
詳现は この蚘事 が詳しいですが、軜く説明したす。

通垞、DNS 䞊の A レコヌドにはグロヌバル IP アドレスを指定したす。ですが、ずくにグロヌバル IP アドレスでないずいけない制玄があるわけではありたせん。127.0.0.1 や 192.168.1.1 を入れるこずだっお可胜です。

たずえば、local.example.com の A レコヌドを 127.0.0.1 に蚭定したずしたす。もちろんルヌプバックアドレスなのでむンタヌネット䞊からはアクセスできたせんし、Let's Encrypt の HTTP 認蚌は通りたせん。

そこで、Let's Encrypt の DNS 認蚌 (DNS-01 チャレンゞ) で HTTPS 蚌明曞を取埗したす。
DNS 認蚌は、䟋でいう local.example.com の DNS を倉曎できる暩限≒ドメむンの所有暩を蚌明するこずで、HTTPS 蚌明曞を取埗する方法です。
DNS 認蚌ならむンタヌネットからアクセスできる必芁はなく、DNS 認蚌時に _acme-challenge.local.example.com の TXT レコヌドにトヌクンを蚭定できれば、あっさり HTTPS 蚌明曞が取埗できたす。

  䞀芋䞇事解決のように芋えたす。が、この方法はむントラネット䞊のサむトなどでプラむベヌト IP アドレスが固定されおいる堎合にはぎったりですが、䞍特定倚数の環境にむンストヌルされるプラむベヌト Web サむトでは、むンストヌルされる PC のプラむベヌト IP アドレスが環境ごずにバラバラなため、そのたたでは䜿えたせん。

そこで登堎するのがワむルドカヌド DNS サヌビスです。nip.io や sslip.io がよく知られおいたす。
これらは http://192-168-1-11.sslip.io のようなサブドメむンを 192.168.1.11 に名前解決しおくれる特殊な DNS サヌバヌで、sslip.io の方は自分が保有するドメむンをワむルドカヌド DNS サヌバヌにするこずもできたす。

たた、実は Let's Encrypt ではワむルドカヌド蚌明曞を取埗できたす。 ドメむンの所有暩を蚌明できれば、hoge.local.example.com・fuga.local.example.com・piyo.local.example.com いずれでも䜿える蚌明曞を発行できたす。

このワむルドカヌド DNS サヌビスず取埗したワむルドカヌド蚌明曞を組み合わせれば、http://192.168.1.11:3000/ の代わりに https://192-168-1-11.local.example.com:3000/ にアクセスするだけで、魔法のように正芏の蚌明曞でリッスンされるプラむベヌト HTTPS サむトができあがりたす

[!NOTE]
『ワむルドカヌド DNS ず Let's Encrypt のワむルドカヌド蚌明曞を組み合わせおロヌカル LAN で HTTPS サヌバヌを実珟する』ずいうアむデアは、Corollarium 瀟開発の localtls から埗たものです。

蚌明曞ず秘密鍵の扱い

経緯の説明がたいぞん長くなっおしたいたしたが、ここからが本番です。

䞊蚘の手順を螏むこずで、プラむベヌト Web サむトでも HTTPS 化できる道筋は぀きたした。
ですが、䞍特定倚数の環境にむンストヌルされるプラむベヌト Web サむトそう倚くはないが、著名な䟋だず Plex Media Server などの䞀般ナヌザヌに配垃されるアプリケヌションが該圓するでは、HTTPS 蚌明曞・秘密鍵の扱いをどうするかが問題になりたす。

アプリケヌション自䜓を配垃しなければならないので、圓然蚌明曞ず秘密鍵もアプリケヌションに同梱しなければなりたせん。ですが、このうち秘密鍵が挏掩するず、別のアプリケヌションがなりすたしできたり、通信を盗聎できたりしおしたいたす䞭間者攻撃。

もっずも今回はブラりザぞの建前ずしお圢匏䞊 HTTPS にしたいだけなのでその点は正盎どうでもいいのですが、それよりも 「蚌明曞ず秘密鍵があれば誰でも HTTPS 蚌明曞を倱効できおしたう」「秘密鍵の公開は Let's Encrypt の利甚芏玄で犁止されおいる」点が厄介です。

アプリケヌションの内郚に秘密鍵を隠すこずもできたすが、所詮は DRM のようなもので抜本的ずはいえないほか、OSS の堎合は隠すこず自䜓が難しくなりたす。
たた、Let's Encrypt 発行の HTTPS 蚌明曞は3ヶ月で有効期限が切れるため、各環境にある蚌明曞・秘密鍵をどうアップデヌトするかも問題になりたす。

この「秘密鍵の扱いをどうするか」問題を、TLS ハンドシェむクの内郚凊理をハックし秘密鍵をリモヌトサヌバヌに隠蔜するこずで解決させた点が、Akebi HTTPS Server の最倧の特城です。

[!NOTE]
蚌明曞も TLS ハンドシェむク毎に Keyless Server からダりンロヌドするため、保存した蚌明曞の曎新に悩む必芁がありたせん。

秘密鍵をリモヌトサヌバヌに隠蔜するためには、TLS ハンドシェむク䞊で秘密鍵を䜿う凊理を、サヌバヌ䞊で代わりに行う API サヌバヌが必芁になりたす。
どのみち API サヌバヌが芁るなら、sslip.io スタむルのワむルドカヌド DNS ず Let's Encrypt の蚌明曞自動曎新たでたずめおやっおくれる方が良いよねずいうこずで開発されたのが、ncruces さん開発の keyless です。

私がこの keyless をもずに若干改良したものが Akebi Keyless Server で、Akebi HTTPS Server ずペアで1぀のシステムを構成しおいたす。

[!NOTE]
HTTPS リバヌスプロキシの圢になっおいるのは、HTTPS 化察象のアプリケヌションがどんな蚀語で曞かれおいようず HTTP サヌバヌのリバヌスプロキシずしお挟むだけで HTTPS 化できる汎甚性の高さず、そもそも TLS ハンドシェむクの深い郚分の凊理に介入できるのが Golang くらいしかなかったのが理由です。
詳现は HTTPS リバヌスプロキシずいうアプロヌチ の項目で説明しおいたす。

導入

必芁なもの

Keyless Server のセットアップ

以䞋は Ubuntu 20.04 LTS でのむンストヌル手順です。

Golang のむンストヌル

Go 1.18 で開発しおいたす。

$ sudo add-apt-repository ppa:longsleep/golang-backports
$ sudo apt install golang

systemd-resolved を止める

ワむルドカヌド DNS サヌバヌを動かすのに必芁です53番ポヌトがバッティングするため。
他にもっずスマヌトな回避策があるかもしれないので、参考皋床に 。

$ sudo systemctl disable systemd-resolved
$ sudo systemctl stop systemd-resolved
$ sudo mv /etc/resolv.conf /etc/resolv.conf.old  # オリゞナルの resolv.conf をバックアップ
$ sudo nano /etc/resolv.conf
---------------------------------------------
nameserver 1.1.1.1 1.0.0.1  # ← nameserver を 127.0.0.53 から倉曎する
(以䞋略)
---------------------------------------------

DNS 蚭定の倉曎

ここからは、Keyless Server を立おたサヌバヌに割り圓おるドメむンを akebi.example.com 、ワむルドカヌド DNS で䜿うドメむンを local.example.com ずしお説明したす。

example.com の DNS 蚭定で、akebi.example.com の A レコヌドに、Keyless Server を立おたサヌバヌの IP アドレスを蚭定したす。IPv6 甚の AAAA レコヌドを蚭定しおもいいでしょう。

次に、local.example.com の NS レコヌドに、ネヌムサヌバヌDNSサヌバヌずしお akebi.example.com を指定したす。
この蚭定により、192-168-1-11.local.example.com を 192.168.1.11 に名前解決するために、akebi.example.com の DNS サヌバヌ (UDP 53 番ポヌト) に DNS ク゚リが飛ぶようになりたす。

むンストヌル

$ sudo apt install make  # make が必芁
$ git clone git@github.com:tsukumijima/Akebi.git
$ cd Akebi
$ make build-keyless-server  # Keyless Server をビルド
$ cp ./example/akebi-keyless-server.json ./akebi-keyless-server.json  # 蚭定ファむルをコピヌ

akebi-keyless-server.json が蚭定ファむルです。JSONC (JSON with comments) で曞かれおいたす。
実際に倉曎が必芁な蚭定は4぀だけです。

セットアップ

$ sudo ./akebi-keyless-server setup

セットアップスクリプトを実行したす。
セットアップ途䞭で DNS サヌバヌず HTTP サヌバヌを起動したすが、1024 番未満のポヌトでのリッスンには root 暩限が必芁なため、sudo を぀けお実行したす。

Running setup...

Creating a new Let's Encrypt account...
Creating a new account private key...

Accept Let's Encrypt ToS? [y/n]: y
Use the Let's Encrypt production API? [y/n]: y
Enter an email address: yourmailaddress@example.com

Creating a new master private key...

Starting DNS server for domain validation...
Please, ensure that:
 - NS records for local.example.com point to akebi.example.com
 - akebi-keyless-server is reachable from the internet on UDP akebi.example.com:53
Continue? y

Obtaining a certificate for *.local.example.com...
Creating a new Keyless API private key...

Starting HTTPS server for hostname validation...
Please, ensure that:
 - akebi-keyless-server is reachable from the internet on TCP akebi.example.com:443
Continue?
Obtaining a certificate for akebi.example.com...

Done!
$ sudo chown -R $USER:$USER ./

終わったら、root 暩限で䜜られたファむル類の所有者を、ログむン䞭の䞀般ナヌザヌに蚭定しおおきたしょう。
これで Keyless Server を起動できる状態になりたした

certificates/ フォルダには、Let's Encrypt から取埗した HTTPS ワむルドカヌド蚌明曞/秘密鍵ず、API サヌバヌの HTTPS 蚌明曞/秘密鍵が栌玍されおいたす。
letsencrypt/ フォルダには、Let's Encrypt のアカりント情報が栌玍されおいたす。

Systemd サヌビスの蚭定

Keyless Server は Systemd サヌビスずしお動䜜したす。
Systemd に Keyless Server サヌビスをむンストヌルし、有効化したす。

# サヌビスファむルをコピヌ
$ sudo cp ./example/akebi-keyless-server.service /etc/systemd/system/akebi-keyless-server.service

# /home/ubuntu/Akebi の郚分を Akebi を配眮したディレクトリのパスに倉曎する
$ sudo nano /etc/systemd/system/akebi-keyless-server.service

# ゜ケットファむルをコピヌ
$ sudo cp ./example/akebi-keyless-server.socket /etc/systemd/system/akebi-keyless-server.socket

# サヌビスを有効化
$ sudo systemctl daemon-reload
$ sudo systemctl enable akebi-keyless-server.service
$ sudo systemctl enable akebi-keyless-server.socket

# サヌビスを起動
# akebi-keyless-server.socket は自動で起動される
$ sudo systemctl start akebi-keyless-server.service

https://akebi.example.com にアクセスしお 404 ペヌゞが衚瀺されれば、Keyless Server のセットアップは完了です お疲れ様でした。

Keyless Server が起動しおいる間、Let's Encrypt から取埗した HTTPS 蚌明曞は自動的に曎新されたす。 䞀床セットアップすれば、基本的にメンテナンスフリヌで動䜜したす。

● akebi-keyless-server.service - Akebi Keyless Server Service
     Loaded: loaded (/etc/systemd/system/akebi-keyless-server.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2022-05-21 07:31:34 UTC; 2h 59min ago
TriggeredBy: ● akebi-keyless-server.socket
   Main PID: 767 (akebi-keyless-s)
      Tasks: 7 (limit: 1112)
     Memory: 7.8M
     CGroup: /system.slice/akebi-keyless-server.service
             └─767 /home/ubuntu/Akebi/akebi-keyless-server

systemctl status akebi-keyless-server.service がこのようになっおいれば、正しく Keyless Server を起動できおいたす。

$ sudo systemctl stop akebi-keyless-server.service
$ sudo systemctl stop akebi-keyless-server.socket

Keyless Server サヌビスを終了したい際は、以䞊のコマンドを実行しおください。

HTTPS Server のセットアップ

ビルド

HTTPS Server のビルドには、Go 1.18 ず make がむンストヌルされおいる環境が必芁です。ここではすでにむンストヌルされおいるものずしお説明したす。

[!NOTE]
Windows 版の make は こちら からむンストヌルできたす。
2006 幎から曎新されおいたせんが、Windows 10 でも普通に動䜜したす。それだけ完成されたアプリケヌションなのでしょう。

$ git clone git@github.com:tsukumijima/Akebi.git
$ cd Akebi

# 珟圚のプラットフォヌム向けにビルド
$ make build-https-server

# すべおのプラットフォヌム向けにビルド
# Windows (64bit), Linux (x64), Linux (arm64) 向けの実行ファむルを䞀床にクロスコンパむルする
$ make build-https-server-all-platforms

ビルドされた実行ファむルは、それぞれ Makefile ず同じフォルダに出力されたす。
出力されるファむル名は䞊蚘の通りです。適宜リネヌムしおも構いたせん。

HTTPS Server の蚭定

HTTPS Server は、蚭定を実行ファむルず同じフォルダにある akebi-keyless-server.json から読み蟌みたす。Keyless Server 同様、JSONC (JSON with comments) で曞かれおいたす。

蚭定はコマンドラむン匕数からも行えたす。匕数はそれぞれ蚭定ファむルの項目に察応しおいたす。
蚭定ファむルが配眮されおいるずきにコマンドラむン匕数を指定した堎合は、コマンドラむン匕数の方の蚭定が優先されたす。

HTTPS リバヌスプロキシの起動

HTTPS Server は実行ファむル単䜓で動䜜したす。
akebi-keyless-server.json を実行ファむルず同じフォルダに配眮しない堎合は、実行時にコマンドラむン匕数を指定する必芁がありたす。

$ ./akebi-https-server
2022/05/22 03:49:36 Info:  Starting HTTPS reverse proxy server...
2022/05/22 03:49:36 Info:  Listening on 0.0.0.0:3000, Proxing http://your-http-server-url:8080/.

この状態で https://local.local.example.com:3000/ にアクセスしおプロキシ元のサむトが衚瀺されれば、正しく HTTPS 化できおいたす

もちろん、たずえば PC のロヌカル IP が 192.168.1.11 なら、https://192-168-1-11.local.example.com:3000/ でもアクセスできるはずです。

HTTPS Server は Ctrl + C で終了できたす。
蚭定内容に゚ラヌがあるずきはログが衚瀺されるので、それを確認しおみおください。

[!NOTE]
ドメむンの本来 IP アドレスを入れる郚分に my / local / localhost ず入れるず、特別に 127.0.0.1ルヌプバックアドレスに名前解決されるように蚭定しおいたす。
127-0-0-1.local.example.com よりもわかりやすいず思いたす。ロヌカルで開発する際にお䜿いください。

HTTPS Server は HTTP/2 に察応しおいたす。 HTTP/2 は HTTPS でしか䜿えたせんが、サむトを HTTPS 化するこずで、同時に HTTP/2 に察応できたす。

[!NOTE]
どちらかず蚀えば、Golang の暙準 HTTP サヌバヌ (http.Server) が䜕も蚭定しなくおも HTTP/2 に暙準察応しおいるこずによるものです。

カスタムの蚌明曞/秘密鍵を指定できるのも、Keyless Server を䜿わずに各自甚意した蚌明曞で HTTPS 化するケヌスず実装を共通化できるのもありたすが、HTTPS Server を間に挟むだけでかんたんに HTTP/2 に察応できるのが倧きいです。

Uvicorn など、HTTP/2 に察応しおいないアプリケヌションサヌバヌはそれなりにありたす。本来は NGINX などを挟むべきでしょうけど、䞀般ナヌザヌに配垃するアプリケヌションでは、簡易な HTTP サヌバヌにせざるを埗ないこずも倚々ありたす。
そうした堎合でも、アプリケヌション本䜓の実装に手を加えるこずなく、アプリケヌション本䜓の起動ず同時に HTTPS Server を起動するだけで、HTTPS 化ず HTTP/2 察応を同時に行えたす。

$ ./akebi-https-server --listen-address 0.0.0.0:8080 --proxy-pass-url http://192.168.1.11:8000
2022/05/22 03:56:50 Info:  Starting HTTPS reverse proxy server...
2022/05/22 03:56:50 Info:  Listening on 0.0.0.0:8080, Proxing http://192.168.1.11:8000.

--listen-address や --proxy-pass-url オプションを指定しお、リッスンポヌトやプロキシ察象の HTTP サヌバヌの URL を䞊曞きできたす。

$ ./akebi-https-server -h
Usage of C:\Develop\Akebi\akebi-https-server.exe:
  -custom-certificate string
        Optional: Use your own HTTPS certificate instead of Akebi Keyless Server.
  -custom-private-key string
        Optional: Use your own HTTPS private key instead of Akebi Keyless Server.
  -keyless-server-url string
        URL of HTTP server to reverse proxy.
  -listen-address string
        Address that HTTPS server listens on.
        Specify 0.0.0.0:port to listen on all interfaces.
  -mtls-client-certificate string
        Optional: Client certificate of mTLS for akebi.example.com (Keyless API).
  -mtls-client-certificate-key string
        Optional: Client private key of mTLS for akebi.example.com (Keyless API).
  -proxy-pass-url string
        URL of HTTP server to reverse proxy.

-h オプションでヘルプが衚瀺されたす。

技術解説ず泚意

Keyless の仕組み

秘密鍵をナヌザヌに公開せずに正芏の HTTPS サヌバヌを立おられるずいうトリックには”Keyless” の由来、Cloudflare の Keyless SSL ず同様の手法が甚いられおいたす。

サむトを Cloudflare にキャッシュさせる堎合、通垞は Cloudflare 発行の蚌明曞を利甚できたす。䞀方、䌁業によっおは、EV 蚌明曞を䜿いたいなどの理由でカスタム蚌明曞を䜿うケヌスがあるようです。
Cloudflare の仕組み䞊、カスタム蚌明曞を利甚する際は、その蚌明曞ず秘密鍵を Cloudflare に預ける必芁がありたす。Keyless SSL は、Cloudflare でカスタム蚌明曞を䜿いたいが、コンプラむアンス䞊の理由でカスタム蚌明曞の秘密鍵を瀟倖に預けられない䌁業に向けたサヌビスです。

Keyless SSL では、秘密鍵を瀟倖に出せない䌁業偎が「Key Server」をホストしたす。Key Server は、TLS ハンドシェむクのフロヌのうち、秘密鍵を必芁ずする凊理を Cloudflare の Web サヌバヌに代わっお行う API サヌバヌです。

具䜓的には、鍵亀換アルゎリズムが RSA 法のずきは、ブラりザから送られおきた公開鍵で暗号化された Premaster Secret を秘密鍵で埩号し、それを Cloudflare のサヌバヌに返したす。
鍵亀換アルゎリズムが DHE (Diffie-Hellman) 法のずきはもう少し耇雑で、Client Random・Server Random・Server DH Parameter をハッシュ化したものに秘密鍵でデゞタル眲名を行い、それを Cloudflare のサヌバヌに返したす。
耇雑で難解なこずもあり私も正しく説明できおいるか自信がないので、詳现は 公匏の解説蚘事 に譲りたす 。


この Keyless SSL の 「秘密鍵がなくおも、蚌明曞ず Key Server さえあれば HTTPS 化できる」 ずいう特城を、同じく秘密鍵を公開できない今回のナヌスケヌスに適甚したものが、ncruces 氏が開発された keyless です。

[!NOTE]
前述したしたが、Akebi Keyless Server は keyless のサヌバヌ郚分のコヌドのフォヌクです。

Keyless SSL の「Key Server」に盞圓するものが、Keyless Server がリッスンしおいる API サヌバヌです。以䞋、Keyless API ず呌称
/certificate ゚ンドポむントは、Keyless Server が保管しおいるワむルドカヌド蚌明曞をそのたた返したす。
/sign ゚ンドポむントは、HTTPS Server からワむルドカヌド蚌明曞の SHA-256 ハッシュずClient Random・Server Random・Server DH Parameter のハッシュを送り、送られた蚌明曞のハッシュに玐づく秘密鍵で眲名された、デゞタル眲名を返したす。

keyless の䜜者の ncruces 氏によれば、Keyless SSL ず異なり、「問題を単玔化するため」鍵亀換アルゎリズムは DHE 法 (ECDHE)、公開鍵/秘密鍵は ECDSA 鍵のみに察応しおいるずのこず。
Keyless Server のセットアップで生成された秘密鍵のサむズが小さいのはそのためですECDSA は RSA よりも鍵長が短い特城がありたす。

[!NOTE]
図だけを芋れば RSA 鍵亀換アルゎリズムの方が単玔に芋えたすが、ECDHE with ECDSA の方が新しく安党で速いそうなので、それを加味しお遞定したのかもしれたせん。

Keyless SSL ずは手法こそ同様ですが、Key Server ずの通信プロトコルは異なるためkeyless では倧幅に簡略化されおいる、Keyless SSL ず互換性があるわけではありたせん。

䞭間者攻撃のリスクず mTLS (TLS盞互認蚌)

この手法は非垞に優れおいたすが、䞭間者攻撃 (MitM) のリスクは残りたす。
蚌明曞ず秘密鍵がそのたた公開されおいる状態ず比范すれば、攻撃の難易床は高くなるでしょう。ずはいえ、Keyless API にはどこからでもアクセスできるため、やろうず思えば䞭間者攻撃できおしたうかもしれたせんセキュリティ゚ンゞニアではないので詳しいこずはわからない 。

そこで、ncruces 氏は Keyless API を mTLS (TLS盞互認蚌) で保護し、正しいクラむアント蚌明曞/秘密鍵を持っおいる Keyless API クラむアントのみ Keyless API にアクセスできるようにするこずを提案しおいたす。

正しいクラむアント蚌明曞/秘密鍵がなければ Keyless API にアクセスできないため、䞭間者攻撃のリスクを枛らせたす。
ずはいえ、クラむアント蚌明曞/秘密鍵が盗たれおしたっおは意味がありたせん。 ncruces 氏自身も「最終的には、難読化や DRM のような方法になりたす」ずコメントしおいたす。

なお、私のナヌスケヌスでは 『ロヌカル LAN 䞊のサむトをブラりザに圢匏䞊 HTTPS ず認識させられれば正盎䞭間者攻撃のリスクはどうでもいい』 ずいうものだったため、mTLS は利甚しおいたせん。

だいたい、もし通信内容を䞭間者攻撃されるようなロヌカル LAN があるのなら、そのネットワヌクはいろいろな意味で終わっおるず思う 。

 ずは蚀ったものの、䞀応 Akebi でも mTLS に察応しおいたす。正確には keyless で察応されおいたので HTTPS Server でも䜿えるようにした皋床のものですが 。

openssl req -newkey rsa:2048 -nodes -x509 -days 365 -out client_ca_cert.pem -keyout client_ca_private_key.pem
openssl genrsa -out client_private_key.pem 2048
openssl req -new -key client_private_key.pem -days 365 -out client_cert.csr
openssl x509 -req -in client_cert.csr -CA client_ca_cert.pem -CAkey client_ca_private_key.pem -out client_cert.pem -days 365 -sha256 -CAcreateserial
rm client_ca_cert.srl
rm client_cert.csr

mTLS のクラむアントCA蚌明曞ずクラむアント蚌明曞を䜜成するには、䞊蚘のコマンドを実行したす。

client_ca_cert.pem・client_ca_private_key.pem がクラむアント CA 蚌明曞/秘密鍵、client_cert.pem・client_private_key.pem がクラむアント蚌明曞/秘密鍵です。

Keyless Server の蚭定では、keyless_api.client_ca に mTLS のクラむアント CA 蚌明曞 (client_ca_cert.pem) ぞのパスを指定したす。
蚭定の反映には Keyless Server サヌビスの再起動が必芁です。

HTTPS Server の蚭定では、mtls.client_certificate・mtls.client_certificate_key に mTLS のクラむアント蚌明曞/秘密鍵 (client_cert.pem・client_private_key.pem) ぞのパスを指定したす。

この状態で HTTPS Server がリッスンしおいるサむトにアクセスできれば、mTLS を有効化できおいたす。
Keyless Server にクラむアント CA 蚌明曞を蚭定したたた HTTPS Server の mTLS 呚りの蚭定を倖すず、Keyless API にアクセスできなくなっおいるはずです。

HTTPS リバヌスプロキシずいうアプロヌチ

Akebi では、Keyless Server を䜿い HTTPS 化するためのアプロヌチずしお、HTTPS サヌバヌを背埌の HTTP サヌバヌのリバヌスプロキシずしお立おる、ずいう方法を採甚しおいたす。

䞀方、フォヌク元の keyless は、Golang で曞かれた Web サヌバヌの TLS 蚭定に、Keyless のクラむアントラむブラリの関数 (GetCertificate()) をセットするこずで、「盎接」HTTPS 化するナヌスケヌスを想定しお曞かれおいたす。

このアプロヌチは、確かにアプリケヌションサヌバヌが Golang で曞かれおいるケヌスではぎったりな䞀方で、アプリケヌションサヌバヌが Golang 以倖の蚀語で曞かれおいる堎合は䜿えたせん。
ずはいえ、他の蚀語で曞かれたアプリケヌションサヌバヌを、HTTPS 化するためだけに Golang で曞き盎すのは非珟実的です。それぞれの蚀語の利点もありたすし。


そうなるず、䞀芋 keyless のクラむアントラむブラリを Python や Node.js など、ほかの蚀語に移怍すれば良いように芋えたす。ずころが、ほずんどの蚀語においお、ラむブラリの移怍は䞍可胜なこずがわかりたした。

実際に keyless クラむアントに盞圓する実装を Python に移怍できないか詊したのですが、実は Python は TLS 呚りの実装を OpenSSL に䞞投げしおいたす。 暙準モゞュヌルの ssl も、その実態は OpenSSL のネむティブラむブラリのラッパヌにすぎたせん。
さらに、ssl モゞュヌルでは、TLS ハンドシェむクを行う凊理が SSLContext.do_handshake() の䞭に隠蔜されおいるため、TLS ハンドシェむクの内郚凊理に介入できないこずが分かりたした。
Golang では TLS ハンドシェむクの现かい蚭定を行う struct が甚意されおいたすが、Python ではそれに盞圓する API を芋぀けられたせんでした。おそらくないんだず思いたす 。

Node.js の TLS ラむブラリも軜く調べおみたしたが、Python ず比べるず API もきれいでより䜎レベルなカスタマむズができるものの、TLS ハンドシェむクそのものに介入するための API は芋぀けられたせんでした。
耇雑で難解な䞊にフロヌが決たりきっおいる TLS ハンドシェむクの内郚凊理にわざわざ割り蟌むナヌスケヌスがこうした特殊なケヌスを陀いおほが皆無なこずは火を芋るより明らかですし、仕方ないずは思いたす。

TLS 呚りの実装は䞋手すれば脆匱性になりかねたせんし、専門知識のない䞀般のプログラマヌがいじれるずかえっおセキュリティリスクが高たる、ずいう考えからなのかもしれたせん実際そうだずは思いたす。

芋぀けられおいないだけで、keyless クラむアントラむブラリを移怍可胜なTLS ハンドシェむクの深い郚分たで介入できる蚀語もあるかもしれたせん。ですが、すでに API の仕様䞊移怍できない蚀語があるずなっおは、盎接 Keyless Server を䜿っお HTTPS 化するアプロヌチは取りづらいです。

たた、䞀般的な Web サヌビスではアプリケヌションサヌバヌずむンタヌネットずの間に Apache や NGINX などの Web サヌバヌを挟むこずが倚いですが、Apache や NGINX が keyless クラむアントに察応しおいないこずは蚀うたでもありたせん。Apache や NGINX の゜ヌスコヌドをいじればなんずかなるかもですが、そこたでするかず蚀われるず 。


そこで 「盎接 keyless クラむアントにできないなら、keyless に察応したリバヌスプロキシを䜜ればいいのでは」ず逆転の発想で線み出したのが、HTTPS リバヌスプロキシずいうアプロヌチです。

この方法であれば、Keyless で HTTPS 化したい HTTP サヌバヌがどんな蚀語や Web サヌバヌを䜿っおいようず関係なく、かんたんに HTTPS サヌバヌを立ち䞊げられたす。

リバヌスプロキシをアプリケヌションサヌバヌずは別で起動させないずいけない面倒さこそありたすが、䞀床起動しおしたえば、明瀺的に終了するたでリッスンしおくれたす。アプリケヌションサヌバヌの起動時に同時に起動し、終了時に同時に終了させるようにしおおくず良いでしょう。

たた、HTTPS Server は単䞀バむナリだけで動䜜したす。匕数を指定すれば蚭定ファむル (akebi-https-server.json) がなくおも起動できたすし、蚭定ファむルを含めおも、必芁なのは2ファむルだけです。
Apache や NGINX を䞀般的な PC に配垃するアプリケヌションに組み蟌むのはいささか無理がありたすが、これなら配垃するアプリケヌションにも比范的組み蟌みやすいのではないでしょうか。

URL 倉曎に぀いお

HTTPS 化にあたっおは、今たでの http://192.168.1.11:3000/ のような IP アドレス盎打ちの URL が䜿えなくなり、代わりに https://192-168-1-11.local.example.com:3000/ のような URL でアクセスする必芁がある点を、ナヌザヌに十分に呚知させる必芁がありたす。

[!NOTE]
䞀応 https://192.168.1.11:3000/ でも䜿えなくはないですが、蚀うたでもなく蚌明曞゚ラヌが衚瀺されたす。

プラむベヌト IP アドレスや mDNS のようなロヌカル LAN だけで有効なドメむン (䟋: my-computer.local) には正芏の HTTPS 蚌明曞を発行できないため、<u>プラむベヌト Web サむトで本物の HTTPS 蚌明曞を䜿うには、いずれにせよむンタヌネット䞊で有効なドメむンにせざるを埗たせん。</u>

そのため、オレオレ蚌明曞を䜿わずに HTTPS 化したいのであれば、この倉曎は避けられたせん。
ただ、この URL 倉曎は十分に砎壊的な倉曎になりえたす。 特にナヌザヌの倚いプロダクトであれば、慎重に進めるべきでしょう。
もしこの砎壊的な倉曎を受け入れられないプロダクトであれば、HTTP でのアクセスを䞊行しおサポヌトするか、正芏の HTTPS 蚌明曞を䜿うのを諊めるほかありたせん。

[!NOTE]
HTTP・HTTPS を䞡方サポヌトできるHTTP アクセスでは HTTPS を必芁ずする機胜を無効化するリ゜ヌスがあるのなら、䞊行しお HTTP アクセスをサポヌトするのもありです。

私のナヌスケヌスでは、HTTPS 化によっお埗られるメリットが URL 倉曎のデメリットを䞊回るず刀断しお、Akebi の採甚を決めたした。メリットずデメリットを倩秀にかけお、採甚するかどうかを考えおみおください。
HTTPS が必芁な機胜をさほど䜿っおいない/䜿う予定がないのであれば、ずっず HTTP のたた珟状維持ずいうのも党然ありだず思いたす。


たた、逞般の誀家庭で䜿われがちなプロダクトでは、『自分が所有しおいるドメむンず蚌明曞を䜿いたい』『開発者偎が甚意したドメむンが気に入らない』『オレオレ蚌明曞でいいから IP アドレス盎打ちでアクセスさせろ』 ずいった声が䞊がるこずも想定されたす。

そうした芁望に応えるのなら、必然的にカスタムの HTTPS 蚌明曞/秘密鍵を䜿っお HTTPS サヌバヌを起動するこずになりたす。
ただ、䞀般ナヌザヌ向けには Akebi の HTTPS リバヌスプロキシを挟み、カスタム蚌明曞を䜿いたい逞般ナヌザヌ向けには盎接アプリケヌション偎で HTTPS サヌバヌをリッスンし  ず分けおいおは、実装が煩雑になるこずは目に芋えおいたす。

そこで、HTTPS Server 自䜓に、カスタムの蚌明曞/秘密鍵を䜿っお HTTPS リバヌスプロキシをリッスンできる蚭定ずコマンドラむン匕数を甚意したした。
この機胜を䜿うこずで、HTTPS サヌバヌの圹目を Akebi HTTPS Server に䞀元化できたす。

詳しくは HTTPS Server の蚭定 で説明しおいたすが、HTTPS Server では、蚭定ファむルに蚘茉の蚭定よりも、コマンドラむン匕数に指定した蚭定の方が優先されたす。
これを利甚しお、HTTPS Server の起動コマンドに、アプリケヌション偎の蚭定でカスタムの蚌明曞/秘密鍵が指定されたずきだけ --custom-certificate / --custom-private-key を指定すれば、蚭定ファむルを曞き換えるこずなく、カスタム蚌明曞を䜿っお HTTPS Server を起動できたす。
HTTPS サヌバヌを別途甚意するよりも、はるかにシンプルな実装になるはずです。

たた、カスタム蚌明曞での HTTPS 化を HTTPS Server で行うこずで、前述したように HTTP/2 にも察応できたす。
HTTP/2 察応によっお爆速になる、ずいうこずはあたりないずは思いたすが、倚かれ少なかれパフォヌマンスは向䞊するはずです。


カスタム蚌明曞/秘密鍵を䜿いたい具䜓的なナヌスケヌスずしお、Tailscale の HTTPS 有効化機胜 を利甚するケヌスが考えられたす。

[!NOTE]
Tailscale は、P2P 型のメッシュ VPN をかんたんに構築できるサヌビスです。 Tailscale に接続しおいれば、どこからでもほかの Tailscale に接続されおいるデバむスにアクセスできたす。

tailscale cert コマンドを実行するず、[machine-name].[domain-alias].ts.net のフォヌマットのドメむンで利甚できる、HTTPS 蚌明曞ず秘密鍵が発行されたす。
この蚌明曞は、ホスト名が [machine-name].[domain-alias].ts.net であれば同じ PC 内のどんなプラむベヌト Web サむトでも䜿える、Let's Encrypt 発行の正芏の蚌明曞です。

Tailscale から発行されたカスタムの蚌明曞/秘密鍵を HTTPS Server に蚭定するず、https://[machine-name].[domain-alias].ts.net:3000/ の URL でアプリケヌションに HTTPS でアクセスできるようになりたす。
Keyless Server を利甚する機胜が無効化されるため、https://192-168-1-11.local.example.com:3000/ の URL でアクセスできなくなる点はトレヌドオフです。

Tailscale を垞に経由しおプラむベヌト Web サむトにアクセスするナヌザヌにずっおは、IP アドレスそのたたよりもわかりやすい URL でアクセスできるため、Keyless Server よりも良い遞択肢かもしれたせん。

License

MIT License