KeycloakのSPDY_PROTOCOL_ERRORの原因と対策について

ふと、QiitaのKeycloak関連の記事を眺めていたら、こんな記事を見つけました。

冗長構成にしたKeycloakの言語設定を日本語にすると、Keycloakの前段にあるロードバランサーやプロキシーサーバー(例えば、Apache)がKeycloakからのレスポンスをクライアントに返さなくなるという内容です。

この記事のコメント欄でosakichiさんが詳しくコメントをされているように、「HTTPレスポンス分割」攻撃の脆弱性に対するロードバランサーやプロキシーサーバーの対策が影響しています。

プロキシやロードバランサではレスポンス分割攻撃への対策のため、
制御文字を含むレスポンスヘッダーがあった場合はエラーとして転送を中止しますが、
日本語の文字化けしたP3Pヘッダーの中身にはコード0x01の文字が含まれているため
この攻撃対策に引っかかってエラーとなってしまっているようです。

Keycloakの日本語プロパティ・ファイル(messages_ja.properties)からp3pPolicyの定義を削除すれば事象は解決するのですが、「そもそもレスポンスヘッダーに誰がこんな余計な日本語化したんだ?」と一瞬思ってすぐに気づきました…

yarakasu

自分でした…(ご迷惑おかけしてすいません)。直接的な原因となったのは、私の日本語対応のプルリクエストで、4.6.0.Final以降で発生します。

ということで、修正してプルリクエストすることにしました。

修正するにあたって、まずはこの問題をJIRAに登録する必要があります。が、念のためJIRAを検索してみたら、既に問題が登録されていました。やはり、日本の方が登録したようです。

この問題でお困りの方は、JIRAにログインして「Vote for this issue」をクリックして下さい。

次に修正ですが、その前にP3Pヘッダーが何であるか調査しないといけません。実は正直言って私は初耳でした。そもそもP3Pヘッダーって何なんでしょう?調べてみたところ、この仕様自体はかなり昔からあるようです。日本語化された仕様書がありましたが(以下)、それによると2000年に勧告されたもののようです。

こんな仕様、普及しないようなぁと思いましたが、実際その通りで、以下の記事にあるように、2012年の時点で既に形骸化されていたようです。

唯一サポートしていたMicrosoftのブラウザー(Edge)も、現在は「deprecated」(非推奨)の扱いになっているようです。詳しくはWikipediaのP3Pのページを読んでみて下さい。

ということなので、古いバージョンのMicrosoftのブラウザーでKeycloakを利用しているユーザーがP3Pのポリシー違反エラーにならないように、あるバージョンからKeycloakが修正された(KeycloakがP3Pヘッダーを付けるようになった)のではないかと予想できます。そこでKeycloakのJIRAを調べてみると、やはりありました。

このページでレポーターも言っている通り、P3Pヘッダーの値は何でもいいということになります。

If I have understood correctly IE doesn’t really care about the header’s value as long as it has been set.

もちろん日本語化する必要もありません。対策の仕方の選択肢として、ヘッダー値を適切にエンコードするということも考えられましたが、それよりも多言語対応をやめた方がいいと私は判断しました。どちらにしてもブラウザの動作に変わりは無いし、仮にこのメッセージがユーザーの目にとまったとしても、意味は分からないでしょうし。

ということでプルリクエストしておきました。

2019/05/16 追記 :

2019/05/15にmasterにマージされました。7.0.0以降であれば、この問題は出ません。

 

 

え、staticイニシャライザーが呼ばれない?…ん、2回呼ばれる?

Qiitaにこんな記事を書いたのですが、実はこのとき調査を混乱させるもう一つの謎の事象がありました。

 Qiita – ログレベルが突然変わる謎の事象を追う ~ あるOSSサポートエンジニアの1日

記事が少し長くなることと話の軸がぶれることを考慮して、その事象についてばっさりカットしたのですが、今後同じような問題に遭遇する人がいるかもしれないので、私のブログで紹介します。


記事の簡単なおさらい

Qiitaの記事の問題となったのは以下のコードでした。

final static String RESTLET_LOGGER_NAME = "org.restlet.Component.LogService";

static {
    Logger logger = Logger.getLogger(RESTLET_LOGGER_NAME);
    logger.setLevel(Level.OFF);
}

このコードはロガーのログ出力を抑止する意図でWebアプリケーションに追加されたコードで、そのWebアプリケーションでは必ず実行されるはずなんですが、それにもかかわらず、ロガーがログを出力したという問題です。

原因は、弱参照しかないロガー(ログレベル=OFF)をGCが回収して、新たにロガーが生成されたことでした。

gc_wrlggr

で、この調査の際に、原因を探るために、以下のような修正を行って動作検証をしました。

final static String RESTLET_LOGGER_NAME = "org.restlet.Component.LogService";

static {
    System.out.println("ServiceEndpointApplication#clinit start");
    Logger logger = Logger.getLogger(RESTLET_LOGGER_NAME);
    logger.setLevel(Level.OFF);
    System.out.println("ServiceEndpointApplication#clinit end");
}

そして、tail -fコマンドでTomcatのログ(catalina.out)を監視しながら、JMeterで大量のリクエストを送信したわけです。

もう一つの謎

実はそのとき、tail -fコマンドの結果に以下のような出力があったのです(Qiitaの記事には書きませんでしたが)。

ServiceEndpointApplication#clinit start
ServiceEndpointApplication#clinit end
ServiceEndpointApplication#clinit end

endが2回出力されています。もちろん、System.out.println("ServiceEndpointApplication#clinit end");が他のソースコードにもあったということではありません。staticイニシャライザーにSystem.out.println();を追加した後でこの奇妙な出力が得られました。もし、次のようにstartが2回出力されたのであれば、そこまで驚きはしませんでした。

