理系学生日記

おまえはいつまで学生気分なのか

忍者TOOLS

Cookieのpath属性に対するブラウザ毎の挙動の違い

ブラウザ毎の Cookie の path に対する挙動の違いによって、検証しているアプリに色々とバグっぽい挙動が出てきたので、あらためて Cookie における挙動をまとめてみます。

検証内容

  • セットするCookie の path 属性に /path、あるいは /path/ を指定した Web アプリを作成
  • 個々のブラウザで、次のパスにアクセスし、Cookie が送信されるかどうかを確認
    • /path
    • /patha
    • /path/
    • /path/a

このへんのアプリだったら Perl とかでサクっと作った方が早いので、PSGI アプリとして実装しました。 だいたい以下のようなかんじです。

#!/usr/bin/env perl
use Plack::Builder;
use Plack::Request;
use Plack::Middleware::Session::Cookie;

my $app = sub {
    my $env = shift;

    my $req = Plack::Request->new($env);
    my $cookies = $req->cookies;
    my $res = $req->new_response(200);

    $res->body(%{$cookies}? 'cookie sent' : 'no cookie');
    $res->finalize;
};

builder {
    enable 'Session',
        state => Plack::Session::State::Cookie->new(path => '/path'); 
        # path は '/path' と '/path/' の双方を試した
    $app;
}

まずは結果を

○ が「Cookie が送信された」、- が「Cookie は送信されなかった」ことを表現しています。 挙動が大きく違っていたのは IE 11 だったんですが、家に Windows がないので、調べることができませんでした。

後述しますが、Chrome と Safari は、RFC 6265 に沿っています。 FIrefox もほぼ沿っているんですが、Cookie の Path の末尾が / の場合に、RFC 6265 と挙動が異なるように見えます。

path=/path

ブラウザ /path /patha /path/ /path/a
Chrome 61.0.3163.100 -
Firefox 56.0.2 -
Safari 11.0 (12604.1.38.1.7) -

path=/path/

ブラウザ /path /patha /path/ /path/a
Chrome 61.0.3163.100 - -
Firefox 56.0.2 -
Safari 11.0 (12604.1.38.1.7) - -

RFC はどうなのか

Cookie について定義しているのは RFC 6265 ですが、この 5.1.4. Paths and Path-Match に、 path をどのようにしてマッチさせるべきなのかの定義があります。

詳細は見て頂ければ良いのですが、まず最初に、cookie の path と比較する対象として、uri-path を定義します。 これは、リクエスト URI のうち、ホスト名の後の / からクエリパラメータの ? より前の最後の / の前までと url-path とされています。 - /hoge/fuga/piyo なら /hoge/fuga - /hoge なら /

これに対し、cookie の path が以下のどれかを満たすなら、そのときに cookie が送出されます。 読めばなるほどなぁと分かるんですが、これを日本語で簡潔に説明すると難しいので、読んで頂いた方が誤解がないと思います。

   o  The cookie-path and the request-path are identical.

   o  The cookie-path is a prefix of the request-path, and the last
      character of the cookie-path is %x2F ("/").

   o  The cookie-path is a prefix of the request-path, and the first
      character of the request-path that is not included in the cookie-
      path is a %x2F ("/") character.

参考文献

known_hostsからエントリを消す

定期的に忘れるんだけど、~/.ssh/known_hosts にあるエントリをスクリプトから消すコマンドは、ssh-kengen -R

そもそも known_hosts とは

接続したことのある各ホストの公開鍵を保存しているファイルで、普通の文脈だとユーザレベルのファイルである ~/.ssh/known_hosts を指します。 サーバレベルのファイルとして、/etc/ssh/known_hosts もあります。

よくあるケースでは、ssh ではじめて特定ホストに接続しようとしたときに、エントリが追加されます。 最初に接続するとき以下のようなことを要求され、「このサーバを信頼するか」どうかを判断することになります。

The authenticity of host 'sshsvr (192.168.0.1)' can't be established.
RSA key fingerprint is 28:d2:80:56:a3:3c:bf:5b:58:f6:68:97:7d:7d:68:db.
Are you sure you want to continue connecting (yes/no)? yes

( より)

ここで信頼する(yes) と答えると、そのサーバの fingerprint が ~/.ssh/known_hosts に追加されます。 ハッシュ化や、証明書のことを考えなければ、だいたい以下のような内容になるはずです。

