Webアプリを自動生成、ここまでできる!

INTER-MediatorはWebアプリケーションを作成できるフレームワークですが、それなりに容易に開発できるように作ってあるものの、それでも敷居は高いと言われることも多いです。理想は「〜をしたい」と要求を入れれば、アプリケーションを生成することです。遠い先か、生きている間は無理かと思っていたのですが、昨今のLLMを中心としたAIの発展でそこそこ実現できそうです。そして、AIの潮流にINTER-Mediatorも乗ることできそうです。以下に示すように、作りたいアプリケーションをテキストで与えれば、INTER-Mediatorのアプリケーションを生成できます。ツールは、Windsurfを使っています。

ただし、単に「言うだけ」と言うのは流石に無理です。超マイナーフレームワークのINTER-Mediatorのことは、相対的には少ない情報しかインターネットで得られないものなので、モデルに正確な情報が含められていない感じです。つまり、生で使ってもうまく行かないので、どうにかすると言うことで、INTER-Mediator向けの追加のプロンプトを作ってみました。それは、こちらのサイトでご覧いただけますが、このプロンプトはまだレポジトリに組み入れる前なので、私がフォークしたレポジトリへのリンクになっています。このページにある「プロンプト例」の直下にある『住所録のWebアプリケーションをINTER-Mediatorを利用して作成してください。INTER-Mediatorについては以下の情報を参照してください。』が、Windsurfに与えるお題です。これに加えて、それ以下の長いテキストを与えます。要するに、プロンプト例より下のコードを、全部プロンプトとして与えます。上記リンクではMarkdown形式が整形されて見えていますが、実際にはソースのテキストをそのまま使うのが良いでしょう。そして、アプリケーションを作った結果が以下の通りです。

最初にプロンプトとそれに引き続いてINTER-Mediatorに関する追加情報をまとめて与えています。ちなみに、同じアカウントで何度も実行しているので、もしかすると、人によっては同じ結果ではないかもしれませんが、もともと、そこそこ問題なく進めていて、追加情報が必要そうなところでプロンプトを追加したものをまとめたのが長いテキストの部分です。概ね、進められると思います。

そして、SQLのスキーマ作成とその適用、Composerを使ったINTER-Mediatorのインストール、INTER-Mediator特有の追加のインストール作業、そして、lib/params.phpの作成と変数の変更とどんどんと自動で行くと思います。途中でコマンド実行を行うかを尋ねてくれば、ほぼ全部Acceptボタンを押せば大丈夫です。

そして、INTER-Mediatorのマスター/ディテール形式のページを運用できるように、定義ファイルとページファイルを作ります。このあたりは素で作る(つまり、追加プロンプトなしで作る)とかなりぐちゃぐちゃになりますが、何をすべきかをかなり詳細に追加のプロンプトで指定しているので、問題なく作成できます。

全部作成できたら、PHPがセットアップされていることを自動的に認識して、PHPのサーバモードでアプリケーションが動作します。ブラウザで接続すれば、ちゃんと動いている!!最初にレコードが入ってしまっているのは、その前に作った時のSQLiteのファイルがそのまま残ってしまっていて、それを使ったからで、普通はレコードがない状態になります。

と言うことで、まとめると、『住所録のWebアプリケーションをINTER-Mediatorを利用して作成してください。』とお願いすれば、こんなWebアプリケーションを作ってくれると言うところまで時代は進歩したと言うことです。いろんなパターンのプロンプトがあれば、便利そうですね。

なお、上記は、macOS 15.2で稼働しました。WindsurfはProプランに入っています。LLMは規定値のClaude 3.5 Sonnetをそのまま使っています。GPT 4oを選択できるのですが、こちらだと、コマンド等の自動入力はできず、実際にコマンドを入れたりするのは人間の側になります。

アクセス権セット内の計算式ではグローバル変数は使えない

FileMakerネタです。ログイン可能なアカウント作成はいいとして、自分で作ったレコードしか見えないようにしたいというのはニーズの多い仕組みです。結構いろんなことをしないと行けないのですが、その設定の肝となるは、アクセス権セットのレコードに対するアクセス権です。細かな設定はこの記事では説明しませんので、詳しい方はご安心ください。FileMakerのあの辺りの機能は、ダイアログボックスが開きまくる手間のかかるUIですので、ポイントだけ示します。

アクセス権セットの [データ入力のみ] の複製を作って、名前を付け替えるなどするのが最初の作業です。「入力のみ」という違和感ある名前はともかく、複製を作り、レコードのポップアップで、「カスタムアクセス権」を選択します。

すると、次のようなダイアログボックスが新たに開きます。ここで、該当するテーブルを選択して、リストの下に見えるポップアップメニューから「制限」を選択します。一般に、「表示」の設定を変えるのは必須ですが、「表示」で制限をかければ、編集/作成/削除に対しては何もできないようなので、全部の項目に設定は必要ありません。

この「制限」を選択すると、式を入力するダイアログボックスが表示されます。そこで、論理式を入力します。論理式がTrueなら表示可能ということで普通にレイアウトにレコードのデータが表示されます。論理式がFalseなら <アクセス権がありません> とフィールドに出ます。こうした表示するかどうかを、レコードごとに決めることができるというのが、カスタムレコードアクセス権の設定で実現します。

ここで対象となるテーブルに、テキスト型でusernameというフィールドを作っているとします。そして、そのフィールドには、レコード新規作成時に自動的に「GET ( アカウント名 ) 」という式の結果を入力します。「ユーザ名」ではなく「アカウント名」で、こちらがログインした認証情報に含まれるユーザ名になります。

そして、カスタムレコードアクセス権の制限の式に「username = GET ( アカウント名 ) 」という式を設定しておけばOKです。この辺り、私たちの書籍「FileMaker as a Relational Database」の「8.3 レコード単位のアクセス権」に記述があります。設定手順がちょっと込み入っとるやないか!と今見たら思ってしまいますが、基本はここで説明したものと同様、アクセス権設定に対するレコード単位のアクセス権指定を行っています。

さて、ここからが本番(長い)。ある案件で、単にユーザ単位でのレコードの分離(レコードごとのアクセス権設定を行なった状態)にするだけでなく、ユーザのグループを定義してグループだと、所属している全てのユーザのレコードが見えるようにするという要件がありました。ここでのグループは、OSのグループとは違って、形態としてはユーザです。要するに権限の高いユーザがグループであり、権限自体に段階があるというか、自分だけと、グループ全体の段階があるという状態です。

これを実現するために、2つの方法を考えました。1つは、ここでのユーザとグループそれぞれ別々のアクセス権セットを割り当てることです。もう1つは1つのアクセス権セットでの「制限」の論理式でうまく処理を組み込むということです。まあ、当然、後者の方が面白そうなので、まずはそちらでやってみました。なお、あるユーザがどのグループに所属するかを記録しないと行けないので、ユーザ管理のテーブルをFileMakerに作る必要があります。{主キー, username, password, is_group, groupname} がテーブル構成です。ユーザ名、グループ名、いずれもテキストで記録するようにします。ここでいちいち主キーでリレーションするのもやり過ぎ感があるので、テキストで十分です。グループは、is_groupがTrueにしておきます。そうすると、ユーザのレコードで所属グループの選択を行うときに、グループだけの(is_groupがTrueのレコードだけの)ポップアップ等が作れるので、このようにしました。このテーブルから、ユーザがどのグループに属するかがわかるという段取りです。passwordはいらんといえばいらんのですが、せっかくこんなテーブルを作るので、いちいち、セキュリティ管理のダイアログボックスを出さなくても、ボタン1つでユーザの作成や削除ができるようなUIを作りました。そのためにはパスワードも入力しておくのが便利です。

そして、大変重要なリレーションシップの設定が必要になります。あるテーブルにusernameフィールドを作って、そこにログインアカウント名を入力しておいて、誰のレコードなのかがわかるようになっているということになるのですが、さらにそのレコードはどのグループの所属なのかをわかるようにしなければなりません。つまり、テーブルから、前者のユーザテーブルのusernameに対してリレーションシップを定義し、ユーザテーブルのgroupnameとして、各レコードの所属グループが得られるようにしておく必要があります。

ここで、アクセス権セットの「制限」の式を構築するとき、要するにログインしているユーザがここでの普通のユーザの場合と、グループユーザの場合では、比較するフィールドが違ってくるということがあります。その処理をやりやすくするために、ログイン直後にユーザテーブルで自分自身のレコードを検索して、is_groupフィールドを見て、自分が通常のユーザなのか、グループユーザなのかを$$変数に置いておき、その変数値を使った計算式を立てれば良いのではと考えました。そして、「制限」の式に$$変数を使ったものを記述したのですが、画面上はなんとなく問題ないように見えるのですが、検索をしても1つもレコードが返ってきません。