ServiceEndpointApplication#clinit start
ServiceEndpointApplication#clinit start
ServiceEndpointApplication#clinit end

startが2回出力されたのであれば、異なるクラスローダーがこのクラスをロードして、一方でエラー(ExceptionInInitializerError)が発生し、それをcatchして無視した可能性も考えられます(まあ、それも考えにくいですが…)。しかし、endが2回出力されたのであれば、そういうレベルの問題ではありません。

このとき、みなさんはどういった可能性を疑いますか?

私がそのとき考えたのはJVMのバグでした。それしか無いように思えました。


あっけなく分かった原因

その後、JVMのバグを探してみたり、同件の事例が無いかググってみましたが、それらしいものは見つからず。ふと、catalina.outviで見たときに気がつきました。実は、ServiceEndpointApplication#clinit endは1回しか出力されていないということに。つまり、JMeterで瞬間的に大量のリクエストを送信した結果、tail -fコマンドが1回余計にServiceEndpointApplication#clinit endを出力したということのようでした。

では、なぜtail -fコマンドが1回余計に出力したのかというと…

実はそれは分かっていません(今回はそれ以上原因追求する必要はなかったので…すいません)。このあたりに詳しい方はぜひ教えて下さい。

追記

シェルの達人さとうさんから以下のようなコメントをいただきました。ありがとうございました~

スタンフォード大学の機械学習コースを修了しました!

たまには技術的ではないことも。

3ヶ月ほど前から始めた「Coursera(コーセラ)」というサイトのスタンフォード大学の機械学習コースを修了しました!

Screenshot_2018-08-28 W9LXN6DRD2UZ(1)

ちょっと覗いてみたら楽しそうだったので、そのまま最後までやりきりました。

AIについては、以前から興味はすごくあったのですが、今の仕事とは直結しないので、なかなか手を出せずにいました。それが、ふとしたきっかけで始めてみると、これがなかなか面白くて…学生時代は極端な理系人間だったので、数学が絡む分野は楽しいし、理解しやすいですね。

ここ数年は認証系の仕事がメインで、認証プロトコルのRFCを読みこむような、決して楽しいとは言えない(忍耐力が求められる)作業(苦行)が多かったので、久しぶりに楽しく学ぶことができました。これまでにやってきた仕事とは今のところあまり関係はないのですが、今はこの分野についてさらに勉強してみたいという気持ちです。認証の分野へのAIの活用事例(顔認証とか異常検知による再認証とか)もすでにたくさんありますし、新しい可能性もいろいろと考えられそうです。

ちなみにこのコースですが、修了した他の皆さんも言ってる通り、とてもよくできています。高校レベルの数学がちゃんと理解できている人であれば、問題なくついていける内容だと思います(おそらく)。一応3ヶ月で終わるようなスケジュールが推奨されています。ただ、コース内のところどころで、解説の際に証明が省略されているので、過程が気になってしまう人はそれを調べる時間も必要です。

それから、意外と英語力がいるかもしれません。大半は日本語字幕が出ますが、全然あってないこともありますし、徐々に字幕と内容がずれたりとか、いろいろな罠があります(一切話をしていないことについて言及したり)。なので、ネイティブのスピードで英語が聞ける人は短時間で終了できると思いますが、そうでない人はそれなりに苦労するかもしれません。

意外と知られていない、Tomcatのエラーページのバージョン番号の簡単な隠し方

Tomcatのデフォルトのエラーページのバージョン番号の隠したいという要望は結構あります。これについて調べてみると、「jarファイルを解凍して、その中のプロパティファイルを変更して、…」という情報が多いようですが、もっと簡単な方法があります。日本語の情報がほぼ無かったので、いくつかの補足情報とともにここに書いておきます。

設定の仕方

結論から言うと、conf/server.xmlに以下のValveの定義を追加して、Tomcatを再起動するだけです。

<Valve className="org.apache.catalina.valves.ErrorReportValve" showReport="false" showServerInfo="false"/> 

設定前後の画面の比較

この設定により、以下のようにデフォルトエラーページの表示が変わります。

設定前

1

設定後

2

NPEが発生したことをここに載せる必要があるのか判断は微妙なところですが…Tomcatの仕様上はこうなります。

注意事項(制限事項)

この設定を利用するに当たって、いくつのか注意事項があります。

  • この設定が機能するのは、バージョン7.0.54以降(8.0以降のメジャーバージョン含む)。
  • この設定は、Managerアプリケーションなどで表示されるバージョン情報は変更しない(以下のように)。

3

バージョン7.0.53以前や、Managerアプリケーションなど(具体的には、他にversion.shversion.bat、起動時のログのServer version)でもバージョン情報を隠したい場合は、後述する方法で対応します。

ErrorReportValveの属性について

ErrorReportValveに設定可能な属性は次の2つです。デフォルト(未設定時)はともにtrueです。

  • showReport:falseに変更すると、デフォルトのエラーページにスタックトレースを表示しないようになる
  • showServerInfo:falseに変更すると、デフォルトのエラーページにTomcatのバージョン番号を表示しないようになる

つまり、showReport="false"だけの場合は以下のようになります。

4

showServerInfo="false"だけの場合は以下のようになります。

5

この画面を見ると分かりますが、「注意 原因のすべてのスタックトレースは、のログに記録されています」というように「の」の前が空文字になってしまっています。これはTomcatのバグと言えますね(超軽微ですが)。

そもそもなぜバージョンを隠す必要があるの?

という疑問を持つ人はきっとこのページを見ないと思いますが…バージョンを隠す理由は、セキュリティの対策のためです。一般的に稼動するサーバーの情報は表示しない方が安全です。バージョンを元に、そのバージョンで対策されていない脆弱性を攻撃することもできるので。TomcatのSecurity Considerationsなどでも推奨設定となっています。

