株式会社クリアコード > ククログ

ククログ


Firefox 38以降で急にSSL/TLSのエラーが出るようになった、という問題の原因と対策

概要

Firefoxをバージョンアップしたところ、それまで問題なく閲覧できていたWebページで急に、「安全な接続ができませんでした」や「接続の安全性を確認できません」といったエラーが出るようになった、というお問い合わせを頂きました。

調査の結果、本質的には証明書そのものに問題があって、それまでは見過ごされていた問題が表面化したという状況であったことが分かりました。 具体的には、Firefox 37以前のバージョンでは「サイト証明書として妥当でない証明書でもサイト証明書として利用できていた」のに対し、Firefox 38以降のバージョンではそのような証明書は不正な証明書としてエラーになるようになっています。 そのため、仮に実験用の証明書であったとしても、Firefox 38以降ではサイト証明書として適切な情報を設定しておく必要があります。

この記事では、詳しい背景と問題の解決方法についての情報をご紹介します。

この問題に遭遇しうる状況

この問題は、以下のような場面で遭遇する可能性が高いです。

  • 組織内専用のWebサイトで、独自のルート証明書を使用している。
  • 「暗号化された通信の内容も検査できる」と謳っているネットワーク機器やファイアウォールのサービス(いわゆるSSLプロキシ)を利用している。

何故かというと、このような場面で使われる証明書はベリサインなどの認証機関に依頼して発行される物ではなく、自組織内の担当部署もしくはSSLプロキシの提供事業者自身が任意の内容で生成する物となるために、その生成過程でミスが発生しやすいからです。

「証明書の問題」となったとき、組織内専用のWebサイトでの証明書で問題が起こるというのは分かりやすいですが、SSLプロキシでも問題が起こりうるという事について理解するためは、SSLプロキシの原理を知る必要があります。

TLSおよびその前身であるSSLでは、電子署名を使った暗号化によって、クライアント(ブラウザ)と通信相手のWebサーバの間で安全な通信を行います。 例えばAmazonでの買い物でクレジットカード番号を入力する場面や、Gmailのサービス上で新しいメールを作成して送信する場面などでは、「送信されたクレジットカード番号」や「入力されたメールの内容」といった通信内容は暗号化されており、途中の通信を中継するプロキシサーバや、ビル内のネットワークを束ねる通信機器などからは情報を読み取ることができません。

これはプライバシーを守るという観点からは素晴らしい事なのですが、「社内からの通信で社外秘の情報が流出していないかどうかを監視したい」というようなニーズをシステム管理者が抱いている場合、TLSでの通信に対しては監視の目が届かないという事でもあります。

そこで登場するのがSSLプロキシです。 SSLプロキシはそれ自身がWebサイトとして振る舞うようになっており、SSLプロキシが設置されたネットワーク環境では、TLSやSSLを使った暗号化通信はすべて「ブラウザとそのSSLプロキシの間での暗号化通信」となります。 SSLプロキシはブラウザからの接続を受け付けると、ブラウザに代わって本来の接続先にリクエストを行い、SSLプロキシとWebサーバの間で暗号化通信を行います。 そして、受信した情報を今度は自分の証明書で暗号化してブラウザに返却し、ブラウザに対してあたかも本来の接続先との間で安全な暗号化通信が行われたかのように見せかけます。 この仕組みによって、SSLプロキシはブラウザと本来の接続先のWebサーバとの間に割り込んで、通信内容の監視や改変を行うという事になります。

しかし、この時SSLプロキシがブラウザに対して返す証明書は、言わば、どんなサイトの証明書としても使える「ワイルドカード証明書」でなくてはなりません。 これは大変危険な物なので、一般的な認証機関はこのような証明書を発行してくれません。 そのため、このような証明書は独自の認証局で発行しなくてはならず、利用者は自己の責任においてその独自認証局(つまりSSLプロキシの提供事業者)を信任する必要があるというわけです。

証明書の用途

前置きが長くなりましたが、ここからが本題です。

電子署名に使われる証明書には、以下のようにいくつかの種類があります。

  • TLSにおいて、通信先のWebサイトの運営者を証明するための物(サイト証明書)
  • 配布されるソフトウェアについて、作成者を証明するための物(コードサイニング証明書)
  • 証明書そのものの正当性を証明するための物(ルート証明書)

これらの証明書は、データ形式としてはX.509という仕様に則っており、種類の違いはKeyUsageフィールドの値の違いで表されます。

言い換えると、これらの証明書はKeyUsageフィールドなどいくつかのフィールドの値が違うだけで、データ形式の点では互換性があるという事でもあります。 そのため、証明書を解釈する側のソフトウェアの作り方次第では、証明書の本来の用途を無視できてしまいます。 実際に、Firefox 37.xおよびそれまでのバージョンでは、TLSにおいてWebサーバが送信してきた証明書が本当にサイト証明書であるかどうか(KeyUsageフィールドの値がサイト証明書として適切かどうか)を確認せずに、サイト証明書であると見なして利用していた模様です。

しかし、Firefoxの暗号化通信に関する処理にBug 1122841Bug 1130754, Bug 1077864, Bug 975229などで段階的に変更が行われた結果、Firefox 38.0以降のバージョンでは、TLSで使用する証明書に記載されたKeyUsageフィールドの情報が仕様通りに解釈されるようになったようです。 具体的には、Firefox 38.0以降のバージョンはKeyUsageフィールドの値が以下の3つのいずれかに該当する証明書のみを有効なサイト証明書として受け入れるようになりました。

  • Digital Signature (0) / 電子署名
  • Key Encipherment (2) / 鍵暗号
  • Key Agreement (4) / 鍵交換

これに対し、例えばルート証明書専用に発行された証明書は、KeyUsageフィールドの値として以下の値のみを保持している事があります。

  • Key Cert Sign (5) / 電子証明書の検証
  • CRL Sign (6) / CRLの署名検証

そのため、ルート証明書として作成された自己署名証明書をTLS用のサイト証明書に転用した場合、Firefox 38はそれを不正なサイト証明書として認識し、冒頭に挙げたようなエラーを返すという結果になります。

対策

上記の判断はCertVerifier::VerifyCert()というメソッドで行われていますが、実装を見ると、サイト証明書として受け入れる証明書のKeyUsageの値や判定ロジックがハードコードされており、Firefoxの設定の変更などの使用者レベルでの対処では変えられないという事が分かります。

よって、この問題を解決するには、社内Webサイトで使用している証明書やSSLプロキシで使用している証明書を、「KeyUsageに上記の3つのうちいずれかの値を持っている、サイト証明書として妥当な証明書」に入れ替える(証明書を作り直す)必要があります。 SSLプロキシの場合、提供事業者に連絡してそのような対応を取ってもらう事になります。

まとめ

Firefox 37およびそれ以前のバージョンでは問題なく通信できていたのが、Firefox 38以降で急にエラーが起こるようになった、という問題について、証明書に記載された用途の情報に起因する事例の詳細とその対策をご紹介しました。

このエントリで述べた事はFirefox 38の開発者向け情報リリースノートには記載されておらず、実際に問題が発生している環境での詳細なログ、Firefox自体のソースコード、Firefoxのリポジトリのコミット履歴などを調査した結果ようやく明らかになりました。 また、調査にあたってはMozilla Japanにもご協力を頂く必要がありました。この場を借りてお礼申し上げます。

クリアコードではFirefoxやThunderbirdなどのMozilla製品の技術サポート事業として、これらの製品の使用において発生したこのようなトラブルに関し、ソースコードレベルでの原因究明や対策の調査を有償にて行っております。 FirefoxやThunderbirdの使用や利用でお困りの際は、お問い合わせの受け付けフォームよりご相談下さい。

タグ: Mozilla
2015-10-01

FluentdのインメモリDump configの機能を使いメモリ上の設定をダンプするには

はじめに

クリアコードはFluentdの開発に参加しています。

また、Fluentdにはプラグインというしくみがあり、たくさんのプラグインが開発されています。 FluentdはGitHub上で開発されています。また、Fluentd自体はそこまで大きくないものの、柔軟性に重点を置いたコードになっているため、内部構造は複雑です。

メモリ上の設定ダンプ機能とは

Fluentdはログなどのデータを集約したり、再配分を行うソフトウェアです。ソフトウェアの性質上、長時間の動作が想定されます。そのため、運用されてある程度経ってくると設定ファイルとメモリ上にある設定が乖離することがあります。また、設定ファイルを書き換えて試している時でもFluentdが使用しているメモリ上の設定と実際の設定ファイルに乖離が生じます。

そのため、今の設定が正しく読み込まれているか、また現在の設定はどうなっているか確認したいという要望がFluentdのIssue に登録されていました。

この機能を実際に作成してv0.12.16へ取り込まれました。メモリ上の設定がダンプ出来るようになり、更に便利になったFluentdですが、注意点もあるので secret parameterと絡めての情報を紹介します。

使い方

次のようにFluentdのRPC機能を有効化することでFluentdが使用しているメモリ上の設定のダンプAPIが有効になります。

<system>
  rpc_endpoint 127.0.0.1:9000
</system>

この設定をFluentdの設定ファイルへ書いた後、http://<rpc_endpoint>/api/config.dump をcurlなどから以下のようにアクセスします。実行した結果、 {"ok":true} が返ってくれば成功です。

$ curl http://127.0.0.1:9000/api/config.dump
{"ok":true}