cvs.example.net,192.0.2.10 ssh-rsa AAAA1234.....= 

このエントリは、その後のサーバ認証に使われます。 クライアント(ssh 接続を行っているホスト) が ssh 接続を行おうとする度に、接続先とこれらのエントリのホスト名(正確には、hostnames) が比較され、 合致したエントリの fingerprint と、サーバが提示してくる fingerprint が一致しているかを基準にして、ssh は接続して良いかを判断します。

fingerprint が一致していない場合は、以下のようなおなじみのメッセージを出力して、接続は拒絶されます。

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!

~/.ssh/known_hosts のエントリを消すユースケース

個人としてのユースケースとしては対象先のサーバをインストールしなおしたとかが多いでしょうし、 サーバのセットアップが自動化された大規模システムにおいては、それらの設定サーバから一時的に ssh 経由でコマンドを投入したい、みたいなケースがあります。

$ vi ~/.ssh/known_hosts でいけるケースは多いんですが、ハッシュ化されているとそういうわけにもいきません。。。

おまえを消す方法

こんなかんじです。はい。

$ ssh-keygen -R 192.168.99.100
# Host 192.168.99.100 found: line 1
/Users/kiririmode/.ssh/known_hosts updated.
Original contents retained as /Users/kiririmode/.ssh/known_hosts.old

参考文献

Google Public NTP の Leap Smear

ずっとエントリ書こうと思っていたんですが、Google Public NTP で使われている Leap Smear についてです。 Leap Smear について書き始めたは良いものの、思っていたよりコンテキストが厚くて、閏秒から話をしないといけませんでした。 そうすると、Leap Smear については相当後の方でないと話ができないので、今回のエントリは、「Leap Smear とは何か」について冒頭で説明したあと、そのほかの関連情報を出すような構成になってます。

なお、閏秒については一応色んな情報を当たってはいるんですが、ちょっと知識不足なところもあるので、なんか間違ってるところもあるかもしれません。そのへんあったらご指摘ください。

Leap Smear

Leap Smear は、過去にグローバルを含む多数の障害を引き起こしてきた閏秒への対策に端を発してます。

誤解を恐れずに言ってしまうと、閏秒による余分な 1 秒を他の「秒」にまぶしてしまおうという NTP サーバ側の技術になります。 わかりやすい例でいえば、「正の閏秒」を「まぶす」場合、一定期間の「秒」の定義を 1.001 秒に改変してしまえば、仮想の 1,000 秒で、実質的に閏秒の「1 秒」を稼ぎ出せます。 このような「まぶされた」時刻情報を NTP サーバからクライアントに伝えることで、クライアント側は、閏秒のような情報を気にする必要がなくなります。

もちろん、Leap Smear はメリットばかりではありません。 Leap Smear によるまぶされた仮想的な秒は、実際の UTC における秒とは異なるため、

  • 一般に、Leap Smear 対応の NTP サーバと非対応のサーバ間では時刻に差が出ます。これらを 1 システム内で混合させると痛い目を見ます (混ぜるな危険)
  • 混ぜるな危険のみならず、正確な「時刻」が必要とされる場合 (法的なものとか) の取り扱いには注意

このLeap Smearing を実装した NTP サーバを一般公開している Google のサーバが Google Public NTP になります。

Google Public NTP において、閏秒の 1 秒を「どの期間」に「どのように」のかは変遷がありました。 2018 あるいは 2019 年に挿入予定の閏秒に対しては、「24 時間」、閏秒の挿入タイミングの前後 12 時間で線形に"まぶす"ようです。

We would like to propose to the community, as the best practice for leap seconds in the future, a 24-hour linear smear from noon to noon UTC. We plan to use this smear starting from the next leap second, which is likely to be in 2018 or 2019.

https://developers.google.com/time/smear

2011 年のブログ には、期間  w に対し、各「秒」に付与する追加秒が  \text{lie}(t) = \frac{1.0 - \cos \pi \frac{t}{w} }{2.0} という話だったので、現在の「線形」という定義であれば、ずいぶんシンプルになったように思います。このへんについては、以下のように言及もありますね。

Compared to a cosine smear, the linear smear is simpler, easier to calculate, and minimizes the maximum frequency change.

無料公開されているので、ぼくも NTP は Google Public NTP に向けています。