7.0.53以前や、Managerアプリでもバージョンを隠したい場合

前述したとおり、ErrorReportValveの設定は、バージョン7.0.53以前や、Managerアプリケーションなどでは機能しません。このような場合では、conf/lib/catalina.jarの中にあるServerInfo.propertiesの次の定義を

server.info=Apache Tomcat/7.0.59

例えば、以下のように変更します。

server.info=Apache Tomcat

これをやるには、jar xf catalina.jarで解凍して…という方法もありますが、Windowsを使っているのであれば、Explzhを使って解凍せずにそのまま編集した方が簡単です。Explzhをインストールしていれば、jarファイルを右クリックして、「Explzh から開く」を選択します。後はファイルブラウザと同様にファイルを選択・修正して、Explzhのウインドウをクリックすれば画面のように反映されます。

6

このプロパティ値を削除するとどうなる?

ちなみに「server.info=」とした場合は空文字「””」がセットされたことになり、以下のように出力が変化します。

7

8

エラー画面は、showServerInfo=”false”だけの場合と同じですね。

このプロパティ自体を削除するとどうなる?

9

見てのとおり、「Apache Tomcat 7.0.x-dev」となります。

server.numberって変更する必要はある?

conf/lib/catalina.jarServerInfo.propertiesを編集しようとすると気がつきますが、「server.info」の下に「server.number」というプロパティもあります。

server.info=Apache Tomcat/7.0.59
server.number=7.0.59.0

これは何かと思って調べてみたのですが、Tomcatの公式ドキュメントには全く言及がありませんでした。ということ、ソースコードを調べてみましたが、これが関係するのは以下の2箇所のようでした。

  • version.batまたはversion.shが出力するServer numberの情報
>version.bat
Using CATALINA_BASE:   "D:\program\Tomcat\apache-tomcat-7.0.59"
Using CATALINA_HOME:   "D:\program\Tomcat\apache-tomcat-7.0.59"
Using CATALINA_TMPDIR: "D:\program\Tomcat\apache-tomcat-7.0.59\temp"
Using JRE_HOME:        "C:\Program Files\Java\jdk1.8.0_172"
Using CLASSPATH:       "D:\program\Tomcat\apache-tomcat-7.0.59\bin\bootstrap.jar;D:\program\Tomcat\apache-tomcat-7.0.59\bin\tomcat-juli.jar"
Server version: Apache Tomcat/7.0.59
Server built:   Jan 28 2015 15:51:10 UTC
Server number:  7.0.59.0
OS Name:        Windows 7
OS Version:     6.1
Architecture:   amd64
JVM Version:    1.8.0_172-ea-b03
JVM Vendor:     Oracle Corporation
  • 起動時のログに表示される以下の情報のServer numberの情報
8 14, 2018 2:51:23 午後 org.apache.catalina.startup.VersionLoggerListener log
情報: Server version:        Apache Tomcat/7.0.59
8 14, 2018 2:51:23 午後 org.apache.catalina.startup.VersionLoggerListener log
情報: Server built:          Jan 28 2015 15:51:10 UTC
8 14, 2018 2:51:23 午後 org.apache.catalina.startup.VersionLoggerListener log
情報: Server number:         7.0.59.0
8 14, 2018 2:51:23 午後 org.apache.catalina.startup.VersionLoggerListener log
情報: OS Name:               Windows 7
8 14, 2018 2:51:23 午後 org.apache.catalina.startup.VersionLoggerListener log
情報: OS Version:            6.1

jarファイルを解凍せずに変更できない?

「jarファイルを変更すると、アップグレード時にリグレッションしてしまうしなぁ」という場合は、lib/org/apache/catalina/utilディレクトリの下に以下の3行を記述したServerInfo.propertiesを配置しておいても同様に機能します。

server.info=Apache Tomcat
server.number=7.0.59.0
server.built=Jan 28 2015 15:51:10 UTC

参考

Pythonアプリでのメールヘッダーインジェクション対策について

Pythonでメール送信機能を実装する場合、標準ライブラリであるsmtplibを利用することが最も一般的な方法のようです。以下のような関数をつくって、それを呼び出せばメールが送信できます。

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

def sendEmail(mail_to, mail_from, subject, content):
    msg = MIMEMultipart('alternative')
    msg['To'] = mail_to
    msg['From'] = mail_from
    msg['Subject'] = subject
    msg.attach(MIMEText(content))

    smtp_server = smtplib.SMTP('smtp.gmail.com', port=587)
    smtp_server.starttls()
    smtp_server.login('xxxxxxxx@gmail.com', 'xxxxxxxx')
    smtp_server.sendmail(mail_from, mail_to, msg.as_string())

簡単ですが、これで問題はないのでしょうか。メールヘッダーインジェクションの脆弱性に配慮する必要はないのでしょうか?


ということで、調べてみました。まずは、正常なメール送信の動作確認のために、以下の実装のスクリプトをつくって(sendmail.pyというファイル名にしました)、メールを送信してみましょう。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

def sendEmail(mail_to, mail_from, subject, content):
    msg = MIMEMultipart('alternative')
    msg['To'] = mail_to
    msg['From'] = mail_from
    msg['Subject'] = subject
    msg.attach(MIMEText(content))
    smtp_server = smtplib.SMTP('localhost', port=2525)
    smtp_server.sendmail(mail_from, mail_to, msg.as_string())

print("Sending an Email.")
try:
    sendEmail('to@example.com', 'from@example.com', 'Test', 'Mail Header Injection Test')
    print("Complete.")