すると、Fluentdの実行されているサーバー上でFluentdが使用している設定がログに出力されます。

2015-10-02 11:26:29 +0900 [info]: <ROOT>
  <system>
    rpc_endpoint 127.0.0.1:9000
  </system>
</ROOT>

のようなログが出力されます。また、HTTP経由でメモリ上の設定をダンプする機能もあります。

しかし、後述する secret parameter の対応がFluentdのプラグインに行き渡っていない現状があるため、v0.12ではこの機能はデフォルトでは無効化されています。将来のバージョンではこの制限は取り除かれ、デフォルトで有効になる予定です。

HTTP経由でFluentdのインメモリの設定を取得する機能を有効化するには次のように enable_get_dump true を追加します。

<system>
  rpc_endpoint 127.0.0.1:9000
  enable_get_dump true
</system>

curlなどを用いて http://<rpc_endpoint>/api/config.getDump へアクセスすると、conf キー以下にFluentdが現在使用している設定が入ってくるようになっています。

$ curl http://127.0.0.1:9000/api/config.getDump
{"conf":"<ROOT>\n  <system>\n    rpc_endpoint 127.0.0.1:9000\n    enable_get_dump true\n  </system>\n</ROOT>\n","ok":true}

メモリ上の設定ダンプ機能とsecret parameter

Fluentd v0.12.13からは secret parameter という機能が入りました。この機能は、config_param でプラグインの設定している箇所に secret: true のHash値を追加出来るようにしている機能です。

この secret: true (古いHashの記法では:secret => true) はv0.12.13より前のバージョンではエラーにならず、単に無視される挙動をします。

前述のメモリ上の設定のダンプ機能を有効化している際にはプラグインが secret parameter に対応しているかどうか確認してください。

例えば、データベースのパスワードや、AWSのAPIトークンやシークレットのような外部に制限なく公開されるのは望ましくないプラグインの設定値を設定している config_param を見つけたら secret: true が追加されているかを確認しておくと良いでしょう。

secret parameter 対応の例として、fluent origanization以下にあるfluent-plugin-mongoというプラグインへの対応を挙げます。このプラグインへの pull request#54 では、例えば config_param :password, :string, :default => nil:secret => true を追加する対応を行いました。 この対応により、メモリ上の設定をダンプしても重要なpasswordなどの情報は xxxxxx でマスクされ、見えなくなります。

まとめ

Fluentd v0.12.16にはメモリ上の設定をダンプできるRPCのハンドラが追加されました。単純にFluentdの動いているサーバーのログへこの機能を使ってメモリ上の設定をダンプするのであれば、RPCを有効化し、新たなRPCハンドラにアクセスすればすぐに使い始めることができます。HTTP経由でのメモリ上の設定のダンプには現在のバージョンでは追加設定が必要な事も解説しました。

プラグインへ secret parameter が適切に設定されていれば、重要な情報を見せてしまうことなく、現在のFluentdの使用しているメモリ上の設定の情報を取得出来るようになります。

このメモリ上の設定のダンプ機能が取り込まれたRPCを使い始める際にはプラグインが secret parameter 機能に対応しているかどうかを確認し、パッチを送ることを試みてみるのはいかがでしょうか。

タグ: Fluentd
2015-10-02

動作確認用の画像をImageMagickで簡単に作るには

はじめに

Webアプリケーションを開発していると、動作確認用の画像が欲しいことがしばしばあります。 ちょっとアップロードを試す程度なら、手元の適当な画像を探して使うということができます。 ただし、動作確認用の画像をリポジトリに入れたりとなると話は別で、そのままではまずいことも多々あります。 そういうときは癖のない無難な画像が欲しくなったりします。

今回はそんなときに便利なImageMagickを使って動作確認用の画像を簡単に作る方法を紹介します。

ImageMagickのセットアップ

まずは、ImageMagickをインストールしましょう。

% sudo apt-get install imagemagick

これで、今回利用するconvertコマンドが使えるようになりました。きちんとインストールできていると-versionオプションをつけて実行すればバージョンが表示されます。*1

% convert -version
Version: ImageMagick 6.8.9-9 Q16 x86_64 2015-09-19 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2014 ImageMagick Studio LLC
Features: DPC Modules OpenMP
Delegates: bzlib djvu fftw fontconfig freetype jbig jng jpeg lcms lqr ltdl lzma openexr pangocairo png tiff wmf x xml zlib

画像を作成してみよう

convertコマンドが使えるようになったところで、実際に動作確認用の画像を作成してみましょう。

例えば、次のような画像が欲しいとします。

  • 画像形式はBMP,JPEG,PNG,TIFFが必要(同一形式で拡張子名が異なるものもあり)
  • 画像形式の情報が描画されている(pngファイルなら「png」と描画されている)
  • サイズは任意

これは次のようなサンプルスクリプトで実現できます。

#!/bin/bash

function usage() {
    echo "Usage $0 width height"
    exit 1
}

if [ -z "$1" ]; then
    usage
fi
if [ -z "$2" ]; then
    usage
fi

width=$1
height=$2
point=`expr $width / 2`
for f in bmp gif jpeg jpg png tiff tif; do
    convert -size ${width}x${height} xc:#97c03e \
	    -pointsize $point \
	    -gravity center \
	    -fill black \
	    -stroke white \
	    -draw "text 0,0 $f" ${width}x${height}.$f
done

上記の内容をcreate-image.shとして保存し、実行可能にしておきます。 次のようにサンプルスクリプトを実行してみましょう。

% ./create-image.sh 100 100

すると、画像をいくつか作成できます。

% ls -la
合計 260
drwxr-xr-x  2 kenhys kenhys  4096 10月  7 10:11 .
drwx------ 25 kenhys kenhys  4096 10月  7 09:10 ..
-rw-r--r--  1 kenhys kenhys 40138 10月  7 09:51 100x100.bmp
-rw-r--r--  1 kenhys kenhys  1474 10月  7 09:51 100x100.gif
-rw-r--r--  1 kenhys kenhys  3406 10月  7 09:51 100x100.jpeg
-rw-r--r--  1 kenhys kenhys  2701 10月  7 09:51 100x100.jpg
-rw-r--r--  1 kenhys kenhys  8265 10月  7 09:51 100x100.png
-rw-r--r--  1 kenhys kenhys 80358 10月  7 09:51 100x100.tif
-rw-r--r--  1 kenhys kenhys 80358 10月  7 09:51 100x100.tiff

スクリプトの説明

サンプルスクリプトではconvertコマンドを次のように使っていました。

convert -size ${width}x${height} xc:#97c03e \
	    -pointsize $point \
	    -gravity center \
	    -fill black \
	    -stroke white \
	    -draw "text 0,0 $f" ${width}x${height}.$f

先程の例だと、実際には次の内容でconvertコマンドを実行していました。*2

convert -size 100x100 xc:#97c03e \
	    -pointsize 50 \
	    -gravity center \
	    -fill black \
	    -stroke white \
	    -draw "text 0,0 png" 100x100.png

これは次のようにconvertコマンドへと指示しています。

  • -size 100x100 xc:#97c03e 背景色が#97c03eの100x100サイズの画像を作成する
  • -pointsize 50 文字サイズは50ポイントにする
  • -gravity center 中央に配置する
  • -fill black 文字色は黒にする
  • -stroke white 文字の縁取りは白にする *3
  • -draw "text 0,0 png" 「png」と描画する
  • 100x100.png 出力ファイル名は100x100.pngにする

このようにconvertコマンドのオプションを組み合わせることで、以下のようなサンプル画像をまとめて作成できました。

gif画像 jpeg画像 jpg画像 png画像

まとめ

動作確認用の画像をImageMagickを使って簡単に作成する方法を紹介しました。 ここで紹介したconvertコマンドにはほかにもたくさんのオプションがあります。 興味のある人はいろいろと調べてみると良いのではないでしょうか。

*1 実際のバージョンはお使いのディストリビューションでインストールできるImageMagickのバージョンによって異なります。

*2 拡張子がpngの場合。

*3 小さいサイズでは縁取りしないほうが視認性が良いかもしれません。

2015-10-07

認められて伸びる・やる気がでるタイプなら依頼者・ユーザーに認めてもらう

クリアコードは楽しく開発することを大事にしています。楽しければ開発を続けられますし、学ぶときの効率もよくなるからです。

楽しさの源は人それぞれです。理由なく楽しめる人もいますし、自分で目指す場所を決めてそれを実現することで楽しくなる人もいますし、他の人から認めてもらうことで楽しくなる人もいます。この例の中の最初の2つは自分で完結するタイプです。最後のタイプは自分以外のだれかが必要なタイプです。

クリアコードのメンバーは(少なくとも仕事では)他の人を認めるときの閾値が高い傾向があります。そのため、最後のタイプのメンバーをサポートすることは苦手です。最後のタイプのメンバーが常に高い成果を出していれば自然と認められますが、多くの場合、それは難しいことです。

最後のタイプのメンバーをサポートする方法として「他の人を認めるときの閾値を下げる」方法がありますが、ここでは別の方法を紹介します。それは、依頼者・ユーザーに認めてもらう方法です。

依頼者・ユーザーに認めてもらう

クリアコードでは仕事の中で接点がある人は社内の人だけではありません。仕事の依頼者とも接点がありますし、開発に参加しているフリーソフトウェアのユーザーとも接点があります。そんな社内以外の人から認めてもらうことで「他の人から認めてもらうことで楽しくなる」タイプの人でも楽しさの源を得ることができます。