なんでか? よく考えてみると、$$変数というか、変数ってコンテキスト依存のものです。つまり、画面に出て来る段階で適用されるようなものと考えて良いでしょう。検索で変数は使えますが、変数の値を求めてそれを条件としてサーバに送っていると考えられます。検索処理そのものは、サーバで行われるとすれば、サーバでレコードを探っている段階では変数の値は全部未確定と考えるべきなので、変数が全部未定義でfalseになるのではと思われます。とは言え、デバッガを動かして検証できる状態ではないので、推測ですが、要するに制限の計算式ではグローバル変数は使えるようでいて使えない場面(この場合は検索時)があるというのが結論のようです。

では、実際にはどうすればいいのか? 1つは、ターゲットテーブルに対してusernameフィールド同士が等しいというリレーションシップを設定したユーザテーブルをtarget_userとすれば、「username = GET(アカウント名) or target_user::groupname = GET(アカウント名)」という式を設定すればいいかと考えました。ですが、あえて、設定がわかりやすいように、アクセス権セットを2つ作り、通常ユーザ用とグループユーザ用として適用し、前述の式のorで区切った前半を前者、後半を後者の「制限」の式に設定しました。ユーザ作成部分はレイアウトをベースにボタンを作って対処してあるので、作り分けることは完全に自動にできます。1つのアクセス権セットにするのもいいのですが、分けておくほうが、後々、いろんな場面に対応できそうな気がして、アクセス権セットを2つに分ける方を選択しました。

ちなみに、「username = GET(アカウント名) or target_user::groupname = GET(アカウント名)」という式で大丈夫だろうと思われるかもしれませんが、ここでのユーザテーブルの整合性が合わない状況の場合、思いがけない結果になります。{is_group, username, groupname}というスキーマに対して{false, user1, boss}{false, user2, boss}{true, boss, boss}という3レコードがあったとします。これを元に、usernameフィールドにuser1、user2あるいはbossという文字列を入れて、ターゲットテーブルに対してどのユーザのレコードかを記録します。bossでログインをした場合は、前述の式のor以降がtrueになることから、user1やuser2が所有しているレコードに対してもbossは参照できます。しかし、ここで、bossがグループユーザではなくて通常のユーザになったとします。is_groupをfalseにしただけだとしたら、user1、user2→bossがグループという情報が残るので、相変わらずbossはuser1やuser2のレコードを参照できます。つまり、user1やuser2のグループを””にするなどの処置が必要となるわけです。計算式にグループユーザかどうかという分岐を入れれば、この問題は回避できると考えて$$変数を使おうとしたのですが、前述の通り、これはうごかない式になってしまいました。複数のアクセス権セットに分離している場合でも同様なことは発生します。その場合、アクセス権セットを別のものに設定し直しが必要になり、これはスクリプトではできない作業になります。同様に通常のユーザをグループユーザに昇格した場合も同様な懸念がありますが、その場合、他のユーザを新たにグループになったユーザに所属させる作業が発生するので、自然に整合性は取れるかと思われます。アクセス権設定が複数ある場合はその所属変更が必要になります。ということで、アカウント作成後は、is_groupの値を変更しないというのがシンプルな解決策になりそうです。いずれにしても、運用間違えるとセキュリティの問題が発生しそうな案件なので、諸々、注意深く実装する必要はありそうです。

macOSで動くFileMaker Server 2024でPHP

FileMaker ServerにはPHPが付属しなくなって久しいのですが、現状、特にFileMaker Serverで動かなくても不便はなく、PHPの処理が欲しい場合は別途サーバを立てるのが最善策なのはいうまでもありません。しかしながら、「どうしても動かしたい」という声が後を絶たず、ここでとりあえず動かす方法をまとめておきます。そもそも、Clarisからの文書として「FileMaker ServerインストーラへのPHP添付の廃止について」があるのですが、内容が非常に込み入っていて、しかも何をしているのかわかりにくいこともあって、PHPを動かすことに躊躇している人も多いかもしれません。この文書は、FileMaker API for PHPという懐かしいライブラリを利用するための方法まで書いてありますが、今時のPHPのバージョンでこのライブラリが無事に動くとは考えづらく、その方法までは不要なのが一般的ではないかと思われます。

以下の方法が成功したのは、macOS Sequoia上のFileMaker Server 2024の場合です。これは対応OSではないのに成功したということです。また、Sequioa上ではFileMaker Server 2023でも成功しました。これも対応OSではありません(2024版はSequioa対応かどうかは公表されていないですね)。一方、macOS Ventura上のFileMaker Server 2023ではエラーになってうまくいきませんでした。これは対応OSであるのに上手くいかなかった組み合わせです。macOS内蔵のApache2に、外部からインストールするphpのモジュールという組み合わせの相性があるようなので、上手くいかない場合もあるという覚悟で進めましょう。

macOSへのFileMaker Serverのインストールはすでにインストールされているものとします。これにHomebrewをインストールします。インストール方法はサイトに記載されているので、それに応じてインストールしてください。インストール後、以下のようなコマンドを叩いて、phpをインストールします。執筆時点では、8.1以降は@をつけるので良いのですが、7.4はタップというサードパーティによるモジュール提供になっているので、コマンドの入れ方が変わります。コマンドラインでphpのバージョンを切り替えるには、unlink/linkを使います。

//最新版のPHPのインストール(執筆時点では8.3)
brew install php
//特定版のインストール
brew install php@8.1
//PHP 7.4のインストール
brew tap shivammathur/php
brew install shivammathur/php/php@7.4
//バージョンの切り替え
brew unlink php
brew link php@7.4

ちなみに、通常は最新版を使うけど、PHPのモジュールとしては古いものを使うということもアリです。以下、自分の使いたいバージョンのパスを確認してください。原則、/opt/homebrew/Cellerというディレクトリ以下にありますが、状況によって違うので、見つからない場合はいろいろ情報を探ってください。

次にコード署名を行います。コード署名とは、秘密鍵/公開鍵を使った暗号化の仕組みの応用で、ざっくり言うと「誰が作ったコードかわかる仕組み」です。iPhoneのアプリケーションでは当初より署名必須となっており、Appleが発行する証明書を利用して署名されていないと、App Storeへの登録ができなくなっています。この手法はmacOSにもかなり浸透してきており、macOSに付属しているApache2のバイナリhttpdも当初よりコード署名がなされています。

PHPはApache2用のモジュール(拡張子.so)を利用します。このモジュールは、httpdと同じプロセス空間で動きます。イメージ的にはhttpdがphpのモジュールを読み込むと思えば良いでしょう。しかし、macOSの制限により、httpdが署名されている場合は、読み込むモジュールも署名されていないといけません。homebrewで得られるモジュールは署名されていないので、そのままではモジュールとして読み込んで実行ができないのです。そこで、brewで得られたphpのモジュールに署名して、httpdが読み込んで利用できるようにする必要があります。

証明書を作る方法はいろいろあるのですが、「キーチェーンアクセス」アプリケーションを利用して、いかような手順で進めて作成される自己署名証明書で大丈夫です。ポイントは、証明書のタイプを「コード署名」にすることと、指定した名前を忘れないようにすることです。この証明書はキーチェーンに入ります。

キーチェーンにコード署名用の証明書を作ったら、コマンド入力を進めます。まず、/opt/homebrew/Celler以下にあるPHPのパスを探って、phpのモジュールを探します。バージョン等でパスの形式が違う場合がありますが、特定バージョンのphpのフォルダには.soファイルは1つだけなので、それをともかく探せば良いでしょう。そして、codesignコマンドで署名を行います。ちなみに、証明書を作ったユーザでログインしてターミナルで作業する範囲では、codesignコマンドはキーチェーンと同一のデータを使うので、コマンドにキーチェーンの場所を指定する必要はありません。コード署名をしたら、署名を読み出してみて、証明書の名前がAuthorityキーの値に見えることを確認しましょう。

モジュールを特定する(以下、「モジュールのパス」と記載)
/opt/homebrew/Cellar/php/8.3.12_1/lib/httpd/modules/libphp.so
コード署名する
codesign -f --options runtime --timestamp -s "msyk-test" モジュールのパス
//コード署名を表示する',
codesign -d -vvv モジュールのパス
//以下、表示結果Authorityの部分が、証明書の名前(作成時に指定したものとなっている)
Executable=/opt/homebrew/Cellar/php@7.4/7.4.33_6/...
Identifier=libphp7
Format=Mach-O thin (arm64)
   :
Authority=msyk-test

続いて、Apache2の設定ファイルを編集します。設定ファイルは、/Library/FileMaker Server/HTTPServer/conf以下にあります。ファイルはhttpd.confなのですが、以下のように、さらに2.4という拡張子が増えているファイルがあります。これを-iをつけたlsコマンドで見ると、1列目にi-nodeが見えています。i-nodeはファイルシステムでのファイルの中身を特定する番号を思ってください。よく見ると、httpd.confとhttpd.conf.2.4は同じi-node番号です。つまり、1つのファイルの実態にファイル名が2つ割り当てられているという状況で、これらは「ハードリンク」という状態であると呼ばれます。つまり、同じ中身なのです。

