2 ポイント 投稿者 GN⁺ 2024-09-26 | 1件のコメント | WhatsAppで共有
  • BlogTechは、小規模から中規模のWebサイトを管理するためのCベースのツールキットで、HTTPS、バーチャルホスト、ACMEベースの自動証明書管理、リモートサーバー管理クライアントを提供する
  • 現在はテスト段階で、バージョン 0.4.xhttps://coz.is/ を数週間運用するのに使われたが、安定版リリースとして提示されているわけではない
  • サーバーはLinuxとWindowsで動作するが、HTTPSはLinuxのみ対応で、Windowsではクライアントとサーバーの両方がHTTPのみ使用可能
  • リモートファイル管理は --put, --get, --delete で行い、リソースを変更するリクエストは共有秘密鍵ベースの HMAC/SHA256 署名で認証する
  • 本番環境ではポート80のHTTPサーバーとドメインのDNSレコードが必要で、ACMEを有効にすると証明書と秘密鍵ファイルを自動生成し、エラーは acme.log に記録される

BlogTechの目的と現在の状態

  • BlogTechは、小規模から中規模のWebサイト管理のためのツールキット
  • 対応機能は次のとおり
    • HTTPS

    • バーチャルホスト

      • ACMEによる自動証明書管理
      • サーバーのリモート管理のためのクライアント
      • まだテスト段階
      • バージョン 0.4.xhttps://coz.is/ を数週間にわたり正常に提供した実績がある
      • 以前の単一ファイル版は single_file ブランチにある

ビルドと基本実行

  • BlogTechはLinuxとWindowsで動作する
    • LinuxビルドにはOpenSSL開発ライブラリと gcc が必要
    • Windowsビルドには clang が必要
  • ビルドはプラットフォーム別スクリプトで行う
    • Linux: ./build.sh
    • Windows: .\build.bat
  • ビルド成果物は次のとおり
    • Linux: blogtech
    • Windows: blogtech.exe
  • Linuxでは ./install.sh でインストール可能
  • 基本的なサーバー実行例では docroot ディレクトリを作成し、--serve, --document-root=docroot, --skip-auth-check を指定する
  • 基本のHTTPサーバーは 127.0.0.1:8080 で待ち受け、docroot のコンテンツを提供する
  • 空の docroot 状態では http://127.0.0.1:8080/ にアクセスすると404が返る

リモートファイル管理と認証

  • BlogTechはクライアントモードでファイルをサーバーにアップロードできる
    • --put でファイルをアップロード
    • --get でリモートファイルをダウンロード
    • --delete でリモートファイルを削除
  • 同じマシン上でクライアントとサーバーを実行しても、リモートマシン上のサーバーに対しても同じように動作する
  • リソースを変更するリクエストは HMAC/SHA256 署名でデジタル署名される
  • クライアントとサーバーは同じ秘密鍵を共有する必要があり、慣例として admin.pwd ファイルに保存する
  • パスワードファイルは --auth-password-file オプションで指定する
  • パスワードファイルが存在しない場合、認証が必要なすべてのリクエストは拒否される
  • 空のパスワードも拒否される
  • 開発中は --skip-auth-check ですべてのリクエストを認証済みとして扱える
    • このオプションを使うと、--auth-password-file が指定されていても無視される
    • 注意して使う必要がある
  • 認証ヘッダーとリプレイ条件

    • 認証済みHTTPリクエストには X-BlogTech- ヘッダーが含まれる
    • X-BlogTech-Nonce: クライアントがランダムに選んだトークン
    • X-BlogTech-Timestamp: リクエストが最初に署名された時点のUNIXタイムスタンプ
    • X-BlogTech-Expire: 署名時点からリクエストが有効な秒数
    • X-BlogTech-Signature: リクエスト情報のHMACをBase64エンコードした値
    • 署名は正規化されたリクエスト文字列を作成したあと、認証パスワードを鍵として HMAC/SHA256 を計算し、その結果をパディング付きBase64でエンコードして得る
    • サーバーがすでに見た nonce はメモリに保存される
    • サーバーが再読み込みされると以前の nonce を忘れるため、期限切れでないリクエストは再送される可能性がある