依頼者・ユーザーに認めてもらうにはどうしたらよいかというと、依頼者・ユーザーが実現したいこと・抱えている問題などを理解し、それを適切な方法で解決します。

難しいのは、次のように、表面的な認識で対応するだけで適切な方法を用いた解決になることが少ないということです。

  1. 「○○が欲しいです。」→
  2. 「○○を作りました。」→
  3. 「ありがとうございます!」

次のように、「理解」した上での対応が必要になることの方が多いです。

  1. 「○○が欲しいです。」→
  2. (「…△△が根本的な問題だな…」)→
  3. 「実は××ができるようになるとよかったのではないでしょうか?」→
  4. 「あぁ、そうですね!」→
  5. 「□□という対応をしました。これで××ができるようになります。」→
  6. 「ありがとうございます!」

適切な方法での解決を積み重ねます。これを繰り返すことで認めてもらえる確率があがります。

世の中にはいろいろ制約があり、解決できないこともあります。そのようなときは、依頼者・ユーザーに納得してもらえるように、依頼者・ユーザーの方を向き、依頼者・ユーザーの言葉で説明する必要があります。

なお、「他の人を認めるときの閾値が高い傾向」があるメンバーでも、依頼者・ユーザーに認めてもらう成果を出すにはどうしたらよいかについては協力できます。なぜなら、それはクリアコードが大事にしていることでメンバーが日々実践していることだからです。

まとめ

「他の人から認めてもらうことで楽しくなる」タイプの人をサポートする方法として「依頼者・ユーザーに認めてもらう」方法を紹介しました。

「依頼者が満足する仕事」を目指すことは普通のことなので、実践している人は多いでしょう。そんな仕事をしている人は、依頼者と接点を持つことで「認めてもらう」機会が増えるはずです。認めてもらうことが原動力になる人は依頼者との接点を増やすと認めてもらえる確率があがるでしょう。

フリーソフトウェア開発者はユーザーに会いに行くとよいです。認められる機会が増えることもそうですが、普段フィードバックをもらえない人からのフィードバックは有益な情報になるかもしれません。

2015-10-08

Fluentd v0.12.16でプラグインの設定をコマンドラインから確認する方法

Fluentd v0.12.16 has been released | Fluentdでも、詳しく紹介されていない機能ですがFluentd v0.12.16からはコマンドライン上でプラグインがどんな設定項目を持っているか確認することができるようになりました。

例えばfluent-plugin-s3のアウトプットプラグインであれば以下のようにして使うことができます。

$ fluentd --show-plugin-config output:s3
2015-10-09 15:25:56 +0900 [info]: Show config for output:s3
2015-10-09 15:25:56 +0900 [info]: 
path: string: <"">
use_server_side_encryption: string: <nil>
aws_key_id: string: <nil>
aws_sec_key: string: <nil>
aws_iam_retries: integer: <5>
s3_bucket: string: <nil>
s3_region: string: <"us-east-1">
s3_endpoint: string: <nil>
s3_object_key_format: string: <"%{path}%{time_slice}_%{index}.%{file_extension}">
store_as: string: <"gzip">
auto_create_bucket: bool: <true>
check_apikey_on_start: bool: <true>
proxy_uri: string: <nil>
reduced_redundancy: bool: <false>
storage_class: string: <"STANDARD">
format: string: <"out_file">
acl: string: <:private>
assume_role_credentials
 role_arn: string: <nil>
 role_session_name: string: <nil>
 policy: string: <nil>
 duration_seconds: integer: <nil>
 external_id: string: <nil>
instance_profile_credentials
 retries: integer: <nil>
 ip_address: string: <nil>
 port: integer: <nil>
 http_open_timeout: float: <nil>
 http_read_timeout: float: <nil>
shared_credentials
 path: string: <nil>
 profile_name: string: <nil>

--show-plugin-configの引数は<プラグインの種類>:<プラグインの名前>で指定します。 現時点ではプラグインの種類は以下の4種類あります。

  • input
  • output
  • filter
  • buffer

ほとんどの場合、プラグインの名前はfluent-plugin-s3であればs3のようにプラグインのGemの名前からfluent-plugin-を除いたものになります。

--show-plugin-configにはいくつか制限事項もあります。

  • fluentdでロードできないプラグインの設定項目を確認することはできません
  • config_paramのブロックを使って設定を書いている場合は、ブロックによって設定されるデフォルト値などは確認できません
  • プラグインのconfigureメソッドで動的にデフォルト値を設定している場合も確認できません

プラグイン作者の方へ

fluentd v0.12.16 から使える--show-plugin-configオプションですが、プラグイン側でconfig_paramdescオプションを書いてあげると--show-plugin-configしたときにdescに指定した説明が表示されます。

descオプションを追加しても--show-plugin-configを持たないバージョンのfluentdでも問題なく動作します。

なので--show-plugin-configで表示される情報を充実させたい場合は以下のように説明を書いてください。

module Fluent
# ...省略
  class S3Output < Fluent::TimeSlicedOutput
    Fluent::Plugin.register_output('s3', self)

    # ...省略

    config_param :path, :string, :default => "",
                 :desc => %Q(path prefix of the files on S3. Default is ""(no prefix))
    config_param :use_server_side_encryption, :string, :default => nil
    config_param :aws_key_id, :string, :default => nil, :secret => true,
                 :desc => "AWS access key id. This parameter is required when your agent is not running on EC2 instance with an IAM Role."
    config_param :aws_sec_key, :string, :default => nil, :secret => true,
                 :desc => "AWS secret key. This parameter is required when your agent is not running on EC2 instance with an IAM Role."
    # ...省略

説明を追加すると以下のように表示されます。

 bundle exec fluentd --show-plugin-config output:s3
2015-10-09 16:02:35 +0900 [info]: Show config for output:s3
2015-10-09 16:02:35 +0900 [info]: 
path: string: <""> # path prefix of the files on S3. Default is ""(no prefix)
use_server_side_encryption: string: <nil>
aws_key_id: string: <nil> # AWS access key id. This parameter is required when your agent is not running on EC2 instance with an IAM Role.
aws_sec_key: string: <nil> # AWS secret key. This parameter is required when your agent is not running on EC2 instance with an IAM Role.
aws_iam_retries: integer: <5>
# ... 省略

Fluentd本体にバンドルされているプラグインでもdescオプションに対応しているプラグインはまだありません。自作のプラグインに対応するついでにFluentd本体にPull Requestを送ってみるのはどうでしょうか?

タグ: Fluentd
2015-10-09

commit-email.info: コミットメール配信サービス

コミットメール配信機能を提供するサービスcommit-email.infoを紹介します。

(commit-email.infoはクリアコードが運営しているサービスではなくクリアコードのメンバーが個人で運営しているサービスです。)

フリーソフトウェアの開発で学んだことのひとつに「コミットを共有する」ということがあります。

「コミットを共有する」とは、他の開発者のコミットを自分が読む、自分のコミットを他の開発者が読む、ということです。最近はpull requestを使った開発がよい開発というイメージが広がっているようなので、「pull requestをしたら他の開発者が自分のコミットを見てくれる」と言えばどういうことかピンときやすいかもしれません。ただ、「コミットを共有する」は「requestされなくても自分が読みたいから読む」という点が「pull requestを使った開発」のケースと違います。

「自分が読みたいからコミットを読む」ことを支援するサービスがcommit-email.infoです。pull requestきっかけでコミットを読みたい場合はGitHubのwatch機能を使うとよいでしょう。

機能

commit-email.infoの機能を説明します。

commit-email.infoはリポジトリーにコミットがpushされたらそのコミットの情報(diff入り)をEメールで配信するサービスです。このEメールのことをコミットメールと呼びます。コミットメールにはdiffが入っているので別途Webブラウザーを開いたりgit log -pを実行しなくても変更の詳細まで確認できるところがポイントです。diffが入っていると確認するひと手間が減るのです。

diffは変更点がわかりやすくなるように工夫しています。次のように色付きでフォーマットしています。これはdiffをわかりやすく表示するツールがよく使うやり方です。

たとえば、GitHubにミラーされたRubyのr52117は次のようになります。

最初にコミットのメタデータがあります。

コミットメールのヘッダー部分

続いてdiffがあります。このスクリーンショットではAF_UNSPECを引数として追加したことがわかりやすくなっています。

コミットメールのdiff部分

コミットメールはメーリングリストで配信します。コミットメールを受信したい場合はメーリングリストを購読し、受信をやめたい場合はメーリングリストから退会します。購読・退会処理は自動化されているため、ユーザーは自分の好きなタイミングで実行できます。

このサービスが提供する機能はこれだけです。

使い方

commit-email.infoの使い方を説明します。

まず、自分がコミットを読みたいプロジェクトのコミットメールを配信しているメーリングリストをリストから探します。たとえば、Rubyのリポジトリーのコミットメールならruby@ml.commit-email.infoです。

このメーリングリストに次の内容のメールを送ります。Ccnull@commit-email.infoを指定することを忘れないでください。

To: ruby@ml.commit-email.info
Cc: null@commit-email.info
Subject: Subscribe
--
subscribe

このメールを送ると購読完了です。それ以降にpushされたコミットはコミットメールとして届きます。

