HEARTBEATS

こんにちは、滝澤です。

Apache HTTP ServerをOpenID Connect Relying Partyにするmod_auth_openidcというモジュールを使ってみる機会がありましたので、本記事で情報共有します。

なお、記事が長くなったので本編と設定例である3編に分けました。

本記事では、複数のOP(OpenID Provider)として、Google(G Suite)とAzure Active Directory(以降、Azure ADと略す)を利用する場合の設定例を紹介します。

設定例

ウェブサイトURI

まず、ウェブサイトURIとリダイレクトURIを決めます。

ウェブサイトURIには、利用するウェブサイトのURIを指定します。このとき、パス名を付けない形式にしてください。

リダイレクトURIには、リダイレクトURI(redirect_uri)として使用されるURIを指定します。コンテンツが存在するURIに被らないものにしてください。このリダイレクトURI経由でリクエストのやりとりが行われます。

ウェブサイトURIhttps://example.com
リダイレクトURIhttps://example.com/redirect_uri

アプリケーションの登録とクライアントIDとクライアントシークレットの取得

Google (G Suite)の場合

次のヘルプのページに従って、アプリケーションの登録とクライアントIDとクライアントシークレットを取得してください。

このとき、以下のような値を指定します。

アプリケーションの種類ウェブアプリケーション
承認済みの JavaScript 生成元先の「ウェブサイトURI」で指定した値(例: https://example.com)
承認済みのリダイレクト URI先の「リダイレクトURI」で指定した値(例: https://example.com/redirect_uri)

Azure ADの場合

次のヘルプのページに従って、アプリケーションの登録を行います。

このとき、以下のような値を指定します。

アプリケーションの種類Web アプリ/API
サインオン URL先の「ウェブサイトURI」で指定した値(例: https://example.com)
応答 URL先の「リダイレクトURI」で指定した値(例: https://example.com/redirect_uri)

ここで、「プロパティ」のブレードで表示される「アプリケーション ID」がクライアントIDになります。

さらに、「APIアクセス」の「キー」をクリックすると、「キー」のブレードが表示されるので、ここで「説明」と「有効期限」を入力して保存すると値が表示されます。これがクライアントシークレットになります。

設定ファイルの編集

設定ファイル /etc/httpd/conf.d/auth_openidc.conf を編集して、各ディレクティブを設定します。

以降、ディレクティブ毎に説明します。ただし、デフォルト値で特に問題ないと思われるものについては説明はしません。

OIDCRedirectURI

リダイレクトURI(request_uri)として指定した値を記述します。

OIDCRedirectURI https://example.com/redirect_uri

OIDCCryptoPassphrase

cookieやキャッシュエントリーなどの暗号化に利用するパスフレーズの文字列を指定します。

OIDCCryptoPassphrase XXXXXXXXXXXXXXXXXXXXXXXX

OIDCMetadataDir

OIDCMetadataDirにメタデータ用のディレクトリを指定します。このディレクトリはapacheの実行権限でファイルの読み書きができる必要があります。

OIDCMetadataDir /var/cache/httpd/mod_auth_openidc/metadata

OIDCProviderMetadataURL

OIDCProviderMetadataURLの指定があったら、コメントアウトして無効にします。

OIDCAuthRequestParams

OP毎に異なることがあるので、コメントアウトされていなければコメントアウトしてください。

OIDCSessionInactivityTimeout

無操作タイムアウトの秒数を指定します。デフォルトは300秒です。

OIDCSessionInactivityTimeout 300

OIDCCacheType

キャッシュのストレージのタイプを指定します。

本記事の動作確認ではRedisを使うので、"redis"を指定します。

OIDCCacheType redis

さらに、関連するディレクティブの設定も行ってください。

例えば、redisを指定した場合は下記のようにOIDCRedisCacheServerとOIDCRedisCachePasswordの指定も行う必要があります。

OIDCRedisCacheServer 127.0.0.1
#OIDCRedisCachePassword 8dTjsdkngM4Y2okg3XjYgWRX

OIDCHTMLErrorTemplate

エラーページのテンプレートファイルのパスを指定します。

OIDCHTMLErrorTemplate /etc/httpd/conf.d/auth_openidc.error.html

上記の設定を行った場合は、ファイル"/etc/httpd/conf.d/auth_openidc.error.html"に次のような内容の記述を行ってください。1つ目の%sにはエラーメッセージが、2つ目の%sにはエラーの説明が埋め込まれます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8"/>
<title>OpenID Connect: Error</title>
</head>
<body>
<h1>OpenID Connect: Error</h1>
<p>Message: %s</p>
<p>Description: %s</p>
<p><a href="/">Home</a></p>
</body>
</html>

OIDCDefaultLoggedOutURL

ログアウト後に遷移するURIを指定します。

OIDCDefaultLoggedOutURL https://example.com/loggedout.html

上記のように設定した場合は、次のよう内容のファイルloggedout.htmlをドキュメントルートに配置します。 再びログインできるようにトップページへにリンクを用意します。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8"/>
<title>OpenID Connect: Logged out</title>
</head>
<body>
<h1>OpenID Connect: Logged out</h1>
<p><a href="/">Login</a></p>
</body>
</html>

OIDCClaimPrefix

claimを環境変数やHTTPヘッダーフィールドにする際に付与するプレフィックスを指定します。

OIDCClaimPrefix OIDC_CLAIM_

例えば、claimの一つ"name"は環境変数あるいはHTTPヘッダーフィールドの名前としては"OIDC_CLAIM_name"になります。

リバースプロキシーとして動かしている場合は、バックエンド側ウェブサーバー側でHTTPヘッダーを環境変数に変換する際の"-"と"_"の曖昧性を排除するために、次のように"_"を使わないようにした方がよいと思われます。

OIDCClaimPrefix OIDC-CLAIM-

OIDCRemoteUserClaim

apacheの認証されたユーザーIDを示す変数REMOTE_USERとして設定するclaimの名前を指定します。

デフォルトでは「sub@iss」の形式になります。 subはエンドユーザーの識別子で、issはIssuerから"https://"を除いたものになります。

複数のOPで共通で利用でき、ユーザーを識別可能なclaimを指定してください。

emailが使えれば次のようにしてもよいでしょう。

OIDCRemoteUserClaim email

共通で利用できるclaimがなければ、コメントアウトしてデフォルトの形式(sub@issの形式)に任せてもよいと思います。

なお、GoogleとAzure ADでは共通で利用できるclaimにはsub, name, family_name, given_nameくらいしかなく、ユーザーを識別可能なものはsubかnameしかありません。同姓同名が存在しなければnameは使えますが、そうでなければコメントアウトしてデフォルトの形式にしてもよいでしょう。

OIDCPassClaimsAs

claimをアプリケーション環境にどのようにして渡すかを指定します。

  • none
  • environment (環境変数)
  • headers (HTTPヘッダー)
  • both (両方) (default)
OIDCPassClaimsAs both

OIDCAuthNHeader

リバースプロキシーとして動かしている場合は、REMOTE_USERの情報をバックエンドに送信するHTTPヘッダーに追加するヘッダーフィールド名を指定します。

OIDCAuthNHeader X-Remote-User

OP毎のメタデータと設定ファイルの配置

OIDCMetadataDirディレクティブで指定したディレクトリ(先の例では"/var/cache/httpd/modauthopenidc/metadata")に、OP毎に次のようなファイル名の3つのファイルを用意します。

  • ISSUER.provider (メタデータ)
  • ISSUER.client (クライアントIDとクライアントシークレット)
  • ISSUER.conf (設定)

ここで、ISSUERはissuerの値から「https://」と最後の「/」を取り除いた文字列をURLエンコードしたものになります。

issuerはメタデータURIから取得できるので、次のように取得して、「https://」と最後の「/」を取り除きます。

Googleの場合:

$ curl https://accounts.google.com/.well-known/openid-configuration 2>/dev/null |  jq .issuer | sed 's|^"||;s|"$||;s|https://||;s|/$||'
accounts.google.com

Azure ADの場合:

$ curl https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration 2>/dev/null | jq .issuer | sed 's|^"||;s|"$||;s|https://||;s|/$||'
sts.windows.net/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

※{tenant}をテナント名に置き換えてください。

次に、この文字列をURLエンコードします。

Googleの場合:

$ echo -n 'accounts.google.com' | jq -s -R -r @uri
accounts.google.com

Azure Adの場合:

$ echo -n 'sts.windows.net/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' | jq -s -R -r @uri
sts.windows.net%2FXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

これにより、3つのファイル名が次のように確定します。

Googleの場合:

  • accounts.google.com.provider
  • accounts.google.com.client
  • accounts.google.com.conf

Azure ADの場合:

  • sts.windows.net%2FXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.provider
  • sts.windows.net%2FXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.client
  • sts.windows.net%2FXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.conf

ISSUER.providerファイル

次のようにメタデータURIのファイルを取得してそのまま保存します。 保存後にapacheのユーザー権限で書き込みできるようにパーミッションを変更してください。

Googleの場合:

$ sudo curl -o /var/cache/httpd/mod_auth_openidc/metadata/accounts.google.com.provider https://accounts.google.com/.well-known/openid-configuration
$ sudo chown apache:apache /var/cache/httpd/mod_auth_openidc/metadata/accounts.google.com.provider

Azure ADの場合:

$ sudo curl -o /var/cache/httpd/mod_auth_openidc/metadata/sts.windows.net%2FXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.provider https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration
$ sudo chown apache:apache /var/cache/httpd/mod_auth_openidc/metadata/sts.windows.net%2FXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.provider

※{tenant}をテナント名に置き換えてください。ファイル名も先に決まったものに置き換えてください。

ISSUER.clientファイル

次のようにクライアントIDとクライアントシークレットをJSONで記述します。

Googleの場合:

{
        "client_id" : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com",
        "client_secret" : "XXXXXXXXXXXXXXXXXXXXXXXX"
}

Azure ADの場合:

{
        "client_id": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
        "client_secret": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}

ISSUER.confファイル

OP毎の設定をJSONで記述します。共通の設定と異なるものがあれば、ここに記述することになります。

上書きできる設定は次のページを参考にしてください。

Googleの場合:

{
        "scope": "openid email profile",
        "response_type": "code",
        "pkce_method": "S256",
        "session_max_duration": "28800",
        "auth_request_params": "hd=example.com"
}

Azure ADの場合:

{
        "scope": "openid",
        "response_type": "code",
        "pkce_method": "S256",
        "session_max_duration": "28800"
}

動作確認用コンテンツの配置

動作確認として取得した変数を表示するために、次の内容のSSIを使ったページindex.shtmlをドキュメントルートに配置してみます。

REMOTE_USERはユーザーIDに、OIDC_CLAIM_nameはユーザーのフルネームに置き換わります。

明示的にログアウトできるように、ログアウトするパス「/redirect_uri?session=logout」へのリンクを用意します。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8"/>
<title>OpenID Connect</title>
</head>
<body>
<h1>OpenID Connect</h1>
<p>
<!--#echo var="OIDC_CLAIM_name" -->
(REMOTE_USER=<!--#echo var="REMOTE_USER" -->)
</p>
<p><a href="/redirect_uri?session=logout">Logout</a></p>
</body>
</html>

apacheの設定

apacheの設定は以下のようになります。

    <Location />
        # OpenID Connectによる認証・認可を要求する。
        AuthType openid-connect

        # G Suiteの認可設定:
        #   claimのhdがexample.comである場合のみ認可する。
        Require claim hd:example.com

        # Azure AD用の認可設定: 
        #   claimのissが下記の値(https://sts.windows.net/〜)の場合のみ認可する。
        #   issuerはテナント毎にユニークであるため、組織内のユーザーに制限できる。
        Require claim iss:https://sts.windows.net/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/

        # 動作確認のためのSSI用の設定を行う。
        Options +Includes
        DirectoryIndex index.shtml index.html
    </Location>

    # ログアウト後に遷移するページでは認証されていないユーザーにもアクセスを許可する。
    <Location /loggedout.html>
        Require all granted
    </Location>

設定の反映

設定が完了したら、設定を反映するために、apacheを再起動してみましょう。

$ sudo apachectl configtest
$ sudo systemctl reload httpd.service

実際にアクセスしてみて何か問題があれば、エラーログに出力されるので確認しましょう。

$ sudo less /var/log/httpd/error_log

動作確認

G SuiteとAzure ADの両方用の設定を行った場合は、動作確認用のページにアクセスすると次のようなOPを選択する画面が表示されます。

OP選択

ここで、それぞれのOPのリンクをクリックすると、それぞれのOPで認証・認可が行われます。

ログインが完了すると次のようなページが表示されます。

サイト画面

氏名とユーザーIDが表示されていれば成功です。

※ この例のユーザーIDについて、OIDCRemoteUserClaimディレクティブの設定をコメントアウトしてデフォルトの「sub@iss」の形式にした場合はこのようになります。

参考サイト

株式会社ハートビーツの技術情報やイベント情報などをお届けする公式ブログです。



ハートビーツをフォロー

  • Twitter:HEARTBEATS
  • Facebook:HEARTBEATS
  • HATENA:HEARTBEATS
  • RSS:HEARTBEATS

殿堂入り記事