% ls -l -i
total 424
158411791 drwxrwxr-x  32 fmserver  fmsadmin   1024 10  9 10:19 extra
158417655 -rwxrwxr-x@  1 fmserver  fmsadmin  18175 10  8 13:32 httpd.conf
158417655 -rwxrwxr-x@  2 fmserver  fmsadmin  18144 10  8 11:57 httpd.conf.2.4
158412007 -rwxrwxr-x   2 fmserver  fmsadmin  13077  1 25  2024 magic
158412007 -rwxrwxr-x   2 fmserver  fmsadmin  13077  1 25  2024 magic.2.4

ここで普通にファイルを変更してもいいのですが、2.4が付いた方がバックアップと売れば、httpd.confは別ファイルとして変更したいような気がするので、たとえば次のようにコマンドを入れて、http.confとhttpd.conf.2.4を別物にしてしまうという手はあります。2つ目でhttpd.confは消えるのですが、中身は別のリンクhttpd.conf.2.4から参照できる状態なので、事実上ファイルは消えません。

cd /Library/FileMaker Server/HTTPServer/conf
rm httpd.conf
cp http.conf.2.4 httpd.conf

ということで、設定ファイル/Library/FileMaker Server/HTTPServer/conf/httpd.confを修正します。以下に、元の状態とその範囲の修正結果を示します。ページ内で、phpというキーワードを探せばすぐに元の範囲は特定できます。その前にLoadModule、そしてFileMatchディレクティブの中にSetHeaderを定義します。モジュールのパスや証明書の名前は、もちろん、前に示したものを指定します。

//元の状態',
<FilesMatch "\.php$">
    Require all denied
</FilesMatch>
//修正した状態',
LoadModule php_module モジュールのパス 証明書の名前
<FilesMatch "\.php$">
    SetHandler application/x-httpd-php
</FilesMatch>

なお、ここで、LoadModuleディレクティブは、通常は証明書の名前は設定できません。Apacheのサイトでもこんな引数はありません。おそらく、macOSに搭載されているhttpdは特別にこのような設定を入れることができるようになっていると思われます。

ちなみに、Apache2のエラーログなどを見ると、正しい状態でもこのようなnoticeメッセージが出てきます。パスが違っていたり、署名されていなかったり、名前が違ったりすと別のエラーメッセージになり、Apache自体が起動しません。

[Tue Oct 08 12:44:50.253541 2024] [so:notice] [pid 17129] AH06662: Allowing module loading process to continue for module at /opt/homebrew/opt/php/lib/httpd/modules/libphp.so because module signature matches authority "Developer ID Application: Nii Masayuki (W3WVRUYJRT)" specified in LoadModule directive

ということで、お決まりの、phpinfo()関数の結果は以下のとおりです。Darwin OS内で動いているのが見えます。もう、HomebrewのPHPは、Sequoia対応になっていますが、この時はSonoma対応の段階でしたけど、ちゃんと動いていました。

そういうわけで、FMDataAPIについても動作確認しました。一連の作業は、自分の普段使いのMacで確認したので、HomebrewによるPHPはいろんなものが最初から入っているため一発で動きましたが(というか、その環境でFMDataAPIを開発している)、足りないモジュールがあればbrew install モジュール名 等でインストールしましょう。

一連の作業で面白い動作を発見しました。codesignコマンドなのですが、SSH経由でログインをしてコード署名を行うと、adhocという名前が出てくる、おそらくはちゃんとしてない署名になります。Authorityは設定されないので、ApacheのPHPモジュールに署名しても稼働はしません。つまり、codesignコマンドは、macOSにログインをして、その上でターミナルから起動して署名しないといけないのです。セキュリティ的な理由なのか、あるいはキーチェーンの扱いなんかがあるのかもしれませんが、ともかく、ログイン方法でコマンドの動作が違って場合によっては正く署名されないというのは落とし穴かもしれません。プロバイダにあるMacや、鍵のかかったサーバルームにあるMacなんかの場合はよく注意をしましょう。

そういうわけで、HomebrewがインストールしたPHPのモジュール、macOSに内蔵されているApache2のhttpdを使うということで、コード署名が必要になるということだったのですが、ちなみに、Homebrewのアップデート(brew update/brew upgradeコマンド)を行うと、場合によってはバイナリが置き換わります。上記のような手順の場合、PHP 8.3の新しいマイナーバージョンの登場で古いものは消されるので、つまりはアップデートがあればその都度コード署名をしなければならないという、面倒かつ、実運用では危険な状態でもある点に注意を払う必要があります。

ちなみに、FileMaker Serverは入れておきたいけど、PHPでも開発作業等をしたい場合、つまり、それぞれ別々に利用するのが一般的な場合は、ここにあるような作業をしなくても、phpを-Sオプションをつけて、サーバモードで動かすのが普通です。公開ディレクトリをカレントディレクトリにして、php -S localhost:9000などとコマンドを入れて、ブラウザから「http://localhost:9000」に接続します。ということで、やっぱりFileMaker ServerのApache2上でPHPを動かす必要性って限られるんですよね。

nginxが動くFileMaker ServerでPHP

もう、だいぶん前ですが、FileMaker ServerのLinux版は、Webサーバとしてnginxを採用しました。また、もう少し前からになるでしょうか、FileMaker Server自体にPHPがバンドルされていない状態になりました。ということで、nginxでPHPダァ〜!ってなるのかと思ったら、全然情報が見つかりません。まあ、先に結論を言えば、PHPを動かすんだったら、別のサーバー立てるのが何かと安心というのがあるかと思います。しかしですね、「なんとか動かないか」と依頼されることが多々あり、手順をまとめてみました。Ubuntu Server 22.04LTSです。sudo権限を持ったmsykというアカウントでログインして作業をしました。

まず、お決まりのUbuntuのアップデート、そしてFileMaker Serverのアップデートです。Linux版は単にinstallすればアップデートもできて結構便利ですね。FileMaker Serverのインストール方法は、Clarisのドキュメントあるいはサイトをご覧ください。この記事は、FileMaker Serverをインストールした後のお話です。

sudo apt update -y
sudo apt upgrade -y

sudo apt install ./filemaker-server-21.0.2.202-arm64.deb

Apache2は、PHPのモジュール、つまりApache2のプロセスに入り込んで動くバイナリを使っていました。一方で、nginxは、別プロセスで動くPHPに対して、ソケットや通信を使って動きます。ということで、単独のプロセスで動くPHPといえば、PHP-FPM(FastCGI Process Manager)ですね。こちらをセットアップして動くようにする必要があります。モジュール名さえ分かっていればOKですね。以下の1行目で、phpとphp-fpmをインストールしていますが、composerも必要でしょうから一緒に入れておきます。2行目は、systemctlで指定するサービス名は「php8.1-fpm」になります。2行目はphp-fpmを再起動後も起動するように設定しておきます。なお、手元ではApache2も動いてしまっていたので、止めて再起動後にも起動しないようにしておきました。

sudo apt -y install php php-fpm composer
sudo systemctl enable php8.1-fpm
sudo systemctl stop apache2
sudo systemctl disable apache2

続いて、nginxの設定ファイルを変更します。FileMaker Serverは、/opt/FileMaker/FileMaker Server以下にファイルを置きます。スペース入るの嫌ですが、仕方ないですね。その中のNginxServerのさらに下にconfがあり、4つの設定ファイルがあります。fms_nginx.confファイルを修正します。例えば、こんな感じでnanoで編集します。

cd /opt/FileMaker/FileMaker\ Server/NginxServer/conf
sudo nano fms_nginx.conf