f:id:kiririmode:20171015074431p:plain
MacOS X設定

閏秒について

そのまえに閏年

閏年というのは有名なもので、暦と太陽の運行を補正するために、2/29 が挿入される日のことです。有名ですね。

いきなり余談ですけど、閏年は 4 年に一度、オリンピックの年が必ず閏年ってわけじゃねーからな。

よくプログラミングの演習で閏年判定アルゴリズムを書けとかあるけど、wikipedia:閏年 とかで分かるように

( year % 4 ) == 0 && (( year % 100 ) != 0 || ( year % 400 ) == 0)

ていう判定になります。

閏秒

閏秒というのも、上記の閏年とよく似たような概念で、不定期に、日本時間 1/1 または 7/1 の 08:59:60 (09:00:00 ではない) という秒を追加することで、UTC (世界標準時) と UT1 (国際原子時) とのズレを補正します。 通常は存在しない「秒」が出現することになること、通常のテストケースでは実行しないことが多い(※)ので、システム的にはバグの温床になったりします。

※閏秒をアプリに意識させるかどうかは、インフラ/アプリチーム間で切り分けされないことがほとんど。

  • アプリケーションが 08:59:60 という時刻を正しく処理できない
  • (08:59:59 が 2 回発生させる場合、時間遡行が発生するので) 秒の単調増加を前提とするアプリケーションが時刻を正しく処理できない

後者については Resolve Leap Second Issues in Red Hat Enterprise Linux - Red Hat Customer Portal から引用しますが、以下のようなかんじ。

2008-12-31 23:59:59:052549000 UTC    <-- 1st occurrence of the 60th second
2008-12-31 23:59:59:259988000 UTC
2008-12-31 23:59:59:465214000 UTC
2008-12-31 23:59:59:669629000 UTC
2008-12-31 23:59:59:873936000 UTC
2008-12-31 23:59:59:079184000 UTC    <-- 2nd occurrence of the 60th second
2008-12-31 23:59:59:284011000 UTC
2008-12-31 23:59:59:488648000 UTC
2008-12-31 23:59:59:692691000 UTC
2008-12-31 23:59:59:896577000 UTC
2009-01-01 00:00:00:052378000 UTC

なお、正確には、閏秒では秒を追加する「正の閏秒」だけでなく、08:59:59 が存在しなくなる「負の閏秒」も存在しますが、未だかつて実施されたことはありません。 ちなみに、正の閏秒は、直近だと今年(2017)の 01/01 に実施されました。インフラエンジニアの方々はお疲れさまでした。

NTP と閏秒

時刻情報を取り扱うプロトコルといえば NTP であって、NTP の世界においては 1 日は 86400 秒ですが、閏秒を適用する日は 1 秒長くなるのでその対策が必要です。

前提として、NTP の世界では、より時刻情報が正確な「親」から「子」そして「孫」というように、時刻情報が階層構造を持って流れていきます(階層は、ストレイタム(Stratum) という値によって、区別されます)。 正の閏秒が適用される場合、NTP における Stratum 0 のサーバは、LI (Leap Indicator) bit を "1" にして、Stratum 1 の NTP サーバにその情報を送り、その伝搬が各層によって繰り返されます。

この閏秒の扱い方には以下のような種類があります。

  • カーネルに閏秒の処理をまかせる
    • この場合、設定に依存して、08:59:60 が挿入されるか、08:59:59 が 2 回繰り返す
  • ntpd 等のデーモンが時刻を巻き戻す (08:59:59 を 2 回繰り返す) (STEP)
    • カーネルではなく、ntpd が強引に時刻を修正する
  • ずれた 1 秒を徐々に時間退行なしに巻き戻していく (SLEW)
  • Leap Smearing

Leap Smear はこのあたりの技術になります。

参考文献

MyFacesでのステートレスビューとCSRF対策の問題(解決済)

JSF において、ポストバックに対する CSRF 対策が実装されているというエントリを書きました。

ここでは、CSRF 対策トークンの値が javax.faces.ViewState という hidden 値に埋め込まれ、それを改竄した場合は ViewExpiredException が発生するという内容を記載しました。