バーチャルホストとディレクトリマッピング

  • BlogTechは同じサーバーで複数のWebサイトをホストできる
  • ドメインは --domain オプションを複数回指定して設定する
  • BlogTechはドキュメントルート内にドメイン別ディレクトリを作成する
    • default
    • websiteA.com
    • websiteB.com
  • 特定ホスト宛てのリクエストは、そのホストに対応するディレクトリを参照する
  • 特定フォルダに結び付けられていないリクエストは default ディレクトリを参照する
  • 例では websiteA.com, websiteB.com, other.com にアップロードしたファイルは、それぞれドメインディレクトリまたは default ディレクトリに保存される

HTTPSと証明書設定

  • HTTPSはLinuxでのみサポートされる
  • 基盤となるHTTPライブラリ cHTTP がOpenSSLでHTTPSを実装しているため
  • Windowsではクライアントとサーバーの両方でHTTPしか使えない
  • HTTPSを有効にするには次のオプションが必要
    • --https-enabled
    • --cert-file
    • --cert-key-file
  • デフォルトのHTTPS待受アドレスは 127.0.0.1:8443
  • 待受アドレスとポートは次のオプションで変更できる
    • --https-addr=<addr>
    • --https-port=<port>
  • 開発中はOpenSSLコマンドで自己署名証明書を作成できる
  • ブラウザは自己署名証明書を使うHTTPSサーバーの閲覧を許可しない
  • cURLでは --insecure フラグを使わないと自己署名証明書サーバーにアクセスできない
  • 複数の証明書が必要な場合は --extra-cert を追加で渡せる
    • 基本証明書は --cert-file, --cert-key-file で提供する
    • 追加証明書はクライアントが特定ドメインをリクエストしたときに使われる
  • 長いコマンドラインオプションは設定ファイルへ移せる

ACMEベースの自動証明書発行

  • ACME プロトコルは、Webサーバーが認証局に証明書発行を自動で要求できるようにする
  • BlogTechは、証明書なしでHTTPSモードを開始し、その後証明書が生成される流れをサポートする
  • 本番環境では次の条件が必要
    • HTTPサーバーをポート80で実行する
    • サーバーが動作するマシンとドメインを結び付けるDNSレコードを作成する
  • ACMEは --acme-enabled をHTTPSオプションと一緒に指定して有効にする
  • 通常のHTTPSモードと異なり、ACMEモードでは証明書が存在しないことを前提とする
  • 証明書生成には次の値が必要
    • --acme-domain
    • --acme-email
    • --acme-country
    • --acme-organization
    • --acme-agree-tos
  • 正常に処理されると次のファイルが生成される
    • acme_key.pem: ACMEアカウントに紐付いた秘密鍵
    • cert.pem: 発行された証明書、または --cert-file で指定したファイル
    • key.pem: 証明書に対応する秘密鍵、または --cert-key-file で指定したファイル
  • エラーが発生した場合、メッセージは acme.log に記録される
  • --acme-domain を複数回指定すると複数ドメインをACMEで処理できる
  • 結果の証明書には指定したすべてのドメインが含まれる

ACMEクライアントのテスト

  • LinuxでACMEクライアントをテストするには Docker をインストールし、Pebble ACME server をクローンする必要がある
  • docker-compose.yml にはテスト用ドメインを host-gateway に接続する extra_hosts 項目を追加する
  • /etc/hosts にはテスト用ドメインを 127.0.0.1 にマッピングする
  • Pebbleは docker compose up で起動する
  • BlogTechは ./blogtech -s --config=misc/pebble_blogtech.conf でテストインスタンスを実行する
  • ACMEサーバーとの相互作用は stdout に出力される
  • 成功すると acme_key.pem, cert.pem, key.pem が生成される
  • 証明書更新テストは pebble/test/config/pebble-config.jsonvalidityPeriod 値を変更して行える
  • --acme-force-renewal-period=<maximum duration in ms> オプションを使うと、証明書がまだ期限切れでなくても更新するよう指示できる

設定ファイルと自動ロード

  • BlogTechは任意個のコマンドライン引数を設定ファイルに移せる
  • たとえばHTTPSやACME関連オプションを blogtech_server.conf に書いたうえで --config=blogtech_server.conf で実行できる
  • 設定ファイル名が正確に blogtech.conf なら、BlogTechが自動でロードする
  • この場合 ./blogtech --serve のように実行できる
  • 暗黙の設定ファイル読み込みを無視するには --no-config を使う