except Exception as e: 
    print("ERROR:" + str(e))

と、その前に、メール送信のために、SMTPサーバーが必要です。今回は単なる検証目的なので、FakeSMTPというテスト用途のSMTPサーバーを使用します。Javaで実装されているので、WindowsでもLinuxでも同様に簡単に起動します。ここからダウンロードして、以下のコマンドで実行します。

$ java -jar fakeSMTP-2.0.jar -s -p 2525

この場合、2525番のポートでSMTPサーバーを起動します。

起動したら、先程作成したsendmail.pyを実行してメールを送信します。

$ python sendmail.py

問題がなければ、「Sending an Email.」、「Complete.」と表示されて、以下のようにFakeSMTPサーバーにメールが送信されているはずです。

Screenshot from 2018-05-09 14-53-26

メールをクリックすると、内容が確認できます。見てのとおり、特に問題はありません。

            Wed, 09 May 2018 14:51:50 +0900 (JST)
Content-Type: multipart/alternative;
 boundary="===============7314347251575062497=="
MIME-Version: 1.0
To: to@example.com
From: from@example.com
Subject: Test

--===============7314347251575062497==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

Mail Header Injection Test
--===============7314347251575062497==--

では、メールヘッダーインジェクションの脆弱性を攻撃できるか検証してみましょう。

sendmail.pyの次の行を、

sendEmail('to@example.com', 'from@example.com', 'Test', 'Mail Header Injection Test')

次のように変更します。

sendEmail('to@example.com', 'from@example.com', 'Test\nbcc: all@example.com', 'Mail Header Injection Test')

件名「Test」の後に、「\nbcc: all@example.com」を追加することで、改行を入れてメールヘッダーとしてbccを追加できるか確認してみます。これができれば、メールヘッダーインジェクションの脆弱性を攻撃される可能性があることになります(メールヘッダーインジェクションがどういうものかについては、いろいろなサイトで解説されているので、このページでは言及しません)。ちなみに、all@example.comはメールが受信可能な適当なメールアドレスと考えて下さい。

では、再度sendmail.pyを実行してメールを送信します。

$ python sendmail.py

新しいバージョンのPythonを使用している場合は、以下のように表示されるはずです(このブログではPython 3.6を使用しています)。

$ python sendmail.py
Sending an Email.
ERROR:header value appears to contain an embedded header: 'Test\nbcc: all@example.com'

メールヘッダー値に別のメールヘッダーを埋め込もうとしたことを検知した旨のエラーメッセージが出力されます。つまり、Python 3.6標準のsmtplibに対しては、メールヘッダインジェクションは攻撃できないということになります。

この検知機能は、いつから実装されているのでしょうか?

GitHubのPythonのリポジトリで、先程のエラーメッセージ「header value appears to contain an embedded header」を検索してみると、このコミットが見つかりました。どうやら、Python 3.1.4から実装されているようです。なので、3.1.3以前のバージョンを使っていなければ、問題は無さそうです(Python 2.x系も最新の2.7には実装(バックポート)されていました)。

ただし、調査していたときに気になるバグレポートを見つけました。

Issue#32606: Email Header Injection Protection Bypass

これによると、「bcc」と「:」の間に空白があるような値を使うことで、メールヘッダーインジェクションの脆弱性を攻撃できるとのことです。つまり、以下のうち前者は検知機能でエラーになるが、後者はスルーしてしまう、ということです。

・’Test\nbcc: all@example.com’ → エラー
・’Test\nbcc :all@example.com’ → スルー

では、さっそく検証してみましょう。sendmail.py'Test\nbcc: all@example.com'の箇所を'Test\nbcc :all@example.com'に修正して、実行します。

$ python2 sendmail.py
Sending an Email.
Complete.

送信できたようです。FakeSMTPが受信したメールをクリックすると、以下のように「bcc :all@example.com」はチェックされること無く、メールヘッダーに付加されてしまいました。

        Wed, 09 May 2018 16:35:27 +0900 (JST)
Content-Type: multipart/alternative;
 boundary="===============6879493970155011648=="
MIME-Version: 1.0
To: to@example.com
From: from@example.com
Subject: Test
bcc :all@example.com

--===============6879493970155011648==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

Mail Header Injection Test
--===============6879493970155011648==--

ただし、先程のバグレポートのコメントにもあるように、RFC 5322ではメールヘッダーのキーの直後には「:」を付けるのが正しい仕様のようです。したがって、「bcc :all@example.com」付きのメールが送信されるかどうかは、SMTPサーバーがRFC 5322を正しく実装しているか次第ということになります。

ということで、SMTPサーバーが「bcc :all@example.com」を「bcc: all@example.com」と変わらず解釈してメールが送信できるかどうかを確認してみましょう。

例えば、GoogleのSMTPサーバーの場合はどうでしょうか?次のようにsendmail.pyを修正し、実行します。

smtp_server = smtplib.SMTP('smtp.gmail.com', port=587)
smtp_server.starttls()
smtp_server.login('xxxxxxxx@gmail.com', 'xxxxxxxx')

bcc宛にはメールが送信されませんでした。Googleはメールヘッダーの「bcc :all@example.com」を無視したことになります。

次に、MSNのSMTPサーバーを検証してみましょう。次のようにsendmail.pyを修正し、実行します。

smtp_server = smtplib.SMTP('smtp-mail.outlook.com', port=587)
smtp_server.starttls()
smtp_server.login('xxxxxxxx@hotmail.com', 'xxxxxxxx')

bcc宛にメールが送信されました。MSNはメールヘッダーの「bcc :all@example.com」をもとにall@example.comにメールを送信したことになります。

このように、RFCを正確に実装していないSMTPサーバーを利用しているPythonプログラムには、メールヘッダーインジェクションの脆弱性が存在するということが言えます。


