Subscribed unsubscribe Subscribe Subscribe

フランスでプリペイドSIMを買ってみた

最近は技術ネタはQiitaに書いているので、たまにはこちらにも投稿です。

ReactEurope 2016に参加

ここ一年半くらいはReact.jsを使っている関係から、6月の頭にReactEurope 2016というカンファレンスに参加してきました。場所はフランスのパリです。ReactEuropeは去年から始まり今年で2回目の開催で、来年もあるとのこと。React.js自体はもう大分枯れてきていて余り大きな変化はないので、今回のカンファレンスは周辺の技術要素がメインという感じでした。セッション内容は ReduxReact NativeGraphQLのテーマのものが多かったです。各セッションの動画がYouTubeで公開されているので興味のある方は是非見てみてください。

www.youtube.com

プリペイドSIM購入

さて、タイトルにある通り本題に入りますが、今の時代はネットが使えると色々と便利なのでフランス現地のプリペイドSIMを買って使ってみました。丁度今使っているスマートフォンがNexus5なので、特に何もせずにSIMを差し替えるだけで使うことができます。

「フランス SIM」などでググると、幾つか使ってみたレポートの記事が見つかります。

事前にこれらをざっと見て使うSIMを決めておきました。最大手はOrangeという会社のようですが、約30ユーロで50GBも使えるという4番手のFree Mobile にしてみました。もしかして現地で npm install で数百MBのダウンロードをする恐れもあるので、これだけ使えると安心です!!

さて、Free Mobile の特徴として自販機で買えるというのも他社と比べてユニークな点です。↓こんなやつです。英語もフランス語も話す必要なく買えちゃいます。旅行客にピッタリ。CDG空港にもあるとバカ売れな気がするんだけどなぜかない。
f:id:Hirohiro:20160624005157j:plain

事前調査によると

  1. 設置場所が限られている
  2. 英語のメニューがなくフランス語のみ
  3. SIMカードのサイズ選択がある
  4. 最後に住所の入力が必要になる (ホテルでいける)

とのことです。

1番目の設置場所については、パリ市内にいくつかあるので、下記サイトのMapでショップの場所をおさえておいたほうがいいです。Free centerというFree Mobileのショップや、Mag Presse というコンビニみたいなお店にも設置されています。私はホテルから比較的近いところにあるMag Presseに丁度設置されていたのでそこで購入しました (少々道に迷ってしまいましたが...)。


2番目の英語のメニューがないという問題は、メニューの選択肢はそれほどないので、適当に繰り返し操作しているとまぁたどり着けます。

3番目は必要なSIMカードのサイズを忘れずにチェックしておいて、選べばOKです。

4番目は、ホテルの住所で良いので忘れずにメモを用意しておきましょう。確か郵便番号が必要なので注意

支払いはICチップ付きVISAカードならOK。そういえばフランスはメトロなどの交通機関やスーパー、パン屋さんでもクレジットカードが使えるのでほとんど現金は使わなかったです。

セットアップ

さて、無事に購入完了するとSIMカードとレシートが出てきます。後はSIMカードを台紙から切り離し、挿入すればOKと言いたいところですが、ここでPINの入力が求められます。てっきりレシートに書いてある数字のどれかかな、と思いきや、実は切り離した台紙に PIN : 1234 とか書かれていました。これに気づくのに少々時間がかかってしまいました。ちなみにPINは1234固定のようです。事前調査足りず...

使ってみた感想

電波は屋外だとかなり快適でした。速度は測ってはいないですがストレスなく使えました。建物の中だと場所によっては不安定なところもありましたが概ね大丈夫なレベル。ただしメトロは駄目ですね。電車の中はもちろん繋がらず、ホームでも駄目なところがあった気がします。

そしてなんといってもGoogle Mapが使えるのが便利だと再認識。カンファレンスの会場まで迷わずにたどり着けました。もちろん街歩きにも。

