果報

二度寝して待つ

AutheliaとLLDAPで認証サーバを構築する

自サーバ(サービス)をいくつか運用していたら、一度は「認証サーバを自前で構築してみたい」と思いませんか。思いますよね。

今回は、AutheliaLLDAPを組み合わせて、自分だけの認証サーバを構築する手順を紹介します。最終的には、一度ログインすれば複数のサービスをそのまま利用できるシングルサインオン(SSO)の実現を目指します。

環境構築にはDockerを利用するため、手順の再現性が高く、メンテナンスも簡単です。

はじめに(Autheliaとは)

Autheliaは、モダンで高機能な認証・認可プロキシです。主な特徴は以下の通りです。

  • 簡単な設定:設定はすべてYAMLファイルで行うため、可読性が高く管理が容易
  • 多機能:多要素認証(MFA)、シングルサインオン(SSO)、パスワードリセットなどの機能を標準搭載
  • 高い連携性:OpenID Connect (OIDC) プロバイダとして機能するため、WordPressやNextcloudなど、多くのアプリケーションと簡単に連携可能
  • 軽量:Dockerコンテナとして軽快に動作

今回は、このAutheliaを認証の中核に据え、ユーザ管理を軽量LDAPサーバであるLLDAPに任せる構成で進めます。

システム構成

まず、今回構築するシステムの全体像と、それぞれのコンポーネントの役割を整理します。

  • Authelia(認証・認可)
    • ログイン画面を提供し、ユーザ認証(ID/パスワード、多要素認証)を行います
    • OIDCプロバイダとして、他のサービスに認証機能を提供します
  • LLDAP(ユーザ管理)
    • ユーザ情報(ID、パスワード、メールアドレスなど)を一元管理する軽量なLDAPサーバで、Web UIで直感的に操作できます
  • Redis(セッション管理)
    • ログイン状態などのセッション情報を保存するインメモリデータベースで、これにより、Autheliaコンポーザが再起動してもユーザはログイン状態を維持できます
  • Caddy(リバースプロキシ)
    • 外部からのリクエストを受け付け、各サービスへ振り分けるWebサーバです
    • Autheliaと連携し、保護対象のサービスへのアクセス時に認証を強制します

なお、Authelia、LLDAP、RedisはDockerコンテナを使用しますが、Caddyはローカルにインストールして使用します。

Dockerによる環境構築

作業を始める前に、設定ファイルを格納するディレクトリを作成します。

$ mkdir -p ~/oidc/authelia
$ mkdir -p ~/oidc/lldap
$ mkdir -p ~/oidc/redis

Authelia、LLDAP、RedisをDockerコンテナとして起動するため、以下のcompose.ymlを作成します。

$ nano ~/oidc/compose.yml
services:
  authelia:
    image: authelia/authelia:latest
    container_name: authelia
    restart: always
    volumes:
      - ./authelia:/config
    ports:
      - 9091:9091
    environment:
      - TZ=Asia/Tokyo

  lldap:
    image: lldap/lldap:stable
    container_name: lldap
    restart: always
    ports:
      - 3890:3890 #LDAP用
      - 6360:6360  #LDAPS用(今回未使用)
      - 17170:17170 #Web UI用
    volumes:
       - ./lldap:/data
    environment:
      - LLDAP_VERBOSE=true #ログレベルをdebugに設定
      - LLDAP_JWT_SECRET=YOUR_RANDOM_SECRET_HERE # 32文字以上のランダムな文字列に変更
      - LLDAP_KEY_SEED=YOUR_RANDOM_KEY_SEED_HERE # 32文字以上のランダムな文字列に変更
      - LLDAP_LDAP_BASE_DN=dc=ldap,dc=yusukesakai,dc=com #ホストをldap.yusukesakai.comに指定

  redis:
    image: redis:7-alpine #サイズとか気にしないならlatestでいいかも
    container_name: redis
    restart: always
    ports:
      - 6379:6379
    volumes:
      - ./redis:/data
    command: redis-server --appendonly yes

LLDAP_JWT_SECRETLLDAP_KEY_SEEDには、必ずランダムで複雑な文字列を設定してください。また、LLDAP_LDAP_BASE_DNは、自身の環境に合わせたドメイン名(例: dc=mydomain,dc=net)に変更します。

Autheliaの設定

configuration.ymlの全体