ところで、何でこんなことを調べたかというと、以前公開したバグだらけのWebアプリケーションのDjango2ベースのクローンをつくっているからです。まだ開発途中ですが、メールヘッダーインジェクションも実装したので、興味がある方はぜひ、チェックしてみて下さい。

EasyBuggy clone built on Django

Keycloakの内部DBにアクセスするには

Keycloakの内部DBにアクセスするにはどうすればいいでしょうか?簡単なので、その方法を紹介します(※今回はKeycloakの超小ネタです)。

その前に、Keycloakの内部DBについて簡単に説明します。Keycloakには、デフォルトでH2というRDBMSが組み込まれています。Keycloakを起動すると、このDBの中にKeycloakの管理コンソールで編集可能な設定データやユーザーデータが登録されます。ユーザーデータは外部のLDAPやActive Directoryで管理することもできますが、デフォルトはここで管理されます。

内部DBへのアクセス手順

任意のSQLクライアントでアクセスできると思いますが、H2には管理コンソールが付属しているので、これを使うのが簡単です。以下のコマンドを実行すると、ブラウザ上で操作可能なH2の管理コンソールが起動します。

$ cd keycloak/modules/system/layers/base/com/h2database/h2/main/
$ java -cp h2-1.4.193.jar org.h2.tools.Console

※h2-1.4.193.jarのバージョン部分は環境に応じて、変更して下さい。

このような画面が表示されるので、

2018-03-01 20.35.51 からのスクリーンショット

以下の値を入力して、「接続」ボタンをクリックして下さい。

入力項目
保存済設定 Generic H2 (Embedded)
設定名 Generic H2 (Embedded)
ドライバクラス org.h2.Driver
JDBC URL jdbc:h2:/opt/keycloak/standalone/data/keycloak;AUTO_SERVER=TRUE
ユーザ名 sa
パスワード sa

※「JDBC URL」は、環境やKeycloak(を起動するWildFly)の起動モードにより異なります。

接続してみると、テーブルがたくさんあることが分かります。テーブル名を見て分かるように、Keycloakの管理コンソールで設定した値はこれらのテーブルで管理されることになります。

2018-02-28 22.06.10 からのスクリーンショット

ちなみにスタンドアローン・モードの場合、KeycloakのDBアクセスの定義は、standalone/configuration/standalone.xmlにあります。

なお、デフォルトではlocalhost以外からこの画面にアクセスできないようになっています。これを許可するには、-webAllowOthersオプションを追加します。

$ cd keycloak/modules/system/layers/base/com/h2database/h2/main/
$ java -cp h2-1.4.193.jar org.h2.tools.Console -webAllowOthers

OpenAMはもはや「Open」ではないのか?

このブログでは、OpenAMについての記事を多く書いてきたのですが、ここ数ヶ月OpenAMの話題については触れていませんでした… というのも、OpenAMを開発しているForgeRock社が、昨年の春頃からOpenAMの公開を大幅に制限したことで、私のOpenAMに対するモチベーションが少し下がってしまったからです。

そのような状況のため、OpenAMはもはや「Open」ではないの?」と思われている方もいるかもしれませんが、そういうわけではありません。OpenAMはOSSです。このページからダウンロードして、利用することができます。CDDL 1.0ライセンスで、StashGItHubにソースコードも公開されています。

ただし、現在、ForgeRock社のサブスクリプションを購入せずに、ダウンロードサイトからダウンロードできるバージョンは、安定バージョンではないメジャーバジョンのみです(OpenAM 11.0.0/12.0.0/13.0.0、Web Agents 3.3.0/4.0.0/4.1.0、Java EE agents 3.1.0/3.3.0/3.5.0など)。また、これらのソースコードはStashに公開されていますが、一部の古いバージョンは公開されていません。安定バージョンはGitHubに公開されている11.0.3が最新です。

OpenAMの過去のメジャーバージョンのリリースは以下の通りでしたが、既に公開されていた13.5.0のソースコードも非公開となりました。

  • OpenAM 11.0.0 – 2013年11月
  • OpenAM 12.0.0 2014年12月
  • OpenAM 13.0.0 – 2016年1月
  • OpenAM 13.5.0 – 2016年7月

OpenAM 12.0.4のような安定バージョンや、新しいOpenAM 13.5.0を使用したい場合は、ForgeRock社のサブスクリプションを購入する必要があります(※私の理解が正しければ、サブスクリプションがなくても、以前ダウンロードした13.5.0のソースコードをビルドしていれば、それを利用することはできます、ただし、現在はMavenのリポジトリからOpenAM関連のライブラリが取得できないようになってしまっています…)。

OpenAM 14.0.0として開発されていたものは、「Access Management 5.0.0」という名称に変更され、2017年3月に非OSSとしてリリースされました。OpenではないAMということです。OpenAMの他にOpenDJなども同様で、以下のように「Open」が無くなり、バージョンは全て5.0.0に統一されています。

  • OpenAM 14.0.0 -> Access Manager 5.0.0
  • OpenDJ 4.0.0 -> Directory Services 5.0.0
  • OpenIDM 5.0.0 -> Identity Management 5.0.0
  • OpenIG 5.0.0 -> Identity Gateway 5.0.0

そして、このリリースは、ForgeRock社がビジネスモデルを変更したことも意味していました。もともとForgeRock社は、100%オープンソースを謳っていたんですが、100%オープンにすることのメリットよりもデメリットの方が大きいと判断したのではないかと想像しています。OpenAM(ForgeRock)が生き残るために必要な選択で、仕方なかったのかもしれません(実際のところは、私には分かりませんが)。

ForgeRock社のフォーラムに以下のようなコメントもありました。