他の会社のSIMだと、同価格で1GBとかなので、Free Mobileはかなりお得でおすすめです!

OpenAM 13の新機能を試してみた(2) - Stateless Session

OpenAM

前回に続いてOpenAM 13の新機能の紹介、その2です。今回は Stateless Sessionという新しい認証セッション方式について紹介。

従来のOpenAMの認証セッションの仕組み

Stateless Sessionの前に、従来のOpenAMの認証セッションの仕組みについて説明しておきます。

OpenAMにログインすると、その認証情報(ユーザIDなど)はセッションとしてメモリ上に保存されています。ブラウザにはデフォルトだとiPlanetDirectoryProというキー名でCookieにセッションIDが格納され、サーバ上のメモリと紐付けられてログイン状態を維持し続けます。セッション情報をサーバ側で保持するこの方式を、Stateful SessionとOpenAMでは呼んでいます。

OpenAM一台構成だとこれでよいが、OpenAM複数台構成だと、サーバダウンなどが起きてしまうとメモリ上にセッション情報がないサーバにリクエストが振り分けられてしまい、ユーザは再ログインが必要になります。一部のサーバがダウンしても再ログイン不要で継続可能にするためには、セッション情報をOpenAMサーバ間で共有する必要があります。

セッションフェイルオーバー (OpenAM 10.0以前)

そこで出てくるのがOpenAMのセッションフェイルオーバーの機能。セッション情報を複数台のOpenAMサーバで自動的に共有することで、たとえ別のサーバにリクエストが振り分けられてもログイン状態を維持し続けることができるようになります。

しかしながら、OpenAM 10.0 以前だとこの機能を利用するのはとても大変。下図はOpenAMのフォーク元のOpenSSOのドキュメントにあるものですが、セッション情報を格納するBerkeley DBとMessage Queue Brokerを用意するといった大掛かりな仕組みが必要でした。

https://docs.oracle.com/cd/E19681-01/820-3320/images/famsfoscenario.gif

出所: Overview of OpenSSO Enterprise Session Failover (Sun OpenSSO Enterprise 8.0 Installation and Configuration Guide)