ここで新しい話なのですが、MyFaces を使っているとき、この javax.faces.ViewState の値を stateless という値に変更(改竄) すると、何も例外もでないまま、処理が正常に行えるということがありました。 これは、JSF で導入されたステートレスビューによるものです。ステートレスビューについては、下記のエントリを参照して頂ければと思うのですが、ここでの問題は、

  • 外部から javax.faces.ViewState の値を操作するだけで、サーバ側をステートレスモードで動作させることができてしまう
  • ステートレスモードで動作させた場合は、コンポーネントビューの再構築が行われないので、CSRF 対策としても働かない

ということです。結果として、CSRF 攻撃が一定条件下では成立してしまいます。

すごく気になったので、1 週間ちかく調べていたのですが、どうも MyFaces の問題だったようで、そのものずばりの issue が MyFaces に挙がっていました。結構前に。

コミットは以下。transient="false" の Facelets を、外部から強制的にステートレスビューで動かそうとした場合はエラーとする対応のようです。

というわけで、いま最新版を取ればだいじょうぶなんじゃないかな。

JSFにおけるCSRF対策

CSRF については、一般に正規利用者の意図したリクエストであることを確認することが対策になります。いわゆる徳丸本で紹介されている、具体的な方法としては以下のとおりです。

  • 秘密情報(トークン)の埋め込み
  • パスワード再入力
  • Referer のチェック

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践

JSF には、この CSRF 対策のうち、最初の「トークンの埋め込み」が実装されています。JSF のうちの stateless view を利用していない場合という前提ですが、POST を使用しようとした場合、 javax.faces.ViewState というキー名の hidden フィールドが自動的に埋め込まれ、その値が暗黙のうちに CSRF 対策トークンとして作用します。 逆に GET を使用した場合は、faces-config.xmlprotected-views に対象ページを定義する前提で、同様のトークンがクエリパラメータとして付与されます。

これは、OWASP のいう CSRF 対策の以下のいずれか(たぶん前者)が使われています。

  • Synchronizer (CSRF) Tokens
  • Encrypted Token Pattern

ぼくはもともと、この javax.faces.ViewState は純然たる CSRF 対策なんだろうと思っていたんですが、じつは CSRF は副次的な効果のようです。JSF (2.2) の仕様書には、以下のような記述があり、 javax.faces.ViewState は「現在のビュー(current view) を表現する」ということが記述されています。

When a JavaServer Faces view is rendered, it will contain a hidden field with the identifier javax.faces.ViewState whose value contains the state for the current view.

JavaServer Faces Specification Version 2.2 14.1 Collecting and Encoding View State

この通り、JSF ではビューの状態を保存する際の ID、あるいは、保存したものそのものをこのフィールドに埋め込みます。結果、このフィールドの値が改竄された場合は、ビューを復元できない、という状況が発生し、結果として CSRF 対策になる、というもののようでした。

参考文献

JSFにおけるPostback、そしてライフサイクル

JSF における Postback とは何なのか、っていうのはなかなか本を見てもわかりませんでした。 かといって、こういう概念は実装してもよくわかんなかったりするので、こういうのはやっぱり公式ドキュメントを読んで理解した方が良いだろうと、そこで学んだことを書き留めておきたいと思います。

JSF の仕様書をパッと見たのですが、postback というのはあまり定義されていないようで、もはや前提知識なのかなとも感じました。 そういうわけなので、仕様書からではなく、Oracle の JSF のチュートリアルから引用します。

The request-response lifecycle handles two kinds of requests: initial requests and postbacks. An initial request occurs when a user makes a request for a page for the first time. A postback request occurs when a user submits the form contained on a page that was previously loaded into the browser as a result of executing an initial request.

https://docs.oracle.com/javaee/7/tutorial/jsf-intro006.htm

要するに、JSF におけるリクエストには 2 種類があると。

  • 初期リクエスト (initial request)
  • ポストバックリクエスト (postback request)

要するに、初期リクエストというのがユーザがページに初めてアクセスするときのリクエストで、postback request は、(既にロード済のページから) POST するときのリクエストということかな。

JSF には、リクエストの処理に明確なフェーズがあるのですが、Initial request か Postback Request かによって、個々のフェーズで何をするのか、そして、そもそもどのフェーズを通るのかというのが異なります。 せっかくチュートリアルに記載があるので、そのあたりもまとめてみました。

結論からいえば、initial request は Restore View Phase -> Render Response Phase が実行され、postback request に対しては、基本的に以下の全フェーズが実行されます。

Restore View Phase