次に、Autheliaの動作を定義するconfiguration.ymlを作成します。ここでは主要な設定項目を抜粋して解説します。

$ nano ~/oidc/authelia/configuration.yml

公式のサンプルも参考にしつつ、最終的に以下のような内容になりました。

---
###############################################################
#                   Authelia configuration                    #
###############################################################

# Authelia全体の基本設定
theme: 'dark'

server:
  address: 'tcp://:9091'
  endpoints:
    authz:
      forward-auth:
        implementation: 'ForwardAuth'

log:
  level: 'debug'

# パスワードリセット機能のJWTシークレット
identity_validation:
  reset_password:
    jwt_secret: 'a_very_important_secret' #ランダムな文字列に変更

# ユーザーデータベース(LDAP)への接続設定
authentication_backend:
  ldap:
    implementation: 'custom'
    address: 'ldap://lldap:3890'
    timeout: '5s'
    start_tls: false
    base_dn: 'dc=ldap,dc=yusukesakai,dc=com'  #compose.ymlで設定したものと合わせる
    users_filter: '(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))'
    additional_users_dn: 'ou=people'
    additional_groups_dn: 'ou=groups'
    attributes:
      username: 'uid'
      display_name: 'displayName'
      mail: 'mail'
      group_name: 'cn'
      member_of: 'memberOf'
      distinguished_name: 'distinguishedName'
    groups_filter: '(member={dn})'
    user: 'uid=authelia,ou=people,dc=ldap,dc=yusukesakai,dc=com' #LLDAP連携用のサービスアカウント
    password: 'XXXXXXXX' #LLDAPで設定するパスワード

# アクセス制御ポリシー(必要に応じて追加)
access_control:
  default_policy: 'deny' #デフォルトは全アクセス拒否
  rules:
    # Authelia自身のダッシュボードへのアクセスポリシー
    - domain: 'auth.yusukesakai.com'
      policy: 'two_factor'
    # LLDAPのWeb UIへのアクセスポリシー
    - domain: 'ldap.yusukesakai.com'
      policy: 'two_factor'

# セッション管理の設定
session:
  secret: 'insecure_session_secret' #ランダムな文字列に変更
  redis:
    host: redis #Docker Composeで定義したサービス名
    port: 6379
    database_index: 0
  cookies:
    - name: 'authelia_session'
      domain: 'yusukesakai.com' #保護対象のルートドメイン
      authelia_url: 'https://auth.yusukesakai.com'
      expiration: '1h'
      inactivity: '5m'
      remember_me: '1y' #「記憶する」の有効期間

# ブルートフォース攻撃対策
regulation:
  max_retries: 3
  find_time: '2m'
  ban_time: '5m'

storage:
  encryption_key: 'you_must_generate_a_random_string_of_more_than_twenty_chars_and_configure_this'
  local:
    path: '/config/db.sqlite3'

notifier:
  disable_startup_check: false
  smtp:
    address: 'smtp://smtp.mailgun.org:587'
    timeout: '5s'
    username: 'authelia@yusukesakai.com'
    password: 'XXXXXXXX'
    sender: "Authelia <authelia@yusukesakai.com>"
    identifier: 'localhost'
    subject: "[Authelia] {title}"

# OIDCプロバイダーとしての設定
identity_providers:
  oidc:
    jwks:
      - key_id: 'authelia'
        algorithm: 'RS256'
        use: 'sig'
        key: |
          -----BEGIN PRIVATE KEY-----
          MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYexbHPYx40sfd
          ................................................................
          zHSOfYxjUCJsq8ZaUK+wmP0=
          -----END PRIVATE KEY-----
    clients:
      - client_id: 'wordpress'
        client_name: 'WordPress'
        client_secret: '$pbkdf2-sha512............'
        public: false
        authorization_policy: 'two_factor'
        redirect_uris:
          - 'https://past.yusukesakai.com/wp-admin/admin-ajax.php?action=openid-connect-authorize'
        scopes:
          - 'openid'
          - 'profile'
          - 'email'
          - 'groups'
        userinfo_signed_response_alg: 'none'
        token_endpoint_auth_method: 'client_secret_post'
configuration.ymlの詳細

configuration.ymlの内容についていくつか補足します。

  endpoints:
    authz:
      forward-auth:
        implementation: 'ForwardAuth'

SSOに対応していないサービスをCaddyでリバースプロキシしてAutheliaに転送するための設定です。デフォルトのようなので不要かもしれませんが、念のため設定しています。