修正箇所を以下に示しますが、ファイルのほとんど末尾です。ファイル全体は、大きく分けて、ポート80に対する設定と、443に対する設定があり、後者の { } 内に以下のコードの赤字部分を追加します。最後にコメントになっている箇所があるので、その直前でいいかと思います。このままコピペして大丈夫だと思いますが、一応、設定のポイントを次に説明します。

    location ^~ /fmi/ {
      proxy_set_header X-Forwarded-Proto https;   # MWPE need ...
           :
      proxy_cookie_path /fmi "/; Secure; HttpOnly; Max-Age=43200";
    }

    location ~ \.php$ {
        fastcgi_pass  unix:/var/run/php/php-fpm.sock;
        fastcgi_index index.php;
        include       /etc/nginx/fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

#   location / {
     # First attempt to serve request as file, then
     # as directory, then fall back to displaying a 404.
         :

まず、locationは正規表現を伴っていますが、これは、拡張子が.phpのファイルに対するリクエストが来たらこの後の定義に従うということです。fastcgi_passは、ソケットあるいはURLを指定しますが、ここではソケットを指定しています。php-fpmはソケットあるいはTCP/IPのポートが開きますが、ソケットについてはこの後のphp-fpmの設定ファイルwww.confの中に記載されているので、それと合わせる必要があります。

fastcgi_indexは、ディレクトリへのリクエストの場合、index.phpファイルを探して、そのファイルへのリクエストとして処理するという意味です。

includeの後に、ファイルのパスを指定しますが、ここで指定したファイルはFileMaker以下ではなく、/etc/nginx以下、つまり一般的なnginx側のファイルです。よほど、複製して使う方がいいかと思いましたが、書き換えないので、標準nginxのファイルをそのまま使います。このファイルや、次のfastcgi_paramでは、CGI呼び出しのパラメータを指定します。PHP的にいえば、$_SERVER変数で得られる値を定義します。もちろん、キーと値を指定します。fastcgi_paramsファイルには一般的なCGIのパラメータがほぼ記載されているのですが、$_SERVER[‘SCRIPT_FILENAME’]の値については、モジュールで動かすPHPと若干違っているので、その設定がファイルに加わっています。変数$fastcgi_script_nameは、例えば、「/info.php」のような、リクエストで指定したパスです。変数$document_rootは、nginxのドキュメントルートで、root “…”で指定すると勝手に変数が定義されます。これらの変数をつなげることで、つまりは、稼働しているスクリプトへの絶対パスがパラメータとして伝達されるということです。

続いて、php-fpmの設定を行います。モジュール版の場合、Ubuntsuではwww-dataという名前およびグループのプロセスとしてApache2が稼働しているので、それらのユーザが前提となります。一方、FileMaker Serverは、nginxも、fmsuser/fmsadminというユーザ・グループで稼働します。それに合わせるため、設定を変更します。例えば、次のようなコマンドで編集作業を行います。

cd /etc/php/8.1/fpm/pool.d/
sudo nano www.conf

修正箇所は2箇所4行ですが、他の部分を入れ混ぜるとちょっと冗長なので、修正部分だけを以下に抜き出します。つまり、user, group, listen-*をそれぞれFileMaker Server向けに書き換えます。

    :
user = fmserver
group = fmsadmin
    :
listen.owner = fmserver
listen.group = fmsadmin
    :

設定はこれだけです。ということで、プロセスを再起動して…と言いたいのですが、FileMaker ServerはApache2だけの頃からも再起動が怪しかったですが、nginxでもやっぱりダメです。こちらのブログ「FileMakerServer での nginx チューニングをするための前準備」で細かく検討されていますが、ここではサーバを再起動させることにします。

再起動後、ドキュメントのルート、/opt/FileMaker/FileMaker\ Server/NginxServer/htdocs/httpsRootに、ファイル名info.php、中身は「<?php phpinfo();」というお決まりのテストファイルを作成します。はい、無事に出ました。

なお、ドキュメントルートは、例えば以下のコマンドで、自分のアカウントに書き込み権限があるようにしておくと良いでしょう。実行については読み出し権限がグループのfmsadminに設定されているので問題ありません。

cd /opt/FileMaker/FileMaker\ Server/NginxServer/
sudo chown -R msyk htdocs
cd htdocs/httpsRoot
echo "<?php phpinfo();" > info.php

ちなみに、FileMaker Serverでnginxをセットアップしているので、NginxServer/logsにログがあるのではと思うところで、実際にアクセスログなどはあります。ですが、設定ファイルのエラー等は、/var/log/nginx/error.logに書かれます。設定ファイルの間違い探しでは、こちらのファイルを参照しなければなりません。なお、PHPはそのままにしているので、/var/log/php8.1-fpm.logに書かれます。iniファイルは、/etc/php/8.1/fpm/php.iniですので、必要ならそのファイルを修正しましょう。

さて、FileMakerでPHPとなると、普通はFMDataAPIクラスを使うと思いますが、FMDataAPIも順調にバージョンアップして、Ver.32になっています。なお、Ver.32以降はPHP 8.1が最低条件ですので、PHP 7の方は前のバージョンを使ってください。Ubuntu 22はかろうじてPHP 8.1ですので最新版を使えます。例えば、次のようにコマンドを入れて、サンプルを動かしてみましょう。サンプルのデータベースは、FMDataAPIのページを参考にして、FileMaker Serverにセットアップしておいてください。

cd /opt/FileMaker/FileMaker\ Server/NginxServer/htdocs/httpsRoot
git clone https://github.com/msyk/FMDataAPI
cd FMDataAPI
composer update

これで必要なライブラリがセットアップされる…と思ったら、Problemとして2つ項目が出ます。

Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.
Loading composer repositories with package information
Updating dependencies                                 
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Root composer.json requires PHP extension ext-curl * but it is missing from your system. Install or enable PHP\'s curl extension.
  Problem 2
    - phpunit/phpunit[3.7.0, ..., 3.7.38, 4.0.0, ..., 4.8.36, 5.0.0, ..., 5.2.7, 8.5.12, ..., 8.5.40, 9.3.0, ..., 9.6.21, 10.0.0, ..., 10.5.36] require ext-dom * -> it is missing from your system. Install or enable PHP\'s dom extension.
    - phpunit/phpunit 4.8.20 requires php ~5.3.3|~5.4|~5.5|~5.6 -> your php version (8.1.2) does not satisfy that requirement.
    - phpunit/phpunit[5.2.8, ..., 5.7.27] require php ^5.6 || ^7.0 -> your php version (8.1.2) does not satisfy that requirement.

色々書いてありますが、要するにcurlとdomの機能が入っていません。これらは、モジュール版と同様に、aptコマンドでインストーすることができます。インストール後、念のためphp-fpmを再起動します。前の続きなら、途中のcdコマンドは不要ですが、カレントディレクトリを明示するために書いておきます。

sudo apt install -y php-curl php-dom
sudo systemctl restart php8.1-fpm
cd /opt/FileMaker/FileMaker\ Server/NginxServer/htdocs/httpsRoot/FMDataAPI
composer update

これで、ブラウザからサンプルコードの.phpファイル(例えば、https://localhost/FMDataAPI/samples/FMDataAPI_Sample.php)を開くと、個別の通信が示されて、途中にはデータベースからデータを取り出す部分などが参照できます。

ちなみに、Ubuntsu上での動いているプロセスを見てみます。例えば、以下のようなコマンドで見てください。綺麗にレイアウトされないので、ここには結果は出しません。

ps aux

ここでは、プロセス名php-fpmで稼働ののち、FileMaker Serverがnginxプロセスを起動して、その後にFileMaker Serverのさまざまなプロセスが稼働しているのがよ句わかります。php-fpmはfmsuserがプロセスのオーナーになっていますし、nginxも同様です。

ということで、FileMaker Server 2024 on Ubuntsu 22において、PHP 8.1が起動しました。ここにはいちいち書きませんが、INTER-Mediatorも動いてサンプルが稼働しています(MySQLでテストしましたが〜笑)。

Webページでのテキストフィールド移動

同じことで何度も悩んでしまったので、ちゃんとまとめて書いておこう。タイトルだけでは何の意味かわからないですが、具体的には、ログインパネルのユーザ名とパスワードと思ってください。通常、ユーザ名を入力してEnterキーを押すと、パスワードのフィールドに自動的に移動したいですよね。ということで、ユーザ名のテキストフィールドをuserBox、パスワードのテキストフィールドをpasswordBoxで参照しているとして、まあ、例えば、こんなプログラムを組みます。イベント設定はいろんな方法があるのでそこは置いといて、keydownイベントが来て、押されたキーがEnterあるいはNumpadEnterなら、パスワードボックスに飛びます。

userBox.onkeydown = function (event) {
  if (event.code === 'Enter' || event.code === 'NumpadEnter') {
    passwordBox.focus()
  }
}

しかし、これだと、Macではおかしな動作が発生します。かな漢字変換途中の文字列でEnterを押すと、確定した文字列がユーザ名だけでなく、パスワード側にも入るのです。つまり、Enterによる確定でユーザ名に確定した文字列が入るのですが、同時に変換途中の状態でパスワード側にフォーカスが移動して、そこでもやっぱり確定されて、両方のフィールドに文字が入ってしまうようなのです。Windowsでは、どうなっているのかというのはちょっと説明しづらいですが、あまりいい感じではありません。

そこで、いろいろ調べると、EventクラスにisComposingというプロパティがあって、変換途中ならtrueになることがわかりました。ということで、結論としてはこのようにisComposingを考慮すればいいということになります。つまり、Enterキーを押しても、変換途中ならばパスワードフィールドへの自動移動をしないということです。

userBox.onkeydown = function (event) {
  if ((event.code === 'Enter' || event.code === 'NumpadEnter')
      && !event.isComposing) {
    passwordBox.focus()
  }
}

ちなみに、caniuse.comによると、isComposingはモダンなブラウザならどれもサポートしているので安心して使えます。

KeycloakをIdpに使ってSAML認証(3)

この記事は、KeycloakをIdpに使ってSAML認証(1)およびKeycloakをIdpに使ってSAML認証(2)に続く記事である。(1)はKeyCloakのインストール、(2)はKeyCloakをSAMLのIdPとして、INTER-Mediatorで作ったアプリケーションに認証処理を提供する方法を記述したが、認証が通ったことはわかるものの、SAML属性にユーザ名が入っていないということで、INTER-Mediator側では想定した状態になっていなかった。そして、その点は解決したので、ここに記載しておく。

KeyCloak をIdPとし、SimpleSAMLphpをSPに設定している場合で、SP側の管理ページに入り、そこで認証をしてみる。そこでは、アプリケーション側で得られてるSAMLの属性がわかるので、その部分の確認と、ユーザ名を入れる拡張を進めてみた。まず、単にKeyCloakのClientとしてSAML SPを登録した状態では次のようになる。管理ぺ時のTestのところにSPが見えている(ここでは「default-sp」)。そのSPをクリックする。

すると、KeyCloakの黒い背景の認証ページに移動する。ここで、既存のkcuser1で認証をした場合、次のように、得られる属性が見えている。(2)で説明した通り、ここでも同様にRoleという属性しか見えていない。

しかしながら、以下のようにKeyCloak側を設定すると、次の図のように、kcuser1でログインした結果、そのユーザの姓名とメールアドレスに加えて、ユーザ名も取得することができた。

INTER-Mediator側で、取得した属性をチェックすると、このような感じになる。以前は、Roleだけだったのが、ユーザ名や氏名、メールアドレスも、ともかく取得できている。

ここで、INTER-Mediatorのparams.phpファイルでは、このように取得される属性から必要なデータを得られるように、$samlAttrRules変数の配列を定義しておく必要がある。この配列のキーは、ユーザテーブル(authuser)のフィールド名である。配列のキーに対する値は、SAMLの属性から取り出す時のキーと、|で挟んで配列の要素番号を指定している。メールや氏名については、前述の属性のダンプにあるように、複雑な記号がキーになっているので、それをコピペするなどして、きちんと配列を定義しなければならない。

いずれにしても、これで、INTER-Mediatorのアプリケーションで、KeyCloakをIdPとして認証ができることが確認できた。

どうすれば属性を増やすことができるのか

ということで、タイトル通りの疑問がここで沸く。言い換えれば、KeyCloakに設定したユーザでの認証については、「何かしなければRoleしか得られない」ということだ。

まず、左側で、Client Scopesを見てみよう。ここをみると、SAMLプロトコルに対しては、role_listという名前のものしか定義されておらず、他はみんなOpenIDのものである。以下の画像には1ページ目しか見えていないが、もう1項目だけ次のページにあり、それはOpenID向けのものである。ここを見て、「属性にRoleしかない」という状況と、Client scopesにSAMLのものはrole_listしかないという結果に大きな関連性があると着目して、ここに設定を加えれば良いということがわかる。

このリストで、Create client scopeボタンをクリックする。そうすると、以下のような画面になる。ここで、Nameを指定するが、この名前は、このリスト上の名前であり、すでに存在する名前はつけられない。ここでは、snとした。そして、もう1つProtocolからSAMを選択しておく。とりあえず、この2つで良いようだ。Protocolは後から変更できないので忘れないようにする。そしてSaveボタンをクリックする。

Saveボタンをクリックすると、Clinet scopeの1項目を編集する画面に入るのだが、上部のタブのMappersをクリックする。最初は次の図のように、No mappersとなっている。ある属性を得るには、マッパーを利用して、既存のどこかの値を持ってくるという設定を記述しないといけないようである。

ここでまず、Add predefined mapperボタンをクリックして、つまりは既定義のマッパーを探して設定する。ちなみに、既定義のマッパーが一覧されるので、該当する項目にチェックを入れて、Addボタンをクリックする。ただ、これをよくみると、既定義のマッパーでは、sureName、givenName、emailの3つしかない。ここでは、snに対するものとして、X500 surenameにチェックを入れて、Addをセットする。ちなみに、すでに設定したものはこのリストでは表示されなくなる。

この設定を、sn、gn、mailというClient scopeに対して行った。

では、usernameはどうすればいいか?まずは、usernameに対応するためのClient scopeの1項目を作る。もちろん、ProtocolをSAMLにするのを忘れないようにする。そしてマッパーを登録すが、usernameのマッパーは既定義には存在しないのでConfigure a new mapperをクリックする。すると、次のような選択画面が表示される。何を選択するか迷うが、とりあえず、User Attribute、User Propertyのいずれも成功している。ここではUser Attributeを選択したとする。

すると、次のようなマッパーを定義する画面に入るので、ここで、Name以下4つの項目で全部、usernameと入力する。なお、ここでは最後の項目にあるように、SAML属性上でのキー名も入力できるので、属性上の値の解釈も分かりやすくなる。入力後、Saveボタンをクリックして保存しておく。

こうして、4つのClient scopeを追加した。Client scopeの一覧で、SAML Protocolに絞って一覧を示すとこのような感じである。username以外は、既定義のマッパーを使っている。

これでOKかと思ったのだが、まだRoleしか属性に入っていない。さらに続いて、次のように設定を行う。Clientsを選択すると、クライアントのリストが表示される。ここで、追加したSAML SPの項目のリンクをクリックする。

そして、Clientの設定画面が出てくるので、上部のタブで、Client scopesを選択する。規定値では、role_listが見えているはずだが、いずれにしても、Client scopesに登録し、さらにここで追加する必要があるということで、ここで、さらに選択を加えないといけない。

Add client scopeボタンをクリックすると、次のように、Client scopesで定義したProtocolがSAMLの項目が一覧される。とりあえず、ここでは全ての項目にチェックを入れて、Addボタンをクリックする。さらに選択するポップアップが出てくるので、Defaultを選択しておく。

SAML SPのClient scopesの設定が次のようになったらOKである。

ここまで設定を行うことで、最初に示したように、SAML属性にusernameや氏名、メールアドレスを含めることができ、INTER-Mediatorでの利用が可能になった。

KeycloakをIdpに使ってSAML認証(2)

この記事は、KeycloakをIdpに使ってSAML認証(1)に続く記事である。(1)では、ともかくKeyCloakを稼働するところまで進めた。そして、KeyCloakをIdPとし、SimpleSAMLphpを使ったINTER-Mediatorのアプリケーションまで稼働する。なお、SP側は、SimpleSAMLphp Ver.2を使ってみる(3)の設定が終わっているものとする。SPは、IdPもSPもSimpleSAMLphpを利用していて、default-spがとりあえず、SimpleSAMLphpのIdPに接続されている。

KeyCloak側にRealmを1つ定義する

ここでまず、KeyCloak側にRealmを1つ定義する。KeyCloakの管理画面にadminユーザでログインをして、左側のナビゲーションバーの一番上にあるドロップダウンリストにある「Create realm」ボタンをクリックする。

すると、次のようなフォームが表示される。ここではとりあえず、Realm Nameだけを入力して、Createボタンをクリックすれば良い。以下は、IM_Testと入力をした。

左側の上の方に、作ったRealmのIM_Testが見えているはずだが、ここでRealmを切り替えて利用できる。以後はここで作成したIM_Testが選択された状態で作業を進めることにする。

KeyCloak側にユーザを定義する

続いてユーザを定義する。左側でUsersを選択すると、ユーザの一覧が表示されるが、その中にあるAdd Userボタンをクリックする。すると、以下のようなフォームになるので、とりあえず、Usernameだけは入力しておく。ここではkcuser1とした。Emailも入力されている。そして、Createボタンをクリックすると、ユーザは作られた。

次に作ったユーザにパスワードを与える。管理画面の左側でUsersを選択すると、作ったユーザkcuser1が見えているはずだ。そのユーザ名のリンクをクリックすると。そのユーザの設定変更ができるフォームが見えている。ここで、右側上部のタブにあるCredentialsをクリックして、パスワードを設定する。

Credentialsをクリックすると、次のようなパネルが表示される。ここで、PasswordとPassword Confirmationに同じパスワードをキータイプする。TemporaryはOnだとログイン時にパスワードの変更が必要になる。ここではテストなので、Offにしておくのが良いだろう。そして、Savaボタンで、パスワードが設定される。

IdP(KeyCloak)のメタデータをSPに登録

ここからは、IdPとSPのメタデータの交換である。まずは、IdPのメタデータをSPに登録する。別記事の手順で作られたSPだが、SP側の設定はほぼそのままにして、IdPのメタデータだけを入れ替えることにする。

IdPのメタデータは、左側のReaml settingsをクリックして、Generalのタブの一番下にあるEndpointsというところにある「SAML 2.0 Identity Provider Metadata」というリンクをクリックする。

すると、次のように新たなウインドウにIdPのメタデータが表示される。このXMLを利用するのだが、ブラウザでXMLのコピペをするとしても、ブラウザが余計なことをしてしまうことになりそうなので、ここでは、URLをコピーしておく。そして、作業用のコンピュータ側でいいので、「curl -O メタデータのURL」とコマンドを入れて確実に中身の変更が行われない状態で、ファイルに納める。descriptorというファイルが作られるが、それをエディタで開いて、中身にコピーしておくと良い。

このメタデータをSP側に登録するのだが、SPはSimpelSAMLphpであるので、メタデータはXML形式ではなくPHPのプログラムにしなければならない。そこで、SP側の管理ページを開く。URLを示す意味があるかは若干疑問だが、別記事の通りにセットアップしてれば、「https://demo.inter-mediator.com/saml-trial/lib/src/INTER-Mediator/vendor/simplesamlphp/simplesamlphp/public/admin」で管理ページへのログインができる。ログイン後、上部で「連携」を選択する。ここにある「ツール」の「XMLをSimleSAMLphpメタデータに変換」の部分をクリックする。

すると、メタデータパーサと書かれたページが出てくるので、XMLメタデータ部分にペーストする。つまり、IdPのメタデータそのものを、ここに貼り付けて、「パース」ボタンをクリックする。

すると、次のように、PHPのコードに変換されたものが「変換されたメタデータ」の部分に見えるようになる。このPHPのコードを慎重にコピーして取り込んでおく。

SPの設定を変更する。設定ファイルのsaml20-idp-remote.phpを修正する。テキストエディタで開いて、末尾に変換されたIdPのメタデータをペーストすれば良い。従来のIdPのメタデータはコメントにしておくのが良いだろう。

SPのメタデータをIdP(KeyCloak)に登録

次は、SPのメタデータをIdPに登録する。SPのメタデータは、SPの管理画面で「連携」のところのdefault-spの下にあるVの部分をクリックすると、表示される。XMLとPHPの両方で表示されるが、ここで必要なのはXMLの方である。ここではコピペは必要なく、SAML Metadataの直下「You can get the metadata XML on a dedicated URL:」のすぐ下にある1行のURLの部分の右端にクリック可能なボックスが出てくる。これをクリックすると、メタデータのXMLがファイルとしてダウンロードされる。ファイル名は、default-sp.xmlという名前になる。

KeyCloakの管理画面に戻る。IM_TestのRealmが選択されていることを確認した上で、左側でClientsをクリックする。以下のような画面になるので、リストの上にあるImport clientの部分をクリックする。

インポートの画面に遷移するが、ここでは、Browseボタンをクリックして、先ほどダウンロードしたSPのメタデータファイル(default-sp.xml)を選択する。すると、Resource fileの部分にメタデータファイルの中身が展開される。ここまででSaveボタンを押して保存をしておく。

アプリケーションを動かしてみるが…

では、実際にSAMLに対応したアプリケーションを動かしてみる。認証が必要な場面で、KeyCloakの認証画面に移動し、登録したユーザkcuser1での認証は通った。なお、名前を入力していなかったので、このように入力を促すパネルが出るが、認証自体は通っているようである。

しかしながら、その後には、次のようなメッセージが出てきた。というか、これはINTER-Mediatorが出すメッセージである。INTER-Mediatorは、認証時に得られる属性の配列から、ユーザ名や氏名、メールアドレス等を取り出すようにしており、ユーザ名の取得は必須にしてある。$samlAttrRules変数は設定ファイルに記述する属性の何を取り出すのかを指定する配列であるが、ここでは既定の状態でユーザ名の取得が正しく取れていないことを意味する。

SAMLのやり取りをみるとRoleしか来てない

デバッグメッセージを表示してもう少し詳しく見てみよう。右側、中央あたりに見えている6要素の配列が、IdPから得られたデータである。これをよくみると、Roleというキーがあるので、Roleが得られていることはわかるのだが、それ以外のデータが全くない。つまり、ログインは成功したもののIdPからRole以外のデータが全く来ていないということになり、その結果、どのユーザでログインしたのかは、アプリケーション側は全く検知できない状態になってしまっているということである。

つまり、KeyCloakを既定の状態で使うままではどうやらINTER-Mediatorでの認証はできない。ユーザ名を「気にしない」アプリケーションなら良いのかもしれないが、ユーザ名を取得できることを必須としているINTER-Mediatorでは対策が必要である。

ちなみに、SAML-tracerでチェックしてみた。ユーザ名とパスワードを入力した直後のSAMLのやり取り行をみてみると、SAML2.0 Responseはあるのだが、ここでやり取りされるデータは非常に少ない。

ちなみに、INTER-Mediatorを使って稼働している別のシステムではこのようなデータが見えている。なお、IdP, SPともにSimpleSAMLphpを使い、IdPはADに対してLDAPで接続して、ADユーザでのログインを可能にしている。ログイン直後に、SAML2.0 AttirubuteStatementの部分には、たくさんのデータが見えているが、事実上、これはADのユーザデータである。見てすぐにわかるように、cnでユーザ名、snとgivenNameで氏名が得られるので、そのようにINTER-Mediatorをセットアップして利用している。

ということで、KeyCloakとSimpleSAMLphpのSPは、接続等のセットアップはできるが、そのままではRoleしか得られないので、INTER-Mediatorでは認証はおそらくできているとしてもユーザ名が得られないので、認証していないとみなしているというところで、その解決策がわかれば、続きの記事を書くとする。

KeycloakをIdpに使ってSAML認証(1)

INTER-Mediatorで作るアプリケーションの認証基盤として、KeycloakをSAMLのIdPとして利用した場合の動作を検証する必要が出てきたので、実際に動かしてのチェックを進めてみる。SPはもちろんSimpleSAMLphpであり、これはINTER-Mediatorではcomposerでインストールできている状態になっている。ということで、まずは、Keycloakをきちんと動くようにしたい。(1)は、Keycloakを動かすまでである。

稼働するサーバは、INTER-Mediatorの公開サーバーで、Ubuntu 22で稼働している。そこに、kc.inter-mediator.comというドメイン名を割り当てて、Keycloakを利用することにする。ただ、Keycloak自体はJavaのアプリケーションであり、Apache2と連携しなくても連動するので、ともかく連携しない方法での稼働を目指すが、Let’s Encryptで、kc.inter-mediator.comの証明書を作るので、Apache2側も幾らかの設定を行うことにした。

なお、インストール方法は、ITC Enginnering Blog:Ubuntu 22にKeycloak 22をインストールして、Identity providers=Azure ADでSAMLを参考にさせていただいた。基本、概ね同じである。

利用するドメインの証明書を取得するまで

INTER-Mediatorの公開サーバーは、Sakura VPSである。すでにWeb等が稼働しているため、当然ながらのポートは開いているが、Keycloak用に、TCP 8443とTCP 9000のポートを開いた。9000はとりあえずは使わない。サーバ側にFirewallを設定する方式ではなく、コントロールパネルから設定するパケットフィルタを設定しているので、そこで8443/9000ポートを開いておいた。

そして、まずは、OSのアップデートを行い、kc.inter-mediator.comサイトを作る。サイトのルートは、/var/www/kcとした。そして、Apache2の設定ファイルをsites-availableディレクトリに作る。80番ポートと433番ポートは同じディレクトリを公開するように設定しているが、80側にはリダイレクトの設定があり、httpでアクセスがあると自動的にhttpsにしている。なお、certbotが成功するまでは、SSLCertificateFileとSSLCertificateKeyFileの設定は頭に#を入れてコメントにしておく必要がある。ここまで準備できれば、a2ensiteでサイトの設定ファイルをアクティブにして、systemctlでApache2を再起動し、certbotコマンドで証明書を作成する。証明書が正しく作成できれば、006-kc.confファイルのSSLCertificateFileとSSLCertificateKeyFileの前にある#を消しておき、改めてApache2をsystemctlで再起動する。もちろん、http://kc.inter-mediator.comにブラウザで接続して、証明書が正しく存在することは確認しよう。ここまでは、Keycloakとは関係のない、単にサーバ証明書を取得するための手順である。

sudo apt update
sudo apt upgrade
cd /var/www
mkdir kc
sudo chown www-data kc
sudo nano /etc/apache2/sites-available/006-kc.conf
# 006-kc.conf ファイルの内容をここで修正(証明書読み込み部分はコメント)
sudo a2ensite 006-kcsudo
systemctl restart apache2
sudo certbot certonly --webroot -w /var/www/kc -d kc.inter-mediator.com
sudo nano /etc/apache2/sites-available/006-kc.conf
# 006-kc.conf ファイルの内容をここで修正(証明書の読み込み部分を活かす)
systemctl restart apache2

以下は最終的な006-kc.confファイルの内容である。もちろん、これは一例であって、皆さんはご自分のスタイルがあるだろうから、それに従って作ろう。ともかく、Let’s Encryptで証明書を作ってもらうためだけのサイトなのである。

<VirtualHost *:80>
    ServerName kc.inter-mediator.com
    ServerAdmin info@inter-mediator.org
    DocumentRoot /var/www/kc

    ErrorLog ${APACHE_LOG_DIR}/kc-error.log
    CustomLog ${APACHE_LOG_DIR}/kc-access.log combined

    RewriteEngine on
    RewriteCond %{SERVER_NAME} =kc.inter-mediator.com
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
<VirtualHost *:443>
    ServerName kc.inter-mediator.com
    ServerAdmin info@inter-mediator.org
    DocumentRoot /var/www/kc

    ErrorLog ${APACHE_LOG_DIR}/kc-error.log
    CustomLog ${APACHE_LOG_DIR}/kc-access.log combined

    SSLCertificateFile /etc/letsencrypt/live/kc.inter-mediator.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/kc.inter-mediator.com/privkey.pem
</VirtualHost>

PostgreSQLのインストールと準備

KeyCloakは、データをPostgreSQLに溜め込むので、そのための準備が必要である。PostgreSQLをセットアップして、さらにデータベース側のユーザとデータベースを定義しておく必要がある。インストールは特に難しくはなく、aptで行う。その後に、システムに作られたpostgresユーザへのログインができるように、そのユーザにパスワードを設定する。ここでは書かないが、自分のパスワードはどこかにメモると良いだろう。その後、suでpostgresユーザに切り替わり、そしてpsqlコマンドでデータベースへのコマンド入力ができる状態にする。そして、CREATE USERとCREATE DATABASEのコマンドを入れる。PostgreSQLユーザのPASSWORDももちろん、独自に設定して自分で管理してもらいたい。

sudo apt install postgresql postgresql-contrib
sudo systemctl enable postgresql
sudo passwd postgres
# パスワードはきちんと把握しておく
su - postgres
psql
CREATE USER keycloak WITH PASSWORD 'PASSWORD' CREATEDB;
CREATE DATABASE keycloak OWNER keycloak;

Keycloakのインストール

やっと、Keycloakをインストールする段階にきた。インストールは簡単だが、aptでインストールはできない。そして、まずはJavaのインストールも必要である。コンポーネントとしては若干複雑な構成のようだが、JavaとKeycloakのインストールがともかく必要になる。

sudo apt install openjdk-21-jdk
java -version
cd /var/www
wget https://github.com/keycloak/keycloak/releases/download/25.0.2/keycloak-25.0.2.tar.gz
tar zfxv keycloak-25.0.2.tar.gz
sudo nano keycloak-25.0.2/conf/keycloak.conf

Javaのインストールは良いとして、Keycloak自体は/var/wwwにインストールした。もちろん、好みの場所で良いだろうが、使っているサーバはインストールしたものが概ね/var/wwwにあるのでこのようにした。wgetの後のURLは、KeyCloakのWebサイトのDownloadのページにあるtar.gzファイルのリンクをそのまま利用した。そして、そのまま展開している。よって、KeyCloak本体は、/var/www/keycloak-25.0.2というディレクトリに展開された。

続いて、KeyCloakの設定ファイル「conf/keycloak.conf」を編集する。実際のファイルにはコメントが丁寧に細かく書かれているので、それを読みながら作業するのが良いと思われる。以下コメントとともにファイルの内容を記載する。

# Basic settings for running in production. Change accordingly before deploying the server.

# Database

# The database vendor.
db=postgres

# The username of the database user.
db-username=keycloak

# The password of the database user.
db-password=PASSWORD

# The full database JDBC URL. If not provided, a default URL is set based on the selected database vendor.
db-url=jdbc:postgresql://localhost/keycloak

# Observability

# If the server should expose healthcheck endpoints.
health-enabled=true

# If the server should expose metrics endpoints.
metrics-enabled=true

# HTTP

# The file path to a server certificate or certificate chain in PEM format.
https-certificate-file=${kc.home.dir}/conf/fullchain.pem

# The file path to a private key in PEM format.
https-certificate-key-file=${kc.home.dir}/conf/privkey.pem

# The proxy address forwarding mode if the server is behind a reverse proxy.
#proxy=reencrypt

# Do not attach route to cookies and rely on the session affinity capabilities from reverse proxy
#spi-sticky-session-encoder-infinispan-should-attach-route=false

# Hostname for the Keycloak server.
hostname=kc.inter-mediator.com

最初のdbはこのまま、次のdb-username、db-passwordは、psqlコマンドで打ち込んだCREATE USERの時の情報をここに記載する。db-urlはこのままでOKである。HTMLセクションの最初の2つは、サーバ証明書である。ここにLet’s Encriptのディレクトリをそのまま書きたいところだが、そこはrootしかアクセス権がなく、KeyCloak自体をそのためのユーザで稼働することを考えているためその方法は使えない。そこで、証明書をKeyCloakの配下に以下のようにコマンドを入れてコピーすることにした。3ヶ月後はどうするかはまた考えるとして、とにかくこのようにした。なお、privkey.pemはユーザしか読み出しできないので、グループや全員に読み出し許可を与えている。

cd /var/www/keycloak-25.0.2/conf
sudo cp /etc/letsencrypt/live/kc.inter-mediator.com/privkey.pem .
sudo chmod a+r privkey.pem
sudo cp /etc/letsencrypt/live/kc.inter-mediator.com/fullchain.pem .

KeyCloakを動かす

KeyCloakの起動スクリプトは、ディレクトリのbinにあるkc.shである。これをまずは起動してみる。とりあえず、サブコマンドにbuildを与えて、そしてサブコマンドstartで実際に起動する。出力結果を見ていてERRORがなければ大丈夫。そして、最後の方に、8443と9000ポートが開くメッセージがあるのでわかる。

cd  /var/www/keycloak-25.0.2/bin
./kc.sh build
./kc.sh start

そして、 https://kc.inter-mediator.com:8443 を開いてみる。すると以下のようなメッセージが見える。現状では管理者としてログインができないということを示している。

ここで、Ctrl+Cで一度止める。そして、記載の通り、環境変数に管理者のユーザ名、パスワードを指定して再度起動する。コマンドとしては次の通り。PASSWORDはもちろん、何か適当なパスワードを指定して、自分で記録管理しておく。

cd  /var/www/keycloak-25.0.2/bin #カレントはKeyCloakの中のbinとする
export KEYCLOAK_ADMIN=admin
export KEYCLOAK_ADMIN_PASSWORD=PASSWORD
printenv|grep KEY #一応確認
./kc.sh --verbose start

すると、次のように、ログインパネルが表示され、ここで、admin/PASSWORDでログインができるはずである。ここまでで、基本的な動作確認ができた。

なお、環境変数にパスワードがある状態になるが、おそらくこの後にサービス化するので、再起動して後付けの環境変数は消えるはずである。このKEYCLOAK_*という2つの環境変数がある状態で、まだ管理者がない状態であれば、この環境変数の値を使って管理者ユーザを作成する。つまり、その瞬間にだけ環境変数があれば良いようで、その後は環境変数になくても管理者は存在し、ログインができるのである。

KeyCloakのサービス化

コマンドラインで起動した状態で使うというのはちょっと現実的ではないので、SystemCTL配下のサービスとして、バックグランドで自動起動するようにしたい。そのために、以下のようにコマンドを入れた(ほとんど前述のブログ記事の通り)。

sudo groupadd -r keycloak
sudo useradd -m -d /var/lib/keycloak -s /sbin/nologin \
     -r -g keycloak keycloak
sudo nano /etc/systemd/system/keycloak.service
# ここで、新しいファイルが作られるので、以下のように記述する
sudo chown -R keycloak /var/www/keycloak-25.0.2
sudo systemctl enable keycloak.service
sudo systemctl start keycloak.service
systemctl status keycloak.service

ファイル「keycloak.service」については、以下のように作成した。おそらく調整ポイントは、WorkingDirectoryとExecStartのパスだけだろう。

[Unit]
Description=Keycloak Application Server
After=syslog.target network.target

[Service]
Type=simple
TimeoutStopSec=0
KillSignal=SIGTERM
KillMode=process
SuccessExitStatus=143
LimitMEMLOCK=infinity
SendSIGKILL=no
WorkingDirectory=/var/www/keycloak-25.0.2/
User=keycloak
Group=keycloak
LimitNOFILE=102642
ExecStart=/var/www/keycloak-25.0.2/bin/kc.sh start --log=console,file

[Install]
WantedBy=multi-user.target

コマンド入力について説明しておく。最初のgroupaddとuseraddコマンドでは、ユーザkeycloak、グループkeycloakを作成している。もちろん、keycloakユーザはkeycloakグループに所属させる。このユーザで、デーモンを動かすということである。ここでのKeyCloakが存在するディレクトリ/var/www/keycloak-25.0.2以下についても、keycloakユーザが所有者となるように、chownコマンドで設定している。useraddについては、-dでホームディレクトリを設定しているが、ここに何かを仕掛ける予定は特にない-mによりホームは自動的に作られる。シェルをnologinにしてあるので、ログインをしないことを想定している。-rはシステムアカウントとしてkeycloakを作る。実際にはIDは997で作られた。

実際のログファイルは、/var/www/keycloak-25.0.2/data/log/keycloak.logとなる。つまり、KeyCloakのディレクトリ以下、data/logの場所に作られる。この内容は、kc.shで起動した時に画面で見えるのと同じである。なお、systemctlコマンドではログの一部はアクセス権がないということで出てこないが、ログの場所がわかっていれば問題ないだろう。

ということで、無事にKeyCloakが起動した。

(1)はここまでとする。

Active DirectoryのLDAPを使ってSAML認証

SAML認証についての解説記事のうち、以下の3つはSimpleSAMLphp v2についての記事で、まだまだ、書いたことはそのまま使える(というか、そうなるように時々アップデートしている)。(1)(2)がIdP、(3)がSPの構築だ。

これらの情報をベースにして、タイトルの通り、Active DirectoryをLDAPサーバとして利用して、SAML認証が行うことに成功したので、やり方を書いておく。以下の手順時は、上記の流れでIdPとSPが稼働できるようにした上での追加の作業について記載する。

まず、SimpleSAMLphpでLDAPの機能を動かすためには、PHPのLDAP機能拡張の追加と、SimpleSAMLphpのLDAPモジュールの追加が必要である。Ubuntu Server 24.0.2 LTSでは、例えば、IdPをセットアップしたディレクトリ(/var/www/simplesaml-idp)で、以下のようにコマンドを入力する。

sudo apt install -y php-ldap
cd /var/www/simplesaml-idp 
composer require simplesamlphp/simplesamlphp-module-ldap

続いて、設定ファイルを変更する。前述の記事の通りにIdPのセットアップがなされているとして、IdP側のconfig.phpに以下の設定を追加する。もちろん、最後のldapのエントリーを追加する。

'module.enable' => [
  'exampleauth' => true,
  'core' => true,
  'admin' => true,
  'saml' => true,
  'ldap' => true,
],

そして、さらに、IdPのauthsource.phpファイルを修正する。ここでは以下のように、LDAPに関する設定を有効にする。すでに、コメントされた箇所があるので、そのコメントをはずして、実際に使用する値にすればよい。以下は、実質的な設定の行だけを残したものであるが、実際には、ファイルに記載されている説明のためのコメントを読みながら設定をすることになるだろう。LDAPについての知識があれば、そんなに大変ではないと思われるが、AD自体の状況も加味しないといけない。設定変更した箇所は、赤字で示したが、「最低限」となるとこれくらいだろう。なお、ドメインは架空であり、実際とはちょっと違う。

    'example-ldap' => [
        'ldap:Ldap',
        'connection_string' => 'ldap://ad.msyk.net/',
        'encryption' => 'none',
        'version' => 3,
        'ldap.debug' => false,
        'options' => [
            'referrals' => 0x00,
            'network_timeout' => 3,
        ],
        'connector' => '\SimpleSAML\Module\ldap\Connector\Ldap',
        'attributes' => null,
        'attributes.binary' => [
            'jpegPhoto',
            'objectGUID',
            'objectSid',
            'mS-DS-ConsistencyGuid'
        ],
        'dnpattern' => 'CN=%username%,CN=Users,DC=msyk,DC=net',
        'search.enable' => false,
        'search.base' => [
            'CN=Users,DC=msyk,dc=net',
        ],
        'search.scope' => 'sub',
        'search.attributes' => ['uid', 'mail'],
        'search.filter' => '(&(objectClass=Person))',
        'search.username' => null,
        'search.password' => null,
    ],

設定の最後の3分の1程は、serch.とプレフィックスがあるようにユーザ検索を行うときの設定のようである。username等をnullにしているので、「検索はしない」という設定にしておいた。認証だけなら、この設定は不要である。なので、search.baseも設定する必要はないとは思われる。

ということで、実際に使用したのは、connection_string、つまり、LDAPサーバへのURLと、dnpatternつまりユーザレコードのDistingished Name(DN)のパターンだけである。AD側は、シンプルに(シンプル過ぎるがww)Users以下にだけユーザを作ってあるので、CN=UsersがDNパスに入るだけである。ユーザ名はCNで得られる。

さらにIdPのmetadataディレクトリに入れるsaml20-idp-hosted.phpファイルも修正をする。URLやキーファイル名はすでに入っているとして、以下のように、authキーの値をauthsource.phpで指定したLDAP設定のキー値に置き換える。もちろん、「example」は実際の運用ではちゃんとした名前に置き換えよう。ちなみに、最初、LDAPの設定をしたのに、example-userpassのテストユーザでないと認証ができないというところでグルグルしてしまったのだが、ちゃんと認証ソースの設定があるということであった。

$metadata['https://idp.topse.jp/'] = [
    'host' => '__DEFAULT__',
    'privatekey' => 'idp.pem',
    'certificate' => 'idp.crt',
    'auth' => 'example-ldap',
:

これでIdP側の管理ページにログインして、表示内容を確認しよう。まず、「設定」のタブのところでは、Modulesにldapが追加されてチェックが入っている。

Testのところを見ると、authsource.phpに設定した、example-ldapが見えている。これをクリックして、このソースでの認証を確かめてみる。

いつも通りのIdPの認証パネルが見える。ここで、LDAPに存在するユーザを入力して認証を確認する。

認証できた。認証した結果の属性が見えている。この属性は、SimpleSAMLphpのSimpleクラスのインスタンスに対して、getAttributes()メソッドで得られるものだ。実際には、例えば、cnキーに対して配列が得られて、0番目の要素として取り出すなどするが、ともかく、この属性値から、アプリケーションに必要な値を取り出す必要がある。とは言え、ざっと見たところ、ユーザ名はcn、フルネームはsnとgivenName、メールアドレスはmailというあたりが実際に欲しい情報かと思われる。

IdPはセットアップできた。SPは、前述の別記事のままで良い。SP側のTestでは、default-spが見えているはずで、これが上記のIdPと連携が取れていると思われるが、そのままdefault-spで認証テストをすると、上記のように、LDAPのユーザ情報が同じように得られる。

INTER-Mediatorでの設定は、params.phpファイルに仕込む。$isSAMLをtrueにして、SPの名前を入れ、キャッシュした情報での認証時間を1800秒とする。ここまでは普通のSAMLの設定だが、$samlAttrRulesに次のような配列を入れて、SAML属性からの値の取り出しを指定している。この変数は、キーがauthuserテーブルのフィールドを示し、値がSAML属性からの取り出しパスを指定している。例えば、属性のcnキーの値から0番目の要素を取ってきて、それをusernameフィールドに入れるということだ。なお、名前が姓と名で分離されているので、その場合は、値を配列にすれば、それぞれの要素をキーとして取り出した値を結合して、そのフィールドの値とする。ここではrealnameフィールドの値は「新居 雅行」となることを期待している(というか、この仕組み、さっき急いで実装したりした)。

$isSAML = true; # The default value of isSAML is false.
$samlAuthSource = 'default-sp';
$samlExpiringSeconds = 1800;
//$samlWithBuiltInAuth = true;
$samlAttrRules = [
    'username' => 'cn|0',
    'realname' => ['sn|0','givenName|0'],
    'email' => 'mail|0',
];

これで、SAMLベースでのLDAPユーザによる認証ができるようになった。

Ubuntu Server 24.04 LTSでINTER-Mediatorを稼働する

Ubuntu 24が正式にリリースされました。この上で、INTER-Mediatorを動かす方法をまとめておきます。期待通り、以前よりも追加モジュールは少なくて済むようになりました。セットアップ作業後、管理者アカウントで、以下のようにコマンドを入力します。

sudo apt -y -U upgrade
sudo apt install -y apache2 php mysql-server
sudo apt install -y nodejs
sudo apt install -y composer
sudo apt install -y php-xml php-gd
sudo apt install -y mysql-client php-pdo-mysql
sudo apt install -y postgresql php-pgsql
sudo apt install -y sqlite3 php-sqlite3

cd /var/www/html
sudo chown -R www-data:adm /var/www
sudo chmod -R g+rw /var/www
sudo systemctl restart apache2

git clone https://github.com/INTER-Mediator/INTER-Mediator.git
cd INTER-Mediator/
composer update

最初の塊は、必要なソフトウエアのインストールです。Linux自体のアップデートは、1つのコマンドでできるようになったので、早速使います。MySQLだけでなく、PostgreSQLやSQLiteについても記述しました。PHPのモジュールで足りないのは、xmlとgdだけでした。これだけのインストールで、最後のcomposer updateで必要なライブラリのダウンロードが成功します。これらでインストールされたソフトウエアのバージョンをまとめておきます。なお、Nodeについては、INTER-Mediatorのcomposer.jsonでも記述があり、そちらは、Ver.20をダウンロードして利用します。

  • Apache2:2.4.58
  • PHP:8.3.6
  • MySQL:8.0.36
  • Node:18.19.1
  • PostgreSQL:16.2
  • SQLite:3.45.1

2つ目の塊では、Apache2が稼働しているユーザwww-dataが、自分自身のホーム以下に書き込み権限があるようにしています。念の為、Apache2を再起動しています。この設定は、INTER-Mediatorの一部の機能では必要になります。

3つ目は、INTER-Mediatorのインストールです。なお、サンプルのデータベースを入れる必要があるので、続いて以下のようにコマンドを入れれば、3つのデータベースにサンプルデータベースのtest_dbがセットアップされます。最初にカレントディレクトリがINTER-Mediatorであることを前提にしています。特定のデータベースだけでいいのなら、他のデータベースについてのセットアップは無視してOKです。

cd dist-docs/
# MySQLのサンプルデータベース登録
sudo mysql -uroot < sample_schema_mysql.sql 

# PostgreSQLのサンプルデータベース登録
sudo -u postgres psql -c 'create database test_db;'
sudo -u postgres psql -f sample_schema_pgsql.sql test_db

# SQLiteのサンプルデータベース登録
sudo mkdir -p /var/db/im
sudo sqlite3 /var/db/im/sample.sq3 < sample_schema_sqlite.sql
sudo chown -R www-data:adm  /var/db/im

これで、「http://(サーバのIPアドレス)/INTER-Mediator/samples/」に接続すれば、INTER-Mediatorのサンプルの稼働が確認できます。