もし、リストにコミットを読みたいプロジェクトがなく、そのプロジェクトのリポジトリーがGitHubにある場合はメーリングリストを購読する前にそのプロジェクトをリストに追加する必要があります。リストに追加する方法は次の通りです。

  1. kou/commit-email.infoにあるansible/files/web-hooks-receiver/config.yamlにそのリポジトリーを追加するpull requestを送ります。
  2. もし、自分がそのリポジトリーの管理者権限を持っているなら、そのリポジトリーのWebフックにhttp://web-hooks-receiver.commit-email.info/を追加します。(リポジトリー単位のWebフックでもorganization単位のWebフックでも構いません。複数のリポジトリーを対象にするならorganization単位のWebフックが便利です。)
  3. もし、自分がそのリポジトリーの管理者権限を持っていないなら、kou/commit-email.infoにあるansible/files/github-event-watcher/config.yamlにそのリポジトリーを追加するpull requestを送ります。

どのケースでも「1.」は必須です。「2.」と「3.」はどちらかをやればいいです。「2.」の方がおすすめです。理由はタイムラグが小さいからです。

まとめ

コミットメール配信機能を提供するサービスcommit-email.infoを紹介しました。GitHubにあるリポジトリーに対応しています。GitLab.comに対応することは容易なので、必要な人はissueを作って相談してください。

commit-email.infoを使って自分が使っているプロダクトのコミットを読み始めてみてはいかがでしょうか?きっと学ぶことがたくさんあるはずです。

そういえば、www.commit-email.infoがやけに質素なデザインだと思いませんでしたか?実はスタイルシートが空っぽなのです。

関連情報:

2015-10-13

OSS開発に参加する人を増やす取り組み「OSS Gate」を開始

クリアコードは去年からSEゼミという企画に協力していました。SEゼミというのは技術的なテーマを軸にして工学系の学生と企業を結びつける就職支援系の企画です。(明示的にそう書かれているページを見つけられませんでしたが、そんなに間違っていないはず。)

去年はリーダブルコードというテーマで協力し、今年はOSSというテーマで協力しました。運営のSEプラスさんは、クリアコードが提案した(チャレンジングな)内容を最大限に活かしてくれました。幸い、参加してくれた学生の方にも企業の方にも楽しんでもらえたようです。クリアコードとしても非常に学ぶことが多いステキな機会でした。ただ、残念ながらお金の面でSEゼミを継続することはできないことが決まりました。

事業としては継続できないのですが、今年のSEゼミで得られた次の知見を活かして「OSS開発に参加する人を増やす取り組み」を始めます。

  • OSS開発に未参加の人は参加方法がわからないだけであり、参加方法がわかればOSS開発に参加できる

この取り組みを「OSS Gate」と名づけました。

OSS Gateについて

「Gate」は「入り口」という意味で使いました。OSS開発に参加する最初の一歩を支援する取り組みとして「入り口」というイメージがしっくりきたからです。

OSS開発に未参加の人が試しに参加してみて「それで終わり」ではなく、試しに参加したあとも「継続して参加する」。入り口を抜けた後にも道は続いています。抜けた後も進んでいける、そんな入り口を用意したいのです。

今年のSEゼミで学んだことはたくさんありましたが、OSS Gateを始めるにあたり特に突き刺さったことは次の2つです。

  • 事前準備を手伝ってあげればあとは自分でOSSの開発に参加できる
  • OSSの開発に関して伝える大事なことは「楽しむ」だけでよい

細かいことはSEゼミ2015のコンテンツ作成と進行をしてわかったこと #sezemiにまとめていますが、ざっくりまとめると次の通りです。

OSSの開発に参加していない理由は漠然とした不安があるから。やり方を教えて、実際にやってみてもらえばその不安は解消できる。

OSSの開発は些細なことから華やかなことまでたくさんあって、華やかなことばかり注目しがちだけど、些細なことにも目を向けることを伝えよう。typoの修正だって開発に参加する1つの形。小さなことの積み重ねでOSSはよくなっていくんだから参加内容が華やかなことばかりじゃなくても気にしない。それがわかれば楽しく開発に参加できる。

楽しもう。楽しんでやっていれば継続して小さなことを積み重ねていける。気づいたらすごいところに到達しているはず。数日でもそうだったんだから、もっと継続すればもっと先に行ける。

SEゼミの対象者は大学生・大学院生でしたが、大学生・大学院生に限らず、中高生でも社会人でもだれでも、漠然とした不安があってOSS開発に参加できていない人たちの最初の一歩を支援しその先を示す、入り口となる場を提供したい。その結果、楽しくOSS開発に参加する人が増えた世界が待っているとうれしい、という取り組みがOSS Gateです。

OSS Gateの進め方で大事にすること

OSSの開発では手を動かさずに口だけ動かしていてもなかなか状況は変わりません。そのため、OSS Gateも口だけではなく手も動かすことを大事にします。

手を動かすことも大事なことですがもう1つ大事なことがあります。それは継続することです。一発大きな花火を打ち上げて終わりではなく、最初の一歩を踏み出そうとする人を継続して迎え入れ、送り出す。そんな「門」のような場を提供することで、OSS開発に参加する人をより多く増やせるのではないか。それが継続することを大事にする理由です。なにより、OSSの開発も継続することが大事なことが多いですからね。

このようなことを大事にするOSS Gateですが、継続的に実現する方法を具体的に決めるためにOSS Gate を立ち上げようというイベントを12月15日(火)の夜に開催します。この取り組みに協力してくれる方はぜひお越しください。

「興味はあるけど協力ってどうすればいいの?」と思う人がいるでしょうから例を挙げます。最初はOSS Hack 4 Beginnersと同様のワークショップを開催するのがよいのではないかという案があります。これを実現するためには次のような作業が必要になります。すでにOSSの開発に参加している人なら「これは協力できそう」と思う作業があるでしょう。

  • 参加者のフォロー
    • ワークショップの内容はOSSの開発に参加している人にとっては基本的な内容なので技術的な難易度は低い
  • 会場準備
    • 会場を探したり当日の会場設営など
  • 宣伝
    • 参加者・協力者を増やす
    • イベントページを用意する

なお、協力する理由はなんだって構いません。たとえば次のような理由でも構いません。

  • サポートすることが好きだ
  • 採用につながるかも
  • 楽しそう
  • 自分のプロジェクトの開発に参加する人が増えるかも
  • 会社の宣伝になるかも
  • 友達が増えそう
  • なんとなくよさそう
  • ユーザーが自由に使えるソフトウェアが重要であると考える人が増えるといいな(クリアコードの場合)

OSSはざっくりいうとユーザーが実行・改造・再頒布できるライセンスを設定したソフトウェアのことです。それに対してどのようなメリットを見出すかは人それぞれです。OSSはそういうものです。そのため、OSS Gateも協力する理由をどのようなところに見出すかは人それぞれで構いません。

参考となりそうな取り組み

Rails Girlsという取り組みはOSS Gateの参考になりそうな取り組みです。たとえば、参加者がその後コーチとして支援する側にまわっていそうなところは非常に興味深いです。

まとめ

残念ながらSEゼミは継続できなくなりました。しかし、SEゼミで学んだことを活かしてOSSの開発に参加する人を増やす取り組みを始めることにしました。それがOSS Gateです。

OSS Gateという取り組みに協力してくれる方はOSS Gate を立ち上げようというイベントにお越しください。この取り組みをどうやって実現していくかを決めます。12月15日(火)の夜に開催します。

2015-10-20

Firefox用アドオンの署名義務化に伴う、一部アドオンのMozilla Add-ons掲載取りやめについて

概要

Firefox 44以降で予定されているアドオンの署名義務化に伴い、弊社にて開発・公開している一部のアドオンについてMozilla Add-onsサイト上への掲載を取りやめました。 以後は、それらのアドオンのインストール用XPIパッケージは各アドオンのGitHubリポジトリにて公開していきます。

この記事では、アドオンの掲載取りやめに至った経緯についてご説明します。

Firefoxアドオンの署名義務化

Firefoxにはアドオンで任意の機能を追加できる拡張性がありますが、現在、これを悪用する(ユーザの意図に反して既定の検索エンジンやスタートページを変更する、不要な広告を表示する、などの)マルウェアが増加しています。 そのため、MozillaはFirefox用アドオンについて、Mozillaのスタッフおよびボランティアの編集者がレビューした結果安全と判断されたアドオンのみに署名を付与してインストール可能とし、それ以外のアドオンを使用できなくするという方針への転換を進めています。 (現在はその過渡期として、未署名のアドオンに対しては警告が表示される状態となっています。)

Mozilla Add-ons(AMO)でのアドオンの掲載および公開にあたっては、セキュリティ上の重大な脅威が無ければとりあえずの掲載が許可される「事前審査」と、審査基準が厳しいながらも通過できればアドオンマネージャなどからの検索結果への表示を許可される「本審査」の、2段階の審査があります。 アドオンの署名に関する技術文書によると、この署名は事前審査をパスした段階で付与されるため、よほど危険性の高いアドオンでもない限りは今後も引き続き利用可能となります。