Some ForgeRock sales reps gave me the heads up about this change, and they suggested it might have been due to integrators in Europe that create their own OpenAM releases based off the trunk which they sell themselves. I suppose this is similar to a RHEL/CentOS situation and that ForgeRock is trying to protect their investment as they are a 99% contributor of the source code, which is not the same for RHEL/CentOS.

莫大な費用をかけて、ForgeRock社がエンハンスするOpenAMを使って、何もコントリビュートしない外部の企業が利益を上げるという状況だったので、ForgeRock社とコミュニティはあまり良い関係性とは言えなかったと思います。2016年あたりから、ForgeRock社は徐々に非公開の範囲を増やしていきました。最初はマイナーバージョンのソースコードを非公開にし、ナイトリービルドのソースコードなども非公開にしていきました。そして、2017年の春には、外部のOpenAMユーザーがJIRAにバグを登録することすらできなくしました。

その当時の、OpenAMのユーザーの方とForgeRockのエンジニアであるPeterさんとの間で、以下のようなTwitterのやりとりがありました。

また、Stack Overflowでの「Is OpenAM free software?」という問いに対して、彼はこのような回答をしています。

これらの内容を要約すると、2017年4月3日以降は次のようなForgeRock版コミュニティ版に別れたことなります。

  • ForgeRock版
    • 前述の「Open」が付かない製品(Access Managerなど)
    • ソースコードは、オープンソースライセンスのもとでは提供されない
    • 使用するには、ForgeRock社のサブスクリプションを購入する必要がある
    • ForgeRockだけに属していないすべてのソースコード(例えば、Sunに属していたソースコード、またはオープンソースのコントリビューターが関わったソースコード)は、CDDLライセンスで継続して利用可能
    • 60日間のみ評価することができ
  • コミュニティ版
    • 「Open」が付く製品(OpenAMなど)
    • CDDL 1.0ライセンスのOSS
    • ソースコードはforgerock.github.ioに公開される
    • EOLとなったバージョンの最新メンテナンスリリースがForgeRock版の後にリリースされる予定
      • 今回は、AM5 -> OpenAM 11.0.3がリリースされた
      • 今後は、AM6 -> OpenAM 12.0.x、AM7 -> OpenAM 13.5.x、AM8 -> OpenAM 14.5とリリースされる予定(つまり、AMの最新バージョンがリリースされて約2年後にEOLとなってから、OpenAMとしてコミュニティ版がリリースされる)
    • 予定ではあるが、今後本当にそうなるかどうかは公言されているわけではない
    • メンテナンスリリースであるため、最初は安定しているが、その後のセキュリティパッチは提供されない

今後のOpenAMとのつきあい方

このブログを書いている際に次期バージョンがどうなるのか気になったので、Peterさんに聞いてみました。それによると、今年(2018年)の春に予定されている6.0.0リリースと同時に12.0.4もGitHubに公開される見込みだそうです(確約はできないようですが)。コミュニティ版のOpenAMが今後どのように公開されるのか不透明(というかForgeRock社次第)なので、未来のことについては明言は難しいですが、現状のOpenAMで事足りる場合は、継続してコミュニティ版を使ってもいいと考えます(脆弱性をどう対処するかによりますが)。何か困ったことがあれば、国内のOpenAMサポートをしている企業にサポートを依頼することもできます。私もサポートします。

Access Manager 5.0以降の新しい機能を使用したい場合や、脆弱性を早期に対策していきたいということであれば、ForgeRock社のサブスクリプションを購入する方がいいでしょう。ただし、結構高いです…

OpenAMのフォーク・プロジェクト

OpenAMがクローズな方向に進む中で、OpenAMをフォークしてOSSとして継続しようとする人たちも現れました。そして、このようなプロジェクトが立ち上がりました。そして、OpenAMは改名されて、GitHubに「WrenAM」という名前のOSSとして公開されました。

ただし、今のところ、このプロジェクトが軌道に乗っているようには見えません。状況をあまり把握できていないので、多くを語ることはできませんが、現時点では「うーん…」という印象です。

※注意:ライセンスなどに関して誤った理解があるかもしれません。正しい情報はForgeRock社にお問い合わせ下さい。

Cocha Iconsのトリセツ

先日、記事や設計書、プレゼン資料の作成に役立つネットワークアイコン集を公開しました。このアイコンは自由に使っていただいて構いませんが、悪用やセンスのひどすぎる使用は避けていただくようお願いします。以下に正しい使い方と誤った使い方の例を挙げておきます。

正しい使い方


グリッドを利用する

use

背景のグリッドを活用して適当な場所にアイコンを配置し、矢印はグリッドの方向に平行にしてください。

光の当たる方向を意識する

PowerPointの「左右反転」でアイコンの向きを変えることができます。

WS000024.JPG

ただし、光の当たる方向を意識して色を変更しないと統一感が崩れます。

gg.png

適当な大きさに拡大・縮小する

アイコンのサイズは適宜変更して下さい。

ss.png

動きをつける

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f34333836392f66666462306263322d376337642d366561322d356366392d3165383464616637303639372e676966

動きをつけてみるのもいいと思います。動くバージョンのアイコンもいつか公開したいです。

誤った使い方


以下のような使い方はしないで下さい。

質感の異なるアイコンとの併用

ff.png

これはやめて欲しいです。

作者の意図に反する使い方

dontsitfw

できるだけ作者(私)の意図を汲んで下さい。

理解しがたい動きをつける

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f34333836392f35616235383236652d353033382d646131642d393762362d3961336539383230303133622e676966

PowerPointってついつい遊んでしまいますよね…

遠近法の無視、悪用

big

部分的に巨大化しないで下さい。

最後に