このフェーズ自体は、Facelets として記述された View を構築し、Backing Bean 等へのメソッドとの紐付けを行うフェーズになります。

Initial Request に対しては、最初に空の View を構築し、そのまま最終的な Render Response View フェーズに移行します。このフェーズにて、View が構築され、FacesContext にその View が格納されます。ここでいう View っていうのは、たぶん UIViewRoot のことですかね。

一方、Postback Request の場合は、FacesContext の中に既にコンテキストが構築済なので、クライアントあるいはサーバ側(これは設定で切り替えられる)に保存されている状態の情報を通して、それを復元することになります。

Apply Request Values Phase

Postback の場合に行われるフェーズなのですが、ここでは View 中の各コンポーネント、たとえば InputText などが、入力された情報を読み取り、自分自身に保存するというフェーズになります。

Process Validation Phase

このフェーズも Postback で実行されるフェーズですが、各コンポーネントに登録された Validator と Converter を利用して、個々のコンポーネントに入力された値の検証と変換を行います。

Update Model Values Phase

Postback で行われるフェーズですが、ここでは検証済の入力値をコンポーネントツリー中の各コンポーネントに反映します。このフェーズで、Managed Bean にその値が反映されます。

Invoke Application Phase

アプリケーションレベルの処理を実行するフェーズです。ボタンやリンクに設定したイベントが実行されます。

Render Response Phase

View を構築して、そのレンダリングを行うフェーズです。

Initial Request に対しては、リクエストの対象となるページの View を構築して、コンポーネントツリーに登録します。

Postback に対しては、既に View が構築済・コンポーネントツリーにも登録済の状態です。もし、今までのフェーズでエラーがあったのであれば、次画面に留まり、そのエラー内容を表示することになります。

いずれも、コンポーネントツリーから XHTML が生成されます。

declare、typeset、そして local

declare とか typesetlocal まわりの使い方について。

まず、declaretypeset は、シノニムなので、この 2 つの差異は考えなくて良いと思います。このため、ここでは declare でひとまとめにして扱いたいと思います。 そういうわけで、declarelocal さえ区別できれば良いんですが、あんま変わらないんですよね。。。

属性の付与

declarelocal は、基本的に変数の宣言で使われますが、変数宣言とともに、その変数に対して属性を付与するときに使われます。

よく使われるのは、readonly のオプションですね。一度 -r 付きで宣言しておくと、その変数は readonly として扱われるようになり、それ以降で代入しようとするとエラーになります。

$ declare -r var="hoge"
$ var="fuga"
var: readonly variable

誤解されやすいのは、declare による属性の付与は、後出しが可能ってことでしょうか。 Google の出している Shell Style Guidereadonly を使った例が載っていますが、これ、declare を使っても同じようなことができます。

zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)"
if [[ -z "${zip_version}" ]]; then
  error_message
else
  readonly zip_version
fi
$ a="hoge"
$ typeset -r a
$ a="fuga"
a: readonly variable
$ echo $a
hoge

「整数型」っていう属性を与えることもできます。整数型という属性を与えることにより、浮動小数点が代入できなくなったりします。

$ x=3.14
bash: 3.14: syntax error: invalid arithmetic operator (error token is ".14")

また、expr(( )) とかを使わなくても、整数計算ができるようになったりします。

# declare -i を使った例
$ declare -i x
$ x=3+4
$ echo $x
7

# declare -i を使わなかった例
$ z=3+4
$ echo $z
3+4

ただ、この手の計算結果の格納は、分かりにくいので使わない気がします。 上記の例だと、x に文字列を代入しようとしたら計算結果が格納されているわけですが、前掲の通り、浮動小数点を代入しようとしたらエラーになるのは、あまり整合性が取れているとは思えないんですよね…。

この他、変数型が array であるとか、そういう属性を付けるときにも利用されたりします。

スコープ

というわけでスコープの話がでてくるんですが、local はわかりやすくて、その名前の通り「ローカル変数」を宣言できます。 Bash スクリプトのスタイルガイドは、結構 local 使えってところが多いですね。Google の Shell Style Guide もそうですし、以下の Defensive BASH Programming でも同様のことが言われています。

change_owner_of_file() {
    local filename=$1
    local user=$2
    local group=$3

    chown $user:$group $filename
}

Defensive BASH programming - Say what? より引用

スコープの話といえば、local の専売特許みたいなところがありますが、じつは typeset でもスコープの概念が導入されます。