ただ、公開を前提としていないアドオン(例えば、社内利用専用のアドオンなど)では、本審査や事前審査以前の問題として、AMOのWebサイト上に掲載されること自体が問題となることがあります。 そのためMozillaはAMOにおいて、公開される(「listed」な)アドオンとは別に、非掲載の(「unlisted」な)アドオンというステータスを新たに設けており、公開を目的としないアドオンは非掲載の状態で事前審査または本審査を受けて、署名を施すよう案内しています。 (なお、非掲載であってもAMOに登録できない、例えば機密情報を含むために社外に出せないアドオンなどに対してどうするのかという事については、2015年10月現在でまだMozillaの方針が定まっていないようで、対応策は特に案内されていません。)

本審査の必要性

弊社が提供するアドオンは事前審査では法人向けサポートでの要件を満たす事ができず、「サイドローディング」のために本審査を受ける必要があります。

Firefox用のアドオンは通常、アドオンマネージャで検索してインストールしたり、ダウンロードしたXPIパッケージファイルをFirefoxのウィンドウにドラッグ&ドロップしたりと、何らかのユーザの操作を契機としてインストールされ、その際には必ずユーザの意志が関わります。

それに対して、Norton Internet Securityのようなアプリケーションをインストールした際にFirefoxに同時にアドオンが組み込まれる、というようなインストール手順があります。 先の技術文書ではこのようなインストール手順を「サイドローディング」と呼んで、通常のインストール手順とは区別しています。

サイドローディングの場合、アドオンは必ずしもユーザの意図に沿ってインストールされるとは限りません。 何か便利そうなアプリケーションをインストールしてみたら、それにバンドルされていたアドオンがFirefoxにインストールされて、知らず知らずのうちにスタートページが変更されてしまった、というような事が起こり得ます。 そのため、サイドローディングされるアドオンには通常以上に高い安全性が求められ、具体的には本審査をパスしたアドオンでなければサイドローディングはできない(事前審査しかパスしていないアドオンの場合、サイドローディングの形でインストールしても有効化できない)ようになっています。

弊社のサポートサービス内では「カスタマイズ済みのFirefoxを一斉展開したい」というご要望を頂く事が多く、カスタマイズ用のアドオンをFirefoxにバンドルしてインストールする場面が多発します。 これはサイドローディングに該当しますので、必然的に、バンドル対象の弊社提供のアドオンは本審査をパスしておく必要があるというわけです。

本審査をパスできないアドオン

この事を受けて、弊社提供のFirefox用アドオンのうちサイドローディングが必要な物について本審査を申請した結果、大部分のアドオンについては本審査をパスできたのですが、以下のアドオンについては本審査をパスできないという結果となりました。

Disable AddonsはFirefoxのアドオン機能を無効化する物ですが、インストールするとアドオンマネージャへのアクセスが禁じられ、元の状態に戻すにはDisable Addonsの実体となっているファイルを手動で削除する必要があります。 このようなアドオンがアドオンマネージャでの検索結果から簡単にインストールできては危険だというのが、審査棄却の理由であるとの事です。

Disable Auto-updateはFirefoxの自動アップデート機能を無効化する物ですが、これはセキュリティ上の脆弱性を抱えた古いバージョンのFirefoxの使用を奨励する事に繋がり、ユーザを危険に晒してしまう事になります。 このようなアドオンも、アドオンマネージャの検索結果から容易にインストールできてしまうのは望ましくないとのことでした。

Cert ImporterはTLS/SSLなどの証明書や、その妥当性の検証に使われるルート証明書を自動的にインポートする物です。 通常、Firefoxはあらかじめ組み込まれたルート証明書とそれによって検証される証明書のみを信頼し、それ以外の証明書は信頼できない物として拒絶します。 Cert Importerを使うと悪意ある攻撃者が任意の自己署名証明書をルート証明書として自動インポートさせられるため、TLS/SSLの仕組みを一切信頼できなくなってしまう、というのが審査棄却の理由であるとの事です。

Disable AddonsとDisable Auto-updateについては、「ユーザがうっかりインストールしてしまった場合の危険性が大きい」ということが審査棄却の大きな理由であったことから、AMOでの掲載を取りやめて非掲載(unlisted)のアドオンとして再登録を行った結果、本審査をパスすることができました。 よって、これらのアドオンは今後は、上記のGitHubのプロジェクトページの「Releases」リンクから辿る事ができるリリース一覧から署名済みのAPIパッケージをダウンロードしていただく事になります。

Cert Importerについては、その機能自体が危険との判断から非掲載の状態でも本審査を棄却されており、今後の対応については検討中です。

まとめ

Firefox 44以降でのアドオンの署名義務化の概要と、その影響として弊社提供の一部のアドオンの入手方法が変わった事についてご案内しました。

Cert Importerについては、現在の所Firefox 44以降の環境では一切利用できない状態となっています。 これについては進展があり次第、また改めてこの場で情報をご案内していく予定です。

タグ: Mozilla
2015-10-22

Firefoxのメモリ消費の状況を定期的に監視する

概要

アドオンを使ってFirefoxのメモリ消費の詳細な情報をモニタリングし、問題の原因を究明・分析する方法と、その事例をご紹介します。

about:memoryによるメモリ消費状況の分析

Firefoxの運用において、時間と共にメモリの消費量が増加し続けるという、いわゆるメモリリークの問題に遭遇する事があります。

こういった問題の原因調査にはFirefoxのメモリの使用状況の内訳を詳しく表示できる「about:memory」が有用です。 ロケーションバーに「about:memory」と入力してEnterし、ページを開いた状態で「Measure」というボタンを押すと、その瞬間のメモリの使用状況の内訳が分析され、結果がツリー表示されます。 この結果を見れば、どのタブやどのアドオンがメモリを大量に消費しているのかについて、あたりをつける手がかりを得る事ができます。

about:memoryが役に立たないケース

しかしながら、about:memoryはページを読み込んだその瞬間の情報をスナップショットとして表示するだけの物なので、状況によっては原因の分析にあまり役立たない場合もあります。

例えば、動画再生などの「一時的に大きなデータをメモリ上に読み込むが、メモリリークはしていない」という処理と、「1回ごとの処理のメモリ消費は小さいが、解放されずにどんどん消費が積み重なっていく」という処理とがある場合には、ある瞬間のスナップショットだけを見ても、どのメモリ消費がどちらの処理に由来するものなのかを判断するのは困難です。

また、普段の利用の中で急に問題が起こってメモリ不足に陥り、最悪の場合はクラッシュに至ってしまうというようなケースだと、「では、クラッシュする直前でのabout:memoryの結果を出力して分析してみよう」などという悠長な事は言っていられません。

ましてや、開発者自身の環境では問題が再現しないという場合には、技術的に詳しくないユーザに「これこれこういう方法で情報を収集して下さい」と伝えても、期待したような情報を得られるかは疑わしい所でしょう。

about:memoryの結果を自動的に保存する

このようなケースにおいて、メモリ消費が増大していく様子をモニタリングするためのアドオンとして、Periodic Memory Usage Dumperを作成しました。

このアドオンの導入後は、「3分以上アイドル状態が続いた場合に」「5分おきにabout:memoryの情報をダンプ出力してタイムスタンプ付きで保存する」ようになります。 ファイルの保存先や、アイドル状態と判定する待ち時間、アイドル状態における定期的な出力の間隔などは、設定から変更する事もできます。

PCの性能にもよりますが、メモリ消費状況の分析は数秒以上の時間を要する場合があるため、文字入力やWebブラウズの最中に処理が走るといわゆる「プチフリーズ」が発生したように見える事になります。 そのため、このアドオンはPCがユーザがPCを一定時間以上操作していないアイドル状態の時にのみ動作するようになっています。 調整次第では、休憩のため離席している時にのみ動作するというような設定も可能です。

ダンプファイルの読み方

Periodic Memory Usage Dumperが動作している環境では、ユーザープロファイル内の「memory-usage-dumps」フォルダ、または指定した任意のフォルダ内に「2015-10-20T05-10-12.123Z.json.gz」といった名前でダンプファイルが保存されます。 (設定ダイアログと実際に出力されたダンプファイルの様子) ファイル名に含まれる日付と時刻は現地時間ではなくUTCである事に注意して下さい。

ダンプファイルの内容は、about:memoryで閲覧する事ができます。 (about:memoryの画面) about:memoryを開いてページ左上部分の「Load...」ボタンをクリックするとファイル選択のダイアログが開かれますが、ここで先のダンプファイルを選択すると内容が読み込まれ、通常のabout:memoryでの分析結果と同じ要領で表示されます。

また、「Load and diff...」ボタンをクリックしてダンプファイルを2つ選択すると、その2つの間で変化があった部分の差分が表示されます。 これにより、以下のような分析を容易に行えます。

  • 常時大量のメモリを消費している部分を無視して、一定時間内でメモリ消費量が急増した部分を見付ける。
  • 3つ以上のダンプファイルを比較して、コンスタントにメモリ消費量が増加している(メモリリークしている)部分を見付ける。

なお、メモリ消費の情報はFirefoxの設計上で明示的に分析対象とされている部分についてのみ分析され、それ以外の部分で消費されているメモリの消費量は「heap-unclassified」という部分にまとめられることになります。 このような、どのモジュールで消費されているのかの詳細が分からない部分はダークマターと呼ばれ、Firefoxのバージョンが新しいほど、モジュールごとのメモリ消費量がより詳細に分析されるようになっていてダークマターは縮小する傾向にあります。 新しいバージョンのFirefoxでもメモリリークの問題が再現するようであれば、なるべく新しいFirefoxでダンプファイルを収集するのがお薦めです。