authentication_backend:
  ldap:
    implementation: 'custom'
    address: 'ldap://lldap:3890'
    timeout: '5s'
    start_tls: false
    base_dn: 'dc=ldap,dc=yusukesakai,dc=com'
    users_filter: '(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))'

このあたりからLLDAPの設定を行います。users_filterで認証時にユーザ名だけでなくメールアドレスでもログインできるように設定しています。

    user: 'uid=authelia,ou=people,dc=ldap,dc=yusukesakai,dc=com'
    password: 'XXXXXXXX'

LLDAP上でautheliaというユーザを作成し、Autheliaとの連携に使用します。

access_control:
  default_policy: 'deny'
  rules:
    - domain: 'auth.yusukesakai.com'
      policy: 'two_factor'
    - domain: 'ldap.yusukesakai.com'
      policy: 'two_factor'

後述するidentity_providersでは定義しませんが、Autheliaで認証したいドメインを列挙します。Authelia本体や、Caddyで転送するサービスなどを指定します。

notifier:
  disable_startup_check: false
  smtp:
    address: 'smtp://smtp.mailgun.org:587'
    timeout: '5s'
    username: 'authelia@yusukesakai.com'
    password: 'XXXXXXXX'
    sender: "Authelia <authelia@yusukesakai.com>"
    identifier: 'localhost'
    subject: "[Authelia] {title}"

メール通知する際に使用するメールサーバを指定します。私は既にアカウントがあったのでMailgunを使いましたが、Gmailのサーバなども無料で使えます。

identity_providers:
  oidc:
    jwks:
      - key_id: 'authelia'
        algorithm: 'RS256'
        use: 'sig'
        key: |
          -----BEGIN PRIVATE KEY-----
          MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYexbHPYx40sfd
          ................................................................
          zHSOfYxjUCJsq8ZaUK+wmP0=
          -----END PRIVATE KEY-----

OpenID Connect(OIDC)プロバイダとしての設定です。jwks(JSON Web Key Set)セクションでは、JWTトークンの署名に使用する秘密鍵を設定します。この秘密鍵は以下のコマンドで生成できます。

$ docker run --rm authelia/authelia:latest authelia crypto pair generate rsa --bits 2048
    clients:
      - client_id: 'wordpress'
        client_name: 'WordPress'
        client_secret: '$pbkdf2-sha512............'
        public: false
        authorization_policy: 'two_factor'
        redirect_uris:
          - 'https://past.yusukesakai.com/wp-admin/admin-ajax.php?action=openid-connect-authorize'
        scopes:
          - 'openid'
          - 'profile'
          - 'email'
          - 'groups'
        userinfo_signed_response_alg: 'none'
        token_endpoint_auth_method: 'client_secret_post'

clientsセクションでは、認証を利用するアプリケーションごとの設定を行います。各クライアントには一意のclient_idclient_secretが必要で、client_secretのハッシュ値は以下のコマンドで生成します。your_secret_hereは任意の文字列に変更してください。

$ docker run --rm authelia/authelia:latest authelia crypto hash generate pbkdf2 --variant sha512 --password your_secret_here

redirect_urisには認証後のリダイレクト先URLを指定し、scopesでは取得できるユーザー情報の範囲を定義します。authorization_policyでは認証レベル(one_factortwo_factorなど)を設定できます。

LLDAPのセットアップ

LLDAPは軽量なLDAPサーバ実装で、ユーザ管理を簡単にするためのWebベースの管理UIを提供します。従来のLDAPサーバと比べて設定が簡素で、コンテナでの運用に適しています。

LLDAPは基本的に環境変数での設定となります。compose.ymlで設定した環境変数により、ベースDNやシークレットが設定されます。初回起動後はhttp://localhost:17170からWeb UIにアクセスしてユーザやグループの管理を行います。

その他、詳細な設定については公式ドキュメントを参照してください。

Redisのセットアップ

Redisはセッション維持のために使用します。Autheliaがステートレスに動作するため、ユーザのセッション情報をRedisに保存することで、コンテナの再起動後もログイン状態を維持できます。

今回は永続化設定(--appendonly yes)を有効にして、データの永続化(セッション情報のディスク保存)を行っています。特別な設定は不要で、標準的なRedisコンテナとして動作します。

Caddyによるリバースプロキシ設定