foo (){
    declare FOO="bar"
}

bar (){
    foo
    echo $FOO
}

bar  # Prints nothing.

9.2. Typing variables: declare or typeset より引用

まぁ local の場合は、グローバル空間で宣言しようとするとエラーになるので、そのあたりは違いますね。

$ local p
bash: local: can only be used in a function
$ typeset p

参考文献

シェルスクリプトで部分文字列を切り出す

ときどき、シェルスクリプトで文字列の 3 文字目から 4 文字を切り出したいみたいなユースケースがあって、これまでずっと cut でやってた。

$ echo $str
1234567890

$ echo $str | cut -c3-6
3456

これ、じつは文字列操作でできる。

$ echo ${str:3:4}
4567

cut みたいな外部プロセスを呼び出すこともないので、シェルスクリプト性能おじさんも黙らせることができます。

はてな開発者ブログから参照されてたうれしい話

はてな開発者ブログから人知れずエントリが引用されてたのが、ちょっとうれしいことでした。

ちょっと Serf いけるかなーと思ってその仕組み調べてたときのエントリ。はてなブログだと、他のブログから言及されたときに通知くるんだけど、上記のエントリのどこから参照されてるのかまったくわからなくて、HTML ソース見てようやく把握した。

WAS Liberty Profileとは

WAS? 使ったことなかったんですけど、アプリケーションサーバですよね、みたいなかんじだったんですけど、さらに、 なに?WAS の Liberty Profile?なにそれ、全然知らんけどおいしいの?というくらいの状況でしたので、なんかよくわからなすぎました。

WAS Libery Profile

WAS の Libery Profile とは何なのかっていうと、WAS のプロファイルの 1 つです。WAS は、v8.5 から、2 つのプロファイルを提供するようになりました。

  • Full Profile
  • Libery Profile

Full Profile の方は JavaEE 仕様を全てサポートしたプロファイルで従来からのものです。

一方で、Liberty Profile は、IBM は「次世代アーキテクチャ」と呼んでますが、軽量・高速な起動をウリにしたプロファイルになっています。 その特徴はというと、必要な機能を選び、使用するフィーチャーのみを組み合わせて起動することができるとともに、構成を動的に変更できるということになります。

Docker にも Liberty Profile の Docker Image が提供されているように、 ランタイムの小型・軽量をウリにして、クラウド、コンテナ、IoT デバイスあたりを狙っているとのこと。

メチャクチャ分かりづらいんですが、Liberty プロファイルはランタイムであって Liberty サーバというサーバを立ち上げるものになっているようで、 Liberty サーバが、アプリケーション要求に従って (server.xml に書く)、必要なフィーチャーのロードとアンロードを行うようになっています。

実際に使ってみて

ローカル環境で起動させた場合、たしかに起動時間はかなり早いですし、server.xml でフィーチャーの構成を変えても、その反映は迅速です。 ローカルでの開発ではストレス感じずにいけているというのが今の実感です。

<server description="test server">
  <!--Enable features which you would like to use -->
  <featureManager>
    <feature>jsp-2.2</feature>
  </featureManager>
  <httpEndpoint id="appHttpEndpoint" host="localhost" httpPort="9080" httpsPort="9443" />
</server>
<!-- cited from https://dzone.com/articles/what-liberty-profile-ibm -->

また、コンビニエンス・フィーチャーという仕組みがあって、これは複数のフィーチャーをまとめたフィーチャーとして定義されています。javaee-7.0 とか、webProfile-7.0 とか。 これを使うとチマチマと個別のフィーチャーを指定することなく開発ができるので、開発初期の、フィーチャーへの要求すら安定していない頃は便利かもしれません。 Developer Works にも、

アプリケーション開発では、単純にコンビニエンス・フィーチャーを使用可能にして済ませておくことが多いようです。アプリケーションをデプロイする準備が整った時点で、アプリケーションが実際に使うフィーチャーの細かい指定でコンビニエンス・フィーチャーを置き換えて、メモリーとディスク・スペースを節約します。

https://www.ibm.com/developerworks/jp/websphere/library/was/liberty_fordev_intro/index.html

という記述があります。

とはいえ、開発中は動いたけど、変にフィーチャーを精査したら動かなくなったーとかザラにありそうなので、早い段階でフィーチャー構成を決めておきたいところではありますが。

参考文献