なお、この方式のセッションフェイルオーバーの検証を2012年にSCSK社が行っています(報告書はこちら https://www.scsk.jp/lib/product/oss/pdf/oss_6.pdf )。報告書の中で、この方式について「構成要素が複雑になる、OpenAM を3台以上に増やした際にスケールするかなどの懸念もある」とコメントされています。

新しいセッションフェイルオーバー(OpenAM 10.1.0-Xpressより)

そこでOpenAM 10.1.0-Xpressでは、より簡単に扱えるセッションフェイルオーバーの仕組みが導入されています。OpenAMには元々設定情報の格納用に組み込みOpenDJ(OpenDSのフォーク)が同梱されていました。OpenAMを起動すると内部で一緒に起動しています。この組み込みOpenDJのマルチマスタレプリケーション機能を利用して、セッション情報を共有する方式が追加されました。というわけで追加のソフトウェアセットアップは不要で、チェックボックス1つの設定で簡単にセッションフェイルオーバー機能を利用できるようになりました。

クラウド・IoT時代の新たな課題

従来のSSOの要件として使う分にはそこまでOpenAMサーバは必要なくこれで十分ですが、グローバル企業がクラウドを活用して大規模なSSOを行いたい(リージョンまたがり)とか、PC・モバイルだけでなく様々なデバイスからの接続も今後増えるIoTを考えると、この方式では限界が来そうです。OpenAMのドキュメントでは、より大規模な構成だとOpenDJサーバを別途立てて、レプリケーションの通信先を減らすような構成案が提示されています。レプリケーション用のOpenDJを別途たてて負荷分散をするという方式です。
でもこれだと管理するサーバも増えてしまい、運用がとても大変。クラウドを活用して柔軟にスケールさせるのも辛そうですし、OpenDJもそこまでスケールするのか不安です。

そこでStateless Sessionですよ

そこでOpenAM 13から新たに追加された方式の、Stateless Sessionの出番。こいつはサーバ側でセッション情報をメモリで持ち続けません。OpenAMサーバはステートレスになります。代わりにユーザのCookieにセッション情報を持ちます。元々Cookieに認証セッションのIDを持っていました。ここに、サーバ側で持っていた認証セッション情報が埋め込まれるようになります。下図はStateful SessionとStateless SessionのCookieで持つ値の違いを示した物ですが、Stateless Sessionの場合は、ey....fQ. の部分が追加されており、これがセッション情報なわけです。

http://openam.forgerock.org/doc/bootstrap/admin-guide/images/thumb_iPlanetDirectoryProCookie.png
出所:http://openam.forgerock.org/doc/bootstrap/admin-guide/index.html#chap-session-state

従来の方式のように、セッションフェイルオーバのためにレプリケーションを行う必要もなくなりますので、容易にスケールさせることができます。

タイムリーなことに、ForgeRockより性能を検証した情報が昨日出ました。ちょうど今開催中のIdentity Summitのセッションスライドとして公開されています。このスライドのp.15から性能に関する記述があります。

www.slideshare.net

OpenAM2台構成でStateful SessionとStateless Sessionの比較を行っており、

  • Stateful Session: 3000ログイン/秒
  • Stateless Session:5000ログイン/秒
と良好な結果が出ている模様です。

セキュリティは大丈夫?

Cookieは簡単に中身を覗いたり、改ざんができてしまうのでセキュリティは大丈夫か気になるところですが、その点は大丈夫。保存形式にはRFC化されたJWTが使われます。デジタル署名による改ざんチェックや暗号化もサポートされます。
ただし、動作を試したNightly Build版だと、署名や暗号化を有効にしたとしてもJWTのalgがnoneとなっていました・・・。正式版を待ちましょう。

その他注意点

Stateless Sessionも万能ではなく、上で紹介したスライドでも述べられているように、同時ログイン数制御なんかはできなくなります。また、OpenAMのクロスドメインSSO機能とSAMLでも制約があるようで、その場合はStateless Sessionは非推奨とのことです。
あと、言及が見られないですが、OAuth2のアクセストークンは従来通りレプリケーションが必要と思われます。これはMSなどのサービスのように、アクセストークン自体をJWT化する対応が必要になってくるかと、、、ここは是非対応してほしいですね。
 

OpenAM 13の新機能を試してみた(1) - 2段階認証

OpenAM

今年のQ4にリリースが予定されているOpenAM 13ですが、Nightly Build版で新機能を試せそうな状態だったので動作を確認してみました。
なお、新機能についてはSNAPSHOT版のリリースノートに一覧が書かれ始めています。

今回のエントリでは2段階認証(Two Step Verification) の新機能について簡単に紹介。

※2015/10/06時点のNightly Build版で確認しています。

2段階認証(Two Step Verification) の新機能

OpenAM 12でも、OTPを使った認証モジュールと組み合わせることで2段階認証自体は可能でした。OpenAM 13からは、デバイス登録の機能も組み込まれたようです。詳細はSNAPSHOT版のマニュアルに書かれていますが、初回ログイン>QRコードでデバイス登録>デバイスでOTP発行してログイン というよくある初期登録のフローがOpenAM単体でできるようになっています。

Nightly Build版で動作確認

Nightly Build版をダウンロードしてインストールし、Procedure 2.10. To Create an Authentication Chain for Two Step Verificationの通りに設定してもまだ動作せず。

エラー情報とソースをしばらく眺めるとちょいと変更すれば動作しそうだったので試してみた。結論から言うと、デバイス登録とOTPによるログインのフローを動作させることはできました。

動作確認

OTP発行にはAndroid版のGoogle Authenticatorを利用して確認しました。

  1. 事前に作成しておいたアカウント(test1)でログインするf:id:Hirohiro:20151008091749p:plain
  2. OTP入力の画面に遷移しますが、まだデバイス未登録なのでここで REGISTER DEVICE をクリックf:id:Hirohiro:20151008091803p:plain
  3. QRコードが表示されるので、Google Authenticatorでキャプチャして設定を行うf:id:Hirohiro:20151008092003p:plain
  4. 次に進み、登録したGoogle Authenticatorで発行されたOTPを入力してSUBMITf:id:Hirohiro:20151008092554p:plainf:id:Hirohiro:20151008092013p:plain
  5. ログインが完了しユーザのプロフィールページに無事に遷移

設定時の注意点

Procedure 2.10. To Create an Authentication Chain for Two Step Verification通りなのだが、認証モジュールは ForgeRock Authenticator (OATH) を選ぶこと(OATHだと駄目)。

その他注意点

  • XUIのログイン画面はどうもまだ安定していなくて、特にレルムを使うとブラウザからのREST API呼び出しでエラーが出まくります。試す場合は、デフォルトのトップレベルのレルム(/)を使ったほうが良いです。
  • 2段階認証をトップレベルレルムのデフォルトの認証連鎖に設定すると、amadminでログインする際にもOTPが求められ管理コンソールにログインできなくなります。その場合は下記URLのようにDataStoreモジュールを直接指定してログイン画面を開けばOK。

Nightly Build版で動作させるための修正点

Nightly Build版で動作させるための変更内容についても書いておきます。HTMLファイルを置くだけでいいので、OpenAMのビルドは不要です。正式リリースまでにはきっと直るはず。

  • (OPENAM_WAR)/XUI/templates/openam/authn/ 以下に AuthenticatorOATH2.html を作成。
<div class="container" id="oath-container">
    {{#if reqs.header}}
    <div class="page-header">
        <h1 class="text-center">{{reqs.header}}</h1>
    </div>
    {{/if}}
    <form action="" method="post" class="form col-sm-6 col-sm-offset-3" autocomplete="off">
        <fieldset>
            {{#each reqs.callbacks}}
            <div class="form-group">
                {{callbackRender}}
            </div>
            {{/each}}
        </fieldset>
    </form>
</div>
  • 同じ内容で、AuthenticatorOATH4.html、AuthenticatorOATH5.html、AuthenticatorOATH7.htmlを同フォルダに作成。

JenkinsでGitブランチの自動ビルドを行うMulti-Branch Project Pluginのパッチ書いた

Jenkins

d.hatena.ne.jp

JenkinsでGitブランチを自動追従してビルドさせるには、上記の記事で紹介されているMulti-Branch Project Pluginが便利なんだけど、不満点がいくつかあるのでパッチを書いてみた。

パッチ内容

ブランチ削除時に対応するジョブを問答無用で削除されないようにオプション化

Multi-Branch Project PluginはGitでブランチを作成すると自動的に専用のジョブを作成してくれるのだが、ブランチ削除時にはそのジョブは即削除される仕様になっている。ビルド履歴や成果物も消えてしまい運用によっては困る場合があるので、削除のかわりにジョブの無効化を行うオプションを追加した。

Bambooみたいにブランチ削除から一定時間後に自動削除、というオプションもあると良いかも? 今後要望あれば追加するかも。

PRはこちら。作者ではない方がレビューしてくれてOKもらえているものの、作者からの反応はなし。というかPR出した後にすぐにこちらの考慮不足を作者が指摘してくれたのだが、対応版をpushするとなぜかその指摘コメントが削除されていた。どういうこっちゃ。github.com

新規ブランチ発見時にジョブを実行しないようにオプション化

Multi-Branch Project PluginはGitでブランチを作成すると自動的に専用のジョブを作成し、その際に必ず作成したジョブを実行する仕様になっている。Issue-37で要望が挙がっているようにこの挙動をオプション化した。

PRはこちら。コメントはなしで放置されてる。github.com

Gitリポジトリ設定でShallow Cloneなどの詳細な設定を可能に

Multi-Branch Project PluginSCM API経由でGitリポジトリ設定を行うようになっている(なのでGit以外のSCMにも対応している)。しかしSCM API経由だとGitリポジトリの場合、Shallow Cloneなどの詳細な設定が残念ながらできない。これはIssue-82で要望が挙がっているのだが、SCM APIの実装側、つまりGit SCM plugin側で実装が必要になる。
工夫すればMulti-Branch Project Plugin側で対応することもできるので、駄目元でPRを送ってみたところやっぱ即リジェクトされた

という訳でGit SCM plugin側に今度はPRを送ってみた。こちらはLGTMのコメントが頂けてマージしてもらえそうな感じ??github.com

カスタムビルド

Multi-Branch Project Pluginは作者が忙しいのか反応が無いのいで、当面Forkプロジェクトでカスタムビルドを行うことにした。あと、Git SCM pluginもマージされるまで用意。Drone.ioを使ってビルドしています(Jenkins Pluginなのに)。

初めてDrone.io使ってみたけどシンプルで良い。

GitlabをDocker上に引っ越し

Git Docker Gitlab

Gitlabを立てているサーバのHDD残容量がかなり少なくなったため、前々からやろうと思っていたけどできていなかった、Docker上への引っ越しをようやく実施しました。
Docker上で構築しておくと、アップグレードも簡単というメリットがあります。今回、移行後についでに7.0.0⇒7.6.2へとアップグレードも行いましたけどかなり楽でした。

以下、移行手順のメモです。

移行先Gitlabの構築

移行先となるGitlab本体は再構築する必要がありますが、Docker上に構築するのでDocker Hubにあるものを利用して簡単に構築できます。今回は一番利用されていそうなsameersbn/gitlabを使用させて頂きました。

データベースにはMySQLを使っていたので、移行後もMySQLを使うことにしました。このMySQLも今回、Docker上に移行します。

なお、MySQLやGitリポジトリデータなど、各種Dockerコンテナのデータで永続化する必要があるものは、Dockerホストに配置する方式にしています(docker runの-vオプションでディレクトリを指定)。

MySQLを構築&起動

sameersbn/gitlab のドキュメントに書かれているDockerのLink機能を使った方法で構築します。

# MySQLコンテナ取得
docker pull sameersbn/mysql:latest

# Dockerホスト上で永続データの配置ディレクトリ作成
mkdir /home/core/gitlab_mysql_data

# MySQLコンテナ起動
docker run --name=gitlab-mysql -d \
  -e 'DB_NAME=gitlabhq_production' -e 'DB_USER=gitlab' -e 'DB_PASS=password' \
  -v /home/core/gitlab_mysq_data:/var/lib/mysql \
  sameersbn/mysql:latest

Redisを構築&起動

GitlabではRedisも必要になります。MySQLと同様に、DockerのLink機能を使った方法で構築します。

# Redisコンテナ取得
docker pull sameersbn/redis:latest

# Redisコンテナ起動
docker run --name=gitlab-redis -d sameersbn/redis:latest


MySQLとRedisの準備ができたら、Gitlab本体の構築です。安全に移行するために、移行元と同バージョンの7.0.0でまずは構築(docker pull)します。後はdocker runの--linkオプションでMySQLとRedisに繋げて起動させればOKです。ここが一番重要なポイントで、docker runコマンドの-eオプションで、Gitlabの各種設定(メール送信設定やタイムゾーン設定など)を行う必要があります。
利用可能なパラメータ一覧はこちらにあります。
また、-v /etc/localtime:/etc/localtime:ro を付与することにより、ホストOSのタイムゾーンをDockerコンテナに同期させています。Gitlabコンテナに用意されているcronによるバックアップを走らせる場合は、タイムゾーンをそろえておいたほうが設定しやすいです。

Gitlab 7.0.0を構築

# Gitlabコンテナ取得
docker pull sameersbn/gitlab:7.0.0

# Dockerホスト上で永続データの配置ディレクトリ作成
mkdir /home/core/gitlab_data

Gitlab 7.0.0を色々オプションをつけて起動

docker run --name=gitlab -d --link gitlab-mysql:mysql \
  --link gitlab-redis:redisio \
  -p 10022:22 -p 80:80 \
  -e "GITLAB_PORT=80" -e "GITLAB_SSH_PORT=10022" \
  -e "DB_USER=gitlab" -e "DB_PASS=password" \
  -e "DB_NAME=gitlabhq_production" \
  -e "GITLAB_HOST=xxx.xxx.xxx.xxx" -e "GITLAB_EMAIL=gitlab@example.org" -e "GITLAB_SUPPORT=support-gitlab@example.org" \
  -e "SMTP_ENABLED=true" \
  -e "SMTP_HOST=yyy.yyy.yyy.yyy" -e "SMTP_PORT=25" -e "SMTP_STARTTLS=false" -e "SMTP_DOMAIN=example.org" \
  -e "GITLAB_PROJECTS_VISIBILITY=public" \
  -e "GITLAB_BACKUPS=daily" -e "GITLAB_BACKUP_EXPIRY=172800" \
  -e "NGINX_MAX_UPLOAD_SIZE=100m" \
  -e "GITLAB_TIMEZONE=Tokyo" \
  -v /home/core/gitlab_data:/home/git/data \
  -v /etc/localtime:/etc/localtime:ro \
  sameersbn/gitlab:7.0.0

上記例だと、80ポートを外部に公開しているので、これで http://DockerのホストIP:80 にアクセスするとGitlabにアクセスできるようになります。移行先のGitlabが構築できましたので、続いてデータの移行を行います。

データ移行

Gitlabにはバックアップとリストア用のコマンドが用意されているのでデータの移行は簡単です。ドキュメントはここ

バックアップの実行

移行元の環境で実行します。

sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production

バックアップしたファイルを再構築したサーバに転送

scp /home/git/gitlab/tmp/backups/1420041115_gitlab_backup.tar core@xxx.xxx.xxx.xxx:/home/core/gitlab_data/backups/

バックアップファイルでリストア実行

移行先のDockerホストでの作業になります。リストアコマンドはGitlabに付属しているrakeタスクを使うのですが、docker pullしたGitlabコンテナを利用して実行することができます。

# 一旦動いているGitlabコンテナを停止
docker stop gitlab

# リストアコマンドとともにGitlabコンテナを新規に開始
docker run --name=gitlab -it --rm --link gitlab-mysql:mysql \
  --link gitlab-redis:redisio \
  -e "DB_USER=gitlab" -e "DB_PASS=password" \
  -e "DB_NAME=gitlabhq_production" \
  -v /home/core/gitlab_data:/home/git/data \
  -v /etc/localtime:/etc/localtime:ro \
  sameersbn/gitlab:latest app:rake gitlab:backup:restore

上記コマンドを実行するとどのファイルでリストアするのか聞かれるので、移行対象のファイル名を入力して実行します。
また、下記のように最後にauthorized_keysの上書き確認がされるので、yesで続けます(リストアのキーで上書きする)。

This will rebuild an authorized_keys file.
You will lose any data stored in authorized_keys file.
Do you want to continue (yes/no)? yes

移行確認

docker start gitlab

で再度gitlabを起動し、データが移行されていることを確認して完了です。

バージョンアップ

7.0.0から最新の7.6.2にアップグレードします。
こちらに手順が書かれています。

やることは簡単で、動いているGitlabコンテナを停止・削除し、バックアップの取得、新しいバージョンのGitlabコンテナでrunしてあげるだけです。初回起動時に、自動的にデータベースのマイグレーションが行われます。

# 新しいバージョンのGitlabコンテナ取得
docker pull sameersbn/gitlab:7.6.2

# 既存のコンテナ停止&削除
docker stop gitlab
docker rm gitlab

# バックアップの実行
docker run --name=gitlab -it --rm --link gitlab-mysql:mysql \
  --link gitlab-redis:redisio \
  -e "DB_USER=gitlab" -e "DB_PASS=password" \
  -e "DB_NAME=gitlabhq_production" \
  -v /home/core/gitlab_data:/home/git/data \
  -v /etc/localtime:/etc/localtime:ro \
  sameersbn/gitlab:latest app:rake gitlab:backup:create

# 新しいバージョンのGitlabコンテナを起動
docker run --name=gitlab -d --link gitlab-mysql:mysql \
  --link gitlab-redis:redisio \
  -p 10022:22 -p 80:80 \
  -e "GITLAB_PORT=80" -e "GITLAB_SSH_PORT=10022" \
  -e "DB_USER=gitlab" -e "DB_PASS=password" \
  -e "DB_NAME=gitlabhq_production" \
  -e "GITLAB_HOST=xxx.xxx.xxx.xxx" -e "GITLAB_EMAIL=gitlab@example.org" -e "GITLAB_SUPPORT=support-gitlab@example.org" \
  -e "SMTP_ENABLED=true" \
  -e "SMTP_HOST=yyy.yyy.yyy.yyy" -e "SMTP_PORT=25" -e "SMTP_STARTTLS=false" -e "SMTP_DOMAIN=example.org" \
  -e "GITLAB_PROJECTS_VISIBILITY=public" \
  -e "GITLAB_BACKUPS=daily" -e "GITLAB_BACKUP_EXPIRY=172800" \
  -e "NGINX_MAX_UPLOAD_SIZE=100m" \
  -e "GITLAB_TIMEZONE=Tokyo" \
  -v /home/core/gitlab_data:/home/git/data \
  -v /etc/localtime:/etc/localtime:ro \
  sameersbn/gitlab:7.6.2

Gitlabはバージョンアップ時に依存ライブラリが増えたりとかRubyのバージョンアップが必要になるケースがあるのですが、このようにDocker上で構築しておくと、docker pullするだけでアップグレード完了なのでかなり楽になるかと思います!!

Bootstrap3のナビゲーションバーをhover化する方法

bootstrap3

javascript - Bootstrap Dropdown with Hover - Stack Overflowにまさにやりたいことが書いてあった。
ここに書いてある通り、

.dropdown:hover .dropdown-menu {
  display: block;
}

と書くのが一番シンプルそう。ただし、モバイルサイズの時もこのhoverが効いてしまってイマイチなレイアウトになっちゃうので、下記のように@mediaでPCサイズの時だけ有効になるように設定すると良さげ。

@media (min-width: 768px) {
    .dropdown:hover .dropdown-menu {
        display: block;
    }
}

OpenIDM+MySQLで使う場合に注意

OpenIDM

OpenIDMは内部リポジトリをデフォルトのOrientDBからMySQLに変更できますが(というかプロダクション環境だとMySQLが推奨)、付属しているJDBC用の設定を使うとうまく動かないところがあります。

具体的には、repo.jdbc.json で定義されている "get-by-field-value" というクエリーなのですが、

WHERE prop.propkey='/' + ${field}

のように、文字列を+で連結しちゃっています。MySQL的にはこれはNG。(参考:「文字列連結」のつもりでうっかり + を使うな!!)

正しくは、下記のようにCONCAT関数を使う必要がある。

WHERE prop.propkey=CONCAT('/', ${field})

JIRA:OPENIDM-1281で報告したところ、すぐに対応してもらえたようです。Nightly版だと直っているかもしれません。