実際の障害対応の例

実際のお客様環境での事例として、Firefoxの使用中にPCの動作が異常に遅くなったり、離席状態から戻ったらFirefoxが強制終了されてしまっていたり、というトラブルに見舞われているケースがありました。

  • そこで、実際に問題が起こっている環境においてPeriodic Memory Usage Dumperで収集したダンプファイルを分析した結果、業務上の必要から常時表示しているWebページでのメモリ消費量が何らかのきっかけで線形増加するようになる状況が発生している事が分かりました。
  • PCの動作が遅くなるのはメモリ消費量が異常に増大した結果として仮想メモリのスワップが頻発するようになるせい、Firefoxが強制終了されるのはFirefoxのプロセスが使用できるメモリを使い尽くしてWindowsにより強制終了されるせいだと推測されました。
  • その事例では、問題のWebページを再読み込みすれば消費されていたメモリが一気に解放される事を確認できました。
  • また、Webページの設計上、不意に再読み込みされても利用に問題がない事も確認できました。

そこで、そのお客様向けには、ユーザが離席している間(アイドル状態の間)に当該Webページを開いているタブを強制的に再読み込みするという対策を取るため、Reload on Idleというアドオンを作成し提供する事にしました。

Reload on Idleは、以下の特徴を持っています。

  • 自動再読み込みの対象にするWebページを、URLの正規表現で指定できる。
  • 自動再読み込みを開始するまでのアイドル時間、および、アイドル時間中の自動再読み込みの間隔を指定できる。
  • Webページによっては、再読み込みの際に「このページから移動しますか? 入力したデータは保存されません。」という確認が表示され再読み込みの操作がブロックされる場合があるが、この確認をスキップして強制的に再読み込みする。

根本的な原因(メモリリークの原因となっているWebページ内に埋め込まれているスクリプトやFlashオブジェクトの問題)の解消とはなりませんでしたが、この対策の結果、お客様環境での問題の発生頻度が下がり、実用上問題ない状態になった模様です。

まとめ

メモリ消費量の詳細な情報を定期的に収集するアドオンPeriodic Memory Usage Dumperの使い方を紹介しました。

また、このアドオンを使用した障害対応の事例と、Reload on Idleを使った回避策についても紹介しました。

クリアコードではFirefoxやThunderbirdなどのMozilla製品の技術サポート事業として、障害の原因調査や対策のご提案を有償にて行っております。 また、その過程で必要が生じた場合には、アドオンなどのソフトウェアを随時開発して提供、および(可能な場合は)フリーソフトウェアとして公開しております。 FirefoxやThunderbirdの使用や利用でお困りの際は、お問い合わせの受け付けフォームよりご相談下さい。

タグ: Mozilla
2015-10-23

Firefoxの独自ビルドにおける、より高度なノーブランド化の手順

概要

Firefoxの独自ビルドの作成手順(2015年版)において、「Firefox」ブランドを使用しない「ノーブランド版Firefox」の作成方法を解説しました。 この記事ではその補足情報として、より完全なノーブランド化の手順と、その際の注意点を解説します。

ノーブランド化の必要性

まず最初におさらいとして、何故ノーブランド化の必要があるのか(どのような場合にノーブランド化しなくてはならないのか)を改めて説明します。

Mozilla FirefoxのソースコードはMPL2.0というフリーソフトウェアライセンスの元で公開されているため、誰でも利用や再配布が可能となっています。 しかし、「Firefox」という名前についてはMozilla Corporationが商標としての権利を保有しており、Mozilla自身が「これをFirefoxと呼ぶ」とお墨付きを与えたバイナリ以外の物に「Firefox」の名前を付けて頒布することはできません。

そのため、何らかの設定を変更したりアドオンを同梱したり、あるいは改造したりした物を第三者に対して頒布したい場合には、「Firefox」という商標を使用しない別の名前を付ける必要があります。 これが「ノーブランド化」です。

ノーブランド化には2つの段階があります。 先の記事で紹介した「見た目の名前の変更」と、この記事で新たに紹介する「アプリケーションの内部名まで含めた変更」です。

  • 「見た目の名前の変更」は、簡単に行えるのが利点ですが、Mozilla公式のFirefoxと併用する場合には様々な注意が必要となります。 企業内での標準ブラウザとしての使用のように、実行環境を管理者が100%決め打ちできる場合にはこの方法で十分な場合が多いと考えられます。
  • 「内部名まで含めた変更」は、コードの編集時に細心の注意が必要となりますが、公式のFirefoxとの併用時も含めて実際の利用時の注意はほとんど必要ないという利点があります。 広く一般向けに頒布する場合など、どのような環境で実行されるのかが事前に分からない場合にはこの方法の方が安全と考えられます。

この記事で、それぞれを順番に解説します。

「名前を変える」の具体的な方法

先の記事で解説している通り、「ノーブランド化」とは実際の所は「ソースコードの書き換え」です。 とはいえ、必要なファイルすべてをテキストエディタで開いて編集して……という事を繰り返していて手間がかかりすぎます。 可能な限り作業を自動化するために、「変更が必要なファイルをgrepで検索し、sedで内容を書き換える」という方法をとるのがお薦めです。

Mozilla Buildでこれを行う時には、2つの点に気をつける必要があります。

  • Mozilla BuildはMinGWを使ったUnix風のコンソールで操作を行いますが、MinGWに同梱されているsed-iオプションを使うと、ファイルのパーミッションが意図せず変更されてしまうという問題が起こります。 MinGW 上の sed による置換で permission denied を回避するにはで解説されている手順を参考に、あらかじめC:\mozilla-sourceおよびその配下のすべてのファイルについてUsersにフルコントロールの権限を与えておきましょう。
  • Gitではgit grepを使うことが多そうな場面ですが、Mercurialではhg grepとすると現在のリビジョンではなく履歴に対する検索となるため、リビジョンによっては検索漏れが出てしまいます。 なので、hg grepではなく通常のgrepの方を使いましょう。

見た目の名前を変える

まず、比較的手軽なノーブランド化手法柄である、見た目の名前の変更の方法です。 先の記事で解説しているのはこちらの方法ですが、復習も兼ねて改めて解説します。

ノーブランド化に際して、アプリケーションの長い名前(正式名)と短い名前(省略名)、ベンダ名とWebサイトのURLを以下の通り決めたと仮定します。

  • アプリケーションの長い名前(正式名):My Custom Browser
  • アプリケーションの短い名前(省略名):Browser
  • ベンダ名:My Company
  • ベンダのWebサイトのURL:http://mycompany.example.com/

様々な都合のため、短い名前には空白文字を含められません。 略語にする、頭字語にするなどして、空白文字を含まない十分に短い名前とするようにして下さい。

この時の一括置換の操作のコマンド列の例は、Bashスクリプトとして表すと以下のようになります。

base="/c/mozilla-source"
firefox_source="mozilla-esr38"
locale="ja"
name="My Custom Browser"
shortname="Browser"
vendorname="My Company"
vendorurl="http://mycompany.example.com/"

cd $base

grep -E -e "\b(Nightly|Firefox)\b|\"(Mozilla|mozilla\\.org|https?://((www|support)\\.mozilla\\.org|mozilla\\.jp)/?\"|(vendorShortName[^=]*=[^=]*)Mozilla" -r $locale/browser $locale/toolkit $firefox_source/browser/branding | \
  grep -v "Binary" | \
  cut -d ":" -f 1 | sort | uniq | \
  while read path; do sed -i -r -e "s/Mozilla Firefox/$name/g" \
                                -e "s/((MOZ_APP_DISPLAYNAME|Shorter).*)(Nightly|Firefox)/\1$shortname/g" \
                                -e "s/((Short|Full).*)(Nightly|Firefox)/\1$name/g" \
                                -e "s/\bFirefox\b/$shortname/g" \
                                -e "s/\bNightly\b/$name/g" \
                                -e "s/Mozilla $name/$name/g" \
                                -e "s/\"(Mozilla|mozilla\\.org)\"/\"$vendorname\"/g" \
                                -e "s;\"https?://((www|support)\\.mozilla\\.org|mozilla\\.jp)/?\";\"$vendorurl\";g" \
                                -e "s/(vendorShortName[^=]*=[^=]*)Mozilla/\1$vendorname/g" \
                        "$path"; done

「Mozilla Firefox」という長い名前や「Firefox」という短い名前が書かれている箇所の置換は容易ですが、「Nightly」と書かれている部分は、それが長い名前なのか短い名前なのかが場合によってまちまちです。 なので、ここでは確実に短い名前と断定できる部分(例えばMOZ_APP_DISPLAYNAMEbrandShorterNameの値など)は短い名前にして、それ以外は長い名前にしています。

上記コマンド列による一括置換の他に、about:rightsの補完アイコン等の画像の差し替えも場合に応じて行います。

  • 「about:rights」には、使用者自身の権利に関する情報、具体的にはプライバシーポリシーであったり、ノーブランド版のFirefoxと組み合わせて使用するWebサービスそのものの利用規約であったりが表示される事になっています。 この情報は初期状態ではプレースホルダ的な内容になっているため、自分で書き換える必要があります。

    about:rightsの内容を変えるには、toolkit/content/aboutRights-unbranded.xhtmlの位置にあるファイル(Firefox 38ESRでも同じ位置にあります)を直接書き換えるか、そこで参照しているエンティティを定義している言語リソースの項目を書き換えます。

  • 特定言語のビルドしか提供しないのであれば、aboutRights-unbranded.xhtmlだけを直接書き換えれば十分でしょう。 なお、文章に日本語などの非ASCII文字を含める場合は、ファイルの文字エンコーディングはUTF-8にする必要があります。

  • ブランドに関する画像はFirefox本体のリポジトリのbrowser/branding/nightly以下にある物が使われます。 独自のアイコンなどを使いたい場合は、これらの画像も差し替えておきます。