使用する場合はGitHubにスターを付けて、以下の画像を作成物のどこかに付け加えていただくようお願いします。

mark.png

Qiitaの記事で使うのであれば、以下を本文の最後に埋め込んで下さい。

[![mark.png](https://github.com/k-tamura/cocha-icons/raw/master/mark.png)](https://github.com/k-tamura/cocha-icons)

HTMLの場合はこちら:

<a href="https://github.com/k-tamura/cocha-icons">
    <img src="https://github.com/k-tamura/cocha-icons/raw/master/mark.png" alt="mark.png" style="max-width:100%;">
</a>

 

 

TomcatのDBアクセスユーザーのパスワードを暗号化する

TomcatでDBにアクセスする場合、server.xmlcontext.xmlResource要素に以下のような設定をします。

<Resource name="jdbc/TestDB" auth="Container"
    type="javax.sql.DataSource"
    maxTotal="100" maxIdle="30" maxWaitMillis="10000"
    username="javauser" password="javadude"
    driverClassName="com.mysql.jdbc.Driver"
    url="jdbc:mysql://localhost:3306/javatest"/>

この時、password 属性の値には平文のパスワードを書くことになりますが、「これはセキュリティ上良くないのでは?」ということを聞かれて、少し調べてみました。

まず、何よりも先に読むのはユーザーガイドです。

Tomcat 8.5 – JNDI Datasource HOW-TO – Database Connection Pool (DBCP 2) Configurations

ここには、暗号化についての言及はなく、設定例には平文のパスワードが記載されています。

ということで、少し調べてみると、TomcatのWikiの中にそれについて書かれたページがありました。

Tomcat Wiki – Why are plain text passwords in the config files?

この中で、いくつか対策の例が上がっていますが、いずれもパスワードを安全に保持にする方法ではなく、“Security by Obscurity”(隠ぺいによるセキュリティの確保)であり、根本的な対策にはならないと書かれています。むしろ、server.xmlに対する読み取りアクセスをrootユーザーやTomcat実行ユーザーだけに限定することの必要性について言及しています。

このXMLファイルのパスワードを暗号化することにどれだけの効果があるかは分かりません。しかし、それでも、気休め程度でも、平文のパスワードではなく、暗号化されたパスワードを書いておきたい、という方はいるのではないかと思います。そんな方々のために簡単な解決策となるファクトリークラスをつくってみました。これを利用すると、前述のXMLのpassword=”javadude”password=”xe2J0sJ+WlEAX3L4v/EI3A==”のように暗号化されたパスワードを書けます。Tomcat 8.5で動作確認しましたが、他のバージョンでは若干のロジック修正が必要かもしれません(DBCP2のライブラリを使用しているので)。

使用方法

使用方法は以下の通りです。

  1. GitHubからファクトリークラスをgit cloneして下さい。
    $ git clone https://github.com/k-tamura/encrypt-db-password.git
  2. encrypt-db-password/src/main/java/org/t246osslab/tomcat/dbcp/dbcp2の中にあるEncryptionDataSourceFactoryのメソッド encrypt()decrypt() に暗号化、復号化の処理を実装します(実装してありますが、変更して下さい)。:
        public static String encrypt(String source) throws NoSuchAlgorithmException, NoSuchPaddingException,
                InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
            // TODO Remove the following code and write a processing of returning an encrypted string
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(KEY.getBytes(), ALGORITHM));
            return new String(Base64.getEncoder().encode(cipher.doFinal(source.getBytes())));
        }
    
        public static String decrypt(String encryptSource) throws NoSuchAlgorithmException, NoSuchPaddingException,
                InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
            // TODO Remove the following code and write a processing of returning an decrypted string
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(KEY.getBytes(), ALGORITHM));
            return new String(cipher.doFinal(Base64.getDecoder().decode(encryptSource.getBytes())));
        }
    
  1. mvnコマンドでビルドします。
    $ mvn clean package
  2. 生成した依存ライブラリを含むjarで平文のパスワードを暗号化します。
    $ java -jar target/encrypt-db-password-1.0.0-jar-with-dependencies.jar \
     -e [平文のパスワード]
  3. 以下のようにデータソースの定義(server.xmlなど)を編集します:
  4. <Resource name="jdbc/TestDB" auth="Container"
        ・・・
        password="[暗号化したパスワード]"
        factory="org.t246osslab.tomcat.dbcp.dbcp2.EncryptionDataSourceFactory" 
        ・・・
        url="jdbc:mysql://localhost:3306/javatest"/>   
    
  1. 依存ライブラリを含まないjarをTomcatのlibディレクトリにコピーします。
    $ cp target/encrypt-db-password-1.0.0.jar $CATALINA_HOME/lib/
  2. Tomcatを起動します。

Tomcatのバージョンによって、若干の変更が必要かもしれませんが、こんな手順でいいはずです。

EasyBuggy Bootのトリセツ

「EasyBuggy Boot」は、バグだらけのWebアプリケーション「EasyBuggy」のSpring Bootベースのクローンで、JavaのWebアプリケーションで起こりうる様々な問題を、非常に手軽に体験できるWebアプリケーションです。EasyBuggyと機能的な差異はほとんどありませんが、起動方法等に若干異なる部分があります。EasyBuggyよりも新しい技術が使われており、開発やデバッグがしやすくなっています。

easybuggy

EasyBuggy Bootの特徴

基本的にはEasyBuggyと同等の機能を持っているので、用途などはこちらのページを参照下さい。「EasyBuggy」と「EasyBuggy Boot」の主な構成の相違点は以下の通りです。