クラッシュログとsystemdデーモン

  • サーバーモードでBlogTechがクラッシュすると crash.bin ファイルが生成される
  • 次回のサーバー起動時に crash.bin は人が読めるスタックトレースである crash.log に変換される
  • アドレスをシンボル名や行番号に変換する処理はやや不安定なことがある
  • BlogTechは systemdデーモン としてインストールできる
  • 例の blogtech.service/root/blogtech/blogtech -s を実行し、失敗時に再起動し、作業ディレクトリを /root/blogtech/ に設定する
  • サーバーオプションは /root/blogtech/blogtech.conf から自動ロードされる
  • サービスファイルを /etc/systemd/system/ にコピーしたあと、次のコマンドで有効化して起動する
    • systemctl daemon-reload
    • systemctl enable blogtech
    • systemctl start blogtech
  • サービス管理は systemctl start, systemctl stop, systemctl restart, journalctl -u blogtech で行う

1件のコメント

 
GN⁺ 2024-09-26
Hacker News の意見
  • リバースプロキシ不要」という部分はいつも不思議に思っていた
    リバースプロキシが特に役立つ理由がなければ、組み込み Jetty アプリを前段なしでインターネットに直接公開していたし、問題はなかった
    インフラやセキュリティ担当者は、なぜ前に nginx を置かないのかと聞いてくるが、理由を尋ねるとセキュリティや性能について漠然と言うだけで具体性に欠ける。最も具体的だった答えは slow loris だったが、それはもうずいぶん前から大きな問題ではなくなっている
    リバースプロキシは集団的にカーゴカルトになっているのか、それとも一般的に当てはまる良い理由を自分が見落としているのか気になる

    • 私にとってリバースプロキシは、オリジンサーバーにアプリケーションの提供だけをさせてくれる。TLS 終端、負荷分散、URL 書き換え、必要なら WAF のようなセキュリティ機能はすべてリバースプロキシで処理でき、役割分担になる
      全体としてオリジンサーバーを保護し、関連するトラフィックだけを受けるようにできる。顧客にカスタムドメインを提供する場合でも、顧客の DNS はオリジンサーバーではなくリバースプロキシを指すので、必要に応じてオリジンサーバーを変更しても顧客側の DNS 変更を心配しなくてよい
    • ホームラボで複数のサーバープログラムを動かしている
      それぞれ別のポートで動いているが、全部を別々の URL で公開アクセス可能にしたいし、インターネットには443 番ポートだけを公開したい
      各ドメインの TLS 証明書の自動更新もしたい。前者の要件にはリバースプロキシが必要で、Caddy は両方をやってくれる
      単一サーバーだけを運用し、そのサーバーが TLS 終端まで行うなら、リバースプロキシは必須ではない
    • ほとんどのデプロイでは、リバースプロキシの性能への影響は無視できる程度で、設定もあらかじめ用意している
      後からTLS 終端、URL 書き換え、その他の作業を大きな手間なく追加できるので、習慣のように使うようになり、今のところその習慣は報われている
    • Web アプリケーションの前に置かれるさまざまな種類のサーバーに当てはまる答えをするなら、セキュリティと性能の両面で追加の装置が役立つ方法はいくつかある
      セキュリティ面では、システム全体で可能な限りコードが少なく堅牢なオペレーティングシステムを使いたい。プロキシ型アプリは Web/アプリケーションサーバーよりはるかに単純にでき、受信トラフィックをフィルタリングしたり、入力を検証したり、より安全かつ高速にパースできる形式へ変換したりもできる。OpenBSD、GenodeOS、INTEGRITY-178B のような攻撃しにくいオペレーティングシステム上で動かすこともできる
      可用性の面でも、負荷分散、監視、復旧をこうしたシステムに置くほうが安全な場合が多い。アプリケーションサーバーのほうがより頻繁に落ちる可能性があるためだ
      性能面では、単純で集中したアプリを高度に最適化できるという利点がまずある。その後は CPU や PCI ハードウェアアクセラレーターで圧縮や暗号化を高速化でき、これはよくオフロードと呼ばれる。費用対効果が最も高い構成は、多数の汎用サーバーが少数の高価なオフロードサーバーの恩恵を受ける形だ。一部は負荷分散によって、受信トラフィックを最も適切に処理できるサーバーへ送り、高価なリソースの使用を減らす
    • すべてのサーバーに対する一般論があるとは思わない
      最小構成では不要だが、単一ホストの運用環境でもローリングリリース、圧縮、TLS、高速な静的ファイル配信、潜在的な A/B テストを可能にしてくれる
      リクエストとサーバーの間にある間接層はかなり有用になり得る
  • すばらしい。私も以前、直接 C の Web サーバーを書いたことがあり、下にソースがある。しばらく商用 Web サイトも運用していた
    HTTP/1.1 Web サーバーをどれほど小さく軽量に作れるかには驚く。その商用サイトは RAM 128MB、CPU 1 個のマシンで動いていて、クローズドソースの対話型 Web ベースのチャットシステムとして英国の学校のかなりの数に定期的にサービスを提供していた。ただし、それはインターネットが今ほど敵対的ではなかった 20 年前の話だ
    記事ではボットが優れたファザーの役割を果たすと言っていたが、それでも実際のファジングも少しはやってみるのがよいと思う
    http://git.annexia.org/?p=rws.git;a=tree
    必要なもの:
    http://git.annexia.org/?p=c2lib.git;a=tree
    http://git.annexia.org/?p=pthrlib.git;a=tree

    • このくらいの規模の Web サーバーを動かしつつ、敵対的なインターネットをあまり心配したくないなら、Rust は良い選択だ
      私の Web サイト https://blessed.rs は見つけられる中で最小の VM なので RAM 256MB のもの上で動いているが、普段は約 60MB しか使っていない
    • これは私が作った小さく軽量な HTTP/1.0 Web サーバーよりずっと実用的に見えるが、rws がそれほど小さく軽量だとは思えない: http://canonical.org/~kragen/sw/dev3/server.s http://canonical.org/~kragen/sw/dev3/httpdito-readme
      本当に驚いたのは、メモリマップが 4KB ページ 5 枚だけだと Linux の fork が非常に速くなることだった
  • 趣味で余暇に始めた小さなプロジェクトだが、ここなら気に入ってもらえそうだと思った :)

    • 古い HTTP サーバーのバグや CVE をざっと見て、自分のコードにも影響し得るのか、どう直せるのかを考えるのは興味深い練習になる。こういうものを自分で作ってみるのは楽しい
    • ちょうど C11 の並行処理 API を触ってみたいと思っていて、C++ から来た立場として、その構造が C でどう動作するのか気になったので、サーバーのようなものを書いてみたかった
  • すごい。こういうふうに最も低いレベルのシステム APIを使って最小限のサービスを立ち上げるのは本当に満足感があると昔から思っていたし、今もそう見ている。
    ほとんど魔法みたいで、実際のトラフィックをさばいているのを見るとなおさらだ。普通の poll() があれほどの数値を出せるのは少し驚きだったが、そのレベルでイベントやベンチマークを扱ってからずいぶん経っているからかもしれない。
    接続ごとの関数と関連する構造体、配列で接続を管理するやり方、poll ファイルディスクリプタ配列も良い。nginx、Redis、memcached のように高スループットで知られるいくつものオープンソースパッケージで採られている方式を思い出す。

    • 大学で C/C++ を扱っていたときは頭が破裂しそうだった。工学、歴史、文化、言語学など、自分の好きなものが少しずつ全部入っている、とても特殊で謙虚な気持ちにさせられる経験だった。
      誰もが、可能な限りあらゆる言語――プログラミング言語であれ自然言語であれ――を知り、試してみるべきだと思うようになった。ある言語で考えるというのは独特の体験だ。異なる文脈はすべてを違って感じさせ、結局は似たようなことをしていても視点が変わる。
      たとえば Linux や Git の本質を本当に理解するには、その言語を話し、翻訳ではたいてい失われるニュアンスを理解しなければならない。ロシア語で「森」という単語の本当の主観的な意味を理解するには、ロシア語を話し理解する必要があるのと似ている。
      文脈は視点を変え、時にはすべてを変える。
    • 完全に同意する。そして実際に使ってみるとさらに満足感がある。今ではメールプロトコルにも興味が湧き始めている。
      私も普通の poll() があれほど耐えるのには驚いた。いつかは epoll に移行することになると思っていたが、poll() はとてもよく動いている。
    • 人々は忘れているようだが、優れた格好いい抽象化も、核心では結局同じことをしている。ソケットを開いて、読み、書くのだ。
      日の下に新しきものなし。
  • これも興味深いかもしれない: https://news.ycombinator.com/item?id=27431910
    2024年時点で sqlite.org の althttpd インスタンスは、1日50万件以上の HTTP リクエスト、秒間約5〜6件を処理し、1日約200GBのコンテンツ、約18Mbit/sを月40ドルの Linode で提供している。このマシンのロードアベレージは通常 0.5 付近にとどまっている。HTTP リクエストの約19%は、複数の Fossil ソースコードリポジトリに向かう CGI である。

    • この記事には大いに刺激を受けた。こういうことが実際に可能だと気づかせてくれた。
  • C アプリを書きたいが、公開インターネットに直接触れる部分を自分で書くのは不安なら、Kore は良いフレームワークだ。
    ACME 証明書管理、Pgsql、curl、WebSocket などの便利な組み込み機能がある。
    基本的にモジュールをビルドして実行し、組み合わせることができ、Lua/Python と C を混ぜることも可能だ。
    https://kore.io/

  • ついにトップページに載っても死なないウェブサイトが出てきた。

    • 前段に CDN があれば、どんなサイトでもそうできる。
      このプロジェクトが素晴らしくないという意味ではないが、運用環境でこういう点を本当に重視し、ほとんど静的コンテンツを配信するなら、単に CDN を使えばいい。自分で書くほぼ何よりも、常に高い性能を出すはずだ。ただ、面白くないだけだ。
    • リンク先は GitHub ではないのか? このコメントは少し混乱する。
      プロジェクトはすっきりしていて素晴らしいが、ほとんどの人は GitHub に行くのであって、ウェブページを表示するリンクには行かない気がする。何か見落としているのだろうか?
  • 「自分の道具を自分で作るのが好きだし、すべてが実戦検証済みでなければならないと言われるのに少し疲れた。だから落ちたら何だというのか? バグは直せばいい :^)」という部分が好きだ。

  • C コード3.4k 行だけで完全な HTTP と HTTPS サーバーだって? 仕様に完全準拠するには、正直もっとずっと多く必要だと思っていた。

    • HTTP/1.1 は、仕様の大半を無視すれば非常に単純だ。
      GET リクエストだけを受け取り、レスポンスに Content-Length を設定すれば、ユーザーエージェントの99%には十分だ。Transfer-Encoding とバイト範囲ヘッダーを扱っても、コードは大して増えない。
      HTTPS は TLS ソケット上の HTTP にすぎず、自分で暗号を実装しないなら、まさにその程度の抽象化レベルであるべきだ。面白いし、実際それほど悪くない。
    • このくらいなら妥当に見える。OpenBSD の httpd(8) [1] も、ドキュメント込みで現在15,000行弱だ。
      いくつかの機能を外し、いくつかの仮定を置けば、このプロジェクトのように5,000行台に収まっても驚かない。
      $ wc -l * の結果は合計14815行。
      [1]: https://man.openbsd.org/httpd.8
    • 自分の実験の一つで、データ収集のライブビューを提供するために簡単な組み込み C Web サーバーを使ったが、250行未満だった。
      もちろん公開インターネットには載せないし、HTTP/1.1 のごく小さな部分しか実装していないが、動作し、初期化時にだけ malloc が必要だ。
    • この規模の HTTP/1.1 サーバーはほかにもいくつかある: https://www.acme.com/software/thttpd/benchmarks.html
  • C で書かれたブログ/Web サーバーに関する Chaos Communication Congress の発表を思い出す。
    イミュータブルストレージ、権限降格、ブログが TLS 証明書にアクセスできないようにするなど、多くのセキュリティ機能を盛り込んだ内容だった: https://www.youtube.com/watch?v=TaE28fJVPTk