この方法でノーブランド化したビルドは、公式のFirefoxのバージョン(エディション)違いに相当する、言うなればリリース版Firefoxに対するベータ版やNightlyに相当する物となります。 実行時には自分で決めた名前で表示されますが、アプリケーションとしての内部名はFirefoxと同一なので、公式のFirefoxを同時にインストールしていると以下のような事が起こります。

  • Firefoxが動作している時にノーブランド版を起動しようとしても、ノーブランド版は起動せず、既に起動しているFirefoxで新しいタブが開かれるだけとなる。
    • その逆に、ノーブランド版が動作している時にFirefoxを起動しようとしても、公式のFirefoxは起動せず、既に起動しているノーブランド版で新しいタブが開かれるだけとなる。
  • ユーザープロファイルが公式のFirefoxと共用される。そのため、公式のFirefoxとノーブランド版のバージョンが一致していないと、お互いを起動する度に設定の移行処理が行われる(Firefoxをアップグレードまたはダウングレードしたのと同じ扱いになる)。

ですので、この状態のノーブランド版は以下のような制限を設けて運用する必要があります

  • ノーブランド版と公式のFirefoxを併用しない
  • ノーブランド版と公式のFirefoxを併用する場合は、ノーブランド版専用のプロファイルを使用する
    • ノーブランド版起動用のショートカットのリンク先には、"C:\Program Files (x86)\My Custom Browser\firefox.exe" --profile "%AppData%\MyCustomBrowser" -no-remoteという要領で、専用プロファイルのパスと-no-remoteオプションを明示する。
    • このショートカットからの多重起動(ウィンドウを見失った時などに、とりあえず起動用ショートカットをダブルクリックしたら、既に開かれていたウィンドウが最前面に出てくる、というような使い方)はできない。

アプリケーションの内部名を変える

上記のような制限事項は、表示名の変更によって作成されたノーブランド版が、内部的には公式のFirefoxのバージョン(エディション)違いに相当する物であるという事により生じています。

一方、OSに対して通知するプロセス名や実行ファイル名なども含めて、内部的なアプリケーション名を完全に変更すれば、このような制限がないノーブランド版を作る事ができます。 このようにして作成したノーブランド版は、Firefoxに対するThunderbirdやSeamonkeyのように、公式のFirefoxとは全く別のアプリケーションとして動作します。 そのため、公式のFirefoxとの併用時にも、色々な事に気をつける必要はありません。

とはいえ、Firefoxのソースコード中にある「Firefox」や「firefox」といった文字列を無差別に書き換えるのは得策ではありません。 文字列の置換は、PCの上で別アプリケーションとして動作させるために必要な部分に限定して行うのがお薦めです。 以下に、その対象を考える際の判断材料を列挙します。

  • Firefox 38ESRの場合、置換対象は以下のファイル群のみで十分です。
    • configure.in:ビルドの設定
    • browser/:ブラウザとしてのフロントエンドに関するコード
    • ipc/:プロセス分離に関するコード
  • Firefoxのリポジトリには上記以外にも「Thunderbirdなどと共通の基盤のコード」「Android版Firefox用のコード」「Firefox OS用のコード」なども含まれていますが、それらに手を加える必要はありません。
    • 共通の基盤のコードは、現在でも既にThunderbirdやSeamonkeyなどでそのまま使われています。 これはつまり、ノーブランド版を公式のFirefoxとは別アプリケーションとして動かす上でこれらの部分はそのまま使えるという事を意味します。
    • Android版やFirefox OS用のコードは、デスクトップアプリケーションであるノーブランド版Firefoxのビルドには関わってきませんので、無視して問題ありません。
  • 「アドオン用のアプリケーションID」(公式のFirefoxでは{ec8030f7-c20a-464f-9b0e-13a3a9e97384})は、変えない方が良いでしょう。 というのも、このIDはアドオンが対応アプリケーション(Firefox、Thunderbird、Seamonkeyなど)を識別するために使われるため、IDを変えてしまうと、既存のFirefox用アドオンが全く使えなくなってしまうからです。
  • 実行ファイル名のfirefox.exe以外の部分における小文字のfirefoxは、極力変更しない方が良いでしょう。 小文字のfirefoxはファイル名のパスに多用されているため、不用意に変更すると、あるはずのファイルが見つからないという問題が起こり得ます。
  • "Mozilla""Firefox""Mozilla Firefox"のように大文字小文字交じりのリテラルとして定義されている物は、置換しても安全である場合が多いです。 これらはソースコード中で定数として定義されている事が多いため、定義している箇所を書き換えれば他の部分もそれに自動的に追従する結果となります。
  • レジストリキーに関する項目も、置換しておくことで公式のFirefoxとの衝突の可能性が低減されます。

以上を踏まえた、Firefoxのソースコード中に埋め込まれた文字列を置換して内部名から別アプリケーション化するコマンド列の例をシェルスクリプト形式(※前項のスクリプトの続きとして読んで下さい)で示すと、以下のようになります。

shortname_lc=$(echo $shortname | tr A-Z a-z)

cd $base

grep -E -e "firefox\.exe|\"(Firefox|Mozilla|Mozilla Firefox)\"|(Software\\\\?)?Mozilla\\\\?(Firefox)?" -r $firefox_source/browser $firefox_source/build $firefox_source/config $firefox_source/configure.in $firefox_source/ipc | \
  grep -v "Binary" | \
  cut -d ":" -f 1 | sort | uniq | \
  grep -v "test" | \
  while read path; do sed -i -r -e "s/firefox\.exe/$shortname_lc.exe/g" \
                                -e "s/\"Firefox\"/\"$shortname\"/g" \
                                -e "s/\"Mozilla\"/\"$vendorname\"/g" \
                                -e "s/\"Mozilla Firefox\"/\"$name\"/g" \
                                -e "s/(Software\\\\?)?Mozilla(\\\\?)Firefox/\1$vendorname\2$shortname/g" \
                                -e "s/(Software\\\\?)Mozilla/\1$vendorname/g" \
                        "$path"; done

for shortname_file in "$firefox_source/browser/confvars.sh" \
                      "$firefox_source/browser/installer/windows/nsis/defines.nsi.in" \
                      "$firefox_source/browser/metro/metroapp.ini.in" \
                      "$firefox_source/ipc/app/plugin-container.exe.manifest"
do
  sed -i -r -e "s/Firefox/$shortname/g" \
            -e "s/=Mozilla$/=\"$vendorname\"/g" \
    "$shortname_file"
done

for shortname_lc_file in "$firefox_source/browser/app/macbuild/Contents/Info.plist.in" \
                         "$firefox_source/browser/app/macbuild/Contents/MacOS-files.in" \
                         "$firefox_source/browser/confvars.sh"
do
  sed -i -r -e "s/firefox/$shortname_lc/g" "$shortname_lc_file"
done

sed -r -e "s/>Firefox</>$shortname</g" \
       -e "s/Firefox/$name/g" \
  $firefox_source/browser/app/firefox.exe.manifest \
  > $firefox_source/browser/app/$shortname_lc.exe.manifest

sed -i -r -e "s/Firefox Launcher/$name Launcher/g" \
          -e "s/Firefox/$shortname/g" \
  $firefox_source/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.exe.manifest

これらの置換操作を行った結果のソースからビルドしたノーブランド版は、以下の挙動になる事を確認できています。

  • 公式のFirefoxと並行起動できる。
  • 既定のユーザープロファイルは %AppData%\ベンダ名\Profiles\アプリケーション名\xxxx.default に作成される。
  • その環境にインストール済みのFirefox用NPAPIプラグイン(Adobe Flash、Javaなど)をそのまま認識する。
    • HKEY_LOCAL_MACHINE\Software\Wow6432Node\MozillaPlugins経由での登録以外の方法で認識されるNPAPIプラグインについては個別の対応が必要。
  • Firefox用のアドオンをインストールできる。
  • レジストリキーが公式のFirefoxとは別に管理されており、FirefoxやThunderbirdのレジストリと混ざらない。
    • 各種の情報は HKEY_LOCAL_MACHINE\Software\Wow6432Node\ベンダ名 以下に保存される
  • 自動アップデートは行われない。
    • Mozilla公式のサーバにアップデート情報を問い合わるが、「そのような名前のアプリケーションのアップデート情報は存在しない」という結果になる。
    • 自動アップデートを行いたい場合、自分で自動アップデート用の情報を提供し、且つ、アップデート情報の問い合わせ先を自分のサーバに変更する必要がある。
  • Firefox Syncを使うと、各種データが公式のFirefoxと相互に同期される。

繰り返しになりますが、Bashスクリプトの形で示した上記の置換操作の例は、あくまでFirefox 38ESRのソースを対象にした物です。 他のバージョンのFirefoxを対象にする場合、置換のし残しが発生したり、置換してはいけない部分まで置換してしまったりして、そのままでは期待通りの結果を得られない可能性があります。 未知のバージョンを相手にする際は、前述の「判断材料」に基づいて慎重に置換対象を見極める必要がありますので、くれぐれも注意して下さい。