Caddyは、リクエストを適切なサービスに振り分けるリバースプロキシです。また、forward_auth機能を使って、特定のサービスへのアクセスをAutheliaに認証させる役割も担います。

/etc/caddy/Caddyfileを以下のように設定します。

# Let's Encryptでの証明書取得用
{
        email mail@yusukesakai.com
}

# Authelia自身のダッシュボード
auth.yusukesakai.com {
        reverse_proxy localhost:9091
}

# Authelia認証対象サービス(例:LLDAPのWeb UI)
ldap.yusukesakai.com {
        forward_auth localhost:9091 {
                uri /api/authz/forward-auth
                copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
        }
        reverse_proxy localhost:17170
}

この設定により、ldap.yusukesakai.comにアクセスすると、まずAutheliaのログイン画面にリダイレクトされ、認証を通過したユーザだけがLLDAPの管理画面にアクセスできるようになります。また、同様の方法で他のSSO未対応のサービスを追加できます。

利用開始までのステップ

すべての設定ファイルが準備できたら、サービスを起動して初期設定を行います。

サービスの起動

compose.ymlがあるディレクトリで、以下のコマンドを実行します。

$ docker compose up -d

LLDAPでのユーザ作成

  1. ブラウザでhttp://localhost:17170またはCaddyで設定したドメイン(今回の場合はhttps://ldap.yusukesakai.com)にアクセスします
  2. 初回アクセス時は、管理者パスワードの設定が求められます
  3. ログイン後、以下の2種類のユーザを作成します
    • Authelia連携用ユーザ:configuration.ymlで指定したユーザ(例:uid=authelia)を作成し、パスワードを設定します
    • ログイン用ユーザ:あなた自身がシングルサインオンで利用するユーザを作成します

二段階認証の設定

  1. ブラウザでAutheliaのダッシュボード(今回の場合はhttps://auth.yusukesakai.com)にアクセスします
  2. LLDAPで作成した自分のユーザでログインをします
  3. 初回ログイン時には、二段階認証(TOTP)の設定を求められます。Google Authenticatorなどの認証アプリでQRコードをスキャンし、登録を完了してください

これで、認証サーバの基本設定は完了です。なお、ログイン時に「記憶する」にチェックを入れると、同じデバイスからは1年間認証なしでアクセスできるようになります。これはconfiguration.ymlremember_me: '1y'設定によるものです。セキュリティと利便性のバランスを考慮して設定期間を調整してください。

応用例:WordPressとの連携

AutheliaのOIDC機能を使えば、既存のWordPressサイトにSSOを導入できます。

Autheliaの設定

configuration.ymlidentity_providers.oidc.clientsにWordPress用の設定を追記します。client_id, client_secret, redirect_urisは自身の環境に合わせて設定してください。

identity_providers:
  oidc:
    clients:
      - client_id: 'wordpress'
        client_name: 'WordPress'
        client_secret: '$pbkdf2-sha512............'
        public: false
        authorization_policy: 'two_factor'
        redirect_uris:
          - 'https://past.yusukesakai.com/wp-admin/admin-ajax.php?action=openid-connect-authorize'
        scopes:
          - 'openid'
          - 'profile'
          - 'email'
          - 'groups'
        userinfo_signed_response_alg: 'none'
        token_endpoint_auth_method: 'client_secret_post'

WordPressの設定

  • 「OpenID Connect Generic」プラグインをインストールして有効化します
  • プラグインの設定画面で、Autheliaの情報を入力します
    • Client ID:wordpress
    • Client Secret: 設定したシークレット(your_secret_hereで指定したハッシュ化する前の文字列)
    • Login Button Text:Autheliaでログインなど
    • Discovery Endpoint:https://auth.yusukesakai.com/.well-known/openid-configuration

設定が完了すると、WordPressのログイン画面に「Autheliaでログイン」ボタンが表示され、SSOが可能になります。

その他、詳細な設定手順は公式ガイドを参照してください。

運用とメンテナンス

設定変更時の再起動

configuration.ymlを変更した場合は、以下のコマンドでAutheliaコンテナを再起動する必要があります。

$ docker compose restart authelia

イメージのアップデート

各コンテナのイメージを最新版にアップデートする場合は以下のコマンドを実行します。

$ docker compose pull
$ docker compose up -d

ログの確認

問題が発生した場合は、各コンテナのログを確認します。

$ docker compose logs authelia
$ docker compose logs lldap

参考リンク