午前2時のアラート音
深夜2時ちょうど、デスクの上のスマートフォンが激しく振動しました。Grafanaからのアラートが止まりません。内部ダッシュボードの管理画面に対して、数万件の不審なリクエストが殺到していました。ログを確認すると、海外のIPアドレスから毎時50,000リクエスト以上のペースで、管理者アカウントへのブルートフォース(総当たり)攻撃が執拗に行われていました。
パスワードは複雑に設定してありましたが、攻撃者が家のドアに次々と鍵を差し込んで試しているのを目の当たりにするのは、決して気分の良いものではありません。これまで10以上のサーバーシステムのセキュリティ監査を行ってきた中で、私はある致命的な脆弱性に気づきました。それは「断片化」です。Nextcloud、Portainer、Grafanaといった各アプリケーションが個別のユーザー名とパスワードを使用しており、2要素認証(2FA)に対応しているものもあれば、そうでないものもありました。たった一つの弱いリンクが突破されるだけで、インフラ全体がハッカーの脅威にさらされてしまうのです。
問題の本質はどこにあるのか?
問題はパスワードの長さだけではありません. 多くのサービスをセルフホスト(自前運用)している場合、アクセス権限の管理はすぐに悪夢へと変わります:
- バラバラな管理: アプリごとにログイン方法が異なると、誰が何にアクセスしているかを把握するのが困難になります。
- 退職時のリスク: メンバーがチームを離れる際、すべてのアプリにログインしてアカウントを削除する必要があります。一箇所でも忘れると、それが致命的なバックドアになります。
- 古いアプリの脆弱性: 多くのオープンソースツールは、いまだに2要素認証をサポートしていません。これらはパスワードリスト攻撃の格好の標的となります。
検討したものの「不採用」となった案
Autheliaの採用を決める前に、いくつかの方法を検討しましたが、それぞれに不便な点がありました:
- VPN(WireGuard/OpenVPN)の利用: 安全性は高いですが、非常に面倒です。スマホでダッシュボードをさっと確認したいだけなのに、毎回VPNをオンにして接続を待つ必要があります。
- Nginx Basic Auth: あまりにも原始的です。パスワード変更画面も2FAもなく、チームの人数が増えると管理が極めて困難になります。
- Cloudflare Access: 非常に優れていますが、外部のクラウドサービスに依存することになります。機密データを完全にオンプレミスで保持したい場合、最優先の選択肢にはなりませんでした。
Authelia – 強力なSSOと2FAの砦
Autheliaは、アプリケーションの前に立つ一元化された認証「ゲートウェイ」として機能します。Single Sign-On (SSO) と多要素認証(TOTP、Duo、U2F)を提供します。簡単に言えば、ユーザーが直接アプリにアクセスする代わりに、NginxがユーザーをAutheliaにリダイレクトして、指紋、パスワード、OTPコードを確認します。認証が有効な場合にのみ、ドアが開く仕組みです。
ステップ1:環境の準備
Dockerがインストールされていることを前提とします。一般的なディレクトリ構造は以下の通りです:
/opt/authelia/
├── docker-compose.yml
└── config/
├── configuration.yml
└── users.yml
ステップ2:Docker Composeでの起動
このファイルでは、Autheliaとセッション保存用のRedisデータベースを起動します。Redisを使用することで、コンテナを再起動してもユーザーが何度もログインし直す必要がなくなり、スムーズな体験が得られます。
version: '3.8'
services:
authelia:
container_name: authelia
image: authelia/authelia:latest
volumes:
- ./config:/config
networks:
- proxy
environment:
- TZ=Asia/Tokyo
restart: unless-stopped
redis:
container_name: authelia-redis
image: redis:alpine
networks:
- proxy
restart: unless-stopped
networks:
proxy:
external: true
ステップ3:システムの核心設定 – configuration.yml
ここではすべての運用ルールを設定します。openssl rand -hex 64 コマンドを使用して、ランダムなシークレット文字列を生成する必要があります。
server:
host: 0.0.0.0
port: 9091
authentication_backend:
file:
path: /config/users.yml
access_control:
default_policy: deny
rules:
- domain: "*.yourdomain.com"
policy: two_factor
session:
name: authelia_session
domain: yourdomain.com
expiration: 3600
inactivity: 900
redis:
host: authelia-redis
port: 6379
notifier:
filesystem:
filename: /config/notification.txt
ステップ4:ユーザーの設定
users.yml ファイル内では、パスワードを暗号化する必要があります。オンラインツールを使用するか、docker run --rm authelia/authelia:latest authelia hash-password "your_password" コマンドを実行して、Argon2idハッシュ文字列を取得してください。
users:
admin:
displayname: "Administrator"
password: "$argon2id$v=19$m=65536,t=3,p=4$your_hashed_password"
email: [email protected]
groups:
- admins
ステップ5:リバースプロキシ(Nginx)との連携
Nginx Proxy Managerを使用している場合は、保護したいアプリケーションの Advanced Custom Configuration セクションに以下のコードを貼り付けます:
auth_request /authelia;
auth_request_set $target_url $scheme://$http_host$request_uri;
auth_request_set $user $upstream_http_remote_user;
proxy_set_header Remote-User $user;
# 未認証の場合、ログインページへリダイレクト
error_page 401 =302 https://auth.yourdomain.com/?rd=$target_url;
実践的なアドバイス:締め出しを食らわないために
実際の導入で何度か苦い経験をした結果、以下の3つの重要なポイントを導き出しました:
- 時刻同期: ワンタイムパスワード(TOTP)はリアルタイムの時刻に基づいています。サーバーとスマートフォンの時刻が30秒以上ずれていると、正しいコードを入力してもアクセスを拒否されます。
chronyなどをインストールして常に時刻を同期させておきましょう。 - HTTPSは必須: Autheliaと現代のブラウザは、安全でないHTTP接続経由での2FA実行を許可しません。
- Cookieドメイン: セッションドメインが
yourdomain.com(ルートドメイン)に設定されていることを確認してください。これにより、auth.yourdomain.comでログインした際に、grafana.yourdomain.comなどの他のサブドメインでもログイン状態が認識されるようになります。
Autheliaを導入してから、ぐっすり眠れるようになりました。攻撃者がパスワードを突き止めたとしても、私のポケットの中にあるOTPコードまでは盗めません。ユーザー管理も一つのファイルに集約され、安全かつプロフェッショナルな環境が整いました。