まとめ

内部的なアプリケーション名の変更まで含めた、Firefoxのより高度なノーブランド化の方法を解説しました。

Firefoxの動作に影響を与えるソースコードの書き換えは、失敗するとビルドや起動ができなくなってしまう事もあります。 そのような時は、Mercurialでアレを元に戻す108の方法 - TIM Labsなどを参考にして、変更を取り消してやり直すとよいでしょう。 筆者も、この記事を書くにあたってhg update -Cを多用しました。

ソースコードのスナップショットをダウンロードして使うのではなく、Mercurialのリポジトリをcloneして使う事には、こういう利点もあります。 バージョン管理システムを使った事がないという人も、これを機にGitやMercurialを試してみてはいかがでしょうか。

タグ: Mozilla
2015-10-26

WerckerのDockerベーススタックへの移行

クリアコードではHatoholというソフトウェアの開発に参加しています。Hatoholは複数の統合監視システムの情報を一括して表示することを可能とするソフトウェアです。現在対応している統合監視システムはZabbix、Nagios及びOpen StackのCeilometerです。他の監視システムに対応することも検討しています。

HatoholはServerとDjangoのWebApp、またユーザーのブラウザで動くJavaScript部分の3つで構成されています。

前回、Hatohol serverの CentOS向けのCIでDockerfileを用い、Werckerで行う方法 - ククログ(2015-07-01)を紹介しました。

その後、WerckerのビルドスタックがDockerベースのものに切り替わったため、移行作業を行いました。

Werckerとは

WerckerとはTravis CIやCircle CIと並ぶCIサービスです。特に、ビルドスタックが使うコンテナを自由にユーザーが用意する事ができ、自由度が高いのが特徴です。

今回WerckerがDockerベースのバックエンドを公開したので、HatoholのCentOS 6系向けCIをこの新しいビルドスタックで行うように移行作業を行いました。

Dockerベースのビルドスタックを使用する

Werckerはビルド時の設定をwercker.ymlで行います。box名のDockerイメージがあるかどうかをDockerHubに問い合わせるようになっています。

box: centos:6.7

例えば、上記のbox名がwercker.ymlに指定されていた場合、DockerHubにcentos:6.7のコンテナがあるかどうか問い合わせに行きます。

boxへDockerHubのイメージが指定出来るようになったことにより、Wercker専用のboxを作らずともDockerHubに登録されているDockerイメージを自由に選択することが出来るようになりました。

Dockerベースのビルドスタックへの移行

それでは本題です。Dockerベースのビルドスタックに移行した場合は、wercker-labs/docker ではDockerfileを直接使っていましたが、Dockerベースのビルドスタックではwercker.ymlにスクリプトとしてビルド手順を記載する必要があります。

移行前

移行前のwercker.ymlとDockerfileは以下のようになっていました。

box: wercker-labs/docker
build:
  steps:
    - script:
        name: Build a CentOS 6.7 Docker Hatohol image box
        code: |
          docker -v
          echo ${WERCKER_GIT_COMMIT} > wercker/GIT_REVISION
          docker build -t centos67/build-hatohol wercker/
from centos:6.7

maintainer hatohol project

# install libraries for Hatohol
RUN yum install -y glib2-devel libsoup-devel sqlite-devel mysql-devel mysql-server \
  libuuid-devel qpid-cpp-client-devel MySQL-python httpd mod_wsgi python-argparse
# setup mysql
RUN echo "NETWORKING=yes" > /etc/sysconfig/network
RUN rpm -ivh --force http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
RUN yum install -y librabbitmq-devel wget
RUN wget -P /etc/yum.repos.d/ http://project-hatohol.github.io/repo/hatohol-el6.repo
# setup qpid
RUN yum install -y json-glib-devel qpid-cpp-server-devel
# setup build environment
RUN yum groupinstall -y 'Development tools'
# setup newer gcc toolchain
RUN cd /etc/yum.repos.d && wget http://people.centos.org/tru/devtools-2/devtools-2.repo
RUN yum install -y devtoolset-2-gcc devtoolset-2-binutils devtoolset-2-gcc-c++
RUN yum install -y Django
RUN rpm -Uvh http://sourceforge.net/projects/cutter/files/centos/cutter-release-1.1.0-0.noarch.rpm
RUN yum install -y cutter tar
# git clone
RUN git clone https://github.com/project-hatohol/hatohol.git ~/hatohol
# set CI terget to WERCKER_GIT_COMMIT
ADD ./GIT_REVISION /root/hatohol/wercker/
# change work dir
WORKDIR /root/hatohol
# checkout target branch
RUN git fetch origin
RUN git checkout -qf `cat ~/hatohol/wercker/GIT_REVISION`
# build
RUN libtoolize && autoreconf -i
RUN scl enable devtoolset-2 "./configure"
RUN scl enable devtoolset-2 "make -j `cat /proc/cpuinfo | grep processor | wc -l`"
RUN make dist -j `cat /proc/cpuinfo | grep processor | wc -l`
# rpm build
RUN yum install -y rpm-build
RUN scl enable devtoolset-2 "MAKEFLAGS=\"-j `cat /proc/cpuinfo | grep processor | wc -l`\" rpmbuild -tb hatohol-*.tar.bz2"
RUN scl enable devtoolset-2 "gcc --version"

また、Dockerfileとwercker.ymlの中で環境が異なるため、Werckerの環境変数(WERCKER_GIT_COMMIT)を用いてGitのコミットハッシュを指定したい場合、一旦ファイルに書き込んでからdocker build時に必要になる前にADDしてやる必要がありました。

移行後

移行後のwercker.ymlは次のようになりました。

box: centos:6.7
build:
  steps:
    - script:
        name: Install libraries for Hatohol
        code: |
          yum install -y glib2-devel libsoup-devel sqlite-devel mysql-devel mysql-server libuuid-devel qpid-cpp-client-devel MySQL-python httpd mod_wsgi python-argparse
    - script:
        name: Setup network
        code: |
          echo "NETWORKING=yes" > /etc/sysconfig/network

    - script:
        name: Setup yum repositories
        code: |
          rpm -ivh --force http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
          yum install -y librabbitmq-devel wget
          wget -P /etc/yum.repos.d/ http://project-hatohol.github.io/repo/hatohol-el6.repo

    - script:
        name: Setup Qpid
        code: |
          yum install -y json-glib-devel qpid-cpp-server-devel

    - script:
        name: Setup build enviroment
        code: |
          yum groupinstall -y 'Development tools'

    - script:
        name: Setup newer gcc toolchain and libraries
        code: |
          cd /etc/yum.repos.d && wget http://people.centos.org/tru/devtools-2/devtools-2.repo
          yum install -y devtoolset-2-gcc devtoolset-2-binutils devtoolset-2-gcc-c++
          yum install -y Django
          rpm -Uvh http://sourceforge.net/projects/cutter/files/centos/cutter-release-1.1.0-0.noarch.rpm
          yum install -y cutter tar

    - script:
        name: Clone & fetch Hatohol repository
        code: |
          git clone https://github.com/project-hatohol/hatohol.git ~/hatohol
          cd ~/hatohol
          git fetch origin
          # HEAD is used for local `wercker` CLI command build
          git checkout -qf ${WERCKER_GIT_COMMIT:-HEAD}

    - script:
        name: Building Hatohol
        code: |
          cd ~/hatohol
          libtoolize && autoreconf -i
          scl enable devtoolset-2 "./configure"
          scl enable devtoolset-2 "make -j `cat /proc/cpuinfo | grep processor | wc -l`"
          make dist -j `cat /proc/cpuinfo | grep processor | wc -l`

    - script:
        name: Build RPM
        code: |
          yum install -y rpm-build
          cd ~/hatohol
          scl enable devtoolset-2 "MAKEFLAGS=\"-j `cat /proc/cpuinfo | grep processor | wc -l`\" rpmbuild -tb hatohol-*.tar.bz2"
          scl enable devtoolset-2 "gcc --version"

比べてみると、Dockerfile特有の記述ではなく、一般的なシェルスクリプトでビルド手順が記述出来るようになっていることが分かります。また、複数のステップに分割出来るようになったため、どのステップで失敗したかが分かりやすくなりました。

更に、wercker.ymlとシェルスクリプトの知識だけで完結するようになったため、Dockerを知らない人が見てもbox名の指定の注意点さえ抑えればメンテナンスが出来るようになりました。

この移行作業により、Werckerでのビルド時間が15分台から10分台に短縮されました。wercker-labs/dockerを用いてdockerを動かしている時にはWerckerの中でCI環境を構築している時に失敗していた現象が発生しなくなりました。

まとめ

WerckerでDockerベースのビルドスタックに移行し、DockerHubからCentOSのDockerイメージを取ってきてCentOS向けのCIを行う事例を紹介しました。

新しいWerckerのビルドスタックではより手軽にDockerイメージをCIに用いることが出来るようになりました。また、ビルドに用いる環境変数をステップに組み込むことも容易になりました。

これを機に新しいWerckerのDockerベースのビルドスタックでのCIに挑戦してみるのはいかがでしょうか。

2015-10-28

«前月 最新記事 翌月»
タグ:
年・日ごとに見る
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|