相違点 EasyBuggy EasyBuggy Boot
ベースとなる技術 Servlet 3.0.1 Spring Boot 1.5.6 (Servlet 3.0.1)
プレゼンテーション層 未使用 (一部 JSP 2.2 + JSTL 1.2) Thymeleaf 2.1.5 (一部 JSP 2.3 + JSTL 1.2)
DBクライアント/サーバー JDBC / Derby 10.8.3.0 Spring JDBC 4.3.9 / Derby 10.12.1.1 (Java 7の場合)、または10.13.1.1 (Java 8の場合)
LDAPクライアント/サーバー Apache DS Client API 1.0.0 / Server 1.5.5 Spring LDAP 2.3.1 / unboundid-ldapsdk 3.2.1
メール JavaMail 1.5.1 JavaMail 1.5.1 (Spring Boot Mailで導入されるJavaMail 1.5.6をオーバーライド)
開発ツール 無し Spring Boot Developer Tools 1.5.6
Java Java 6以上をサポート Java 7以上をサポート

スクリーンショット

以下はメインページで、ブラウザの言語設定によって日本語と英語が切り替えられるようになっています。見ての通り、いろいろなバグを確認できます。

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f34333836392f34633864333931322d383736302d626363312d383834652d3465356131346536373131642e706e67

バグのリンクをクリックすると、そのバグが確認できます。

起動方法

EasyBuggyを起動して、http://localhost:8080にアクセスすると、メインページが表示されます。起動方法には次の3つがあります。

  • javaコマンドで起動
  • mvnコマンドで起動
  • warファイルをサーブレットコンテナにデプロイして起動

それぞれどのように使い分けるか説明します。

● javaコマンドで起動:

もっとも関単にEasyBuggyを起動する方法です。ROOT.warをダウンロードして、次のコマンドで起動します。

$ java -jar ROOT.war

この時、以下のようにJVMオプションを指定しておくと、問題が発生しやすく、またログを確認しやすくなります。

$ java -Xmx256m -XX:MaxMetaspaceSize=64m -XX:MaxDirectMemorySize=90m -XX:+UseSerialGC -Xloggc:logs/gc.log -Xloggc:logs/gc_%p_%t.log -XX:+PrintHeapAtGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M -XX:GCTimeLimit=15 -XX:GCHeapFreeLimit=50 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/ -XX:ErrorFile=logs/hs_err_pid%p.log -XX:NativeMemoryTracking=summary -agentlib:jdwp=transport=dt_socket,server=y,address=9009,suspend=n -Dderby.stream.error.file=logs/derby.log -Dderby.infolog.append=true -Dderby.language.logStatementText=true -Dderby.locks.deadlockTrace=true -Dderby.locks.monitor=true -Dderby.storage.rowLocking=true -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=7900 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -ea -jar ROOT.war

● mvnコマンドで起動:

GitHubからEasyBuggy Bootをgit cloneして下さい。

$ git clone https://github.com/k-tamura/easybuggy4sb 
$ cd easybuggy4sb

その後、次のコマンドでビルドから、起動までが一気にできます。

$ mvn clean spring-boot:run

新しい機能を追加したり、ソースコードを変更して動作確認してみたい場合、このコマンドを実行すればビルドして、すぐに動作確認をすることができます。

● warファイルをサーブレットコンテナにデプロイして起動:

Tomcat以外のコンテナにもデプロイできます。ROOT.warをダウンロードして、PayaraやJettyなどにデプロイすれば、起動します。javaコマンドで起動したときと同様に、JVMオプションを指定することをお勧めします。

開発の方法

STS (Spring Tool Suite)を使用して、EasyBuggy Bootのソースコードを参照したり、開発、デバッグする方法についても載せておきます。STSはEclipseベースのIDEで、Springベースの アプリケーションの開発が簡単にできるようにカスタマイズされています。

  1. このページからSTSをダウンロードして下さい。
  2. GitHubからEasyBuggy Bootをgit cloneして下さい。
    $ git clone https://github.com/k-tamura/easybuggy4sb
    $ cd easybuggy4sb
    
  3. 以下のコマンドを実行します。このコマンドでSTSでの開発に必要なファイル(.projectファイルや.classpathファイル)が作成されます。また、依存するライブラリのソースコードも参照できます。
    $ mvn dependency:sources
    $ mvn eclipse:eclipse
    
  4. STSを起動します。
  5. パッケージエクスプローラからクローンしたプロジェクトをインポートします。「Existing Maven Projects」を選択して、「Next」をクリックして下さい。
    Screenshot-Import .png
    「Root Directory」にクローンしたプロジェクトへのパスを入力し、「Finish」をクリックします。
    Screenshot-Import Maven Projects .png
  6. パッケージエクスプローラのeasybuggy4sbプロジェクトを右クリックし、「Debug As」、「Spring Boot App」と選択すると、デバッグモードでEasyBuggy Bootが起動します。
    Screenshot-Spring - easybuggy4sb-src-main-java-org-t246osslab-easybuggy4sb-Easybuggy4sbApplication.java - Spring Tool Suite .png

ソースコードを修正すると、自動的にリロードされて、修正が反映されることも確認してみて下さい。

リモートデバッグする方法

リモートのEasyBuggy Bootをデバッグする場合は、次のデバッグ設定を追加することでデバッガーをアタッチできます。

  • Connection type: Standard (Socket Listen)
  • Host: localhost
  • Port: 9009

Screenshot-Debug Configurations

 ビルドの方法

以下のコマンドで、実行可能かつデプロイ可能なwarファイルを作成することができます。

$ mvn clean package

注意事項

OutOfMemoryError関連のリンク(特にネイティブメモリを操作するもの)をクリックすると、PCの動作が不安定になる可能性があります。CPUやメモリを制限したVM上で起動するなどしてから、自己責任でリンクをクリックして下さい。